Merge "Add tethering client callbacks"
diff --git a/api/current.txt b/api/current.txt
index 80a7bb4..37b7ebc 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -28356,6 +28356,7 @@
     method public int getAudioChannelCount();
     method public int getAudioSampleRate();
     method public CharSequence getDescription();
+    method @Nullable public String getEncoding();
     method public android.os.Bundle getExtra();
     method public String getId();
     method public String getLanguage();
@@ -28383,6 +28384,7 @@
     method @NonNull public android.media.tv.TvTrackInfo.Builder setAudioDescription(boolean);
     method public android.media.tv.TvTrackInfo.Builder setAudioSampleRate(int);
     method public android.media.tv.TvTrackInfo.Builder setDescription(CharSequence);
+    method @NonNull public android.media.tv.TvTrackInfo.Builder setEncoding(@Nullable String);
     method @NonNull public android.media.tv.TvTrackInfo.Builder setEncrypted(boolean);
     method public android.media.tv.TvTrackInfo.Builder setExtra(android.os.Bundle);
     method @NonNull public android.media.tv.TvTrackInfo.Builder setHardOfHearing(boolean);
@@ -69783,6 +69785,7 @@
     method public static java.time.chrono.JapaneseEra[] values();
     field public static final java.time.chrono.JapaneseEra HEISEI;
     field public static final java.time.chrono.JapaneseEra MEIJI;
+    field public static final java.time.chrono.JapaneseEra REIWA;
     field public static final java.time.chrono.JapaneseEra SHOWA;
     field public static final java.time.chrono.JapaneseEra TAISHO;
   }
diff --git a/api/lint-baseline.txt b/api/lint-baseline.txt
index 5c31f41..b14a12f 100644
--- a/api/lint-baseline.txt
+++ b/api/lint-baseline.txt
@@ -537,6 +537,9 @@
     
 MissingNullability: android.icu.util.VersionInfo#UNICODE_13_0:
     Missing nullability on field `UNICODE_13_0` in class `class android.icu.util.VersionInfo`
+    
+MissingNullability: java.time.chrono.JapaneseEra#REIWA:
+    Missing nullability on field `REIWA` in class `class java.time.chrono.JapaneseEra`
 
 
 RequiresPermission: android.accounts.AccountManager#getAccountsByTypeAndFeatures(String, String[], android.accounts.AccountManagerCallback<android.accounts.Account[]>, android.os.Handler):
diff --git a/api/system-current.txt b/api/system-current.txt
index 5463cba..971ea08 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3350,8 +3350,14 @@
     method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void setCurrentFunctions(long);
     field @RequiresPermission(android.Manifest.permission.MANAGE_USB) public static final String ACTION_USB_PORT_CHANGED = "android.hardware.usb.action.USB_PORT_CHANGED";
     field public static final String ACTION_USB_STATE = "android.hardware.usb.action.USB_STATE";
+    field public static final long FUNCTION_ACCESSORY = 2L; // 0x2L
+    field public static final long FUNCTION_ADB = 1L; // 0x1L
+    field public static final long FUNCTION_AUDIO_SOURCE = 64L; // 0x40L
+    field public static final long FUNCTION_MIDI = 8L; // 0x8L
+    field public static final long FUNCTION_MTP = 4L; // 0x4L
     field public static final long FUNCTION_NCM = 1024L; // 0x400L
     field public static final long FUNCTION_NONE = 0L; // 0x0L
