diff options
21 files changed, 386 insertions, 101 deletions
diff --git a/core/java/android/net/UidRange.java b/core/java/android/net/UidRange.java index 3bc0f9ca4e6a..b172ccc4e370 100644 --- a/core/java/android/net/UidRange.java +++ b/core/java/android/net/UidRange.java @@ -21,6 +21,7 @@ import static android.os.UserHandle.PER_USER_RANGE; import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; +import android.os.UserHandle; import java.util.Collection; @@ -45,6 +46,14 @@ public final class UidRange implements Parcelable { return new UidRange(userId * PER_USER_RANGE, (userId + 1) * PER_USER_RANGE - 1); } + /** Creates a UidRange for the specified user. */ + public static UidRange createForUser(UserHandle user) { + final UserHandle nextUser = UserHandle.of(user.getIdentifier() + 1); + final int start = UserHandle.getUid(user, 0 /* appId */); + final int end = UserHandle.getUid(nextUser, 0) - 1; + return new UidRange(start, end); + } + /** Returns the smallest user Id which is contained in this UidRange */ public int getStartUser() { return start / PER_USER_RANGE; diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java index ea282afb8b8e..903bea3dc2ce 100644 --- a/core/java/android/os/Debug.java +++ b/core/java/android/os/Debug.java @@ -2560,6 +2560,14 @@ public final class Debug public static native long getDmabufTotalExportedKb(); /** + * Return total memory size in kilobytes for DMA-BUFs exported from the DMA-BUF + * heaps frameworks or -1 in the case of an error. + * + * @hide + */ + public static native long getDmabufHeapTotalExportedKb(); + + /** * Return memory size in kilobytes allocated for ION heaps or -1 if * /sys/kernel/ion/total_heaps_kb could not be read. * diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index a41018000d77..65beb9360241 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -590,7 +590,7 @@ public final class Zygote { static Runnable forkUsap(LocalServerSocket usapPoolSocket, int[] sessionSocketRawFDs, boolean isPriorityFork) { - FileDescriptor[] pipeFDs = null; + FileDescriptor[] pipeFDs; try { pipeFDs = Os.pipe2(O_CLOEXEC); diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index 6573e4217a7f..5a576ebbc442 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -505,8 +505,8 @@ class ZygoteConnection { parsedArgs.mDisabledCompatChanges, parsedArgs.mRemainingArgs, null /* classLoader */); } else { - return ZygoteInit.childZygoteInit(parsedArgs.mTargetSdkVersion, - parsedArgs.mRemainingArgs, null /* classLoader */); + return ZygoteInit.childZygoteInit( + parsedArgs.mRemainingArgs /* classLoader */); } } } diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 7fde5474ec92..ef1c50f43681 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -117,7 +117,7 @@ public class ZygoteInit { /** * Controls whether we should preload resources during zygote init. */ - public static final boolean PRELOAD_RESOURCES = true; + private static final boolean PRELOAD_RESOURCES = true; private static final int UNPRIVILEGED_UID = 9999; private static final int UNPRIVILEGED_GID = 9999; @@ -159,7 +159,7 @@ public class ZygoteInit { sPreloadComplete = true; } - public static void lazyPreload() { + static void lazyPreload() { Preconditions.checkState(!sPreloadComplete); Log.i(TAG, "Lazily preloading resources."); @@ -364,7 +364,7 @@ public class ZygoteInit { } if ("true".equals(prop)) { Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "ResetJitCounters"); - runtime.resetJitCounters(); + VMRuntime.resetJitCounters(); Trace.traceEnd(Trace.TRACE_TAG_DALVIK); } @@ -418,8 +418,6 @@ public class ZygoteInit { * larger. */ private static void preloadResources() { - final VMRuntime runtime = VMRuntime.getRuntime(); - try { mResources = Resources.getSystem(); mResources.startPreloading(); @@ -463,9 +461,7 @@ public class ZygoteInit { int N = ar.length(); for (int i = 0; i < N; i++) { int id = ar.getResourceId(i, 0); - if (false) { - Log.v(TAG, "Preloading resource #" + Integer.toHexString(id)); - } + if (id != 0) { if (mResources.getColorStateList(id, null) == null) { throw new IllegalArgumentException( @@ -483,9 +479,7 @@ public class ZygoteInit { int N = ar.length(); for (int i = 0; i < N; i++) { int id = ar.getResourceId(i, 0); - if (false) { - Log.v(TAG, "Preloading resource #" + Integer.toHexString(id)); - } + if (id != 0) { if (mResources.getDrawable(id, null) == null) { throw new IllegalArgumentException( @@ -688,13 +682,12 @@ public class ZygoteInit { final String packageName = "*"; final String outputPath = null; final int dexFlags = 0; - final String compilerFilter = systemServerFilter; final String uuid = StorageManager.UUID_PRIVATE_INTERNAL; final String seInfo = null; final int targetSdkVersion = 0; // SystemServer targets the system's SDK version try { installd.dexopt(classPathElement, Process.SYSTEM_UID, packageName, - instructionSet, dexoptNeeded, outputPath, dexFlags, compilerFilter, + instructionSet, dexoptNeeded, outputPath, dexFlags, systemServerFilter, uuid, classLoaderContext, seInfo, false /* downgrade */, targetSdkVersion, /*profileName*/ null, /*dexMetadataPath*/ null, "server-dexopt"); @@ -770,7 +763,7 @@ public class ZygoteInit { capabilities &= ((long) data[0].effective) | (((long) data[1].effective) << 32); /* Hardcoded command line to start the system server */ - String args[] = { + String[] args = { "--setuid=1000", "--setgid=1000", "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023," @@ -781,7 +774,7 @@ public class ZygoteInit { "--target-sdk-version=" + VMRuntime.SDK_VERSION_CUR_DEVELOPMENT, "com.android.server.SystemServer", }; - ZygoteArguments parsedArgs = null; + ZygoteArguments parsedArgs; int pid; @@ -870,7 +863,7 @@ public class ZygoteInit { * @param argv Command line arguments used to specify the Zygote's configuration. */ @UnsupportedAppUsage - public static void main(String argv[]) { + public static void main(String[] argv) { ZygoteServer zygoteServer = null; // Mark zygote start. This ensures that thread creation will throw @@ -1029,7 +1022,7 @@ public class ZygoteInit { * are enabled) * @param argv arg strings */ - public static final Runnable zygoteInit(int targetSdkVersion, long[] disabledCompatChanges, + public static Runnable zygoteInit(int targetSdkVersion, long[] disabledCompatChanges, String[] argv, ClassLoader classLoader) { if (RuntimeInit.DEBUG) { Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote"); @@ -1049,11 +1042,10 @@ public class ZygoteInit { * to zygoteInit(), which skips calling into initialization routines that start the Binder * threadpool. */ - static final Runnable childZygoteInit( - int targetSdkVersion, String[] argv, ClassLoader classLoader) { + static Runnable childZygoteInit(String[] argv) { RuntimeInit.Arguments args = new RuntimeInit.Arguments(argv); - return RuntimeInit.findStaticMain(args.startClass, args.startArgs, classLoader); + return RuntimeInit.findStaticMain(args.startClass, args.startArgs, /* classLoader= */null); } - private static final native void nativeZygoteInit(); + private static native void nativeZygoteInit(); } diff --git a/core/java/com/android/internal/os/ZygoteServer.java b/core/java/com/android/internal/os/ZygoteServer.java index db7cbbca450e..585ddf6ddf98 100644 --- a/core/java/com/android/internal/os/ZygoteServer.java +++ b/core/java/com/android/internal/os/ZygoteServer.java @@ -411,7 +411,7 @@ class ZygoteServer { } } - void resetUsapRefillState() { + private void resetUsapRefillState() { mUsapPoolRefillAction = UsapPoolRefillAction.NONE; mUsapPoolRefillTriggerTimestamp = INVALID_TIMESTAMP; } diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp index 223b4dcc0549..0e3db46bd0c9 100644 --- a/core/jni/android_os_Debug.cpp +++ b/core/jni/android_os_Debug.cpp @@ -826,6 +826,16 @@ static jlong android_os_Debug_getDmabufTotalExportedKb(JNIEnv* env, jobject claz return dmabufTotalSizeKb; } +static jlong android_os_Debug_getDmabufHeapTotalExportedKb(JNIEnv* env, jobject clazz) { + jlong dmabufHeapTotalSizeKb = -1; + uint64_t size; + + if (meminfo::ReadDmabufHeapTotalExportedKb(&size)) { + dmabufHeapTotalSizeKb = size; + } + return dmabufHeapTotalSizeKb; +} + static jlong android_os_Debug_getIonPoolsSizeKb(JNIEnv* env, jobject clazz) { jlong poolsSizeKb = -1; uint64_t size; @@ -983,6 +993,8 @@ static const JNINativeMethod gMethods[] = { (void*)android_os_Debug_getDmabufTotalExportedKb }, { "getGpuDmaBufUsageKb", "()J", (void*)android_os_Debug_getGpuDmaBufUsageKb }, + { "getDmabufHeapTotalExportedKb", "()J", + (void*)android_os_Debug_getDmabufHeapTotalExportedKb }, { "getIonPoolsSizeKb", "()J", (void*)android_os_Debug_getIonPoolsSizeKb }, { "getDmabufMappedSizeKb", "()J", diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index e92eaca2b6e9..2b0d7e53b749 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -236,6 +236,47 @@ import javax.security.auth.x500.X500Principal; * keyStore.load(null); * key = (SecretKey) keyStore.getKey("key2", null); * }</pre> + * + * <p><h3 id="example:ecdh">Example: EC key for ECDH key agreement</h3> + * This example illustrates how to generate an elliptic curve key pair, used to establish a shared + * secret with another party using ECDH key agreement. + * <pre> {@code + * KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance( + * KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore"); + * keyPairGenerator.initialize( + * new KeyGenParameterSpec.Builder( + * "eckeypair", + * KeyProperties.PURPOSE_AGREE_KEY) + * .setAlgorithmParameterSpec(new ECGenParameterSpec("secp256r1")) + * .build()); + * KeyPair myKeyPair = keyPairGenerator.generateKeyPair(); + * + * // Exchange public keys with server. A new ephemeral key MUST be used for every message. + * PublicKey serverEphemeralPublicKey; // Ephemeral key received from server. + * + * // Create a shared secret based on our private key and the other party's public key. + * KeyAgreement keyAgreement = KeyAgreement.getInstance("ECDH", "AndroidKeyStore"); + * keyAgreement.init(myKeyPair.getPrivate()); + * keyAgreement.doPhase(serverEphemeralPublicKey, true); + * byte[] sharedSecret = keyAgreement.generateSecret(); + * + * // sharedSecret cannot safely be used as a key yet. We must run it through a key derivation + * // function with some other data: "salt" and "info". Salt is an optional random value, + * // omitted in this example. It's good practice to include both public keys and any other + * // key negotiation data in info. Here we use the public keys and a label that indicates + * // messages encrypted with this key are coming from the server. + * byte[] salt = {}; + * ByteArrayOutputStream info = new ByteArrayOutputStream(); + * info.write("ECDH secp256r1 AES-256-GCM-SIV\0".getBytes(StandardCharsets.UTF_8)); + * info.write(myKeyPair.getPublic().getEncoded()); + * info.write(serverEphemeralPublicKey.getEncoded()); + * + * // This example uses the Tink library and the HKDF key derivation function. + * AesGcmSiv key = new AesGcmSiv(Hkdf.computeHkdf( + * "HMACSHA256", sharedSecret, salt, info.toByteArray(), 32)); + * byte[] associatedData = {}; + * return key.decrypt(ciphertext, associatedData); + * } */ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAuthArgs { diff --git a/keystore/java/android/security/keystore/KeyProperties.java b/keystore/java/android/security/keystore/KeyProperties.java index 3ebca6ad302d..293ab05e0b10 100644 --- a/keystore/java/android/security/keystore/KeyProperties.java +++ b/keystore/java/android/security/keystore/KeyProperties.java @@ -100,6 +100,15 @@ public abstract class KeyProperties { /** * Purpose of key: creating a shared ECDH secret through key agreement. + * + * <p>A key having this purpose can be combined with the elliptic curve public key of another + * party to establish a shared secret over an insecure channel. It should be used as a + * parameter to {@link javax.crypto.KeyAgreement#init(java.security.Key)} (a complete example is + * available <a + * href="{@docRoot}reference/android/security/keystore/KeyGenParameterSpec#example:ecdh" + * >here</a>). + * See <a href="https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman">this + * article</a> for a more detailed explanation. */ public static final int PURPOSE_AGREE_KEY = 1 << 6; diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 75f4809d752f..dfa1b7e01809 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -62,6 +62,7 @@ import android.telephony.TelephonyManager; import android.util.EventLog; import android.util.Log; import android.util.Slog; +import android.util.SparseBooleanArray; import android.util.SparseIntArray; import android.view.View; import android.view.ViewGroup; @@ -294,6 +295,13 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable { */ private final SparseIntArray mLastSimStates = new SparseIntArray(); + /** + * Indicates if a SIM card had the SIM PIN enabled during the initialization, before + * reaching the SIM_STATE_READY state. The flag is reset to false at SIM_STATE_READY. + * Index is the slotId - in case of multiple SIM cards. + */ + private final SparseBooleanArray mSimWasLocked = new SparseBooleanArray(); + private boolean mDeviceInteractive; private boolean mGoingToSleep; @@ -465,10 +473,10 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable { } } - boolean simWasLocked; + boolean lastSimStateWasLocked; synchronized (KeyguardViewMediator.this) { int lastState = mLastSimStates.get(slotId); - simWasLocked = (lastState == TelephonyManager.SIM_STATE_PIN_REQUIRED + lastSimStateWasLocked = (lastState == TelephonyManager.SIM_STATE_PIN_REQUIRED || lastState == TelephonyManager.SIM_STATE_PUK_REQUIRED); mLastSimStates.append(slotId, simState); } @@ -492,17 +500,19 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable { if (simState == TelephonyManager.SIM_STATE_ABSENT) { // MVNO SIMs can become transiently NOT_READY when switching networks, // so we should only lock when they are ABSENT. - if (simWasLocked) { + if (lastSimStateWasLocked) { if (DEBUG_SIM_STATES) Log.d(TAG, "SIM moved to ABSENT when the " + "previous state was locked. Reset the state."); resetStateLocked(); } + mSimWasLocked.append(slotId, false); } } break; case TelephonyManager.SIM_STATE_PIN_REQUIRED: case TelephonyManager.SIM_STATE_PUK_REQUIRED: synchronized (KeyguardViewMediator.this) { + mSimWasLocked.append(slotId, true); if (!mShowing) { if (DEBUG_SIM_STATES) Log.d(TAG, "INTENT_VALUE_ICC_LOCKED and keygaurd isn't " @@ -529,9 +539,10 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable { case TelephonyManager.SIM_STATE_READY: synchronized (KeyguardViewMediator.this) { if (DEBUG_SIM_STATES) Log.d(TAG, "READY, reset state? " + mShowing); - if (mShowing && simWasLocked) { + if (mShowing && mSimWasLocked.get(slotId, false)) { if (DEBUG_SIM_STATES) Log.d(TAG, "SIM moved to READY when the " - + "previous state was locked. Reset the state."); + + "previously was locked. Reset the state."); + mSimWasLocked.append(slotId, false); resetStateLocked(); } } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index c2e50a685ca0..154e1831ceee 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -4983,12 +4983,12 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - private void onUserAdded(int userId) { - mPermissionMonitor.onUserAdded(userId); + private void onUserAdded(UserHandle user) { + mPermissionMonitor.onUserAdded(user); } - private void onUserRemoved(int userId) { - mPermissionMonitor.onUserRemoved(userId); + private void onUserRemoved(UserHandle user) { + mPermissionMonitor.onUserRemoved(user); } private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @@ -4996,15 +4996,18 @@ public class ConnectivityService extends IConnectivityManager.Stub public void onReceive(Context context, Intent intent) { ensureRunningOnConnectivityServiceThread(); final String action = intent.getAction(); - final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); + final UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER); - // UserId should be filled for below intents, check the existence. - if (userId == UserHandle.USER_NULL) return; + // User should be filled for below intents, check the existence. + if (user == null) { + Log.wtf(TAG, intent.getAction() + " broadcast without EXTRA_USER"); + return; + } if (Intent.ACTION_USER_ADDED.equals(action)) { - onUserAdded(userId); + onUserAdded(user); } else if (Intent.ACTION_USER_REMOVED.equals(action)) { - onUserRemoved(userId); + onUserRemoved(user); } else { Log.wtf(TAG, "received unexpected intent: " + action); } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 126d04f794f5..661d49690a00 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -13767,6 +13767,15 @@ public class ActivityManagerService extends IActivityManager.Stub totalPss -= totalMemtrackGraphics; totalPss += dmabufMapped; } + + // totalDmabufHeapExported is included in totalExportedDmabuf above and hence do not + // need to be added to kernelUsed. + final long totalDmabufHeapExported = Debug.getDmabufHeapTotalExportedKb(); + if (totalDmabufHeapExported >= 0) { + pw.print("DMA-BUF Heaps: "); + pw.println(stringifyKBSize(totalDmabufHeapExported)); + } + final long totalDmabufHeapPool = Debug.getDmabufHeapPoolsSizeKb(); if (totalDmabufHeapPool >= 0) { pw.print("DMA-BUF Heaps pool: "); @@ -14619,6 +14628,16 @@ public class ActivityManagerService extends IActivityManager.Stub totalPss -= totalMemtrackGraphics; totalPss += dmabufMapped; } + + // These are included in the totalExportedDmabuf above and hence do not need to be added + // to kernelUsed. + final long totalExportedDmabufHeap = Debug.getDmabufHeapTotalExportedKb(); + if (totalExportedDmabufHeap >= 0) { + memInfoBuilder.append("DMA-BUF Heap: "); + memInfoBuilder.append(stringifyKBSize(totalExportedDmabufHeap)); + memInfoBuilder.append("\n"); + } + final long totalDmabufHeapPool = Debug.getDmabufHeapPoolsSizeKb(); if (totalDmabufHeapPool >= 0) { memInfoBuilder.append("DMA-BUF Heaps pool: "); diff --git a/services/core/java/com/android/server/connectivity/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java index 8d21f6f0f59f..8bf188696c27 100644 --- a/services/core/java/com/android/server/connectivity/PermissionMonitor.java +++ b/services/core/java/com/android/server/connectivity/PermissionMonitor.java @@ -83,9 +83,8 @@ public class PermissionMonitor implements PackageManagerInternal.PackageListObse private final INetd mNetd; private final Dependencies mDeps; - // Values are User IDs. @GuardedBy("this") - private final Set<Integer> mUsers = new HashSet<>(); + private final Set<UserHandle> mUsers = new HashSet<>(); // Keys are app uids. Values are true for SYSTEM permission and false for NETWORK permission. @GuardedBy("this") @@ -173,10 +172,7 @@ public class PermissionMonitor implements PackageManagerInternal.PackageListObse netdPermsUids.put(uid, netdPermsUids.get(uid) | otherNetdPerms); } - final List<UserHandle> users = mUserManager.getUserHandles(true /* excludeDying */); - for (UserHandle user : users) { - mUsers.add(user.getIdentifier()); - } + mUsers.addAll(mUserManager.getUserHandles(true /* excludeDying */)); final SparseArray<ArraySet<String>> systemPermission = SystemConfig.getInstance().getSystemPermissions(); @@ -259,16 +255,15 @@ public class PermissionMonitor implements PackageManagerInternal.PackageListObse return array; } - private void update(Set<Integer> users, Map<Integer, Boolean> apps, boolean add) { + private void update(Set<UserHandle> users, Map<Integer, Boolean> apps, boolean add) { List<Integer> network = new ArrayList<>(); List<Integer> system = new ArrayList<>(); for (Entry<Integer, Boolean> app : apps.entrySet()) { List<Integer> list = app.getValue() ? system : network; - for (int user : users) { - final UserHandle handle = UserHandle.of(user); - if (handle == null) continue; + for (UserHandle user : users) { + if (user == null) continue; - list.add(UserHandle.getUid(handle, app.getKey())); + list.add(UserHandle.getUid(user, app.getKey())); } } try { @@ -291,14 +286,10 @@ public class PermissionMonitor implements PackageManagerInternal.PackageListObse * * @hide */ - public synchronized void onUserAdded(int user) { - if (user < 0) { - loge("Invalid user in onUserAdded: " + user); - return; - } + public synchronized void onUserAdded(@NonNull UserHandle user) { mUsers.add(user); - Set<Integer> users = new HashSet<>(); + Set<UserHandle> users = new HashSet<>(); users.add(user); update(users, mApps, true); } @@ -310,14 +301,10 @@ public class PermissionMonitor implements PackageManagerInternal.PackageListObse * * @hide */ - public synchronized void onUserRemoved(int user) { - if (user < 0) { - loge("Invalid user in onUserRemoved: " + user); - return; - } + public synchronized void onUserRemoved(@NonNull UserHandle user) { mUsers.remove(user); - Set<Integer> users = new HashSet<>(); + Set<UserHandle> users = new HashSet<>(); users.add(user); update(users, mApps, false); } diff --git a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java index 8aab3a66ada3..12590eba81f8 100644 --- a/services/core/java/com/android/server/vcn/VcnGatewayConnection.java +++ b/services/core/java/com/android/server/vcn/VcnGatewayConnection.java @@ -38,10 +38,12 @@ import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkAgent; +import android.net.NetworkAgent.ValidationStatus; import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; import android.net.RouteInfo; import android.net.TelephonyNetworkSpecifier; +import android.net.Uri; import android.net.annotations.PolicyDirection; import android.net.ipsec.ike.ChildSessionCallback; import android.net.ipsec.ike.ChildSessionConfiguration; @@ -151,6 +153,9 @@ public class VcnGatewayConnection extends StateMachine { @VisibleForTesting(visibility = Visibility.PRIVATE) static final String RETRY_TIMEOUT_ALARM = TAG + "_RETRY_TIMEOUT_ALARM"; + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final String SAFEMODE_TIMEOUT_ALARM = TAG + "_SAFEMODE_TIMEOUT_ALARM"; + private static final int[] MERGED_CAPABILITIES = new int[] {NET_CAPABILITY_NOT_METERED, NET_CAPABILITY_NOT_ROAMING}; private static final int ARG_NOT_PRESENT = Integer.MIN_VALUE; @@ -167,6 +172,9 @@ public class VcnGatewayConnection extends StateMachine { @VisibleForTesting(visibility = Visibility.PRIVATE) static final int TEARDOWN_TIMEOUT_SECONDS = 5; + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final int SAFEMODE_TIMEOUT_SECONDS = 30; + private interface EventInfo {} /** @@ -409,6 +417,23 @@ public class VcnGatewayConnection extends StateMachine { // TODO(b/178426520): implement handling of this event private static final int EVENT_SUBSCRIPTIONS_CHANGED = 9; + /** + * Sent when this VcnGatewayConnection has entered Safemode. + * + * <p>A VcnGatewayConnection enters Safemode when it takes over {@link + * #SAFEMODE_TIMEOUT_SECONDS} to enter {@link ConnectedState}. + * + * <p>When a VcnGatewayConnection enters safe mode, it will fire {@link + * VcnGatewayStatusCallback#onEnteredSafemode()} to notify its Vcn. The Vcn will then shut down + * its VcnGatewayConnectin(s). + * + * <p>Relevant in DisconnectingState, ConnectingState, ConnectedState (if the Vcn Network is not + * validated yet), and RetryTimeoutState. + * + * @param arg1 The "all" token; this signal is always honored. + */ + private static final int EVENT_SAFEMODE_TIMEOUT_EXCEEDED = 10; + @VisibleForTesting(visibility = Visibility.PRIVATE) @NonNull final DisconnectedState mDisconnectedState = new DisconnectedState(); @@ -520,11 +545,13 @@ public class VcnGatewayConnection extends StateMachine { * <p>Set in Connected state, always @NonNull in Connected, Migrating states, @Nullable * otherwise. */ - private NetworkAgent mNetworkAgent; + @VisibleForTesting(visibility = Visibility.PRIVATE) + NetworkAgent mNetworkAgent; @Nullable private WakeupMessage mTeardownTimeoutAlarm; @Nullable private WakeupMessage mDisconnectRequestAlarm; @Nullable private WakeupMessage mRetryTimeoutAlarm; + @Nullable private WakeupMessage mSafemodeTimeoutAlarm; public VcnGatewayConnection( @NonNull VcnContext vcnContext, @@ -611,6 +638,7 @@ public class VcnGatewayConnection extends StateMachine { cancelTeardownTimeoutAlarm(); cancelDisconnectRequestAlarm(); cancelRetryTimeoutAlarm(); + cancelSafemodeAlarm(); mUnderlyingNetworkTracker.teardown(); } @@ -899,6 +927,30 @@ public class VcnGatewayConnection extends StateMachine { removeEqualMessages(EVENT_RETRY_TIMEOUT_EXPIRED); } + @VisibleForTesting(visibility = Visibility.PRIVATE) + void setSafemodeAlarm() { + // Only schedule a NEW alarm if none is already set. + if (mSafemodeTimeoutAlarm != null) { + return; + } + + final Message delayedMessage = obtainMessage(EVENT_SAFEMODE_TIMEOUT_EXCEEDED, TOKEN_ALL); + mSafemodeTimeoutAlarm = + createScheduledAlarm( + SAFEMODE_TIMEOUT_ALARM, + delayedMessage, + TimeUnit.SECONDS.toMillis(SAFEMODE_TIMEOUT_SECONDS)); + } + + private void cancelSafemodeAlarm() { + if (mSafemodeTimeoutAlarm != null) { + mSafemodeTimeoutAlarm.cancel(); + mSafemodeTimeoutAlarm = null; + } + + removeEqualMessages(EVENT_SAFEMODE_TIMEOUT_EXCEEDED); + } + private void sessionLost(int token, @Nullable Exception exception) { sendMessageAndAcquireWakeLock( EVENT_SESSION_LOST, token, new EventSessionLostInfo(exception)); @@ -1072,6 +1124,8 @@ public class VcnGatewayConnection extends StateMachine { if (mIkeSession != null || mNetworkAgent != null) { Slog.wtf(TAG, "Active IKE Session or NetworkAgent in DisconnectedState"); } + + cancelSafemodeAlarm(); } @Override @@ -1095,6 +1149,12 @@ public class VcnGatewayConnection extends StateMachine { break; } } + + @Override + protected void exitState() { + // Safe to blindly set up, as it is cancelled and cleared on entering this state + setSafemodeAlarm(); + } } private abstract class ActiveBaseState extends BaseState { @@ -1185,6 +1245,10 @@ public class VcnGatewayConnection extends StateMachine { transitionTo(mDisconnectedState); } break; + case EVENT_SAFEMODE_TIMEOUT_EXCEEDED: + mGatewayStatusCallback.onEnteredSafemode(); + mSafemodeTimeoutAlarm = null; + break; default: logUnhandledMessage(msg); break; @@ -1267,6 +1331,10 @@ public class VcnGatewayConnection extends StateMachine { case EVENT_DISCONNECT_REQUESTED: handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason); break; + case EVENT_SAFEMODE_TIMEOUT_EXCEEDED: + mGatewayStatusCallback.onEnteredSafemode(); + mSafemodeTimeoutAlarm = null; + break; default: logUnhandledMessage(msg); break; @@ -1310,6 +1378,14 @@ public class VcnGatewayConnection extends StateMachine { public void unwanted() { teardownAsynchronously(); } + + @Override + public void onValidationStatus( + @ValidationStatus int status, @Nullable Uri redirectUri) { + if (status == NetworkAgent.VALIDATION_STATUS_VALID) { + clearFailedAttemptCounterAndSafeModeAlarm(); + } + } }; agent.register(); @@ -1318,6 +1394,14 @@ public class VcnGatewayConnection extends StateMachine { return agent; } + protected void clearFailedAttemptCounterAndSafeModeAlarm() { + mVcnContext.ensureRunningOnLooperThread(); + + // Validated connection, clear failed attempt counter + mFailedAttempts = 0; + cancelSafemodeAlarm(); + } + protected void applyTransform( int token, @NonNull IpSecTunnelInterface tunnelIface, @@ -1325,7 +1409,7 @@ public class VcnGatewayConnection extends StateMachine { @NonNull IpSecTransform transform, int direction) { try { - // TODO: Set underlying network of tunnel interface + // TODO(b/180163196): Set underlying network of tunnel interface // Transforms do not need to be persisted; the IkeSession will keep them alive mIpSecManager.applyTunnelModeTransform(tunnelIface, direction, transform); @@ -1397,9 +1481,6 @@ public class VcnGatewayConnection extends StateMachine { teardownAsynchronously(); } } - - // Successful connection, clear failed attempt counter - mFailedAttempts = 0; } @Override @@ -1436,6 +1517,10 @@ public class VcnGatewayConnection extends StateMachine { case EVENT_DISCONNECT_REQUESTED: handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason); break; + case EVENT_SAFEMODE_TIMEOUT_EXCEEDED: + mGatewayStatusCallback.onEnteredSafemode(); + mSafemodeTimeoutAlarm = null; + break; default: logUnhandledMessage(msg); break; @@ -1455,6 +1540,7 @@ public class VcnGatewayConnection extends StateMachine { // mUnderlying assumed non-null, given check above. // If network changed, migrate. Otherwise, update any existing networkAgent. if (oldUnderlying == null || !oldUnderlying.network.equals(mUnderlying.network)) { + Slog.v(TAG, "Migrating to new network: " + mUnderlying.network); mIkeSession.setNetwork(mUnderlying.network); } else { // oldUnderlying is non-null & underlying network itself has not changed @@ -1478,8 +1564,19 @@ public class VcnGatewayConnection extends StateMachine { mNetworkAgent = buildNetworkAgent(tunnelIface, childConfig); } else { updateNetworkAgent(tunnelIface, mNetworkAgent, childConfig); + + // mNetworkAgent not null, so the VCN Network has already been established. Clear + // the failed attempt counter and safe mode alarm since this transition is complete. + clearFailedAttemptCounterAndSafeModeAlarm(); } } + + @Override + protected void exitState() { + // Attempt to set the safe mode alarm - this requires the Vcn Network being validated + // while in ConnectedState (which cancels the previous alarm) + setSafemodeAlarm(); + } } /** @@ -1526,6 +1623,10 @@ public class VcnGatewayConnection extends StateMachine { case EVENT_DISCONNECT_REQUESTED: handleDisconnectRequested(((EventDisconnectRequestedInfo) msg.obj).reason); break; + case EVENT_SAFEMODE_TIMEOUT_EXCEEDED: + mGatewayStatusCallback.onEnteredSafemode(); + mSafemodeTimeoutAlarm = null; + break; default: logUnhandledMessage(msg); break; @@ -1708,6 +1809,15 @@ public class VcnGatewayConnection extends StateMachine { } @Override + public void onIpSecTransformsMigrated( + @NonNull IpSecTransform inIpSecTransform, + @NonNull IpSecTransform outIpSecTransform) { + Slog.v(TAG, "ChildTransformsMigrated; token " + mToken); + onIpSecTransformCreated(inIpSecTransform, IpSecManager.DIRECTION_IN); + onIpSecTransformCreated(outIpSecTransform, IpSecManager.DIRECTION_OUT); + } + + @Override public void onIpSecTransformDeleted(@NonNull IpSecTransform transform, int direction) { // Nothing to be done; no references to the IpSecTransform are held, and this transform // will be closed by the IKE library. diff --git a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java index 3556c72776dc..8f5ae97bc4c5 100644 --- a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java +++ b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java @@ -89,8 +89,8 @@ import java.util.Set; @RunWith(AndroidJUnit4.class) @SmallTest public class PermissionMonitorTest { - private static final int MOCK_USER1 = 0; - private static final int MOCK_USER2 = 1; + private static final UserHandle MOCK_USER1 = UserHandle.of(0); + private static final UserHandle MOCK_USER2 = UserHandle.of(1); private static final int MOCK_UID1 = 10001; private static final int MOCK_UID2 = 10086; private static final int SYSTEM_UID1 = 1000; @@ -123,10 +123,7 @@ public class PermissionMonitorTest { when(mContext.getPackageManager()).thenReturn(mPackageManager); when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager); when(mUserManager.getUserHandles(eq(true))).thenReturn( - Arrays.asList(new UserHandle[] { - new UserHandle(MOCK_USER1), - new UserHandle(MOCK_USER2), - })); + Arrays.asList(new UserHandle[] { MOCK_USER1, MOCK_USER2 })); mPermissionMonitor = spy(new PermissionMonitor(mContext, mNetdService, mDeps)); @@ -184,7 +181,8 @@ public class PermissionMonitorTest { return packageInfo; } - private static PackageInfo buildPackageInfo(boolean hasSystemPermission, int uid, int userId) { + private static PackageInfo buildPackageInfo(boolean hasSystemPermission, int uid, + UserHandle user) { final PackageInfo pkgInfo; if (hasSystemPermission) { pkgInfo = systemPackageInfoWithPermissions( @@ -192,7 +190,7 @@ public class PermissionMonitorTest { } else { pkgInfo = packageInfoWithPermissions(REQUESTED_PERMISSION_GRANTED, new String[] {}, ""); } - pkgInfo.applicationInfo.uid = UserHandle.getUid(userId, UserHandle.getAppId(uid)); + pkgInfo.applicationInfo.uid = UserHandle.getUid(user, UserHandle.getAppId(uid)); return pkgInfo; } @@ -382,8 +380,8 @@ public class PermissionMonitorTest { }).when(mockNetd).networkClearPermissionForUser(any(int[].class)); } - public void expectPermission(Boolean permission, int[] users, int[] apps) { - for (final int user : users) { + public void expectPermission(Boolean permission, UserHandle[] users, int[] apps) { + for (final UserHandle user : users) { for (final int app : apps) { final int uid = UserHandle.getUid(user, app); if (!mApps.containsKey(uid)) { @@ -396,8 +394,8 @@ public class PermissionMonitorTest { } } - public void expectNoPermission(int[] users, int[] apps) { - for (final int user : users) { + public void expectNoPermission(UserHandle[] users, int[] apps) { + for (final UserHandle user : users) { for (final int app : apps) { final int uid = UserHandle.getUid(user, app); if (mApps.containsKey(uid)) { @@ -425,46 +423,48 @@ public class PermissionMonitorTest { // Add SYSTEM_PACKAGE2, expect only have network permission. mPermissionMonitor.onUserAdded(MOCK_USER1); - addPackageForUsers(new int[]{MOCK_USER1}, SYSTEM_PACKAGE2, SYSTEM_UID); - mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1}, new int[]{SYSTEM_UID}); + addPackageForUsers(new UserHandle[]{MOCK_USER1}, SYSTEM_PACKAGE2, SYSTEM_UID); + mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER1}, new int[]{SYSTEM_UID}); // Add SYSTEM_PACKAGE1, expect permission escalate. - addPackageForUsers(new int[]{MOCK_USER1}, SYSTEM_PACKAGE1, SYSTEM_UID); - mNetdMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1}, new int[]{SYSTEM_UID}); + addPackageForUsers(new UserHandle[]{MOCK_USER1}, SYSTEM_PACKAGE1, SYSTEM_UID); + mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1}, new int[]{SYSTEM_UID}); mPermissionMonitor.onUserAdded(MOCK_USER2); - mNetdMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1, MOCK_USER2}, + mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1, MOCK_USER2}, new int[]{SYSTEM_UID}); - addPackageForUsers(new int[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_UID1); - mNetdMonitor.expectPermission(SYSTEM, new int[]{MOCK_USER1, MOCK_USER2}, + addPackageForUsers(new UserHandle[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_UID1); + mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1, MOCK_USER2}, new int[]{SYSTEM_UID}); - mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1, MOCK_USER2}, + mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER1, MOCK_USER2}, new int[]{MOCK_UID1}); // Remove MOCK_UID1, expect no permission left for all user. mPermissionMonitor.onPackageRemoved(MOCK_PACKAGE1, MOCK_UID1); - removePackageForUsers(new int[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_UID1); - mNetdMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2}, new int[]{MOCK_UID1}); + removePackageForUsers(new UserHandle[]{MOCK_USER1, MOCK_USER2}, MOCK_PACKAGE1, MOCK_UID1); + mNetdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1, MOCK_USER2}, + new int[]{MOCK_UID1}); // Remove SYSTEM_PACKAGE1, expect permission downgrade. when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(new String[]{SYSTEM_PACKAGE2}); - removePackageForUsers(new int[]{MOCK_USER1, MOCK_USER2}, SYSTEM_PACKAGE1, SYSTEM_UID); - mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER1, MOCK_USER2}, + removePackageForUsers(new UserHandle[]{MOCK_USER1, MOCK_USER2}, + SYSTEM_PACKAGE1, SYSTEM_UID); + mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER1, MOCK_USER2}, new int[]{SYSTEM_UID}); mPermissionMonitor.onUserRemoved(MOCK_USER1); - mNetdMonitor.expectPermission(NETWORK, new int[]{MOCK_USER2}, new int[]{SYSTEM_UID}); + mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER2}, new int[]{SYSTEM_UID}); // Remove all packages, expect no permission left. when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(new String[]{}); - removePackageForUsers(new int[]{MOCK_USER2}, SYSTEM_PACKAGE2, SYSTEM_UID); - mNetdMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2}, + removePackageForUsers(new UserHandle[]{MOCK_USER2}, SYSTEM_PACKAGE2, SYSTEM_UID); + mNetdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1, MOCK_USER2}, new int[]{SYSTEM_UID, MOCK_UID1}); // Remove last user, expect no redundant clearPermission is invoked. mPermissionMonitor.onUserRemoved(MOCK_USER2); - mNetdMonitor.expectNoPermission(new int[]{MOCK_USER1, MOCK_USER2}, + mNetdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1, MOCK_USER2}, new int[]{SYSTEM_UID, MOCK_UID1}); } @@ -548,14 +548,14 @@ public class PermissionMonitorTest { // Normal package add/remove operations will trigger multiple intent for uids corresponding to // each user. To simulate generic package operations, the onPackageAdded/Removed will need to be // called multiple times with the uid corresponding to each user. - private void addPackageForUsers(int[] users, String packageName, int uid) { - for (final int user : users) { + private void addPackageForUsers(UserHandle[] users, String packageName, int uid) { + for (final UserHandle user : users) { mPermissionMonitor.onPackageAdded(packageName, UserHandle.getUid(user, uid)); } } - private void removePackageForUsers(int[] users, String packageName, int uid) { - for (final int user : users) { + private void removePackageForUsers(UserHandle[] users, String packageName, int uid) { + for (final UserHandle user : users) { mPermissionMonitor.onPackageRemoved(packageName, UserHandle.getUid(user, uid)); } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java index faa8b234cf51..e7154802f1f2 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectedStateTest.java @@ -34,8 +34,10 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import android.net.LinkProperties; +import android.net.NetworkAgent; import android.net.NetworkCapabilities; import androidx.test.filters.SmallTest; @@ -73,6 +75,11 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection } @Test + public void testEnterStateDoesNotCancelSafemodeAlarm() { + verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */); + } + + @Test public void testNullNetworkDoesNotTriggerDisconnect() throws Exception { mGatewayConnection .getUnderlyingNetworkTrackerCallback() @@ -121,7 +128,24 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection } @Test + public void testMigratedTransformsAreApplied() throws Exception { + getChildSessionCallback() + .onIpSecTransformsMigrated(makeDummyIpSecTransform(), makeDummyIpSecTransform()); + mTestLooper.dispatchAll(); + + for (int direction : new int[] {DIRECTION_IN, DIRECTION_OUT}) { + verify(mIpSecSvc) + .applyTunnelModeTransform( + eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), eq(direction), anyInt(), any()); + } + assertEquals(mGatewayConnection.mConnectedState, mGatewayConnection.getCurrentState()); + } + + @Test public void testChildOpenedRegistersNetwork() throws Exception { + // Verify scheduled but not canceled when entering ConnectedState + verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */); + final VcnChildSessionConfiguration mMockChildSessionConfig = mock(VcnChildSessionConfiguration.class); doReturn(Collections.singletonList(TEST_INTERNAL_ADDR)) @@ -163,24 +187,41 @@ public class VcnGatewayConnectionConnectedStateTest extends VcnGatewayConnection for (int cap : mConfig.getAllExposedCapabilities()) { assertTrue(nc.hasCapability(cap)); } + + // Now that Vcn Network is up, notify it as validated and verify the Safemode alarm is + // canceled + mGatewayConnection.mNetworkAgent.onValidationStatus( + NetworkAgent.VALIDATION_STATUS_VALID, null /* redirectUri */); + verify(mSafemodeTimeoutAlarm).cancel(); } @Test public void testChildSessionClosedTriggersDisconnect() throws Exception { + // Verify scheduled but not canceled when entering ConnectedState + verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */); + getChildSessionCallback().onClosed(); mTestLooper.dispatchAll(); assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */); + + // Since network never validated, verify mSafemodeTimeoutAlarm not canceled + verifyNoMoreInteractions(mSafemodeTimeoutAlarm); } @Test public void testIkeSessionClosedTriggersDisconnect() throws Exception { + // Verify scheduled but not canceled when entering ConnectedState + verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */); + getIkeSessionCallback().onClosed(); mTestLooper.dispatchAll(); assertEquals(mGatewayConnection.mRetryTimeoutState, mGatewayConnection.getCurrentState()); verify(mIkeSession).close(); - verifyTeardownTimeoutAlarmAndGetCallback(true /* expectCanceled */); + + // Since network never validated, verify mSafemodeTimeoutAlarm not canceled + verifyNoMoreInteractions(mSafemodeTimeoutAlarm); } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java index 760379d6649d..07282c920088 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionConnectingStateTest.java @@ -106,4 +106,9 @@ public class VcnGatewayConnectionConnectingStateTest extends VcnGatewayConnectio verify(mIkeSession).close(); verifyTeardownTimeoutAlarmAndGetCallback(true /* expectCanceled */); } + + @Test + public void testSafemodeTimeoutNotifiesCallback() { + verifySafemodeTimeoutNotifiesCallback(mGatewayConnection.mConnectingState); + } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java index 1a1787ac19d9..49ce54d4f684 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectedStateTest.java @@ -48,7 +48,8 @@ public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnect .createIpSecTunnelInterface( DUMMY_ADDR, DUMMY_ADDR, TEST_UNDERLYING_NETWORK_RECORD_1.network); mGatewayConnection.setTunnelInterface(tunnelIface); - mGatewayConnection.transitionTo(mGatewayConnection.mDisconnectedState); + + // Don't need to transition to DisconnectedState because it is the starting state mTestLooper.dispatchAll(); } @@ -78,6 +79,7 @@ public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnect mTestLooper.dispatchAll(); assertEquals(mGatewayConnection.mConnectingState, mGatewayConnection.getCurrentState()); + verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */); } @Test @@ -98,5 +100,6 @@ public class VcnGatewayConnectionDisconnectedStateTest extends VcnGatewayConnect assertNull(mGatewayConnection.getCurrentState()); verify(mIpSecSvc).deleteTunnelInterface(eq(TEST_IPSEC_TUNNEL_RESOURCE_ID), any()); + verifySafemodeTimeoutAlarmAndGetCallback(true /* expectCanceled */); } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java index b09f3a008c29..22eab2a1aa65 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionDisconnectingStateTest.java @@ -80,4 +80,9 @@ public class VcnGatewayConnectionDisconnectingStateTest extends VcnGatewayConnec assertEquals(mGatewayConnection.mDisconnectingState, mGatewayConnection.getCurrentState()); verifyTeardownTimeoutAlarmAndGetCallback(false /* expectCanceled */); } + + @Test + public void testSafemodeTimeoutNotifiesCallback() { + verifySafemodeTimeoutNotifiesCallback(mGatewayConnection.mDisconnectingState); + } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java index d37e92f661cc..6c2607586629 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionRetryTimeoutStateTest.java @@ -91,4 +91,9 @@ public class VcnGatewayConnectionRetryTimeoutStateTest extends VcnGatewayConnect assertEquals(mGatewayConnection.mConnectingState, mGatewayConnection.getCurrentState()); verifyRetryTimeoutAlarmAndGetCallback(mFirstRetryInterval, true /* expectCanceled */); } + + @Test + public void testSafemodeTimeoutNotifiesCallback() { + verifySafemodeTimeoutNotifiesCallback(mGatewayConnection.mRetryTimeoutState); + } } diff --git a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java index 0b44e03a5e5a..ac9ec0663df2 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java +++ b/tests/vcn/java/com/android/server/vcn/VcnGatewayConnectionTestBase.java @@ -20,6 +20,7 @@ import static com.android.server.vcn.UnderlyingNetworkTracker.UnderlyingNetworkR import static com.android.server.vcn.VcnGatewayConnection.VcnIkeSession; import static com.android.server.vcn.VcnTestUtils.setupIpSecManager; +import static org.junit.Assert.assertEquals; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.atLeastOnce; @@ -50,6 +51,7 @@ import android.os.ParcelUuid; import android.os.PowerManager; import android.os.test.TestLooper; +import com.android.internal.util.State; import com.android.internal.util.WakeupMessage; import com.android.server.IpSecService; import com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; @@ -108,6 +110,7 @@ public class VcnGatewayConnectionTestBase { @NonNull protected final WakeupMessage mTeardownTimeoutAlarm; @NonNull protected final WakeupMessage mDisconnectRequestAlarm; @NonNull protected final WakeupMessage mRetryTimeoutAlarm; + @NonNull protected final WakeupMessage mSafemodeTimeoutAlarm; @NonNull protected final IpSecService mIpSecSvc; @NonNull protected final ConnectivityManager mConnMgr; @@ -128,6 +131,7 @@ public class VcnGatewayConnectionTestBase { mTeardownTimeoutAlarm = mock(WakeupMessage.class); mDisconnectRequestAlarm = mock(WakeupMessage.class); mRetryTimeoutAlarm = mock(WakeupMessage.class); + mSafemodeTimeoutAlarm = mock(WakeupMessage.class); mIpSecSvc = mock(IpSecService.class); setupIpSecManager(mContext, mIpSecSvc); @@ -150,6 +154,7 @@ public class VcnGatewayConnectionTestBase { setUpWakeupMessage(mTeardownTimeoutAlarm, VcnGatewayConnection.TEARDOWN_TIMEOUT_ALARM); setUpWakeupMessage(mDisconnectRequestAlarm, VcnGatewayConnection.DISCONNECT_REQUEST_ALARM); setUpWakeupMessage(mRetryTimeoutAlarm, VcnGatewayConnection.RETRY_TIMEOUT_ALARM); + setUpWakeupMessage(mSafemodeTimeoutAlarm, VcnGatewayConnection.SAFEMODE_TIMEOUT_ALARM); doReturn(ELAPSED_REAL_TIME).when(mDeps).getElapsedRealTime(); } @@ -253,4 +258,24 @@ public class VcnGatewayConnectionTestBase { delayInMillis, expectCanceled); } + + protected Runnable verifySafemodeTimeoutAlarmAndGetCallback(boolean expectCanceled) { + return verifyWakeupMessageSetUpAndGetCallback( + VcnGatewayConnection.SAFEMODE_TIMEOUT_ALARM, + mSafemodeTimeoutAlarm, + TimeUnit.SECONDS.toMillis(VcnGatewayConnection.SAFEMODE_TIMEOUT_SECONDS), + expectCanceled); + } + + protected void verifySafemodeTimeoutNotifiesCallback(@NonNull State expectedState) { + // Safemode timer starts when VcnGatewayConnection exits DisconnectedState (the initial + // state) + final Runnable delayedEvent = + verifySafemodeTimeoutAlarmAndGetCallback(false /* expectCanceled */); + delayedEvent.run(); + mTestLooper.dispatchAll(); + + verify(mGatewayStatusCallback).onEnteredSafemode(); + assertEquals(expectedState, mGatewayConnection.getCurrentState()); + } } |