diff options
111 files changed, 3809 insertions, 773 deletions
diff --git a/apct-tests/perftests/core/OWNERS b/apct-tests/perftests/core/OWNERS new file mode 100644 index 000000000000..18486af9d12c --- /dev/null +++ b/apct-tests/perftests/core/OWNERS @@ -0,0 +1 @@ +include /graphics/java/android/graphics/fonts/OWNERS diff --git a/apex/OWNERS b/apex/OWNERS index bde2bec0816b..b3e81b925ddc 100644 --- a/apex/OWNERS +++ b/apex/OWNERS @@ -1,8 +1 @@ -# Mainline modularization team - -andreionea@google.com -dariofreni@google.com -hansson@google.com -mathewi@google.com -pedroql@google.com -satayev@google.com +file:platform/packages/modules/common:/OWNERS diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java index bb94275fc409..15052f8b12a4 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java @@ -16,7 +16,6 @@ package com.android.server.job.controllers; -import static android.net.NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; @@ -325,7 +324,7 @@ public final class ConnectivityController extends RestrictingController implemen if (downloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) { final long bandwidth = capabilities.getLinkDownstreamBandwidthKbps(); // If we don't know the bandwidth, all we can do is hope the job finishes in time. - if (bandwidth != LINK_BANDWIDTH_UNSPECIFIED) { + if (bandwidth > 0) { // Divide by 8 to convert bits to bytes. final long estimatedMillis = ((downloadBytes * DateUtils.SECOND_IN_MILLIS) / (DataUnit.KIBIBYTES.toBytes(bandwidth) / 8)); @@ -343,7 +342,7 @@ public final class ConnectivityController extends RestrictingController implemen if (uploadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) { final long bandwidth = capabilities.getLinkUpstreamBandwidthKbps(); // If we don't know the bandwidth, all we can do is hope the job finishes in time. - if (bandwidth != LINK_BANDWIDTH_UNSPECIFIED) { + if (bandwidth > 0) { // Divide by 8 to convert bits to bytes. final long estimatedMillis = ((uploadBytes * DateUtils.SECOND_IN_MILLIS) / (DataUnit.KIBIBYTES.toBytes(bandwidth) / 8)); @@ -373,18 +372,16 @@ public final class ConnectivityController extends RestrictingController implemen private static boolean isStrictSatisfied(JobStatus jobStatus, Network network, NetworkCapabilities capabilities, Constants constants) { - final NetworkCapabilities required; // A restricted job that's out of quota MUST use an unmetered network. if (jobStatus.getEffectiveStandbyBucket() == RESTRICTED_INDEX && !jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)) { - required = new NetworkCapabilities( + final NetworkCapabilities required = new NetworkCapabilities.Builder( jobStatus.getJob().getRequiredNetwork().networkCapabilities) - .addCapability(NET_CAPABILITY_NOT_METERED); + .addCapability(NET_CAPABILITY_NOT_METERED).build(); + return required.satisfiedByNetworkCapabilities(capabilities); } else { - required = jobStatus.getJob().getRequiredNetwork().networkCapabilities; + return jobStatus.getJob().getRequiredNetwork().canBeSatisfiedBy(capabilities); } - - return required.satisfiedByNetworkCapabilities(capabilities); } private static boolean isRelaxedSatisfied(JobStatus jobStatus, Network network, @@ -395,9 +392,9 @@ public final class ConnectivityController extends RestrictingController implemen } // See if we match after relaxing any unmetered request - final NetworkCapabilities relaxed = new NetworkCapabilities( + final NetworkCapabilities relaxed = new NetworkCapabilities.Builder( jobStatus.getJob().getRequiredNetwork().networkCapabilities) - .removeCapability(NET_CAPABILITY_NOT_METERED); + .removeCapability(NET_CAPABILITY_NOT_METERED).build(); if (relaxed.satisfiedByNetworkCapabilities(capabilities)) { // TODO: treat this as "maybe" response; need to check quotas return jobStatus.getFractionRunTime() > constants.CONN_PREFETCH_RELAX_FRAC; diff --git a/core/api/current.txt b/core/api/current.txt index f453ccaf75ad..976ec5608d5a 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -9140,7 +9140,7 @@ package android.bluetooth.le { method public boolean getIncludeTxPowerLevel(); method public android.util.SparseArray<byte[]> getManufacturerSpecificData(); method public java.util.Map<android.os.ParcelUuid,byte[]> getServiceData(); - method @Nullable public java.util.List<android.os.ParcelUuid> getServiceSolicitationUuids(); + method @NonNull public java.util.List<android.os.ParcelUuid> getServiceSolicitationUuids(); method public java.util.List<android.os.ParcelUuid> getServiceUuids(); method public void writeToParcel(android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.le.AdvertiseData> CREATOR; @@ -12134,6 +12134,8 @@ package android.content.pm { field public static final String FEATURE_INPUT_METHODS = "android.software.input_methods"; field public static final String FEATURE_IPSEC_TUNNELS = "android.software.ipsec_tunnels"; field public static final String FEATURE_IRIS = "android.hardware.biometrics.iris"; + field public static final String FEATURE_KEYSTORE_LIMITED_USE_KEY = "android.hardware.keystore.limited_use_key"; + field public static final String FEATURE_KEYSTORE_SINGLE_USE_KEY = "android.hardware.keystore.single_use_key"; field public static final String FEATURE_LEANBACK = "android.software.leanback"; field public static final String FEATURE_LEANBACK_ONLY = "android.software.leanback_only"; field public static final String FEATURE_LIVE_TV = "android.software.live_tv"; @@ -34323,6 +34325,55 @@ package android.provider { field public static final String PATH_SETTING_INTENT = "intent"; } + public final class SimPhonebookContract { + field public static final String AUTHORITY = "com.android.simphonebook"; + field @NonNull public static final android.net.Uri AUTHORITY_URI; + } + + public static final class SimPhonebookContract.ElementaryFiles { + method @NonNull public static android.net.Uri getItemUri(int, int); + field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/sim-elementary-file"; + field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/sim-elementary-file"; + field @NonNull public static final android.net.Uri CONTENT_URI; + field public static final int EF_ADN = 1; // 0x1 + field public static final int EF_FDN = 2; // 0x2 + field public static final int EF_SDN = 3; // 0x3 + field public static final String EF_TYPE = "ef_type"; + field public static final int EF_UNKNOWN = 0; // 0x0 + field public static final String MAX_RECORDS = "max_records"; + field public static final String NAME_MAX_LENGTH = "name_max_length"; + field public static final String PHONE_NUMBER_MAX_LENGTH = "phone_number_max_length"; + field public static final String RECORD_COUNT = "record_count"; + field public static final String SLOT_INDEX = "slot_index"; + field public static final String SUBSCRIPTION_ID = "subscription_id"; + } + + public static final class SimPhonebookContract.SimRecords { + method @NonNull public static android.net.Uri getContentUri(int, int); + method @NonNull public static android.net.Uri getItemUri(int, int, int); + method @NonNull @WorkerThread public static android.provider.SimPhonebookContract.SimRecords.NameValidationResult validateName(@NonNull android.content.ContentResolver, int, int, @NonNull String); + field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/sim-contact_v2"; + field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/sim-contact_v2"; + field public static final String ELEMENTARY_FILE_TYPE = "elementary_file_type"; + field public static final String NAME = "name"; + field public static final String PHONE_NUMBER = "phone_number"; + field public static final String RECORD_NUMBER = "record_number"; + field public static final String SUBSCRIPTION_ID = "subscription_id"; + } + + public static final class SimPhonebookContract.SimRecords.NameValidationResult implements android.os.Parcelable { + ctor public SimPhonebookContract.SimRecords.NameValidationResult(@NonNull String, @NonNull String, int, int); + method public int describeContents(); + method public int getEncodedLength(); + method public int getMaxEncodedLength(); + method @NonNull public String getName(); + method @NonNull public String getSanitizedName(); + method public boolean isSupportedCharacter(int); + method public boolean isValid(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.provider.SimPhonebookContract.SimRecords.NameValidationResult> CREATOR; + } + public class SyncStateContract { ctor public SyncStateContract(); } @@ -36205,6 +36256,7 @@ package android.security.keystore { method @Nullable public java.util.Date getKeyValidityForOriginationEnd(); method @Nullable public java.util.Date getKeyValidityStart(); method @NonNull public String getKeystoreAlias(); + method public int getMaxUsageCount(); method public int getPurposes(); method @NonNull public String[] getSignaturePaddings(); method public int getUserAuthenticationType(); @@ -36241,6 +36293,7 @@ package android.security.keystore { method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityForConsumptionEnd(java.util.Date); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityForOriginationEnd(java.util.Date); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityStart(java.util.Date); + method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setMaxUsageCount(int); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setRandomizedEncryptionRequired(boolean); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setSignaturePaddings(java.lang.String...); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUnlockedDeviceRequired(boolean); @@ -36263,6 +36316,7 @@ package android.security.keystore { method public String getKeystoreAlias(); method public int getOrigin(); method public int getPurposes(); + method public int getRemainingUsageCount(); method public int getSecurityLevel(); method @NonNull public String[] getSignaturePaddings(); method public int getUserAuthenticationType(); @@ -36332,6 +36386,7 @@ package android.security.keystore { field public static final int SECURITY_LEVEL_UNKNOWN_SECURE = -1; // 0xffffffff field public static final String SIGNATURE_PADDING_RSA_PKCS1 = "PKCS1"; field public static final String SIGNATURE_PADDING_RSA_PSS = "PSS"; + field public static final int UNRESTRICTED_USAGE_COUNT = -1; // 0xffffffff } public final class KeyProtection implements java.security.KeyStore.ProtectionParameter { @@ -36341,6 +36396,7 @@ package android.security.keystore { method @Nullable public java.util.Date getKeyValidityForConsumptionEnd(); method @Nullable public java.util.Date getKeyValidityForOriginationEnd(); method @Nullable public java.util.Date getKeyValidityStart(); + method public int getMaxUsageCount(); method public int getPurposes(); method @NonNull public String[] getSignaturePaddings(); method public int getUserAuthenticationType(); @@ -36366,6 +36422,7 @@ package android.security.keystore { method @NonNull public android.security.keystore.KeyProtection.Builder setKeyValidityForConsumptionEnd(java.util.Date); method @NonNull public android.security.keystore.KeyProtection.Builder setKeyValidityForOriginationEnd(java.util.Date); method @NonNull public android.security.keystore.KeyProtection.Builder setKeyValidityStart(java.util.Date); + method @NonNull public android.security.keystore.KeyProtection.Builder setMaxUsageCount(int); method @NonNull public android.security.keystore.KeyProtection.Builder setRandomizedEncryptionRequired(boolean); method @NonNull public android.security.keystore.KeyProtection.Builder setSignaturePaddings(java.lang.String...); method @NonNull public android.security.keystore.KeyProtection.Builder setUnlockedDeviceRequired(boolean); @@ -39679,6 +39736,7 @@ package android.telephony { field public static final String KEY_ENABLE_PRESENCE_GROUP_SUBSCRIBE_BOOL = "ims.enable_presence_group_subscribe_bool"; field public static final String KEY_ENABLE_PRESENCE_PUBLISH_BOOL = "ims.enable_presence_publish_bool"; field public static final String KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL = "ims.ims_single_registration_required_bool"; + field public static final String KEY_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC_INT = "ims.non_rcs_capabilities_cache_expiration_sec_int"; field public static final String KEY_PREFIX = "ims."; field public static final String KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL = "ims.rcs_bulk_capability_exchange_bool"; field public static final String KEY_WIFI_OFF_DEFERRING_TIME_MILLIS_INT = "ims.wifi_off_deferring_time_millis_int"; @@ -41155,7 +41213,9 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void removeSubscriptionsFromGroup(@NonNull java.util.List<java.lang.Integer>, @NonNull android.os.ParcelUuid); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunistic(boolean, int); method public void setSubscriptionOverrideCongested(int, boolean, long); + method public void setSubscriptionOverrideCongested(int, boolean, @NonNull int[], long); method public void setSubscriptionOverrideUnmetered(int, boolean, long); + method public void setSubscriptionOverrideUnmetered(int, boolean, @NonNull int[], long); method public void setSubscriptionPlans(int, @NonNull java.util.List<android.telephony.SubscriptionPlan>); method @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void switchToSubscription(int, @NonNull android.app.PendingIntent); field public static final String ACTION_DEFAULT_SMS_SUBSCRIPTION_CHANGED = "android.telephony.action.DEFAULT_SMS_SUBSCRIPTION_CHANGED"; diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 72fcc72169c3..664b9e515d84 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -2131,6 +2131,7 @@ package android.content.pm { field public static final String FEATURE_INCREMENTAL_DELIVERY = "android.software.incremental_delivery"; field public static final String FEATURE_REBOOT_ESCROW = "android.hardware.reboot_escrow"; field public static final String FEATURE_TELEPHONY_CARRIERLOCK = "android.hardware.telephony.carrierlock"; + field public static final String FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION = "android.hardware.telephony.ims.singlereg"; field public static final int FLAGS_PERMISSION_RESERVED_PERMISSION_CONTROLLER = -268435456; // 0xf0000000 field public static final int FLAG_PERMISSION_APPLY_RESTRICTION = 16384; // 0x4000 field public static final int FLAG_PERMISSION_AUTO_REVOKED = 131072; // 0x20000 @@ -8289,6 +8290,24 @@ package android.provider { method @RequiresPermission(android.Manifest.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE) public static boolean putString(@NonNull android.content.ContentResolver, @NonNull String, @Nullable String, boolean); } + public final class SimPhonebookContract { + method @NonNull public static String getEfUriPath(int); + field public static final String SUBSCRIPTION_ID_PATH_SEGMENT = "subid"; + } + + public static final class SimPhonebookContract.ElementaryFiles { + field public static final String EF_ADN_PATH_SEGMENT = "adn"; + field public static final String EF_FDN_PATH_SEGMENT = "fdn"; + field public static final String EF_SDN_PATH_SEGMENT = "sdn"; + field public static final String ELEMENTARY_FILES_PATH_SEGMENT = "elementary_files"; + } + + public static final class SimPhonebookContract.SimRecords { + field public static final String EXTRA_NAME_VALIDATION_RESULT = "android.provider.extra.NAME_VALIDATION_RESULT"; + field public static final String QUERY_ARG_PIN2 = "android:query-arg-pin2"; + field public static final String VALIDATE_NAME_PATH_SEGMENT = "validate_name"; + } + public static final class Telephony.Carriers implements android.provider.BaseColumns { field public static final String APN_SET_ID = "apn_set_id"; field public static final int CARRIER_EDITED = 4; // 0x4 @@ -12375,11 +12394,13 @@ package android.telephony.ims.stub { public static interface RcsCapabilityExchangeImplBase.PublishResponseCallback { method public void onCommandError(int) throws android.telephony.ims.ImsException; method public void onNetworkResponse(@IntRange(from=100, to=699) int, @NonNull String) throws android.telephony.ims.ImsException; + method public void onNetworkResponse(@IntRange(from=100, to=699) int, @NonNull String, @IntRange(from=100, to=699) int, @NonNull String) throws android.telephony.ims.ImsException; } public static interface RcsCapabilityExchangeImplBase.SubscribeResponseCallback { method public void onCommandError(int) throws android.telephony.ims.ImsException; method public void onNetworkResponse(@IntRange(from=100, to=699) int, @NonNull String) throws android.telephony.ims.ImsException; + method public void onNetworkResponse(@IntRange(from=100, to=699) int, @NonNull String, @IntRange(from=100, to=699) int, @NonNull String) throws android.telephony.ims.ImsException; method public void onNotifyCapabilitiesUpdate(@NonNull java.util.List<java.lang.String>) throws android.telephony.ims.ImsException; method public void onResourceTerminated(@NonNull java.util.List<android.util.Pair<android.net.Uri,java.lang.String>>) throws android.telephony.ims.ImsException; method public void onTerminated(@NonNull String, long) throws android.telephony.ims.ImsException; diff --git a/core/java/android/bluetooth/le/AdvertiseData.java b/core/java/android/bluetooth/le/AdvertiseData.java index 573b93232642..fa7ac2b27c92 100644 --- a/core/java/android/bluetooth/le/AdvertiseData.java +++ b/core/java/android/bluetooth/le/AdvertiseData.java @@ -44,7 +44,7 @@ public final class AdvertiseData implements Parcelable { @Nullable private final List<ParcelUuid> mServiceUuids; - @Nullable + @NonNull private final List<ParcelUuid> mServiceSolicitationUuids; private final SparseArray<byte[]> mManufacturerSpecificData; @@ -77,7 +77,7 @@ public final class AdvertiseData implements Parcelable { /** * Returns a list of service solicitation UUIDs within the advertisement that we invite to connect. */ - @Nullable + @NonNull public List<ParcelUuid> getServiceSolicitationUuids() { return mServiceSolicitationUuids; } @@ -221,7 +221,7 @@ public final class AdvertiseData implements Parcelable { public static final class Builder { @Nullable private List<ParcelUuid> mServiceUuids = new ArrayList<ParcelUuid>(); - @Nullable + @NonNull private List<ParcelUuid> mServiceSolicitationUuids = new ArrayList<ParcelUuid>(); private SparseArray<byte[]> mManufacturerSpecificData = new SparseArray<byte[]>(); private Map<ParcelUuid, byte[]> mServiceData = new ArrayMap<ParcelUuid, byte[]>(); diff --git a/core/java/android/content/AutofillOptions.java b/core/java/android/content/AutofillOptions.java index 80a7b16ee761..0581ed5cbf22 100644 --- a/core/java/android/content/AutofillOptions.java +++ b/core/java/android/content/AutofillOptions.java @@ -17,6 +17,7 @@ package android.content; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.TestApi; import android.app.ActivityThread; import android.os.Parcel; @@ -62,6 +63,7 @@ public final class AutofillOptions implements Parcelable { * List of allowlisted activities. */ @Nullable + @SuppressLint("NullableCollection") public ArraySet<ComponentName> whitelistedActivitiesForAugmentedAutofill; /** @@ -73,6 +75,7 @@ public final class AutofillOptions implements Parcelable { * The disabled Activities of the package. key is component name string, value is when they * will be enabled. */ + @SuppressLint("NullableCollection") @Nullable public ArrayMap<String, Long> disabledActivities; diff --git a/core/java/android/content/ContentCaptureOptions.java b/core/java/android/content/ContentCaptureOptions.java index ef49e029db13..c296bb52e73d 100644 --- a/core/java/android/content/ContentCaptureOptions.java +++ b/core/java/android/content/ContentCaptureOptions.java @@ -17,6 +17,7 @@ package android.content; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.TestApi; import android.app.ActivityThread; import android.os.Parcel; @@ -73,6 +74,7 @@ public final class ContentCaptureOptions implements Parcelable { * for all acitivites in the package). */ @Nullable + @SuppressLint("NullableCollection") public final ArraySet<ComponentName> whitelistedComponents; /** @@ -96,6 +98,7 @@ public final class ContentCaptureOptions implements Parcelable { */ public ContentCaptureOptions(int loggingLevel, int maxBufferSize, int idleFlushingFrequencyMs, int textChangeFlushingFrequencyMs, int logHistorySize, + @SuppressLint("NullableCollection") @Nullable ArraySet<ComponentName> whitelistedComponents) { this(/* lite= */ false, loggingLevel, maxBufferSize, idleFlushingFrequencyMs, textChangeFlushingFrequencyMs, logHistorySize, whitelistedComponents); diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 31beb6e6a565..04e0468fde90 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -59,6 +59,7 @@ import android.content.res.XmlResourceParser; import android.graphics.Rect; import android.graphics.drawable.AdaptiveIconDrawable; import android.graphics.drawable.Drawable; +import android.net.ConnectivityManager; import android.net.wifi.WifiManager; import android.os.Build; import android.os.Bundle; @@ -71,6 +72,12 @@ import android.os.incremental.IncrementalManager; import android.os.storage.StorageManager; import android.os.storage.VolumeInfo; import android.permission.PermissionManager; +import android.telephony.TelephonyManager; +import android.telephony.gba.GbaService; +import android.telephony.ims.ImsService; +import android.telephony.ims.ProvisioningManager; +import android.telephony.ims.RcsUceAdapter; +import android.telephony.ims.SipDelegateManager; import android.util.AndroidException; import android.util.Log; @@ -2602,6 +2609,37 @@ public abstract class PackageManager { public static final String FEATURE_TELEPHONY_IMS = "android.hardware.telephony.ims"; /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device + * supports a single IMS registration as defined by carrier networks in the IMS service + * implementation using the {@link ImsService} API, {@link GbaService} API, and IRadio 1.6 HAL. + * <p> + * When set, the device must fully support the following APIs for an application to implement + * IMS single registration: + * <ul> + * <li> Updating RCS provisioning status using the {@link ProvisioningManager} API to supply an + * RCC.14 defined XML and notify IMS applications of Auto Configuration Server (ACS) or + * proprietary server provisioning updates.</li> + * <li>Opening a delegate in the device IMS service to forward SIP traffic to the carrier's + * network using the {@link SipDelegateManager} API</li> + * <li>Listening to EPS dedicated bearer establishment via the + * {@link ConnectivityManager#registerQosCallback} + * API to indicate to the application when to start/stop media traffic.</li> + * <li>Implementing Generic Bootstrapping Architecture (GBA) and providing the associated + * authentication keys to applications + * requesting this information via the {@link TelephonyManager#bootstrapAuthenticationRequest} + * API</li> + * <li>Implementing RCS User Capability Exchange using the {@link RcsUceAdapter} API</li> + * </ul> + * <p> + * This feature should only be defined if {@link #FEATURE_TELEPHONY_IMS} is also defined. + * @hide + */ + @SystemApi + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION = + "android.hardware.telephony.ims.singlereg"; + + /** * Feature for {@link #getSystemAvailableFeatures} and * {@link #hasSystemFeature}: The device is capable of communicating with * other devices via ultra wideband. @@ -3224,6 +3262,24 @@ public abstract class PackageManager { @SdkConstant(SdkConstantType.FEATURE) public static final String FEATURE_APP_ENUMERATION = "android.software.app_enumeration"; + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has + * a Keystore implementation that can only enforce limited use key in hardware with max usage + * count equals to 1. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_KEYSTORE_SINGLE_USE_KEY = + "android.hardware.keystore.single_use_key"; + + /** + * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has + * a Keystore implementation that can enforce limited use key in hardware with any max usage + * count (including count equals to 1). + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_KEYSTORE_LIMITED_USE_KEY = + "android.hardware.keystore.limited_use_key"; + /** @hide */ public static final boolean APP_ENUMERATION_ENABLED_BY_DEFAULT = true; @@ -4945,7 +5001,7 @@ public abstract class PackageManager { * * @hide */ - @SuppressWarnings("HiddenAbstractMethod") + @SuppressWarnings({"HiddenAbstractMethod", "NullableCollection"}) @TestApi public abstract @Nullable String[] getNamesForUids(int[] uids); diff --git a/core/java/android/inputmethodservice/OWNERS b/core/java/android/inputmethodservice/OWNERS index e6a04dad25c2..d7db7c741364 100644 --- a/core/java/android/inputmethodservice/OWNERS +++ b/core/java/android/inputmethodservice/OWNERS @@ -2,3 +2,5 @@ set noparent include /services/core/java/com/android/server/inputmethod/OWNERS + +per-file *InlineSuggestion* = file:/core/java/android/service/autofill/OWNERS diff --git a/core/java/android/net/INetworkPolicyListener.aidl b/core/java/android/net/INetworkPolicyListener.aidl index fe9141cb6a20..dfb1e996c55a 100644 --- a/core/java/android/net/INetworkPolicyListener.aidl +++ b/core/java/android/net/INetworkPolicyListener.aidl @@ -23,6 +23,6 @@ oneway interface INetworkPolicyListener { void onMeteredIfacesChanged(in String[] meteredIfaces); void onRestrictBackgroundChanged(boolean restrictBackground); void onUidPoliciesChanged(int uid, int uidPolicies); - void onSubscriptionOverride(int subId, int overrideMask, int overrideValue); + void onSubscriptionOverride(int subId, int overrideMask, int overrideValue, in int[] networkTypes); void onSubscriptionPlansChanged(int subId, in SubscriptionPlan[] plans); } diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl index 29a3fdf59e8b..b016ed67c4d9 100644 --- a/core/java/android/net/INetworkPolicyManager.aidl +++ b/core/java/android/net/INetworkPolicyManager.aidl @@ -76,10 +76,11 @@ interface INetworkPolicyManager { SubscriptionPlan[] getSubscriptionPlans(int subId, String callingPackage); void setSubscriptionPlans(int subId, in SubscriptionPlan[] plans, String callingPackage); String getSubscriptionPlansOwner(int subId); - void setSubscriptionOverride(int subId, int overrideMask, int overrideValue, long timeoutMillis, String callingPackage); + void setSubscriptionOverride(int subId, int overrideMask, int overrideValue, in int[] networkTypes, long timeoutMillis, String callingPackage); void factoryReset(String subscriber); boolean isUidNetworkingBlocked(int uid, boolean meteredNetwork); boolean isUidRestrictedOnMeteredNetworks(int uid); + boolean checkUidNetworkingBlocked(int uid, int uidRules, boolean isNetworkMetered, boolean isBackgroundRestricted); } diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java index 82b035b08428..11146bd45fe4 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -34,6 +34,7 @@ import android.net.wifi.WifiInfo; import android.os.Build; import android.os.Process; import android.os.RemoteException; +import android.telephony.Annotation; import android.telephony.SubscriptionPlan; import android.util.DebugUtils; import android.util.Pair; @@ -377,6 +378,8 @@ public class NetworkPolicyManager { * @param overrideMask the bitmask that specifies which of the overrides is being * set or cleared. * @param overrideValue the override values to set or clear. + * @param networkTypes the network types this override applies to. + * {@see TelephonyManager#getAllNetworkTypes()} * @param timeoutMillis the timeout after which the requested override will * be automatically cleared, or {@code 0} to leave in the * requested state until explicitly cleared, or the next reboot, @@ -385,11 +388,12 @@ public class NetworkPolicyManager { * @hide */ public void setSubscriptionOverride(int subId, @SubscriptionOverrideMask int overrideMask, - @SubscriptionOverrideMask int overrideValue, long timeoutMillis, - @NonNull String callingPackage) { + @SubscriptionOverrideMask int overrideValue, + @NonNull @Annotation.NetworkType int[] networkTypes, long timeoutMillis, + @NonNull String callingPackage) { try { - mService.setSubscriptionOverride(subId, overrideMask, overrideValue, timeoutMillis, - callingPackage); + mService.setSubscriptionOverride(subId, overrideMask, overrideValue, networkTypes, + timeoutMillis, callingPackage); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -460,6 +464,31 @@ public class NetworkPolicyManager { } /** + * Figure out if networking is blocked for a given set of conditions. + * + * This is used by ConnectivityService via passing stale copies of conditions, so it must not + * take any locks. + * + * @param uid The target uid. + * @param uidRules The uid rules which are obtained from NetworkPolicyManagerService. + * @param isNetworkMetered True if the network is metered. + * @param isBackgroundRestricted True if data saver is enabled. + * + * @return true if networking is blocked for the UID under the specified conditions. + * + * @hide + */ + public boolean checkUidNetworkingBlocked(int uid, int uidRules, + boolean isNetworkMetered, boolean isBackgroundRestricted) { + try { + return mService.checkUidNetworkingBlocked(uid, uidRules, isNetworkMetered, + isBackgroundRestricted); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Check that the given uid is restricted from doing networking on metered networks. * * @param uid The target uid. @@ -613,9 +642,10 @@ public class NetworkPolicyManager { * @param subId the subscriber this override applies to. * @param overrideMask a bitmask that specifies which of the overrides is set. * @param overrideValue a bitmask that specifies the override values. + * @param networkTypes the network types this override applies to. */ public void onSubscriptionOverride(int subId, @SubscriptionOverrideMask int overrideMask, - @SubscriptionOverrideMask int overrideValue) {} + @SubscriptionOverrideMask int overrideValue, int[] networkTypes) {} /** * Notify of subscription plans change about a given subscription. @@ -639,8 +669,8 @@ public class NetworkPolicyManager { @Override public void onSubscriptionOverride(int subId, @SubscriptionOverrideMask int overrideMask, - @SubscriptionOverrideMask int overrideValue) { - mCallback.onSubscriptionOverride(subId, overrideMask, overrideValue); + @SubscriptionOverrideMask int overrideValue, int[] networkTypes) { + mCallback.onSubscriptionOverride(subId, overrideMask, overrideValue, networkTypes); } @Override @@ -656,7 +686,7 @@ public class NetworkPolicyManager { @Override public void onRestrictBackgroundChanged(boolean restrictBackground) { } @Override public void onUidPoliciesChanged(int uid, int uidPolicies) { } @Override public void onSubscriptionOverride(int subId, int overrideMask, - int overrideValue) { } + int overrideValue, int[] networkTypes) { } @Override public void onSubscriptionPlansChanged(int subId, SubscriptionPlan[] plans) { } } } diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java index 0185ba444ca4..16d041ac60f2 100644 --- a/core/java/android/os/BinderProxy.java +++ b/core/java/android/os/BinderProxy.java @@ -547,7 +547,8 @@ public final class BinderProxy implements IBinder { } try { - return transactNative(code, data, reply, flags); + boolean replyOwnsNative = (reply == null) ? false : reply.ownsNativeParcelObject(); + return transactNative(code, data, reply, replyOwnsNative, flags); } finally { AppOpsManager.resumeNotedAppOpsCollection(prevCollection); @@ -572,7 +573,7 @@ public final class BinderProxy implements IBinder { * Native implementation of transact() for proxies */ public native boolean transactNative(int code, Parcel data, Parcel reply, - int flags) throws RemoteException; + boolean replyOwnsNativeParcelObject, int flags) throws RemoteException; /** * See {@link IBinder#linkToDeath(DeathRecipient, int)} */ diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index 6acdcc4722d2..f0a99ed5c2fd 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -3691,4 +3691,9 @@ public final class Parcel { public long getBlobAshmemSize() { return nativeGetBlobAshmemSize(mNativePtr); } + + /** @hide */ + /*package*/ boolean ownsNativeParcelObject() { + return mOwnsNativeParcelObject; + } } diff --git a/core/java/android/provider/OWNERS b/core/java/android/provider/OWNERS index cb1509af66ac..cb06515910bf 100644 --- a/core/java/android/provider/OWNERS +++ b/core/java/android/provider/OWNERS @@ -1,5 +1,6 @@ per-file *BlockedNumber* = file:/telephony/OWNERS per-file *Telephony* = file:/telephony/OWNERS +per-file *SimPhonebook* = file:/telephony/OWNERS per-file *CallLog* = file:platform/packages/providers/ContactsProvider:/OWNERS per-file *Contacts* = file:platform/packages/providers/ContactsProvider:/OWNERS diff --git a/core/java/android/provider/SimPhonebookContract.java b/core/java/android/provider/SimPhonebookContract.java new file mode 100644 index 000000000000..2efc21229422 --- /dev/null +++ b/core/java/android/provider/SimPhonebookContract.java @@ -0,0 +1,506 @@ +/* + * 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 android.provider; + +import static android.provider.SimPhonebookContract.ElementaryFiles.EF_ADN; +import static android.provider.SimPhonebookContract.ElementaryFiles.EF_ADN_PATH_SEGMENT; +import static android.provider.SimPhonebookContract.ElementaryFiles.EF_FDN; +import static android.provider.SimPhonebookContract.ElementaryFiles.EF_FDN_PATH_SEGMENT; +import static android.provider.SimPhonebookContract.ElementaryFiles.EF_SDN; +import static android.provider.SimPhonebookContract.ElementaryFiles.EF_SDN_PATH_SEGMENT; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.annotation.WorkerThread; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.telephony.SubscriptionInfo; +import android.telephony.TelephonyManager; + +import com.android.internal.util.Preconditions; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * The contract between the provider of contact records on the device's SIM cards and applications. + * Contains definitions of the supported URIs and columns. + * + * <p>This content provider does not support any of the QUERY_ARG_SQL* bundle arguments. An + * IllegalArgumentException will be thrown if these are included. + */ +public final class SimPhonebookContract { + + /** The authority for the SIM phonebook provider. */ + public static final String AUTHORITY = "com.android.simphonebook"; + /** The content:// style uri to the authority for the SIM phonebook provider. */ + @NonNull + public static final Uri AUTHORITY_URI = Uri.parse("content://com.android.simphonebook"); + /** + * The Uri path element used to indicate that the following path segment is a subscription ID + * for the SIM card that will be operated on. + * + * @hide + */ + @SystemApi + public static final String SUBSCRIPTION_ID_PATH_SEGMENT = "subid"; + + private SimPhonebookContract() { + } + + /** + * Returns the Uri path segment used to reference the specified elementary file type for Uris + * returned by this API. + * + * @hide + */ + @NonNull + @SystemApi + public static String getEfUriPath(@ElementaryFiles.EfType int efType) { + switch (efType) { + case EF_ADN: + return EF_ADN_PATH_SEGMENT; + case EF_FDN: + return EF_FDN_PATH_SEGMENT; + case EF_SDN: + return EF_SDN_PATH_SEGMENT; + default: + throw new IllegalArgumentException("Unsupported EfType " + efType); + } + } + + /** Constants for the contact records on a SIM card. */ + public static final class SimRecords { + + /** + * The subscription ID of the SIM the record is from. + * + * @see SubscriptionInfo#getSubscriptionId() + */ + public static final String SUBSCRIPTION_ID = "subscription_id"; + /** + * The type of the elementary file the record is from. + * + * @see ElementaryFiles#EF_ADN + * @see ElementaryFiles#EF_FDN + * @see ElementaryFiles#EF_SDN + */ + public static final String ELEMENTARY_FILE_TYPE = "elementary_file_type"; + /** + * The 1-based offset of the record in the elementary file that contains it. + * + * <p>This can be used to access individual SIM records by appending it to the + * elementary file URIs but it is not like a normal database ID because it is not + * auto-incrementing and it is not unique across SIM cards or elementary files. Hence, care + * should be taken when using it to ensure that it is applied to the correct SIM and EF. + * + * @see #getItemUri(int, int, int) + */ + public static final String RECORD_NUMBER = "record_number"; + /** + * The name for this record. + * + * <p>An {@link IllegalArgumentException} will be thrown by insert and update if this + * exceeds the maximum supported length or contains unsupported characters. + * {@link #validateName(ContentResolver, int, int, String)} )} can be used to + * check whether the name is supported. + * + * @see ElementaryFiles#NAME_MAX_LENGTH + * @see #validateName(ContentResolver, int, int, String) ) + */ + public static final String NAME = "name"; + /** + * The phone number for this record. + * + * <p>Only dialable characters are supported. + * + * <p>An {@link IllegalArgumentException} will be thrown by insert and update if this + * exceeds the maximum supported length or contains unsupported characters. + * + * @see ElementaryFiles#PHONE_NUMBER_MAX_LENGTH + * @see android.telephony.PhoneNumberUtils#isDialable(char) + */ + public static final String PHONE_NUMBER = "phone_number"; + + /** The MIME type of a CONTENT_URI subdirectory of a single SIM record. */ + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/sim-contact_v2"; + /** The MIME type of CONTENT_URI providing a directory of SIM records. */ + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/sim-contact_v2"; + + /** + * The path segment that is appended to {@link #getContentUri(int, int)} which indicates + * that the following path segment contains a name to be validated. + * + * @hide + * @see #validateName(ContentResolver, int, int, String) + */ + @SystemApi + public static final String VALIDATE_NAME_PATH_SEGMENT = "validate_name"; + + /** + * The key for a cursor extra that contains the result of a validate name query. + * + * @hide + * @see #validateName(ContentResolver, int, int, String) + */ + @SystemApi + public static final String EXTRA_NAME_VALIDATION_RESULT = + "android.provider.extra.NAME_VALIDATION_RESULT"; + + + /** + * Key for the PIN2 needed to modify FDN record that should be passed in the Bundle + * passed to {@link ContentResolver#insert(Uri, ContentValues, Bundle)}, + * {@link ContentResolver#update(Uri, ContentValues, Bundle)} + * and {@link ContentResolver#delete(Uri, Bundle)}. + * + * <p>Modifying FDN records also requires either + * {@link android.Manifest.permission#MODIFY_PHONE_STATE} or + * {@link TelephonyManager#hasCarrierPrivileges()} + * + * @hide + */ + @SystemApi + public static final String QUERY_ARG_PIN2 = "android:query-arg-pin2"; + + private SimRecords() { + } + + /** + * Returns the content Uri for the specified elementary file on the specified SIM. + * + * <p>When queried this Uri will return all of the contact records in the specified + * elementary file on the specified SIM. The available subscriptionIds and efTypes can + * be discovered by querying {@link ElementaryFiles#CONTENT_URI}. + * + * <p>If a SIM with the provided subscription ID does not exist or the SIM with the provided + * subscription ID doesn't support the specified entity file then queries will return + * and empty cursor and inserts will throw an {@link IllegalArgumentException} + * + * @param subscriptionId the subscriptionId of the SIM card that this Uri will reference + * @param efType the elementary file on the SIM that this Uri will reference + * @see ElementaryFiles#EF_ADN + * @see ElementaryFiles#EF_FDN + * @see ElementaryFiles#EF_SDN + */ + @NonNull + public static Uri getContentUri(int subscriptionId, @ElementaryFiles.EfType int efType) { + return buildContentUri(subscriptionId, efType).build(); + } + + /** + * Content Uri for the specific SIM record with the provided {@link #RECORD_NUMBER}. + * + * <p>When queried this will return the record identified by the provided arguments. + * + * <p>For a non-existent record: + * <ul> + * <li>query will return an empty cursor</li> + * <li>update will return 0</li> + * <li>delete will return 0</li> + * </ul> + * + * @param subscriptionId the subscription ID of the SIM containing the record. If no SIM + * with this subscription ID exists then it will be treated as a + * non-existent record + * @param efType the elementary file type containing the record. If the specified + * SIM doesn't support this elementary file then it will be treated + * as a non-existent record. + * @param recordNumber the record number of the record this Uri should reference. This + * must be greater than 0. If there is no record with this record + * number in the specified entity file then it will be treated as a + * non-existent record. + */ + @NonNull + public static Uri getItemUri( + int subscriptionId, @ElementaryFiles.EfType int efType, int recordNumber) { + // Elementary file record indices are 1-based. + Preconditions.checkArgument(recordNumber > 0, "Invalid recordNumber"); + + return buildContentUri(subscriptionId, efType) + .appendPath(String.valueOf(recordNumber)) + .build(); + } + + /** + * Validates a value that is being provided for the {@link #NAME} column. + * + * <p>The return value can be used to check if the name is valid. If it is not valid then + * inserts and updates to the specified elementary file that use the provided name value + * will throw an {@link IllegalArgumentException}. + * + * <p>If the specified SIM or elementary file don't exist then + * {@link NameValidationResult#getMaxEncodedLength()} will be zero and + * {@link NameValidationResult#isValid()} will return false. + */ + @NonNull + @WorkerThread + public static NameValidationResult validateName( + @NonNull ContentResolver resolver, int subscriptionId, + @ElementaryFiles.EfType int efType, + @NonNull String name) { + Bundle queryArgs = new Bundle(); + queryArgs.putString(SimRecords.NAME, name); + try (Cursor cursor = + resolver.query(buildContentUri(subscriptionId, efType) + .appendPath(VALIDATE_NAME_PATH_SEGMENT) + .build(), null, queryArgs, null)) { + NameValidationResult result = cursor.getExtras() + .getParcelable(EXTRA_NAME_VALIDATION_RESULT); + return result != null ? result : new NameValidationResult(name, "", 0, 0); + } + } + + private static Uri.Builder buildContentUri( + int subscriptionId, @ElementaryFiles.EfType int efType) { + return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) + .authority(AUTHORITY) + .appendPath(SUBSCRIPTION_ID_PATH_SEGMENT) + .appendPath(String.valueOf(subscriptionId)) + .appendPath(getEfUriPath(efType)); + } + + /** Contains details about the validity of a value provided for the {@link #NAME} column. */ + public static final class NameValidationResult implements Parcelable { + + @NonNull + public static final Creator<NameValidationResult> CREATOR = + new Creator<NameValidationResult>() { + + @Override + public NameValidationResult createFromParcel(@NonNull Parcel in) { + return new NameValidationResult(in); + } + + @NonNull + @Override + public NameValidationResult[] newArray(int size) { + return new NameValidationResult[size]; + } + }; + + private final String mName; + private final String mSanitizedName; + private final int mEncodedLength; + private final int mMaxEncodedLength; + + /** Creates a new instance from the provided values. */ + public NameValidationResult(@NonNull String name, @NonNull String sanitizedName, + int encodedLength, int maxEncodedLength) { + this.mName = Objects.requireNonNull(name); + this.mSanitizedName = Objects.requireNonNull(sanitizedName); + this.mEncodedLength = encodedLength; + this.mMaxEncodedLength = maxEncodedLength; + } + + private NameValidationResult(Parcel in) { + this(in.readString(), in.readString(), in.readInt(), in.readInt()); + } + + /** Returns the original name that is being validated. */ + @NonNull + public String getName() { + return mName; + } + + /** + * Returns a sanitized copy of the original name with all unsupported characters + * replaced with spaces. + */ + @NonNull + public String getSanitizedName() { + return mSanitizedName; + } + + /** + * Returns whether the original name isValid. + * + * <p>If this returns false then inserts and updates using the name will throw an + * {@link IllegalArgumentException} + */ + public boolean isValid() { + return mMaxEncodedLength > 0 && mEncodedLength <= mMaxEncodedLength + && Objects.equals( + mName, mSanitizedName); + } + + /** Returns whether the character at the specified position is supported by the SIM. */ + public boolean isSupportedCharacter(int position) { + return mName.charAt(position) == mSanitizedName.charAt(position); + } + + /** + * Returns the number of bytes required to save the name. + * + * <p>This may be more than the number of characters in the name. + */ + public int getEncodedLength() { + return mEncodedLength; + } + + /** + * Returns the maximum number of bytes that are supported for the name. + * + * @see ElementaryFiles#NAME_MAX_LENGTH + */ + public int getMaxEncodedLength() { + return mMaxEncodedLength; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString(mName); + dest.writeString(mSanitizedName); + dest.writeInt(mEncodedLength); + dest.writeInt(mMaxEncodedLength); + } + } + } + + /** Constants for metadata about the elementary files of the SIM cards in the phone. */ + public static final class ElementaryFiles { + + /** {@link SubscriptionInfo#getSimSlotIndex()} of the SIM for this row. */ + public static final String SLOT_INDEX = "slot_index"; + /** {@link SubscriptionInfo#getSubscriptionId()} of the SIM for this row. */ + public static final String SUBSCRIPTION_ID = "subscription_id"; + /** + * The elementary file type for this row. + * + * @see ElementaryFiles#EF_ADN + * @see ElementaryFiles#EF_FDN + * @see ElementaryFiles#EF_SDN + */ + public static final String EF_TYPE = "ef_type"; + /** The maximum number of records supported by the elementary file. */ + public static final String MAX_RECORDS = "max_records"; + /** Count of the number of records that are currently stored in the elementary file. */ + public static final String RECORD_COUNT = "record_count"; + /** The maximum length supported for the name of a record in the elementary file. */ + public static final String NAME_MAX_LENGTH = "name_max_length"; + /** + * The maximum length supported for the phone number of a record in the elementary file. + */ + public static final String PHONE_NUMBER_MAX_LENGTH = "phone_number_max_length"; + + /** + * A value for an elementary file that is not recognized. + * + * <p>Generally this should be ignored. If new values are added then this will be used + * for apps that target SDKs where they aren't defined. + */ + public static final int EF_UNKNOWN = 0; + /** + * Type for accessing records in the "abbreviated dialing number" (ADN) elementary file on + * the SIM. + * + * <p>ADN records are typically user created. + */ + public static final int EF_ADN = 1; + /** + * Type for accessing records in the "fixed dialing number" (FDN) elementary file on the + * SIM. + * + * <p>FDN numbers are the numbers that are allowed to dialed for outbound calls when FDN is + * enabled. + * + * <p>FDN records cannot be modified by applications. Hence, insert, update and + * delete methods operating on this Uri will throw UnsupportedOperationException + */ + public static final int EF_FDN = 2; + /** + * Type for accessing records in the "service dialing number" (SDN) elementary file on the + * SIM. + * + * <p>Typically SDNs are preset numbers provided by the carrier for common operations (e.g. + * voicemail, check balance, etc). + * + * <p>SDN records cannot be modified by applications. Hence, insert, update and delete + * methods operating on this Uri will throw UnsupportedOperationException + */ + public static final int EF_SDN = 3; + /** @hide */ + @SystemApi + public static final String EF_ADN_PATH_SEGMENT = "adn"; + /** @hide */ + @SystemApi + public static final String EF_FDN_PATH_SEGMENT = "fdn"; + /** @hide */ + @SystemApi + public static final String EF_SDN_PATH_SEGMENT = "sdn"; + /** The MIME type of CONTENT_URI providing a directory of ADN-like elementary files. */ + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/sim-elementary-file"; + /** The MIME type of a CONTENT_URI subdirectory of a single ADN-like elementary file. */ + public static final String CONTENT_ITEM_TYPE = + "vnd.android.cursor.item/sim-elementary-file"; + /** + * The Uri path segment used to construct Uris for the metadata defined in this class. + * + * @hide + */ + @SystemApi + public static final String ELEMENTARY_FILES_PATH_SEGMENT = "elementary_files"; + + /** Content URI for the ADN-like elementary files available on the device. */ + @NonNull + public static final Uri CONTENT_URI = AUTHORITY_URI + .buildUpon() + .appendPath(ELEMENTARY_FILES_PATH_SEGMENT).build(); + + private ElementaryFiles() { + } + + /** + * Returns a content uri for a specific elementary file. + * + * <p>If a SIM with the specified subscriptionId is not present an exception will be thrown. + * If the SIM doesn't support the specified elementary file it will have a zero value for + * {@link #MAX_RECORDS}. + */ + @NonNull + public static Uri getItemUri(int subscriptionId, @EfType int efType) { + return CONTENT_URI.buildUpon().appendPath(SUBSCRIPTION_ID_PATH_SEGMENT) + .appendPath(String.valueOf(subscriptionId)) + .appendPath(getEfUriPath(efType)) + .build(); + } + + /** + * Annotation for the valid elementary file types. + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef( + prefix = {"EF"}, + value = {EF_UNKNOWN, EF_ADN, EF_FDN, EF_SDN}) + public @interface EfType { + } + } +} diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java index f994d2930cd9..c39b8c5eb6d1 100644 --- a/core/java/android/security/keymaster/KeymasterDefs.java +++ b/core/java/android/security/keymaster/KeymasterDefs.java @@ -80,6 +80,7 @@ public final class KeymasterDefs { public static final int KM_TAG_MIN_SECONDS_BETWEEN_OPS = Tag.MIN_SECONDS_BETWEEN_OPS; // KM_UINT | 403; public static final int KM_TAG_MAX_USES_PER_BOOT = Tag.MAX_USES_PER_BOOT; // KM_UINT | 404; + public static final int KM_TAG_USAGE_COUNT_LIMIT = Tag.USAGE_COUNT_LIMIT; // KM_UINT | 405; public static final int KM_TAG_USER_ID = Tag.USER_ID; // KM_UINT | 501; public static final int KM_TAG_USER_SECURE_ID = Tag.USER_SECURE_ID; // KM_ULONG_REP | 502; diff --git a/core/java/android/view/FrameMetrics.java b/core/java/android/view/FrameMetrics.java index f8500fa9f0aa..6511a9429eea 100644 --- a/core/java/android/view/FrameMetrics.java +++ b/core/java/android/view/FrameMetrics.java @@ -149,7 +149,7 @@ public final class FrameMetrics { * <p> * The time value that was used in all the vsync listeners and drawing for * the frame (Choreographer frame callbacks, animations, - * {@link View#getDrawingTime()}, etc…) + * {@link View#getDrawingTime()}, etc.) * </p> */ public static final int VSYNC_TIMESTAMP = 11; diff --git a/core/java/android/view/inputmethod/InlineSuggestionInfo.java b/core/java/android/view/inputmethod/InlineSuggestionInfo.java index 1c703ecf06ca..a17c012d16ad 100644 --- a/core/java/android/view/inputmethod/InlineSuggestionInfo.java +++ b/core/java/android/view/inputmethod/InlineSuggestionInfo.java @@ -82,6 +82,7 @@ public final class InlineSuggestionInfo implements Parcelable { public static InlineSuggestionInfo newInlineSuggestionInfo( @NonNull InlinePresentationSpec presentationSpec, @NonNull @Source String source, + @SuppressLint("NullableCollection") @Nullable String[] autofillHints, @NonNull @Type String type, boolean isPinned) { return new InlineSuggestionInfo(presentationSpec, source, autofillHints, type, isPinned); } diff --git a/core/java/android/view/inputmethod/OWNERS b/core/java/android/view/inputmethod/OWNERS index e6a04dad25c2..d7db7c741364 100644 --- a/core/java/android/view/inputmethod/OWNERS +++ b/core/java/android/view/inputmethod/OWNERS @@ -2,3 +2,5 @@ set noparent include /services/core/java/com/android/server/inputmethod/OWNERS + +per-file *InlineSuggestion* = file:/core/java/android/service/autofill/OWNERS diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java index 502680de9bcf..7b4fcbf16754 100644 --- a/core/java/android/window/TaskOrganizer.java +++ b/core/java/android/window/TaskOrganizer.java @@ -20,6 +20,7 @@ import android.annotation.BinderThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.annotation.SuppressLint; import android.annotation.TestApi; import android.app.ActivityManager; import android.os.RemoteException; @@ -101,6 +102,7 @@ public class TaskOrganizer extends WindowOrganizer { /** Gets direct child tasks (ordered from top-to-bottom) */ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) @Nullable + @SuppressLint("NullableCollection") public static List<ActivityManager.RunningTaskInfo> getChildTasks( @NonNull WindowContainerToken parent, @NonNull int[] activityTypes) { try { @@ -113,6 +115,7 @@ public class TaskOrganizer extends WindowOrganizer { /** Gets all root tasks on a display (ordered from top-to-bottom) */ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) @Nullable + @SuppressLint("NullableCollection") public static List<ActivityManager.RunningTaskInfo> getRootTasks( int displayId, @NonNull int[] activityTypes) { try { diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 16991b472037..1270185e1ea5 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -1210,25 +1210,6 @@ public class ResolverActivity extends Activity implements if (TextUtils.isEmpty(packageName)) { pm.setDefaultBrowserPackageNameAsUser(ri.activityInfo.packageName, userId); } - } else { - // Update Domain Verification status - ComponentName cn = intent.getComponent(); - String packageName = cn.getPackageName(); - String dataScheme = (data != null) ? data.getScheme() : null; - - boolean isHttpOrHttps = (dataScheme != null) && - (dataScheme.equals(IntentFilter.SCHEME_HTTP) || - dataScheme.equals(IntentFilter.SCHEME_HTTPS)); - - boolean isViewAction = (action != null) && action.equals(Intent.ACTION_VIEW); - boolean hasCategoryBrowsable = (categories != null) && - categories.contains(Intent.CATEGORY_BROWSABLE); - - if (isHttpOrHttps && isViewAction && hasCategoryBrowsable) { - pm.updateIntentVerificationStatusAsUser(packageName, - PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS, - userId); - } } } else { try { diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp index 241570a7f9d3..0fdab722dd05 100644 --- a/core/jni/android_os_Parcel.cpp +++ b/core/jni/android_os_Parcel.cpp @@ -36,6 +36,7 @@ #include <utils/List.h> #include <utils/KeyedVector.h> #include <binder/Parcel.h> +#include <binder/ParcelRef.h> #include <binder/ProcessState.h> #include <binder/IServiceManager.h> #include <utils/threads.h> @@ -529,8 +530,9 @@ static jobject android_os_Parcel_readFileDescriptor(JNIEnv* env, jclass clazz, j static jlong android_os_Parcel_create(JNIEnv* env, jclass clazz) { - Parcel* parcel = new Parcel(); - return reinterpret_cast<jlong>(parcel); + sp<ParcelRef> parcelRef = ParcelRef::create(); + parcelRef->incStrong(reinterpret_cast<const void*>(android_os_Parcel_create)); + return reinterpret_cast<jlong>(static_cast<Parcel *>(parcelRef.get())); } static jlong android_os_Parcel_freeBuffer(JNIEnv* env, jclass clazz, jlong nativePtr) @@ -545,8 +547,8 @@ static jlong android_os_Parcel_freeBuffer(JNIEnv* env, jclass clazz, jlong nativ static void android_os_Parcel_destroy(JNIEnv* env, jclass clazz, jlong nativePtr) { - Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr); - delete parcel; + ParcelRef* derivative = static_cast<ParcelRef*>(reinterpret_cast<Parcel*>(nativePtr)); + derivative->decStrong(reinterpret_cast<const void*>(android_os_Parcel_create)); } static jbyteArray android_os_Parcel_marshall(JNIEnv* env, jclass clazz, jlong nativePtr) diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp index 581dc0848a28..f71b42c4793f 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -35,6 +35,7 @@ #include <binder/IPCThreadState.h> #include <binder/IServiceManager.h> #include <binder/Parcel.h> +#include <binder/ParcelRef.h> #include <binder/ProcessState.h> #include <binder/Stability.h> #include <binderthreadstate/CallerUtils.h> @@ -1374,7 +1375,8 @@ static bool should_time_binder_calls() { } static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj, - jint code, jobject dataObj, jobject replyObj, jint flags) // throws RemoteException + jint code, jobject dataObj, jobject replyObj, jboolean replyObjOwnsNativeParcel, + jint flags) // throws RemoteException { if (dataObj == NULL) { jniThrowNullPointerException(env, NULL); @@ -1416,6 +1418,21 @@ static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj, status_t err = target->transact(code, *data, reply, flags); //if (reply) printf("Transact from Java code to %p received: ", target); reply->print(); + if (reply) { + if (replyObjOwnsNativeParcel) { + // as per Parcel java class constructor, here, "reply" MUST be a "ParcelRef" + // only for Parcel that contained Binder objects + if (reply->objectsCount() > 0) { + IPCThreadState::self()->createTransactionReference(static_cast<ParcelRef*>(reply)); + } + } else { + // as per Parcel.java, if Parcel java object NOT owning native Parcel object, it will + // NOT destroy the native Parcel object upon GC(finalize()), so, there will be no race + // condtion in this case. Please refer to the java class methods: Parcel.finalize(), + // Parcel.destroy(). + } + } + if (kEnableBinderSample) { if (time_binder_calls) { conditionally_log_binder_call(start_millis, target, code); @@ -1542,7 +1559,7 @@ static const JNINativeMethod gBinderProxyMethods[] = { {"pingBinder", "()Z", (void*)android_os_BinderProxy_pingBinder}, {"isBinderAlive", "()Z", (void*)android_os_BinderProxy_isBinderAlive}, {"getInterfaceDescriptor", "()Ljava/lang/String;", (void*)android_os_BinderProxy_getInterfaceDescriptor}, - {"transactNative", "(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z", (void*)android_os_BinderProxy_transact}, + {"transactNative", "(ILandroid/os/Parcel;Landroid/os/Parcel;ZI)Z", (void*)android_os_BinderProxy_transact}, {"linkToDeath", "(Landroid/os/IBinder$DeathRecipient;I)V", (void*)android_os_BinderProxy_linkToDeath}, {"unlinkToDeath", "(Landroid/os/IBinder$DeathRecipient;I)Z", (void*)android_os_BinderProxy_unlinkToDeath}, {"getNativeFinalizer", "()J", (void*)android_os_BinderProxy_getNativeFinalizer}, diff --git a/core/proto/OWNERS b/core/proto/OWNERS index 748b4b4f5743..99fd21592411 100644 --- a/core/proto/OWNERS +++ b/core/proto/OWNERS @@ -16,6 +16,7 @@ ogunwale@google.com jjaggi@google.com roosa@google.com per-file usagestatsservice.proto, usagestatsservice_v2.proto = mwachens@google.com +per-file apphibernationservice.proto = file:/core/java/android/apphibernation/OWNERS # Biometrics kchyn@google.com diff --git a/core/proto/android/server/apphibernationservice.proto b/core/proto/android/server/apphibernationservice.proto new file mode 100644 index 000000000000..d341c4b2f0a8 --- /dev/null +++ b/core/proto/android/server/apphibernationservice.proto @@ -0,0 +1,42 @@ +/* + * 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. + */ + +syntax = "proto2"; +package com.android.server.apphibernation; + +option java_multiple_files = true; + +// Proto for hibernation states for all packages for a user. +message UserLevelHibernationStatesProto { + repeated UserLevelHibernationStateProto hibernation_state = 1; +} + +// Proto for com.android.server.apphibernation.UserLevelState. +message UserLevelHibernationStateProto { + optional string package_name = 1; + optional bool hibernated = 2; +} + +// Proto for global hibernation states for all packages. +message GlobalLevelHibernationStatesProto { + repeated GlobalLevelHibernationStateProto hibernation_state = 1; +} + +// Proto for com.android.server.apphibernation.GlobalLevelState +message GlobalLevelHibernationStateProto { + optional string package_name = 1; + optional bool hibernated = 2; +}
\ No newline at end of file diff --git a/core/tests/coretests/assets/fonts/OWNERS b/core/tests/coretests/assets/fonts/OWNERS new file mode 100644 index 000000000000..b0e0b9deaddb --- /dev/null +++ b/core/tests/coretests/assets/fonts/OWNERS @@ -0,0 +1 @@ +include /core/java/android/text/OWNERS diff --git a/core/tests/coretests/assets/fonts_for_family_selection/OWNERS b/core/tests/coretests/assets/fonts_for_family_selection/OWNERS new file mode 100644 index 000000000000..b0e0b9deaddb --- /dev/null +++ b/core/tests/coretests/assets/fonts_for_family_selection/OWNERS @@ -0,0 +1 @@ +include /core/java/android/text/OWNERS diff --git a/core/tests/coretests/res/font/OWNERS b/core/tests/coretests/res/font/OWNERS new file mode 100644 index 000000000000..b0e0b9deaddb --- /dev/null +++ b/core/tests/coretests/res/font/OWNERS @@ -0,0 +1 @@ +include /core/java/android/text/OWNERS diff --git a/core/tests/coretests/src/android/provider/SimPhonebookContractTest.java b/core/tests/coretests/src/android/provider/SimPhonebookContractTest.java new file mode 100644 index 000000000000..be3826007aa3 --- /dev/null +++ b/core/tests/coretests/src/android/provider/SimPhonebookContractTest.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.provider; + +import static com.google.common.truth.Truth.assertThat; + +import static org.testng.Assert.assertThrows; + +import android.content.ContentValues; +import android.os.Parcel; +import android.provider.SimPhonebookContract.SimRecords.NameValidationResult; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class SimPhonebookContractTest { + + @Test + public void getContentUri_invalidEfType_throwsIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> + SimPhonebookContract.SimRecords.getContentUri(1, 100) + ); + assertThrows(IllegalArgumentException.class, () -> + SimPhonebookContract.SimRecords.getContentUri(1, -1) + ); + assertThrows(IllegalArgumentException.class, () -> + SimPhonebookContract.SimRecords.getContentUri(1, + SimPhonebookContract.ElementaryFiles.EF_UNKNOWN) + ); + } + + @Test + public void getItemUri_invalidEfType_throwsIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> + SimPhonebookContract.SimRecords.getItemUri(1, 100, 1) + ); + assertThrows(IllegalArgumentException.class, () -> + SimPhonebookContract.SimRecords.getItemUri(1, -1, 1) + ); + assertThrows(IllegalArgumentException.class, () -> + SimPhonebookContract.SimRecords.getItemUri(1, + SimPhonebookContract.ElementaryFiles.EF_UNKNOWN, 1) + ); + } + + @Test + public void getItemUri_invalidRecordIndex_throwsIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> + SimPhonebookContract.SimRecords.getItemUri(1, + SimPhonebookContract.ElementaryFiles.EF_ADN, 0) + ); + assertThrows(IllegalArgumentException.class, () -> + SimPhonebookContract.SimRecords.getItemUri(1, + SimPhonebookContract.ElementaryFiles.EF_ADN, -1) + ); + } + + @Test + public void nameValidationResult_isValid_validNames() { + assertThat(new NameValidationResult("", "", 0, 1).isValid()).isTrue(); + assertThat(new NameValidationResult("a", "a", 1, 1).isValid()).isTrue(); + assertThat(new NameValidationResult("First Last", "First Last", 10, 10).isValid()).isTrue(); + assertThat( + new NameValidationResult("First Last", "First Last", 10, 100).isValid()).isTrue(); + } + + @Test + public void nameValidationResult_isValid_invalidNames() { + assertThat(new NameValidationResult("", "", 0, 0).isValid()).isFalse(); + assertThat(new NameValidationResult("ab", "ab", 2, 1).isValid()).isFalse(); + NameValidationResult unsupportedCharactersResult = new NameValidationResult("A_b_c", + "A b c", 5, 5); + assertThat(unsupportedCharactersResult.isValid()).isFalse(); + assertThat(unsupportedCharactersResult.isSupportedCharacter(0)).isTrue(); + assertThat(unsupportedCharactersResult.isSupportedCharacter(1)).isFalse(); + assertThat(unsupportedCharactersResult.isSupportedCharacter(2)).isTrue(); + assertThat(unsupportedCharactersResult.isSupportedCharacter(3)).isFalse(); + assertThat(unsupportedCharactersResult.isSupportedCharacter(4)).isTrue(); + } + + @Test + public void nameValidationResult_parcel() { + ContentValues values = new ContentValues(); + values.put("name", "Name"); + values.put("phone_number", "123"); + + NameValidationResult result; + Parcel parcel = Parcel.obtain(); + try { + parcel.writeParcelable(new NameValidationResult("name", "sanitized name", 1, 2), 0); + parcel.setDataPosition(0); + result = parcel.readParcelable(NameValidationResult.class.getClassLoader()); + } finally { + parcel.recycle(); + } + + assertThat(result.getName()).isEqualTo("name"); + assertThat(result.getSanitizedName()).isEqualTo("sanitized name"); + assertThat(result.getEncodedLength()).isEqualTo(1); + assertThat(result.getMaxEncodedLength()).isEqualTo(2); + } +} + diff --git a/keystore/java/android/security/IKeyChainService.aidl b/keystore/java/android/security/IKeyChainService.aidl index 97da3cc6f80f..1ae6a631dbcb 100644 --- a/keystore/java/android/security/IKeyChainService.aidl +++ b/keystore/java/android/security/IKeyChainService.aidl @@ -46,6 +46,7 @@ interface IKeyChainService { boolean installKeyPair( in byte[] privateKey, in byte[] userCert, in byte[] certChain, String alias, int uid); boolean removeKeyPair(String alias); + boolean containsKeyPair(String alias); // APIs used by Settings boolean deleteCaCertificate(String alias); diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java index b1b6306e0cf6..16bf5469296f 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java @@ -23,7 +23,6 @@ import android.security.KeyStore; import android.security.keymaster.ExportResult; import android.security.keymaster.KeyCharacteristics; import android.security.keymaster.KeymasterDefs; -import android.sysprop.Keystore2Properties; import java.io.IOException; import java.security.KeyFactory; @@ -117,8 +116,6 @@ public class AndroidKeyStoreProvider extends Provider { putSecretKeyFactoryImpl("HmacSHA512"); } - private static boolean sKeystore2Enabled; - /** * This function indicates whether or not Keystore 2.0 is enabled. Some parts of the * Keystore SPI must behave subtly differently when Keystore 2.0 is enabled. However, @@ -133,10 +130,9 @@ public class AndroidKeyStoreProvider extends Provider { * @hide */ public static boolean isKeystore2Enabled() { - return sKeystore2Enabled; + return android.security.keystore2.AndroidKeyStoreProvider.isInstalled(); } - /** * Installs a new instance of this provider (and the * {@link AndroidKeyStoreBCWorkaroundProvider}). @@ -164,11 +160,6 @@ public class AndroidKeyStoreProvider extends Provider { // priority. Security.addProvider(workaroundProvider); } - - // {@code install()} is run by zygote when this property is still accessible. We store its - // value so that the Keystore SPI can act accordingly without having to access an internal - // property. - sKeystore2Enabled = Keystore2Properties.keystore2_enabled().orElse(false); } private void putSecretKeyFactoryImpl(String algorithm) { diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java index 3694d635422f..d2678c71813c 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java @@ -215,7 +215,8 @@ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { // Keystore 1.0 does not tell us the exact security level of the key // so we report an unknown but secure security level. insideSecureHardware ? KeyProperties.SECURITY_LEVEL_UNKNOWN_SECURE - : KeyProperties.SECURITY_LEVEL_SOFTWARE); + : KeyProperties.SECURITY_LEVEL_SOFTWARE, + KeyProperties.UNRESTRICTED_USAGE_COUNT); } private static BigInteger getGateKeeperSecureUserId() throws ProviderException { diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index e9aac7ddb56d..c2a7b2ee6323 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -274,6 +274,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu private final boolean mUserConfirmationRequired; private final boolean mUnlockedDeviceRequired; private final boolean mCriticalToDeviceEncryption; + private final int mMaxUsageCount; /* * ***NOTE***: All new fields MUST also be added to the following: * ParcelableKeyGenParameterSpec class. @@ -313,7 +314,8 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu boolean isStrongBoxBacked, boolean userConfirmationRequired, boolean unlockedDeviceRequired, - boolean criticalToDeviceEncryption) { + boolean criticalToDeviceEncryption, + int maxUsageCount) { if (TextUtils.isEmpty(keyStoreAlias)) { throw new IllegalArgumentException("keyStoreAlias must not be empty"); } @@ -366,6 +368,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu mUserConfirmationRequired = userConfirmationRequired; mUnlockedDeviceRequired = unlockedDeviceRequired; mCriticalToDeviceEncryption = criticalToDeviceEncryption; + mMaxUsageCount = maxUsageCount; } /** @@ -782,7 +785,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu } /** - * Return whether this key is critical to the device encryption flow. + * Returns whether this key is critical to the device encryption flow. * * @see android.security.KeyStore#FLAG_CRITICAL_TO_DEVICE_ENCRYPTION * @hide @@ -792,6 +795,17 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu } /** + * Returns the maximum number of times the limited use key is allowed to be used or + * {@link KeyProperties#UNRESTRICTED_USAGE_COUNT} if there’s no restriction on the number of + * times the key can be used. + * + * @see Builder#setMaxUsageCount(int) + */ + public int getMaxUsageCount() { + return mMaxUsageCount; + } + + /** * Builder of {@link KeyGenParameterSpec} instances. */ public final static class Builder { @@ -827,6 +841,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu private boolean mUserConfirmationRequired; private boolean mUnlockedDeviceRequired = false; private boolean mCriticalToDeviceEncryption = false; + private int mMaxUsageCount = KeyProperties.UNRESTRICTED_USAGE_COUNT; /** * Creates a new instance of the {@code Builder}. @@ -894,6 +909,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu mUserConfirmationRequired = sourceSpec.isUserConfirmationRequired(); mUnlockedDeviceRequired = sourceSpec.isUnlockedDeviceRequired(); mCriticalToDeviceEncryption = sourceSpec.isCriticalToDeviceEncryption(); + mMaxUsageCount = sourceSpec.getMaxUsageCount(); } /** @@ -1553,6 +1569,47 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu } /** + * Sets the maximum number of times the key is allowed to be used. After every use of the + * key, the use counter will decrease. This authorization applies only to secret key and + * private key operations. Public key operations are not restricted. For example, after + * successfully encrypting and decrypting data using methods such as + * {@link Cipher#doFinal()}, the use counter of the secret key will decrease. After + * successfully signing data using methods such as {@link Signature#sign()}, the use + * counter of the private key will decrease. + * + * When the use counter is depleted, the key will be marked for deletion by Android + * Keystore and any subsequent attempt to use the key will throw + * {@link KeyPermanentlyInvalidatedException}. There is no key to be loaded from the + * Android Keystore once the exhausted key is permanently deleted, as if the key never + * existed before. + * + * <p>By default, there is no restriction on the usage of key. + * + * <p>Some secure hardware may not support this feature at all, in which case it will + * be enforced in software, some secure hardware may support it but only with + * maxUsageCount = 1, and some secure hardware may support it with larger value + * of maxUsageCount. + * + * <p>The PackageManger feature flags: + * {@link android.content.pm.PackageManager#FEATURE_KEYSTORE_SINGLE_USE_KEY} and + * {@link android.content.pm.PackageManager#FEATURE_KEYSTORE_LIMITED_USE_KEY} can be used + * to check whether the secure hardware cannot enforce this feature, can only enforce it + * with maxUsageCount = 1, or can enforce it with larger value of maxUsageCount. + * + * @param maxUsageCount maximum number of times the key is allowed to be used or + * {@link KeyProperties#UNRESTRICTED_USAGE_COUNT} if there is no restriction on the + * usage. + */ + @NonNull + public Builder setMaxUsageCount(int maxUsageCount) { + if (maxUsageCount == KeyProperties.UNRESTRICTED_USAGE_COUNT || maxUsageCount > 0) { + mMaxUsageCount = maxUsageCount; + return this; + } + throw new IllegalArgumentException("maxUsageCount is not valid"); + } + + /** * Builds an instance of {@code KeyGenParameterSpec}. */ @NonNull @@ -1587,7 +1644,8 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu mIsStrongBoxBacked, mUserConfirmationRequired, mUnlockedDeviceRequired, - mCriticalToDeviceEncryption); + mCriticalToDeviceEncryption, + mMaxUsageCount); } } } diff --git a/keystore/java/android/security/keystore/KeyInfo.java b/keystore/java/android/security/keystore/KeyInfo.java index 7158d0cf248e..f50efd2c3328 100644 --- a/keystore/java/android/security/keystore/KeyInfo.java +++ b/keystore/java/android/security/keystore/KeyInfo.java @@ -85,6 +85,7 @@ public class KeyInfo implements KeySpec { private final boolean mInvalidatedByBiometricEnrollment; private final boolean mUserConfirmationRequired; private final @KeyProperties.SecurityLevelEnum int mSecurityLevel; + private final int mRemainingUsageCount; /** * @hide @@ -109,7 +110,8 @@ public class KeyInfo implements KeySpec { boolean trustedUserPresenceRequired, boolean invalidatedByBiometricEnrollment, boolean userConfirmationRequired, - @KeyProperties.SecurityLevelEnum int securityLevel) { + @KeyProperties.SecurityLevelEnum int securityLevel, + int remainingUsageCount) { mKeystoreAlias = keystoreKeyAlias; mInsideSecureHardware = insideSecureHardware; mOrigin = origin; @@ -134,6 +136,7 @@ public class KeyInfo implements KeySpec { mInvalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment; mUserConfirmationRequired = userConfirmationRequired; mSecurityLevel = securityLevel; + mRemainingUsageCount = remainingUsageCount; } /** @@ -374,4 +377,15 @@ public class KeyInfo implements KeySpec { public @KeyProperties.SecurityLevelEnum int getSecurityLevel() { return mSecurityLevel; } + + /** + * Returns the remaining number of times the key is allowed to be used or + * {@link KeyProperties#UNRESTRICTED_USAGE_COUNT} if there's no restriction on the number of + * times the key can be used. Note that this gives a best effort count and need not be + * accurate (as there might be usages happening in parallel and the count maintained here need + * not be in sync with the usage). + */ + public int getRemainingUsageCount() { + return mRemainingUsageCount; + } } diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java index 014d6882be8d..fa4f8b1674d1 100644 --- a/keystore/java/android/security/keystore/KeyProperties.java +++ b/keystore/java/android/security/keystore/KeyProperties.java @@ -907,4 +907,9 @@ public abstract class KeyProperties { + uid); } } + + /** + * This value indicates that there is no restriction on the number of times the key can be used. + */ + public static final int UNRESTRICTED_USAGE_COUNT = -1; } diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java index 2e793dea3e05..76ce23efd05b 100644 --- a/keystore/java/android/security/keystore/KeyProtection.java +++ b/keystore/java/android/security/keystore/KeyProtection.java @@ -235,6 +235,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { private final boolean mUserConfirmationRequired; private final boolean mUnlockedDeviceRequired; private final boolean mIsStrongBoxBacked; + private final int mMaxUsageCount; private KeyProtection( Date keyValidityStart, @@ -256,7 +257,8 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { boolean criticalToDeviceEncryption, boolean userConfirmationRequired, boolean unlockedDeviceRequired, - boolean isStrongBoxBacked) { + boolean isStrongBoxBacked, + int maxUsageCount) { mKeyValidityStart = Utils.cloneIfNotNull(keyValidityStart); mKeyValidityForOriginationEnd = Utils.cloneIfNotNull(keyValidityForOriginationEnd); mKeyValidityForConsumptionEnd = Utils.cloneIfNotNull(keyValidityForConsumptionEnd); @@ -279,6 +281,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { mUserConfirmationRequired = userConfirmationRequired; mUnlockedDeviceRequired = unlockedDeviceRequired; mIsStrongBoxBacked = isStrongBoxBacked; + mMaxUsageCount = maxUsageCount; } /** @@ -548,6 +551,17 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { } /** + * Returns the maximum number of times the limited use key is allowed to be used or + * {@link KeyProperties#UNRESTRICTED_USAGE_COUNT} if there’s no restriction on the number of + * times the key can be used. + * + * @see Builder#setMaxUsageCount(int) + */ + public int getMaxUsageCount() { + return mMaxUsageCount; + } + + /** * Builder of {@link KeyProtection} instances. */ public final static class Builder { @@ -574,6 +588,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { private long mBoundToSecureUserId = GateKeeper.INVALID_SECURE_USER_ID; private boolean mCriticalToDeviceEncryption = false; private boolean mIsStrongBoxBacked = false; + private int mMaxUsageCount = KeyProperties.UNRESTRICTED_USAGE_COUNT; /** * Creates a new instance of the {@code Builder}. @@ -1040,6 +1055,47 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { } /** + * Sets the maximum number of times the key is allowed to be used. After every use of the + * key, the use counter will decrease. This authorization applies only to secret key and + * private key operations. Public key operations are not restricted. For example, after + * successfully encrypting and decrypting data using methods such as + * {@link Cipher#doFinal()}, the use counter of the secret key will decrease. After + * successfully signing data using methods such as {@link Signature#sign()}, the use + * counter of the private key will decrease. + * + * When the use counter is depleted, the key will be marked for deletion by Android + * Keystore and any subsequent attempt to use the key will throw + * {@link KeyPermanentlyInvalidatedException}. There is no key to be loaded from the + * Android Keystore once the exhausted key is permanently deleted, as if the key never + * existed before. + * + * <p>By default, there is no restriction on the usage of key. + * + * <p>Some secure hardware may not support this feature at all, in which case it will + * be enforced in software, some secure hardware may support it but only with + * maxUsageCount = 1, and some secure hardware may support it with larger value + * of maxUsageCount. + * + * <p>The PackageManger feature flags: + * {@link android.content.pm.PackageManager#FEATURE_KEYSTORE_SINGLE_USE_KEY} and + * {@link android.content.pm.PackageManager#FEATURE_KEYSTORE_LIMITED_USE_KEY} can be used + * to check whether the secure hardware cannot enforce this feature, can only enforce it + * with maxUsageCount = 1, or can enforce it with larger value of maxUsageCount. + * + * @param maxUsageCount maximum number of times the key is allowed to be used or + * {@link KeyProperties#UNRESTRICTED_USAGE_COUNT} if there is no restriction on the + * usage. + */ + @NonNull + public Builder setMaxUsageCount(int maxUsageCount) { + if (maxUsageCount == KeyProperties.UNRESTRICTED_USAGE_COUNT || maxUsageCount > 0) { + mMaxUsageCount = maxUsageCount; + return this; + } + throw new IllegalArgumentException("maxUsageCount is not valid"); + } + + /** * Builds an instance of {@link KeyProtection}. * * @throws IllegalArgumentException if a required field is missing @@ -1066,7 +1122,8 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { mCriticalToDeviceEncryption, mUserConfirmationRequired, mUnlockedDeviceRequired, - mIsStrongBoxBacked); + mIsStrongBoxBacked, + mMaxUsageCount); } } } diff --git a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java index 69c15cca68bf..8163472abdfb 100644 --- a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java @@ -108,6 +108,7 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable { out.writeBoolean(mSpec.isUserConfirmationRequired()); out.writeBoolean(mSpec.isUnlockedDeviceRequired()); out.writeBoolean(mSpec.isCriticalToDeviceEncryption()); + out.writeInt(mSpec.getMaxUsageCount()); } private static Date readDateOrNull(Parcel in) { @@ -166,6 +167,7 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable { final boolean userConfirmationRequired = in.readBoolean(); final boolean unlockedDeviceRequired = in.readBoolean(); final boolean criticalToDeviceEncryption = in.readBoolean(); + final int maxUsageCount = in.readInt(); // The KeyGenParameterSpec is intentionally not constructed using a Builder here: // The intention is for this class to break if new parameters are added to the // KeyGenParameterSpec constructor (whereas using a builder would silently drop them). @@ -199,7 +201,8 @@ public final class ParcelableKeyGenParameterSpec implements Parcelable { isStrongBoxBacked, userConfirmationRequired, unlockedDeviceRequired, - criticalToDeviceEncryption); + criticalToDeviceEncryption, + maxUsageCount); } public static final @android.annotation.NonNull Creator<ParcelableKeyGenParameterSpec> CREATOR = new Creator<ParcelableKeyGenParameterSpec>() { diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java index 233f352989ab..1575bb411562 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java @@ -366,6 +366,13 @@ public abstract class AndroidKeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { )); } + if (spec.getMaxUsageCount() != KeyProperties.UNRESTRICTED_USAGE_COUNT) { + params.add(KeyStore2ParameterUtils.makeInt( + KeymasterDefs.KM_TAG_USAGE_COUNT_LIMIT, + spec.getMaxUsageCount() + )); + } + byte[] additionalEntropy = KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng( mRng, (mKeySizeBits + 7) / 8); diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java index df0e1462a492..6a92980de37c 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java @@ -586,6 +586,13 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato )); } + if (mSpec.getMaxUsageCount() != KeyProperties.UNRESTRICTED_USAGE_COUNT) { + params.add(KeyStore2ParameterUtils.makeInt( + KeymasterDefs.KM_TAG_USAGE_COUNT_LIMIT, + mSpec.getMaxUsageCount() + )); + } + addAlgorithmSpecificParameters(params); if (mSpec.isUniqueIdIncluded()) { diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java index 74503e1eecfa..fe05989c3846 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java @@ -95,6 +95,7 @@ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { boolean userAuthenticationValidWhileOnBody = false; boolean trustedUserPresenceRequired = false; boolean trustedUserConfirmationRequired = false; + int remainingUsageCount = KeyProperties.UNRESTRICTED_USAGE_COUNT; try { for (Authorization a : key.getAuthorizations()) { switch (a.keyParameter.tag) { @@ -195,6 +196,16 @@ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { trustedUserConfirmationRequired = KeyStore2ParameterUtils.isSecureHardware(a.securityLevel); break; + case KeymasterDefs.KM_TAG_USAGE_COUNT_LIMIT: + long remainingUsageCountUnsigned = + KeyStore2ParameterUtils.getUnsignedInt(a); + if (remainingUsageCountUnsigned > Integer.MAX_VALUE) { + throw new ProviderException( + "Usage count of limited use key too long: " + + remainingUsageCountUnsigned); + } + remainingUsageCount = (int) remainingUsageCountUnsigned; + break; } } } catch (IllegalArgumentException e) { @@ -247,7 +258,8 @@ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { trustedUserPresenceRequired, invalidatedByBiometricEnrollment, trustedUserConfirmationRequired, - securityLevel); + securityLevel, + remainingUsageCount); } private static BigInteger getGateKeeperSecureUserId() throws ProviderException { diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java index 07169cedc1d9..8c8acc418a0e 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java @@ -535,6 +535,12 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { spec.getKeyValidityForConsumptionEnd() )); } + if (spec.getMaxUsageCount() != KeyProperties.UNRESTRICTED_USAGE_COUNT) { + importArgs.add(KeyStore2ParameterUtils.makeInt( + KeymasterDefs.KM_TAG_USAGE_COUNT_LIMIT, + spec.getMaxUsageCount() + )); + } } catch (IllegalArgumentException | IllegalStateException e) { throw new KeyStoreException(e); } @@ -772,6 +778,12 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { params.getKeyValidityForConsumptionEnd() )); } + if (params.getMaxUsageCount() != KeyProperties.UNRESTRICTED_USAGE_COUNT) { + importArgs.add(KeyStore2ParameterUtils.makeInt( + KeymasterDefs.KM_TAG_USAGE_COUNT_LIMIT, + params.getMaxUsageCount() + )); + } } catch (IllegalArgumentException | IllegalStateException e) { throw new KeyStoreException(e); } diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java index 33c69511d273..ccb74eb39642 100644 --- a/location/java/android/location/LocationManager.java +++ b/location/java/android/location/LocationManager.java @@ -1445,6 +1445,7 @@ public class LocationManager { @TestApi @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG) @Nullable + @SuppressWarnings("NullableCollection") public List<String> getProviderPackages(@NonNull String provider) { try { return mService.getProviderPackages(provider); diff --git a/native/android/OWNERS b/native/android/OWNERS index ac5a89527ef0..d414ed4cd5e2 100644 --- a/native/android/OWNERS +++ b/native/android/OWNERS @@ -1,3 +1,4 @@ per-file libandroid_net.map.txt, net.c = set noparent per-file libandroid_net.map.txt, net.c = codewiz@google.com, jchalard@google.com, junyulai@google.com per-file libandroid_net.map.txt, net.c = lorenzo@google.com, reminv@google.com, satk@google.com +per-file system_fonts.cpp = file:/graphics/java/android/graphics/fonts/OWNERS diff --git a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java index 987dcc4898b5..06dec9335dee 100644 --- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java +++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java @@ -3231,32 +3231,6 @@ public class ConnectivityManager { } } - /** {@hide} - returns the factory serial number */ - @UnsupportedAppUsage - @RequiresPermission(anyOf = { - NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, - android.Manifest.permission.NETWORK_FACTORY}) - public int registerNetworkFactory(Messenger messenger, String name) { - try { - return mService.registerNetworkFactory(messenger, name); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** {@hide} */ - @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) - @RequiresPermission(anyOf = { - NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, - android.Manifest.permission.NETWORK_FACTORY}) - public void unregisterNetworkFactory(Messenger messenger) { - try { - mService.unregisterNetworkFactory(messenger); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - /** * Registers the specified {@link NetworkProvider}. * Each listener must only be registered once. The listener can be unregistered with diff --git a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl index 1b4d2e413943..db8b7ed3b177 100644 --- a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl +++ b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl @@ -156,9 +156,6 @@ interface IConnectivityManager boolean requestBandwidthUpdate(in Network network); - int registerNetworkFactory(in Messenger messenger, in String name); - void unregisterNetworkFactory(in Messenger messenger); - int registerNetworkProvider(in Messenger messenger, in String name); void unregisterNetworkProvider(in Messenger messenger); diff --git a/packages/Connectivity/framework/src/android/net/NetworkAgent.java b/packages/Connectivity/framework/src/android/net/NetworkAgent.java index d22d82d1f4d0..27aa15d1e1e4 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkAgent.java +++ b/packages/Connectivity/framework/src/android/net/NetworkAgent.java @@ -20,6 +20,7 @@ import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; @@ -775,7 +776,8 @@ public abstract class NetworkAgent { * @param underlyingNetworks the new list of underlying networks. * @see {@link VpnService.Builder#setUnderlyingNetworks(Network[])} */ - public final void setUnderlyingNetworks(@Nullable List<Network> underlyingNetworks) { + public final void setUnderlyingNetworks( + @SuppressLint("NullableCollection") @Nullable List<Network> underlyingNetworks) { final ArrayList<Network> underlyingArray = (underlyingNetworks != null) ? new ArrayList<>(underlyingNetworks) : null; queueOrSendMessage(reg -> reg.sendUnderlyingNetworks(underlyingArray)); diff --git a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java index 8bfa77acd36d..55b2c3c9e11f 100644 --- a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java +++ b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java @@ -1786,6 +1786,15 @@ public final class NetworkCapabilities implements Parcelable { return 0; } + private <T extends Parcelable> void writeParcelableArraySet(Parcel in, + @Nullable ArraySet<T> val, int flags) { + final int size = (val != null) ? val.size() : -1; + in.writeInt(size); + for (int i = 0; i < size; i++) { + in.writeParcelable(val.valueAt(i), flags); + } + } + @Override public void writeToParcel(Parcel dest, int flags) { dest.writeLong(mNetworkCapabilities); @@ -1796,7 +1805,7 @@ public final class NetworkCapabilities implements Parcelable { dest.writeParcelable((Parcelable) mNetworkSpecifier, flags); dest.writeParcelable((Parcelable) mTransportInfo, flags); dest.writeInt(mSignalStrength); - dest.writeArraySet(mUids); + writeParcelableArraySet(dest, mUids, flags); dest.writeString(mSSID); dest.writeBoolean(mPrivateDnsBroken); dest.writeIntArray(getAdministratorUids()); @@ -1819,8 +1828,7 @@ public final class NetworkCapabilities implements Parcelable { netCap.mNetworkSpecifier = in.readParcelable(null); netCap.mTransportInfo = in.readParcelable(null); netCap.mSignalStrength = in.readInt(); - netCap.mUids = (ArraySet<UidRange>) in.readArraySet( - null /* ClassLoader, null for default */); + netCap.mUids = readParcelableArraySet(in, null /* ClassLoader, null for default */); netCap.mSSID = in.readString(); netCap.mPrivateDnsBroken = in.readBoolean(); netCap.setAdministratorUids(in.createIntArray()); @@ -1833,6 +1841,20 @@ public final class NetworkCapabilities implements Parcelable { public NetworkCapabilities[] newArray(int size) { return new NetworkCapabilities[size]; } + + private @Nullable <T extends Parcelable> ArraySet<T> readParcelableArraySet(Parcel in, + @Nullable ClassLoader loader) { + final int size = in.readInt(); + if (size < 0) { + return null; + } + final ArraySet<T> result = new ArraySet<>(size); + for (int i = 0; i < size; i++) { + final T value = in.readParcelable(loader); + result.append(value); + } + return result; + } }; @Override diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java index c5d4fa9f1b40..cb610fc61142 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java @@ -89,15 +89,7 @@ public class SystemSettingsValidators { return value == null || value.length() < MAX_LENGTH; } }); - VALIDATORS.put( - System.FONT_SCALE, - value -> { - try { - return Float.parseFloat(value) >= 0; - } catch (NumberFormatException | NullPointerException e) { - return false; - } - }); + VALIDATORS.put(System.FONT_SCALE, new InclusiveFloatRangeValidator(0.85f, 1.3f)); VALIDATORS.put(System.DIM_SCREEN, BOOLEAN_VALIDATOR); VALIDATORS.put( System.DISPLAY_COLOR_MODE, diff --git a/services/backup/OWNERS b/services/backup/OWNERS index 3c5268c5a2a9..ba2a63abb62d 100644 --- a/services/backup/OWNERS +++ b/services/backup/OWNERS @@ -3,6 +3,7 @@ aabhinav@google.com bryanmawhinney@google.com jstemmer@google.com +millmore@google.com nathch@google.com niagra@google.com niamhfw@google.com diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index f2e192065ed1..bf9d564e434a 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -222,6 +222,7 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.net.Inet4Address; import java.net.InetAddress; +import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; @@ -739,11 +740,11 @@ public class ConnectivityService extends IConnectivityManager.Stub } private void maybeLogBroadcast(NetworkAgentInfo nai, DetailedState state, int type, - boolean isFallbackNetwork) { + boolean isDefaultNetwork) { if (DBG) { log("Sending " + state + " broadcast for type " + type + " " + nai.toShortString() - + " isFallbackNetwork=" + isFallbackNetwork); + + " isDefaultNetwork=" + isDefaultNetwork); } } @@ -762,10 +763,10 @@ public class ConnectivityService extends IConnectivityManager.Stub list.add(nai); } - // Send a broadcast if this is the first network of its type or if it's the fallback. - final boolean isFallbackNetwork = mService.isFallbackNetwork(nai); - if ((list.size() == 1) || isFallbackNetwork) { - maybeLogBroadcast(nai, DetailedState.CONNECTED, type, isFallbackNetwork); + // Send a broadcast if this is the first network of its type or if it's the default. + final boolean isDefaultNetwork = mService.isDefaultNetwork(nai); + if ((list.size() == 1) || isDefaultNetwork) { + maybeLogBroadcast(nai, DetailedState.CONNECTED, type, isDefaultNetwork); mService.sendLegacyNetworkBroadcast(nai, DetailedState.CONNECTED, type); } } @@ -794,7 +795,7 @@ public class ConnectivityService extends IConnectivityManager.Stub ", sending connected broadcast"); final NetworkAgentInfo replacement = list.get(0); maybeLogBroadcast(replacement, DetailedState.CONNECTED, type, - mService.isFallbackNetwork(replacement)); + mService.isDefaultNetwork(replacement)); mService.sendLegacyNetworkBroadcast(replacement, DetailedState.CONNECTED, type); } } @@ -810,14 +811,14 @@ public class ConnectivityService extends IConnectivityManager.Stub // send out another legacy broadcast - currently only used for suspend/unsuspend // toggle public void update(NetworkAgentInfo nai) { - final boolean isFallback = mService.isFallbackNetwork(nai); + final boolean isDefault = mService.isDefaultNetwork(nai); final DetailedState state = nai.networkInfo.getDetailedState(); for (int type = 0; type < mTypeLists.length; type++) { final ArrayList<NetworkAgentInfo> list = mTypeLists[type]; final boolean contains = (list != null && list.contains(nai)); final boolean isFirst = contains && (nai == list.get(0)); - if (isFirst || contains && isFallback) { - maybeLogBroadcast(nai, state, type, isFallback); + if (isFirst || contains && isDefault) { + maybeLogBroadcast(nai, state, type, isDefault); mService.sendLegacyNetworkBroadcast(nai, state, type); } } @@ -990,6 +991,15 @@ public class ConnectivityService extends IConnectivityManager.Stub } /** + * Gets the UID that owns a socket connection. Needed because opening SOCK_DIAG sockets + * requires CAP_NET_ADMIN, which the unit tests do not have. + */ + public int getConnectionOwnerUid(int protocol, InetSocketAddress local, + InetSocketAddress remote) { + return InetDiagMessage.getConnectionOwnerUid(protocol, local, remote); + } + + /** * @see MultinetworkPolicyTracker */ public MultinetworkPolicyTracker makeMultinetworkPolicyTracker( @@ -1022,12 +1032,12 @@ public class ConnectivityService extends IConnectivityManager.Stub mMetricsLog = logger; mNetworkRanker = new NetworkRanker(); - final NetworkRequest fallbackRequest = createDefaultInternetRequestForTransport( + final NetworkRequest defaultInternetRequest = createDefaultInternetRequestForTransport( -1, NetworkRequest.Type.REQUEST); - mFallbackRequest = new NetworkRequestInfo(null, fallbackRequest, new Binder()); - mNetworkRequests.put(fallbackRequest, mFallbackRequest); - mDefaultNetworkRequests.add(mFallbackRequest); - mNetworkRequestInfoLogs.log("REGISTER " + mFallbackRequest); + mDefaultRequest = new NetworkRequestInfo(null, defaultInternetRequest, new Binder()); + mNetworkRequests.put(defaultInternetRequest, mDefaultRequest); + mDefaultNetworkRequests.add(mDefaultRequest); + mNetworkRequestInfoLogs.log("REGISTER " + mDefaultRequest); mDefaultMobileDataRequest = createDefaultInternetRequestForTransport( NetworkCapabilities.TRANSPORT_CELLULAR, NetworkRequest.Type.BACKGROUND_REQUEST); @@ -1217,6 +1227,14 @@ public class ConnectivityService extends IConnectivityManager.Stub mDnsManager = new DnsManager(mContext, mDnsResolver); registerPrivateDnsSettingsCallbacks(); + + mNoServiceNetwork = new NetworkAgentInfo(null, + new Network(NO_SERVICE_NET_ID), + new NetworkInfo(TYPE_NONE, 0, "", ""), + new LinkProperties(), new NetworkCapabilities(), 0, mContext, + null, new NetworkAgentConfig(), this, null, + null, null, 0, INVALID_UID, + mQosCallbackTracker); } private static NetworkCapabilities createDefaultNetworkCapabilitiesForUid(int uid) { @@ -1366,7 +1384,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } private NetworkState getUnfilteredActiveNetworkState(int uid) { - NetworkAgentInfo nai = getFallbackNetwork(); + NetworkAgentInfo nai = getDefaultNetworkForUid(uid); final Network[] networks = getVpnUnderlyingNetworks(uid); if (networks != null) { @@ -1499,7 +1517,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - NetworkAgentInfo nai = getFallbackNetwork(); + NetworkAgentInfo nai = getDefaultNetworkForUid(uid); if (nai == null || isNetworkWithCapabilitiesBlocked(nai.networkCapabilities, uid, ignoreBlocked)) { return null; @@ -1638,21 +1656,28 @@ public class ConnectivityService extends IConnectivityManager.Stub HashMap<Network, NetworkCapabilities> result = new HashMap<>(); - final NetworkAgentInfo nai = getFallbackNetwork(); - NetworkCapabilities nc = getNetworkCapabilitiesInternal(nai); - if (nc != null) { - result.put( - nai.network, - createWithLocationInfoSanitizedIfNecessaryWhenParceled( - nc, mDeps.getCallingUid(), callingPackageName)); + for (final NetworkRequestInfo nri : mDefaultNetworkRequests) { + if (!nri.isBeingSatisfied()) { + continue; + } + final NetworkAgentInfo nai = nri.getSatisfier(); + final NetworkCapabilities nc = getNetworkCapabilitiesInternal(nai); + if (null != nc + && nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED) + && !result.containsKey(nai.network)) { + result.put( + nai.network, + createWithLocationInfoSanitizedIfNecessaryWhenParceled( + nc, mDeps.getCallingUid(), callingPackageName)); + } } // No need to check mLockdownEnabled. If it's true, getVpnUnderlyingNetworks returns null. final Network[] networks = getVpnUnderlyingNetworks(Binder.getCallingUid()); - if (networks != null) { - for (Network network : networks) { - nc = getNetworkCapabilitiesInternal(network); - if (nc != null) { + if (null != networks) { + for (final Network network : networks) { + final NetworkCapabilities nc = getNetworkCapabilitiesInternal(network); + if (null != nc) { result.put( network, createWithLocationInfoSanitizedIfNecessaryWhenParceled( @@ -1674,9 +1699,7 @@ public class ConnectivityService extends IConnectivityManager.Stub /** * Return LinkProperties for the active (i.e., connected) default - * network interface. It is assumed that at most one default network - * is active at a time. If more than one is active, it is indeterminate - * which will be returned. + * network interface for the calling uid. * @return the ip properties for the active network, or {@code null} if * none is active */ @@ -2025,7 +2048,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // TODO: Move the Dns Event to NetworkMonitor. NetdEventListenerService only allow one // callback from each caller type. Need to re-factor NetdEventListenerService to allow // multiple NetworkMonitor registrants. - if (nai != null && nai.satisfies(mFallbackRequest.mRequests.get(0))) { + if (nai != null && nai.satisfies(mDefaultRequest.mRequests.get(0))) { nai.networkMonitor().notifyDnsResponse(returnCode); } } @@ -2119,8 +2142,8 @@ public class ConnectivityService extends IConnectivityManager.Stub private boolean isUidBlockedByRules(int uid, int uidRules, boolean isNetworkMetered, boolean isBackgroundRestricted) { - return NetworkPolicyManagerInternal.isUidNetworkingBlocked(uid, uidRules, - isNetworkMetered, isBackgroundRestricted); + return mPolicyManager.checkUidNetworkingBlocked(uid, uidRules, isNetworkMetered, + isBackgroundRestricted); } /** @@ -2582,12 +2605,12 @@ public class ConnectivityService extends IConnectivityManager.Stub pw.println(); pw.println(); - final NetworkAgentInfo fallbackNai = getFallbackNetwork(); + final NetworkAgentInfo defaultNai = getDefaultNetwork(); pw.print("Active default network: "); - if (fallbackNai == null) { + if (defaultNai == null) { pw.println("none"); } else { - pw.println(fallbackNai.network.getNetId()); + pw.println(defaultNai.network.getNetId()); } pw.println(); @@ -2703,9 +2726,9 @@ public class ConnectivityService extends IConnectivityManager.Stub pw.println(nai.requestAt(i).toString()); } pw.decreaseIndent(); - pw.println("Lingered:"); + pw.println("Inactivity Timers:"); pw.increaseIndent(); - nai.dumpLingerTimers(pw); + nai.dumpInactivityTimers(pw); pw.decreaseIndent(); pw.decreaseIndent(); } @@ -2970,7 +2993,7 @@ public class ConnectivityService extends IConnectivityManager.Stub final boolean valid = ((testResult & NETWORK_VALIDATION_RESULT_VALID) != 0); final boolean wasValidated = nai.lastValidated; - final boolean wasFallback = isFallbackNetwork(nai); + final boolean wasDefault = isDefaultNetwork(nai); if (DBG) { final String logMsg = !TextUtils.isEmpty(redirectUrl) @@ -2979,7 +3002,7 @@ public class ConnectivityService extends IConnectivityManager.Stub log(nai.toShortString() + " validation " + (valid ? "passed" : "failed") + logMsg); } if (valid != nai.lastValidated) { - if (wasFallback) { + if (wasDefault) { mMetricsLog.logDefaultNetworkValidity(valid); } final int oldScore = nai.getCurrentScore(); @@ -3300,27 +3323,27 @@ public class ConnectivityService extends IConnectivityManager.Stub } /** - * Updates the linger state from the network requests inside the NAI. + * Updates the inactivity state from the network requests inside the NAI. * @param nai the agent info to update * @param now the timestamp of the event causing this update - * @return whether the network was lingered as a result of this update + * @return whether the network was inactive as a result of this update */ - private boolean updateLingerState(@NonNull final NetworkAgentInfo nai, final long now) { - // 1. Update the linger timer. If it's changed, reschedule or cancel the alarm. - // 2. If the network was lingering and there are now requests, unlinger it. + private boolean updateInactivityState(@NonNull final NetworkAgentInfo nai, final long now) { + // 1. Update the inactivity timer. If it's changed, reschedule or cancel the alarm. + // 2. If the network was inactive and there are now requests, unset inactive. // 3. If this network is unneeded (which implies it is not lingering), and there is at least - // one lingered request, start lingering. - nai.updateLingerTimer(); + // one lingered request, set inactive. + nai.updateInactivityTimer(); if (nai.isLingering() && nai.numForegroundNetworkRequests() > 0) { - if (DBG) log("Unlingering " + nai.toShortString()); - nai.unlinger(); + if (DBG) log("Unsetting inactive " + nai.toShortString()); + nai.unsetInactive(); logNetworkEvent(nai, NetworkEvent.NETWORK_UNLINGER); - } else if (unneeded(nai, UnneededFor.LINGER) && nai.getLingerExpiry() > 0) { + } else if (unneeded(nai, UnneededFor.LINGER) && nai.getInactivityExpiry() > 0) { if (DBG) { - final int lingerTime = (int) (nai.getLingerExpiry() - now); - log("Lingering " + nai.toShortString() + " for " + lingerTime + "ms"); + final int lingerTime = (int) (nai.getInactivityExpiry() - now); + log("Setting inactive " + nai.toShortString() + " for " + lingerTime + "ms"); } - nai.linger(); + nai.setInactive(); logNetworkEvent(nai, NetworkEvent.NETWORK_LINGER); return true; } @@ -3334,7 +3357,6 @@ public class ConnectivityService extends IConnectivityManager.Stub if (VDBG) log("NetworkFactory connected"); // Finish setting up the full connection NetworkProviderInfo npi = mNetworkProviderInfos.get(msg.replyTo); - npi.completeConnection(); sendAllRequestsToProvider(npi); } else { loge("Error connecting NetworkFactory"); @@ -3355,13 +3377,13 @@ public class ConnectivityService extends IConnectivityManager.Stub loge("Error connecting NetworkAgent"); mNetworkAgentInfos.remove(nai); if (nai != null) { - final boolean wasFallback = isFallbackNetwork(nai); + final boolean wasDefault = isDefaultNetwork(nai); synchronized (mNetworkForNetId) { mNetworkForNetId.remove(nai.network.getNetId()); } mNetIdManager.releaseNetId(nai.network.getNetId()); // Just in case. - mLegacyTypeTracker.remove(nai, wasFallback); + mLegacyTypeTracker.remove(nai, wasDefault); } } } @@ -3400,8 +3422,8 @@ public class ConnectivityService extends IConnectivityManager.Stub nai.networkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, null, null); } - final boolean wasFallback = isFallbackNetwork(nai); - if (wasFallback) { + final boolean wasDefault = isDefaultNetwork(nai); + if (wasDefault) { mDefaultInetConditionPublished = 0; // Log default network disconnection before required book-keeping. // Let rematchAllNetworksAndRequests() below record a new default network event @@ -3436,17 +3458,20 @@ public class ConnectivityService extends IConnectivityManager.Stub propagateUnderlyingNetworkCapabilities(nai.network); // Remove all previously satisfied requests. for (int i = 0; i < nai.numNetworkRequests(); i++) { - NetworkRequest request = nai.requestAt(i); + final NetworkRequest request = nai.requestAt(i); final NetworkRequestInfo nri = mNetworkRequests.get(request); final NetworkAgentInfo currentNetwork = nri.getSatisfier(); if (currentNetwork != null && currentNetwork.network.getNetId() == nai.network.getNetId()) { + // uid rules for this network will be removed in destroyNativeNetwork(nai). nri.setSatisfier(null, null); - sendUpdatedScoreToFactories(request, null); + if (request.isRequest()) { + sendUpdatedScoreToFactories(request, null); + } - if (mFallbackRequest == nri) { + if (mDefaultRequest == nri) { // TODO : make battery stats aware that since 2013 multiple interfaces may be - // active at the same time. For now keep calling this with the fallback + // active at the same time. For now keep calling this with the default // network, because while incorrect this is the closest to the old (also // incorrect) behavior. mNetworkActivityTracker.updateDataActivityTracking( @@ -3456,11 +3481,11 @@ public class ConnectivityService extends IConnectivityManager.Stub } } } - nai.clearLingerState(); + nai.clearInactivityState(); // TODO: mLegacyTypeTracker.remove seems redundant given there's a full rematch right after. - // Currently, deleting it breaks tests that check for the fallback network disconnecting. + // Currently, deleting it breaks tests that check for the default network disconnecting. // Find out why, fix the rematch code, and delete this. - mLegacyTypeTracker.remove(nai, wasFallback); + mLegacyTypeTracker.remove(nai, wasDefault); rematchAllNetworksAndRequests(); mLingerMonitor.noteDisconnect(nai); if (nai.created) { @@ -3468,10 +3493,9 @@ public class ConnectivityService extends IConnectivityManager.Stub // (routing rules, DNS, etc). // This may be slow as it requires a lot of netd shelling out to ip and // ip[6]tables to flush routes and remove the incoming packet mark rule, so do it - // after we've rematched networks with requests which should make a potential - // fallback network the default or requested a new network from the - // NetworkProviders, so network traffic isn't interrupted for an unnecessarily - // long time. + // after we've rematched networks with requests (which might change the default + // network or service a new request from an app), so network traffic isn't interrupted + // for an unnecessarily long time. destroyNativeNetwork(nai); mDnsManager.removeNetwork(nai.network); } @@ -3556,8 +3580,8 @@ public class ConnectivityService extends IConnectivityManager.Stub } } rematchAllNetworksAndRequests(); - // If an active request exists, return as its score has already been sent if needed. - if (null != nri.getActiveRequest()) { + // If the nri is satisfied, return as its score has already been sent if needed. + if (nri.isBeingSatisfied()) { return; } @@ -3700,7 +3724,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (mNetworkRequests.get(nri.mRequests.get(0)) == null) { return; } - if (nri.getSatisfier() != null) { + if (nri.isBeingSatisfied()) { return; } if (VDBG || (DBG && nri.mRequests.get(0).isRequest())) { @@ -3799,7 +3823,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // If there are still lingered requests on this network, don't tear it down, // but resume lingering instead. final long now = SystemClock.elapsedRealtime(); - if (updateLingerState(nai, now)) { + if (updateInactivityState(nai, now)) { notifyNetworkLosing(nai, now); } if (unneeded(nai, UnneededFor.TEARDOWN)) { @@ -4260,7 +4284,7 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public NetworkRequest getDefaultRequest() { - return mFallbackRequest.mRequests.get(0); + return mDefaultRequest.mRequests.get(0); } private class InternalHandler extends Handler { @@ -4506,7 +4530,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // revalidate the network and generate a ConnectivityDiagnostics ConnectivityReport event. final NetworkAgentInfo nai; if (network == null) { - nai = getFallbackNetwork(); + nai = getDefaultNetwork(); } else { nai = getNetworkAgentInfoForNetwork(network); } @@ -4525,7 +4549,7 @@ public class ConnectivityService extends IConnectivityManager.Stub Network network, int uid, boolean hasConnectivity) { final NetworkAgentInfo nai; if (network == null) { - nai = getFallbackNetwork(); + nai = getDefaultNetwork(); } else { nai = getNetworkAgentInfoForNetwork(network); } @@ -4891,7 +4915,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // see VpnService.setUnderlyingNetworks()'s javadoc about how to interpret // the underlyingNetworks list. if (underlyingNetworks == null) { - final NetworkAgentInfo defaultNai = getFallbackNetwork(); + final NetworkAgentInfo defaultNai = getDefaultNetworkForUid( + nai.networkCapabilities.getOwnerUid()); if (defaultNai != null) { underlyingNetworks = new Network[] { defaultNai.network }; } @@ -4942,8 +4967,10 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - private Network[] underlyingNetworksOrDefault(Network[] underlyingNetworks) { - final Network defaultNetwork = getNetwork(getFallbackNetwork()); + // TODO This needs to be the default network that applies to the NAI. + private Network[] underlyingNetworksOrDefault(final int ownerUid, + Network[] underlyingNetworks) { + final Network defaultNetwork = getNetwork(getDefaultNetworkForUid(ownerUid)); if (underlyingNetworks == null && defaultNetwork != null) { // null underlying networks means to track the default. underlyingNetworks = new Network[] { defaultNetwork }; @@ -4956,7 +4983,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // TODO: support more than one level of underlying networks, either via a fixed-depth search // (e.g., 2 levels of underlying networks), or via loop detection, or.... if (!nai.supportsUnderlyingNetworks()) return false; - final Network[] underlying = underlyingNetworksOrDefault(nai.declaredUnderlyingNetworks); + final Network[] underlying = underlyingNetworksOrDefault( + nai.networkCapabilities.getOwnerUid(), nai.declaredUnderlyingNetworks); return ArrayUtils.contains(underlying, network); } @@ -5414,27 +5442,21 @@ public class ConnectivityService extends IConnectivityManager.Stub private static class NetworkProviderInfo { public final String name; public final Messenger messenger; - private final AsyncChannel mAsyncChannel; private final IBinder.DeathRecipient mDeathRecipient; public final int providerId; NetworkProviderInfo(String name, Messenger messenger, AsyncChannel asyncChannel, - int providerId, IBinder.DeathRecipient deathRecipient) { + int providerId, @NonNull IBinder.DeathRecipient deathRecipient) { this.name = name; this.messenger = messenger; this.providerId = providerId; - mAsyncChannel = asyncChannel; mDeathRecipient = deathRecipient; - if ((mAsyncChannel == null) == (mDeathRecipient == null)) { - throw new AssertionError("Must pass exactly one of asyncChannel or deathRecipient"); + if (mDeathRecipient == null) { + throw new AssertionError("Must pass a deathRecipient"); } } - boolean isLegacyNetworkFactory() { - return mAsyncChannel != null; - } - void sendMessageToNetworkProvider(int what, int arg1, int arg2, Object obj) { try { messenger.send(Message.obtain(null /* handler */, what, arg1, arg2, obj)); @@ -5445,38 +5467,19 @@ public class ConnectivityService extends IConnectivityManager.Stub } void requestNetwork(NetworkRequest request, int score, int servingProviderId) { - if (isLegacyNetworkFactory()) { - mAsyncChannel.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK, score, - servingProviderId, request); - } else { - sendMessageToNetworkProvider(NetworkProvider.CMD_REQUEST_NETWORK, score, + sendMessageToNetworkProvider(NetworkProvider.CMD_REQUEST_NETWORK, score, servingProviderId, request); - } } void cancelRequest(NetworkRequest request) { - if (isLegacyNetworkFactory()) { - mAsyncChannel.sendMessage(android.net.NetworkFactory.CMD_CANCEL_REQUEST, request); - } else { - sendMessageToNetworkProvider(NetworkProvider.CMD_CANCEL_REQUEST, 0, 0, request); - } + sendMessageToNetworkProvider(NetworkProvider.CMD_CANCEL_REQUEST, 0, 0, request); } void connect(Context context, Handler handler) { - if (isLegacyNetworkFactory()) { - mAsyncChannel.connect(context, handler, messenger); - } else { - try { - messenger.getBinder().linkToDeath(mDeathRecipient, 0); - } catch (RemoteException e) { - mDeathRecipient.binderDied(); - } - } - } - - void completeConnection() { - if (isLegacyNetworkFactory()) { - mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION); + try { + messenger.getBinder().linkToDeath(mDeathRecipient, 0); + } catch (RemoteException e) { + mDeathRecipient.binderDied(); } } } @@ -5512,9 +5515,8 @@ public class ConnectivityService extends IConnectivityManager.Stub mActiveRequest = activeRequest; } - // The network currently satisfying this request, or null if none. Must only be touched - // on the handler thread. This only makes sense for network requests and not for listens, - // as defined by NetworkRequest#isRequest(). For listens, this is always null. + // The network currently satisfying this NRI. Only one request in an NRI can have a + // satisfier. For non-multilayer requests, only REQUEST-type requests can have a satisfier. @Nullable private NetworkAgentInfo mSatisfier; NetworkAgentInfo getSatisfier() { @@ -5537,6 +5539,18 @@ public class ConnectivityService extends IConnectivityManager.Stub final int mUid; final Messenger messenger; + /** + * Get the list of UIDs this nri applies to. + */ + @NonNull + private Set<UidRange> getUids() { + // networkCapabilities.getUids() returns a defensive copy. + // multilayer requests will all have the same uids so return the first one. + final Set<UidRange> uids = null == mRequests.get(0).networkCapabilities.getUids() + ? new ArraySet<>() : mRequests.get(0).networkCapabilities.getUids(); + return uids; + } + NetworkRequestInfo(NetworkRequest r, PendingIntent pi) { mRequests = initializeRequests(r); ensureAllNetworkRequestsHaveType(mRequests); @@ -5570,6 +5584,13 @@ public class ConnectivityService extends IConnectivityManager.Stub this(r, null); } + // True if this NRI is being satisfied. It also accounts for if the nri has its satisifer + // set to the mNoServiceNetwork in which case mActiveRequest will be null thus returning + // false. + boolean isBeingSatisfied() { + return (null != mSatisfier && null != mActiveRequest); + } + boolean isMultilayerRequest() { return mRequests.size() > 1; } @@ -5595,7 +5616,9 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public String toString() { - return "uid/pid:" + mUid + "/" + mPid + " " + mRequests + return "uid/pid:" + mUid + "/" + mPid + " active request Id: " + + (mActiveRequest == null ? null : mActiveRequest.requestId) + + " " + mRequests + (mPendingIntent == null ? "" : " to trigger " + mPendingIntent); } } @@ -5951,15 +5974,6 @@ public class ConnectivityService extends IConnectivityManager.Stub EVENT_RELEASE_NETWORK_REQUEST, getCallingUid(), 0, networkRequest)); } - @Override - public int registerNetworkFactory(Messenger messenger, String name) { - enforceNetworkFactoryPermission(); - NetworkProviderInfo npi = new NetworkProviderInfo(name, messenger, new AsyncChannel(), - nextNetworkProviderId(), null /* deathRecipient */); - mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_PROVIDER, npi)); - return npi.providerId; - } - private void handleRegisterNetworkProvider(NetworkProviderInfo npi) { if (mNetworkProviderInfos.containsKey(npi.messenger)) { // Avoid creating duplicates. even if an app makes a direct AIDL call. @@ -5973,10 +5987,7 @@ public class ConnectivityService extends IConnectivityManager.Stub if (DBG) log("Got NetworkProvider Messenger for " + npi.name); mNetworkProviderInfos.put(npi.messenger, npi); npi.connect(mContext, mTrackerHandler); - if (!npi.isLegacyNetworkFactory()) { - // Legacy NetworkFactories get their requests when their AsyncChannel connects. - sendAllRequestsToProvider(npi); - } + sendAllRequestsToProvider(npi); } @Override @@ -5995,11 +6006,6 @@ public class ConnectivityService extends IConnectivityManager.Stub mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_NETWORK_PROVIDER, messenger)); } - @Override - public void unregisterNetworkFactory(Messenger messenger) { - unregisterNetworkProvider(messenger); - } - private void handleUnregisterNetworkProvider(Messenger messenger) { NetworkProviderInfo npi = mNetworkProviderInfos.remove(messenger); if (npi == null) { @@ -6050,11 +6056,31 @@ public class ConnectivityService extends IConnectivityManager.Stub // The always-on request for an Internet-capable network that apps without a specific default // fall back to. @NonNull - private final NetworkRequestInfo mFallbackRequest; + private final NetworkRequestInfo mDefaultRequest; // Collection of NetworkRequestInfo's used for default networks. @NonNull private final ArraySet<NetworkRequestInfo> mDefaultNetworkRequests = new ArraySet<>(); + private boolean isPerAppDefaultRequest(@NonNull final NetworkRequestInfo nri) { + return (mDefaultNetworkRequests.contains(nri) && mDefaultRequest != nri); + } + + /** + * Determine if an nri is a managed default request that disallows default networking. + * @param nri the request to evaluate + * @return true if device-default networking is disallowed + */ + private boolean isDefaultBlocked(@NonNull final NetworkRequestInfo nri) { + // Check if this nri is a managed default that supports the default network at its + // lowest priority request. + final NetworkRequest defaultNetworkRequest = mDefaultRequest.mRequests.get(0); + final NetworkCapabilities lowestPriorityNetCap = + nri.mRequests.get(nri.mRequests.size() - 1).networkCapabilities; + return isPerAppDefaultRequest(nri) + && !(defaultNetworkRequest.networkCapabilities.equalRequestableCapabilities( + lowestPriorityNetCap)); + } + // Request used to optionally keep mobile data active even when higher // priority networks like Wi-Fi are active. private final NetworkRequest mDefaultMobileDataRequest; @@ -6066,10 +6092,37 @@ public class ConnectivityService extends IConnectivityManager.Stub // Request used to optionally keep vehicle internal network always active private final NetworkRequest mDefaultVehicleRequest; - // TODO: b/178729499 update this in favor of a method taking in a UID. - // The NetworkAgentInfo currently satisfying the fallback request, if any. - private NetworkAgentInfo getFallbackNetwork() { - return mFallbackRequest.mSatisfier; + // TODO replace with INetd.DUMMY_NET_ID when available. + private static final int NO_SERVICE_NET_ID = 51; + // Sentinel NAI used to direct apps with default networks that should have no connectivity to a + // network with no service. This NAI should never be matched against, nor should any public API + // ever return the associated network. For this reason, this NAI is not in the list of available + // NAIs. It is used in computeNetworkReassignment() to be set as the satisfier for non-device + // default requests that don't support using the device default network which will ultimately + // allow ConnectivityService to use this no-service network when calling makeDefaultForApps(). + @VisibleForTesting + final NetworkAgentInfo mNoServiceNetwork; + + // The NetworkAgentInfo currently satisfying the default request, if any. + private NetworkAgentInfo getDefaultNetwork() { + return mDefaultRequest.mSatisfier; + } + + private NetworkAgentInfo getDefaultNetworkForUid(final int uid) { + for (final NetworkRequestInfo nri : mDefaultNetworkRequests) { + // Currently, all network requests will have the same uids therefore checking the first + // one is sufficient. If/when uids are tracked at the nri level, this can change. + final Set<UidRange> uids = nri.mRequests.get(0).networkCapabilities.getUids(); + if (null == uids) { + continue; + } + for (final UidRange range : uids) { + if (range.contains(uid)) { + return nri.getSatisfier(); + } + } + } + return getDefaultNetwork(); } @Nullable @@ -6086,8 +6139,8 @@ public class ConnectivityService extends IConnectivityManager.Stub } @VisibleForTesting - protected boolean isFallbackNetwork(NetworkAgentInfo nai) { - return nai == getFallbackNetwork(); + protected boolean isDefaultNetwork(NetworkAgentInfo nai) { + return nai == getDefaultNetwork(); } // TODO : remove this method. It's a stopgap measure to help sheperding a number of dependent @@ -6156,8 +6209,6 @@ public class ConnectivityService extends IConnectivityManager.Stub LinkProperties lp = new LinkProperties(linkProperties); - // TODO: Instead of passing mFallbackRequest, provide an API to determine whether a Network - // satisfies mFallbackRequest. final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities); final NetworkAgentInfo nai = new NetworkAgentInfo(na, new Network(mNetIdManager.reserveNetId()), new NetworkInfo(networkInfo), lp, nc, @@ -6234,7 +6285,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // for (LinkProperties lp : newLp.getStackedLinks()) { // updateMtu(lp, null); // } - if (isFallbackNetwork(networkAgent)) { + if (isDefaultNetwork(networkAgent)) { updateTcpBufferSizes(newLp.getTcpBufferSizes()); } @@ -6246,7 +6297,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // updateDnses will fetch the private DNS configuration from DnsManager. mDnsManager.updatePrivateDnsStatus(netId, newLp); - if (isFallbackNetwork(networkAgent)) { + if (isDefaultNetwork(networkAgent)) { handleApplyDefaultProxy(newLp.getHttpProxy()); } else { updateProxy(newLp, oldLp); @@ -6580,7 +6631,8 @@ public class ConnectivityService extends IConnectivityManager.Stub @VisibleForTesting void applyUnderlyingCapabilities(@Nullable Network[] underlyingNetworks, @NonNull NetworkCapabilities agentCaps, @NonNull NetworkCapabilities newNc) { - underlyingNetworks = underlyingNetworksOrDefault(underlyingNetworks); + underlyingNetworks = underlyingNetworksOrDefault( + agentCaps.getOwnerUid(), underlyingNetworks); int[] transportTypes = agentCaps.getTransportTypes(); int downKbps = NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED; int upKbps = NetworkCapabilities.LINK_BANDWIDTH_UNSPECIFIED; @@ -7186,7 +7238,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // If we get here it means that the last linger timeout for this network expired. So there // must be no other active linger timers, and we must stop lingering. - oldNetwork.clearLingerState(); + oldNetwork.clearInactivityState(); if (unneeded(oldNetwork, UnneededFor.TEARDOWN)) { // Tear the network down. @@ -7224,21 +7276,20 @@ public class ConnectivityService extends IConnectivityManager.Stub log("Switching to new default network for: " + nri + " using " + newDefaultNetwork); } - try { - // TODO http://b/176191930 update netd calls in follow-up CL for multinetwork changes. - if (mFallbackRequest != nri) { - return; - } + // Fix up the NetworkCapabilities of any networks that have this network as underlying. + if (newDefaultNetwork != null) { + propagateUnderlyingNetworkCapabilities(newDefaultNetwork.network); + } - if (null != newDefaultNetwork) { - mNetd.networkSetDefault(newDefaultNetwork.network.getNetId()); - } else { - mNetd.networkClearDefault(); - } - } catch (RemoteException | ServiceSpecificException e) { - loge("Exception setting default network :" + e); + // Set an app level managed default and return since further processing only applies to the + // default network. + if (mDefaultRequest != nri) { + makeDefaultForApps(nri, oldDefaultNetwork, newDefaultNetwork); + return; } + makeDefaultNetwork(newDefaultNetwork); + if (oldDefaultNetwork != null) { mLingerMonitor.noteLingerDefaultNetwork(oldDefaultNetwork, newDefaultNetwork); } @@ -7249,10 +7300,6 @@ public class ConnectivityService extends IConnectivityManager.Stub updateTcpBufferSizes(null != newDefaultNetwork ? newDefaultNetwork.linkProperties.getTcpBufferSizes() : null); notifyIfacesChangedForNetworkStats(); - // Fix up the NetworkCapabilities of any networks that have this network as underlying. - if (newDefaultNetwork != null) { - propagateUnderlyingNetworkCapabilities(newDefaultNetwork.network); - } // Log 0 -> X and Y -> X default network transitions, where X is the new default. final Network network = (newDefaultNetwork != null) ? newDefaultNetwork.network : null; @@ -7276,6 +7323,49 @@ public class ConnectivityService extends IConnectivityManager.Stub prevNetwork, prevScore, prevLp, prevNc); } + private void makeDefaultForApps(@NonNull final NetworkRequestInfo nri, + @Nullable final NetworkAgentInfo oldDefaultNetwork, + @Nullable final NetworkAgentInfo newDefaultNetwork) { + try { + if (VDBG) { + log("Setting default network for " + nri + + " using UIDs " + nri.getUids() + + " with old network " + (oldDefaultNetwork != null + ? oldDefaultNetwork.network().getNetId() : "null") + + " and new network " + (newDefaultNetwork != null + ? newDefaultNetwork.network().getNetId() : "null")); + } + if (nri.getUids().isEmpty()) { + throw new IllegalStateException("makeDefaultForApps called without specifying" + + " any applications to set as the default." + nri); + } + if (null != newDefaultNetwork) { + mNetd.networkAddUidRanges( + newDefaultNetwork.network.getNetId(), + toUidRangeStableParcels(nri.getUids())); + } + if (null != oldDefaultNetwork) { + mNetd.networkRemoveUidRanges( + oldDefaultNetwork.network.getNetId(), + toUidRangeStableParcels(nri.getUids())); + } + } catch (RemoteException | ServiceSpecificException e) { + loge("Exception setting OEM network preference default network :" + e); + } + } + + private void makeDefaultNetwork(@Nullable final NetworkAgentInfo newDefaultNetwork) { + try { + if (null != newDefaultNetwork) { + mNetd.networkSetDefault(newDefaultNetwork.network.getNetId()); + } else { + mNetd.networkClearDefault(); + } + } catch (RemoteException | ServiceSpecificException e) { + loge("Exception setting default network :" + e); + } + } + private void processListenRequests(@NonNull final NetworkAgentInfo nai) { // For consistency with previous behaviour, send onLost callbacks before onAvailable. processNewlyLostListenRequests(nai); @@ -7397,9 +7487,9 @@ public class ConnectivityService extends IConnectivityManager.Stub @Nullable final NetworkAgentInfo previousSatisfier, @Nullable final NetworkAgentInfo newSatisfier, final long now) { - if (newSatisfier != null) { + if (null != newSatisfier && mNoServiceNetwork != newSatisfier) { if (VDBG) log("rematch for " + newSatisfier.toShortString()); - if (previousSatisfier != null) { + if (null != previousSatisfier && mNoServiceNetwork != previousSatisfier) { if (VDBG || DDBG) { log(" accepting network in place of " + previousSatisfier.toShortString()); } @@ -7413,7 +7503,7 @@ public class ConnectivityService extends IConnectivityManager.Stub Log.wtf(TAG, "BUG: " + newSatisfier.toShortString() + " already has " + newRequest); } - } else { + } else if (null != previousSatisfier) { if (DBG) { log("Network " + previousSatisfier.toShortString() + " stopped satisfying" + " request " + previousRequest.requestId); @@ -7464,7 +7554,11 @@ public class ConnectivityService extends IConnectivityManager.Stub break; } } - if (bestNetwork != nri.mSatisfier) { + if (null == bestNetwork && isDefaultBlocked(nri)) { + // Remove default networking if disallowed for managed default requests. + bestNetwork = mNoServiceNetwork; + } + if (nri.getSatisfier() != bestNetwork) { // bestNetwork may be null if no network can satisfy this request. changes.addRequestReassignment(new NetworkReassignment.RequestReassignment( nri, nri.mActiveRequest, bestRequest, nri.getSatisfier(), bestNetwork)); @@ -7557,7 +7651,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // if the state has not changed : the source of truth is controlled with // NetworkAgentInfo#lingerRequest and NetworkAgentInfo#unlingerRequest, which have been // called while rematching the individual networks above. - if (updateLingerState(nai, now)) { + if (updateInactivityState(nai, now)) { lingeredNetworks.add(nai); } } @@ -7584,7 +7678,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // Tear down all unneeded networks. for (NetworkAgentInfo nai : mNetworkAgentInfos) { if (unneeded(nai, UnneededFor.TEARDOWN)) { - if (nai.getLingerExpiry() > 0) { + if (nai.getInactivityExpiry() > 0) { // This network has active linger timers and no requests, but is not // lingering. Linger it. // @@ -7592,7 +7686,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // and became unneeded due to another network improving its score to the // point where this network will no longer be able to satisfy any requests // even if it validates. - if (updateLingerState(nai, now)) { + if (updateInactivityState(nai, now)) { notifyNetworkLosing(nai, now); } } else { @@ -7624,34 +7718,34 @@ public class ConnectivityService extends IConnectivityManager.Stub private void updateLegacyTypeTrackerAndVpnLockdownForRematch( @NonNull final NetworkReassignment changes, @NonNull final Collection<NetworkAgentInfo> nais) { - final NetworkReassignment.RequestReassignment fallbackReassignment = - changes.getReassignment(mFallbackRequest); - final NetworkAgentInfo oldFallbackNetwork = - null != fallbackReassignment ? fallbackReassignment.mOldNetwork : null; - final NetworkAgentInfo newFallbackNetwork = - null != fallbackReassignment ? fallbackReassignment.mNewNetwork : null; - - if (oldFallbackNetwork != newFallbackNetwork) { + final NetworkReassignment.RequestReassignment reassignmentOfDefault = + changes.getReassignment(mDefaultRequest); + final NetworkAgentInfo oldDefaultNetwork = + null != reassignmentOfDefault ? reassignmentOfDefault.mOldNetwork : null; + final NetworkAgentInfo newDefaultNetwork = + null != reassignmentOfDefault ? reassignmentOfDefault.mNewNetwork : null; + + if (oldDefaultNetwork != newDefaultNetwork) { // Maintain the illusion : since the legacy API only understands one network at a time, // if the default network changed, apps should see a disconnected broadcast for the // old default network before they see a connected broadcast for the new one. - if (oldFallbackNetwork != null) { - mLegacyTypeTracker.remove(oldFallbackNetwork.networkInfo.getType(), - oldFallbackNetwork, true); + if (oldDefaultNetwork != null) { + mLegacyTypeTracker.remove(oldDefaultNetwork.networkInfo.getType(), + oldDefaultNetwork, true); } - if (newFallbackNetwork != null) { + if (newDefaultNetwork != null) { // The new default network can be newly null if and only if the old default // network doesn't satisfy the default request any more because it lost a // capability. - mDefaultInetConditionPublished = newFallbackNetwork.lastValidated ? 100 : 0; + mDefaultInetConditionPublished = newDefaultNetwork.lastValidated ? 100 : 0; mLegacyTypeTracker.add( - newFallbackNetwork.networkInfo.getType(), newFallbackNetwork); + newDefaultNetwork.networkInfo.getType(), newDefaultNetwork); // If the legacy VPN is connected, notifyLockdownVpn may end up sending a broadcast // to reflect the NetworkInfo of this new network. This broadcast has to be sent // after the disconnect broadcasts above, but before the broadcasts sent by the // legacy type tracker below. // TODO : refactor this, it's too complex - notifyLockdownVpn(newFallbackNetwork); + notifyLockdownVpn(newDefaultNetwork); } } @@ -7686,7 +7780,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } // A VPN generally won't get added to the legacy tracker in the "for (nri)" loop above, - // because usually there are no NetworkRequests it satisfies (e.g., mFallbackRequest + // because usually there are no NetworkRequests it satisfies (e.g., mDefaultRequest // wants the NOT_VPN capability, so it will never be satisfied by a VPN). So, add the // newNetwork to the tracker explicitly (it's a no-op if it has already been added). if (nai.isVPN()) { @@ -7697,9 +7791,9 @@ public class ConnectivityService extends IConnectivityManager.Stub private void updateInetCondition(NetworkAgentInfo nai) { // Don't bother updating until we've graduated to validated at least once. if (!nai.everValidated) return; - // For now only update icons for the fallback connection. + // For now only update icons for the default connection. // TODO: Update WiFi and cellular icons separately. b/17237507 - if (!isFallbackNetwork(nai)) return; + if (!isDefaultNetwork(nai)) return; int newInetCondition = nai.lastValidated ? 100 : 0; // Don't repeat publish. @@ -7869,7 +7963,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // Notify the requests on this NAI that the network is now lingered. private void notifyNetworkLosing(@NonNull final NetworkAgentInfo nai, final long now) { - final int lingerTime = (int) (nai.getLingerExpiry() - now); + final int lingerTime = (int) (nai.getInactivityExpiry() - now); notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_LOSING, lingerTime); } @@ -7967,8 +8061,8 @@ public class ConnectivityService extends IConnectivityManager.Stub intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, info.getExtraInfo()); } NetworkAgentInfo newDefaultAgent = null; - if (nai.isSatisfyingRequest(mFallbackRequest.mRequests.get(0).requestId)) { - newDefaultAgent = getFallbackNetwork(); + if (nai.isSatisfyingRequest(mDefaultRequest.mRequests.get(0).requestId)) { + newDefaultAgent = mDefaultRequest.getSatisfier(); if (newDefaultAgent != null) { intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO, newDefaultAgent.networkInfo); @@ -8016,9 +8110,14 @@ public class ConnectivityService extends IConnectivityManager.Stub private Network[] getDefaultNetworks() { ensureRunningOnConnectivityServiceThread(); final ArrayList<Network> defaultNetworks = new ArrayList<>(); - final NetworkAgentInfo fallbackNetwork = getFallbackNetwork(); + final Set<Integer> activeNetIds = new ArraySet<>(); + for (final NetworkRequestInfo nri : mDefaultNetworkRequests) { + if (nri.isBeingSatisfied()) { + activeNetIds.add(nri.getSatisfier().network().netId); + } + } for (NetworkAgentInfo nai : mNetworkAgentInfos) { - if (nai.everConnected && (nai == fallbackNetwork || nai.isVPN())) { + if (nai.everConnected && (activeNetIds.contains(nai.network().netId) || nai.isVPN())) { defaultNetworks.add(nai.network); } } @@ -8350,7 +8449,7 @@ public class ConnectivityService extends IConnectivityManager.Stub throw new IllegalArgumentException("Unsupported protocol " + connectionInfo.protocol); } - final int uid = InetDiagMessage.getConnectionOwnerUid(connectionInfo.protocol, + final int uid = mDeps.getConnectionOwnerUid(connectionInfo.protocol, connectionInfo.local, connectionInfo.remote); /* Filter out Uids not associated with the VPN. */ diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java index 6a72010738db..916bec27af39 100644 --- a/services/core/java/com/android/server/VcnManagementService.java +++ b/services/core/java/com/android/server/VcnManagementService.java @@ -290,8 +290,9 @@ public class VcnManagementService extends IVcnManagementService.Stub { public Vcn newVcn( @NonNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, - @NonNull VcnConfig config) { - return new Vcn(vcnContext, subscriptionGroup, config); + @NonNull VcnConfig config, + @NonNull TelephonySubscriptionSnapshot snapshot) { + return new Vcn(vcnContext, subscriptionGroup, config, snapshot); } /** Gets the subId indicated by the given {@link WifiInfo}. */ @@ -382,6 +383,7 @@ public class VcnManagementService extends IVcnManagementService.Stub { // delay) for (Entry<ParcelUuid, Vcn> entry : mVcns.entrySet()) { final VcnConfig config = mConfigs.get(entry.getKey()); + if (config == null || !snapshot.packageHasPermissionsForSubscriptionGroup( entry.getKey(), config.getProvisioningPackageName())) { @@ -395,10 +397,13 @@ public class VcnManagementService extends IVcnManagementService.Stub { // correct instance is torn down. This could happen as a result of a // Carrier App manually removing/adding a VcnConfig. if (mVcns.get(uuidToTeardown) == instanceToTeardown) { - mVcns.remove(uuidToTeardown).teardownAsynchronously(); + stopVcnLocked(uuidToTeardown); } } }, instanceToTeardown, CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS); + } else { + // If this VCN's status has not changed, update it with the new snapshot + entry.getValue().updateSubscriptionSnapshot(mLastSnapshot); } } } @@ -406,14 +411,39 @@ public class VcnManagementService extends IVcnManagementService.Stub { } @GuardedBy("mLock") + private void stopVcnLocked(@NonNull ParcelUuid uuidToTeardown) { + final Vcn vcnToTeardown = mVcns.remove(uuidToTeardown); + if (vcnToTeardown == null) { + return; + } + + vcnToTeardown.teardownAsynchronously(); + + // Now that the VCN is removed, notify all registered listeners to refresh their + // UnderlyingNetworkPolicy. + notifyAllPolicyListenersLocked(); + } + + @GuardedBy("mLock") + private void notifyAllPolicyListenersLocked() { + for (final PolicyListenerBinderDeath policyListener : mRegisteredPolicyListeners.values()) { + Binder.withCleanCallingIdentity(() -> policyListener.mListener.onPolicyChanged()); + } + } + + @GuardedBy("mLock") private void startVcnLocked(@NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config) { Slog.v(TAG, "Starting VCN config for subGrp: " + subscriptionGroup); // TODO(b/176939047): Support multiple VCNs active at the same time, or limit to one active // VCN. - final Vcn newInstance = mDeps.newVcn(mVcnContext, subscriptionGroup, config); + final Vcn newInstance = mDeps.newVcn(mVcnContext, subscriptionGroup, config, mLastSnapshot); mVcns.put(subscriptionGroup, newInstance); + + // Now that a new VCN has started, notify all registered listeners to refresh their + // UnderlyingNetworkPolicy. + notifyAllPolicyListenersLocked(); } @GuardedBy("mLock") @@ -476,9 +506,7 @@ public class VcnManagementService extends IVcnManagementService.Stub { synchronized (mLock) { mConfigs.remove(subscriptionGroup); - if (mVcns.containsKey(subscriptionGroup)) { - mVcns.remove(subscriptionGroup).teardownAsynchronously(); - } + stopVcnLocked(subscriptionGroup); writeConfigsToDiskLocked(); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 686adbb7b793..1e6c7ac39e00 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -7144,67 +7144,68 @@ public class ActivityManagerService extends IActivityManager.Stub "getContentProviderImpl: after checkContentProviderPermission"); final long origId = Binder.clearCallingIdentity(); + try { + checkTime(startTime, "getContentProviderImpl: incProviderCountLocked"); + + // Return the provider instance right away since it already exists. + conn = incProviderCountLocked(r, cpr, token, callingUid, callingPackage, + callingTag, stable); + if (conn != null && (conn.stableCount+conn.unstableCount) == 1) { + if (cpr.proc != null + && r != null && r.setAdj <= ProcessList.PERCEPTIBLE_LOW_APP_ADJ) { + // If this is a perceptible app accessing the provider, + // make sure to count it as being accessed and thus + // back up on the LRU list. This is good because + // content providers are often expensive to start. + checkTime(startTime, "getContentProviderImpl: before updateLruProcess"); + mProcessList.updateLruProcessLocked(cpr.proc, false, null); + checkTime(startTime, "getContentProviderImpl: after updateLruProcess"); + } + } - checkTime(startTime, "getContentProviderImpl: incProviderCountLocked"); - - // In this case the provider instance already exists, so we can - // return it right away. - conn = incProviderCountLocked(r, cpr, token, callingUid, callingPackage, callingTag, - stable); - if (conn != null && (conn.stableCount+conn.unstableCount) == 1) { - if (cpr.proc != null - && r != null && r.setAdj <= ProcessList.PERCEPTIBLE_LOW_APP_ADJ) { - // If this is a perceptible app accessing the provider, - // make sure to count it as being accessed and thus - // back up on the LRU list. This is good because - // content providers are often expensive to start. - checkTime(startTime, "getContentProviderImpl: before updateLruProcess"); - mProcessList.updateLruProcessLocked(cpr.proc, false, null); - checkTime(startTime, "getContentProviderImpl: after updateLruProcess"); - } - } - - checkTime(startTime, "getContentProviderImpl: before updateOomAdj"); - final int verifiedAdj = cpr.proc.verifiedAdj; - boolean success = updateOomAdjLocked(cpr.proc, true, - OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER); - // XXX things have changed so updateOomAdjLocked doesn't actually tell us - // if the process has been successfully adjusted. So to reduce races with - // it, we will check whether the process still exists. Note that this doesn't - // completely get rid of races with LMK killing the process, but should make - // them much smaller. - if (success && verifiedAdj != cpr.proc.setAdj && !isProcessAliveLocked(cpr.proc)) { - success = false; - } - maybeUpdateProviderUsageStatsLocked(r, cpr.info.packageName, name); - checkTime(startTime, "getContentProviderImpl: after updateOomAdj"); - if (DEBUG_PROVIDER) Slog.i(TAG_PROVIDER, "Adjust success: " + success); - // NOTE: there is still a race here where a signal could be - // pending on the process even though we managed to update its - // adj level. Not sure what to do about this, but at least - // the race is now smaller. - if (!success) { - // Uh oh... it looks like the provider's process - // has been killed on us. We need to wait for a new - // process to be started, and make sure its death - // doesn't kill our process. - Slog.wtf(TAG, "Existing provider " + cpr.name.flattenToShortString() - + " is crashing; detaching " + r); - boolean lastRef = decProviderCountLocked(conn, cpr, token, stable); - if (!lastRef) { - // This wasn't the last ref our process had on - // the provider... we will be killed during cleaning up, bail. - return null; + checkTime(startTime, "getContentProviderImpl: before updateOomAdj"); + final int verifiedAdj = cpr.proc.verifiedAdj; + boolean success = updateOomAdjLocked(cpr.proc, true, + OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER); + // XXX things have changed so updateOomAdjLocked doesn't actually tell us + // if the process has been successfully adjusted. So to reduce races with + // it, we will check whether the process still exists. Note that this doesn't + // completely get rid of races with LMK killing the process, but should make + // them much smaller. + if (success && verifiedAdj != cpr.proc.setAdj + && !isProcessAliveLocked(cpr.proc)) { + success = false; + } + maybeUpdateProviderUsageStatsLocked(r, cpr.info.packageName, name); + checkTime(startTime, "getContentProviderImpl: after updateOomAdj"); + if (DEBUG_PROVIDER) Slog.i(TAG_PROVIDER, "Adjust success: " + success); + // NOTE: there is still a race here where a signal could be + // pending on the process even though we managed to update its + // adj level. Not sure what to do about this, but at least + // the race is now smaller. + if (!success) { + // Uh oh... it looks like the provider's process + // has been killed on us. We need to wait for a new + // process to be started, and make sure its death + // doesn't kill our process. + Slog.wtf(TAG, "Existing provider " + cpr.name.flattenToShortString() + + " is crashing; detaching " + r); + boolean lastRef = decProviderCountLocked(conn, cpr, token, stable); + if (!lastRef) { + // This wasn't the last ref our process had on + // the provider... we will be killed during cleaning up, bail. + return null; + } + // We'll just start a new process to host the content provider + providerRunning = false; + conn = null; + dyingProc = cpr.proc; + } else { + cpr.proc.verifiedAdj = cpr.proc.setAdj; } - // We'll just start a new process to host the content provider - providerRunning = false; - conn = null; - dyingProc = cpr.proc; - } else { - cpr.proc.verifiedAdj = cpr.proc.setAdj; + } finally { + Binder.restoreCallingIdentity(origId); } - - Binder.restoreCallingIdentity(origId); } if (!providerRunning) { diff --git a/services/core/java/com/android/server/apphibernation/AppHibernationService.java b/services/core/java/com/android/server/apphibernation/AppHibernationService.java index fded85cd9126..e97f0b47380a 100644 --- a/services/core/java/com/android/server/apphibernation/AppHibernationService.java +++ b/services/core/java/com/android/server/apphibernation/AppHibernationService.java @@ -18,11 +18,9 @@ package com.android.server.apphibernation; import static android.content.Intent.ACTION_PACKAGE_ADDED; import static android.content.Intent.ACTION_PACKAGE_REMOVED; -import static android.content.Intent.ACTION_USER_ADDED; -import static android.content.Intent.ACTION_USER_REMOVED; import static android.content.Intent.EXTRA_REMOVED_FOR_ALL_USERS; import static android.content.Intent.EXTRA_REPLACING; -import static android.content.pm.PackageManager.MATCH_ALL; +import static android.content.pm.PackageManager.MATCH_ANY_USER; import static android.provider.DeviceConfig.NAMESPACE_APP_HIBERNATION; import android.annotation.NonNull; @@ -36,8 +34,9 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; -import android.content.pm.UserInfo; +import android.content.pm.PackageManager; import android.os.Binder; +import android.os.Environment; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; @@ -48,16 +47,21 @@ import android.os.UserManager; import android.provider.DeviceConfig; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.server.SystemService; +import java.io.File; import java.io.FileDescriptor; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; /** * System service that manages app hibernation state, a state apps can enter that means they are @@ -66,6 +70,11 @@ import java.util.Set; */ public final class AppHibernationService extends SystemService { private static final String TAG = "AppHibernationService"; + private static final int PACKAGE_MATCH_FLAGS = + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE + | PackageManager.MATCH_UNINSTALLED_PACKAGES + | PackageManager.MATCH_DISABLED_COMPONENTS; /** * Lock for accessing any in-memory hibernation state @@ -76,9 +85,13 @@ public final class AppHibernationService extends SystemService { private final IActivityManager mIActivityManager; private final UserManager mUserManager; @GuardedBy("mLock") - private final SparseArray<Map<String, UserPackageState>> mUserStates = new SparseArray<>(); + private final SparseArray<Map<String, UserLevelState>> mUserStates = new SparseArray<>(); + private final SparseArray<HibernationStateDiskStore<UserLevelState>> mUserDiskStores = + new SparseArray<>(); @GuardedBy("mLock") - private final Set<String> mGloballyHibernatedPackages = new ArraySet<>(); + private final Map<String, GlobalLevelState> mGlobalHibernationStates = new ArrayMap<>(); + private final HibernationStateDiskStore<GlobalLevelState> mGlobalLevelHibernationDiskStore; + private final Injector mInjector; /** * Initializes the system service. @@ -90,28 +103,22 @@ public final class AppHibernationService extends SystemService { * @param context The system server context. */ public AppHibernationService(@NonNull Context context) { - this(context, IPackageManager.Stub.asInterface(ServiceManager.getService("package")), - ActivityManager.getService(), - context.getSystemService(UserManager.class)); + this(new InjectorImpl(context)); } @VisibleForTesting - AppHibernationService(@NonNull Context context, IPackageManager packageManager, - IActivityManager activityManager, UserManager userManager) { - super(context); - mContext = context; - mIPackageManager = packageManager; - mIActivityManager = activityManager; - mUserManager = userManager; + AppHibernationService(@NonNull Injector injector) { + super(injector.getContext()); + mContext = injector.getContext(); + mIPackageManager = injector.getPackageManager(); + mIActivityManager = injector.getActivityManager(); + mUserManager = injector.getUserManager(); + mGlobalLevelHibernationDiskStore = injector.getGlobalLevelDiskStore(); + mInjector = injector; final Context userAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */); IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(ACTION_USER_ADDED); - intentFilter.addAction(ACTION_USER_REMOVED); - userAllContext.registerReceiver(mBroadcastReceiver, intentFilter); - - intentFilter = new IntentFilter(); intentFilter.addAction(ACTION_PACKAGE_ADDED); intentFilter.addAction(ACTION_PACKAGE_REMOVED); intentFilter.addDataScheme("package"); @@ -126,12 +133,10 @@ public final class AppHibernationService extends SystemService { @Override public void onBootPhase(int phase) { if (phase == PHASE_BOOT_COMPLETED) { + List<GlobalLevelState> states = + mGlobalLevelHibernationDiskStore.readHibernationStates(); synchronized (mLock) { - final List<UserInfo> users = mUserManager.getUsers(); - // TODO: Pull from persistent disk storage. For now, just make from scratch. - for (UserInfo user : users) { - addUserPackageStatesL(user.id); - } + initializeGlobalHibernationStates(states); } } } @@ -145,12 +150,14 @@ public final class AppHibernationService extends SystemService { */ boolean isHibernatingForUser(String packageName, int userId) { userId = handleIncomingUser(userId, "isHibernating"); + if (!mUserManager.isUserUnlockingOrUnlocked(userId)) { + Slog.e(TAG, "Attempt to get hibernation state of stopped or nonexistent user " + + userId); + return false; + } synchronized (mLock) { - final Map<String, UserPackageState> packageStates = mUserStates.get(userId); - if (packageStates == null) { - throw new IllegalArgumentException("No user associated with user id " + userId); - } - final UserPackageState pkgState = packageStates.get(packageName); + final Map<String, UserLevelState> packageStates = mUserStates.get(userId); + final UserLevelState pkgState = packageStates.get(packageName); if (pkgState == null) { throw new IllegalArgumentException( String.format("Package %s is not installed for user %s", @@ -168,7 +175,12 @@ public final class AppHibernationService extends SystemService { */ boolean isHibernatingGlobally(String packageName) { synchronized (mLock) { - return mGloballyHibernatedPackages.contains(packageName); + GlobalLevelState state = mGlobalHibernationStates.get(packageName); + if (state == null) { + throw new IllegalArgumentException( + String.format("Package %s is not installed", packageName)); + } + return state.hibernated; } } @@ -181,12 +193,14 @@ public final class AppHibernationService extends SystemService { */ void setHibernatingForUser(String packageName, int userId, boolean isHibernating) { userId = handleIncomingUser(userId, "setHibernating"); + if (!mUserManager.isUserUnlockingOrUnlocked(userId)) { + Slog.w(TAG, "Attempt to set hibernation state for a stopped or nonexistent user " + + userId); + return; + } synchronized (mLock) { - if (!mUserStates.contains(userId)) { - throw new IllegalArgumentException("No user associated with user id " + userId); - } - Map<String, UserPackageState> packageStates = mUserStates.get(userId); - UserPackageState pkgState = packageStates.get(packageName); + final Map<String, UserLevelState> packageStates = mUserStates.get(userId); + final UserLevelState pkgState = packageStates.get(packageName); if (pkgState == null) { throw new IllegalArgumentException( String.format("Package %s is not installed for user %s", @@ -198,10 +212,12 @@ public final class AppHibernationService extends SystemService { } if (isHibernating) { - hibernatePackageForUserL(packageName, userId, pkgState); + hibernatePackageForUser(packageName, userId, pkgState); } else { - unhibernatePackageForUserL(packageName, userId, pkgState); + unhibernatePackageForUser(packageName, userId, pkgState); } + List<UserLevelState> states = new ArrayList<>(mUserStates.get(userId).values()); + mUserDiskStores.get(userId).scheduleWriteHibernationStates(states); } } @@ -213,25 +229,32 @@ public final class AppHibernationService extends SystemService { * @param isHibernating new hibernation state */ void setHibernatingGlobally(String packageName, boolean isHibernating) { - if (isHibernating != mGloballyHibernatedPackages.contains(packageName)) { - synchronized (mLock) { + synchronized (mLock) { + GlobalLevelState state = mGlobalHibernationStates.get(packageName); + if (state == null) { + throw new IllegalArgumentException( + String.format("Package %s is not installed for any user", packageName)); + } + if (state.hibernated != isHibernating) { if (isHibernating) { - hibernatePackageGloballyL(packageName); + hibernatePackageGlobally(packageName, state); } else { - unhibernatePackageGloballyL(packageName); + unhibernatePackageGlobally(packageName, state); } + List<GlobalLevelState> states = new ArrayList<>(mGlobalHibernationStates.values()); + mGlobalLevelHibernationDiskStore.scheduleWriteHibernationStates(states); } } } /** * Put an app into hibernation for a given user, allowing user-level optimizations to occur. - * The caller should hold {@link #mLock} * * @param pkgState package hibernation state */ - private void hibernatePackageForUserL(@NonNull String packageName, int userId, - @NonNull UserPackageState pkgState) { + @GuardedBy("mLock") + private void hibernatePackageForUser(@NonNull String packageName, int userId, + @NonNull UserLevelState pkgState) { Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "hibernatePackage"); final long caller = Binder.clearCallingIdentity(); try { @@ -249,12 +272,13 @@ public final class AppHibernationService extends SystemService { } /** - * Remove a package from hibernation for a given user. The caller should hold {@link #mLock}. + * Remove a package from hibernation for a given user. * * @param pkgState package hibernation state */ - private void unhibernatePackageForUserL(@NonNull String packageName, int userId, - UserPackageState pkgState) { + @GuardedBy("mLock") + private void unhibernatePackageForUser(@NonNull String packageName, int userId, + UserLevelState pkgState) { Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "unhibernatePackage"); final long caller = Binder.clearCallingIdentity(); try { @@ -271,60 +295,140 @@ public final class AppHibernationService extends SystemService { /** * Put a package into global hibernation, optimizing its storage at a package / APK level. - * The caller should hold {@link #mLock}. */ - private void hibernatePackageGloballyL(@NonNull String packageName) { + @GuardedBy("mLock") + private void hibernatePackageGlobally(@NonNull String packageName, GlobalLevelState state) { Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "hibernatePackageGlobally"); // TODO(175830194): Delete vdex/odex when DexManager API is built out - mGloballyHibernatedPackages.add(packageName); + state.hibernated = true; Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); } /** - * Unhibernate a package from global hibernation. The caller should hold {@link #mLock}. + * Unhibernate a package from global hibernation. */ - private void unhibernatePackageGloballyL(@NonNull String packageName) { + @GuardedBy("mLock") + private void unhibernatePackageGlobally(@NonNull String packageName, GlobalLevelState state) { Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "unhibernatePackageGlobally"); - mGloballyHibernatedPackages.remove(packageName); + state.hibernated = false; Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); } /** - * Populates {@link #mUserStates} with the users installed packages. The caller should hold - * {@link #mLock}. + * Initializes in-memory store of user-level hibernation states for the given user * * @param userId user id to add installed packages for + * @param diskStates states pulled from disk, if available */ - private void addUserPackageStatesL(int userId) { - Map<String, UserPackageState> packages = new ArrayMap<>(); - List<PackageInfo> packageList; + @GuardedBy("mLock") + private void initializeUserHibernationStates(int userId, + @Nullable List<UserLevelState> diskStates) { + List<PackageInfo> packages; try { - packageList = mIPackageManager.getInstalledPackages(MATCH_ALL, userId).getList(); + packages = mIPackageManager.getInstalledPackages(PACKAGE_MATCH_FLAGS, userId).getList(); } catch (RemoteException e) { - throw new IllegalStateException("Package manager not available.", e); + throw new IllegalStateException("Package manager not available", e); } - for (int i = 0, size = packageList.size(); i < size; i++) { - packages.put(packageList.get(i).packageName, new UserPackageState()); + Map<String, UserLevelState> userLevelStates = new ArrayMap<>(); + + for (int i = 0, size = packages.size(); i < size; i++) { + String packageName = packages.get(i).packageName; + UserLevelState state = new UserLevelState(); + state.packageName = packageName; + userLevelStates.put(packageName, state); } - mUserStates.put(userId, packages); + + if (diskStates != null) { + Set<String> installedPackages = new ArraySet<>(); + for (int i = 0, size = packages.size(); i < size; i++) { + installedPackages.add(packages.get(i).packageName); + } + for (int i = 0, size = diskStates.size(); i < size; i++) { + String packageName = diskStates.get(i).packageName; + if (!installedPackages.contains(packageName)) { + Slog.w(TAG, String.format( + "No hibernation state associated with package %s user %d. Maybe" + + "the package was uninstalled? ", packageName, userId)); + continue; + } + userLevelStates.put(packageName, diskStates.get(i)); + } + } + mUserStates.put(userId, userLevelStates); } - private void onUserAdded(int userId) { + /** + * Initialize in-memory store of global level hibernation states. + * + * @param diskStates global level hibernation states pulled from disk, if available + */ + @GuardedBy("mLock") + private void initializeGlobalHibernationStates(@Nullable List<GlobalLevelState> diskStates) { + List<PackageInfo> packages; + try { + packages = mIPackageManager.getInstalledPackages( + PACKAGE_MATCH_FLAGS | MATCH_ANY_USER, 0 /* userId */).getList(); + } catch (RemoteException e) { + throw new IllegalStateException("Package manager not available", e); + } + + for (int i = 0, size = packages.size(); i < size; i++) { + String packageName = packages.get(i).packageName; + GlobalLevelState state = new GlobalLevelState(); + state.packageName = packageName; + mGlobalHibernationStates.put(packageName, state); + } + if (diskStates != null) { + Set<String> installedPackages = new ArraySet<>(); + for (int i = 0, size = packages.size(); i < size; i++) { + installedPackages.add(packages.get(i).packageName); + } + for (int i = 0, size = diskStates.size(); i < size; i++) { + GlobalLevelState state = diskStates.get(i); + if (!installedPackages.contains(state.packageName)) { + Slog.w(TAG, String.format( + "No hibernation state associated with package %s. Maybe the " + + "package was uninstalled? ", state.packageName)); + continue; + } + mGlobalHibernationStates.put(state.packageName, state); + } + } + } + + @Override + public void onUserUnlocking(@NonNull TargetUser user) { + int userId = user.getUserIdentifier(); + HibernationStateDiskStore<UserLevelState> diskStore = + mInjector.getUserLevelDiskStore(userId); + mUserDiskStores.put(userId, diskStore); + List<UserLevelState> storedStates = diskStore.readHibernationStates(); synchronized (mLock) { - addUserPackageStatesL(userId); + initializeUserHibernationStates(userId, storedStates); } } - private void onUserRemoved(int userId) { + @Override + public void onUserStopping(@NonNull TargetUser user) { + int userId = user.getUserIdentifier(); + // TODO: Flush any scheduled writes to disk immediately on user stopping / power off. synchronized (mLock) { + mUserDiskStores.remove(userId); mUserStates.remove(userId); } } private void onPackageAdded(@NonNull String packageName, int userId) { synchronized (mLock) { - mUserStates.get(userId).put(packageName, new UserPackageState()); + UserLevelState userState = new UserLevelState(); + userState.packageName = packageName; + mUserStates.get(userId).put(packageName, userState); + if (!mGlobalHibernationStates.containsKey(packageName)) { + GlobalLevelState globalState = new GlobalLevelState(); + globalState.packageName = packageName; + mGlobalHibernationStates.put(packageName, globalState); + } } } @@ -336,7 +440,7 @@ public final class AppHibernationService extends SystemService { private void onPackageRemovedForAllUsers(@NonNull String packageName) { synchronized (mLock) { - mGloballyHibernatedPackages.remove(packageName); + mGlobalHibernationStates.remove(packageName); } } @@ -395,7 +499,7 @@ public final class AppHibernationService extends SystemService { } } - // Broadcast receiver for user and package add/removal events + // Broadcast receiver for package add/removal events private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -405,12 +509,6 @@ public final class AppHibernationService extends SystemService { } final String action = intent.getAction(); - if (ACTION_USER_ADDED.equals(action)) { - onUserAdded(userId); - } - if (ACTION_USER_REMOVED.equals(action)) { - onUserRemoved(userId); - } if (ACTION_PACKAGE_ADDED.equals(action) || ACTION_PACKAGE_REMOVED.equals(action)) { final String packageName = intent.getData().getSchemeSpecificPart(); if (intent.getBooleanExtra(EXTRA_REPLACING, false)) { @@ -443,10 +541,66 @@ public final class AppHibernationService extends SystemService { } /** - * Data class that contains hibernation state info of a package for a user. + * Dependency injector for {@link #AppHibernationService)}. */ - private static final class UserPackageState { - public boolean hibernated; - // TODO: Track whether hibernation is exempted by the user + interface Injector { + Context getContext(); + + IPackageManager getPackageManager(); + + IActivityManager getActivityManager(); + + UserManager getUserManager(); + + HibernationStateDiskStore<GlobalLevelState> getGlobalLevelDiskStore(); + + HibernationStateDiskStore<UserLevelState> getUserLevelDiskStore(int userId); + } + + private static final class InjectorImpl implements Injector { + private static final String HIBERNATION_DIR_NAME = "hibernation"; + private final Context mContext; + private final ScheduledExecutorService mScheduledExecutorService; + private final UserLevelHibernationProto mUserLevelHibernationProto; + + InjectorImpl(Context context) { + mContext = context; + mScheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); + mUserLevelHibernationProto = new UserLevelHibernationProto(); + } + + @Override + public Context getContext() { + return mContext; + } + + @Override + public IPackageManager getPackageManager() { + return IPackageManager.Stub.asInterface(ServiceManager.getService("package")); + } + + @Override + public IActivityManager getActivityManager() { + return ActivityManager.getService(); + } + + @Override + public UserManager getUserManager() { + return mContext.getSystemService(UserManager.class); + } + + @Override + public HibernationStateDiskStore<GlobalLevelState> getGlobalLevelDiskStore() { + File dir = new File(Environment.getDataSystemDirectory(), HIBERNATION_DIR_NAME); + return new HibernationStateDiskStore<>( + dir, new GlobalLevelHibernationProto(), mScheduledExecutorService); + } + + @Override + public HibernationStateDiskStore<UserLevelState> getUserLevelDiskStore(int userId) { + File dir = new File(Environment.getDataSystemCeDirectory(userId), HIBERNATION_DIR_NAME); + return new HibernationStateDiskStore<>( + dir, mUserLevelHibernationProto, mScheduledExecutorService); + } } } diff --git a/services/core/java/com/android/server/apphibernation/GlobalLevelHibernationProto.java b/services/core/java/com/android/server/apphibernation/GlobalLevelHibernationProto.java new file mode 100644 index 000000000000..79e995b038fa --- /dev/null +++ b/services/core/java/com/android/server/apphibernation/GlobalLevelHibernationProto.java @@ -0,0 +1,78 @@ +/* + * 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.apphibernation; + + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.Slog; +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoOutputStream; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * Reads and writes protos for {@link GlobalLevelState} hiberation states. + */ +final class GlobalLevelHibernationProto implements ProtoReadWriter<List<GlobalLevelState>> { + private static final String TAG = "GlobalLevelHibernationProtoReadWriter"; + + @Override + public void writeToProto(@NonNull ProtoOutputStream stream, + @NonNull List<GlobalLevelState> data) { + for (int i = 0, size = data.size(); i < size; i++) { + long token = stream.start(GlobalLevelHibernationStatesProto.HIBERNATION_STATE); + GlobalLevelState state = data.get(i); + stream.write(GlobalLevelHibernationStateProto.PACKAGE_NAME, state.packageName); + stream.write(GlobalLevelHibernationStateProto.HIBERNATED, state.hibernated); + stream.end(token); + } + } + + @Override + public @Nullable List<GlobalLevelState> readFromProto(@NonNull ProtoInputStream stream) + throws IOException { + List<GlobalLevelState> list = new ArrayList<>(); + while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + if (stream.getFieldNumber() + != (int) GlobalLevelHibernationStatesProto.HIBERNATION_STATE) { + continue; + } + GlobalLevelState state = new GlobalLevelState(); + long token = stream.start(GlobalLevelHibernationStatesProto.HIBERNATION_STATE); + while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (stream.getFieldNumber()) { + case (int) GlobalLevelHibernationStateProto.PACKAGE_NAME: + state.packageName = + stream.readString(GlobalLevelHibernationStateProto.PACKAGE_NAME); + break; + case (int) GlobalLevelHibernationStateProto.HIBERNATED: + state.hibernated = + stream.readBoolean(GlobalLevelHibernationStateProto.HIBERNATED); + break; + default: + Slog.w(TAG, "Undefined field in proto: " + stream.getFieldNumber()); + } + } + stream.end(token); + list.add(state); + } + return list; + } +} diff --git a/services/core/java/com/android/server/apphibernation/GlobalLevelState.java b/services/core/java/com/android/server/apphibernation/GlobalLevelState.java new file mode 100644 index 000000000000..4f756756c2ab --- /dev/null +++ b/services/core/java/com/android/server/apphibernation/GlobalLevelState.java @@ -0,0 +1,25 @@ +/* + * 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.apphibernation; + +/** + * Data class that contains global hibernation state for a package. + */ +final class GlobalLevelState { + public String packageName; + public boolean hibernated; +} diff --git a/services/core/java/com/android/server/apphibernation/HibernationStateDiskStore.java b/services/core/java/com/android/server/apphibernation/HibernationStateDiskStore.java new file mode 100644 index 000000000000..c83659d2ff56 --- /dev/null +++ b/services/core/java/com/android/server/apphibernation/HibernationStateDiskStore.java @@ -0,0 +1,162 @@ +/* + * 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.apphibernation; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.WorkerThread; +import android.text.format.DateUtils; +import android.util.AtomicFile; +import android.util.Slog; +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoOutputStream; + +import com.android.internal.annotations.VisibleForTesting; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +/** + * Disk store utility class for hibernation states. + * + * @param <T> the type of hibernation state data + */ +class HibernationStateDiskStore<T> { + private static final String TAG = "HibernationStateDiskStore"; + + // Time to wait before actually writing. Saves extra writes if data changes come in batches. + private static final long DISK_WRITE_DELAY = 1L * DateUtils.MINUTE_IN_MILLIS; + private static final String STATES_FILE_NAME = "states"; + + private final File mHibernationFile; + private final ScheduledExecutorService mExecutorService; + private final ProtoReadWriter<List<T>> mProtoReadWriter; + private List<T> mScheduledStatesToWrite = new ArrayList<>(); + private ScheduledFuture<?> mFuture; + + /** + * Initialize a disk store for hibernation states in the given directory. + * + * @param hibernationDir directory to write/read states file + * @param readWriter writer/reader of states proto + * @param executorService scheduled executor for writing data + */ + HibernationStateDiskStore(@NonNull File hibernationDir, + @NonNull ProtoReadWriter<List<T>> readWriter, + @NonNull ScheduledExecutorService executorService) { + this(hibernationDir, readWriter, executorService, STATES_FILE_NAME); + } + + @VisibleForTesting + HibernationStateDiskStore(@NonNull File hibernationDir, + @NonNull ProtoReadWriter<List<T>> readWriter, + @NonNull ScheduledExecutorService executorService, + @NonNull String fileName) { + mHibernationFile = new File(hibernationDir, fileName); + mExecutorService = executorService; + mProtoReadWriter = readWriter; + } + + /** + * Schedule a full write of all the hibernation states to the file on disk. Does not run + * immediately and subsequent writes override previous ones. + * + * @param hibernationStates list of hibernation states to write to disk + */ + void scheduleWriteHibernationStates(@NonNull List<T> hibernationStates) { + synchronized (this) { + mScheduledStatesToWrite = hibernationStates; + if (mExecutorService.isShutdown()) { + Slog.e(TAG, "Scheduled executor service is shut down."); + return; + } + + // Already have write scheduled + if (mFuture != null) { + Slog.i(TAG, "Write already scheduled. Skipping schedule."); + return; + } + + mFuture = mExecutorService.schedule(this::writeHibernationStates, DISK_WRITE_DELAY, + TimeUnit.MILLISECONDS); + } + } + + /** + * Read hibernation states from disk. + * + * @return the parsed list of hibernation states, null if file does not exist + */ + @Nullable + List<T> readHibernationStates() { + synchronized (this) { + if (!mHibernationFile.exists()) { + Slog.i(TAG, "No hibernation file on disk for file " + mHibernationFile.getPath()); + return null; + } + AtomicFile atomicFile = new AtomicFile(mHibernationFile); + + try { + FileInputStream inputStream = atomicFile.openRead(); + ProtoInputStream protoInputStream = new ProtoInputStream(inputStream); + return mProtoReadWriter.readFromProto(protoInputStream); + } catch (IOException e) { + Slog.e(TAG, "Failed to read states protobuf.", e); + return null; + } + } + } + + @WorkerThread + private void writeHibernationStates() { + synchronized (this) { + writeStateProto(mScheduledStatesToWrite); + mScheduledStatesToWrite.clear(); + mFuture = null; + } + } + + @WorkerThread + private void writeStateProto(List<T> states) { + AtomicFile atomicFile = new AtomicFile(mHibernationFile); + + FileOutputStream fileOutputStream; + try { + fileOutputStream = atomicFile.startWrite(); + } catch (IOException e) { + Slog.e(TAG, "Failed to start write to states protobuf.", e); + return; + } + + try { + ProtoOutputStream protoOutputStream = new ProtoOutputStream(fileOutputStream); + mProtoReadWriter.writeToProto(protoOutputStream, states); + protoOutputStream.flush(); + atomicFile.finishWrite(fileOutputStream); + } catch (Exception e) { + Slog.e(TAG, "Failed to finish write to states protobuf.", e); + atomicFile.failWrite(fileOutputStream); + } + } +} diff --git a/services/core/java/com/android/server/apphibernation/ProtoReadWriter.java b/services/core/java/com/android/server/apphibernation/ProtoReadWriter.java new file mode 100644 index 000000000000..0cbc09a7a99d --- /dev/null +++ b/services/core/java/com/android/server/apphibernation/ProtoReadWriter.java @@ -0,0 +1,42 @@ +/* + * 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.apphibernation; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoOutputStream; + +import java.io.IOException; + +/** + * Proto utility that reads and writes proto for some data. + * + * @param <T> data that can be written and read from a proto + */ +interface ProtoReadWriter<T> { + + /** + * Write data to a proto stream + */ + void writeToProto(@NonNull ProtoOutputStream stream, @NonNull T data); + + /** + * Parse data from the proto stream and return + */ + @Nullable T readFromProto(@NonNull ProtoInputStream stream) throws IOException; +} diff --git a/services/core/java/com/android/server/apphibernation/UserLevelHibernationProto.java b/services/core/java/com/android/server/apphibernation/UserLevelHibernationProto.java new file mode 100644 index 000000000000..a24c4c575975 --- /dev/null +++ b/services/core/java/com/android/server/apphibernation/UserLevelHibernationProto.java @@ -0,0 +1,78 @@ +/* + * 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.apphibernation; + + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.Slog; +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoOutputStream; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * Reads and writes protos for {@link UserLevelState} hiberation states. + */ +final class UserLevelHibernationProto implements ProtoReadWriter<List<UserLevelState>> { + private static final String TAG = "UserLevelHibernationProtoReadWriter"; + + @Override + public void writeToProto(@NonNull ProtoOutputStream stream, + @NonNull List<UserLevelState> data) { + for (int i = 0, size = data.size(); i < size; i++) { + long token = stream.start(UserLevelHibernationStatesProto.HIBERNATION_STATE); + UserLevelState state = data.get(i); + stream.write(UserLevelHibernationStateProto.PACKAGE_NAME, state.packageName); + stream.write(UserLevelHibernationStateProto.HIBERNATED, state.hibernated); + stream.end(token); + } + } + + @Override + public @Nullable List<UserLevelState> readFromProto(@NonNull ProtoInputStream stream) + throws IOException { + List<UserLevelState> list = new ArrayList<>(); + while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + if (stream.getFieldNumber() + != (int) UserLevelHibernationStatesProto.HIBERNATION_STATE) { + continue; + } + UserLevelState state = new UserLevelState(); + long token = stream.start(UserLevelHibernationStatesProto.HIBERNATION_STATE); + while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + switch (stream.getFieldNumber()) { + case (int) UserLevelHibernationStateProto.PACKAGE_NAME: + state.packageName = + stream.readString(UserLevelHibernationStateProto.PACKAGE_NAME); + break; + case (int) UserLevelHibernationStateProto.HIBERNATED: + state.hibernated = + stream.readBoolean(UserLevelHibernationStateProto.HIBERNATED); + break; + default: + Slog.w(TAG, "Undefined field in proto: " + stream.getFieldNumber()); + } + } + stream.end(token); + list.add(state); + } + return list; + } +} diff --git a/services/core/java/com/android/server/apphibernation/UserLevelState.java b/services/core/java/com/android/server/apphibernation/UserLevelState.java new file mode 100644 index 000000000000..c66dad87c891 --- /dev/null +++ b/services/core/java/com/android/server/apphibernation/UserLevelState.java @@ -0,0 +1,25 @@ +/* + * 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.apphibernation; + +/** + * Data class that contains hibernation state info of a package for a user. + */ +final class UserLevelState { + public String packageName; + public boolean hibernated; +} diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index 1a4f20c7101e..a9a705f07ac4 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -210,23 +210,23 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { // network is taken down. This usually only happens to the default network. Lingering ends with // either the linger timeout expiring and the network being taken down, or the network // satisfying a request again. - public static class LingerTimer implements Comparable<LingerTimer> { + public static class InactivityTimer implements Comparable<InactivityTimer> { public final int requestId; public final long expiryMs; - public LingerTimer(int requestId, long expiryMs) { + public InactivityTimer(int requestId, long expiryMs) { this.requestId = requestId; this.expiryMs = expiryMs; } public boolean equals(Object o) { - if (!(o instanceof LingerTimer)) return false; - LingerTimer other = (LingerTimer) o; + if (!(o instanceof InactivityTimer)) return false; + InactivityTimer other = (InactivityTimer) o; return (requestId == other.requestId) && (expiryMs == other.expiryMs); } public int hashCode() { return Objects.hash(requestId, expiryMs); } - public int compareTo(LingerTimer other) { + public int compareTo(InactivityTimer other) { return (expiryMs != other.expiryMs) ? Long.compare(expiryMs, other.expiryMs) : Integer.compare(requestId, other.requestId); @@ -269,30 +269,31 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { */ public static final int ARG_AGENT_SUCCESS = 1; - // All linger timers for this network, sorted by expiry time. A linger timer is added whenever + // All inactivity timers for this network, sorted by expiry time. A timer is added whenever // a request is moved to a network with a better score, regardless of whether the network is or // was lingering or not. // TODO: determine if we can replace this with a smaller or unsorted data structure. (e.g., // SparseLongArray) combined with the timestamp of when the last timer is scheduled to fire. - private final SortedSet<LingerTimer> mLingerTimers = new TreeSet<>(); + private final SortedSet<InactivityTimer> mInactivityTimers = new TreeSet<>(); - // For fast lookups. Indexes into mLingerTimers by request ID. - private final SparseArray<LingerTimer> mLingerTimerForRequest = new SparseArray<>(); + // For fast lookups. Indexes into mInactivityTimers by request ID. + private final SparseArray<InactivityTimer> mInactivityTimerForRequest = new SparseArray<>(); - // Linger expiry timer. Armed whenever mLingerTimers is non-empty, regardless of whether the - // network is lingering or not. Always set to the expiry of the LingerTimer that expires last. - // When the timer fires, all linger state is cleared, and if the network has no requests, it is - // torn down. - private WakeupMessage mLingerMessage; + // Inactivity expiry timer. Armed whenever mInactivityTimers is non-empty, regardless of + // whether the network is inactive or not. Always set to the expiry of the mInactivityTimers + // that expires last. When the timer fires, all inactivity state is cleared, and if the network + // has no requests, it is torn down. + private WakeupMessage mInactivityMessage; - // Linger expiry. Holds the expiry time of the linger timer, or 0 if the timer is not armed. - private long mLingerExpiryMs; + // Inactivity expiry. Holds the expiry time of the inactivity timer, or 0 if the timer is not + // armed. + private long mInactivityExpiryMs; - // Whether the network is lingering or not. Must be maintained separately from the above because + // Whether the network is inactive or not. Must be maintained separately from the above because // it depends on the state of other networks and requests, which only ConnectivityService knows. // (Example: we don't linger a network if it would become the best for a NetworkRequest if it // validated). - private boolean mLingering; + private boolean mInactive; // This represents the quality of the network with no clear scale. private int mScore; @@ -898,17 +899,17 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { * ConnectivityService when the request is moved to another network with a higher score. */ public void lingerRequest(int requestId, long now, long duration) { - if (mLingerTimerForRequest.get(requestId) != null) { + if (mInactivityTimerForRequest.get(requestId) != null) { // Cannot happen. Once a request is lingering on a particular network, we cannot // re-linger it unless that network becomes the best for that request again, in which // case we should have unlingered it. Log.wtf(TAG, toShortString() + ": request " + requestId + " already lingered"); } final long expiryMs = now + duration; - LingerTimer timer = new LingerTimer(requestId, expiryMs); - if (VDBG) Log.d(TAG, "Adding LingerTimer " + timer + " to " + toShortString()); - mLingerTimers.add(timer); - mLingerTimerForRequest.put(requestId, timer); + InactivityTimer timer = new InactivityTimer(requestId, expiryMs); + if (VDBG) Log.d(TAG, "Adding InactivityTimer " + timer + " to " + toShortString()); + mInactivityTimers.add(timer); + mInactivityTimerForRequest.put(requestId, timer); } /** @@ -916,23 +917,25 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { * Returns true if the given requestId was lingering on this network, false otherwise. */ public boolean unlingerRequest(int requestId) { - LingerTimer timer = mLingerTimerForRequest.get(requestId); + InactivityTimer timer = mInactivityTimerForRequest.get(requestId); if (timer != null) { - if (VDBG) Log.d(TAG, "Removing LingerTimer " + timer + " from " + toShortString()); - mLingerTimers.remove(timer); - mLingerTimerForRequest.remove(requestId); + if (VDBG) { + Log.d(TAG, "Removing InactivityTimer " + timer + " from " + toShortString()); + } + mInactivityTimers.remove(timer); + mInactivityTimerForRequest.remove(requestId); return true; } return false; } - public long getLingerExpiry() { - return mLingerExpiryMs; + public long getInactivityExpiry() { + return mInactivityExpiryMs; } - public void updateLingerTimer() { - long newExpiry = mLingerTimers.isEmpty() ? 0 : mLingerTimers.last().expiryMs; - if (newExpiry == mLingerExpiryMs) return; + public void updateInactivityTimer() { + long newExpiry = mInactivityTimers.isEmpty() ? 0 : mInactivityTimers.last().expiryMs; + if (newExpiry == mInactivityExpiryMs) return; // Even if we're going to reschedule the timer, cancel it first. This is because the // semantics of WakeupMessage guarantee that if cancel is called then the alarm will @@ -940,49 +943,52 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { // WakeupMessage makes no such guarantees about rescheduling a message, so if mLingerMessage // has already been dispatched, rescheduling to some time in the future won't stop it // from calling its callback immediately. - if (mLingerMessage != null) { - mLingerMessage.cancel(); - mLingerMessage = null; + if (mInactivityMessage != null) { + mInactivityMessage.cancel(); + mInactivityMessage = null; } if (newExpiry > 0) { - mLingerMessage = new WakeupMessage( + mInactivityMessage = new WakeupMessage( mContext, mHandler, "NETWORK_LINGER_COMPLETE." + network.getNetId() /* cmdName */, EVENT_NETWORK_LINGER_COMPLETE /* cmd */, 0 /* arg1 (unused) */, 0 /* arg2 (unused) */, this /* obj (NetworkAgentInfo) */); - mLingerMessage.schedule(newExpiry); + mInactivityMessage.schedule(newExpiry); } - mLingerExpiryMs = newExpiry; + mInactivityExpiryMs = newExpiry; } - public void linger() { - mLingering = true; + public void setInactive() { + mInactive = true; } - public void unlinger() { - mLingering = false; + public void unsetInactive() { + mInactive = false; } public boolean isLingering() { - return mLingering; + return mInactive; } - public void clearLingerState() { - if (mLingerMessage != null) { - mLingerMessage.cancel(); - mLingerMessage = null; + public void clearInactivityState() { + if (mInactivityMessage != null) { + mInactivityMessage.cancel(); + mInactivityMessage = null; } - mLingerTimers.clear(); - mLingerTimerForRequest.clear(); - updateLingerTimer(); // Sets mLingerExpiryMs, cancels and nulls out mLingerMessage. - mLingering = false; + mInactivityTimers.clear(); + mInactivityTimerForRequest.clear(); + // Sets mInactivityExpiryMs, cancels and nulls out mInactivityMessage. + updateInactivityTimer(); + mInactive = false; } - public void dumpLingerTimers(PrintWriter pw) { - for (LingerTimer timer : mLingerTimers) { pw.println(timer); } + public void dumpInactivityTimers(PrintWriter pw) { + for (InactivityTimer timer : mInactivityTimers) { + pw.println(timer); + } } /** diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index d956ba375ba1..5956fe1abdd1 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -277,6 +277,10 @@ public class Vpn { return LocalServices.getService(DeviceIdleInternal.class); } + public PendingIntent getIntentForStatusPanel(Context context) { + return VpnConfig.getIntentForStatusPanel(context); + } + public void sendArgumentsToDaemon( final String daemon, final LocalSocket socket, final String[] arguments, final RetryScheduler retryScheduler) throws IOException, InterruptedException { @@ -1768,7 +1772,7 @@ public class Vpn { private void prepareStatusIntent() { final long token = Binder.clearCallingIdentity(); try { - mStatusIntent = VpnConfig.getIntentForStatusPanel(mContext); + mStatusIntent = mDeps.getIntentForStatusPanel(mContext); } finally { Binder.restoreCallingIdentity(token); } diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java index f92f3dcd77ef..39ed7e8b1e1a 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java @@ -16,8 +16,6 @@ package com.android.server.net; -import static com.android.server.net.NetworkPolicyManagerService.isUidNetworkingBlockedInternal; - import android.annotation.NonNull; import android.net.Network; import android.net.NetworkTemplate; @@ -39,28 +37,6 @@ public abstract class NetworkPolicyManagerInternal { public abstract void resetUserState(int userId); /** - * Figure out if networking is blocked for a given set of conditions. - * - * This is used by ConnectivityService via passing stale copies of conditions, so it must not - * take any locks. - * - * @param uid The target uid. - * @param uidRules The uid rules which are obtained from NetworkPolicyManagerService. - * @param isNetworkMetered True if the network is metered. - * @param isBackgroundRestricted True if data saver is enabled. - * - * @return true if networking is blocked for the UID under the specified conditions. - */ - public static boolean isUidNetworkingBlocked(int uid, int uidRules, boolean isNetworkMetered, - boolean isBackgroundRestricted) { - // Log of invoking internal function is disabled because it will be called very - // frequently. And metrics are unlikely needed on this method because the callers are - // external and this method doesn't take any locks or perform expensive operations. - return isUidNetworkingBlockedInternal(uid, uidRules, isNetworkMetered, - isBackgroundRestricted, null); - } - - /** * Informs that an appId has been added or removed from the temp-powersave-allowlist so that * that network rules for that appId can be updated. * diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index 0fd39a452b04..6c67cba19117 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -70,6 +70,7 @@ import static android.net.NetworkPolicyManager.RULE_REJECT_ALL; import static android.net.NetworkPolicyManager.RULE_REJECT_METERED; import static android.net.NetworkPolicyManager.RULE_REJECT_RESTRICTED_MODE; import static android.net.NetworkPolicyManager.RULE_TEMPORARY_ALLOW_METERED; +import static android.net.NetworkPolicyManager.SUBSCRIPTION_OVERRIDE_UNMETERED; import static android.net.NetworkPolicyManager.isProcStateAllowedWhileIdleOrPowerSaveMode; import static android.net.NetworkPolicyManager.isProcStateAllowedWhileOnRestrictBackground; import static android.net.NetworkPolicyManager.resolveNetworkId; @@ -231,6 +232,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.notification.SystemNotificationChannels; +import com.android.internal.os.SomeArgs; import com.android.internal.util.ArrayUtils; import com.android.internal.util.CollectionUtils; import com.android.internal.util.ConcurrentUtils; @@ -1253,7 +1255,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { // identified carrier, which may want to manage their own notifications. This method // should be called every time the carrier config changes anyways, and there's no // reason to alert if there isn't a carrier. - return; + continue; } final boolean notifyWarning = getBooleanDefeatingNullable(config, @@ -3488,13 +3490,27 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { @Override public void setSubscriptionOverride(int subId, int overrideMask, int overrideValue, - long timeoutMillis, String callingPackage) { + int[] networkTypes, long timeoutMillis, String callingPackage) { enforceSubscriptionPlanAccess(subId, Binder.getCallingUid(), callingPackage); - // We can only override when carrier told us about plans + final ArraySet<Integer> allNetworksSet = new ArraySet<>(); + addAll(allNetworksSet, TelephonyManager.getAllNetworkTypes()); + final IntArray applicableNetworks = new IntArray(); + + // ensure all network types are valid + for (int networkType : networkTypes) { + if (allNetworksSet.contains(networkType)) { + applicableNetworks.add(networkType); + } else { + Log.d(TAG, "setSubscriptionOverride removing invalid network type: " + networkType); + } + } + + // We can only override when carrier told us about plans. For the unmetered case, + // allow override without having plans defined. synchronized (mNetworkPoliciesSecondLock) { final SubscriptionPlan plan = getPrimarySubscriptionPlanLocked(subId); - if (plan == null + if (overrideMask != SUBSCRIPTION_OVERRIDE_UNMETERED && plan == null || plan.getDataLimitBehavior() == SubscriptionPlan.LIMIT_BEHAVIOR_UNKNOWN) { throw new IllegalStateException( "Must provide valid SubscriptionPlan to enable overriding"); @@ -3506,11 +3522,16 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final boolean overrideEnabled = Settings.Global.getInt(mContext.getContentResolver(), NETPOLICY_OVERRIDE_ENABLED, 1) != 0; if (overrideEnabled || overrideValue == 0) { - mHandler.sendMessage(mHandler.obtainMessage(MSG_SUBSCRIPTION_OVERRIDE, - overrideMask, overrideValue, subId)); + SomeArgs args = SomeArgs.obtain(); + args.arg1 = subId; + args.arg2 = overrideMask; + args.arg3 = overrideValue; + args.arg4 = applicableNetworks.toArray(); + mHandler.sendMessage(mHandler.obtainMessage(MSG_SUBSCRIPTION_OVERRIDE, args)); if (timeoutMillis > 0) { - mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SUBSCRIPTION_OVERRIDE, - overrideMask, 0, subId), timeoutMillis); + args.arg3 = 0; + mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SUBSCRIPTION_OVERRIDE, args), + timeoutMillis); } } } @@ -4778,10 +4799,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } private void dispatchSubscriptionOverride(INetworkPolicyListener listener, int subId, - int overrideMask, int overrideValue) { + int overrideMask, int overrideValue, int[] networkTypes) { if (listener != null) { try { - listener.onSubscriptionOverride(subId, overrideMask, overrideValue); + listener.onSubscriptionOverride(subId, overrideMask, overrideValue, networkTypes); } catch (RemoteException ignored) { } } @@ -4913,13 +4934,16 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { return true; } case MSG_SUBSCRIPTION_OVERRIDE: { - final int overrideMask = msg.arg1; - final int overrideValue = msg.arg2; - final int subId = (int) msg.obj; + final SomeArgs args = (SomeArgs) msg.obj; + final int subId = (int) args.arg1; + final int overrideMask = (int) args.arg2; + final int overrideValue = (int) args.arg3; + final int[] networkTypes = (int[]) args.arg4; final int length = mListeners.beginBroadcast(); for (int i = 0; i < length; i++) { final INetworkPolicyListener listener = mListeners.getBroadcastItem(i); - dispatchSubscriptionOverride(listener, subId, overrideMask, overrideValue); + dispatchSubscriptionOverride(listener, subId, overrideMask, overrideValue, + networkTypes); } mListeners.finishBroadcast(); return true; @@ -5380,6 +5404,17 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } @Override + public boolean checkUidNetworkingBlocked(int uid, int uidRules, + boolean isNetworkMetered, boolean isBackgroundRestricted) { + mContext.enforceCallingOrSelfPermission(OBSERVE_NETWORK_POLICY, TAG); + // Log of invoking this function is disabled because it will be called very frequently. And + // metrics are unlikely needed on this method because the callers are external and this + // method doesn't take any locks or perform expensive operations. + return isUidNetworkingBlockedInternal(uid, uidRules, isNetworkMetered, + isBackgroundRestricted, null); + } + + @Override public boolean isUidRestrictedOnMeteredNetworks(int uid) { mContext.enforceCallingOrSelfPermission(OBSERVE_NETWORK_POLICY, TAG); final int uidRules; @@ -5388,9 +5423,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { uidRules = mUidRules.get(uid, RULE_ALLOW_ALL); isBackgroundRestricted = mRestrictBackground; } - //TODO(b/177490332): The logic here might not be correct because it doesn't consider - // RULE_REJECT_METERED condition. And it could be replaced by - // isUidNetworkingBlockedInternal(). + // TODO(b/177490332): The logic here might not be correct because it doesn't consider + // RULE_REJECT_METERED condition. And it could be replaced by + // isUidNetworkingBlockedInternal(). return isBackgroundRestricted && !hasRule(uidRules, RULE_ALLOW_METERED) && !hasRule(uidRules, RULE_TEMPORARY_ALLOW_METERED); diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index c3cb42f95cc6..45419fe3bf76 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -22,8 +22,8 @@ import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL; import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE; import static android.app.NotificationManager.IMPORTANCE_NONE; import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; +import static android.util.StatsLog.ANNOTATION_ID_IS_UID; -import static com.android.internal.util.FrameworkStatsLog.ANNOTATION_ID_IS_UID; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES; diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 5417275bc8f1..2067fd081b4a 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -21,8 +21,8 @@ import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ENABLED; import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_REMOVED; import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY; import static android.service.notification.DNDModeProto.ROOT_CONFIG; +import static android.util.StatsLog.ANNOTATION_ID_IS_UID; -import static com.android.internal.util.FrameworkStatsLog.ANNOTATION_ID_IS_UID; import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE; import android.app.AppOpsManager; diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 330f99523507..9f0efa5fad83 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -299,6 +299,10 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements final ArraySet<File> unclaimedStages = newArraySet( stagingDir.listFiles(sStageFilter)); + // We also need to clean up orphaned staging directory for staged sessions + final File stagedSessionStagingDir = Environment.getDataStagingDirectory(volumeUuid); + unclaimedStages.addAll(newArraySet(stagedSessionStagingDir.listFiles())); + // Ignore stages claimed by active sessions for (int i = 0; i < mSessions.size(); i++) { final PackageInstallerSession session = mSessions.valueAt(i); diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 6594a90d2478..ae2e58ff8f55 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -1598,6 +1598,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { destroyInternal(); // Dispatch message to remove session from PackageInstallerService. dispatchSessionFinished(error, detailMessage, null); + // TODO(b/173194203): clean up staged session in destroyInternal() call instead + if (isStaged() && stageDir != null) { + cleanStageDir(); + } } private void onStorageUnhealthy() { diff --git a/services/core/java/com/android/server/tv/TvInputHardwareManager.java b/services/core/java/com/android/server/tv/TvInputHardwareManager.java index 3dfb99e5c0fc..bba5dcb0d1b9 100755 --- a/services/core/java/com/android/server/tv/TvInputHardwareManager.java +++ b/services/core/java/com/android/server/tv/TvInputHardwareManager.java @@ -976,7 +976,7 @@ class TvInputHardwareManager implements TvInputHal.Callback { AudioPortConfig sourceConfig = mAudioSource.activeConfig(); List<AudioPortConfig> sinkConfigs = new ArrayList<>(); AudioPatch[] audioPatchArray = new AudioPatch[] { mAudioPatch }; - boolean shouldRecreateAudioPatch = sourceUpdated || sinkUpdated; + boolean shouldRecreateAudioPatch = sourceUpdated || sinkUpdated || mAudioPatch == null; for (AudioDevicePort audioSink : mAudioSink) { AudioPortConfig sinkConfig = audioSink.activeConfig(); diff --git a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java index fd12c2d2ebb8..b6ddd93af3b8 100644 --- a/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java +++ b/services/core/java/com/android/server/vcn/UnderlyingNetworkTracker.java @@ -28,16 +28,15 @@ import android.net.NetworkRequest; import android.net.TelephonyNetworkSpecifier; import android.os.Handler; import android.os.ParcelUuid; -import android.telephony.SubscriptionInfo; -import android.telephony.SubscriptionManager; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; -import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; +import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; -import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; @@ -55,19 +54,18 @@ public class UnderlyingNetworkTracker { @NonNull private final VcnContext mVcnContext; @NonNull private final ParcelUuid mSubscriptionGroup; + @NonNull private final Set<Integer> mRequiredUnderlyingNetworkCapabilities; @NonNull private final UnderlyingNetworkTrackerCallback mCb; @NonNull private final Dependencies mDeps; @NonNull private final Handler mHandler; @NonNull private final ConnectivityManager mConnectivityManager; - @NonNull private final SubscriptionManager mSubscriptionManager; - @NonNull private final SparseArray<NetworkCallback> mCellBringupCallbacks = new SparseArray<>(); + @NonNull private final Map<Integer, NetworkCallback> mCellBringupCallbacks = new ArrayMap<>(); @NonNull private final NetworkCallback mWifiBringupCallback = new NetworkBringupCallback(); @NonNull private final NetworkCallback mRouteSelectionCallback = new RouteSelectionCallback(); - @NonNull private final Set<Integer> mSubIds = new ArraySet<>(); - - @NonNull private final Set<Integer> mRequiredUnderlyingNetworkCapabilities; + @NonNull private TelephonySubscriptionSnapshot mLastSnapshot; + private boolean mIsRunning = true; @Nullable private UnderlyingNetworkRecord mCurrentRecord; @Nullable private UnderlyingNetworkRecord.Builder mRecordInProgress; @@ -75,11 +73,13 @@ public class UnderlyingNetworkTracker { public UnderlyingNetworkTracker( @NonNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, + @NonNull TelephonySubscriptionSnapshot snapshot, @NonNull Set<Integer> requiredUnderlyingNetworkCapabilities, @NonNull UnderlyingNetworkTrackerCallback cb) { this( vcnContext, subscriptionGroup, + snapshot, requiredUnderlyingNetworkCapabilities, cb, new Dependencies()); @@ -88,11 +88,13 @@ public class UnderlyingNetworkTracker { private UnderlyingNetworkTracker( @NonNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, + @NonNull TelephonySubscriptionSnapshot snapshot, @NonNull Set<Integer> requiredUnderlyingNetworkCapabilities, @NonNull UnderlyingNetworkTrackerCallback cb, @NonNull Dependencies deps) { mVcnContext = Objects.requireNonNull(vcnContext, "Missing vcnContext"); mSubscriptionGroup = Objects.requireNonNull(subscriptionGroup, "Missing subscriptionGroup"); + mLastSnapshot = Objects.requireNonNull(snapshot, "Missing snapshot"); mRequiredUnderlyingNetworkCapabilities = Objects.requireNonNull( requiredUnderlyingNetworkCapabilities, @@ -103,7 +105,6 @@ public class UnderlyingNetworkTracker { mHandler = new Handler(mVcnContext.getLooper()); mConnectivityManager = mVcnContext.getContext().getSystemService(ConnectivityManager.class); - mSubscriptionManager = mVcnContext.getContext().getSystemService(SubscriptionManager.class); registerNetworkRequests(); } @@ -149,36 +150,49 @@ public class UnderlyingNetworkTracker { private void updateSubIdsAndCellularRequests() { mVcnContext.ensureRunningOnLooperThread(); - Set<Integer> prevSubIds = new ArraySet<>(mSubIds); - mSubIds.clear(); + // Don't bother re-filing NetworkRequests if this Tracker has been torn down. + if (!mIsRunning) { + return; + } - // Ensure NetworkRequests filed for all current subIds in mSubscriptionGroup - // STOPSHIP: b/177364490 use TelephonySubscriptionSnapshot to avoid querying Telephony - List<SubscriptionInfo> subInfos = - mSubscriptionManager.getSubscriptionsInGroup(mSubscriptionGroup); + final Set<Integer> subIdsInSubGroup = mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup); - for (SubscriptionInfo subInfo : subInfos) { - final int subId = subInfo.getSubscriptionId(); - mSubIds.add(subId); + // new subIds to track = (updated list of subIds) - (currently tracked subIds) + final Set<Integer> subIdsToRegister = new ArraySet<>(subIdsInSubGroup); + subIdsToRegister.removeAll(mCellBringupCallbacks.keySet()); - if (!mCellBringupCallbacks.contains(subId)) { - final NetworkBringupCallback cb = new NetworkBringupCallback(); - mCellBringupCallbacks.put(subId, cb); + // subIds to stop tracking = (currently tracked subIds) - (updated list of subIds) + final Set<Integer> subIdsToUnregister = new ArraySet<>(mCellBringupCallbacks.keySet()); + subIdsToUnregister.removeAll(subIdsInSubGroup); - mConnectivityManager.requestBackgroundNetwork( - getCellNetworkRequestForSubId(subId), mHandler, cb); - } + for (final int subId : subIdsToRegister) { + final NetworkBringupCallback cb = new NetworkBringupCallback(); + mCellBringupCallbacks.put(subId, cb); + + mConnectivityManager.requestBackgroundNetwork( + getCellNetworkRequestForSubId(subId), mHandler, cb); } - // unregister all NetworkCallbacks for outdated subIds - for (final int subId : prevSubIds) { - if (!mSubIds.contains(subId)) { - final NetworkCallback cb = mCellBringupCallbacks.removeReturnOld(subId); - mConnectivityManager.unregisterNetworkCallback(cb); - } + for (final int subId : subIdsToUnregister) { + final NetworkCallback cb = mCellBringupCallbacks.remove(subId); + mConnectivityManager.unregisterNetworkCallback(cb); } } + /** + * Update this UnderlyingNetworkTracker's TelephonySubscriptionSnapshot. + * + * <p>Updating the TelephonySubscriptionSnapshot will cause this UnderlyingNetworkTracker to + * reevaluate its NetworkBringupCallbacks. This may result in NetworkRequests being registered + * or unregistered if the subIds mapped to the this Tracker's SubscriptionGroup change. + */ + public void updateSubscriptionSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot) { + Objects.requireNonNull(snapshot, "Missing snapshot"); + + mLastSnapshot = snapshot; + updateSubIdsAndCellularRequests(); + } + /** Tears down this Tracker, and releases all underlying network requests. */ public void teardown() { mVcnContext.ensureRunningOnLooperThread(); @@ -186,11 +200,12 @@ public class UnderlyingNetworkTracker { mConnectivityManager.unregisterNetworkCallback(mWifiBringupCallback); mConnectivityManager.unregisterNetworkCallback(mRouteSelectionCallback); - for (final int subId : mSubIds) { - final NetworkCallback cb = mCellBringupCallbacks.removeReturnOld(subId); + for (final NetworkCallback cb : mCellBringupCallbacks.values()) { mConnectivityManager.unregisterNetworkCallback(cb); } - mSubIds.clear(); + mCellBringupCallbacks.clear(); + + mIsRunning = false; } /** Returns whether the currently selected Network matches the given network. */ diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java index 132883e4a041..a82f239948ff 100644 --- a/services/core/java/com/android/server/vcn/Vcn.java +++ b/services/core/java/com/android/server/vcn/Vcn.java @@ -27,9 +27,16 @@ import android.os.Message; import android.os.ParcelUuid; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.annotations.VisibleForTesting.Visibility; +import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; + +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Objects; +import java.util.Set; /** * Represents an single instance of a VCN. @@ -63,6 +70,15 @@ public class Vcn extends Handler { */ private static final int MSG_EVENT_NETWORK_REQUESTED = MSG_EVENT_BASE + 1; + /** + * The TelephonySubscriptionSnapshot tracked by VcnManagementService has changed. + * + * <p>This updated snapshot should be cached locally and passed to all VcnGatewayConnections. + * + * @param obj TelephonySubscriptionSnapshot + */ + private static final int MSG_EVENT_SUBSCRIPTIONS_CHANGED = MSG_EVENT_BASE + 2; + /** Triggers an immediate teardown of the entire Vcn, including GatewayConnections. */ private static final int MSG_CMD_TEARDOWN = MSG_CMD_BASE; @@ -76,20 +92,24 @@ public class Vcn extends Handler { new HashMap<>(); @NonNull private VcnConfig mConfig; + @NonNull private TelephonySubscriptionSnapshot mLastSnapshot; private boolean mIsRunning = true; public Vcn( @NonNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, - @NonNull VcnConfig config) { - this(vcnContext, subscriptionGroup, config, new Dependencies()); + @NonNull VcnConfig config, + @NonNull TelephonySubscriptionSnapshot snapshot) { + this(vcnContext, subscriptionGroup, config, snapshot, new Dependencies()); } - private Vcn( + @VisibleForTesting(visibility = Visibility.PRIVATE) + public Vcn( @NonNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, @NonNull VcnConfig config, + @NonNull TelephonySubscriptionSnapshot snapshot, @NonNull Dependencies deps) { super(Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper()); mVcnContext = vcnContext; @@ -98,6 +118,7 @@ public class Vcn extends Handler { mRequestListener = new VcnNetworkRequestListener(); mConfig = Objects.requireNonNull(config, "Missing config"); + mLastSnapshot = Objects.requireNonNull(snapshot, "Missing snapshot"); // Register to receive cached and future NetworkRequests mVcnContext.getVcnNetworkProvider().registerListener(mRequestListener); @@ -110,11 +131,24 @@ public class Vcn extends Handler { sendMessage(obtainMessage(MSG_EVENT_CONFIG_UPDATED, config)); } + /** Asynchronously updates the Subscription snapshot for this VCN. */ + public void updateSubscriptionSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot) { + Objects.requireNonNull(snapshot, "Missing snapshot"); + + sendMessage(obtainMessage(MSG_EVENT_SUBSCRIPTIONS_CHANGED, snapshot)); + } + /** Asynchronously tears down this Vcn instance, including VcnGatewayConnection(s) */ public void teardownAsynchronously() { sendMessageAtFrontOfQueue(obtainMessage(MSG_CMD_TEARDOWN)); } + /** Get current Gateways for testing purposes */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + public Set<VcnGatewayConnection> getVcnGatewayConnections() { + return Collections.unmodifiableSet(new HashSet<>(mVcnGatewayConnections.values())); + } + private class VcnNetworkRequestListener implements VcnNetworkProvider.NetworkRequestListener { @Override public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) { @@ -137,6 +171,9 @@ public class Vcn extends Handler { case MSG_EVENT_NETWORK_REQUESTED: handleNetworkRequested((NetworkRequest) msg.obj, msg.arg1, msg.arg2); break; + case MSG_EVENT_SUBSCRIPTIONS_CHANGED: + handleSubscriptionsChanged((TelephonySubscriptionSnapshot) msg.obj); + break; case MSG_CMD_TEARDOWN: handleTeardown(); break; @@ -192,13 +229,26 @@ public class Vcn extends Handler { "Bringing up new VcnGatewayConnection for request " + request.requestId); final VcnGatewayConnection vcnGatewayConnection = - new VcnGatewayConnection( - mVcnContext, mSubscriptionGroup, gatewayConnectionConfig); + mDeps.newVcnGatewayConnection( + mVcnContext, + mSubscriptionGroup, + mLastSnapshot, + gatewayConnectionConfig); mVcnGatewayConnections.put(gatewayConnectionConfig, vcnGatewayConnection); } } } + private void handleSubscriptionsChanged(@NonNull TelephonySubscriptionSnapshot snapshot) { + mLastSnapshot = snapshot; + + if (mIsRunning) { + for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) { + gatewayConnection.updateSubscriptionSnapshot(mLastSnapshot); + } + } + } + private boolean requestSatisfiedByGatewayConnectionConfig( @NonNull NetworkRequest request, @NonNull VcnGatewayConnectionConfig config) { final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder(); @@ -214,11 +264,24 @@ public class Vcn extends Handler { } /** Retrieves the network score for a VCN Network */ - private int getNetworkScore() { + // Package visibility for use in VcnGatewayConnection + static int getNetworkScore() { // TODO: STOPSHIP (b/173549607): Make this use new NetworkSelection, or some magic "max in // subGrp" value return 52; } - private static class Dependencies {} + /** External dependencies used by Vcn, for injection in tests */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + public static class Dependencies { + /** Builds a new VcnGatewayConnection */ + public VcnGatewayConnection newVcnGatewayConnection( + VcnContext vcnContext, + ParcelUuid subscriptionGroup, + TelephonySubscriptionSnapshot snapshot, + VcnGatewayConnectionConfig connectionConfig) { + return new VcnGatewayConnection( + vcnContext, subscriptionGroup, snapshot, connectionConfig); + } + } } diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java index 39c96069f9c6..853bb4324f90 100644 --- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java +++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java @@ -17,8 +17,11 @@ package com.android.server.vcn; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static com.android.server.VcnManagementService.VDBG; @@ -34,8 +37,10 @@ import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkAgent; +import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; import android.net.RouteInfo; +import android.net.TelephonyNetworkSpecifier; import android.net.annotations.PolicyDirection; import android.net.ipsec.ike.ChildSessionCallback; import android.net.ipsec.ike.ChildSessionConfiguration; @@ -47,16 +52,21 @@ import android.net.ipsec.ike.IkeSessionParams; import android.net.ipsec.ike.exceptions.IkeException; import android.net.ipsec.ike.exceptions.IkeProtocolException; import android.net.vcn.VcnGatewayConnectionConfig; +import android.net.vcn.VcnTransportInfo; +import android.net.wifi.WifiInfo; import android.os.Handler; import android.os.HandlerExecutor; import android.os.Message; import android.os.ParcelUuid; +import android.util.ArraySet; import android.util.Slog; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; import com.android.internal.util.State; import com.android.internal.util.StateMachine; +import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord; import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkTrackerCallback; @@ -64,6 +74,7 @@ import java.io.IOException; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; +import java.util.Arrays; import java.util.Objects; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -113,6 +124,9 @@ import java.util.concurrent.TimeUnit; public class VcnGatewayConnection extends StateMachine { private static final String TAG = VcnGatewayConnection.class.getSimpleName(); + private static final int[] MERGED_CAPABILITIES = + new int[] {NET_CAPABILITY_NOT_METERED, NET_CAPABILITY_NOT_ROAMING}; + private static final InetAddress DUMMY_ADDR = InetAddresses.parseNumericAddress("192.0.2.0"); private static final int ARG_NOT_PRESENT = Integer.MIN_VALUE; @@ -359,6 +373,16 @@ public class VcnGatewayConnection extends StateMachine { */ private static final int EVENT_TEARDOWN_TIMEOUT_EXPIRED = 8; + /** + * Sent when this VcnGatewayConnection is notified of a change in TelephonySubscriptions. + * + * <p>Relevant in all states. + * + * @param arg1 The "all" token; this signal is always honored. + */ + // TODO(b/178426520): implement handling of this event + private static final int EVENT_SUBSCRIPTIONS_CHANGED = 9; + @VisibleForTesting(visibility = Visibility.PRIVATE) @NonNull final DisconnectedState mDisconnectedState = new DisconnectedState(); @@ -379,6 +403,11 @@ public class VcnGatewayConnection extends StateMachine { @NonNull final RetryTimeoutState mRetryTimeoutState = new RetryTimeoutState(); + @NonNull private final Object mLock = new Object(); + + @GuardedBy("mLock") + @NonNull private TelephonySubscriptionSnapshot mLastSnapshot; + @NonNull private final VcnContext mVcnContext; @NonNull private final ParcelUuid mSubscriptionGroup; @NonNull private final UnderlyingNetworkTracker mUnderlyingNetworkTracker; @@ -457,14 +486,16 @@ public class VcnGatewayConnection extends StateMachine { public VcnGatewayConnection( @NonNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, + @NonNull TelephonySubscriptionSnapshot snapshot, @NonNull VcnGatewayConnectionConfig connectionConfig) { - this(vcnContext, subscriptionGroup, connectionConfig, new Dependencies()); + this(vcnContext, subscriptionGroup, snapshot, connectionConfig, new Dependencies()); } @VisibleForTesting(visibility = Visibility.PRIVATE) VcnGatewayConnection( @NonNull VcnContext vcnContext, @NonNull ParcelUuid subscriptionGroup, + @NonNull TelephonySubscriptionSnapshot snapshot, @NonNull VcnGatewayConnectionConfig connectionConfig, @NonNull Dependencies deps) { super(TAG, Objects.requireNonNull(vcnContext, "Missing vcnContext").getLooper()); @@ -473,12 +504,17 @@ public class VcnGatewayConnection extends StateMachine { mConnectionConfig = Objects.requireNonNull(connectionConfig, "Missing connectionConfig"); mDeps = Objects.requireNonNull(deps, "Missing deps"); + synchronized (mLock) { + mLastSnapshot = Objects.requireNonNull(snapshot, "Missing snapshot"); + } + mUnderlyingNetworkTrackerCallback = new VcnUnderlyingNetworkTrackerCallback(); mUnderlyingNetworkTracker = mDeps.newUnderlyingNetworkTracker( mVcnContext, subscriptionGroup, + mLastSnapshot, mConnectionConfig.getAllUnderlyingCapabilities(), mUnderlyingNetworkTrackerCallback); mIpSecManager = mVcnContext.getContext().getSystemService(IpSecManager.class); @@ -533,10 +569,31 @@ public class VcnGatewayConnection extends StateMachine { mUnderlyingNetworkTracker.teardown(); } + /** + * Notify this Gateway that subscriptions have changed. + * + * <p>This snapshot should be used to update any keepalive requests necessary for potential + * underlying Networks in this Gateway's subscription group. + */ + public void updateSubscriptionSnapshot(@NonNull TelephonySubscriptionSnapshot snapshot) { + Objects.requireNonNull(snapshot, "Missing snapshot"); + + // Vcn is the only user of this method and runs on the same Thread, but lock around + // mLastSnapshot to be technically correct. + synchronized (mLock) { + mLastSnapshot = snapshot; + mUnderlyingNetworkTracker.updateSubscriptionSnapshot(mLastSnapshot); + } + + sendMessage(EVENT_SUBSCRIPTIONS_CHANGED, TOKEN_ALL); + } + private class VcnUnderlyingNetworkTrackerCallback implements UnderlyingNetworkTrackerCallback { @Override public void onSelectedUnderlyingNetworkChanged( @Nullable UnderlyingNetworkRecord underlying) { + // TODO(b/179091925): Move the delayed-message handling to BaseState + // If underlying is null, all underlying networks have been lost. Disconnect VCN after a // timeout. if (underlying == null) { @@ -668,7 +725,8 @@ public class VcnGatewayConnection extends StateMachine { case EVENT_TRANSFORM_CREATED: // Fallthrough case EVENT_SETUP_COMPLETED: // Fallthrough case EVENT_DISCONNECT_REQUESTED: // Fallthrough - case EVENT_TEARDOWN_TIMEOUT_EXPIRED: + case EVENT_TEARDOWN_TIMEOUT_EXPIRED: // Fallthrough + case EVENT_SUBSCRIPTIONS_CHANGED: logUnexpectedEvent(msg.what); break; default: @@ -921,6 +979,8 @@ public class VcnGatewayConnection extends StateMachine { transitionTo(mDisconnectingState); break; case EVENT_SESSION_CLOSED: + // Disconnecting state waits for EVENT_SESSION_CLOSED to shutdown, and this + // message may not be posted again. Defer to ensure immediate shutdown. deferMessage(msg); transitionTo(mDisconnectingState); @@ -941,7 +1001,108 @@ public class VcnGatewayConnection extends StateMachine { } } - private abstract class ConnectedStateBase extends ActiveBaseState {} + private abstract class ConnectedStateBase extends ActiveBaseState { + protected void updateNetworkAgent( + @NonNull IpSecTunnelInterface tunnelIface, + @NonNull NetworkAgent agent, + @NonNull ChildSessionConfiguration childConfig) { + final NetworkCapabilities caps = + buildNetworkCapabilities(mConnectionConfig, mUnderlying); + final LinkProperties lp = + buildConnectedLinkProperties(mConnectionConfig, tunnelIface, childConfig); + + agent.sendNetworkCapabilities(caps); + agent.sendLinkProperties(lp); + } + + protected NetworkAgent buildNetworkAgent( + @NonNull IpSecTunnelInterface tunnelIface, + @NonNull ChildSessionConfiguration childConfig) { + final NetworkCapabilities caps = + buildNetworkCapabilities(mConnectionConfig, mUnderlying); + final LinkProperties lp = + buildConnectedLinkProperties(mConnectionConfig, tunnelIface, childConfig); + + final NetworkAgent agent = + new NetworkAgent( + mVcnContext.getContext(), + mVcnContext.getLooper(), + TAG, + caps, + lp, + Vcn.getNetworkScore(), + new NetworkAgentConfig(), + mVcnContext.getVcnNetworkProvider()) { + @Override + public void unwanted() { + teardownAsynchronously(); + } + }; + + agent.register(); + agent.markConnected(); + + return agent; + } + + protected void applyTransform( + int token, + @NonNull IpSecTunnelInterface tunnelIface, + @NonNull Network underlyingNetwork, + @NonNull IpSecTransform transform, + int direction) { + try { + // TODO: Set underlying network of tunnel interface + + // Transforms do not need to be persisted; the IkeSession will keep them alive + mIpSecManager.applyTunnelModeTransform(tunnelIface, direction, transform); + } catch (IOException e) { + Slog.d(TAG, "Transform application failed for network " + token, e); + sessionLost(token, e); + } + } + + protected void setupInterface( + int token, + @NonNull IpSecTunnelInterface tunnelIface, + @NonNull ChildSessionConfiguration childConfig) { + setupInterface(token, tunnelIface, childConfig, null); + } + + protected void setupInterface( + int token, + @NonNull IpSecTunnelInterface tunnelIface, + @NonNull ChildSessionConfiguration childConfig, + @Nullable ChildSessionConfiguration oldChildConfig) { + try { + final Set<LinkAddress> newAddrs = + new ArraySet<>(childConfig.getInternalAddresses()); + final Set<LinkAddress> existingAddrs = new ArraySet<>(); + if (oldChildConfig != null) { + existingAddrs.addAll(oldChildConfig.getInternalAddresses()); + } + + final Set<LinkAddress> toAdd = new ArraySet<>(); + toAdd.addAll(newAddrs); + toAdd.removeAll(existingAddrs); + + final Set<LinkAddress> toRemove = new ArraySet<>(); + toRemove.addAll(existingAddrs); + toRemove.removeAll(newAddrs); + + for (LinkAddress address : toAdd) { + tunnelIface.addAddress(address.getAddress(), address.getPrefixLength()); + } + + for (LinkAddress address : toRemove) { + tunnelIface.removeAddress(address.getAddress(), address.getPrefixLength()); + } + } catch (IOException e) { + Slog.d(TAG, "Adding address to tunnel failed for token " + token, e); + sessionLost(token, e); + } + } + } /** * Stable state representing a VCN that has a functioning connection to the mobility anchor. @@ -951,7 +1112,89 @@ public class VcnGatewayConnection extends StateMachine { */ class ConnectedState extends ConnectedStateBase { @Override - protected void processStateMsg(Message msg) {} + protected void enterState() throws Exception { + // Successful connection, clear failed attempt counter + mFailedAttempts = 0; + } + + @Override + protected void processStateMsg(Message msg) { + switch (msg.what) { + case EVENT_UNDERLYING_NETWORK_CHANGED: + handleUnderlyingNetworkChanged(msg); + break; + case EVENT_SESSION_CLOSED: + // Disconnecting state waits for EVENT_SESSION_CLOSED to shutdown, and this + // message may not be posted again. Defer to ensure immediate shutdown. + deferMessage(msg); + transitionTo(mDisconnectingState); + break; + case EVENT_SESSION_LOST: + transitionTo(mDisconnectingState); + break; + case EVENT_TRANSFORM_CREATED: + final EventTransformCreatedInfo transformCreatedInfo = + (EventTransformCreatedInfo) msg.obj; + + applyTransform( + mCurrentToken, + mTunnelIface, + mUnderlying.network, + transformCreatedInfo.transform, + transformCreatedInfo.direction); + break; + case EVENT_SETUP_COMPLETED: + mChildConfig = ((EventSetupCompletedInfo) msg.obj).childSessionConfig; + + setupInterfaceAndNetworkAgent(mCurrentToken, mTunnelIface, mChildConfig); + break; + case EVENT_DISCONNECT_REQUESTED: + handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason); + break; + default: + logUnhandledMessage(msg); + break; + } + } + + private void handleUnderlyingNetworkChanged(@NonNull Message msg) { + final UnderlyingNetworkRecord oldUnderlying = mUnderlying; + mUnderlying = ((EventUnderlyingNetworkChangedInfo) msg.obj).newUnderlying; + + if (mUnderlying == null) { + // Ignored for now; a new network may be coming up. If none does, the delayed + // NETWORK_LOST disconnect will be fired, and tear down the session + network. + return; + } + + // mUnderlying assumed non-null, given check above. + // If network changed, migrate. Otherwise, update any existing networkAgent. + if (oldUnderlying == null || !oldUnderlying.network.equals(mUnderlying.network)) { + mIkeSession.setNetwork(mUnderlying.network); + } else { + // oldUnderlying is non-null & underlying network itself has not changed + // (only network properties were changed). + + // Network not yet set up, or child not yet connected. + if (mNetworkAgent != null && mChildConfig != null) { + // If only network properties changed and agent is active, update properties + updateNetworkAgent(mTunnelIface, mNetworkAgent, mChildConfig); + } + } + } + + protected void setupInterfaceAndNetworkAgent( + int token, + @NonNull IpSecTunnelInterface tunnelIface, + @NonNull ChildSessionConfiguration childConfig) { + setupInterface(token, tunnelIface, childConfig); + + if (mNetworkAgent == null) { + mNetworkAgent = buildNetworkAgent(tunnelIface, childConfig); + } else { + updateNetworkAgent(tunnelIface, mNetworkAgent, childConfig); + } + } } /** @@ -966,7 +1209,8 @@ public class VcnGatewayConnection extends StateMachine { @VisibleForTesting(visibility = Visibility.PRIVATE) static NetworkCapabilities buildNetworkCapabilities( - @NonNull VcnGatewayConnectionConfig gatewayConnectionConfig) { + @NonNull VcnGatewayConnectionConfig gatewayConnectionConfig, + @Nullable UnderlyingNetworkRecord underlying) { final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder(); builder.addTransportType(TRANSPORT_CELLULAR); @@ -978,6 +1222,52 @@ public class VcnGatewayConnection extends StateMachine { builder.addCapability(cap); } + if (underlying != null) { + final NetworkCapabilities underlyingCaps = underlying.networkCapabilities; + + // Mirror merged capabilities. + for (int cap : MERGED_CAPABILITIES) { + if (underlyingCaps.hasCapability(cap)) { + builder.addCapability(cap); + } + } + + // Set admin UIDs for ConnectivityDiagnostics use. + final int[] underlyingAdminUids = underlyingCaps.getAdministratorUids(); + Arrays.sort(underlyingAdminUids); // Sort to allow contains check below. + + final int[] adminUids; + if (underlyingCaps.getOwnerUid() > 0 // No owner UID specified + && 0 > Arrays.binarySearch(// Owner UID not found in admin UID list. + underlyingAdminUids, underlyingCaps.getOwnerUid())) { + adminUids = Arrays.copyOf(underlyingAdminUids, underlyingAdminUids.length + 1); + adminUids[adminUids.length - 1] = underlyingCaps.getOwnerUid(); + Arrays.sort(adminUids); + } else { + adminUids = underlyingAdminUids; + } + builder.setAdministratorUids(adminUids); + + // Set TransportInfo for SysUI use (never parcelled out of SystemServer). + if (underlyingCaps.hasTransport(TRANSPORT_WIFI) + && underlyingCaps.getTransportInfo() instanceof WifiInfo) { + final WifiInfo wifiInfo = (WifiInfo) underlyingCaps.getTransportInfo(); + builder.setTransportInfo(new VcnTransportInfo(wifiInfo)); + } else if (underlyingCaps.hasTransport(TRANSPORT_CELLULAR) + && underlyingCaps.getNetworkSpecifier() instanceof TelephonyNetworkSpecifier) { + final TelephonyNetworkSpecifier telNetSpecifier = + (TelephonyNetworkSpecifier) underlyingCaps.getNetworkSpecifier(); + builder.setTransportInfo(new VcnTransportInfo(telNetSpecifier.getSubscriptionId())); + } else { + Slog.wtf( + TAG, + "Unknown transport type or missing TransportInfo/NetworkSpecifier for" + + " non-null underlying network"); + } + } + + // TODO: Make a VcnNetworkSpecifier, and match all underlying subscription IDs. + return builder.build(); } @@ -1138,10 +1428,15 @@ public class VcnGatewayConnection extends StateMachine { public UnderlyingNetworkTracker newUnderlyingNetworkTracker( VcnContext vcnContext, ParcelUuid subscriptionGroup, + TelephonySubscriptionSnapshot snapshot, Set<Integer> requiredUnderlyingNetworkCapabilities, UnderlyingNetworkTrackerCallback callback) { return new UnderlyingNetworkTracker( - vcnContext, subscriptionGroup, requiredUnderlyingNetworkCapabilities, callback); + vcnContext, + subscriptionGroup, + snapshot, + requiredUnderlyingNetworkCapabilities, + callback); } /** Builds a new IkeSession. */ diff --git a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java index b9babae4c6b7..fe4ea303610f 100644 --- a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java +++ b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java @@ -25,6 +25,9 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.annotations.VisibleForTesting.Visibility; + import java.util.Objects; import java.util.Set; @@ -52,8 +55,13 @@ public class VcnNetworkProvider extends NetworkProvider { super(context, looper, VcnNetworkProvider.class.getSimpleName()); } - // Package-private - void registerListener(@NonNull NetworkRequestListener listener) { + /** + * Registers a NetworkRequestListener with this NetworkProvider. + * + * <p>Upon registering, the provided listener will receive all cached requests. + */ + @VisibleForTesting(visibility = Visibility.PACKAGE) + public void registerListener(@NonNull NetworkRequestListener listener) { mListeners.add(listener); // Send listener all cached requests @@ -62,8 +70,9 @@ public class VcnNetworkProvider extends NetworkProvider { } } - // Package-private - void unregisterListener(@NonNull NetworkRequestListener listener) { + /** Unregisters the specified listener from receiving future NetworkRequests. */ + @VisibleForTesting(visibility = Visibility.PACKAGE) + public void unregisterListener(@NonNull NetworkRequestListener listener) { mListeners.remove(listener); } diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 7af237b80cfa..fd8fa82ef0a5 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -2023,13 +2023,6 @@ class ActivityStarter { final ActivityRecord top = targetTask.performClearTaskForReuseLocked(mStartActivity, mLaunchFlags); - // The above code can remove {@code reusedActivity} from the task, leading to the - // {@code ActivityRecord} removing its reference to the {@code Task}. The task - // reference is needed in the call below to {@link setTargetStackAndMoveToFrontIfNeeded} - if (targetTaskTop.getTask() == null) { - targetTask.addChild(targetTaskTop); - } - if (top != null) { if (top.isRootOfTask()) { // Activity aliases may mean we use different intents for the top activity, diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS index 389d07ae34b5..bbcc2c1e581b 100644 --- a/services/core/jni/OWNERS +++ b/services/core/jni/OWNERS @@ -11,6 +11,9 @@ per-file com_android_server_input_InputManagerService.cpp = michaelwr@google.com per-file com_android_server_HardwarePropertiesManagerService.cpp = michaelwr@google.com, santoscordon@google.com per-file com_android_server_power_PowerManagerService.* = michaelwr@google.com, santoscordon@google.com +# BatteryStats +per-file com_android_server_am_BatteryStatsService.cpp = file:/BATTERY_STATS_OWNERS + per-file Android.bp = file:platform/build/soong:/OWNERS per-file com_android_server_Usb* = file:/services/usb/OWNERS per-file com_android_server_Vibrator* = file:/services/core/java/com/android/server/vibrator/OWNERS diff --git a/services/incremental/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp index 0ae10b6dc3b5..a3cadf3cc0b2 100644 --- a/services/incremental/BinderIncrementalService.cpp +++ b/services/incremental/BinderIncrementalService.cpp @@ -88,7 +88,6 @@ BinderIncrementalService* BinderIncrementalService::start(JNIEnv* env) { } sp<ProcessState> ps(ProcessState::self()); ps->startThreadPool(); - ps->giveThreadPoolName(); // sm->addService increments the reference count, and now we're OK with returning the pointer. return self.get(); } diff --git a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java index 45bca6829553..1328b91d03f9 100644 --- a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java @@ -16,16 +16,18 @@ package com.android.server.apphibernation; +import static android.content.pm.PackageManager.MATCH_ANY_USER; + import static org.junit.Assert.assertTrue; import static org.mockito.AdditionalAnswers.returnsArgAt; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.intThat; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; -import static org.mockito.internal.verification.VerificationModeFactory.times; import android.app.IActivityManager; import android.content.BroadcastReceiver; @@ -48,6 +50,7 @@ import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.util.ArrayList; @@ -76,18 +79,21 @@ public final class AppHibernationServiceTest { private IActivityManager mIActivityManager; @Mock private UserManager mUserManager; + @Mock + private HibernationStateDiskStore<UserLevelState> mHibernationStateDiskStore; @Captor private ArgumentCaptor<BroadcastReceiver> mReceiverCaptor; @Before public void setUp() throws RemoteException { + // Share class loader to allow access to package-private classes + System.setProperty("dexmaker.share_classloader", "true"); MockitoAnnotations.initMocks(this); doReturn(mContext).when(mContext).createContextAsUser(any(), anyInt()); - mAppHibernationService = new AppHibernationService(mContext, mIPackageManager, - mIActivityManager, mUserManager); + mAppHibernationService = new AppHibernationService(new MockInjector(mContext)); - verify(mContext, times(2)).registerReceiver(mReceiverCaptor.capture(), any()); + verify(mContext).registerReceiver(mReceiverCaptor.capture(), any()); mBroadcastReceiver = mReceiverCaptor.getValue(); doReturn(mUserInfos).when(mUserManager).getUsers(); @@ -95,12 +101,19 @@ public final class AppHibernationServiceTest { doAnswer(returnsArgAt(2)).when(mIActivityManager).handleIncomingUser(anyInt(), anyInt(), anyInt(), anyBoolean(), anyBoolean(), any(), any()); - addUser(USER_ID_1); + List<PackageInfo> packages = new ArrayList<>(); + packages.add(makePackageInfo(PACKAGE_NAME_1)); + doReturn(new ParceledListSlice<>(packages)).when(mIPackageManager).getInstalledPackages( + intThat(arg -> (arg & MATCH_ANY_USER) != 0), anyInt()); mAppHibernationService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); + + UserInfo userInfo = addUser(USER_ID_1); + mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(userInfo)); + doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_1); } @Test - public void testSetHibernatingForUser_packageIsHibernating() throws RemoteException { + public void testSetHibernatingForUser_packageIsHibernating() { // WHEN we hibernate a package for a user mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_1, true); @@ -109,8 +122,7 @@ public final class AppHibernationServiceTest { } @Test - public void testSetHibernatingForUser_newPackageAdded_packageIsHibernating() - throws RemoteException { + public void testSetHibernatingForUser_newPackageAdded_packageIsHibernating() { // WHEN a new package is added and it is hibernated Intent intent = new Intent(Intent.ACTION_PACKAGE_ADDED, Uri.fromParts(PACKAGE_SCHEME, PACKAGE_NAME_2, null /* fragment */)); @@ -124,17 +136,12 @@ public final class AppHibernationServiceTest { } @Test - public void testSetHibernatingForUser_newUserAdded_packageIsHibernating() + public void testSetHibernatingForUser_newUserUnlocked_packageIsHibernating() throws RemoteException { // WHEN a new user is added and a package from the user is hibernated - List<PackageInfo> userPackages = new ArrayList<>(); - userPackages.add(makePackageInfo(PACKAGE_NAME_1)); - doReturn(new ParceledListSlice<>(userPackages)).when(mIPackageManager) - .getInstalledPackages(anyInt(), eq(USER_ID_2)); - Intent intent = new Intent(Intent.ACTION_USER_ADDED); - intent.putExtra(Intent.EXTRA_USER_HANDLE, USER_ID_2); - mBroadcastReceiver.onReceive(mContext, intent); - + UserInfo user2 = addUser(USER_ID_2); + mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(user2)); + doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_2); mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_2, true); // THEN the new user's package is hibernated @@ -142,8 +149,7 @@ public final class AppHibernationServiceTest { } @Test - public void testIsHibernatingForUser_packageReplaced_stillReturnsHibernating() - throws RemoteException { + public void testIsHibernatingForUser_packageReplaced_stillReturnsHibernating() { // GIVEN a package is currently hibernated mAppHibernationService.setHibernatingForUser(PACKAGE_NAME_1, USER_ID_1, true); @@ -168,25 +174,25 @@ public final class AppHibernationServiceTest { } /** - * Add a mock user with one package. Must be called before - * {@link AppHibernationService#onBootPhase(int)} to work properly. + * Add a mock user with one package. */ - private void addUser(int userId) throws RemoteException { - addUser(userId, new String[]{PACKAGE_NAME_1}); + private UserInfo addUser(int userId) throws RemoteException { + return addUser(userId, new String[]{PACKAGE_NAME_1}); } /** - * Add a mock user with the packages specified. Must be called before - * {@link AppHibernationService#onBootPhase(int)} to work properly + * Add a mock user with the packages specified. */ - private void addUser(int userId, String[] packageNames) throws RemoteException { - mUserInfos.add(new UserInfo(userId, "user_" + userId, 0 /* flags */)); + private UserInfo addUser(int userId, String[] packageNames) throws RemoteException { + UserInfo userInfo = new UserInfo(userId, "user_" + userId, 0 /* flags */); + mUserInfos.add(userInfo); List<PackageInfo> userPackages = new ArrayList<>(); for (String pkgName : packageNames) { userPackages.add(makePackageInfo(pkgName)); } doReturn(new ParceledListSlice<>(userPackages)).when(mIPackageManager) - .getInstalledPackages(anyInt(), eq(userId)); + .getInstalledPackages(intThat(arg -> (arg & MATCH_ANY_USER) == 0), eq(userId)); + return userInfo; } private static PackageInfo makePackageInfo(String packageName) { @@ -194,4 +200,42 @@ public final class AppHibernationServiceTest { pkg.packageName = packageName; return pkg; } + + private class MockInjector implements AppHibernationService.Injector { + private final Context mContext; + + MockInjector(Context context) { + mContext = context; + } + + @Override + public IActivityManager getActivityManager() { + return mIActivityManager; + } + + @Override + public Context getContext() { + return mContext; + } + + @Override + public IPackageManager getPackageManager() { + return mIPackageManager; + } + + @Override + public UserManager getUserManager() { + return mUserManager; + } + + @Override + public HibernationStateDiskStore<GlobalLevelState> getGlobalLevelDiskStore() { + return Mockito.mock(HibernationStateDiskStore.class); + } + + @Override + public HibernationStateDiskStore<UserLevelState> getUserLevelDiskStore(int userId) { + return Mockito.mock(HibernationStateDiskStore.class); + } + } } diff --git a/services/tests/servicestests/src/com/android/server/apphibernation/HibernationStateDiskStoreTest.java b/services/tests/servicestests/src/com/android/server/apphibernation/HibernationStateDiskStoreTest.java new file mode 100644 index 000000000000..59f3c35f2137 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/apphibernation/HibernationStateDiskStoreTest.java @@ -0,0 +1,236 @@ +/* + * 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.apphibernation; + +import static org.junit.Assert.assertEquals; + +import android.os.FileUtils; +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoOutputStream; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + + +@SmallTest +public class HibernationStateDiskStoreTest { + private static final String STATES_FILE_NAME = "states"; + private final MockScheduledExecutorService mMockScheduledExecutorService = + new MockScheduledExecutorService(); + + private File mFile; + private HibernationStateDiskStore<String> mHibernationStateDiskStore; + + + @Before + public void setUp() { + mFile = new File(InstrumentationRegistry.getContext().getCacheDir(), "test"); + mHibernationStateDiskStore = new HibernationStateDiskStore<>(mFile, + new MockProtoReadWriter(), mMockScheduledExecutorService, STATES_FILE_NAME); + } + + @After + public void tearDown() { + FileUtils.deleteContentsAndDir(mFile); + } + + @Test + public void testScheduleWriteHibernationStates_writesDataThatCanBeRead() { + // GIVEN some data to be written + List<String> toWrite = new ArrayList<>(Arrays.asList("A", "B")); + + // WHEN the data is written + mHibernationStateDiskStore.scheduleWriteHibernationStates(toWrite); + mMockScheduledExecutorService.executeScheduledTask(); + + // THEN the read data is equal to what was written + List<String> storedStrings = mHibernationStateDiskStore.readHibernationStates(); + for (int i = 0; i < toWrite.size(); i++) { + assertEquals(toWrite.get(i), storedStrings.get(i)); + } + } + + @Test + public void testScheduleWriteHibernationStates_laterWritesOverwritePrevious() { + // GIVEN store has some data it is scheduled to write + mHibernationStateDiskStore.scheduleWriteHibernationStates( + new ArrayList<>(Arrays.asList("C", "D"))); + + // WHEN a write is scheduled with new data + List<String> toWrite = new ArrayList<>(Arrays.asList("A", "B")); + mHibernationStateDiskStore.scheduleWriteHibernationStates(toWrite); + mMockScheduledExecutorService.executeScheduledTask(); + + // THEN the written data is the last scheduled data + List<String> storedStrings = mHibernationStateDiskStore.readHibernationStates(); + for (int i = 0; i < toWrite.size(); i++) { + assertEquals(toWrite.get(i), storedStrings.get(i)); + } + } + + /** + * Mock proto read / writer that just writes and reads a list of String data. + */ + private final class MockProtoReadWriter implements ProtoReadWriter<List<String>> { + private static final long FIELD_ID = 1; + + @Override + public void writeToProto(@NonNull ProtoOutputStream stream, + @NonNull List<String> data) { + for (int i = 0, size = data.size(); i < size; i++) { + stream.write(FIELD_ID, data.get(i)); + } + } + + @Nullable + @Override + public List<String> readFromProto(@NonNull ProtoInputStream stream) + throws IOException { + ArrayList<String> list = new ArrayList<>(); + while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + list.add(stream.readString(FIELD_ID)); + } + return list; + } + } + + /** + * Mock scheduled executor service that has minimum implementation and can synchronously + * execute scheduled tasks. + */ + private final class MockScheduledExecutorService implements ScheduledExecutorService { + + Runnable mScheduledRunnable = null; + + @Override + public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) { + mScheduledRunnable = command; + return Mockito.mock(ScheduledFuture.class); + } + + @Override + public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) { + throw new UnsupportedOperationException(); + } + + @Override + public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, + long period, TimeUnit unit) { + throw new UnsupportedOperationException(); + } + + @Override + public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, + long delay, TimeUnit unit) { + throw new UnsupportedOperationException(); + } + + @Override + public void shutdown() { + throw new UnsupportedOperationException(); + } + + @Override + public List<Runnable> shutdownNow() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isShutdown() { + return false; + } + + @Override + public boolean isTerminated() { + return false; + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + throw new UnsupportedOperationException(); + } + + @Override + public <T> Future<T> submit(Callable<T> task) { + throw new UnsupportedOperationException(); + } + + @Override + public <T> Future<T> submit(Runnable task, T result) { + throw new UnsupportedOperationException(); + } + + @Override + public Future<?> submit(Runnable task) { + throw new UnsupportedOperationException(); + } + + @Override + public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) + throws InterruptedException { + throw new UnsupportedOperationException(); + } + + @Override + public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, + TimeUnit unit) throws InterruptedException { + throw new UnsupportedOperationException(); + } + + @Override + public <T> T invokeAny(Collection<? extends Callable<T>> tasks) + throws InterruptedException, ExecutionException { + throw new UnsupportedOperationException(); + } + + @Override + public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + throw new UnsupportedOperationException(); + } + + @Override + public void execute(Runnable command) { + throw new UnsupportedOperationException(); + } + + void executeScheduledTask() { + mScheduledRunnable.run(); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java index df19aeb13707..58ba90726b80 100644 --- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java @@ -1829,11 +1829,11 @@ public class NetworkPolicyManagerServiceTest { } /** - * Exhaustively test isUidNetworkingBlocked to output the expected results based on external + * Exhaustively test checkUidNetworkingBlocked to output the expected results based on external * conditions. */ @Test - public void testIsUidNetworkingBlocked() { + public void testCheckUidNetworkingBlocked() { final ArrayList<Pair<Boolean, Integer>> expectedBlockedStates = new ArrayList<>(); // Metered network. Data saver on. @@ -1877,17 +1877,16 @@ public class NetworkPolicyManagerServiceTest { private void verifyNetworkBlockedState(boolean metered, boolean backgroundRestricted, ArrayList<Pair<Boolean, Integer>> expectedBlockedStateForRules) { - final NetworkPolicyManagerInternal npmi = LocalServices - .getService(NetworkPolicyManagerInternal.class); for (Pair<Boolean, Integer> pair : expectedBlockedStateForRules) { final boolean expectedResult = pair.first; final int rule = pair.second; assertEquals(formatBlockedStateError(UID_A, rule, metered, backgroundRestricted), - expectedResult, - npmi.isUidNetworkingBlocked(UID_A, rule, metered, backgroundRestricted)); + expectedResult, mService.checkUidNetworkingBlocked(UID_A, rule, + metered, backgroundRestricted)); assertFalse(formatBlockedStateError(SYSTEM_UID, rule, metered, backgroundRestricted), - npmi.isUidNetworkingBlocked(SYSTEM_UID, rule, metered, backgroundRestricted)); + mService.checkUidNetworkingBlocked(SYSTEM_UID, rule, metered, + backgroundRestricted)); } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index a118e0df1338..bbb25cd20149 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -28,8 +28,8 @@ import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_MAX; import static android.app.NotificationManager.IMPORTANCE_NONE; import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; +import static android.util.StatsLog.ANNOTATION_ID_IS_UID; -import static com.android.internal.util.FrameworkStatsLog.ANNOTATION_ID_IS_UID; import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES; import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.CHANNEL_ID_FIELD_NUMBER; import static com.android.os.AtomsProto.PackageNotificationChannelPreferences.CHANNEL_NAME_FIELD_NUMBER; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index 3c7206fee9d1..69e4190a02ae 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -33,8 +33,8 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCRE import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; +import static android.util.StatsLog.ANNOTATION_ID_IS_UID; -import static com.android.internal.util.FrameworkStatsLog.ANNOTATION_ID_IS_UID; import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE; import static com.android.os.AtomsProto.DNDModeProto.CHANNELS_BYPASSING_FIELD_NUMBER; import static com.android.os.AtomsProto.DNDModeProto.ENABLED_FIELD_NUMBER; diff --git a/startop/view_compiler/Android.bp b/startop/view_compiler/Android.bp index 7cc233b2439e..cac7b82adc2c 100644 --- a/startop/view_compiler/Android.bp +++ b/startop/view_compiler/Android.bp @@ -84,7 +84,6 @@ cc_test_host { static_libs: [ "libviewcompiler", ], - test_suites: ["general-tests"], } cc_binary_host { diff --git a/startop/view_compiler/TEST_MAPPING b/startop/view_compiler/TEST_MAPPING index 5f7d3f99ae81..791e47105ff9 100644 --- a/startop/view_compiler/TEST_MAPPING +++ b/startop/view_compiler/TEST_MAPPING @@ -10,10 +10,6 @@ "include-filter": "android.view.cts.PrecompiledLayoutTest" } ] - }, - { - "name": "view-compiler-tests", - "host": true } ] } diff --git a/telecomm/java/android/telecom/ConnectionRequest.java b/telecomm/java/android/telecom/ConnectionRequest.java index b73ef9b794e4..be5fae488d5e 100644 --- a/telecomm/java/android/telecom/ConnectionRequest.java +++ b/telecomm/java/android/telecom/ConnectionRequest.java @@ -18,6 +18,7 @@ package android.telecom; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.TestApi; import android.net.Uri; @@ -67,7 +68,8 @@ public final class ConnectionRequest implements Parcelable { * Sets the participants for the resulting {@link ConnectionRequest} * @param participants The participants to which the {@link Connection} is to connect. */ - public @NonNull Builder setParticipants(@Nullable List<Uri> participants) { + public @NonNull Builder setParticipants( + @SuppressLint("NullableCollection") @Nullable List<Uri> participants) { this.mParticipants = participants; return this; } diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index a42e3642c0d0..b81c4f2c71c8 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -3937,6 +3937,20 @@ public class CarrierConfigManager { public static final String KEY_ENABLE_PRESENCE_GROUP_SUBSCRIBE_BOOL = KEY_PREFIX + "enable_presence_group_subscribe_bool"; + /** + * An integer key associated with the period of time in seconds the non-rcs capability + * information of each contact is cached on the device. + * <p> + * The rcs capability cache expiration sec is managed by + * {@code android.telephony.ims.ProvisioningManager} but non-rcs capability is managed by + * {@link CarrierConfigManager} since non-rcs capability will be provided via ACS or carrier + * config. + * <p> + * The default value is 2592000 secs (30 days), see RCC.07 Annex A.1.9. + */ + public static final String KEY_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC_INT = + KEY_PREFIX + "non_rcs_capabilities_cache_expiration_sec_int"; + private Ims() {} private static PersistableBundle getDefaults() { @@ -3947,6 +3961,7 @@ public class CarrierConfigManager { defaults.putBoolean(KEY_ENABLE_PRESENCE_CAPABILITY_EXCHANGE_BOOL, false); defaults.putBoolean(KEY_RCS_BULK_CAPABILITY_EXCHANGE_BOOL, false); defaults.putBoolean(KEY_ENABLE_PRESENCE_GROUP_SUBSCRIBE_BOOL, true); + defaults.putInt(KEY_NON_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC_INT, 30 * 24 * 60 * 60); return defaults; } } diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java index 8507d8512a5c..706e3cb93a0f 100644 --- a/telephony/java/android/telephony/NetworkRegistrationInfo.java +++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java @@ -344,6 +344,7 @@ public final class NetworkRegistrationInfo implements Parcelable { // TODO: Instead of doing this, we should create a formal way for cloning cell identity. // Cell identity is not an immutable object so we have to deep copy it. mCellIdentity = CellIdentity.CREATOR.createFromParcel(p); + p.recycle(); } if (nri.mVoiceSpecificInfo != null) { diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 904232b54b8f..4e481b3ad837 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -2589,14 +2589,45 @@ public class SubscriptionManager { * requested state until explicitly cleared, or the next reboot, * whichever happens first. * @throws SecurityException if the caller doesn't meet the requirements - * outlined above. + * outlined above. */ public void setSubscriptionOverrideUnmetered(int subId, boolean overrideUnmetered, @DurationMillisLong long timeoutMillis) { + setSubscriptionOverrideUnmetered(subId, overrideUnmetered, + TelephonyManager.getAllNetworkTypes(), timeoutMillis); + } + /** + * Temporarily override the billing relationship plan between a carrier and + * a specific subscriber to be considered unmetered. This will be reflected + * to apps via {@link NetworkCapabilities#NET_CAPABILITY_NOT_METERED}. + * <p> + * This method is only accessible to the following narrow set of apps: + * <ul> + * <li>The carrier app for this subscriberId, as determined by + * {@link TelephonyManager#hasCarrierPrivileges()}. + * <li>The carrier app explicitly delegated access through + * {@link CarrierConfigManager#KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING}. + * </ul> + * + * @param subId the subscriber this override applies to. + * @param overrideUnmetered set if the billing relationship should be + * considered unmetered. + * @param networkTypes the network types this override applies to. + * {@see TelephonyManager#getAllNetworkTypes()} + * @param timeoutMillis the timeout after which the requested override will + * be automatically cleared, or {@code 0} to leave in the + * requested state until explicitly cleared, or the next reboot, + * whichever happens first. + * @throws SecurityException if the caller doesn't meet the requirements + * outlined above. + */ + public void setSubscriptionOverrideUnmetered(int subId, boolean overrideUnmetered, + @NonNull @Annotation.NetworkType int[] networkTypes, + @DurationMillisLong long timeoutMillis) { final int overrideValue = overrideUnmetered ? SUBSCRIPTION_OVERRIDE_UNMETERED : 0; getNetworkPolicyManager().setSubscriptionOverride(subId, SUBSCRIPTION_OVERRIDE_UNMETERED, - overrideValue, timeoutMillis, mContext.getOpPackageName()); + overrideValue, networkTypes, timeoutMillis, mContext.getOpPackageName()); } /** @@ -2621,13 +2652,46 @@ public class SubscriptionManager { * requested state until explicitly cleared, or the next reboot, * whichever happens first. * @throws SecurityException if the caller doesn't meet the requirements - * outlined above. + * outlined above. + */ + public void setSubscriptionOverrideCongested(int subId, boolean overrideCongested, + @DurationMillisLong long timeoutMillis) { + setSubscriptionOverrideCongested(subId, overrideCongested, + TelephonyManager.getAllNetworkTypes(), timeoutMillis); + } + + /** + * Temporarily override the billing relationship plan between a carrier and + * a specific subscriber to be considered congested. This will cause the + * device to delay certain network requests when possible, such as developer + * jobs that are willing to run in a flexible time window. + * <p> + * This method is only accessible to the following narrow set of apps: + * <ul> + * <li>The carrier app for this subscriberId, as determined by + * {@link TelephonyManager#hasCarrierPrivileges()}. + * <li>The carrier app explicitly delegated access through + * {@link CarrierConfigManager#KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING}. + * </ul> + * + * @param subId the subscriber this override applies to. + * @param overrideCongested set if the subscription should be considered + * congested. + * @param networkTypes the network types this override applies to. + * {@see TelephonyManager#getAllNetworkTypes()} + * @param timeoutMillis the timeout after which the requested override will + * be automatically cleared, or {@code 0} to leave in the + * requested state until explicitly cleared, or the next reboot, + * whichever happens first. + * @throws SecurityException if the caller doesn't meet the requirements + * outlined above. */ public void setSubscriptionOverrideCongested(int subId, boolean overrideCongested, + @NonNull @Annotation.NetworkType int[] networkTypes, @DurationMillisLong long timeoutMillis) { final int overrideValue = overrideCongested ? SUBSCRIPTION_OVERRIDE_CONGESTED : 0; getNetworkPolicyManager().setSubscriptionOverride(subId, SUBSCRIPTION_OVERRIDE_CONGESTED, - overrideValue, timeoutMillis, mContext.getOpPackageName()); + overrideValue, networkTypes, timeoutMillis, mContext.getOpPackageName()); } /** diff --git a/telephony/java/android/telephony/ims/DelegateStateCallback.java b/telephony/java/android/telephony/ims/DelegateStateCallback.java index fb659490d546..6bf992e64480 100644 --- a/telephony/java/android/telephony/ims/DelegateStateCallback.java +++ b/telephony/java/android/telephony/ims/DelegateStateCallback.java @@ -18,6 +18,7 @@ package android.telephony.ims; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.telephony.ims.stub.SipDelegate; import android.telephony.ims.stub.SipTransportImplBase; @@ -52,7 +53,9 @@ public interface DelegateStateCallback { * implementing this feature elsewhere. If all features of this {@link SipDelegate} are * denied, this method should still be called. */ - void onCreated(@NonNull SipDelegate delegate, @Nullable Set<FeatureTagState> deniedTags); + void onCreated(@NonNull SipDelegate delegate, + @SuppressLint("NullableCollection") // TODO(b/154763999): Mark deniedTags @Nonnull + @Nullable Set<FeatureTagState> deniedTags); /** * This must be called by the ImsService after the framework calls diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java index f3c38bcba98a..08eec29d5ac2 100644 --- a/telephony/java/android/telephony/ims/ProvisioningManager.java +++ b/telephony/java/android/telephony/ims/ProvisioningManager.java @@ -25,6 +25,7 @@ import android.annotation.SdkConstant; import android.annotation.StringDef; import android.annotation.SystemApi; import android.annotation.WorkerThread; +import android.content.pm.PackageManager; import android.os.Binder; import android.os.RemoteException; import android.os.ServiceSpecificException; @@ -1325,7 +1326,7 @@ public class ProvisioningManager { * the RCS VoLTE single registration feature. Only default messaging application may receive * the intent. * - * <p>Contains {@link #EXTRA_SUBSCRIPTION_INDEX} to specify the subscription index for which + * <p>Contains {@link #EXTRA_SUBSCRIPTION_ID} to specify the subscription index for which * the intent is valid. and {@link #EXTRA_STATUS} to specify RCS VoLTE single registration * status. */ @@ -1371,7 +1372,7 @@ public class ProvisioningManager { * provisioning is done using autoconfiguration, then these parameters shall be * sent in the HTTP get request to fetch the RCS provisioning. RCS client * configuration must be provided by the application before registering for the - * provisioning status events {@link #registerRcsProvisioningChangedCallback()} + * provisioning status events {@link #registerRcsProvisioningChangedCallback} * @param rcc RCS client configuration {@link RcsClientConfiguration} */ @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE) @@ -1387,13 +1388,15 @@ public class ProvisioningManager { } /** - * Returns a flag to indicate if the device software and the carrier - * have the capability to support RCS Volte single IMS registration. - * @return true if this single registration is capable, false otherwise + * Returns a flag to indicate whether or not the device supports IMS single registration for + * MMTEL and RCS features as well as if the carrier has provisioned the feature. + * @return true if IMS single registration is capable at this time, or false otherwise * @throws ImsException If the remote ImsService is not available for * any reason or the subscription associated with this instance is no * longer active. See {@link ImsException#getCode()} for more * information. + * @see PackageManager#FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION for whether or not this + * device supports IMS single registration. */ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isRcsVolteSingleRegistrationCapable() throws ImsException { @@ -1430,7 +1433,7 @@ public class ProvisioningManager { * available. This can happen if the service crashed, for example. * It shall also throw this exception when the RCS client parameters for the * application are not valid. In that case application must set the client - * params (See {@link #setRcsClientConfiguration()}) and re register the + * params (See {@link #setRcsClientConfiguration}) and re register the * callback. * See {@link ImsException#getCode()} for a more detailed reason. */ @@ -1458,9 +1461,9 @@ public class ProvisioningManager { * will result in a no-op. * @param callback The existing {@link RcsProvisioningCallback} to be * removed. - * @see #registerRcsProvisioningChangedCallback(RcsClientConfiguration, - * Executor, RcsProvisioningCallback) @throws IllegalArgumentException - * if the subscription associated with this callback is invalid. + * @see #registerRcsProvisioningChangedCallback + * @throws IllegalArgumentException if the subscription associated with this callback is + * invalid. */ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterRcsProvisioningChangedCallback( diff --git a/telephony/java/android/telephony/ims/SipDelegateManager.java b/telephony/java/android/telephony/ims/SipDelegateManager.java index 2e9eb94605a5..04421c9a2449 100644 --- a/telephony/java/android/telephony/ims/SipDelegateManager.java +++ b/telephony/java/android/telephony/ims/SipDelegateManager.java @@ -24,6 +24,7 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.content.Context; +import android.content.pm.PackageManager; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.telephony.BinderCacheManager; @@ -47,6 +48,9 @@ import java.util.concurrent.Executor; * This allows multiple IMS applications to forward SIP messages to/from their application for the * purposes of providing a single IMS registration to the carrier's IMS network from potentially * many IMS stacks implementing a subset of the supported MMTEL/RCS features. + * <p> + * This API is only supported if the device supports the + * {@link PackageManager#FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION} feature. * @hide */ @SystemApi @@ -269,6 +273,7 @@ public class SipDelegateManager { * {@link ImsException#getCode()} for more information. * * @see CarrierConfigManager.Ims#KEY_IMS_SINGLE_REGISTRATION_REQUIRED_BOOL + * @see PackageManager#FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION */ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isSupported() throws ImsException { diff --git a/telephony/java/android/telephony/ims/aidl/IPublishResponseCallback.aidl b/telephony/java/android/telephony/ims/aidl/IPublishResponseCallback.aidl index 481e7f8b37b9..b99d8a7d6d38 100644 --- a/telephony/java/android/telephony/ims/aidl/IPublishResponseCallback.aidl +++ b/telephony/java/android/telephony/ims/aidl/IPublishResponseCallback.aidl @@ -26,4 +26,5 @@ import java.util.List; oneway interface IPublishResponseCallback { void onCommandError(int code); void onNetworkResponse(int code, String reason); + void onNetworkRespHeader(int code, String reasonPhrase, int reasonHeaderCause, String reasonHeaderText); } diff --git a/telephony/java/android/telephony/ims/aidl/ISubscribeResponseCallback.aidl b/telephony/java/android/telephony/ims/aidl/ISubscribeResponseCallback.aidl index a14199365b07..8cc8020df29a 100644 --- a/telephony/java/android/telephony/ims/aidl/ISubscribeResponseCallback.aidl +++ b/telephony/java/android/telephony/ims/aidl/ISubscribeResponseCallback.aidl @@ -30,6 +30,7 @@ import java.util.Map; oneway interface ISubscribeResponseCallback { void onCommandError(int code); void onNetworkResponse(int code, in String reason); + void onNetworkRespHeader(int code, String reasonPhrase, int reasonHeaderCause, String reasonHeaderText); void onNotifyCapabilitiesUpdate(in List<String> pidfXmls); void onResourceTerminated(in List<RcsContactTerminatedReason> uriTerminatedReason); void onTerminated(in String reason, long retryAfterMilliseconds); diff --git a/telephony/java/android/telephony/ims/aidl/RcsPublishResponseAidlWrapper.java b/telephony/java/android/telephony/ims/aidl/RcsPublishResponseAidlWrapper.java index 22985d0cf85c..65415ea441b5 100644 --- a/telephony/java/android/telephony/ims/aidl/RcsPublishResponseAidlWrapper.java +++ b/telephony/java/android/telephony/ims/aidl/RcsPublishResponseAidlWrapper.java @@ -34,10 +34,11 @@ public class RcsPublishResponseAidlWrapper implements PublishResponseCallback { } @Override - public void onCommandError(int code) { + public void onCommandError(int code) throws ImsException { try { mResponseBinder.onCommandError(code); } catch (RemoteException e) { + throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } } @@ -46,6 +47,18 @@ public class RcsPublishResponseAidlWrapper implements PublishResponseCallback { try { mResponseBinder.onNetworkResponse(code, reason); } catch (RemoteException e) { + throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); + } + } + + @Override + public void onNetworkResponse(int code, String reasonPhrase, int reasonHeaderCause, + String reasonHeaderText) throws ImsException { + try { + mResponseBinder.onNetworkRespHeader(code, reasonPhrase, reasonHeaderCause, + reasonHeaderText); + } catch (RemoteException e) { + throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } } } diff --git a/telephony/java/android/telephony/ims/aidl/RcsSubscribeResponseAidlWrapper.java b/telephony/java/android/telephony/ims/aidl/RcsSubscribeResponseAidlWrapper.java index 1fb339c0cf89..11118c0617c2 100644 --- a/telephony/java/android/telephony/ims/aidl/RcsSubscribeResponseAidlWrapper.java +++ b/telephony/java/android/telephony/ims/aidl/RcsSubscribeResponseAidlWrapper.java @@ -40,10 +40,11 @@ public class RcsSubscribeResponseAidlWrapper implements SubscribeResponseCallbac } @Override - public void onCommandError(int code) { + public void onCommandError(int code) throws ImsException { try { mResponseBinder.onCommandError(code); } catch (RemoteException e) { + throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } } @@ -52,6 +53,18 @@ public class RcsSubscribeResponseAidlWrapper implements SubscribeResponseCallbac try { mResponseBinder.onNetworkResponse(code, reason); } catch (RemoteException e) { + throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); + } + } + + @Override + public void onNetworkResponse(int code, String reasonPhrase, int reasonHeaderCause, + String reasonHeaderText) throws ImsException { + try { + mResponseBinder.onNetworkRespHeader(code, reasonPhrase, reasonHeaderCause, + reasonHeaderText); + } catch (RemoteException e) { + throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } } @@ -60,6 +73,7 @@ public class RcsSubscribeResponseAidlWrapper implements SubscribeResponseCallbac try { mResponseBinder.onNotifyCapabilitiesUpdate(pidfXmls); } catch (RemoteException e) { + throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } } @@ -69,6 +83,7 @@ public class RcsSubscribeResponseAidlWrapper implements SubscribeResponseCallbac try { mResponseBinder.onResourceTerminated(getTerminatedReasonList(uriTerminatedReason)); } catch (RemoteException e) { + throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } } @@ -90,6 +105,7 @@ public class RcsSubscribeResponseAidlWrapper implements SubscribeResponseCallbac try { mResponseBinder.onTerminated(reason, retryAfterMilliseconds); } catch (RemoteException e) { + throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE); } } } diff --git a/telephony/java/android/telephony/ims/stub/ImsEcbmImplBase.java b/telephony/java/android/telephony/ims/stub/ImsEcbmImplBase.java index 2e35d27614d1..5f8e93d02a00 100644 --- a/telephony/java/android/telephony/ims/stub/ImsEcbmImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsEcbmImplBase.java @@ -44,8 +44,12 @@ public class ImsEcbmImplBase { @Override public void setListener(IImsEcbmListener listener) { synchronized (mLock) { - if (mImsEcbm != null && listener != null && Objects.equals( - mImsEcbm.asBinder(), listener.asBinder())) { + if (mListener != null && !mListener.asBinder().isBinderAlive()) { + Log.w(TAG, "setListener: discarding dead Binder"); + mListener = null; + } + if (mListener != null && listener != null && Objects.equals( + mListener.asBinder(), listener.asBinder())) { return; } if (listener == null) { diff --git a/telephony/java/android/telephony/ims/stub/ImsMultiEndpointImplBase.java b/telephony/java/android/telephony/ims/stub/ImsMultiEndpointImplBase.java index 555a47eb8200..8e961acc7b36 100644 --- a/telephony/java/android/telephony/ims/stub/ImsMultiEndpointImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsMultiEndpointImplBase.java @@ -48,6 +48,10 @@ public class ImsMultiEndpointImplBase { @Override public void setListener(IImsExternalCallStateListener listener) throws RemoteException { synchronized (mLock) { + if (mListener != null && !mListener.asBinder().isBinderAlive()) { + Log.w(TAG, "setListener: discarding dead Binder"); + mListener = null; + } if (mListener != null && listener != null && Objects.equals( mListener.asBinder(), listener.asBinder())) { return; diff --git a/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java b/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java index eef4fcaceeaf..83b89aa8e814 100644 --- a/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java +++ b/telephony/java/android/telephony/ims/stub/ImsUtImplBase.java @@ -23,6 +23,7 @@ import android.annotation.SystemApi; import android.os.Bundle; import android.os.RemoteException; import android.telephony.ims.ImsUtListener; +import android.util.Log; import com.android.ims.internal.IImsUt; import com.android.ims.internal.IImsUtListener; @@ -41,6 +42,7 @@ import java.util.Objects; // will break other implementations of ImsUt maintained by other ImsServices. @SystemApi public class ImsUtImplBase { + private static final String TAG = "ImsUtImplBase"; /** * Bar all incoming calls. (See 3GPP TS 24.611) * @hide @@ -207,6 +209,11 @@ public class ImsUtImplBase { @Override public void setListener(IImsUtListener listener) throws RemoteException { synchronized (mLock) { + if (mUtListener != null + && !mUtListener.getListenerInterface().asBinder().isBinderAlive()) { + Log.w(TAG, "setListener: discarding dead Binder"); + mUtListener = null; + } if (mUtListener != null && listener != null && Objects.equals( mUtListener.getListenerInterface().asBinder(), listener.asBinder())) { return; diff --git a/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java b/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java index 7eba709a11da..ec98be6e5062 100644 --- a/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java +++ b/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java @@ -140,6 +140,9 @@ public class RcsCapabilityExchangeImplBase { * Provide the framework with a subsequent network response update to * {@link #publishCapabilities(String, PublishResponseCallback)}. * + * If this network response also contains a “Reason” header, then the + * {@link onNetworkResponse(int, String, int, String)} method should be used instead. + * * @param sipCode The SIP response code sent from the network for the operation * token specified. * @param reason The optional reason response from the network. If there is a reason header @@ -154,6 +157,31 @@ public class RcsCapabilityExchangeImplBase { */ void onNetworkResponse(@IntRange(from = 100, to = 699) int sipCode, @NonNull String reason) throws ImsException; + + /** + * Provide the framework with a subsequent network response update to + * {@link #publishCapabilities(RcsContactUceCapability, int)} that also + * includes a reason provided in the “reason” header. See RFC3326 for more + * information. + * + * @param sipCode The SIP response code sent from the network. + * @param reasonPhrase The optional reason response from the network. If the + * network provided no reason with the sip code, the string should be empty. + * @param reasonHeaderCause The “cause” parameter of the “reason” header + * included in the SIP message. + * @param reasonHeaderText The “text” parameter of the “reason” header + * included in the SIP message. + * @throws ImsException If this {@link RcsCapabilityExchangeImplBase} instance is + * not currently connected to the framework. This can happen if the + * {@link RcsFeature} is not + * {@link ImsFeature#STATE_READY} and the {@link RcsFeature} has not received + * the {@link ImsFeature#onFeatureReady()} callback. This may also happen in + * rare cases when the Telephony stack has crashed. + */ + void onNetworkResponse(@IntRange(from = 100, to = 699) int sipCode, + @NonNull String reasonPhrase, + @IntRange(from = 100, to = 699) int reasonHeaderCause, + @NonNull String reasonHeaderText) throws ImsException; } /** @@ -222,6 +250,9 @@ public class RcsCapabilityExchangeImplBase { * {@link #onResourceTerminated}, and {@link #onTerminated} as required for the * subsequent NOTIFY responses to the subscription. * + * If this network response also contains a “Reason” header, then the + * {@link onNetworkResponse(int, String, int, String)} method should be used instead. + * * @param sipCode The SIP response code sent from the network for the operation * token specified. * @param reason The optional reason response from the network. If the network @@ -236,6 +267,31 @@ public class RcsCapabilityExchangeImplBase { @NonNull String reason) throws ImsException; /** + * Notify the framework of the response to the SUBSCRIBE request from + * {@link #subscribeForCapabilities(RcsContactUceCapability, int)} that also + * includes a reason provided in the “reason” header. See RFC3326 for more + * information. + * + * @param sipCode The SIP response code sent from the network, + * @param reasonPhrase The optional reason response from the network. If the + * network provided no reason with the sip code, the string should be empty. + * @param reasonHeaderCause The “cause” parameter of the “reason” header + * included in the SIP message. + * @param reasonHeaderText The “text” parameter of the “reason” header + * included in the SIP message. + * @throws ImsException If this {@link RcsCapabilityExchangeImplBase} instance is + * not currently connected to the framework. This can happen if the + * {@link RcsFeature} is not + * {@link ImsFeature#STATE_READY} and the {@link RcsFeature} has not received + * the {@link ImsFeature#onFeatureReady()} callback. This may also happen in + * rare cases when the Telephony stack has crashed. + */ + void onNetworkResponse(@IntRange(from = 100, to = 699) int sipCode, + @NonNull String reasonPhrase, + @IntRange(from = 100, to = 699) int reasonHeaderCause, + @NonNull String reasonHeaderText) throws ImsException; + + /** * Notify the framework of the latest XML PIDF documents included in the network response * for the requested contacts' capabilities requested by the Framework using * {@link RcsUceAdapter#requestCapabilities(Executor, List<Uri>, CapabilitiesCallback)}. diff --git a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java index 02597d548361..e67354982b05 100644 --- a/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java +++ b/tests/StagedInstallTest/app/src/com/android/tests/stagedinstallinternal/StagedInstallInternalTest.java @@ -96,6 +96,19 @@ public class StagedInstallInternalTest { assertSessionReady(sessionId); } + @Test + public void testStagedInstallationShouldCleanUpOnValidationFailure() throws Exception { + InstallUtils.commitExpectingFailure(AssertionError.class, "INSTALL_FAILED_INVALID_APK", + Install.single(TestApp.AIncompleteSplit).setStaged()); + } + + @Test + public void testStagedInstallationShouldCleanUpOnValidationFailureMultiPackage() + throws Exception { + InstallUtils.commitExpectingFailure(AssertionError.class, "INSTALL_FAILED_INVALID_APK", + Install.multi(TestApp.AIncompleteSplit, TestApp.B1, TestApp.Apex1).setStaged()); + } + private static void assertSessionReady(int sessionId) { assertSessionState(sessionId, (session) -> assertThat(session.isStagedSessionReady()).isTrue()); diff --git a/tests/UpdatableSystemFontTest/OWNERS b/tests/UpdatableSystemFontTest/OWNERS new file mode 100644 index 000000000000..34ac813f02e0 --- /dev/null +++ b/tests/UpdatableSystemFontTest/OWNERS @@ -0,0 +1,3 @@ +# Bug component: 24939 + +include /graphics/java/android/graphics/fonts/OWNERS diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index b0cc7f1361f6..9f4853551575 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -1262,22 +1262,28 @@ public class ConnectivityServiceTest { } } - private void updateUidNetworkingBlocked() { - doAnswer(i -> NetworkPolicyManagerInternal.isUidNetworkingBlocked( - i.getArgument(0) /* uid */, mUidRules, i.getArgument(1) /* metered */, - mRestrictBackground) + private void mockUidNetworkingBlocked() { + doAnswer(i -> mContext.getSystemService(NetworkPolicyManager.class) + .checkUidNetworkingBlocked(i.getArgument(0) /* uid */, mUidRules, + i.getArgument(1) /* metered */, mRestrictBackground) ).when(mNetworkPolicyManager).isUidNetworkingBlocked(anyInt(), anyBoolean()); + + doAnswer(inv -> mContext.getSystemService(NetworkPolicyManager.class) + .checkUidNetworkingBlocked(inv.getArgument(0) /* uid */, + inv.getArgument(1) /* uidRules */, + inv.getArgument(2) /* isNetworkMetered */, + inv.getArgument(3) /* isBackgroundRestricted */) + ).when(mNetworkPolicyManager).checkUidNetworkingBlocked( + anyInt(), anyInt(), anyBoolean(), anyBoolean()); } private void setUidRulesChanged(int uidRules) throws RemoteException { mUidRules = uidRules; - updateUidNetworkingBlocked(); mPolicyListener.onUidRulesChanged(Process.myUid(), mUidRules); } private void setRestrictBackgroundChanged(boolean restrictBackground) throws RemoteException { mRestrictBackground = restrictBackground; - updateUidNetworkingBlocked(); mPolicyListener.onRestrictBackgroundChanged(mRestrictBackground); } @@ -6809,6 +6815,7 @@ public class ConnectivityServiceTest { .addTransportType(TRANSPORT_CELLULAR) .build(); mCm.registerNetworkCallback(cellRequest, cellNetworkCallback); + mockUidNetworkingBlocked(); mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); @@ -6891,6 +6898,7 @@ public class ConnectivityServiceTest { public void testNetworkBlockedStatusBeforeAndAfterConnect() throws Exception { final TestNetworkCallback defaultCallback = new TestNetworkCallback(); mCm.registerDefaultNetworkCallback(defaultCallback); + mockUidNetworkingBlocked(); // No Networkcallbacks invoked before any network is active. setUidRulesChanged(RULE_REJECT_ALL); @@ -7160,6 +7168,13 @@ public class ConnectivityServiceTest { when(mKeyStore.get(Credentials.VPN + profileName)).thenReturn(encodedProfile); } + private void establishLegacyLockdownVpn() throws Exception { + // The legacy lockdown VPN only supports userId 0. + final Set<UidRange> ranges = Collections.singleton(UidRange.createForUser(PRIMARY_USER)); + mMockVpn.registerAgent(ranges); + mMockVpn.connect(true); + } + @Test public void testLegacyLockdownVpn() throws Exception { mServiceContext.setPermission( @@ -7254,22 +7269,30 @@ public class ConnectivityServiceTest { mMockVpn.expectStartLegacyVpnRunner(); b1 = expectConnectivityAction(TYPE_VPN, DetailedState.CONNECTED); ExpectedBroadcast b2 = expectConnectivityAction(TYPE_MOBILE, DetailedState.CONNECTED); - mMockVpn.establishForMyUid(); + establishLegacyLockdownVpn(); callback.expectAvailableThenValidatedCallbacks(mMockVpn); defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn); + NetworkCapabilities vpnNc = mCm.getNetworkCapabilities(mMockVpn.getNetwork()); b1.expectBroadcast(); b2.expectBroadcast(); assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); assertNetworkInfo(TYPE_WIFI, DetailedState.DISCONNECTED); assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED); + assertTrue(vpnNc.hasTransport(TRANSPORT_VPN)); + assertTrue(vpnNc.hasTransport(TRANSPORT_CELLULAR)); + assertFalse(vpnNc.hasTransport(TRANSPORT_WIFI)); + assertFalse(vpnNc.hasCapability(NET_CAPABILITY_NOT_METERED)); // Switch default network from cell to wifi. Expect VPN to disconnect and reconnect. final LinkProperties wifiLp = new LinkProperties(); wifiLp.setInterfaceName("wlan0"); wifiLp.addLinkAddress(new LinkAddress("192.0.2.163/25")); wifiLp.addRoute(new RouteInfo(new IpPrefix("0.0.0.0/0"), null, "wlan0")); - mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp); + final NetworkCapabilities wifiNc = new NetworkCapabilities(); + wifiNc.addTransportType(TRANSPORT_WIFI); + wifiNc.addCapability(NET_CAPABILITY_NOT_METERED); + mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp, wifiNc); b1 = expectConnectivityAction(TYPE_MOBILE, DetailedState.DISCONNECTED); // Wifi is CONNECTING because the VPN isn't up yet. @@ -7302,16 +7325,20 @@ public class ConnectivityServiceTest { // The VPN comes up again on wifi. b1 = expectConnectivityAction(TYPE_VPN, DetailedState.CONNECTED); b2 = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED); - mMockVpn.establishForMyUid(); + establishLegacyLockdownVpn(); callback.expectAvailableThenValidatedCallbacks(mMockVpn); defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn); b1.expectBroadcast(); b2.expectBroadcast(); - assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED); assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED); + vpnNc = mCm.getNetworkCapabilities(mMockVpn.getNetwork()); + assertTrue(vpnNc.hasTransport(TRANSPORT_VPN)); + assertTrue(vpnNc.hasTransport(TRANSPORT_WIFI)); + assertFalse(vpnNc.hasTransport(TRANSPORT_CELLULAR)); + assertTrue(vpnNc.hasCapability(NET_CAPABILITY_NOT_METERED)); // Disconnect cell. Nothing much happens since it's not the default network. // Whenever LockdownVpnTracker is connected, it will send a connected broadcast any time any @@ -8355,13 +8382,14 @@ public class ConnectivityServiceTest { private void setupConnectionOwnerUid(int vpnOwnerUid, @VpnManager.VpnType int vpnType) throws Exception { final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(PRIMARY_USER)); + mMockVpn.setVpnType(vpnType); mMockVpn.establish(new LinkProperties(), vpnOwnerUid, vpnRange); assertVpnUidRangesUpdated(true, vpnRange, vpnOwnerUid); - mMockVpn.setVpnType(vpnType); final UnderlyingNetworkInfo underlyingNetworkInfo = new UnderlyingNetworkInfo(vpnOwnerUid, VPN_IFNAME, new ArrayList<String>()); mMockVpn.setUnderlyingNetworkInfo(underlyingNetworkInfo); + when(mDeps.getConnectionOwnerUid(anyInt(), any(), any())).thenReturn(42); } private void setupConnectionOwnerUidAsVpnApp(int vpnOwnerUid, @VpnManager.VpnType int vpnType) @@ -8410,8 +8438,7 @@ public class ConnectivityServiceTest { final int myUid = Process.myUid(); setupConnectionOwnerUidAsVpnApp(myUid, VpnManager.TYPE_VPN_SERVICE); - // TODO: Test the returned UID - mService.getConnectionOwnerUid(getTestConnectionInfo()); + assertEquals(42, mService.getConnectionOwnerUid(getTestConnectionInfo())); } @Test @@ -8421,8 +8448,7 @@ public class ConnectivityServiceTest { mServiceContext.setPermission( android.Manifest.permission.NETWORK_STACK, PERMISSION_GRANTED); - // TODO: Test the returned UID - mService.getConnectionOwnerUid(getTestConnectionInfo()); + assertEquals(42, mService.getConnectionOwnerUid(getTestConnectionInfo())); } @Test @@ -8433,8 +8459,7 @@ public class ConnectivityServiceTest { mServiceContext.setPermission( NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, PERMISSION_GRANTED); - // TODO: Test the returned UID - mService.getConnectionOwnerUid(getTestConnectionInfo()); + assertEquals(42, mService.getConnectionOwnerUid(getTestConnectionInfo())); } private static PackageInfo buildPackageInfo(boolean hasSystemPermission, int uid) { diff --git a/tests/net/java/com/android/server/LegacyTypeTrackerTest.kt b/tests/net/java/com/android/server/LegacyTypeTrackerTest.kt index e590fb76f2ec..a10a3c81bc86 100644 --- a/tests/net/java/com/android/server/LegacyTypeTrackerTest.kt +++ b/tests/net/java/com/android/server/LegacyTypeTrackerTest.kt @@ -55,7 +55,7 @@ class LegacyTypeTrackerTest { private val supportedTypes = arrayOf(TYPE_MOBILE, TYPE_WIFI, TYPE_ETHERNET, TYPE_MOBILE_SUPL) private val mMockService = mock(ConnectivityService::class.java).apply { - doReturn(false).`when`(this).isFallbackNetwork(any()) + doReturn(false).`when`(this).isDefaultNetwork(any()) } private val mTracker = LegacyTypeTracker(mMockService).apply { supportedTypes.forEach { @@ -126,11 +126,11 @@ class LegacyTypeTrackerTest { fun testBroadcastOnDisconnect() { val mobileNai1 = mock(NetworkAgentInfo::class.java) val mobileNai2 = mock(NetworkAgentInfo::class.java) - doReturn(false).`when`(mMockService).isFallbackNetwork(mobileNai1) + doReturn(false).`when`(mMockService).isDefaultNetwork(mobileNai1) mTracker.add(TYPE_MOBILE, mobileNai1) verify(mMockService).sendLegacyNetworkBroadcast(mobileNai1, CONNECTED, TYPE_MOBILE) reset(mMockService) - doReturn(false).`when`(mMockService).isFallbackNetwork(mobileNai2) + doReturn(false).`when`(mMockService).isDefaultNetwork(mobileNai2) mTracker.add(TYPE_MOBILE, mobileNai2) verify(mMockService, never()).sendLegacyNetworkBroadcast(any(), any(), anyInt()) mTracker.remove(TYPE_MOBILE, mobileNai1, false /* wasDefault */) diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java index f4782829cff7..3a93c5b10540 100644 --- a/tests/net/java/com/android/server/connectivity/VpnTest.java +++ b/tests/net/java/com/android/server/connectivity/VpnTest.java @@ -49,6 +49,7 @@ import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.AppOpsManager; import android.app.NotificationManager; +import android.app.PendingIntent; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -119,6 +120,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; @@ -213,6 +215,8 @@ public class VpnTest { when(mContext.getPackageName()).thenReturn(TEST_VPN_PKG); when(mContext.getOpPackageName()).thenReturn(TEST_VPN_PKG); + when(mContext.getSystemServiceName(UserManager.class)) + .thenReturn(Context.USER_SERVICE); when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager); when(mContext.getSystemService(eq(Context.APP_OPS_SERVICE))).thenReturn(mAppOps); when(mContext.getSystemServiceName(NotificationManager.class)) @@ -954,7 +958,14 @@ public class VpnTest { } private Vpn startLegacyVpn(final Vpn vpn, final VpnProfile vpnProfile) throws Exception { - setMockedUsers(primaryUser); + // TODO(b/175883995): once these tests have been updated for the changes to the UserManager + // API, remove this ad-hoc setup code and use setMockedUsers(primaryUser) again. + // setMockedUsers(primaryUser); + final ArrayList<UserInfo> users = new ArrayList<>(); + users.add(primaryUser); + when(mUserManager.getAliveUsers()).thenReturn(users); + when(mUserManager.getUserInfo(primaryUser.id)).thenReturn(primaryUser); + when(mUserManager.canHaveRestrictedProfile()).thenReturn(false); // Dummy egress interface final LinkProperties lp = new LinkProperties(); @@ -997,14 +1008,12 @@ public class VpnTest { profile.ipsecIdentifier = "id"; profile.ipsecSecret = "secret"; profile.l2tpSecret = "l2tpsecret"; + when(mConnectivityManager.getAllNetworks()) .thenReturn(new Network[] { new Network(101) }); + when(mConnectivityManager.registerNetworkAgent(any(), any(), any(), any(), - anyInt(), any(), anyInt())).thenAnswer(invocation -> { - // The runner has registered an agent and is now ready. - legacyRunnerReady.open(); - return new Network(102); - }); + anyInt(), any(), anyInt())).thenReturn(new Network(102)); final Vpn vpn = startLegacyVpn(createVpn(primaryUser.id), profile); final TestDeps deps = (TestDeps) vpn.mDeps; try { @@ -1020,14 +1029,20 @@ public class VpnTest { "linkname", "vpn", "refuse-eap", "nodefaultroute", "usepeerdns", "idle", "1800", "mtu", "1270", "mru", "1270" }, deps.mtpdArgs.get(10, TimeUnit.SECONDS)); + // Now wait for the runner to be ready before testing for the route. - legacyRunnerReady.block(10_000); - // In this test the expected address is always v4 so /32 + ArgumentCaptor<LinkProperties> lpCaptor = ArgumentCaptor.forClass(LinkProperties.class); + verify(mConnectivityManager, timeout(10_000)).registerNetworkAgent(any(), any(), + lpCaptor.capture(), any(), anyInt(), any(), anyInt()); + + // In this test the expected address is always v4 so /32. + // Note that the interface needs to be specified because RouteInfo objects stored in + // LinkProperties objects always acquire the LinkProperties' interface. final RouteInfo expectedRoute = new RouteInfo(new IpPrefix(expectedAddr + "/32"), - RouteInfo.RTN_THROW); - assertTrue("Routes lack the expected throw route (" + expectedRoute + ") : " - + vpn.mConfig.routes, - vpn.mConfig.routes.contains(expectedRoute)); + null, EGRESS_IFACE, RouteInfo.RTN_THROW); + final List<RouteInfo> actualRoutes = lpCaptor.getValue().getRoutes(); + assertTrue("Expected throw route (" + expectedRoute + ") not found in " + actualRoutes, + actualRoutes.contains(expectedRoute)); } finally { // Now interrupt the thread, unblock the runner and clean up. vpn.mVpnRunner.exitVpnRunner(); @@ -1083,6 +1098,11 @@ public class VpnTest { } @Override + public PendingIntent getIntentForStatusPanel(Context context) { + return null; + } + + @Override public void sendArgumentsToDaemon( final String daemon, final LocalSocket socket, final String[] arguments, final Vpn.RetryScheduler interruptChecker) throws IOException { diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java index e7d334ebd490..e32e1e831f83 100644 --- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java +++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java @@ -184,7 +184,7 @@ public class VcnManagementServiceTest { doAnswer((invocation) -> { // Mock-within a doAnswer is safe, because it doesn't actually run nested. return mock(Vcn.class); - }).when(mMockDeps).newVcn(any(), any(), any()); + }).when(mMockDeps).newVcn(any(), any(), any(), any()); final PersistableBundle bundle = PersistableBundleUtils.fromMap( @@ -304,14 +304,17 @@ public class VcnManagementServiceTest { @Test public void testTelephonyNetworkTrackerCallbackStartsInstances() throws Exception { - triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1)); - verify(mMockDeps).newVcn(eq(mVcnContext), eq(TEST_UUID_1), eq(TEST_VCN_CONFIG)); + TelephonySubscriptionSnapshot snapshot = + triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_1)); + verify(mMockDeps) + .newVcn(eq(mVcnContext), eq(TEST_UUID_1), eq(TEST_VCN_CONFIG), eq(snapshot)); } @Test public void testTelephonyNetworkTrackerCallbackStopsInstances() throws Exception { final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback(); final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2); + mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); triggerSubscriptionTrackerCbAndGetSnapshot(Collections.emptySet()); @@ -319,6 +322,7 @@ public class VcnManagementServiceTest { mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS); mTestLooper.dispatchAll(); verify(vcn).teardownAsynchronously(); + verify(mMockPolicyListener).onPolicyChanged(); } @Test @@ -389,6 +393,7 @@ public class VcnManagementServiceTest { mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); fail("Expected security exception for non system user"); } catch (SecurityException expected) { + verify(mMockPolicyListener, never()).onPolicyChanged(); } } @@ -400,6 +405,7 @@ public class VcnManagementServiceTest { mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); fail("Expected security exception for missing carrier privileges"); } catch (SecurityException expected) { + verify(mMockPolicyListener, never()).onPolicyChanged(); } } @@ -409,6 +415,7 @@ public class VcnManagementServiceTest { mVcnMgmtSvc.setVcnConfig(TEST_UUID_1, TEST_VCN_CONFIG, "IncorrectPackage"); fail("Expected exception due to mismatched packages in config and method call"); } catch (IllegalArgumentException expected) { + verify(mMockPolicyListener, never()).onPolicyChanged(); } } @@ -473,7 +480,12 @@ public class VcnManagementServiceTest { verify(mConfigReadWriteHelper).writeToDisk(any(PersistableBundle.class)); // Verify Vcn is started - verify(mMockDeps).newVcn(eq(mVcnContext), eq(TEST_UUID_2), eq(TEST_VCN_CONFIG)); + verify(mMockDeps) + .newVcn( + eq(mVcnContext), + eq(TEST_UUID_2), + eq(TEST_VCN_CONFIG), + eq(TelephonySubscriptionSnapshot.EMPTY_SNAPSHOT)); // Verify Vcn is updated if it was previously started mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); @@ -520,7 +532,7 @@ public class VcnManagementServiceTest { Collections.singleton(subGroup), Collections.singletonMap(subId, subGroup)); } - private void verifyMergedNetworkCapabilities( + private void verifyMergedNetworkCapabilitiesIsVcnManaged( NetworkCapabilities mergedCapabilities, @Transport int transportType) { assertTrue(mergedCapabilities.hasTransport(transportType)); assertFalse( @@ -529,7 +541,7 @@ public class VcnManagementServiceTest { } @Test - public void testGetUnderlyingNetworkPolicyTransportCell() throws Exception { + public void testGetUnderlyingNetworkPolicyCellular() throws Exception { setUpVcnSubscription(TEST_SUBSCRIPTION_ID, TEST_UUID_2); NetworkCapabilities nc = @@ -542,12 +554,12 @@ public class VcnManagementServiceTest { mVcnMgmtSvc.getUnderlyingNetworkPolicy(nc, new LinkProperties()); assertFalse(policy.isTeardownRequested()); - verifyMergedNetworkCapabilities( + verifyMergedNetworkCapabilitiesIsVcnManaged( policy.getMergedNetworkCapabilities(), NetworkCapabilities.TRANSPORT_CELLULAR); } @Test - public void testGetUnderlyingNetworkPolicyTransportWifi() throws Exception { + public void testGetUnderlyingNetworkPolicyWifi() throws Exception { setUpVcnSubscription(TEST_SUBSCRIPTION_ID, TEST_UUID_2); WifiInfo wifiInfo = mock(WifiInfo.class); @@ -563,7 +575,7 @@ public class VcnManagementServiceTest { mVcnMgmtSvc.getUnderlyingNetworkPolicy(nc, new LinkProperties()); assertFalse(policy.isTeardownRequested()); - verifyMergedNetworkCapabilities( + verifyMergedNetworkCapabilitiesIsVcnManaged( policy.getMergedNetworkCapabilities(), NetworkCapabilities.TRANSPORT_WIFI); } @@ -591,4 +603,35 @@ public class VcnManagementServiceTest { mVcnMgmtSvc.getUnderlyingNetworkPolicy(new NetworkCapabilities(), new LinkProperties()); } + + @Test + public void testSubscriptionSnapshotUpdateNotifiesVcn() { + mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); + final Map<ParcelUuid, Vcn> vcnInstances = mVcnMgmtSvc.getAllVcns(); + final Vcn vcnInstance = vcnInstances.get(TEST_UUID_2); + + TelephonySubscriptionSnapshot snapshot = + triggerSubscriptionTrackerCbAndGetSnapshot(Collections.singleton(TEST_UUID_2)); + + verify(vcnInstance).updateSubscriptionSnapshot(eq(snapshot)); + } + + @Test + public void testAddNewVcnUpdatesPolicyListener() throws Exception { + mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); + + mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); + + verify(mMockPolicyListener).onPolicyChanged(); + } + + @Test + public void testRemoveVcnUpdatesPolicyListener() throws Exception { + mVcnMgmtSvc.setVcnConfig(TEST_UUID_2, TEST_VCN_CONFIG, TEST_PACKAGE_NAME); + mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); + + mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2); + + verify(mMockPolicyListener).onPolicyChanged(); + } } diff --git a/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java b/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java index 48e068d14182..1d459a347526 100644 --- a/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java +++ b/tests/vcn/java/com/android/server/vcn/UnderlyingNetworkTrackerTest.java @@ -42,8 +42,9 @@ import android.net.TelephonyNetworkSpecifier; import android.os.ParcelUuid; import android.os.test.TestLooper; import android.telephony.SubscriptionInfo; -import android.telephony.SubscriptionManager; +import android.util.ArraySet; +import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import com.android.server.vcn.UnderlyingNetworkTracker.NetworkBringupCallback; import com.android.server.vcn.UnderlyingNetworkTracker.RouteSelectionCallback; import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord; @@ -58,13 +59,19 @@ import org.mockito.MockitoAnnotations; import java.util.Arrays; import java.util.Collections; -import java.util.List; +import java.util.Set; import java.util.UUID; public class UnderlyingNetworkTrackerTest { private static final ParcelUuid SUB_GROUP = new ParcelUuid(new UUID(0, 0)); private static final int INITIAL_SUB_ID_1 = 1; private static final int INITIAL_SUB_ID_2 = 2; + private static final int UPDATED_SUB_ID = 3; + + private static final Set<Integer> INITIAL_SUB_IDS = + new ArraySet<>(Arrays.asList(INITIAL_SUB_ID_1, INITIAL_SUB_ID_2)); + private static final Set<Integer> UPDATED_SUB_IDS = + new ArraySet<>(Arrays.asList(UPDATED_SUB_ID)); private static final NetworkCapabilities INITIAL_NETWORK_CAPABILITIES = new NetworkCapabilities.Builder() @@ -90,7 +97,7 @@ public class UnderlyingNetworkTrackerTest { @Mock private Context mContext; @Mock private VcnNetworkProvider mVcnNetworkProvider; @Mock private ConnectivityManager mConnectivityManager; - @Mock private SubscriptionManager mSubscriptionManager; + @Mock private TelephonySubscriptionSnapshot mSubscriptionSnapshot; @Mock private UnderlyingNetworkTrackerCallback mNetworkTrackerCb; @Mock private Network mNetwork; @@ -113,23 +120,14 @@ public class UnderlyingNetworkTrackerTest { mConnectivityManager, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class); - setupSystemService( - mContext, - mSubscriptionManager, - Context.TELEPHONY_SUBSCRIPTION_SERVICE, - SubscriptionManager.class); - List<SubscriptionInfo> initialSubInfos = - Arrays.asList( - getSubscriptionInfoForSubId(INITIAL_SUB_ID_1), - getSubscriptionInfoForSubId(INITIAL_SUB_ID_2)); - when(mSubscriptionManager.getSubscriptionsInGroup(eq(SUB_GROUP))) - .thenReturn(initialSubInfos); + when(mSubscriptionSnapshot.getAllSubIdsInGroup(eq(SUB_GROUP))).thenReturn(INITIAL_SUB_IDS); mUnderlyingNetworkTracker = new UnderlyingNetworkTracker( mVcnContext, SUB_GROUP, + mSubscriptionSnapshot, Collections.singleton(NetworkCapabilities.NET_CAPABILITY_INTERNET), mNetworkTrackerCb); } @@ -154,23 +152,45 @@ public class UnderlyingNetworkTrackerTest { eq(getWifiRequest()), any(), any(NetworkBringupCallback.class)); - verify(mConnectivityManager) - .requestBackgroundNetwork( - eq(getCellRequestForSubId(INITIAL_SUB_ID_1)), - any(), - any(NetworkBringupCallback.class)); - verify(mConnectivityManager) - .requestBackgroundNetwork( - eq(getCellRequestForSubId(INITIAL_SUB_ID_2)), - any(), - any(NetworkBringupCallback.class)); + verifyBackgroundCellRequests(mSubscriptionSnapshot, SUB_GROUP, INITIAL_SUB_IDS); + verify(mConnectivityManager) .requestBackgroundNetwork( eq(getRouteSelectionRequest()), any(), any(RouteSelectionCallback.class)); + } + + private void verifyBackgroundCellRequests( + TelephonySubscriptionSnapshot snapshot, + ParcelUuid subGroup, + Set<Integer> expectedSubIds) { + verify(snapshot).getAllSubIdsInGroup(eq(subGroup)); + + for (final int subId : expectedSubIds) { + verify(mConnectivityManager) + .requestBackgroundNetwork( + eq(getCellRequestForSubId(subId)), + any(), + any(NetworkBringupCallback.class)); + } + } - verify(mSubscriptionManager).getSubscriptionsInGroup(eq(SUB_GROUP)); + @Test + public void testUpdateSubscriptionSnapshot() { + // Verify initial cell background requests filed + verifyBackgroundCellRequests(mSubscriptionSnapshot, SUB_GROUP, INITIAL_SUB_IDS); + + TelephonySubscriptionSnapshot subscriptionUpdate = + mock(TelephonySubscriptionSnapshot.class); + when(subscriptionUpdate.getAllSubIdsInGroup(eq(SUB_GROUP))).thenReturn(UPDATED_SUB_IDS); + + mUnderlyingNetworkTracker.updateSubscriptionSnapshot(subscriptionUpdate); + + // verify that initially-filed bringup requests are unregistered + verify(mConnectivityManager, times(INITIAL_SUB_IDS.size())) + .unregisterNetworkCallback(any(NetworkBringupCallback.class)); + verifyBackgroundCellRequests(subscriptionUpdate, SUB_GROUP, UPDATED_SUB_IDS); } private NetworkRequest getWifiRequest() { diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java new file mode 100644 index 000000000000..e20070ee4f07 --- /dev/null +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java @@ -0,0 +1,127 @@ +/* + * 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.server.vcn; + +import static android.net.IpSecManager.DIRECTION_IN; +import static android.net.IpSecManager.DIRECTION_OUT; + +import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Tests for VcnGatewayConnection.ConnectedState */ +@RunWith(AndroidJUnit4.class) +@SmallTest +public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnectionTestBase { + private VcnIkeSession mIkeSession; + + @Before + public void setUp() throws Exception { + super.setUp(); + + mGatewayConnection.setUnderlyingNetwork(TEST_UNDERLYING_NETWORK_RECORD_1); + + mIkeSession = mGatewayConnection.buildIkeSession(); + mGatewayConnection.setIkeSession(mIkeSession); + + mGatewayConnection.transitionTo(mGatewayConnection.mConnectedState); + mTestLooper.dispatchAll(); + } + + @Test + public void testEnterStateCreatesNewIkeSession() throws Exception { + verify(mDeps).newIkeSession(any(), any(), any(), any(), any()); + } + + @Test + public void testNullNetworkDoesNotTriggerDisconnect() throws Exception { + mGatewayConnection + .getUnderlyingNetworkTrackerCallback() + .onSelectedUnderlyingNetworkChanged(null); + mTestLooper.dispatchAll(); + + assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState()); + verify(mIkeSession, never()).close(); + } + + @Test + public void testNewNetworkTriggersMigration() throws Exception { + mGatewayConnection + .getUnderlyingNetworkTrackerCallback() + .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_2); + mTestLooper.dispatchAll(); + + assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState()); + verify(mIkeSession, never()).close(); + verify(mIkeSession).setNetwork(TEST_UNDERLYING_NETWORK_RECORD_2.network); + } + + @Test + public void testSameNetworkDoesNotTriggerMigration() throws Exception { + mGatewayConnection + .getUnderlyingNetworkTrackerCallback() + .onSelectedUnderlyingNetworkChanged(TEST_UNDERLYING_NETWORK_RECORD_1); + mTestLooper.dispatchAll(); + + assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState()); + } + + @Test + public void testCreatedTransformsAreApplied() throws Exception { + for (int direction : new int[] {DIRECTION_IN, DIRECTION_OUT}) { + getChildSessionCallback().onIpSecTransformCreated(makeDummyIpSecTransform(), direction); + mTestLooper.dispatchAll(); + + verify(mIpSecSvc) + .applyTunnelModeTransform( + eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), eq(direction), anyInt(), any()); + } + + assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState()); + } + + @Test + public void testChildSessionClosedTriggersDisconnect() throws Exception { + getChildSessionCallback().onClosed(); + mTestLooper.dispatchAll(); + + assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); + } + + @Test + public void testIkeSessionClosedTriggersDisconnect() throws Exception { + getIkeSessionCallback().onClosed(); + mTestLooper.dispatchAll(); + + assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState()); + verify(mIkeSession).close(); + } + + // TODO: Add tests for childOpened() when ChildSessionConfiguration can be mocked or created +} diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java index 4ecd21503165..fbaae6f534a9 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java @@ -44,7 +44,8 @@ public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnect @Test public void testEnterWhileNotRunningTriggersQuit() throws Exception { final VcnGatewayConnection vgc = - new VcnGatewayConnection(mVcnContext, TEST_SUB_GRP, mConfig, mDeps); + new VcnGatewayConnection( + mVcnContext, TEST_SUB_GRP, TEST_SUBSCRIPTION_SNAPSHOT, mConfig, mDeps); vgc.setIsRunning(false); vgc.transitionTo(vgc.mDisconnectedState); diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java index d741e5cf4b35..bc6bee28d14f 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTest.java @@ -16,20 +16,36 @@ package com.android.server.vcn; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; -import android.annotation.NonNull; -import android.content.Context; +import android.net.LinkProperties; +import android.net.Network; import android.net.NetworkCapabilities; +import android.net.TelephonyNetworkSpecifier; import android.net.vcn.VcnGatewayConnectionConfigTest; +import android.net.vcn.VcnTransportInfo; +import android.net.wifi.WifiInfo; import android.os.ParcelUuid; -import android.os.test.TestLooper; +import android.os.Process; import android.telephony.SubscriptionInfo; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; +import com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkRecord; + +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -41,7 +57,9 @@ import java.util.UUID; /** Tests for TelephonySubscriptionTracker */ @RunWith(AndroidJUnit4.class) @SmallTest -public class VcnGatewayConnectionTest { +public class VcnGatewayConnectionTest extends VcnGatewayConnectionTestBase { + private static final int TEST_UID = Process.myUid(); + private static final ParcelUuid TEST_PARCEL_UUID = new ParcelUuid(UUID.randomUUID()); private static final int TEST_SIM_SLOT_INDEX = 1; private static final int TEST_SUBSCRIPTION_ID_1 = 2; @@ -57,26 +75,67 @@ public class VcnGatewayConnectionTest { TEST_SUBID_TO_GROUP_MAP = Collections.unmodifiableMap(subIdToGroupMap); } - @NonNull private final Context mContext; - @NonNull private final TestLooper mTestLooper; - @NonNull private final VcnNetworkProvider mVcnNetworkProvider; - @NonNull private final VcnGatewayConnection.Dependencies mDeps; + private WifiInfo mWifiInfo; - public VcnGatewayConnectionTest() { - mContext = mock(Context.class); - mTestLooper = new TestLooper(); - mVcnNetworkProvider = mock(VcnNetworkProvider.class); - mDeps = mock(VcnGatewayConnection.Dependencies.class); + @Before + public void setUp() throws Exception { + super.setUp(); + + mWifiInfo = mock(WifiInfo.class); } - @Test - public void testBuildNetworkCapabilities() throws Exception { - final NetworkCapabilities caps = + private void verifyBuildNetworkCapabilitiesCommon(int transportType) { + final NetworkCapabilities underlyingCaps = new NetworkCapabilities(); + underlyingCaps.addTransportType(transportType); + underlyingCaps.addCapability(NET_CAPABILITY_NOT_METERED); + underlyingCaps.addCapability(NET_CAPABILITY_NOT_ROAMING); + + if (transportType == TRANSPORT_WIFI) { + underlyingCaps.setTransportInfo(mWifiInfo); + underlyingCaps.setOwnerUid(TEST_UID); + } else if (transportType == TRANSPORT_CELLULAR) { + underlyingCaps.setAdministratorUids(new int[] {TEST_UID}); + underlyingCaps.setNetworkSpecifier( + new TelephonyNetworkSpecifier(TEST_SUBSCRIPTION_ID_1)); + } + + UnderlyingNetworkRecord record = + new UnderlyingNetworkRecord( + new Network(0), underlyingCaps, new LinkProperties(), false); + final NetworkCapabilities vcnCaps = VcnGatewayConnection.buildNetworkCapabilities( - VcnGatewayConnectionConfigTest.buildTestConfig()); + VcnGatewayConnectionConfigTest.buildTestConfig(), record); - for (int exposedCapability : VcnGatewayConnectionConfigTest.EXPOSED_CAPS) { - assertTrue(caps.hasCapability(exposedCapability)); + assertTrue(vcnCaps.hasTransport(TRANSPORT_CELLULAR)); + assertTrue(vcnCaps.hasCapability(NET_CAPABILITY_NOT_METERED)); + assertTrue(vcnCaps.hasCapability(NET_CAPABILITY_NOT_ROAMING)); + assertArrayEquals(new int[] {TEST_UID}, vcnCaps.getAdministratorUids()); + assertTrue(vcnCaps.getTransportInfo() instanceof VcnTransportInfo); + + final VcnTransportInfo info = (VcnTransportInfo) vcnCaps.getTransportInfo(); + if (transportType == TRANSPORT_WIFI) { + assertEquals(mWifiInfo, info.getWifiInfo()); + } else if (transportType == TRANSPORT_CELLULAR) { + assertEquals(TEST_SUBSCRIPTION_ID_1, info.getSubId()); } } + + @Test + public void testBuildNetworkCapabilitiesUnderlyingWifi() throws Exception { + verifyBuildNetworkCapabilitiesCommon(TRANSPORT_WIFI); + } + + @Test + public void testBuildNetworkCapabilitiesUnderlyingCell() throws Exception { + verifyBuildNetworkCapabilitiesCommon(TRANSPORT_CELLULAR); + } + + @Test + public void testSubscriptionSnapshotUpdateNotifiesUnderlyingNetworkTracker() { + final TelephonySubscriptionSnapshot updatedSnapshot = + mock(TelephonySubscriptionSnapshot.class); + mGatewayConnection.updateSubscriptionSnapshot(updatedSnapshot); + + verify(mUnderlyingNetworkTracker).updateSubscriptionSnapshot(eq(updatedSnapshot)); + } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java index 4d92fb9c42f2..df1341cce20f 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java @@ -27,7 +27,9 @@ import static org.mockito.Mockito.verify; import android.annotation.NonNull; import android.content.Context; +import android.net.IpSecConfig; import android.net.IpSecManager; +import android.net.IpSecTransform; import android.net.IpSecTunnelInterfaceResponse; import android.net.LinkProperties; import android.net.Network; @@ -40,15 +42,21 @@ import android.os.ParcelUuid; import android.os.test.TestLooper; import com.android.server.IpSecService; +import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import org.junit.Before; import org.mockito.ArgumentCaptor; +import java.util.Collections; import java.util.UUID; public class VcnGatewayConnectionTestBase { protected static final ParcelUuid TEST_SUB_GRP = new ParcelUuid(UUID.randomUUID()); - protected static final int TEST_IPSEC_TUNNEL_RESOURCE_ID = 1; + protected static final int TEST_IPSEC_SPI_VALUE = 0x1234; + protected static final int TEST_IPSEC_SPI_RESOURCE_ID = 1; + protected static final int TEST_IPSEC_TRANSFORM_RESOURCE_ID = 2; + protected static final int TEST_IPSEC_TUNNEL_RESOURCE_ID = 3; + protected static final int TEST_SUB_ID = 5; protected static final String TEST_IPSEC_TUNNEL_IFACE = "IPSEC_IFACE"; protected static final UnderlyingNetworkRecord TEST_UNDERLYING_NETWORK_RECORD_1 = new UnderlyingNetworkRecord( @@ -63,6 +71,10 @@ public class VcnGatewayConnectionTestBase { new LinkProperties(), false /* blocked */); + protected static final TelephonySubscriptionSnapshot TEST_SUBSCRIPTION_SNAPSHOT = + new TelephonySubscriptionSnapshot( + Collections.singletonMap(TEST_SUB_ID, TEST_SUB_GRP), Collections.EMPTY_MAP); + @NonNull protected final Context mContext; @NonNull protected final TestLooper mTestLooper; @NonNull protected final VcnNetworkProvider mVcnNetworkProvider; @@ -94,7 +106,7 @@ public class VcnGatewayConnectionTestBase { doReturn(mUnderlyingNetworkTracker) .when(mDeps) - .newUnderlyingNetworkTracker(any(), any(), any(), any()); + .newUnderlyingNetworkTracker(any(), any(), any(), any(), any()); } @Before @@ -109,7 +121,13 @@ public class VcnGatewayConnectionTestBase { mMockIkeSession = mock(VcnIkeSession.class); doReturn(mMockIkeSession).when(mDeps).newIkeSession(any(), any(), any(), any(), any()); - mGatewayConnection = new VcnGatewayConnection(mVcnContext, TEST_SUB_GRP, mConfig, mDeps); + mGatewayConnection = + new VcnGatewayConnection( + mVcnContext, TEST_SUB_GRP, TEST_SUBSCRIPTION_SNAPSHOT, mConfig, mDeps); + } + + protected IpSecTransform makeDummyIpSecTransform() throws Exception { + return new IpSecTransform(mContext, new IpSecConfig()); } protected IkeSessionCallback getIkeSessionCallback() { diff --git a/tests/vcn/java/com/android/server/vcn/VcnTest.java b/tests/vcn/java/com/android/server/vcn/VcnTest.java new file mode 100644 index 000000000000..0c1df763a08e --- /dev/null +++ b/tests/vcn/java/com/android/server/vcn/VcnTest.java @@ -0,0 +1,129 @@ +/* + * 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.vcn; + +import static org.junit.Assert.assertFalse; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.net.NetworkRequest; +import android.net.vcn.VcnConfig; +import android.net.vcn.VcnGatewayConnectionConfigTest; +import android.os.ParcelUuid; +import android.os.test.TestLooper; + +import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; +import com.android.server.vcn.VcnNetworkProvider.NetworkRequestListener; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +import java.util.Set; +import java.util.UUID; + +public class VcnTest { + private static final String PKG_NAME = VcnTest.class.getPackage().getName(); + private static final ParcelUuid TEST_SUB_GROUP = new ParcelUuid(new UUID(0, 0)); + private static final int NETWORK_SCORE = 0; + private static final int PROVIDER_ID = 5; + + private Context mContext; + private VcnContext mVcnContext; + private TelephonySubscriptionSnapshot mSubscriptionSnapshot; + private VcnNetworkProvider mVcnNetworkProvider; + private Vcn.Dependencies mDeps; + + private TestLooper mTestLooper; + private VcnConfig mConfig; + private Vcn mVcn; + + @Before + public void setUp() { + mContext = mock(Context.class); + mVcnContext = mock(VcnContext.class); + mSubscriptionSnapshot = mock(TelephonySubscriptionSnapshot.class); + mVcnNetworkProvider = mock(VcnNetworkProvider.class); + mDeps = mock(Vcn.Dependencies.class); + + mTestLooper = new TestLooper(); + + doReturn(PKG_NAME).when(mContext).getOpPackageName(); + doReturn(mContext).when(mVcnContext).getContext(); + doReturn(mTestLooper.getLooper()).when(mVcnContext).getLooper(); + doReturn(mVcnNetworkProvider).when(mVcnContext).getVcnNetworkProvider(); + + // Setup VcnGatewayConnection instance generation + doAnswer((invocation) -> { + // Mock-within a doAnswer is safe, because it doesn't actually run nested. + return mock(VcnGatewayConnection.class); + }).when(mDeps).newVcnGatewayConnection(any(), any(), any(), any()); + + mConfig = + new VcnConfig.Builder(mContext) + .addGatewayConnectionConfig( + VcnGatewayConnectionConfigTest.buildTestConfig()) + .build(); + + mVcn = new Vcn(mVcnContext, TEST_SUB_GROUP, mConfig, mSubscriptionSnapshot, mDeps); + } + + private NetworkRequestListener verifyAndGetRequestListener() { + ArgumentCaptor<NetworkRequestListener> mNetworkRequestListenerCaptor = + ArgumentCaptor.forClass(NetworkRequestListener.class); + verify(mVcnNetworkProvider).registerListener(mNetworkRequestListenerCaptor.capture()); + + return mNetworkRequestListenerCaptor.getValue(); + } + + private NetworkRequest getNetworkRequestWithCapabilities(int[] networkCapabilities) { + final NetworkRequest.Builder builder = new NetworkRequest.Builder(); + for (final int netCapability : networkCapabilities) { + builder.addCapability(netCapability); + } + return builder.build(); + } + + @Test + public void testSubscriptionSnapshotUpdatesVcnGatewayConnections() { + final NetworkRequestListener requestListener = verifyAndGetRequestListener(); + + requestListener.onNetworkRequested( + getNetworkRequestWithCapabilities(VcnGatewayConnectionConfigTest.EXPOSED_CAPS), + NETWORK_SCORE, + PROVIDER_ID); + mTestLooper.dispatchAll(); + + final Set<VcnGatewayConnection> gatewayConnections = mVcn.getVcnGatewayConnections(); + assertFalse(gatewayConnections.isEmpty()); + + final TelephonySubscriptionSnapshot updatedSnapshot = + mock(TelephonySubscriptionSnapshot.class); + + mVcn.updateSubscriptionSnapshot(updatedSnapshot); + mTestLooper.dispatchAll(); + + for (final VcnGatewayConnection gateway : gatewayConnections) { + verify(gateway).updateSubscriptionSnapshot(eq(updatedSnapshot)); + } + } +} |