+    field public static final long FUNCTION_PTP = 16L; // 0x10L
     field public static final long FUNCTION_RNDIS = 32L; // 0x20L
     field public static final String USB_CONFIGURED = "configured";
     field public static final String USB_CONNECTED = "connected";
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
new file mode 100644
index 0000000..844e72e
--- /dev/null
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -0,0 +1,428 @@
+/*
+ * Copyright (C) 2019 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.app;
+import android.annotation.NonNull;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * LRU cache that's invalidated when an opaque value in a property changes. Self-synchronizing,
+ * but doesn't hold a lock across data fetches on query misses.
+ *
+ * The intended use case is caching frequently-read, seldom-changed information normally
+ * retrieved across interprocess communication. Imagine that you've written a user birthday
+ * information daemon called "birthdayd" that exposes an {@code IUserBirthdayService} interface
+ * over binder. That binder interface looks something like this:
+ *
+ * <pre>
+ * parcelable Birthday {
+ *   int month;
+ *   int day;
+ * }
+ * interface IUserBirthdayService {
+ *   Birthday getUserBirthday(int userId);
+ * }
+ * </pre>
+ *
+ * Suppose the service implementation itself looks like this...
+ *
+ * <pre>
+ * public class UserBirthdayServiceImpl implements IUserBirthdayService {
+ *   private final HashMap<Integer, Birthday> mUidToBirthday;
+ *   @Override
+ *   public synchronized Birthday getUserBirthday(int userId) {
+ *     return mUidToBirthday.get(userId);
+ *   }
+ *   private synchronized void updateBirthdays(Map<Integer, Birthday> uidToBirthday) {
+ *     mUidToBirthday.clear();
+ *     mUidToBirthday.putAll(uidToBirthday);
+ *   }
+ * }
+ * </pre>
+ *
+ * ... and we have a client in frameworks (loaded into every app process) that looks
+ * like this:
+ *
+ * <pre>
+ * public class ActivityThread {
+ *   ...
+ *   public Birthday getUserBirthday(int userId) {
+ *     return GetService("birthdayd").getUserBirthday(userId);
+ *   }
+ *   ...
+ * }
+ * </pre>
+ *
+ * With this code, every time an app calls {@code getUserBirthday(uid)}, we make a binder call
+ * to the birthdayd process and consult its database of birthdays. If we query user birthdays
+ * frequently, we do a lot of work that we don't have to do, since user birthdays
+ * change infrequently.
+ *
+ * PropertyInvalidatedCache is part of a pattern for optimizing this kind of
+ * information-querying code. Using {@code PropertyInvalidatedCache}, you'd write the client
+ * this way:
+ *
+ * <pre>
+ * public class ActivityThread {
+ *   ...
+ *   private static final int BDAY_CACHE_MAX = 8;  // Maximum birthdays to cache
+ *   private static final String BDAY_CACHE_KEY = "cache_key.birthdayd";
+ *   private final PropertyInvalidatedCache<Integer, Birthday> mBirthdayCache = new
+ *     PropertyInvalidatedCache<Integer, Birthday>(BDAY_CACHE_MAX, BDAY_CACHE_KEY) {
+ *       @Override
+ *       protected Birthday recompute(Integer userId) {
+ *         return GetService("birthdayd").getUserBirthday(userId);
+ *       }
+ *     };
+ *   public void disableUserBirthdayCache() {
+ *     mBirthdayCache.disableLocal();
+ *   }
+ *   public void invalidateUserBirthdayCache() {
+ *     mBirthdayCache.invalidateCache();
+ *   }
+ *   public Birthday getUserBirthday(int userId) {
+ *     return mBirthdayCache.query(userId);
+ *   }
+ *   ...
+ * }
+ * </pre>
+ *
+ * With this cache, clients perform a binder call to birthdayd if asking for a user's birthday
+ * for the first time; on subsequent queries, we return the already-known Birthday object.
+ *
+ * User birthdays do occasionally change, so we have to modify the server to invalidate this
+ * cache when necessary. That invalidation code looks like this:
+ *
+ * <pre>
+ * public class UserBirthdayServiceImpl {
+ *   ...
+ *   public UserBirthdayServiceImpl() {
+ *     ...
+ *     ActivityThread.currentActivityThread().disableUserBirthdayCache();
+ *     ActivityThread.currentActivityThread().invalidateUserBirthdayCache();
+ *   }
+ *
+ *   private synchronized void updateBirthdays(Map<Integer, Birthday> uidToBirthday) {
+ *     mUidToBirthday.clear();
+ *     mUidToBirthday.putAll(uidToBirthday);
+ *     ActivityThread.currentActivityThread().invalidateUserBirthdayCache();
+ *   }
+ *   ...
+ * }
+ * </pre>
+ *
+ * The call to {@code PropertyInvalidatedCache.invalidateCache()} guarantees that all clients
+ * will re-fetch birthdays from binder during consequent calls to
+ * {@code ActivityThread.getUserBirthday()}. Because the invalidate call happens with the lock
+ * held, we maintain consistency between different client views of the birthday state. The use
+ * of PropertyInvalidatedCache in this idiomatic way introduces no new race conditions.
+ *
+ * PropertyInvalidatedCache has a few other features for doing things like incremental
+ * enhancement of cached values and invalidation of multiple caches (that all share the same
+ * property key) at once.
+ *
+ * {@code BDAY_CACHE_KEY} is the name of a property that we set to an opaque unique value each
+ * time we update the cache. SELinux configuration must allow everyone to read this property
+ * and it must allow any process that needs to invalidate the cache (here, birthdayd) to write
+ * the property. (These properties conventionally begin with the "cache_key." prefix.)
+ *
+ * The {@code UserBirthdayServiceImpl} constructor calls {@code disableUserBirthdayCache()} so
+ * that calls to {@code getUserBirthday} from inside birthdayd don't go through the cache. In
+ * this local case, there's no IPC, so use of the cache is (depending on exact
+ * circumstance) unnecessary.
+ *
+ * @param <Query> The class used to index cache entries: must be hashable and comparable
+ * @param <Result> The class holding cache entries; use a boxed primitive if possible
+ *
+ * {@hide}
+ */
+public abstract class PropertyInvalidatedCache<Query, Result> {
+    private static final long NONCE_UNSET = 0;
+    private static final long NONCE_DISABLED = -1;
+
+    private static final String TAG = "PropertyInvalidatedCache";
+    private static final boolean DEBUG = false;
+    private static final boolean ENABLE = true;
+
+    private final Object mLock = new Object();
+
+    /**
+     * Name of the property that holds the unique value that we use to invalidate the cache.
+     */
+    private final String mPropertyName;
+
+    /**
+     * Handle to the {@code mPropertyName} property, transitioning to non-{@code null} once the
+     * property exists on the system.
+     */
+    private volatile SystemProperties.Handle mPropertyHandle;
+
+    @GuardedBy("mLock")
+    private final LinkedHashMap<Query, Result> mCache;
+
+    /**
+     * The last value of the {@code mPropertyHandle} that we observed.
+     */
+    @GuardedBy("mLock")
+    private long mLastSeenNonce = NONCE_UNSET;
+
+    /**
+     * Whether we've disabled the cache in this process.
+     */
+    private boolean mDisabled = false;
+
+    /**
+     * Make a new property invalidated cache.
+     *
+     * @param maxEntries Maximum number of entries to cache; LRU discard
+     * @param propertyName Name of the system property holding the cache invalidation nonce
+     */
+    public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName) {
+        mPropertyName = propertyName;
+        mCache = new LinkedHashMap<Query, Result>(
+            2 /* start small */,
+            0.75f /* default load factor */,
+            true /* LRU access order */) {
+                @Override
+                protected boolean removeEldestEntry(Map.Entry eldest) {
+                    return size() > maxEntries;
+                }
+            };
+    }
+
+    /**
+     * Forget all cached values.
+     */
+    public final void clear() {
+        synchronized (mLock) {
+            mCache.clear();
+        }
+    }
+
+    /**
+     * Fetch a result from scratch in case it's not in the cache at all.  Called unlocked: may
+     * block. If this function returns null, the result of the cache query is null. There is no
+     * "negative cache" in the query: we don't cache null results at all.
+     */
+    protected abstract Result recompute(Query query);
+
+    /**
+     * Make result up-to-date on a cache hit.  Called unlocked;
+     * may block.
+     *
+     * Return either 1) oldResult itself (the same object, by reference equality), in which
+     * case we just return oldResult as the result of the cache query, 2) a new object, which
+     * replaces oldResult in the cache and which we return as the result of the cache query
+     * after performing another property read to make sure that the result hasn't changed in
+     * the meantime (if the nonce has changed in the meantime, we drop the cache and try the
+     * whole query again), or 3) null, which causes the old value to be removed from the cache
+     * and null to be returned as the result of the cache query.
+     */
+    protected Result refresh(Result oldResult, Query query) {
+        return oldResult;
+    }
+
+    private long getCurrentNonce() {
+        SystemProperties.Handle handle = mPropertyHandle;
+        if (handle == null) {
+            handle = SystemProperties.find(mPropertyName);
+            if (handle == null) {
+                return NONCE_UNSET;
+            }
+            mPropertyHandle = handle;
+        }
+        return handle.getLong(NONCE_UNSET);
+    }
+
+    /**
+     * Disable the use of this cache in this process.
+     */
+    public final void disableLocal() {
+        synchronized (mLock) {
+            mDisabled = true;
+            mCache.clear();
+        }
+    }
+
+    /**
+     * Return whether the cache is disabled in this process.
+     */
+    public final boolean isDisabledLocal() {
+        return mDisabled;
+    }
+
+    /**
+     * Get a value from the cache or recompute it.
+     */
+    public Result query(Query query) {
+        // Let access to mDisabled race: it's atomic anyway.
+        long currentNonce = (ENABLE && !mDisabled) ? getCurrentNonce() : NONCE_DISABLED;
+        for (;;) {
+            if (currentNonce == NONCE_DISABLED || currentNonce == NONCE_UNSET) {
+                if (DEBUG) {
+                    Log.d(TAG,
+                            String.format("cache %s for %s",
+                                currentNonce == NONCE_DISABLED ? "disabled" : "unset",
+                                query));
+                }
+                return recompute(query);
+            }
+            final Result cachedResult;
+            synchronized (mLock) {
+                if (currentNonce == mLastSeenNonce) {
+                    cachedResult = mCache.get(query);
+                } else {
+                    if (DEBUG) {
+                        Log.d(TAG,
+                                String.format("clearing cache because nonce changed [%s] -> [%s]",
+                                        mLastSeenNonce, currentNonce));
+                    }
+                    mCache.clear();
+                    mLastSeenNonce = currentNonce;
+                    cachedResult = null;
+                }
+            }
+            // Cache hit --- but we're not quite done yet.  A value in the cache might need to
+            // be augmented in a "refresh" operation.  The refresh operation can combine the
+            // old and the new nonce values.  In order to make sure the new parts of the value
+            // are consistent with the old, possibly-reused parts, we check the property value
+            // again after the refresh and do the whole fetch again if the property invalidated
+            // us while we were refreshing.
+            if (cachedResult != null) {
+                final Result refreshedResult = refresh(cachedResult, query);
+                if (refreshedResult != cachedResult) {
+                    if (DEBUG) {
+                        Log.d(TAG, "cache refresh for " + query);
+                    }
+                    final long afterRefreshNonce = getCurrentNonce();
+                    if (currentNonce != afterRefreshNonce) {
+                        currentNonce = afterRefreshNonce;
+                        if (DEBUG) {
+                            Log.d(TAG, "restarting query because nonce changed in refresh");
+                        }
+                        continue;
+                    }
+                    synchronized (mLock) {
+                        if (currentNonce != mLastSeenNonce) {
+                            // Do nothing: cache is already out of date. Just return the value
+                            // we already have: there's no guarantee that the contents of mCache
+                            // won't become invalid as soon as we return.
+                        } else if (refreshedResult == null) {
+                            mCache.remove(query);
+                        } else {
+                            mCache.put(query, refreshedResult);
+                        }
+                    }
+                    return refreshedResult;
+                }
+                if (DEBUG) {
+                    Log.d(TAG, "cache hit for " + query);
+                }
+                return cachedResult;
+            }
+            // Cache miss: make the value from scratch.
+            if (DEBUG) {
+                Log.d(TAG, "cache miss for " + query);
+            }
+            final Result result = recompute(query);
+            synchronized (mLock) {
+                // If someone else invalidated the cache while we did the recomputation, don't
+                // update the cache with a potentially stale result.
+                if (mLastSeenNonce == currentNonce && result != null) {
+                    mCache.put(query, result);
+                }
+            }
+            return result;
+        }
+    }
+
+    // Inner class avoids initialization in processes that don't do any invalidation
+    private static final class NoPreloadHolder {
+        private static final AtomicLong sNextNonce = new AtomicLong((new Random()).nextLong());
+        public static long next() {
+            return sNextNonce.getAndIncrement();
+        }
+    }
+
+    /**
+     * Non-static convenience version of disableSystemWide() for situations in which only a
+     * single PropertyInvalidatedCache is keyed on a particular property value.
+     *
+     * When multiple caches share a single property value, using an instance method on one of
+     * the cache objects to invalidate all of the cache objects becomes confusing and you should
+     * just use the static version of this function.
+     */
+    public final void disableSystemWide() {
+        disableSystemWide(mPropertyName);
+    }
+
+    /**
+     * Disable all caches system-wide that are keyed on {@var name}. This
+     * function is synchronous: caches are invalidated and disabled upon return.
+     *
+     * @param name Name of the cache-key property to invalidate
+     */
+    public static void disableSystemWide(@NonNull String name) {
+        SystemProperties.set(name, Long.toString(NONCE_DISABLED));
+    }
+
+    /**
+     * Non-static convenience version of invalidateCache() for situations in which only a single
+     * PropertyInvalidatedCache is keyed on a particular property value.
+     */
+    public final void invalidateCache() {
+        invalidateCache(mPropertyName);
+    }
+
+    /**
+     * Invalidate PropertyInvalidatedCache caches in all processes that are keyed on
+     * {@var name}. This function is synchronous: caches are invalidated upon return.
+     *
+     * @param name Name of the cache-key property to invalidate
+     */
+    public static void invalidateCache(@NonNull String name) {
+        // There's no race here: we don't require that values strictly increase, but instead
+        // only that each is unique in a single runtime-restart session.
+        final long nonce = SystemProperties.getLong(name, NONCE_UNSET);
+        if (nonce == NONCE_DISABLED) {
+            if (DEBUG) {
+                Log.d(TAG, "refusing to invalidate disabled cache: " + name);
+            }
+            return;
+        }
+        long newValue;
+        do {
+            newValue = NoPreloadHolder.next();
+        } while (newValue == NONCE_UNSET || newValue == NONCE_DISABLED);
+        final String newValueString = Long.toString(newValue);
+        if (DEBUG) {
+            Log.d(TAG,
+                    String.format("invalidating cache [%s]: [%s] -> [%s]",
+                            name,
+                            nonce,
+                            newValueString));
+        }
+        SystemProperties.set(name, newValueString);
+    }
+}
diff --git a/core/java/android/app/compat/ChangeIdStateCache.java b/core/java/android/app/compat/ChangeIdStateCache.java
new file mode 100644
index 0000000..9ef63f6
--- /dev/null
+++ b/core/java/android/app/compat/ChangeIdStateCache.java
@@ -0,0 +1,86 @@
+/*
+ * 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.app.compat;
+
+import android.app.PropertyInvalidatedCache;
+import android.content.Context;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+import com.android.internal.compat.IPlatformCompat;
+
+/**
+ * Handles caching of calls to {@link com.android.internal.compat.IPlatformCompat}
+ * @hide
+ */
+public final class ChangeIdStateCache
+        extends PropertyInvalidatedCache<ChangeIdStateQuery, Boolean> {
+    private static final String CACHE_KEY = "cache_key.is_compat_change_enabled";
+    private static final int MAX_ENTRIES = 20;
+    private static boolean sDisabled = false;
+
+    /** @hide */
+    public ChangeIdStateCache() {
+        super(MAX_ENTRIES, CACHE_KEY);
+    }
+
+    /**
+     * Disable cache.
+     *
+     * <p>Should only be used in unit tests.
+     * @hide
+     */
+    public static void disable() {
+        sDisabled = true;
+    }
+
+    /**
+     * Invalidate the cache.
+     *
+     * <p>Can only be called by the system server process.
+     * @hide
+     */
+    public static void invalidate() {
+        if (!sDisabled) {
+            PropertyInvalidatedCache.invalidateCache(CACHE_KEY);
+        }
+    }
+
+    @Override
+    protected Boolean recompute(ChangeIdStateQuery query) {
+        IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
+                ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+        final long token = Binder.clearCallingIdentity();
+        try {
+            if (query.type == ChangeIdStateQuery.QUERY_BY_PACKAGE_NAME) {
+                return platformCompat.isChangeEnabledByPackageName(query.changeId,
+                                                                   query.packageName,
+                                                                   query.userId);
+            } else if (query.type == ChangeIdStateQuery.QUERY_BY_UID) {
+                return platformCompat.isChangeEnabledByUid(query.changeId, query.uid);
+            } else {
+                throw new IllegalArgumentException("Invalid query type: " + query.type);
+            }
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        throw new IllegalStateException("Could not recompute value!");
+    }
+}
diff --git a/core/java/android/app/compat/ChangeIdStateQuery.java b/core/java/android/app/compat/ChangeIdStateQuery.java
new file mode 100644
index 0000000..2c4c120
--- /dev/null
+++ b/core/java/android/app/compat/ChangeIdStateQuery.java
@@ -0,0 +1,87 @@
+/*
+ * 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.app.compat;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+
+/**
+ * A key type for caching calls to {@link com.android.internal.compat.IPlatformCompat}
+ *
+ * <p>For {@link com.android.internal.compat.IPlatformCompat#isChangeEnabledByPackageName}
+ * and {@link com.android.internal.compat.IPlatformCompat#isChangeEnabledByUid}
+ *
+ * @hide
+ */
+final class ChangeIdStateQuery {
+
+    static final int QUERY_BY_PACKAGE_NAME = 0;
+    static final int QUERY_BY_UID = 1;
+    @IntDef({QUERY_BY_PACKAGE_NAME, QUERY_BY_UID})
+    @Retention(RetentionPolicy.SOURCE)
+    @interface QueryType {}
+
+    public @QueryType int type;
+    public long changeId;
+    public String packageName;
+    public int uid;
+    public int userId;
+
+    private ChangeIdStateQuery(@QueryType int type, long changeId, String packageName,
+                               int uid, int userId) {
+        this.type = type;
+        this.changeId = changeId;
+        this.packageName = packageName;
+        this.uid = uid;
+        this.userId = userId;
+    }
+
+    static ChangeIdStateQuery byPackageName(long changeId, @NonNull String packageName,
+                                            int userId) {
+        return new ChangeIdStateQuery(QUERY_BY_PACKAGE_NAME, changeId, packageName, 0, userId);
+    }
+
+    static ChangeIdStateQuery byUid(long changeId, int uid) {
+        return new ChangeIdStateQuery(QUERY_BY_UID, changeId, null, uid, 0);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+        if ((other == null) || !(other instanceof ChangeIdStateQuery)) {
+            return false;
+        }
+        final ChangeIdStateQuery that = (ChangeIdStateQuery) other;
+        return this.type == that.type
+            && this.changeId == that.changeId
+            && Objects.equals(this.packageName, that.packageName)
+            && this.uid == that.uid
+            && this.userId == that.userId;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(type, changeId, packageName, uid, userId);
+    }
+}
diff --git a/core/java/android/app/compat/CompatChanges.java b/core/java/android/app/compat/CompatChanges.java
index e289a27..0d5e45f 100644
--- a/core/java/android/app/compat/CompatChanges.java
+++ b/core/java/android/app/compat/CompatChanges.java
@@ -19,14 +19,8 @@
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.compat.Compatibility;
-import android.content.Context;
-import android.os.Binder;
-import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.UserHandle;
 
-import com.android.internal.compat.IPlatformCompat;
-
 /**
  * CompatChanges APIs - to be used by platform code only (including mainline
  * modules).
@@ -35,6 +29,7 @@
  */
 @SystemApi
 public final class CompatChanges {
+    private static final ChangeIdStateCache QUERY_CACHE = new ChangeIdStateCache();
     private CompatChanges() {}
 
     /**
@@ -69,17 +64,8 @@
      */
     public static boolean isChangeEnabled(long changeId, @NonNull String packageName,
             @NonNull UserHandle user) {
-        IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
-                ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
-        final long token = Binder.clearCallingIdentity();
-        try {
-            return platformCompat.isChangeEnabledByPackageName(changeId, packageName,
-                    user.getIdentifier());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
+        return QUERY_CACHE.query(ChangeIdStateQuery.byPackageName(changeId, packageName,
+                                                           user.getIdentifier()));
     }
 
     /**
@@ -101,15 +87,7 @@
      * @return {@code true} if the change is enabled for the current app.
      */
     public static boolean isChangeEnabled(long changeId, int uid) {
-        IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
-                ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
-        final long token = Binder.clearCallingIdentity();
-        try {
-            return platformCompat.isChangeEnabledByUid(changeId, uid);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
+        return QUERY_CACHE.query(ChangeIdStateQuery.byUid(changeId, uid));
     }
+
 }
diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java
index 72eea84..7e8a7de 100644
--- a/core/java/android/app/job/JobInfo.java
+++ b/core/java/android/app/job/JobInfo.java
@@ -625,10 +625,6 @@
         return hasLateConstraint;
     }
 
-    private static boolean kindofEqualsBundle(BaseBundle a, BaseBundle b) {
-        return (a == b) || (a != null && a.kindofEquals(b));
-    }
-
     @Override
     public boolean equals(Object o) {
         if (!(o instanceof JobInfo)) {
@@ -639,11 +635,11 @@
             return false;
         }
         // XXX won't be correct if one is parcelled and the other not.
-        if (!kindofEqualsBundle(extras, j.extras)) {
+        if (!BaseBundle.kindofEquals(extras, j.extras)) {
             return false;
         }
         // XXX won't be correct if one is parcelled and the other not.
-        if (!kindofEqualsBundle(transientExtras, j.transientExtras)) {
+        if (!BaseBundle.kindofEquals(transientExtras, j.transientExtras)) {
             return false;
         }
         // XXX for now we consider two different clip data objects to be different,
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index d859a3af..8b6840b 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -6632,7 +6632,7 @@
                     this.mClipData = new ClipData(o.mClipData);
                 }
             } else {
-                if (o.mExtras != null && !o.mExtras.maybeIsEmpty()) {
+                if (o.mExtras != null && !o.mExtras.isDefinitelyEmpty()) {
                     this.mExtras = Bundle.STRIPPED;
                 }
 
diff --git a/core/java/android/debug/AdbManager.java b/core/java/android/debug/AdbManager.java
index 0a76bed..7714dd8 100644
--- a/core/java/android/debug/AdbManager.java
+++ b/core/java/android/debug/AdbManager.java
@@ -31,6 +31,114 @@
 public class AdbManager {
     private static final String TAG = "AdbManager";
 
+    /**
+     * Action indicating the state change of wireless debugging. Can be either
+     *   STATUS_CONNECTED
+     *   STATUS_DISCONNECTED
+     *
+     * @hide
+     */
+    public static final String WIRELESS_DEBUG_STATE_CHANGED_ACTION =
+            "com.android.server.adb.WIRELESS_DEBUG_STATUS";
+
+    /**
+     * Contains the list of paired devices.
+     *
+     * @hide
+     */
+    public static final String WIRELESS_DEBUG_PAIRED_DEVICES_ACTION =
+            "com.android.server.adb.WIRELESS_DEBUG_PAIRED_DEVICES";
+
+    /**
+     * Action indicating the status of a pairing. Can be either
+     *   WIRELESS_STATUS_FAIL
+     *   WIRELESS_STATUS_SUCCESS
+     *   WIRELESS_STATUS_CANCELLED
+     *   WIRELESS_STATUS_PAIRING_CODE
+     *   WIRELESS_STATUS_CONNECTED
+     *
+     * @hide
+     */
+    public static final String WIRELESS_DEBUG_PAIRING_RESULT_ACTION =
+            "com.android.server.adb.WIRELESS_DEBUG_PAIRING_RESULT";
+
+    /**
+     * Extra containing the PairDevice map of paired/pairing devices.
+     *
+     * @hide
+     */
+    public static final String WIRELESS_DEVICES_EXTRA = "devices_map";
+
+    /**
+     * The status of the pairing/unpairing.
+     *
+     * @hide
+     */
+    public static final String WIRELESS_STATUS_EXTRA = "status";
+
+    /**
+     * The PairDevice.
+     *
+     * @hide
+     */
+    public static final String WIRELESS_PAIR_DEVICE_EXTRA = "pair_device";
+
+    /**
+     * The six-digit pairing code.
+     *
+     * @hide
+     */
+    public static final String WIRELESS_PAIRING_CODE_EXTRA = "pairing_code";
+
+    /**
+     * The adb connection/pairing port that was opened.
+     *
+     * @hide
+     */
+    public static final String WIRELESS_DEBUG_PORT_EXTRA = "adb_port";
+
+    /**
+     * Status indicating the pairing/unpairing failed.
+     *
+     * @hide
+     */
+    public static final int WIRELESS_STATUS_FAIL = 0;
+
+    /**
+     * Status indicating the pairing/unpairing succeeded.
+     *
+     * @hide
+     */
+    public static final int WIRELESS_STATUS_SUCCESS = 1;
+
+    /**
+     * Status indicating the pairing/unpairing was cancelled.
+     *
+     * @hide
+     */
+    public static final int WIRELESS_STATUS_CANCELLED = 2;
+
+    /**
+     * Status indicating the pairing code for pairing.
+     *
+     * @hide
+     */
+    public static final int WIRELESS_STATUS_PAIRING_CODE = 3;
+
+    /**
+     * Status indicating wireless debugging is connected.
+     *
+     * @hide
+     */
+    public static final int WIRELESS_STATUS_CONNECTED = 4;
+
+    /**
+     * Status indicating wireless debugging is disconnected.
+     *
+     * @hide
+     */
+    public static final int WIRELESS_STATUS_DISCONNECTED = 5;
+
     private final Context mContext;
     private final IAdbManager mService;
 
diff --git a/core/java/android/debug/AdbManagerInternal.java b/core/java/android/debug/AdbManagerInternal.java
index 51eb7fc..0bd9f19 100644
--- a/core/java/android/debug/AdbManagerInternal.java
+++ b/core/java/android/debug/AdbManagerInternal.java
@@ -42,7 +42,7 @@
     /**
      * Returns {@code true} if ADB debugging is enabled.
      */
-    public abstract boolean isAdbEnabled();
+    public abstract boolean isAdbEnabled(byte transportType);
 
     /**
      * Returns the file that contains all of the ADB keys used by the device.
diff --git a/core/java/android/debug/AdbTransportType.aidl b/core/java/android/debug/AdbTransportType.aidl
new file mode 100644
index 0000000..6904615
--- /dev/null
+++ b/core/java/android/debug/AdbTransportType.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2019 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.debug;
+
+/** @hide */
+@Backing(type="byte")
+enum AdbTransportType {
+    USB,
+    WIFI,
+}
+
diff --git a/core/java/android/debug/IAdbManager.aidl b/core/java/android/debug/IAdbManager.aidl
index c48fc07..aea7633 100644
--- a/core/java/android/debug/IAdbManager.aidl
+++ b/core/java/android/debug/IAdbManager.aidl
@@ -43,6 +43,62 @@
     void clearDebuggingKeys();
 
     /**
+     * Allow ADB wireless debugging on the connected network. If {@code alwaysAllow}
+     * is {@code true}, add {@code bssid} to list of networks that the user has
+     * approved.
+     *
+     * @param alwaysAllow if true, add permanently to list of allowed networks
+     * @param bssid BSSID of the network
+     */
+    void allowWirelessDebugging(boolean alwaysAllow, String bssid);
+
+    /**
+     * Deny ADB wireless debugging on the connected network.
+     */
+    void denyWirelessDebugging();
+
+    /**
+     * Returns a Map<String, PairDevice> with the key fingerprint mapped to the device information.
+     */
+    Map getPairedDevices();
+
+    /**
+     * Unpair the device identified by the key fingerprint it uses.
+     *
+     * @param fingerprint fingerprint of the key the device is using.
+     */
+    void unpairDevice(String fingerprint);
+
+    /**
+     * Enables pairing by pairing code. The result of the enable will be sent via intent action
+     * {@link android.debug.AdbManager#WIRELESS_DEBUG_ENABLE_DISCOVER_ACTION}. Furthermore, the
+     * pairing code will also be sent in the intent as an extra
+     * @{link android.debug.AdbManager#WIRELESS_PAIRING_CODE_EXTRA}. Note that only one
+     * pairing method can be enabled at a time, either by pairing code, or by QR code.
+     */
+    void enablePairingByPairingCode();
+
+    /**
+     * Enables pairing by QR code. The result of the enable will be sent via intent action
+     * {@link android.debug.AdbManager#WIRELESS_DEBUG_ENABLE_DISCOVER_ACTION}. Note that only one
+     * pairing method can be enabled at a time, either by pairing code, or by QR code.
+     *
+     * @param serviceName The MDNS service name parsed from the QR code.
+     * @param password The password parsed from the QR code.
+     */
+    void enablePairingByQrCode(String serviceName, String password);
+
+    /**
+     * Returns the network port that adb wireless server is running on.
+     */
+    int getAdbWirelessPort();
+
+    /**
+     * Disables pairing.
+     */
+    void disablePairing();
+
+    /**
      * Returns true if device supports secure Adb over Wi-Fi.
      */
     boolean isAdbWifiSupported();
diff --git a/core/java/android/debug/IAdbTransport.aidl b/core/java/android/debug/IAdbTransport.aidl
index 77211fc93..f018813 100644
--- a/core/java/android/debug/IAdbTransport.aidl
+++ b/core/java/android/debug/IAdbTransport.aidl
@@ -16,7 +16,9 @@
 
 package android.debug;
 
+import android.debug.AdbTransportType;
+
 /** @hide */
 interface IAdbTransport {
-    void onAdbEnabled(boolean enabled);
+    void onAdbEnabled(boolean enabled, in AdbTransportType type);
 }
diff --git a/core/java/android/debug/PairDevice.java b/core/java/android/debug/PairDevice.java
new file mode 100644
index 0000000..2d5b446
--- /dev/null
+++ b/core/java/android/debug/PairDevice.java
@@ -0,0 +1,112 @@
+/*
+ * 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.debug;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.annotations.Immutable;
+import com.android.internal.util.Preconditions;
+
+/**
+ * Contains information about the client in an ADB connection.
+ * @hide
+ */
+@Immutable
+public class PairDevice implements Parcelable {
+    /**
+     * The human-readable name of the device.
+     */
+    @NonNull private final String mName;
+
+    /**
+     * The device's guid.
+     */
+    @NonNull private final String mGuid;
+
+    /**
+     * Indicates whether the device is currently connected to adbd.
+     */
+    private final boolean mConnected;
+
+    public PairDevice(@NonNull String name, @NonNull String guid, boolean connected) {
+        Preconditions.checkStringNotEmpty(name);
+        Preconditions.checkStringNotEmpty(guid);
+        mName = name;
+        mGuid = guid;
+        mConnected = connected;
+    }
+
+    /**
+     * @return the device name.
+     */
+    @NonNull
+    public String getDeviceName() {
+        return mName;
+    }
+
+    /**
+     * @return the device GUID.
+     */
+    @NonNull
+    public String getGuid() {
+        return mGuid;
+    }
+
+    /**
+     * @return the adb connection state of the device.
+     */
+    public boolean isConnected() {
+        return mConnected;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(mName);
+        dest.writeString(mGuid);
+        dest.writeBoolean(mConnected);
+    }
+
+    /**
+     * @return Human-readable info about the object.
+     */
+    @Override
+    public String toString() {
+        return "\n" + mName + "\n" + mGuid + "\n" + mConnected;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<PairDevice> CREATOR =
+            new Creator<PairDevice>() {
+                @Override
+                public PairDevice createFromParcel(Parcel source) {
+                    return new PairDevice(source.readString(), source.readString(),
+                            source.readBoolean());
+                }
+
+                @Override
+                public PairDevice[] newArray(int size) {
+                    return new PairDevice[size];
+                }
+            };
+}
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index b0d0b4c..f540bfb 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -18,6 +18,7 @@
 package android.hardware.usb;
 
 import android.Manifest;
+import android.annotation.LongDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresFeature;
@@ -337,12 +338,14 @@
      * Code for the mtp usb function. Passed as a mask into {@link #setCurrentFunctions(long)}
      * {@hide}
      */
+    @SystemApi
     public static final long FUNCTION_MTP = GadgetFunction.MTP;
 
     /**
      * Code for the ptp usb function. Passed as a mask into {@link #setCurrentFunctions(long)}
      * {@hide}
      */
+    @SystemApi
     public static final long FUNCTION_PTP = GadgetFunction.PTP;
 
     /**
@@ -356,24 +359,28 @@
      * Code for the midi usb function. Passed as a mask into {@link #setCurrentFunctions(long)}
      * {@hide}
      */
+    @SystemApi
     public static final long FUNCTION_MIDI = GadgetFunction.MIDI;
 
     /**
      * Code for the accessory usb function.
      * {@hide}
      */
+    @SystemApi
     public static final long FUNCTION_ACCESSORY = GadgetFunction.ACCESSORY;
 
     /**
      * Code for the audio source usb function.
      * {@hide}
      */
+    @SystemApi
     public static final long FUNCTION_AUDIO_SOURCE = GadgetFunction.AUDIO_SOURCE;
 
     /**
      * Code for the adb usb function.
      * {@hide}
      */
+    @SystemApi
     public static final long FUNCTION_ADB = GadgetFunction.ADB;
 
     /**
@@ -399,6 +406,20 @@
         FUNCTION_NAME_TO_CODE.put(UsbManager.USB_FUNCTION_NCM, FUNCTION_NCM);
     }
 
+    /** @hide */
+    @LongDef(flag = true, prefix = { "FUNCTION_" }, value = {
+            FUNCTION_NONE,
+            FUNCTION_MTP,
+            FUNCTION_PTP,
+            FUNCTION_RNDIS,
+            FUNCTION_MIDI,
+            FUNCTION_ACCESSORY,
+            FUNCTION_AUDIO_SOURCE,
+            FUNCTION_ADB,
+            FUNCTION_NCM,
+    })
+    public @interface UsbFunctionMode {}
+
     private final Context mContext;
     private final IUsbManager mService;
 
@@ -721,7 +742,7 @@
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.MANAGE_USB)
-    public void setCurrentFunctions(long functions) {
+    public void setCurrentFunctions(@UsbFunctionMode long functions) {
         try {
             mService.setCurrentFunctions(functions);
         } catch (RemoteException e) {
diff --git a/core/java/android/net/Ikev2VpnProfile.java b/core/java/android/net/Ikev2VpnProfile.java
index 42b4da1..f19a341 100644
--- a/core/java/android/net/Ikev2VpnProfile.java
+++ b/core/java/android/net/Ikev2VpnProfile.java
@@ -25,7 +25,10 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.os.Process;
 import android.security.Credentials;
+import android.security.KeyStore;
+import android.security.keystore.AndroidKeyStoreProvider;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.net.VpnProfile;
@@ -59,6 +62,11 @@
  *     Exchange, Version 2 (IKEv2)</a>
  */
 public final class Ikev2VpnProfile extends PlatformVpnProfile {
+    /** Prefix for when a Private Key is an alias to look for in KeyStore @hide */
+    public static final String PREFIX_KEYSTORE_ALIAS = "KEYSTORE_ALIAS:";
+    /** Prefix for when a Private Key is stored directly in the profile @hide */
+    public static final String PREFIX_INLINE = "INLINE:";
+
     private static final String MISSING_PARAM_MSG_TMPL = "Required parameter was not provided: %s";
     private static final String EMPTY_CERT = "";
 
@@ -339,7 +347,8 @@
                 break;
             case TYPE_IKEV2_IPSEC_RSA:
                 profile.ipsecUserCert = certificateToPemString(mUserCert);
-                profile.ipsecSecret = encodeForIpsecSecret(mRsaPrivateKey.getEncoded());
+                profile.ipsecSecret =
+                        PREFIX_INLINE + encodeForIpsecSecret(mRsaPrivateKey.getEncoded());
                 profile.ipsecCaCert =
                         mServerRootCaCert == null ? "" : certificateToPemString(mServerRootCaCert);
                 break;
@@ -360,6 +369,22 @@
     @NonNull
     public static Ikev2VpnProfile fromVpnProfile(@NonNull VpnProfile profile)
             throws IOException, GeneralSecurityException {
+        return fromVpnProfile(profile, null);
+    }
+
+    /**
+     * Builds the Ikev2VpnProfile from the given profile.
+     *
+     * @param profile the source VpnProfile to build from
+     * @param keyStore the Android Keystore instance to use to retrieve the private key, or null if
+     *     the private key is PEM-encoded into the profile.
+     * @return The IKEv2/IPsec VPN profile
+     * @hide
+     */
+    @NonNull
+    public static Ikev2VpnProfile fromVpnProfile(
+            @NonNull VpnProfile profile, @Nullable KeyStore keyStore)
+            throws IOException, GeneralSecurityException {
         final Builder builder = new Builder(profile.server, profile.ipsecIdentifier);
         builder.setProxy(profile.proxy);
         builder.setAllowedAlgorithms(profile.getAllowedAlgorithms());
@@ -378,8 +403,21 @@
                 builder.setAuthPsk(decodeFromIpsecSecret(profile.ipsecSecret));
                 break;
             case TYPE_IKEV2_IPSEC_RSA:
+                final PrivateKey key;
+                if (profile.ipsecSecret.startsWith(PREFIX_KEYSTORE_ALIAS)) {
+                    Objects.requireNonNull(keyStore, "Missing Keystore for aliased PrivateKey");
+
+                    final String alias =
+                            profile.ipsecSecret.substring(PREFIX_KEYSTORE_ALIAS.length());
+                    key = AndroidKeyStoreProvider.loadAndroidKeyStorePrivateKeyFromKeystore(
+                            keyStore, alias, Process.myUid());
+                } else if (profile.ipsecSecret.startsWith(PREFIX_INLINE)) {
+                    key = getPrivateKey(profile.ipsecSecret.substring(PREFIX_INLINE.length()));
+                } else {
+                    throw new IllegalArgumentException("Invalid RSA private key prefix");
+                }
+
                 final X509Certificate userCert = certificateFromPemString(profile.ipsecUserCert);
-                final PrivateKey key = getPrivateKey(profile.ipsecSecret);
                 final X509Certificate serverRootCa = certificateFromPemString(profile.ipsecCaCert);
                 builder.setAuthDigitalSignature(userCert, key, serverRootCa);
                 break;
@@ -391,6 +429,39 @@
     }
 
     /**
+     * Validates that the VpnProfile is acceptable for the purposes of an Ikev2VpnProfile.
+     *
+     * @hide
+     */
+    public static boolean isValidVpnProfile(@NonNull VpnProfile profile) {
+        if (profile.server.isEmpty() || profile.ipsecIdentifier.isEmpty()) {
+            return false;
+        }
+
+        switch (profile.type) {
+            case TYPE_IKEV2_IPSEC_USER_PASS:
+                if (profile.username.isEmpty() || profile.password.isEmpty()) {
+                    return false;
+                }
+                break;
+            case TYPE_IKEV2_IPSEC_PSK:
+                if (profile.ipsecSecret.isEmpty()) {
+                    return false;
+                }
+                break;
+            case TYPE_IKEV2_IPSEC_RSA:
+                if (profile.ipsecSecret.isEmpty() || profile.ipsecUserCert.isEmpty()) {
+                    return false;
+                }
+                break;
+            default:
+                return false;
+        }
+
+        return true;
+    }
+
+    /**
      * Converts a X509 Certificate to a PEM-formatted string.
      *
      * <p>Must be public due to runtime-package restrictions.
@@ -432,7 +503,6 @@
 
     /** @hide */
     @NonNull
-    @VisibleForTesting(visibility = Visibility.PRIVATE)
     public static String encodeForIpsecSecret(@NonNull byte[] secret) {
         checkNotNull(secret, MISSING_PARAM_MSG_TMPL, "secret");
 
diff --git a/core/java/android/net/VpnManager.java b/core/java/android/net/VpnManager.java
index f19ba0f..2041cfb 100644
--- a/core/java/android/net/VpnManager.java
+++ b/core/java/android/net/VpnManager.java
@@ -126,7 +126,11 @@
         return getIntentForConfirmation();
     }
 
-    /** Delete the VPN profile configuration that was provisioned by the calling app */
+    /**
+     * Delete the VPN profile configuration that was provisioned by the calling app
+     *
+     * @throws SecurityException if this would violate user settings
+     */
     public void deleteProvisionedVpnProfile() {
         try {
             mService.deleteVpnProfile(mContext.getOpPackageName());
diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java
index e6ad917..c2f6794 100644
--- a/core/java/android/os/BaseBundle.java
+++ b/core/java/android/os/BaseBundle.java
@@ -365,12 +365,16 @@
     }
 
     /**
+     * This method returns true when the parcel is 'definitely' empty.
+     * That is, it may return false for an empty parcel. But will never return true for a non-empty
+     * one.
+     *
      * @hide this should probably be the implementation of isEmpty().  To do that we
      * need to ensure we always use the special empty parcel form when the bundle is
      * empty.  (This may already be the case, but to be safe we'll do this later when
      * we aren't trying to stabilize.)
      */
-    public boolean maybeIsEmpty() {
+    public boolean isDefinitelyEmpty() {
         if (isParcelled()) {
             return isEmptyParcel();
         } else {
@@ -402,6 +406,9 @@
         if (other == null) {
             return false;
         }
+        if (isDefinitelyEmpty() && other.isDefinitelyEmpty()) {
+            return true;
+        }
         if (isParcelled() != other.isParcelled()) {
             // Big kind-of here!
             return false;
diff --git a/core/java/android/os/image/DynamicSystemManager.java b/core/java/android/os/image/DynamicSystemManager.java
index cbf531c..17851ad 100644
--- a/core/java/android/os/image/DynamicSystemManager.java
+++ b/core/java/android/os/image/DynamicSystemManager.java
@@ -19,6 +19,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
 import android.content.Context;
+import android.gsi.AvbPublicKey;
 import android.gsi.GsiProgress;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
@@ -85,6 +86,23 @@
                 throw new RuntimeException(e.toString());
             }
         }
+
+        /**
+         * Retrieve AVB public key from installing partition.
+         *
+         * @param dst           Output the AVB public key.
+         * @return              true on success, false if partition doesn't have a
+         *                      valid VBMeta block to retrieve the AVB key from.
+         */
+        @RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
+        public boolean getAvbPublicKey(AvbPublicKey dst) {
+            try {
+                return mService.getAvbPublicKey(dst);
+            } catch (RemoteException e) {
+                throw new RuntimeException(e.toString());
+            }
+        }
+
         /**
          * Finish write and make device to boot into the it after reboot.
          *
diff --git a/core/java/android/os/image/IDynamicSystemService.aidl b/core/java/android/os/image/IDynamicSystemService.aidl
index cc32f99..a1f9272 100644
--- a/core/java/android/os/image/IDynamicSystemService.aidl
+++ b/core/java/android/os/image/IDynamicSystemService.aidl
@@ -15,6 +15,7 @@
  */
 package android.os.image;
 
+import android.gsi.AvbPublicKey;
 import android.gsi.GsiProgress;
 
 /** {@hide} */
@@ -108,4 +109,13 @@
      * @return              true on success, false otherwise.
      */
     boolean submitFromAshmem(long bytes);
+
+    /**
+     * Retrieve AVB public key from installing partition.
+     *
+     * @param dst           Output the AVB public key.
+     * @return              true on success, false if partition doesn't have a
+     *                      valid VBMeta block to retrieve the AVB key from.
+     */
+    boolean getAvbPublicKey(out AvbPublicKey dst);
 }
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index 15b1d75..9c8ab0c 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -133,7 +133,7 @@
     void noteNetworkStatsEnabled();
     void noteDeviceIdleMode(int mode, String activeReason, int activeUid);
     void setBatteryState(int status, int health, int plugType, int level, int temp, int volt,
-            int chargeUAh, int chargeFullUAh);
+            int chargeUAh, int chargeFullUAh, long chargeTimeToFullSeconds);
     @UnsupportedAppUsage
     long getAwakeTimeBattery();
     long getAwakeTimePlugged();
diff --git a/core/java/com/android/internal/net/VpnProfile.java b/core/java/com/android/internal/net/VpnProfile.java
index bbae027..23b1ab5 100644
--- a/core/java/com/android/internal/net/VpnProfile.java
+++ b/core/java/com/android/internal/net/VpnProfile.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.net.Ikev2VpnProfile;
 import android.net.ProxyInfo;
 import android.os.Build;
 import android.os.Parcel;
@@ -332,15 +333,38 @@
         return builder.toString().getBytes(StandardCharsets.UTF_8);
     }
 
+    /** Checks if this profile specifies a LegacyVpn type. */
+    public static boolean isLegacyType(int type) {
+        switch (type) {
+            case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS: // fall through
+            case VpnProfile.TYPE_IKEV2_IPSEC_RSA: // fall through
+            case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
+                return false;
+            default:
+                return true;
+        }
+    }
+
+    private boolean isValidLockdownLegacyVpnProfile() {
+        return isLegacyType(type) && isServerAddressNumeric() && hasDns()
+                && areDnsAddressesNumeric();
+    }
+
+    private boolean isValidLockdownPlatformVpnProfile() {
+        return Ikev2VpnProfile.isValidVpnProfile(this);
+    }
+
     /**
-     * Tests if profile is valid for lockdown, which requires IPv4 address for both server and DNS.
-     * Server hostnames would require using DNS before connection.
+     * Tests if profile is valid for lockdown.
+     *
+     * <p>For LegacyVpn profiles, this requires an IPv4 address for both the server and DNS.
+     *
+     * <p>For PlatformVpn profiles, this requires a server, an identifier and the relevant fields to
+     * be non-null.
      */
     public boolean isValidLockdownProfile() {
         return isTypeValidForLockdown()
-                && isServerAddressNumeric()
-                && hasDns()
-                && areDnsAddressesNumeric();
+                && (isValidLockdownLegacyVpnProfile() || isValidLockdownPlatformVpnProfile());
     }
 
     /** Returns {@code true} if the VPN type is valid for lockdown. */
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 183c0fb..3d6f233 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -996,6 +996,8 @@
     private int mMinLearnedBatteryCapacity = -1;
     private int mMaxLearnedBatteryCapacity = -1;
 
+    private long mBatteryTimeToFullSeconds = -1;
+
     private long[] mCpuFreqs;
 
     @VisibleForTesting
@@ -12218,7 +12220,7 @@
     @GuardedBy("this")
     public void setBatteryStateLocked(final int status, final int health, final int plugType,
             final int level, /* not final */ int temp, final int volt, final int chargeUAh,
-            final int chargeFullUAh) {
+            final int chargeFullUAh, final long chargeTimeToFullSeconds) {
         // Temperature is encoded without the signed bit, so clamp any negative temperatures to 0.
         temp = Math.max(0, temp);
 
@@ -12421,6 +12423,8 @@
             mMinLearnedBatteryCapacity = Math.min(mMinLearnedBatteryCapacity, chargeFullUAh);
         }
         mMaxLearnedBatteryCapacity = Math.max(mMaxLearnedBatteryCapacity, chargeFullUAh);
+
+        mBatteryTimeToFullSeconds = chargeTimeToFullSeconds;
     }
 
     public static boolean isOnBattery(int plugType, int status) {
@@ -12570,19 +12574,10 @@
             // Not yet working.
             return -1;
         }
-        /* Broken
-        int curLevel = mCurrentBatteryLevel;
-        int plugLevel = mDischargePlugLevel;
-        if (plugLevel < 0 || curLevel < (plugLevel+1)) {
-            return -1;
+        if (mBatteryTimeToFullSeconds >= 0) {
+            return mBatteryTimeToFullSeconds * (1000 * 1000); // s to us
         }
-        long duration = computeBatteryRealtime(curTime, STATS_SINCE_UNPLUGGED);
-        if (duration < 1000*1000) {
-            return -1;
-        }
-        long usPerLevel = duration/(curLevel-plugLevel);
-        return usPerLevel * (100-curLevel);
-        */
+        // Else use algorithmic approach
         if (mChargeStepTracker.mNumStepDurations < 1) {
             return -1;
         }
@@ -12590,7 +12585,7 @@
         if (msPerLevel <= 0) {
             return -1;
         }
-        return (msPerLevel * (100-mCurrentBatteryLevel)) * 1000;
+        return (msPerLevel * (100 - mCurrentBatteryLevel)) * 1000;
     }
 
     /*@hide */
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index 16c0b56..13d0c5c 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -19,8 +19,6 @@
 import android.app.ActivityManager;
 import android.app.ActivityThread;
 import android.app.ApplicationErrorReport;
-import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledAfter;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.type.DefaultMimeMapFactory;
 import android.os.Build;
@@ -36,7 +34,6 @@
 import com.android.internal.logging.AndroidConfig;
 import com.android.server.NetworkManagementSocketTagger;
 
-import dalvik.annotation.compat.VersionCodes;
 import dalvik.system.RuntimeHooks;
 import dalvik.system.ThreadPrioritySetter;
 import dalvik.system.VMRuntime;
@@ -67,18 +64,8 @@
 
     private static volatile boolean mCrashing = false;
 
-    /**
-     * Native heap allocations will now have a non-zero tag in the most significant byte.
-     * See
-     * <a href="https://source.android.com/devices/tech/debug/tagged-pointers">https://source.android.com/devices/tech/debug/tagged-pointers</a>.
-     */
-    @ChangeId
-    @EnabledAfter(targetSdkVersion = VersionCodes.Q)
-    private static final long NATIVE_HEAP_POINTER_TAGGING = 135754954; // This is a bug id.
-
     private static final native void nativeFinishInit();
     private static final native void nativeSetExitWithoutCleanup(boolean exitWithoutCleanup);
-    private static native void nativeDisableHeapPointerTagging();
 
     private static int Clog_e(String tag, String msg, Throwable tr) {
         return Log.printlns(Log.LOG_ID_CRASH, Log.ERROR, tag, msg, tr);
@@ -411,20 +398,6 @@
         if (DEBUG) Slog.d(TAG, "Leaving RuntimeInit!");
     }
 
-    private static void maybeDisableHeapPointerTagging(long[] disabledCompatChanges) {
-        // Heap tagging needs to be disabled before any additional threads are created, but the
-        // AppCompat framework is not initialized enough at this point.
-        // Check if the change is enabled manually.
-        if (disabledCompatChanges != null) {
-            for (int i = 0; i < disabledCompatChanges.length; i++) {
-                if (disabledCompatChanges[i] == NATIVE_HEAP_POINTER_TAGGING) {
-                    nativeDisableHeapPointerTagging();
-                    break;
-                }
-            }
-        }
-    }
-
     protected static Runnable applicationInit(int targetSdkVersion, long[] disabledCompatChanges,
             String[] argv, ClassLoader classLoader) {
         // If the application calls System.exit(), terminate the process
@@ -437,8 +410,6 @@
         VMRuntime.getRuntime().setTargetSdkVersion(targetSdkVersion);
         VMRuntime.getRuntime().setDisabledCompatChanges(disabledCompatChanges);
 
-        maybeDisableHeapPointerTagging(disabledCompatChanges);
-
         final Arguments args = new Arguments(argv);
 
         // The end of of the RuntimeInit event (see #zygoteInit).
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 72eb32a..5f196a0 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -121,6 +121,25 @@
      */
     public static final int DISABLE_TEST_API_ENFORCEMENT_POLICY = 1 << 18;
 
+    public static final int MEMORY_TAG_LEVEL_MASK = (1 << 19) | (1 << 20);
+    /**
+     * Enable pointer tagging in this process.
+     * Tags are checked during memory deallocation, but not on access.
+     * TBI stands for Top-Byte-Ignore, an ARM CPU feature.
+     * {@link https://developer.arm.com/docs/den0024/latest/the-memory-management-unit/translation-table-configuration/virtual-address-tagging}
+     */
+    public static final int MEMORY_TAG_LEVEL_TBI = 1 << 19;
+
+    /**
+     * Enable asynchronous memory tag checks in this process.
+     */
+    public static final int MEMORY_TAG_LEVEL_ASYNC = 2 << 19;
+
+    /**
+     * Enable synchronous memory tag checks in this process.
+     */
+    public static final int MEMORY_TAG_LEVEL_SYNC = 3 << 19;
+
     /** No external storage should be mounted. */
     public static final int MOUNT_EXTERNAL_NONE = IVold.REMOUNT_MODE_NONE;
     /** Default external storage should be mounted. */
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index decc92c..300f71a 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -781,6 +781,10 @@
             Zygote.applyDebuggerSystemProperty(parsedArgs);
             Zygote.applyInvokeWithSystemProperty(parsedArgs);
 
+            /* Enable pointer tagging in the system server unconditionally. Hardware support for
+             * this is present in all ARMv8 CPUs; this flag has no effect on other platforms. */
+            parsedArgs.mRuntimeFlags |= Zygote.MEMORY_TAG_LEVEL_TBI;
+
             if (shouldProfileSystemServer()) {
                 parsedArgs.mRuntimeFlags |= Zygote.PROFILE_SYSTEM_SERVER;
             }
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 5b80af5..5c3640e 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -282,14 +282,6 @@
     gCurRuntime->setExitWithoutCleanup(exitWithoutCleanup);
 }
 
-static void com_android_internal_os_RuntimeInit_nativeDisableHeapPointerTagging(
-        JNIEnv* env, jobject clazz) {
-    HeapTaggingLevel tag_level = M_HEAP_TAGGING_LEVEL_NONE;
-    if (!android_mallopt(M_SET_HEAP_TAGGING_LEVEL, &tag_level, sizeof(tag_level))) {
-        ALOGE("ERROR: could not disable heap pointer tagging\n");
-    }
-}
-
 /*
  * JNI registration.
  */
@@ -301,8 +293,6 @@
              (void*)com_android_internal_os_RuntimeInit_nativeFinishInit},
             {"nativeSetExitWithoutCleanup", "(Z)V",
              (void*)com_android_internal_os_RuntimeInit_nativeSetExitWithoutCleanup},
-            {"nativeDisableHeapPointerTagging", "()V",
-             (void*)com_android_internal_os_RuntimeInit_nativeDisableHeapPointerTagging},
     };
     return jniRegisterNativeMethods(env, "com/android/internal/os/RuntimeInit",
         methods, NELEM(methods));
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 7e4a16d..ea58cbd 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -315,6 +315,8 @@
 enum RuntimeFlags : uint32_t {
   DEBUG_ENABLE_JDWP = 1,
   PROFILE_FROM_SHELL = 1 << 15,
+  MEMORY_TAG_LEVEL_MASK = (1 << 19) | (1 << 20),
+  MEMORY_TAG_LEVEL_TBI = 1 << 19,
 };
 
 enum UnsolicitedZygoteMessageTypes : uint32_t {
@@ -1153,6 +1155,16 @@
     }
   }
 
+  HeapTaggingLevel heap_tagging_level;
+  switch (runtime_flags & RuntimeFlags::MEMORY_TAG_LEVEL_MASK) {
+    case RuntimeFlags::MEMORY_TAG_LEVEL_TBI:
+      heap_tagging_level = M_HEAP_TAGGING_LEVEL_TBI;
+      break;
+    default:
+      heap_tagging_level = M_HEAP_TAGGING_LEVEL_NONE;
+  }
+  android_mallopt(M_SET_HEAP_TAGGING_LEVEL, &heap_tagging_level, sizeof(heap_tagging_level));
+
   if (NeedsNoRandomizeWorkaround()) {
     // Work around ARM kernel ASLR lossage (http://b/5817320).
     int old_personality = personality(0xffffffff);
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index c217caf..5690af5 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2437,4 +2437,10 @@
     //   > Pair device with QR code > Scan QR code > Pairing device dialog
     // CATEGORY: SETTINGS
     ADB_WIRELESS_DEVICE_QR_PAIRING_DIALOG = 1833;
+
+    // OPEN: Settings > Developer Options > Wireless debugging
+    //   > Click on paired device
+    // CATEGORY: SETTINGS
+    // OS: R
+    ADB_WIRELESS_DEVICE_DETAILS = 1836;
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 0a2c59f..a5f6564 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -391,6 +391,9 @@
     <protected-broadcast android:name="android.intent.action.AIRPLANE_MODE" />
     <protected-broadcast android:name="android.intent.action.ADVANCED_SETTINGS" />
     <protected-broadcast android:name="android.intent.action.APPLICATION_RESTRICTIONS_CHANGED" />
+    <protected-broadcast android:name="com.android.server.adb.WIRELESS_DEBUG_PAIRED_DEVICES" />
+    <protected-broadcast android:name="com.android.server.adb.WIRELESS_DEBUG_PAIRING_RESULT" />
+    <protected-broadcast android:name="com.android.server.adb.WIRELESS_DEBUG_STATUS" />
 
     <!-- Legacy -->
     <protected-broadcast android:name="android.intent.action.ACTION_IDLE_MAINTENANCE_START" />
diff --git a/core/tests/coretests/src/android/os/BundleTest.java b/core/tests/coretests/src/android/os/BundleTest.java
index e4dc993..4cc70ba 100644
--- a/core/tests/coretests/src/android/os/BundleTest.java
+++ b/core/tests/coretests/src/android/os/BundleTest.java
@@ -30,12 +30,23 @@
  * Unit tests for bundle that requires accessing hidden APS.  Tests that can be written only with
  * public APIs should go in the CTS counterpart.
  *
- * Run with:
- * bit FrameworksCoreTests:android.os.BundleTest
+ * Run with: atest FrameworksCoreTests:android.os.BundleTest
  */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class BundleTest {
+
+    /**
+     * Take a bundle, write it to a parcel and return the parcel.
+     */
+    private Parcel getParcelledBundle(Bundle bundle) {
+        final Parcel p = Parcel.obtain();
+        // Don't use p.writeParcelabe(), which would write the creator, which we don't need.
+        bundle.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        return p;
+    }
+
     /**
      * Create a test bundle, parcel it and return the parcel.
      */
@@ -48,12 +59,7 @@
             pipe[1].close();
             source.putParcelable("fd", pipe[0]);
         }
-        final Parcel p = Parcel.obtain();
-        // Don't use p.writeParcelabe(), which would write the creator, which we don't need.
-        source.writeToParcel(p, 0);
-        p.setDataPosition(0);
-
-        return p;
+        return getParcelledBundle(source);
     }
 
     /**
@@ -137,4 +143,78 @@
         checkBundle(b, withFd);
         p.recycle();
     }
+
+    @Test
+    public void kindofEquals_bothUnparcelled_same() {
+        Bundle bundle1 = new Bundle();
+        bundle1.putString("StringKey", "S");
+        bundle1.putInt("IntKey", 2);
+
+        Bundle bundle2 = new Bundle();
+        bundle2.putString("StringKey", "S");
+        bundle2.putInt("IntKey", 2);
+
+        assertTrue(BaseBundle.kindofEquals(bundle1, bundle2));
+    }
+
+    @Test
+    public void kindofEquals_bothUnparcelled_different() {
+        Bundle bundle1 = new Bundle();
+        bundle1.putString("StringKey", "S");
+        bundle1.putInt("IntKey", 2);
+
+        Bundle bundle2 = new Bundle();
+        bundle2.putString("StringKey", "T");
+        bundle2.putLong("LongKey", 30L);
+
+        assertFalse(BaseBundle.kindofEquals(bundle1, bundle2));
+    }
+
+    @Test
+    public void kindofEquals_bothParcelled_same() {
+        Bundle bundle1 = new Bundle();
+        bundle1.putString("StringKey", "S");
+        bundle1.putInt("IntKey", 2);
+        bundle1.readFromParcel(getParcelledBundle(bundle1));
+
+        Bundle bundle2 = new Bundle();
+        bundle2.putString("StringKey", "S");
+        bundle2.putInt("IntKey", 2);
+        bundle2.readFromParcel(getParcelledBundle(bundle2));
+
+        assertTrue(bundle1.isParcelled());
+        assertTrue(bundle2.isParcelled());
+        assertTrue(BaseBundle.kindofEquals(bundle1, bundle2));
+    }
+
+    @Test
+    public void kindofEquals_bothParcelled_different() {
+        Bundle bundle1 = new Bundle();
+        bundle1.putString("StringKey", "S");
+        bundle1.putInt("IntKey", 2);
+        bundle1.readFromParcel(getParcelledBundle(bundle1));
+
+        Bundle bundle2 = new Bundle();
+        bundle2.putString("StringKey", "T");
+        bundle2.putLong("LongKey", 5);
+        bundle2.readFromParcel(getParcelledBundle(bundle2));
+
+        assertTrue(bundle1.isParcelled());
+        assertTrue(bundle2.isParcelled());
+        assertFalse(BaseBundle.kindofEquals(bundle1, bundle2));
+    }
+
+    @Test
+    public void kindofEquals_ParcelledUnparcelled_empty() {
+        Bundle bundle1 = new Bundle();
+        bundle1.readFromParcel(getParcelledBundle(bundle1));
+
+        Bundle bundle2 = new Bundle();
+
+        assertTrue(bundle1.isParcelled());
+        assertFalse(bundle2.isParcelled());
+        // Even though one is parcelled and the other is not, both are empty, so it should
+        // return true
+        assertTrue(BaseBundle.kindofEquals(bundle1, bundle2));
+    }
 }
diff --git a/core/tests/systemproperties/src/android/os/PropertyInvalidatedCacheTest.java b/core/tests/systemproperties/src/android/os/PropertyInvalidatedCacheTest.java
new file mode 100644
index 0000000..c4080e8
--- /dev/null
+++ b/core/tests/systemproperties/src/android/os/PropertyInvalidatedCacheTest.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2019 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.os;
+
+import android.app.PropertyInvalidatedCache;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import junit.framework.TestCase;
+
+public class PropertyInvalidatedCacheTest extends TestCase {
+    private static final String KEY = "sys.testkey";
+    private static final String UNSET_KEY = "Aiw7woh6ie4toh7W";
+
+    private static class TestCache extends PropertyInvalidatedCache<Integer, String> {
+        TestCache() {
+            this(KEY);
+        }
+
+        TestCache(String key) {
+            super(4, key);
+        }
+
+        @Override
+        protected String recompute(Integer qv) {
+            mRecomputeCount += 1;
+            return "foo" + qv.toString();
+        }
+
+        int getRecomputeCount() {
+            return mRecomputeCount;
+        }
+
+        private int mRecomputeCount = 0;
+    }
+
+    @Override
+    protected void setUp() {
+        SystemProperties.set(KEY, "");
+    }
+
+    @SmallTest
+    public void testCacheRecompute() throws Exception {
+        TestCache cache = new TestCache();
+        cache.invalidateCache();
+        assertEquals("foo5", cache.query(5));
+        assertEquals(1, cache.getRecomputeCount());
+        assertEquals("foo5", cache.query(5));
+        assertEquals(1, cache.getRecomputeCount());
+        assertEquals("foo6", cache.query(6));
+        assertEquals(2, cache.getRecomputeCount());
+        cache.invalidateCache();
+        assertEquals("foo5", cache.query(5));
+        assertEquals("foo5", cache.query(5));
+        assertEquals(3, cache.getRecomputeCount());
+    }
+
+    @SmallTest
+    public void testCacheInitialState() throws Exception {
+        TestCache cache = new TestCache();
+        assertEquals("foo5", cache.query(5));
+        assertEquals("foo5", cache.query(5));
+        assertEquals(2, cache.getRecomputeCount());
+        cache.invalidateCache();
+        assertEquals("foo5", cache.query(5));
+        assertEquals("foo5", cache.query(5));
+        assertEquals(3, cache.getRecomputeCount());
+    }
+
+    @SmallTest
+    public void testCachePropertyUnset() throws Exception {
+        TestCache cache = new TestCache(UNSET_KEY);
+        assertEquals("foo5", cache.query(5));
+        assertEquals("foo5", cache.query(5));
+        assertEquals(2, cache.getRecomputeCount());
+    }
+
+    @SmallTest
+    public void testCacheDisableState() throws Exception {
+        TestCache cache = new TestCache();
+        assertEquals("foo5", cache.query(5));
+        assertEquals("foo5", cache.query(5));
+        assertEquals(2, cache.getRecomputeCount());
+        cache.invalidateCache();
+        assertEquals("foo5", cache.query(5));
+        assertEquals("foo5", cache.query(5));
+        assertEquals(3, cache.getRecomputeCount());
+        cache.disableSystemWide();
+        assertEquals("foo5", cache.query(5));
+        assertEquals("foo5", cache.query(5));
+        assertEquals(5, cache.getRecomputeCount());
+        cache.invalidateCache();  // Should not reenable
+        assertEquals("foo5", cache.query(5));
+        assertEquals("foo5", cache.query(5));
+        assertEquals(7, cache.getRecomputeCount());
+    }
+
+    @SmallTest
+    public void testRefreshSameObject() throws Exception {
+        int[] refreshCount = new int[1];
+        TestCache cache = new TestCache() {
+            @Override
+            protected String refresh(String oldResult, Integer query) {
+                refreshCount[0] += 1;
+                return oldResult;
+            }
+        };
+        cache.invalidateCache();
+        String result1 = cache.query(5);
+        assertEquals("foo5", result1);
+        String result2 = cache.query(5);
+        assertSame(result1, result2);
+        assertEquals(1, cache.getRecomputeCount());
+        assertEquals(1, refreshCount[0]);
+        assertEquals("foo5", cache.query(5));
+        assertEquals(2, refreshCount[0]);
+    }
+
+    @SmallTest
+    public void testRefreshInvalidateRace() throws Exception {
+        int[] refreshCount = new int[1];
+        TestCache cache = new TestCache() {
+            @Override
+            protected String refresh(String oldResult, Integer query) {
+                refreshCount[0] += 1;
+                invalidateCache();
+                return new String(oldResult);
+            }
+        };
+        cache.invalidateCache();
+        String result1 = cache.query(5);
+        assertEquals("foo5", result1);
+        String result2 = cache.query(5);
+        assertEquals(result1, result2);
+        assertNotSame(result1, result2);
+        assertEquals(2, cache.getRecomputeCount());
+    }
+
+    @SmallTest
+    public void testLocalProcessDisable() throws Exception {
+        TestCache cache = new TestCache();
+        cache.invalidateCache();
+        assertEquals("foo5", cache.query(5));
+        assertEquals(1, cache.getRecomputeCount());
+        assertEquals("foo5", cache.query(5));
+        assertEquals(1, cache.getRecomputeCount());
+        assertEquals(cache.isDisabledLocal(), false);
+        cache.disableLocal();
+        assertEquals(cache.isDisabledLocal(), true);
+        assertEquals("foo5", cache.query(5));
+        assertEquals("foo5", cache.query(5));
+        assertEquals(3, cache.getRecomputeCount());
+    }
+
+}
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
index 6fd47c4..66a59e0 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -509,15 +509,19 @@
      * @return The position of the {@link Uri}, or -1 if it cannot be found.
      */
     public int getRingtonePosition(Uri ringtoneUri) {
-        if (ringtoneUri == null) return -1;
-        final long ringtoneId = ContentUris.parseId(ringtoneUri);
-        
-        final Cursor cursor = getCursor();
-        cursor.moveToPosition(-1);
-        while (cursor.moveToNext()) {
-            if (ringtoneId == cursor.getLong(ID_COLUMN_INDEX)) {
-                return cursor.getPosition();
+        try {
+            if (ringtoneUri == null) return -1;
+            final long ringtoneId = ContentUris.parseId(ringtoneUri);
+
+            final Cursor cursor = getCursor();
+            cursor.moveToPosition(-1);
+            while (cursor.moveToNext()) {
+                if (ringtoneId == cursor.getLong(ID_COLUMN_INDEX)) {
+                    return cursor.getPosition();
+                }
             }
+        } catch (NumberFormatException e) {
+            Log.e(TAG, "NumberFormatException while getting ringtone position, returning -1", e);
         }
         return -1;
     }
diff --git a/media/java/android/media/tv/TvTrackInfo.java b/media/java/android/media/tv/TvTrackInfo.java
index d4c4a62..7d19985 100644
--- a/media/java/android/media/tv/TvTrackInfo.java
+++ b/media/java/android/media/tv/TvTrackInfo.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -58,6 +59,8 @@
     private final String mId;
     private final String mLanguage;
     private final CharSequence mDescription;
+    @Nullable
+    private final String mEncoding;
     private final boolean mEncrypted;
     private final int mAudioChannelCount;
     private final int mAudioSampleRate;
@@ -73,14 +76,15 @@
     private final Bundle mExtra;
 
     private TvTrackInfo(int type, String id, String language, CharSequence description,
-            boolean encrypted, int audioChannelCount, int audioSampleRate, boolean audioDescription,
-            boolean hardOfHearing, boolean spokenSubtitle, int videoWidth, int videoHeight,
-            float videoFrameRate, float videoPixelAspectRatio, byte videoActiveFormatDescription,
-            Bundle extra) {
+            String encoding, boolean encrypted, int audioChannelCount, int audioSampleRate,
+            boolean audioDescription, boolean hardOfHearing, boolean spokenSubtitle, int videoWidth,
+            int videoHeight, float videoFrameRate, float videoPixelAspectRatio,
+            byte videoActiveFormatDescription, Bundle extra) {
         mType = type;
         mId = id;
         mLanguage = language;
         mDescription = description;
+        mEncoding = encoding;
         mEncrypted = encrypted;
         mAudioChannelCount = audioChannelCount;
         mAudioSampleRate = audioSampleRate;
@@ -100,6 +104,7 @@
         mId = in.readString();
         mLanguage = in.readString();
         mDescription = in.readString();
+        mEncoding = in.readString();
         mEncrypted = in.readInt() != 0;
         mAudioChannelCount = in.readInt();
         mAudioSampleRate = in.readInt();
@@ -146,13 +151,26 @@
     }
 
     /**
+     * Returns the codec in the form of mime type. If the encoding is unknown or could not be
+     * determined, the corresponding value will be {@code null}.
+     *
+     * <p>For example of broadcast, codec information may be referred to broadcast standard (e.g.
+     * Component Descriptor of ETSI EN 300 468). In the case that track type is subtitle, mime type
+     * could be defined in broadcast standard (e.g. "text/dvb.subtitle" or "text/dvb.teletext" in
+     * ETSI TS 102 812 V1.3.1 section 7.6).
+     */
+    @Nullable
+    public String getEncoding() {
+        return mEncoding;
+    }
+
+    /**
      * Returns {@code true} if the track is encrypted, {@code false} otherwise. If the encryption
      * status is unknown or could not be determined, the corresponding value will be {@code false}.
      *
      * <p>For example: ISO/IEC 13818-1 defines a CA descriptor that can be used to determine the
      * encryption status of some broadcast streams.
      */
-
     public boolean isEncrypted() {
         return mEncrypted;
     }
@@ -323,6 +341,7 @@
         dest.writeString(mId);
         dest.writeString(mLanguage);
         dest.writeString(mDescription != null ? mDescription.toString() : null);
+        dest.writeString(mEncoding);
         dest.writeInt(mEncrypted ? 1 : 0);
         dest.writeInt(mAudioChannelCount);
         dest.writeInt(mAudioSampleRate);
@@ -352,6 +371,7 @@
         if (!TextUtils.equals(mId, obj.mId) || mType != obj.mType
                 || !TextUtils.equals(mLanguage, obj.mLanguage)
                 || !TextUtils.equals(mDescription, obj.mDescription)
+                || !TextUtils.equals(mEncoding, obj.mEncoding)
                 || mEncrypted != obj.mEncrypted) {
             return false;
         }
@@ -413,6 +433,7 @@
         private final int mType;
         private String mLanguage;
         private CharSequence mDescription;
+        private String mEncoding;
         private boolean mEncrypted;
         private int mAudioChannelCount;
         private int mAudioSampleRate;
@@ -468,6 +489,22 @@
         }
 
         /**
+         * Sets the encoding of the track.
+         *
+         * <p>For example of broadcast, codec information may be referred to broadcast standard
+         * (e.g. Component Descriptor of ETSI EN 300 468). In the case that track type is subtitle,
+         * mime type could be defined in broadcast standard (e.g. "text/dvb.subtitle" or
+         * "text/dvb.teletext" in ETSI TS 102 812 V1.3.1 section 7.6).
+         *
+         * @param encoding The encoding of the track in the form of mime type.
+         */
+        @NonNull
+        public Builder setEncoding(@Nullable String encoding) {
+            mEncoding = encoding;
+            return this;
+        }
+
+        /**
          * Sets the encryption status of the track.
          *
          * <p>For example: ISO/IEC 13818-1 defines a CA descriptor that can be used to determine the
@@ -672,7 +709,7 @@
          * @return The new {@link TvTrackInfo} instance
          */
         public TvTrackInfo build() {
-            return new TvTrackInfo(mType, mId, mLanguage, mDescription, mEncrypted,
+            return new TvTrackInfo(mType, mId, mLanguage, mDescription, mEncoding, mEncrypted,
                     mAudioChannelCount, mAudioSampleRate, mAudioDescription, mHardOfHearing,
                     mSpokenSubtitle, mVideoWidth, mVideoHeight, mVideoFrameRate,
                     mVideoPixelAspectRatio, mVideoActiveFormatDescription, mExtra);
diff --git a/packages/Tethering/TEST_MAPPING b/packages/Tethering/TEST_MAPPING
new file mode 100644
index 0000000..73254cd
--- /dev/null
+++ b/packages/Tethering/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "postsubmit": [
+    {
+      "name": "TetheringTests"
+    }
+  ]
+}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 8922245..40992b9 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -43,6 +43,7 @@
         "android.hardware.broadcastradio-V2.0-java",
         "android.hardware.health-V1.0-java",
         "android.hardware.health-V2.0-java",
+        "android.hardware.health-V2.1-java",
         "android.hardware.light-java",
         "android.hardware.weaver-V1.0-java",
         "android.hardware.biometrics.face-V1.0-java",
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 0816955..b676f99 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -26,8 +26,10 @@
 import android.database.ContentObserver;
 import android.hardware.health.V1_0.HealthInfo;
 import android.hardware.health.V2_0.IHealth;
-import android.hardware.health.V2_0.IHealthInfoCallback;
 import android.hardware.health.V2_0.Result;
+import android.hardware.health.V2_1.BatteryCapacityLevel;
+import android.hardware.health.V2_1.Constants;
+import android.hardware.health.V2_1.IHealthInfoCallback;
 import android.hidl.manager.V1_0.IServiceManager;
 import android.hidl.manager.V1_0.IServiceNotification;
 import android.metrics.LogMaker;
@@ -144,6 +146,7 @@
 
     private HealthInfo mHealthInfo;
     private final HealthInfo mLastHealthInfo = new HealthInfo();
+    private android.hardware.health.V2_1.HealthInfo mHealthInfo2p1;
     private boolean mBatteryLevelCritical;
     private int mLastBatteryStatus;
     private int mLastBatteryHealth;
@@ -358,6 +361,9 @@
     }
 
     private boolean shouldShutdownLocked() {
+        if (mHealthInfo2p1.batteryCapacityLevel != BatteryCapacityLevel.UNSUPPORTED) {
+            return (mHealthInfo2p1.batteryCapacityLevel == BatteryCapacityLevel.CRITICAL);
+        }
         if (mHealthInfo.batteryLevel > 0) {
             return false;
         }
@@ -415,22 +421,23 @@
         }
     }
 
-    private void update(android.hardware.health.V2_0.HealthInfo info) {
+    private void update(android.hardware.health.V2_1.HealthInfo info) {
         traceBegin("HealthInfoUpdate");
 
         Trace.traceCounter(Trace.TRACE_TAG_POWER, "BatteryChargeCounter",
-                info.legacy.batteryChargeCounter);
+                info.legacy.legacy.batteryChargeCounter);
         Trace.traceCounter(Trace.TRACE_TAG_POWER, "BatteryCurrent",
-                info.legacy.batteryCurrent);
+                info.legacy.legacy.batteryCurrent);
 
         synchronized (mLock) {
             if (!mUpdatesStopped) {
-                mHealthInfo = info.legacy;
+                mHealthInfo = info.legacy.legacy;
+                mHealthInfo2p1 = info;
                 // Process the new values.
                 processValuesLocked(false);
                 mLock.notifyAll(); // for any waiters on new info
             } else {
-                copy(mLastHealthInfo, info.legacy);
+                copy(mLastHealthInfo, info.legacy.legacy);
             }
         }
         traceEnd();
@@ -484,7 +491,8 @@
             mBatteryStats.setBatteryState(mHealthInfo.batteryStatus, mHealthInfo.batteryHealth,
                     mPlugType, mHealthInfo.batteryLevel, mHealthInfo.batteryTemperature,
                     mHealthInfo.batteryVoltage, mHealthInfo.batteryChargeCounter,
-                    mHealthInfo.batteryFullCharge);
+                    mHealthInfo.batteryFullCharge,
+                    mHealthInfo2p1.batteryChargeTimeToFullNowSeconds);
         } catch (RemoteException e) {
             // Should never happen.
         }
@@ -1120,8 +1128,21 @@
     private final class HealthHalCallback extends IHealthInfoCallback.Stub
             implements HealthServiceWrapper.Callback {
         @Override public void healthInfoChanged(android.hardware.health.V2_0.HealthInfo props) {
+            android.hardware.health.V2_1.HealthInfo propsLatest =
+                    new android.hardware.health.V2_1.HealthInfo();
+            propsLatest.legacy = props;
+
+            propsLatest.batteryCapacityLevel = BatteryCapacityLevel.UNSUPPORTED;
+            propsLatest.batteryChargeTimeToFullNowSeconds =
+                Constants.BATTERY_CHARGE_TIME_TO_FULL_NOW_SECONDS_UNSUPPORTED;
+
+            BatteryService.this.update(propsLatest);
+        }
+
+        @Override public void healthInfoChanged_2_1(android.hardware.health.V2_1.HealthInfo props) {
             BatteryService.this.update(props);
         }
+
         // on new service registered
         @Override public void onRegistration(IHealth oldService, IHealth newService,
                 String instance) {
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 5f032fc..52a2ca97 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -4783,7 +4783,7 @@
                 return false;
             }
 
-            return vpn.startAlwaysOnVpn();
+            return vpn.startAlwaysOnVpn(mKeyStore);
         }
     }
 
@@ -4798,7 +4798,7 @@
                 Slog.w(TAG, "User " + userId + " has no Vpn configuration");
                 return false;
             }
-            return vpn.isAlwaysOnPackageSupported(packageName);
+            return vpn.isAlwaysOnPackageSupported(packageName, mKeyStore);
         }
     }
 
