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) {