Merge "Add @Nullable to Object#equals()"
diff --git a/apct-tests/perftests/core/OWNERS b/apct-tests/perftests/core/OWNERS
new file mode 100644
index 0000000..18486af
--- /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 bde2bec..b3e81b9 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 bb94275..15052f8 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 @@
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 @@
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 @@
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 @@
}
// 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 f453cca..976ec56 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9140,7 +9140,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 72fcc72..664b9e5 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2131,6 +2131,7 @@
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 @@
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 @@
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 573b932..fa7ac2b 100644
--- a/core/java/android/bluetooth/le/AdvertiseData.java
+++ b/core/java/android/bluetooth/le/AdvertiseData.java
@@ -44,7 +44,7 @@
@Nullable
private final List<ParcelUuid> mServiceUuids;
- @Nullable
+ @NonNull
private final List<ParcelUuid> mServiceSolicitationUuids;
private final SparseArray<byte[]> mManufacturerSpecificData;
@@ -77,7 +77,7 @@
/**
* 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 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 80a7b16..0581ed5 100644
--- a/core/java/android/content/AutofillOptions.java
+++ b/core/java/android/content/AutofillOptions.java
@@ -17,6 +17,7 @@
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 @@
* List of allowlisted activities.
*/
@Nullable
+ @SuppressLint("NullableCollection")
public ArraySet<ComponentName> whitelistedActivitiesForAugmentedAutofill;
/**
@@ -73,6 +75,7 @@
* 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 ef49e02..c296bb5 100644
--- a/core/java/android/content/ContentCaptureOptions.java
+++ b/core/java/android/content/ContentCaptureOptions.java
@@ -17,6 +17,7 @@
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 @@
* for all acitivites in the package).
*/
@Nullable
+ @SuppressLint("NullableCollection")
public final ArraySet<ComponentName> whitelistedComponents;
/**
@@ -96,6 +98,7 @@
*/
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 31beb6e..04e0468 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -59,6 +59,7 @@
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.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 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 @@
@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 @@
*
* @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 e6a04da..d7db7c7 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 fe9141c..dfb1e99 100644
--- a/core/java/android/net/INetworkPolicyListener.aidl
+++ b/core/java/android/net/INetworkPolicyListener.aidl
@@ -23,6 +23,6 @@
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 29a3fdf..b016ed6 100644
--- a/core/java/android/net/INetworkPolicyManager.aidl
+++ b/core/java/android/net/INetworkPolicyManager.aidl
@@ -76,10 +76,11 @@
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 82b035b..11146bd 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -34,6 +34,7 @@
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 @@
* @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 @@
* @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 @@
}
/**
+ * 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 @@
* @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 @@
@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 @@
@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 0185ba4..16d041a 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -547,7 +547,8 @@
}
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 @@
* 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 6acdcc4..f0a99ed 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -3691,4 +3691,9 @@
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 cb1509a..cb06515 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 0000000..2efc212
--- /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 f994d29..c39b8c5 100644
--- a/core/java/android/security/keymaster/KeymasterDefs.java
+++ b/core/java/android/security/keymaster/KeymasterDefs.java
@@ -80,6 +80,7 @@
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 f8500fa..6511a94 100644
--- a/core/java/android/view/FrameMetrics.java
+++ b/core/java/android/view/FrameMetrics.java
@@ -149,7 +149,7 @@
* <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 1c703ec..a17c012 100644
--- a/core/java/android/view/inputmethod/InlineSuggestionInfo.java
+++ b/core/java/android/view/inputmethod/InlineSuggestionInfo.java
@@ -82,6 +82,7 @@
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 e6a04da..d7db7c7 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 502680d..7b4fcbf 100644
--- a/core/java/android/window/TaskOrganizer.java
+++ b/core/java/android/window/TaskOrganizer.java
@@ -20,6 +20,7 @@
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 @@
/** 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 @@
/** 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 16991b4..1270185 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1210,25 +1210,6 @@
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 241570a..0fdab72 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 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 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 581dc08..f71b42c 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 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 @@
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 @@
{"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 748b4b4..99fd215 100644
--- a/core/proto/OWNERS
+++ b/core/proto/OWNERS
@@ -16,6 +16,7 @@
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 0000000..d341c4b
--- /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 0000000..b0e0b9d
--- /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 0000000..b0e0b9d
--- /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 0000000..b0e0b9d
--- /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 0000000..be38260
--- /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 97da3cc..1ae6a63 100644
--- a/keystore/java/android/security/IKeyChainService.aidl
+++ b/keystore/java/android/security/IKeyChainService.aidl
@@ -46,6 +46,7 @@
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 b1b6306..16bf546 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreProvider.java
@@ -23,7 +23,6 @@
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 @@
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 @@
* @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 @@
// 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 3694d63..d2678c7 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
@@ -215,7 +215,8 @@
// 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 e9aac7d..c2a7b2e 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -274,6 +274,7 @@
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 @@
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 @@
mUserConfirmationRequired = userConfirmationRequired;
mUnlockedDeviceRequired = unlockedDeviceRequired;
mCriticalToDeviceEncryption = criticalToDeviceEncryption;
+ mMaxUsageCount = maxUsageCount;
}
/**
@@ -782,7 +785,7 @@
}
/**
- * 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 @@
}
/**
+ * 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 @@
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 @@
mUserConfirmationRequired = sourceSpec.isUserConfirmationRequired();
mUnlockedDeviceRequired = sourceSpec.isUnlockedDeviceRequired();
mCriticalToDeviceEncryption = sourceSpec.isCriticalToDeviceEncryption();
+ mMaxUsageCount = sourceSpec.getMaxUsageCount();
}
/**
@@ -1553,6 +1569,47 @@
}
/**
+ * 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 @@
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 7158d0c..f50efd2 100644
--- a/keystore/java/android/security/keystore/KeyInfo.java
+++ b/keystore/java/android/security/keystore/KeyInfo.java
@@ -85,6 +85,7 @@
private final boolean mInvalidatedByBiometricEnrollment;
private final boolean mUserConfirmationRequired;
private final @KeyProperties.SecurityLevelEnum int mSecurityLevel;
+ private final int mRemainingUsageCount;
/**
* @hide
@@ -109,7 +110,8 @@
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 @@
mInvalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment;
mUserConfirmationRequired = userConfirmationRequired;
mSecurityLevel = securityLevel;
+ mRemainingUsageCount = remainingUsageCount;
}
/**
@@ -374,4 +377,15 @@
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 014d688..fa4f8b1 100644
--- a/keystore/java/android/security/keystore/KeyProperties.java
+++ b/keystore/java/android/security/keystore/KeyProperties.java
@@ -907,4 +907,9 @@
+ 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 2e793de..76ce23e 100644
--- a/keystore/java/android/security/keystore/KeyProtection.java
+++ b/keystore/java/android/security/keystore/KeyProtection.java
@@ -235,6 +235,7 @@
private final boolean mUserConfirmationRequired;
private final boolean mUnlockedDeviceRequired;
private final boolean mIsStrongBoxBacked;
+ private final int mMaxUsageCount;
private KeyProtection(
Date keyValidityStart,
@@ -256,7 +257,8 @@
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 @@
mUserConfirmationRequired = userConfirmationRequired;
mUnlockedDeviceRequired = unlockedDeviceRequired;
mIsStrongBoxBacked = isStrongBoxBacked;
+ mMaxUsageCount = maxUsageCount;
}
/**
@@ -548,6 +551,17 @@
}
/**
+ * 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 @@
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 @@
}
/**
+ * 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 @@
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 69c15cc..8163472 100644
--- a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
@@ -108,6 +108,7 @@
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 @@
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 @@
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 233f352..1575bb4 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyGeneratorSpi.java
@@ -366,6 +366,13 @@
));
}
+ 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 df0e146..6a92980 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -586,6 +586,13 @@
));
}
+ 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 74503e1..fe05989 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSecretKeyFactorySpi.java
@@ -95,6 +95,7 @@
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 @@
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 @@
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 07169ce..8c8acc4 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
@@ -535,6 +535,12 @@
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 @@
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 33c6951..ccb74eb 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -1445,6 +1445,7 @@
@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 ac5a895..d414ed4 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 987dcc4..06dec93 100644
--- a/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
+++ b/packages/Connectivity/framework/src/android/net/ConnectivityManager.java
@@ -3231,32 +3231,6 @@
}
}
- /** {@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 1b4d2e4..db8b7ed 100644
--- a/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl
+++ b/packages/Connectivity/framework/src/android/net/IConnectivityManager.aidl
@@ -156,9 +156,6 @@
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 d22d82d..27aa15d 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.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 @@
* @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 8bfa77a..55b2c3c 100644
--- a/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
+++ b/packages/Connectivity/framework/src/android/net/NetworkCapabilities.java
@@ -1786,6 +1786,15 @@
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 @@
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 @@
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 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 c5d4fa9..cb610fc 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 @@
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 3c5268c..ba2a63a 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 f2e1920..bf9d564 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.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 @@
}
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 @@
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 @@
", 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 @@
// 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 @@
}
/**
+ * 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 @@
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 @@
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 @@
}
private NetworkState getUnfilteredActiveNetworkState(int uid) {
- NetworkAgentInfo nai = getFallbackNetwork();
+ NetworkAgentInfo nai = getDefaultNetworkForUid(uid);
final Network[] networks = getVpnUnderlyingNetworks(uid);
if (networks != null) {
@@ -1499,7 +1517,7 @@
}
}
- NetworkAgentInfo nai = getFallbackNetwork();
+ NetworkAgentInfo nai = getDefaultNetworkForUid(uid);
if (nai == null || isNetworkWithCapabilitiesBlocked(nai.networkCapabilities, uid,
ignoreBlocked)) {
return null;
@@ -1638,21 +1656,28 @@
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 @@
/**
* 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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
/**
- * 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 @@
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 @@
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 @@
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 @@
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 @@
}
}
}
- 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 @@
// (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 @@
}
}
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 @@
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 @@
// 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 @@
@Override
public NetworkRequest getDefaultRequest() {
- return mFallbackRequest.mRequests.get(0);
+ return mDefaultRequest.mRequests.get(0);
}
private class InternalHandler extends Handler {
@@ -4506,7 +4530,7 @@
// 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 @@
Network network, int uid, boolean hasConnectivity) {
final NetworkAgentInfo nai;
if (network == null) {
- nai = getFallbackNetwork();
+ nai = getDefaultNetwork();
} else {
nai = getNetworkAgentInfoForNetwork(network);
}
@@ -4891,7 +4915,8 @@
// 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 @@
}
}
- 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 @@
// 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 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
@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 @@
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 @@
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 @@
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 @@
// 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 @@
// 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 @@
}
@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 @@
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 @@
// for (LinkProperties lp : newLp.getStackedLinks()) {
// updateMtu(lp, null);
// }
- if (isFallbackNetwork(networkAgent)) {
+ if (isDefaultNetwork(networkAgent)) {
updateTcpBufferSizes(newLp.getTcpBufferSizes());
}
@@ -6246,7 +6297,7 @@
// 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 @@
@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 @@
// 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 @@
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;
- }
-
- if (null != newDefaultNetwork) {
- mNetd.networkSetDefault(newDefaultNetwork.network.getNetId());
- } else {
- mNetd.networkClearDefault();
- }
- } catch (RemoteException | ServiceSpecificException e) {
- loge("Exception setting default network :" + e);
+ // Fix up the NetworkCapabilities of any networks that have this network as underlying.
+ if (newDefaultNetwork != null) {
+ propagateUnderlyingNetworkCapabilities(newDefaultNetwork.network);
}
+ // 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 @@
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 @@
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 @@
@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 @@
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 @@
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 @@
// 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 @@
// 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 @@
// 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 @@
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;
+ final NetworkReassignment.RequestReassignment reassignmentOfDefault =
+ changes.getReassignment(mDefaultRequest);
+ final NetworkAgentInfo oldDefaultNetwork =
+ null != reassignmentOfDefault ? reassignmentOfDefault.mOldNetwork : null;
+ final NetworkAgentInfo newDefaultNetwork =
+ null != reassignmentOfDefault ? reassignmentOfDefault.mNewNetwork : null;
- if (oldFallbackNetwork != newFallbackNetwork) {
+ 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 @@
}
// 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 @@
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 @@
// 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 @@
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 @@
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 @@
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 6a72010..916bec2 100644
--- a/services/core/java/com/android/server/VcnManagementService.java
+++ b/services/core/java/com/android/server/VcnManagementService.java
@@ -290,8 +290,9 @@
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 @@
// 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 @@
// 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 @@
}
@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 @@
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 686adbb..1e6c7ac 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 @@
"getContentProviderImpl: after checkContentProviderPermission");
final long origId = Binder.clearCallingIdentity();
+ try {
+ checkTime(startTime, "getContentProviderImpl: incProviderCountLocked");
- 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");
+ // 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: 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;
}
- // 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;
+ 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;
+ }
+ } 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 fded85c..e97f0b4 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 @@
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.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.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 @@
*/
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 @@
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 @@
* @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 @@
@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 @@
*/
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 @@
*/
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 @@
*/
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 @@
}
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 @@
* @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 @@
}
/**
- * 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 @@
/**
* 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) {
- synchronized (mLock) {
- addUserPackageStatesL(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);
+ }
}
}
- private void onUserRemoved(int userId) {
+ @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) {
+ initializeUserHibernationStates(userId, storedStates);
+ }
+ }
+
+ @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 @@
private void onPackageRemovedForAllUsers(@NonNull String packageName) {
synchronized (mLock) {
- mGloballyHibernatedPackages.remove(packageName);
+ mGlobalHibernationStates.remove(packageName);
}
}
@@ -395,7 +499,7 @@
}
}
- // 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 @@
}
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 @@
}
/**
- * 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 0000000..79e995b
--- /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 0000000..4f75675
--- /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 0000000..c83659d
--- /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 0000000..0cbc09a
--- /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 0000000..a24c4c5
--- /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 0000000..c66dad8
--- /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 1a4f20c7..a9a705f 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 @@
// 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 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 @@
* 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 @@
* 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 @@
// 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 d956ba3..5956fe1 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 @@
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 @@
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 f92f3dc..39ed7e8 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 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 0fd39a4..6c67cba 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_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.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 @@
// 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 @@
@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 @@
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 @@
}
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 @@
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 @@
}
@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 @@
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 c3cb42f..45419fe 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_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 5417275..2067fd0 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_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 330f995..9f0efa5 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 @@
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 6594a90..ae2e58f 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 @@
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 3dfb99e..bba5dcb 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 @@
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 fd12c2d2..b6ddd93 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.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 @@
@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 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 @@
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 @@
mHandler = new Handler(mVcnContext.getLooper());
mConnectivityManager = mVcnContext.getContext().getSystemService(ConnectivityManager.class);
- mSubscriptionManager = mVcnContext.getContext().getSystemService(SubscriptionManager.class);
registerNetworkRequests();
}
@@ -149,34 +150,47 @@
private void updateSubIdsAndCellularRequests() {
mVcnContext.ensureRunningOnLooperThread();
- Set<Integer> prevSubIds = new ArraySet<>(mSubIds);
- mSubIds.clear();
-
- // Ensure NetworkRequests filed for all current subIds in mSubscriptionGroup
- // STOPSHIP: b/177364490 use TelephonySubscriptionSnapshot to avoid querying Telephony
- List<SubscriptionInfo> subInfos =
- mSubscriptionManager.getSubscriptionsInGroup(mSubscriptionGroup);
-
- for (SubscriptionInfo subInfo : subInfos) {
- final int subId = subInfo.getSubscriptionId();
- mSubIds.add(subId);
-
- if (!mCellBringupCallbacks.contains(subId)) {
- final NetworkBringupCallback cb = new NetworkBringupCallback();
- mCellBringupCallbacks.put(subId, cb);
-
- mConnectivityManager.requestBackgroundNetwork(
- getCellNetworkRequestForSubId(subId), mHandler, cb);
- }
+ // Don't bother re-filing NetworkRequests if this Tracker has been torn down.
+ if (!mIsRunning) {
+ return;
}
- // unregister all NetworkCallbacks for outdated subIds
- for (final int subId : prevSubIds) {
- if (!mSubIds.contains(subId)) {
- final NetworkCallback cb = mCellBringupCallbacks.removeReturnOld(subId);
- mConnectivityManager.unregisterNetworkCallback(cb);
- }
+ final Set<Integer> subIdsInSubGroup = mLastSnapshot.getAllSubIdsInGroup(mSubscriptionGroup);
+
+ // new subIds to track = (updated list of subIds) - (currently tracked subIds)
+ final Set<Integer> subIdsToRegister = new ArraySet<>(subIdsInSubGroup);
+ subIdsToRegister.removeAll(mCellBringupCallbacks.keySet());
+
+ // subIds to stop tracking = (currently tracked subIds) - (updated list of subIds)
+ final Set<Integer> subIdsToUnregister = new ArraySet<>(mCellBringupCallbacks.keySet());
+ subIdsToUnregister.removeAll(subIdsInSubGroup);
+
+ for (final int subId : subIdsToRegister) {
+ final NetworkBringupCallback cb = new NetworkBringupCallback();
+ mCellBringupCallbacks.put(subId, cb);
+
+ mConnectivityManager.requestBackgroundNetwork(
+ getCellNetworkRequestForSubId(subId), mHandler, 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. */
@@ -186,11 +200,12 @@
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 132883e..a82f239 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.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 @@
*/
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 @@
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 @@
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 @@
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 @@
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 @@
"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 @@
}
/** 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 39c9606..853bb43 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.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.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.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 @@
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 @@
*/
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 @@
@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 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 @@
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 @@
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 @@
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 @@
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 @@
}
}
- 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 @@
*/
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 @@
@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 @@
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 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 b9babae..fe4ea30 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.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 @@
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 @@
}
}
- // 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 7af237b..fd8fa82 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 @@
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 389d07a..bbcc2c1 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -11,6 +11,9 @@
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 0ae10b6..a3cadf3 100644
--- a/services/incremental/BinderIncrementalService.cpp
+++ b/services/incremental/BinderIncrementalService.cpp
@@ -88,7 +88,6 @@
}
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 45bca68..1328b91 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.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 @@
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 @@
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 @@
}
@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 @@
}
@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 @@
}
@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 @@
}
/**
- * 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 @@
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 0000000..59f3c35
--- /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 df19aeb..58ba907 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 @@
}
/**
- * 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 @@
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 a118e0d..bbb25cd 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_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 3c7206f..69e4190 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_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 7cc233b..cac7b82 100644
--- a/startop/view_compiler/Android.bp
+++ b/startop/view_compiler/Android.bp
@@ -84,7 +84,6 @@
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 5f7d3f9..791e471 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 b73ef9b..be5fae4 100644
--- a/telecomm/java/android/telecom/ConnectionRequest.java
+++ b/telecomm/java/android/telecom/ConnectionRequest.java
@@ -18,6 +18,7 @@
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 @@
* 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 a42e364..b81c4f2 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -3937,6 +3937,20 @@
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 @@
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 8507d85..706e3cb 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -344,6 +344,7 @@
// 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 904232b..4e481b3 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -2589,14 +2589,45 @@
* 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 @@
* 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 fb65949..6bf992e 100644
--- a/telephony/java/android/telephony/ims/DelegateStateCallback.java
+++ b/telephony/java/android/telephony/ims/DelegateStateCallback.java
@@ -18,6 +18,7 @@
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 @@
* 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 f3c38bc..08eec29d 100644
--- a/telephony/java/android/telephony/ims/ProvisioningManager.java
+++ b/telephony/java/android/telephony/ims/ProvisioningManager.java
@@ -25,6 +25,7 @@
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 @@
* 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 @@
* 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 @@
}
/**
- * 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 @@
* 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 @@
* 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 2e9eb94..04421c9 100644
--- a/telephony/java/android/telephony/ims/SipDelegateManager.java
+++ b/telephony/java/android/telephony/ims/SipDelegateManager.java
@@ -24,6 +24,7 @@
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 @@
* 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 @@
* {@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 481e7f8..b99d8a7d 100644
--- a/telephony/java/android/telephony/ims/aidl/IPublishResponseCallback.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IPublishResponseCallback.aidl
@@ -26,4 +26,5 @@
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 a141993..8cc8020 100644
--- a/telephony/java/android/telephony/ims/aidl/ISubscribeResponseCallback.aidl
+++ b/telephony/java/android/telephony/ims/aidl/ISubscribeResponseCallback.aidl
@@ -30,6 +30,7 @@
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 22985d0..65415ea 100644
--- a/telephony/java/android/telephony/ims/aidl/RcsPublishResponseAidlWrapper.java
+++ b/telephony/java/android/telephony/ims/aidl/RcsPublishResponseAidlWrapper.java
@@ -34,10 +34,11 @@
}
@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 @@
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 1fb339c..11118c0 100644
--- a/telephony/java/android/telephony/ims/aidl/RcsSubscribeResponseAidlWrapper.java
+++ b/telephony/java/android/telephony/ims/aidl/RcsSubscribeResponseAidlWrapper.java
@@ -40,10 +40,11 @@
}
@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 @@
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 @@
try {
mResponseBinder.onNotifyCapabilitiesUpdate(pidfXmls);
} catch (RemoteException e) {
+ throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
}
}
@@ -69,6 +83,7 @@
try {
mResponseBinder.onResourceTerminated(getTerminatedReasonList(uriTerminatedReason));
} catch (RemoteException e) {
+ throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
}
}
@@ -90,6 +105,7 @@
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 2e35d27..5f8e93d 100644
--- a/telephony/java/android/telephony/ims/stub/ImsEcbmImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsEcbmImplBase.java
@@ -44,8 +44,12 @@
@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 555a47e..8e961ac 100644
--- a/telephony/java/android/telephony/ims/stub/ImsMultiEndpointImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsMultiEndpointImplBase.java
@@ -48,6 +48,10 @@
@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 eef4fca..83b89aa 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.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 @@
// 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 @@
@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 7eba709..ec98be6 100644
--- a/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/RcsCapabilityExchangeImplBase.java
@@ -140,6 +140,9 @@
* 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 @@
*/
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 @@
* {@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 @@
@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 02597d5..e673549 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 @@
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 0000000..34ac813
--- /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 b0cc7f1..9f48535 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -1262,22 +1262,28 @@
}
}
- 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 @@
.addTransportType(TRANSPORT_CELLULAR)
.build();
mCm.registerNetworkCallback(cellRequest, cellNetworkCallback);
+ mockUidNetworkingBlocked();
mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
mCellNetworkAgent.connect(true);
@@ -6891,6 +6898,7 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
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 e590fb7..a10a3c8 100644
--- a/tests/net/java/com/android/server/LegacyTypeTrackerTest.kt
+++ b/tests/net/java/com/android/server/LegacyTypeTrackerTest.kt
@@ -55,7 +55,7 @@
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 @@
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 f478282..3a93c5b 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.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.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 @@
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 @@
}
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 @@
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 @@
"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 @@
}
@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 e7d334e..e32e1e8 100644
--- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
+++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java
@@ -184,7 +184,7 @@
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 @@
@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 @@
mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS);
mTestLooper.dispatchAll();
verify(vcn).teardownAsynchronously();
+ verify(mMockPolicyListener).onPolicyChanged();
}
@Test
@@ -389,6 +393,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
@Test
- public void testGetUnderlyingNetworkPolicyTransportCell() throws Exception {
+ public void testGetUnderlyingNetworkPolicyCellular() throws Exception {
setUpVcnSubscription(TEST_SUBSCRIPTION_ID, TEST_UUID_2);
NetworkCapabilities nc =
@@ -542,12 +554,12 @@
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 @@
mVcnMgmtSvc.getUnderlyingNetworkPolicy(nc, new LinkProperties());
assertFalse(policy.isTeardownRequested());
- verifyMergedNetworkCapabilities(
+ verifyMergedNetworkCapabilitiesIsVcnManaged(
policy.getMergedNetworkCapabilities(), NetworkCapabilities.TRANSPORT_WIFI);
}
@@ -591,4 +603,35 @@
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 48e068d..1d459a3 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.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 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 @@
@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 @@
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 @@
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));
+ }
- verify(mSubscriptionManager).getSubscriptionsInGroup(eq(SUB_GROUP));
+ 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));
+ }
+ }
+
+ @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 0000000..e20070e
--- /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 4ecd215..fbaae6f 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 @@
@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 d741e5c..bc6bee2 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 org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
+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 android.annotation.NonNull;
-import android.content.Context;
+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.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 @@
/** 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 @@
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);
+ }
+
+ 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(), record);
+
+ 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 testBuildNetworkCapabilities() throws Exception {
- final NetworkCapabilities caps =
- VcnGatewayConnection.buildNetworkCapabilities(
- VcnGatewayConnectionConfigTest.buildTestConfig());
+ public void testBuildNetworkCapabilitiesUnderlyingWifi() throws Exception {
+ verifyBuildNetworkCapabilitiesCommon(TRANSPORT_WIFI);
+ }
- for (int exposedCapability : VcnGatewayConnectionConfigTest.EXPOSED_CAPS) {
- assertTrue(caps.hasCapability(exposedCapability));
- }
+ @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 4d92fb9..df1341c 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 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.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 @@
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 @@
doReturn(mUnderlyingNetworkTracker)
.when(mDeps)
- .newUnderlyingNetworkTracker(any(), any(), any(), any());
+ .newUnderlyingNetworkTracker(any(), any(), any(), any(), any());
}
@Before
@@ -109,7 +121,13 @@
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 0000000..0c1df76
--- /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));
+ }
+ }
+}