@@ -4819,11 +4819,11 @@
                 Slog.w(TAG, "User " + userId + " has no Vpn configuration");
                 return false;
             }
-            if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownWhitelist)) {
+            if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownWhitelist, mKeyStore)) {
                 return false;
             }
             if (!startAlwaysOnVpn(userId)) {
-                vpn.setAlwaysOnPackage(null, false, null);
+                vpn.setAlwaysOnPackage(null, false, null, mKeyStore);
                 return false;
             }
         }
@@ -5009,7 +5009,7 @@
                 loge("Starting user already has a VPN");
                 return;
             }
-            userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, userId);
+            userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, userId, mKeyStore);
             mVpns.put(userId, userVpn);
             if (mUserManager.getUserInfo(userId).isPrimary() && LockdownVpnTracker.isEnabled()) {
                 updateLockdownVpn();
@@ -5080,7 +5080,7 @@
             if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName)) {
                 Slog.d(TAG, "Restarting always-on VPN package " + packageName + " for user "
                         + userId);
-                vpn.startAlwaysOnVpn();
+                vpn.startAlwaysOnVpn(mKeyStore);
             }
         }
     }
@@ -5102,7 +5102,7 @@
             if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) {
                 Slog.d(TAG, "Removing always-on VPN package " + packageName + " for user "
                         + userId);
-                vpn.setAlwaysOnPackage(null, false, null);
+                vpn.setAlwaysOnPackage(null, false, null, mKeyStore);
             }
         }
     }
diff --git a/services/core/java/com/android/server/DynamicSystemService.java b/services/core/java/com/android/server/DynamicSystemService.java
index c60460f..41207c9 100644
--- a/services/core/java/com/android/server/DynamicSystemService.java
+++ b/services/core/java/com/android/server/DynamicSystemService.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.gsi.AvbPublicKey;
 import android.gsi.GsiProgress;
 import android.gsi.IGsiService;
 import android.gsi.IGsid;
@@ -227,4 +228,13 @@
             throw new RuntimeException(e.toString());
         }
     }
+
+    @Override
+    public boolean getAvbPublicKey(AvbPublicKey dst) {
+        try {
+            return getGsiService().getAvbPublicKey(dst) == 0;
+        } catch (RemoteException e) {
+            throw new RuntimeException(e.toString());
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
index 4b48ef9..da93998 100644
--- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java
+++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
@@ -29,6 +29,7 @@
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.debug.AdbProtoEnums;
+import android.debug.AdbTransportType;
 import android.net.LocalSocket;
 import android.net.LocalSocketAddress;
 import android.net.Uri;
@@ -717,13 +718,21 @@
     }
 
     /**
-     * When {@code enabled} is {@code true}, this allows ADB debugging and starts the ADB hanler
-     * thread. When {@code enabled} is {@code false}, this disallows ADB debugging and shuts
-     * down the handler thread.
+     * When {@code enabled} is {@code true}, this allows ADB debugging and starts the ADB handler
+     * thread. When {@code enabled} is {@code false}, this disallows ADB debugging for the given
+     * @{code transportType}. See {@link IAdbTransport} for all available transport types.
+     * If all transport types are disabled, the ADB handler thread will shut down.
      */
-    public void setAdbEnabled(boolean enabled) {
-        mHandler.sendEmptyMessage(enabled ? AdbDebuggingHandler.MESSAGE_ADB_ENABLED
-                                          : AdbDebuggingHandler.MESSAGE_ADB_DISABLED);
+    public void setAdbEnabled(boolean enabled, byte transportType) {
+        if (transportType == AdbTransportType.USB) {
+            mHandler.sendEmptyMessage(enabled ? AdbDebuggingHandler.MESSAGE_ADB_ENABLED
+                                              : AdbDebuggingHandler.MESSAGE_ADB_DISABLED);
+        } else if (transportType == AdbTransportType.WIFI) {
+            // TODO(joshuaduong): Not implemented
+        } else {
+            throw new IllegalArgumentException(
+                    "setAdbEnabled called with unimplemented transport type=" + transportType);
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/adb/AdbService.java b/services/core/java/com/android/server/adb/AdbService.java
index c125b1b..f2a8615 100644
--- a/services/core/java/com/android/server/adb/AdbService.java
+++ b/services/core/java/com/android/server/adb/AdbService.java
@@ -15,19 +15,23 @@
  */
 package com.android.server.adb;
 
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.database.ContentObserver;
 import android.debug.AdbManagerInternal;
+import android.debug.AdbTransportType;
 import android.debug.IAdbManager;
 import android.debug.IAdbTransport;
+import android.debug.PairDevice;
 import android.hardware.usb.UsbManager;
+import android.net.Uri;
 import android.os.Binder;
-import android.os.Handler;
 import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.provider.Settings;
@@ -38,7 +42,6 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
-
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.dump.DualDumpOutputStream;
@@ -50,6 +53,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.Collections;
+import java.util.Map;
 
 /**
  * The Android Debug Bridge (ADB) service. This controls the availability of ADB and authorization
@@ -77,7 +81,8 @@
             if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
                 mAdbService.systemReady();
             } else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
-                mAdbService.bootCompleted();
+                FgThread.getHandler().sendMessage(obtainMessage(
+                        AdbService::bootCompleted, mAdbService));
             }
         }
     }
@@ -94,8 +99,14 @@
         }
 
         @Override
-        public boolean isAdbEnabled() {
-            return mAdbEnabled;
+        public boolean isAdbEnabled(byte transportType) {
+            if (transportType == AdbTransportType.USB) {
+                return mIsAdbUsbEnabled;
+            } else if (transportType == AdbTransportType.WIFI) {
+                return mIsAdbWifiEnabled;
+            }
+            throw new IllegalArgumentException(
+                    "isAdbEnabled called with unimplemented transport type=" + transportType);
         }
 
         @Override
@@ -109,77 +120,60 @@
         }
     }
 
-    private final class AdbHandler extends Handler {
-        AdbHandler(Looper looper) {
-            super(looper);
-            try {
-                /*
-                 * Use the normal bootmode persistent prop to maintain state of adb across
-                 * all boot modes.
-                 */
-                mAdbEnabled = containsFunction(
-                        SystemProperties.get(USB_PERSISTENT_CONFIG_PROPERTY, ""),
-                        UsbManager.USB_FUNCTION_ADB);
+    private void initAdbState() {
+        try {
+            /*
+             * Use the normal bootmode persistent prop to maintain state of adb across
+             * all boot modes.
+             */
+            mIsAdbUsbEnabled = containsFunction(
+                    SystemProperties.get(USB_PERSISTENT_CONFIG_PROPERTY, ""),
+                    UsbManager.USB_FUNCTION_ADB);
+            // TODO(joshuaduong): Read the adb wifi state from a persistent system
+            // property (persist.sys.adb.wifi).
+            mIsAdbWifiEnabled = false;
 
-                // register observer to listen for settings changes
-                mContentResolver.registerContentObserver(
-                        Settings.Global.getUriFor(Settings.Global.ADB_ENABLED),
-                        false, new AdbSettingsObserver());
-            } catch (Exception e) {
-                Slog.e(TAG, "Error initializing AdbHandler", e);
-            }
-        }
-
-        private boolean containsFunction(String functions, String function) {
-            int index = functions.indexOf(function);
-            if (index < 0) return false;
-            if (index > 0 && functions.charAt(index - 1) != ',') return false;
-            int charAfter = index + function.length();
-            if (charAfter < functions.length() && functions.charAt(charAfter) != ',') return false;
-            return true;
-        }
-
-        public void sendMessage(int what, boolean arg) {
-            removeMessages(what);
-            Message m = Message.obtain(this, what);
-            m.arg1 = (arg ? 1 : 0);
-            sendMessage(m);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_ENABLE_ADB:
-                    setAdbEnabled(msg.arg1 == 1);
-                    break;
-                case MSG_BOOT_COMPLETED:
-                    if (mDebuggingManager != null) {
-                        mDebuggingManager.setAdbEnabled(mAdbEnabled);
-                    }
-                    break;
-            }
+            // register observer to listen for settings changes
+            mContentResolver.registerContentObserver(
+                    Settings.Global.getUriFor(Settings.Global.ADB_ENABLED),
+                    false, new AdbSettingsObserver());
+        } catch (Exception e) {
+            Slog.e(TAG, "Error in initAdbState", e);
         }
     }
 
+    private static boolean containsFunction(String functions, String function) {
+        int index = functions.indexOf(function);
+        if (index < 0) return false;
+        if (index > 0 && functions.charAt(index - 1) != ',') return false;
+        int charAfter = index + function.length();
+        if (charAfter < functions.length() && functions.charAt(charAfter) != ',') return false;
+        return true;
+    }
+
     private class AdbSettingsObserver extends ContentObserver {
+        private final Uri mAdbUsbUri = Settings.Global.getUriFor(Settings.Global.ADB_ENABLED);
+
         AdbSettingsObserver() {
             super(null);
         }
 
         @Override
-        public void onChange(boolean selfChange) {
-            boolean enable = (Settings.Global.getInt(mContentResolver,
-                    Settings.Global.ADB_ENABLED, 0) > 0);
-            mHandler.sendMessage(MSG_ENABLE_ADB, enable);
+        public void onChange(boolean selfChange, @NonNull Uri uri, @UserIdInt int userId) {
+            if (mAdbUsbUri.equals(uri)) {
+                boolean shouldEnable = (Settings.Global.getInt(mContentResolver,
+                        Settings.Global.ADB_ENABLED, 0) > 0);
+                FgThread.getHandler().sendMessage(obtainMessage(
+                        AdbService::setAdbEnabled, AdbService.this, shouldEnable,
+                            AdbTransportType.USB));
+            }
+            // TODO(joshuaduong): Add condition for WIFI transport
         }
     }
 
     private static final String TAG = "AdbService";
     private static final boolean DEBUG = false;
 
-    private static final int MSG_ENABLE_ADB = 1;
-    private static final int MSG_BOOT_COMPLETED = 2;
-
     /**
      * The persistent property which stores whether adb is enabled or not.
      * May also contain vendor-specific default functions for testing purposes.
@@ -188,10 +182,10 @@
 
     private final Context mContext;
     private final ContentResolver mContentResolver;
-    private final AdbService.AdbHandler mHandler;
     private final ArrayMap<IBinder, IAdbTransport> mTransports = new ArrayMap<>();
 
-    private boolean mAdbEnabled;
+    private boolean mIsAdbUsbEnabled;
+    private boolean mIsAdbWifiEnabled;
     private AdbDebuggingManager mDebuggingManager;
 
     private AdbService(Context context) {
@@ -204,8 +198,7 @@
             mDebuggingManager = new AdbDebuggingManager(context);
         }
 
-        mHandler = new AdbHandler(FgThread.get().getLooper());
-
+        initAdbState();
         LocalServices.addService(AdbManagerInternal.class, new AdbManagerInternalImpl());
     }
 
@@ -219,7 +212,7 @@
         // make sure the ADB_ENABLED setting value matches the current state
         try {
             Settings.Global.putInt(mContentResolver,
-                    Settings.Global.ADB_ENABLED, mAdbEnabled ? 1 : 0);
+                    Settings.Global.ADB_ENABLED, mIsAdbUsbEnabled ? 1 : 0);
         } catch (SecurityException e) {
             // If UserManager.DISALLOW_DEBUGGING_FEATURES is on, that this setting can't be changed.
             Slog.d(TAG, "ADB_ENABLED is restricted.");
@@ -231,7 +224,10 @@
      */
     public void bootCompleted() {
         if (DEBUG) Slog.d(TAG, "boot completed");
-        mHandler.sendEmptyMessage(MSG_BOOT_COMPLETED);
+        if (mDebuggingManager != null) {
+            mDebuggingManager.setAdbEnabled(mIsAdbUsbEnabled, AdbTransportType.USB);
+            mDebuggingManager.setAdbEnabled(mIsAdbWifiEnabled, AdbTransportType.WIFI);
+        }
     }
 
     @Override
@@ -285,24 +281,82 @@
                 PackageManager.FEATURE_CAMERA_ANY);
     }
 
-    private void setAdbEnabled(boolean enable) {
-        if (DEBUG) Slog.d(TAG, "setAdbEnabled(" + enable + "), mAdbEnabled=" + mAdbEnabled);
+    @Override
+    public void allowWirelessDebugging(boolean alwaysAllow, String bssid) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null);
+        // TODO(joshuaduong): NOT IMPLEMENTED
+    }
 
-        if (enable == mAdbEnabled) {
+    @Override
+    public void denyWirelessDebugging() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null);
+        // TODO(joshuaduong): NOT IMPLEMENTED
+    }
+
+    @Override
+    public Map<String, PairDevice> getPairedDevices() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null);
+        // TODO(joshuaduong): NOT IMPLEMENTED
+        return null;
+    }
+
+    @Override
+    public void unpairDevice(String fingerprint) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null);
+        // TODO(joshuaduong): NOT IMPLEMENTED
+    }
+
+    @Override
+    public void enablePairingByPairingCode() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null);
+        // TODO(joshuaduong): NOT IMPLEMENTED
+    }
+
+    @Override
+    public void enablePairingByQrCode(String serviceName, String password) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null);
+        // TODO(joshuaduong): NOT IMPLEMENTED
+    }
+
+    @Override
+    public void disablePairing() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null);
+        // TODO(joshuaduong): NOT IMPLEMENTED
+    }
+
+    @Override
+    public int getAdbWirelessPort() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_DEBUGGING, null);
+        // TODO(joshuaduong): NOT IMPLEMENTED
+        return 0;
+    }
+
+    private void setAdbEnabled(boolean enable, byte transportType) {
+        if (DEBUG) {
+            Slog.d(TAG, "setAdbEnabled(" + enable + "), mIsAdbUsbEnabled=" + mIsAdbUsbEnabled
+                    + ", mIsAdbWifiEnabled=" + mIsAdbWifiEnabled + ", transportType="
+                        + transportType);
+        }
+
+        if (transportType == AdbTransportType.USB && enable != mIsAdbUsbEnabled) {
+            mIsAdbUsbEnabled = enable;
+        } else if (transportType == AdbTransportType.WIFI && enable != mIsAdbWifiEnabled) {
+            mIsAdbWifiEnabled = enable;
+        } else {
+            // No change
             return;
         }
-        mAdbEnabled = enable;
 
         for (IAdbTransport transport : mTransports.values()) {
             try {
-                transport.onAdbEnabled(enable);
+                transport.onAdbEnabled(enable, transportType);
             } catch (RemoteException e) {
                 Slog.w(TAG, "Unable to send onAdbEnabled to transport " + transport.toString());
             }
         }
 
         if (mDebuggingManager != null) {
-            mDebuggingManager.setAdbEnabled(enable);
+            mDebuggingManager.setAdbEnabled(enable, transportType);
         }
     }
 
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 8c60d0c..a19ac5e 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -1133,7 +1133,7 @@
     @Override
     public void setBatteryState(final int status, final int health, final int plugType,
             final int level, final int temp, final int volt, final int chargeUAh,
-            final int chargeFullUAh) {
+            final int chargeFullUAh, final long chargeTimeToFullSeconds) {
         enforceCallingPermission();
 
         // BatteryService calls us here and we may update external state. It would be wrong
@@ -1145,7 +1145,7 @@
                     // The battery state has not changed, so we don't need to sync external
                     // stats immediately.
                     mStats.setBatteryStateLocked(status, health, plugType, level, temp, volt,
-                            chargeUAh, chargeFullUAh);
+                            chargeUAh, chargeFullUAh, chargeTimeToFullSeconds);
                     return;
                 }
             }
@@ -1158,7 +1158,7 @@
             mWorker.scheduleRunnable(() -> {
                 synchronized (mStats) {
                     mStats.setBatteryStateLocked(status, health, plugType, level, temp, volt,
-                            chargeUAh, chargeFullUAh);
+                            chargeUAh, chargeFullUAh, chargeTimeToFullSeconds);
                 }
             });
         });
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index c2652c0..8520cb7 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -51,6 +51,9 @@
 import android.app.AppGlobals;
 import android.app.AppProtoEnums;
 import android.app.IApplicationThread;
+import android.app.IUidObserver;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -103,6 +106,7 @@
 import com.android.server.wm.ActivityServiceConnectionsHolder;
 import com.android.server.wm.WindowManagerService;
 
+import dalvik.annotation.compat.VersionCodes;
 import dalvik.system.VMRuntime;
 
 import java.io.File;
@@ -280,6 +284,15 @@
     // lmkd reconnect delay in msecs
     private static final long LMKD_RECONNECT_DELAY_MS = 1000;
 
+    /**
+     * Native heap allocations will now have a non-zero tag in the most significant byte.
+     * @see <a href="https://source.android.com/devices/tech/debug/tagged-pointers">Tagged
+     * Pointers</a>
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = VersionCodes.Q)
+    private static final long NATIVE_HEAP_POINTER_TAGGING = 135754954; // This is a bug id.
+
     ActivityManagerService mService = null;
 
     // To kill process groups asynchronously
@@ -1653,6 +1666,10 @@
                 runtimeFlags |= Zygote.USE_APP_IMAGE_STARTUP_CACHE;
             }
 
+            if (mPlatformCompat.isChangeEnabled(NATIVE_HEAP_POINTER_TAGGING, app.info)) {
+                runtimeFlags |= Zygote.MEMORY_TAG_LEVEL_TBI;
+            }
+
             String invokeWith = null;
             if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
                 // Debuggable apps may include a wrapper script with their library directory.
diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java
index f15d999..cc5b766 100644
--- a/services/core/java/com/android/server/compat/CompatConfig.java
+++ b/services/core/java/com/android/server/compat/CompatConfig.java
@@ -16,6 +16,7 @@
 
 package com.android.server.compat;
 
+import android.app.compat.ChangeIdStateCache;
 import android.compat.Compatibility.ChangeConfig;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -78,6 +79,7 @@
     void addChange(CompatChange change) {
         synchronized (mChanges) {
             mChanges.put(change.getId(), change);
+            invalidateCache();
         }
     }
 
@@ -170,6 +172,7 @@
                 addChange(c);
             }
             c.addPackageOverride(packageName, enabled);
+            invalidateCache();
         }
         return alreadyKnown;
     }
@@ -226,6 +229,7 @@
                 // Should never occur, since validator is in the same process.
                 throw new RuntimeException("Unable to call override validator!", e);
             }
+            invalidateCache();
         }
         return overrideExists;
     }
@@ -248,6 +252,7 @@
                 addOverride(changeId, packageName, false);
 
             }
+            invalidateCache();
         }
     }
 
@@ -277,6 +282,7 @@
                     throw new RuntimeException("Unable to call override validator!", e);
                 }
             }
+            invalidateCache();
         }
     }
 
@@ -396,4 +402,8 @@
     IOverrideValidator getOverrideValidator() {
         return mOverrideValidator;
     }
+
+    private void invalidateCache() {
+        ChangeIdStateCache.invalidate();
+    }
 }
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 8692436..3c21d1a 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -216,14 +216,14 @@
      * Whether to keep the connection active after rebooting, or upgrading or reinstalling. This
      * only applies to {@link VpnService} connections.
      */
-    private boolean mAlwaysOn = false;
+    @VisibleForTesting protected boolean mAlwaysOn = false;
 
     /**
      * Whether to disable traffic outside of this VPN even when the VPN is not connected. System
      * apps can still bypass by choosing explicit networks. Has no effect if {@link mAlwaysOn} is
-     * not set.
+     * not set. Applies to all types of VPNs.
      */
-    private boolean mLockdown = false;
+    @VisibleForTesting protected boolean mLockdown = false;
 
     /**
      * Set of packages in addition to the VPN app itself that can access the network directly when
@@ -252,14 +252,14 @@
     private final int mUserHandle;
 
     public Vpn(Looper looper, Context context, INetworkManagementService netService,
-            @UserIdInt int userHandle) {
-        this(looper, context, netService, userHandle,
+            @UserIdInt int userHandle, @NonNull KeyStore keyStore) {
+        this(looper, context, netService, userHandle, keyStore,
                 new SystemServices(context), new Ikev2SessionCreator());
     }
 
     @VisibleForTesting
     protected Vpn(Looper looper, Context context, INetworkManagementService netService,
-            int userHandle, SystemServices systemServices,
+            int userHandle, @NonNull KeyStore keyStore, SystemServices systemServices,
             Ikev2SessionCreator ikev2SessionCreator) {
         mContext = context;
         mNetd = netService;
@@ -285,7 +285,7 @@
         mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN);
         updateCapabilities(null /* defaultNetwork */);
 
-        loadAlwaysOnPackage();
+        loadAlwaysOnPackage(keyStore);
     }
 
     /**
@@ -437,23 +437,36 @@
     /**
      * Checks if a VPN app supports always-on mode.
      *
-     * In order to support the always-on feature, an app has to
+     * <p>In order to support the always-on feature, an app has to either have an installed
+     * PlatformVpnProfile, or:
+     *
      * <ul>
-     *     <li>target {@link VERSION_CODES#N API 24} or above, and
-     *     <li>not opt out through the {@link VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON}
-     *         meta-data field.
+     *   <li>target {@link VERSION_CODES#N API 24} or above, and
+     *   <li>not opt out through the {@link VpnService#SERVICE_META_DATA_SUPPORTS_ALWAYS_ON}
+     *       meta-data field.
      * </ul>
      *
      * @param packageName the canonical package name of the VPN app
+     * @param keyStore the keystore instance to use for checking if the app has a Platform VPN
+     *     profile installed.
      * @return {@code true} if and only if the VPN app exists and supports always-on mode
      */
-    public boolean isAlwaysOnPackageSupported(String packageName) {
+    public boolean isAlwaysOnPackageSupported(String packageName, @NonNull KeyStore keyStore) {
         enforceSettingsPermission();
 
         if (packageName == null) {
             return false;
         }
 
+        final long oldId = Binder.clearCallingIdentity();
+        try {
+            if (getVpnProfilePrivileged(packageName, keyStore) != null) {
+                return true;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(oldId);
+        }
+
         PackageManager pm = mContext.getPackageManager();
         ApplicationInfo appInfo = null;
         try {
@@ -485,27 +498,31 @@
     }
 
     /**
-     * Configures an always-on VPN connection through a specific application.
-     * This connection is automatically granted and persisted after a reboot.
+     * Configures an always-on VPN connection through a specific application. This connection is
+     * automatically granted and persisted after a reboot.
      *
-     * <p>The designated package should exist and declare a {@link VpnService} in its
-     *    manifest guarded by {@link android.Manifest.permission.BIND_VPN_SERVICE},
-     *    otherwise the call will fail.
+     * <p>The designated package should either have a PlatformVpnProfile installed, or declare a
+     * {@link VpnService} in its manifest guarded by {@link
+     * android.Manifest.permission.BIND_VPN_SERVICE}, otherwise the call will fail.
      *
      * <p>Note that this method does not check if the VPN app supports always-on mode. The check is
-     *    delayed to {@link #startAlwaysOnVpn()}, which is always called immediately after this
-     *    method in {@link android.net.IConnectivityManager#setAlwaysOnVpnPackage}.
+     * delayed to {@link #startAlwaysOnVpn()}, which is always called immediately after this method
+     * in {@link android.net.IConnectivityManager#setAlwaysOnVpnPackage}.
      *
      * @param packageName the package to designate as always-on VPN supplier.
      * @param lockdown whether to prevent traffic outside of a VPN, for example while connecting.
      * @param lockdownWhitelist packages to be whitelisted from lockdown.
+     * @param keyStore the Keystore instance to use for checking of PlatformVpnProfile(s)
      * @return {@code true} if the package has been set as always-on, {@code false} otherwise.
      */
     public synchronized boolean setAlwaysOnPackage(
-            String packageName, boolean lockdown, List<String> lockdownWhitelist) {
+            @Nullable String packageName,
+            boolean lockdown,
+            @Nullable List<String> lockdownWhitelist,
+            @NonNull KeyStore keyStore) {
         enforceControlPermissionOrInternalCaller();
 
-        if (setAlwaysOnPackageInternal(packageName, lockdown, lockdownWhitelist)) {
+        if (setAlwaysOnPackageInternal(packageName, lockdown, lockdownWhitelist, keyStore)) {
             saveAlwaysOnPackage();
             return true;
         }
@@ -513,20 +530,22 @@
     }
 
     /**
-     * Configures an always-on VPN connection through a specific application, the same as
-     * {@link #setAlwaysOnPackage}.
+     * Configures an always-on VPN connection through a specific application, the same as {@link
+     * #setAlwaysOnPackage}.
      *
-     * Does not perform permission checks. Does not persist any of the changes to storage.
+     * <p>Does not perform permission checks. Does not persist any of the changes to storage.
      *
      * @param packageName the package to designate as always-on VPN supplier.
      * @param lockdown whether to prevent traffic outside of a VPN, for example while connecting.
      * @param lockdownWhitelist packages to be whitelisted from lockdown. This is only used if
-     *        {@code lockdown} is {@code true}. Packages must not contain commas.
+     *     {@code lockdown} is {@code true}. Packages must not contain commas.
+     * @param keyStore the system keystore instance to check for profiles
      * @return {@code true} if the package has been set as always-on, {@code false} otherwise.
      */
     @GuardedBy("this")
     private boolean setAlwaysOnPackageInternal(
-            String packageName, boolean lockdown, List<String> lockdownWhitelist) {
+            @Nullable String packageName, boolean lockdown,
+            @Nullable List<String> lockdownWhitelist, @NonNull KeyStore keyStore) {
         if (VpnConfig.LEGACY_VPN.equals(packageName)) {
             Log.w(TAG, "Not setting legacy VPN \"" + packageName + "\" as always-on.");
             return false;
@@ -542,11 +561,18 @@
         }
 
         if (packageName != null) {
-            // TODO: Give the minimum permission possible; if there is a Platform VPN profile, only
-            // grant ACTIVATE_PLATFORM_VPN.
-            // Pre-authorize new always-on VPN package. Grant the full ACTIVATE_VPN appop, allowing
-            // both VpnService and Platform VPNs.
-            if (!setPackageAuthorization(packageName, VpnManager.TYPE_VPN_SERVICE)) {
+            final VpnProfile profile;
+            final long oldId = Binder.clearCallingIdentity();
+            try {
+                profile = getVpnProfilePrivileged(packageName, keyStore);
+            } finally {
+                Binder.restoreCallingIdentity(oldId);
+            }
+
+            // Pre-authorize new always-on VPN package.
+            final int grantType =
+                    (profile == null) ? VpnManager.TYPE_VPN_SERVICE : VpnManager.TYPE_VPN_PLATFORM;
+            if (!setPackageAuthorization(packageName, grantType)) {
                 return false;
             }
             mAlwaysOn = true;
@@ -611,11 +637,9 @@
         }
     }
 
-    /**
-     * Load the always-on package and lockdown config from Settings.Secure
-     */
+    /** Load the always-on package and lockdown config from Settings. */
     @GuardedBy("this")
-    private void loadAlwaysOnPackage() {
+    private void loadAlwaysOnPackage(@NonNull KeyStore keyStore) {
         final long token = Binder.clearCallingIdentity();
         try {
             final String alwaysOnPackage = mSystemServices.settingsSecureGetStringForUser(
@@ -626,17 +650,21 @@
                     Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST, mUserHandle);
             final List<String> whitelistedPackages = TextUtils.isEmpty(whitelistString)
                     ? Collections.emptyList() : Arrays.asList(whitelistString.split(","));
-            setAlwaysOnPackageInternal(alwaysOnPackage, alwaysOnLockdown, whitelistedPackages);
+            setAlwaysOnPackageInternal(
+                    alwaysOnPackage, alwaysOnLockdown, whitelistedPackages, keyStore);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
     }
 
     /**
+     * Starts the currently selected always-on VPN
+     *
+     * @param keyStore the keyStore instance for looking up PlatformVpnProfile(s)
      * @return {@code true} if the service was started, the service was already connected, or there
-     *         was no always-on VPN to start. {@code false} otherwise.
+     *     was no always-on VPN to start. {@code false} otherwise.
      */
-    public boolean startAlwaysOnVpn() {
+    public boolean startAlwaysOnVpn(@NonNull KeyStore keyStore) {
         final String alwaysOnPackage;
         synchronized (this) {
             alwaysOnPackage = getAlwaysOnPackage();
@@ -645,8 +673,8 @@
                 return true;
             }
             // Remove always-on VPN if it's not supported.
-            if (!isAlwaysOnPackageSupported(alwaysOnPackage)) {
-                setAlwaysOnPackage(null, false, null);
+            if (!isAlwaysOnPackageSupported(alwaysOnPackage, keyStore)) {
+                setAlwaysOnPackage(null, false, null, keyStore);
                 return false;
             }
             // Skip if the service is already established. This isn't bulletproof: it's not bound
@@ -657,10 +685,25 @@
             }
         }
 
-        // Tell the OS that background services in this app need to be allowed for
-        // a short time, so we can bootstrap the VPN service.
         final long oldId = Binder.clearCallingIdentity();
         try {
+            // Prefer VPN profiles, if any exist.
+            VpnProfile profile = getVpnProfilePrivileged(alwaysOnPackage, keyStore);
+            if (profile != null) {
+                startVpnProfilePrivileged(profile, alwaysOnPackage,
+                        null /* keyStore for private key retrieval - unneeded */);
+
+                // If the above startVpnProfilePrivileged() call returns, the Ikev2VpnProfile was
+                // correctly parsed, and the VPN has started running in a different thread. The only
+                // other possibility is that the above call threw an exception, which will be
+                // caught below, and returns false (clearing the always-on VPN). Once started, the
+                // Platform VPN cannot permanently fail, and is resilient to temporary failures. It
+                // will continue retrying until shut down by the user, or always-on is toggled off.
+                return true;
+            }
+
+            // Tell the OS that background services in this app need to be allowed for
+            // a short time, so we can bootstrap the VPN service.
             DeviceIdleController.LocalService idleController =
                     LocalServices.getService(DeviceIdleController.LocalService.class);
             idleController.addPowerSaveTempWhitelistApp(Process.myUid(), alwaysOnPackage,
@@ -675,6 +718,9 @@
                 Log.e(TAG, "VpnService " + serviceIntent + " failed to start", e);
                 return false;
             }
+        } catch (Exception e) {
+            Log.e(TAG, "Error starting always-on VPN", e);
+            return false;
         } finally {
             Binder.restoreCallingIdentity(oldId);
         }
@@ -773,6 +819,7 @@
     }
 
     /** Prepare the VPN for the given package. Does not perform permission checks. */
+    @GuardedBy("this")
     private void prepareInternal(String newPackage) {
         long token = Binder.clearCallingIdentity();
         try {
@@ -1894,6 +1941,27 @@
         // Prepare arguments for racoon.
         String[] racoon = null;
         switch (profile.type) {
+            case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
+                // Secret key is still just the alias (not the actual private key). The private key
+                // is retrieved from the KeyStore during conversion of the VpnProfile to an
+                // Ikev2VpnProfile.
+                profile.ipsecSecret = Ikev2VpnProfile.PREFIX_KEYSTORE_ALIAS + privateKey;
+                profile.ipsecUserCert = userCert;
+                // Fallthrough
+            case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
+                profile.ipsecCaCert = caCert;
+
+                // Start VPN profile
+                startVpnProfilePrivileged(profile, VpnConfig.LEGACY_VPN, keyStore);
+                return;
+            case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
+                // Ikev2VpnProfiles expect a base64-encoded preshared key.
+                profile.ipsecSecret =
+                        Ikev2VpnProfile.encodeForIpsecSecret(profile.ipsecSecret.getBytes());
+
+                // Start VPN profile
+                startVpnProfilePrivileged(profile, VpnConfig.LEGACY_VPN, keyStore);
+                return;
             case VpnProfile.TYPE_L2TP_IPSEC_PSK:
                 racoon = new String[] {
                     iface, profile.server, "udppsk", profile.ipsecIdentifier,
@@ -2816,6 +2884,10 @@
         return isVpnProfilePreConsented(mContext, packageName);
     }
 
+    private boolean isCurrentIkev2VpnLocked(@NonNull String packageName) {
+        return isCurrentPreparedPackage(packageName) && mVpnRunner instanceof IkeV2VpnRunner;
+    }
+
     /**
      * Deletes an app-provisioned VPN profile.
      *
@@ -2832,6 +2904,17 @@
 
         Binder.withCleanCallingIdentity(
                 () -> {
+                    // If this profile is providing the current VPN, turn it off, disabling
+                    // always-on as well if enabled.
+                    if (isCurrentIkev2VpnLocked(packageName)) {
+                        if (mAlwaysOn) {
+                            // Will transitively call prepareInternal(VpnConfig.LEGACY_VPN).
+                            setAlwaysOnPackage(null, false, null, keyStore);
+                        } else {
+                            prepareInternal(VpnConfig.LEGACY_VPN);
+                        }
+                    }
+
                     keyStore.delete(getProfileNameForPackage(packageName), Process.SYSTEM_UID);
                 });
     }
@@ -2885,24 +2968,35 @@
                         throw new IllegalArgumentException("No profile found for " + packageName);
                     }
 
-                    startVpnProfilePrivileged(profile, packageName);
+                    startVpnProfilePrivileged(profile, packageName,
+                            null /* keyStore for private key retrieval - unneeded */);
                 });
     }
 
-    private void startVpnProfilePrivileged(
-            @NonNull VpnProfile profile, @NonNull String packageName) {
-        // Ensure that no other previous instance is running.
-        if (mVpnRunner != null) {
-            mVpnRunner.exit();
-            mVpnRunner = null;
-        }
+    private synchronized void startVpnProfilePrivileged(
+            @NonNull VpnProfile profile, @NonNull String packageName, @Nullable KeyStore keyStore) {
+        // Make sure VPN is prepared. This method can be called by user apps via startVpnProfile(),
+        // by the Setting app via startLegacyVpn(), or by ConnectivityService via
+        // startAlwaysOnVpn(), so this is the common place to prepare the VPN. This also has the
+        // nice property of ensuring there are no other VpnRunner instances running.
+        prepareInternal(packageName);
         updateState(DetailedState.CONNECTING, "startPlatformVpn");
 
         try {
             // Build basic config
             mConfig = new VpnConfig();
-            mConfig.user = packageName;
-            mConfig.isMetered = profile.isMetered;
+            if (VpnConfig.LEGACY_VPN.equals(packageName)) {
+                mConfig.legacy = true;
+                mConfig.session = profile.name;
+                mConfig.user = profile.key;
+
+                // TODO: Add support for configuring meteredness via Settings. Until then, use a
+                // safe default.
+                mConfig.isMetered = true;
+            } else {
+                mConfig.user = packageName;
+                mConfig.isMetered = profile.isMetered;
+            }
             mConfig.startTime = SystemClock.elapsedRealtime();
             mConfig.proxyInfo = profile.proxy;
 
@@ -2910,7 +3004,8 @@
                 case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
                 case VpnProfile.TYPE_IKEV2_IPSEC_PSK:
                 case VpnProfile.TYPE_IKEV2_IPSEC_RSA:
-                    mVpnRunner = new IkeV2VpnRunner(Ikev2VpnProfile.fromVpnProfile(profile));
+                    mVpnRunner =
+                            new IkeV2VpnRunner(Ikev2VpnProfile.fromVpnProfile(profile, keyStore));
                     mVpnRunner.start();
                     break;
                 default:
@@ -2942,11 +3037,9 @@
 
         // To stop the VPN profile, the caller must be the current prepared package and must be
         // running an Ikev2VpnProfile.
-        if (!isCurrentPreparedPackage(packageName) && mVpnRunner instanceof IkeV2VpnRunner) {
-            return;
+        if (isCurrentIkev2VpnLocked(packageName)) {
+            prepareInternal(VpnConfig.LEGACY_VPN);
         }
-
-        prepareInternal(VpnConfig.LEGACY_VPN);
     }
 
     /**
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index e655b35..6fa9923 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -1502,11 +1502,11 @@
                     pw.println();
                 }
             }
-            if (job.getExtras() != null && !job.getExtras().maybeIsEmpty()) {
+            if (job.getExtras() != null && !job.getExtras().isDefinitelyEmpty()) {
                 pw.print(prefix); pw.print("  Extras: ");
                 pw.println(job.getExtras().toShortString());
             }
-            if (job.getTransientExtras() != null && !job.getTransientExtras().maybeIsEmpty()) {
+            if (job.getTransientExtras() != null && !job.getTransientExtras().isDefinitelyEmpty()) {
                 pw.print(prefix); pw.print("  Transient extras: ");
                 pw.println(job.getTransientExtras().toShortString());
             }
@@ -1702,10 +1702,10 @@
                             job.getTriggerContentMaxDelay());
                 }
             }
-            if (job.getExtras() != null && !job.getExtras().maybeIsEmpty()) {
+            if (job.getExtras() != null && !job.getExtras().isDefinitelyEmpty()) {
                 job.getExtras().writeToProto(proto, JobStatusDumpProto.JobInfo.EXTRAS);
             }
-            if (job.getTransientExtras() != null && !job.getTransientExtras().maybeIsEmpty()) {
+            if (job.getTransientExtras() != null && !job.getTransientExtras().isDefinitelyEmpty()) {
                 job.getTransientExtras().writeToProto(proto, JobStatusDumpProto.JobInfo.TRANSIENT_EXTRAS);
             }
             if (job.getClipData() != null) {
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index a7f8618..896bf67 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -1253,7 +1253,7 @@
         final NetworkStats uidSnapshot = getNetworkStatsUidDetail(INTERFACES_ALL);
         Trace.traceEnd(TRACE_TAG_NETWORK);
         Trace.traceBegin(TRACE_TAG_NETWORK, "snapshotXt");
-        final NetworkStats xtSnapshot = getNetworkStatsXt();
+        final NetworkStats xtSnapshot = readNetworkStatsSummaryXt();
         Trace.traceEnd(TRACE_TAG_NETWORK);
         Trace.traceBegin(TRACE_TAG_NETWORK, "snapshotDev");
         final NetworkStats devSnapshot = readNetworkStatsSummaryDev();
@@ -1742,18 +1742,6 @@
                 mUseBpfTrafficStats);
         uidSnapshot.combineAllValues(tetherSnapshot);
 
-        final TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
-                Context.TELEPHONY_SERVICE);
-
-        // fold video calling data usage stats into uid snapshot
-        final NetworkStats vtStats = telephonyManager.getVtDataUsage(STATS_PER_UID);
-        if (vtStats != null) {
-            vtStats.filter(UID_ALL, ifaces, TAG_ALL);
-            mStatsFactory.apply464xlatAdjustments(uidSnapshot, vtStats,
-                    mUseBpfTrafficStats);
-            uidSnapshot.combineAllValues(vtStats);
-        }
-
         // get a stale copy of uid stats snapshot provided by providers.
         final NetworkStats providerStats = getNetworkStatsFromProviders(STATS_PER_UID);
         providerStats.filter(UID_ALL, ifaces, TAG_ALL);
@@ -1766,24 +1754,6 @@
     }
 
     /**
-     * Return snapshot of current XT statistics with video calling data usage statistics.
-     */
-    private NetworkStats getNetworkStatsXt() throws RemoteException {
-        final NetworkStats xtSnapshot = readNetworkStatsSummaryXt();
-
-        final TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
-                Context.TELEPHONY_SERVICE);
-
-        // Merge video calling data usage into XT
-        final NetworkStats vtSnapshot = telephonyManager.getVtDataUsage(STATS_PER_IFACE);
-        if (vtSnapshot != null) {
-            xtSnapshot.combineAllValues(vtSnapshot);
-        }
-
-        return xtSnapshot;
-    }
-
-    /**
      * Return snapshot of current tethering statistics. Will return empty
      * {@link NetworkStats} if any problems are encountered.
      */
diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
index 407f67e..44f4ccf 100644
--- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
@@ -24,6 +24,7 @@
 import static org.mockito.Mockito.when;
 import static org.testng.Assert.assertThrows;
 
+import android.app.compat.ChangeIdStateCache;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
@@ -74,6 +75,7 @@
         // Assume userdebug/eng non-final build
         when(mBuildClassifier.isDebuggableBuild()).thenReturn(true);
         when(mBuildClassifier.isFinalBuild()).thenReturn(false);
+        ChangeIdStateCache.disable();
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
index ce5d6d9..717a78d 100644
--- a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
@@ -57,6 +57,7 @@
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+        android.app.compat.ChangeIdStateCache.disable();
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
         when(mPackageManager.getPackageUid(eq(PACKAGE_NAME), eq(0))).thenThrow(
                 new PackageManager.NameNotFoundException());
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 45b840c..5bb75c9 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -41,6 +41,7 @@
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.debug.AdbManagerInternal;
+import android.debug.AdbTransportType;
 import android.debug.IAdbTransport;
 import android.hardware.usb.ParcelableUsbPort;
 import android.hardware.usb.UsbAccessory;
@@ -774,8 +775,10 @@
             }
 
             @Override
-            public void onAdbEnabled(boolean enabled) {
-                mHandler.sendMessage(MSG_ENABLE_ADB, enabled);
+            public void onAdbEnabled(boolean enabled, byte transportType) {
+                if (transportType == AdbTransportType.USB) {
+                    mHandler.sendMessage(MSG_ENABLE_ADB, enabled);
+                }
             }
         }
 
@@ -1169,7 +1172,8 @@
         }
 
         protected boolean isAdbEnabled() {
-            return LocalServices.getService(AdbManagerInternal.class).isAdbEnabled();
+            return LocalServices.getService(AdbManagerInternal.class)
+                    .isAdbEnabled(AdbTransportType.USB);
         }
 
         protected void updateAdbNotification(boolean force) {
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 2c85b27..bb51399 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -45,10 +45,8 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.net.ConnectivityManager;
-import android.net.NetworkStats;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Binder;
@@ -10840,28 +10838,6 @@
     }
 
     /**
-     * Get aggregated video call data usage since boot.
-     * Permissions android.Manifest.permission.READ_NETWORK_USAGE_HISTORY is required.
-     *
-     * @param how one of the NetworkStats.STATS_PER_* constants depending on whether the request is
-     * for data usage per uid or overall usage.
-     * @return Snapshot of video call data usage
-     * @hide
-     */
-    public NetworkStats getVtDataUsage(int how) {
-        boolean perUidStats = (how == NetworkStats.STATS_PER_UID);
-        try {
-            ITelephony service = getITelephony();
-            if (service != null) {
-                return service.getVtDataUsage(getSubId(), perUidStats);
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error calling ITelephony#getVtDataUsage", e);
-        }
-        return null;
-    }
-
-    /**
      * Policy control of data connection. Usually used when data limit is passed.
      * @param enabled True if enabling the data, otherwise disabling.
      * @param subId sub id
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 5c18cdd..d8339c8 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1622,16 +1622,6 @@
     void carrierActionResetAll(int subId);
 
     /**
-     * Get aggregated video call data usage since boot.
-     * Permissions android.Manifest.permission.READ_NETWORK_USAGE_HISTORY is required.
-     *
-     * @param perUidStats True if requesting data usage per uid, otherwise overall usage.
-     * @return Snapshot of video call data usage
-     * @hide
-     */
-    NetworkStats getVtDataUsage(int subId, boolean perUidStats);
-
-    /**
      * Gets the voice call forwarding info {@link CallForwardingInfo}, given the call forward
      * reason.
      *
diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java
index 39f1fc2..48cb1cd 100644
--- a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java
+++ b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java
@@ -248,10 +248,10 @@
         public int hour;
         public int monthDay;
 
-        /** Month [0-11] */
-        public int month;
+        /** Month in the range 1(Jan) - 12(Dec). */
+        public int monthOrdinal;
 
-        /** Full year. For example, 1970. */
+        /** Full year in the range 1996 - 2095. */
         public int year;
 
         private ZoneId mZoneId;
@@ -269,7 +269,7 @@
             ts.year = year >= 96 ? year + 1900 : year + 2000;
             int month = IccUtils.cdmaBcdByteToInt(data[1]);
             if (month < 1 || month > 12) return null;
-            ts.month = month - 1;
+            ts.monthOrdinal = month;
             int day = IccUtils.cdmaBcdByteToInt(data[2]);
             if (day < 1 || day > 31) return null;
             ts.monthDay = day;
@@ -292,7 +292,7 @@
             int year = localDateTime.getYear();
             if (year < 1996 || year > 2095) return null;
             ts.year = year;
-            ts.month = localDateTime.getMonthValue();
+            ts.monthOrdinal = localDateTime.getMonthValue();
             ts.monthDay = localDateTime.getDayOfMonth();
             ts.hour = localDateTime.getHour();
             ts.minute = localDateTime.getMinute();
@@ -304,7 +304,7 @@
             int year = this.year % 100; // 00 - 99
             ByteArrayOutputStream outStream = new ByteArrayOutputStream(6);
             outStream.write((((year / 10) & 0x0F) << 4) | ((year % 10) & 0x0F));
-            outStream.write((((month / 10) << 4) & 0xF0) | ((month % 10) & 0x0F));
+            outStream.write((((monthOrdinal / 10) << 4) & 0xF0) | ((monthOrdinal % 10) & 0x0F));
             outStream.write((((monthDay / 10) << 4) & 0xF0) | ((monthDay % 10) & 0x0F));
             outStream.write((((hour / 10) << 4) & 0xF0) | ((hour % 10) & 0x0F));
             outStream.write((((minute / 10) << 4) & 0xF0) | ((minute % 10) & 0x0F));
@@ -314,7 +314,7 @@
 
         public long toMillis() {
             LocalDateTime localDateTime =
-                    LocalDateTime.of(year, month + 1, monthDay, hour, minute, second);
+                    LocalDateTime.of(year, monthOrdinal, monthDay, hour, minute, second);
             Instant instant = localDateTime.toInstant(mZoneId.getRules().getOffset(localDateTime));
             return instant.toEpochMilli();
         }
@@ -325,7 +325,7 @@
             StringBuilder builder = new StringBuilder();
             builder.append("TimeStamp ");
             builder.append("{ year=" + year);
-            builder.append(", month=" + month);
+            builder.append(", month=" + monthOrdinal);
             builder.append(", day=" + monthDay);
             builder.append(", hour=" + hour);
             builder.append(", minute=" + minute);
diff --git a/tests/net/java/android/net/Ikev2VpnProfileTest.java b/tests/net/java/android/net/Ikev2VpnProfileTest.java
index d6a2176..2273bc6 100644
--- a/tests/net/java/android/net/Ikev2VpnProfileTest.java
+++ b/tests/net/java/android/net/Ikev2VpnProfileTest.java
@@ -22,7 +22,6 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
-import static org.mockito.Mockito.mock;
 
 import android.test.mock.MockContext;
 
@@ -232,10 +231,12 @@
         builder.setAuthDigitalSignature(mUserCert, mPrivateKey, mServerRootCa);
         final VpnProfile profile = builder.build().toVpnProfile();
 
+        final String expectedSecret = Ikev2VpnProfile.PREFIX_INLINE
+                + Ikev2VpnProfile.encodeForIpsecSecret(mPrivateKey.getEncoded());
         verifyVpnProfileCommon(profile);
         assertEquals(Ikev2VpnProfile.certificateToPemString(mUserCert), profile.ipsecUserCert);
         assertEquals(
-                Ikev2VpnProfile.encodeForIpsecSecret(mPrivateKey.getEncoded()),
+                expectedSecret,
                 profile.ipsecSecret);
         assertEquals(Ikev2VpnProfile.certificateToPemString(mServerRootCa), profile.ipsecCaCert);
 
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 8ed497b..77147c8 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -204,6 +204,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.security.KeyStore;
 import android.system.Os;
 import android.test.mock.MockContentResolver;
 import android.text.TextUtils;
@@ -1019,7 +1020,7 @@
 
         public MockVpn(int userId) {
             super(startHandlerThreadAndReturnLooper(), mServiceContext, mNetworkManagementService,
-                    userId);
+                    userId, mock(KeyStore.class));
         }
 
         public void setNetworkAgent(TestNetworkAgentWrapper agent) {
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index ac1c518..1994d1f 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -59,9 +59,15 @@
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.net.ConnectivityManager;
+import android.net.Ikev2VpnProfile;
+import android.net.InetAddresses;
+import android.net.IpPrefix;
+import android.net.IpSecManager;
+import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo.DetailedState;
+import android.net.RouteInfo;
 import android.net.UidRange;
 import android.net.VpnManager;
 import android.net.VpnService;
@@ -72,6 +78,7 @@
 import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.Settings;
 import android.security.Credentials;
 import android.security.KeyStore;
 import android.util.ArrayMap;
@@ -83,6 +90,7 @@
 import com.android.internal.R;
 import com.android.internal.net.VpnConfig;
 import com.android.internal.net.VpnProfile;
+import com.android.server.IpSecService;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -92,6 +100,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.net.Inet4Address;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -124,6 +133,9 @@
     }
 
     static final String TEST_VPN_PKG = "com.dummy.vpn";
+    private static final String TEST_VPN_SERVER = "1.2.3.4";
+    private static final String TEST_VPN_IDENTITY = "identity";
+    private static final byte[] TEST_VPN_PSK = "psk".getBytes();
 
     /**
      * Names and UIDs for some fake packages. Important points:
@@ -150,23 +162,39 @@
     @Mock private Vpn.SystemServices mSystemServices;
     @Mock private Vpn.Ikev2SessionCreator mIkev2SessionCreator;
     @Mock private ConnectivityManager mConnectivityManager;
+    @Mock private IpSecService mIpSecService;
     @Mock private KeyStore mKeyStore;
-    private final VpnProfile mVpnProfile = new VpnProfile("key");
+    private final VpnProfile mVpnProfile;
+
+    private IpSecManager mIpSecManager;
+
+    public VpnTest() throws Exception {
+        // Build an actual VPN profile that is capable of being converted to and from an
+        // Ikev2VpnProfile
+        final Ikev2VpnProfile.Builder builder =
+                new Ikev2VpnProfile.Builder(TEST_VPN_SERVER, TEST_VPN_IDENTITY);
+        builder.setAuthPsk(TEST_VPN_PSK);
+        mVpnProfile = builder.build().toVpnProfile();
+    }
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
+        mIpSecManager = new IpSecManager(mContext, mIpSecService);
+
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
         setMockedPackages(mPackages);
 
-        when(mContext.getPackageName()).thenReturn(Vpn.class.getPackage().getName());
+        when(mContext.getPackageName()).thenReturn(TEST_VPN_PKG);
+        when(mContext.getOpPackageName()).thenReturn(TEST_VPN_PKG);
         when(mContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mUserManager);
         when(mContext.getSystemService(eq(Context.APP_OPS_SERVICE))).thenReturn(mAppOps);
         when(mContext.getSystemService(eq(Context.NOTIFICATION_SERVICE)))
                 .thenReturn(mNotificationManager);
         when(mContext.getSystemService(eq(Context.CONNECTIVITY_SERVICE)))
                 .thenReturn(mConnectivityManager);
+        when(mContext.getSystemService(eq(Context.IPSEC_SERVICE))).thenReturn(mIpSecManager);
         when(mContext.getString(R.string.config_customVpnAlwaysOnDisconnectedDialogComponent))
                 .thenReturn(Resources.getSystem().getString(
                         R.string.config_customVpnAlwaysOnDisconnectedDialogComponent));
@@ -260,17 +288,17 @@
         assertFalse(vpn.getLockdown());
 
         // Set always-on without lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, Collections.emptyList()));
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, Collections.emptyList(), mKeyStore));
         assertTrue(vpn.getAlwaysOn());
         assertFalse(vpn.getLockdown());
 
         // Set always-on with lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.emptyList()));
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.emptyList(), mKeyStore));
         assertTrue(vpn.getAlwaysOn());
         assertTrue(vpn.getLockdown());
 
         // Remove always-on configuration.
-        assertTrue(vpn.setAlwaysOnPackage(null, false, Collections.emptyList()));
+        assertTrue(vpn.setAlwaysOnPackage(null, false, Collections.emptyList(), mKeyStore));
         assertFalse(vpn.getAlwaysOn());
         assertFalse(vpn.getLockdown());
     }
@@ -284,11 +312,11 @@
         assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
 
         // Set always-on without lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, null));
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false, null, mKeyStore));
         assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1], user.start + PKG_UIDS[2], user.start + PKG_UIDS[3]);
 
         // Set always-on with lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null));
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, null, mKeyStore));
         verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
             new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
             new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
@@ -297,7 +325,7 @@
         assertUnblocked(vpn, user.start + PKG_UIDS[1]);
 
         // Switch to another app.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null));
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null, mKeyStore));
         verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
             new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
             new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
@@ -316,7 +344,8 @@
         final UidRange user = UidRange.createForUser(primaryUser.id);
 
         // Set always-on with lockdown and whitelist app PKGS[2] from lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.singletonList(PKGS[2])));
+        assertTrue(vpn.setAlwaysOnPackage(
+                PKGS[1], true, Collections.singletonList(PKGS[2]), mKeyStore));
         verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
                 new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
                 new UidRange(user.start + PKG_UIDS[2] + 1, user.stop)
@@ -325,7 +354,8 @@
         assertUnblocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[2]);
 
         // Change whitelisted app to PKGS[3].
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true, Collections.singletonList(PKGS[3])));
+        assertTrue(vpn.setAlwaysOnPackage(
+                PKGS[1], true, Collections.singletonList(PKGS[3]), mKeyStore));
         verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
                 new UidRange(user.start + PKG_UIDS[2] + 1, user.stop)
         }));
@@ -337,7 +367,8 @@
         assertUnblocked(vpn, user.start + PKG_UIDS[1], user.start + PKG_UIDS[3]);
 
         // Change the VPN app.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, Collections.singletonList(PKGS[3])));
+        assertTrue(vpn.setAlwaysOnPackage(
+                PKGS[0], true, Collections.singletonList(PKGS[3]), mKeyStore));
         verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
                 new UidRange(user.start, user.start + PKG_UIDS[1] - 1),
                 new UidRange(user.start + PKG_UIDS[1] + 1, user.start + PKG_UIDS[3] - 1)
@@ -350,7 +381,7 @@
         assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[3]);
 
         // Remove the whitelist.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null));
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, null, mKeyStore));
         verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
                 new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[3] - 1),
                 new UidRange(user.start + PKG_UIDS[3] + 1, user.stop)
@@ -363,7 +394,8 @@
         assertUnblocked(vpn, user.start + PKG_UIDS[0]);
 
         // Add the whitelist.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true, Collections.singletonList(PKGS[1])));
+        assertTrue(vpn.setAlwaysOnPackage(
+                PKGS[0], true, Collections.singletonList(PKGS[1]), mKeyStore));
         verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[] {
                 new UidRange(user.start + PKG_UIDS[0] + 1, user.stop)
         }));
@@ -375,12 +407,13 @@
         assertUnblocked(vpn, user.start + PKG_UIDS[0], user.start + PKG_UIDS[1]);
 
         // Try whitelisting a package with a comma, should be rejected.
-        assertFalse(vpn.setAlwaysOnPackage(PKGS[0], true, Collections.singletonList("a.b,c.d")));
+        assertFalse(vpn.setAlwaysOnPackage(
+                PKGS[0], true, Collections.singletonList("a.b,c.d"), mKeyStore));
 
         // Pass a non-existent packages in the whitelist, they (and only they) should be ignored.
         // Whitelisted package should change from PGKS[1] to PKGS[2].
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[0], true,
-                Arrays.asList("com.foo.app", PKGS[2], "com.bar.app")));
+        assertTrue(vpn.setAlwaysOnPackage(
+                PKGS[0], true, Arrays.asList("com.foo.app", PKGS[2], "com.bar.app"), mKeyStore));
         verify(mNetService).setAllowOnlyVpnForUids(eq(false), aryEq(new UidRange[]{
                 new UidRange(user.start + PKG_UIDS[0] + 1, user.start + PKG_UIDS[1] - 1),
                 new UidRange(user.start + PKG_UIDS[1] + 1, user.stop)
@@ -405,7 +438,7 @@
         final UidRange profile = UidRange.createForUser(tempProfile.id);
 
         // Set lockdown.
-        assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null));
+        assertTrue(vpn.setAlwaysOnPackage(PKGS[3], true, null, mKeyStore));
         verify(mNetService).setAllowOnlyVpnForUids(eq(true), aryEq(new UidRange[] {
             new UidRange(user.start, user.start + PKG_UIDS[3] - 1),
             new UidRange(user.start + PKG_UIDS[3] + 1, user.stop)
@@ -499,22 +532,22 @@
                 .thenReturn(Collections.singletonList(resInfo));
 
         // null package name should return false
-        assertFalse(vpn.isAlwaysOnPackageSupported(null));
+        assertFalse(vpn.isAlwaysOnPackageSupported(null, mKeyStore));
 
         // Pre-N apps are not supported
         appInfo.targetSdkVersion = VERSION_CODES.M;
-        assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0]));
+        assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0], mKeyStore));
 
         // N+ apps are supported by default
         appInfo.targetSdkVersion = VERSION_CODES.N;
-        assertTrue(vpn.isAlwaysOnPackageSupported(PKGS[0]));
+        assertTrue(vpn.isAlwaysOnPackageSupported(PKGS[0], mKeyStore));
 
         // Apps that opt out explicitly are not supported
         appInfo.targetSdkVersion = VERSION_CODES.CUR_DEVELOPMENT;
         Bundle metaData = new Bundle();
         metaData.putBoolean(VpnService.SERVICE_META_DATA_SUPPORTS_ALWAYS_ON, false);
         svcInfo.metaData = metaData;
-        assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0]));
+        assertFalse(vpn.isAlwaysOnPackageSupported(PKGS[0], mKeyStore));
     }
 
     @Test
@@ -531,7 +564,7 @@
                 .cancelAsUser(anyString(), anyInt(), eq(userHandle));
 
         // Start showing a notification for disconnected once always-on.
-        vpn.setAlwaysOnPackage(PKGS[0], false, null);
+        vpn.setAlwaysOnPackage(PKGS[0], false, null, mKeyStore);
         order.verify(mNotificationManager)
                 .notifyAsUser(anyString(), anyInt(), any(), eq(userHandle));
 
@@ -545,7 +578,7 @@
                 .notifyAsUser(anyString(), anyInt(), any(), eq(userHandle));
 
         // Notification should be cleared after unsetting always-on package.
-        vpn.setAlwaysOnPackage(null, false, null);
+        vpn.setAlwaysOnPackage(null, false, null, mKeyStore);
         order.verify(mNotificationManager).cancelAsUser(anyString(), anyInt(), eq(userHandle));
     }
 
@@ -920,12 +953,68 @@
                         eq(AppOpsManager.MODE_IGNORED));
     }
 
+    private void setAndVerifyAlwaysOnPackage(Vpn vpn, int uid, boolean lockdownEnabled) {
+        assertTrue(vpn.setAlwaysOnPackage(TEST_VPN_PKG, lockdownEnabled, null, mKeyStore));
+
+        verify(mKeyStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
+        verify(mAppOps).setMode(
+                eq(AppOpsManager.OP_ACTIVATE_PLATFORM_VPN), eq(uid), eq(TEST_VPN_PKG),
+                eq(AppOpsManager.MODE_ALLOWED));
+
+        verify(mSystemServices).settingsSecurePutStringForUser(
+                eq(Settings.Secure.ALWAYS_ON_VPN_APP), eq(TEST_VPN_PKG), eq(primaryUser.id));
+        verify(mSystemServices).settingsSecurePutIntForUser(
+                eq(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN), eq(lockdownEnabled ? 1 : 0),
+                eq(primaryUser.id));
+        verify(mSystemServices).settingsSecurePutStringForUser(
+                eq(Settings.Secure.ALWAYS_ON_VPN_LOCKDOWN_WHITELIST), eq(""), eq(primaryUser.id));
+    }
+
+    @Test
+    public void testSetAndStartAlwaysOnVpn() throws Exception {
+        final Vpn vpn = createVpn(primaryUser.id);
+        setMockedUsers(primaryUser);
+
+        // UID checks must return a different UID; otherwise it'll be treated as already prepared.
+        final int uid = Process.myUid() + 1;
+        when(mPackageManager.getPackageUidAsUser(eq(TEST_VPN_PKG), anyInt()))
+                .thenReturn(uid);
+        when(mKeyStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
+                .thenReturn(mVpnProfile.encode());
+
+        setAndVerifyAlwaysOnPackage(vpn, uid, false);
+        assertTrue(vpn.startAlwaysOnVpn(mKeyStore));
+
+        // TODO: Test the Ikev2VpnRunner started up properly. Relies on utility methods added in
+        // a subsequent CL.
+    }
+
+    @Test
+    public void testStartLegacyVpn() throws Exception {
+        final Vpn vpn = createVpn(primaryUser.id);
+        setMockedUsers(primaryUser);
+
+        // Dummy egress interface
+        final String egressIface = "DUMMY0";
+        final LinkProperties lp = new LinkProperties();
+        lp.setInterfaceName(egressIface);
+
+        final RouteInfo defaultRoute = new RouteInfo(new IpPrefix(Inet4Address.ANY, 0),
+                        InetAddresses.parseNumericAddress("192.0.2.0"), egressIface);
+        lp.addRoute(defaultRoute);
+
+        vpn.startLegacyVpn(mVpnProfile, mKeyStore, lp);
+
+        // TODO: Test the Ikev2VpnRunner started up properly. Relies on utility methods added in
+        // a subsequent CL.
+    }
+
     /**
      * Mock some methods of vpn object.
      */
     private Vpn createVpn(@UserIdInt int userId) {
         return new Vpn(Looper.myLooper(), mContext, mNetService,
-                userId, mSystemServices, mIkev2SessionCreator);
+                userId, mKeyStore, mSystemServices, mIkev2SessionCreator);
     }
 
     private static void assertBlocked(Vpn vpn, int... uids) {