diff options
57 files changed, 1973 insertions, 384 deletions
diff --git a/api/current.txt b/api/current.txt index 80a7bb49b1e0..37b7ebc89901 100644 --- a/api/current.txt +++ b/api/current.txt @@ -28356,6 +28356,7 @@ package android.media.tv { 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 @@ package android.media.tv { 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 @@ package java.time.chrono { 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 5c31f41e2e3c..b14a12f62e34 100644 --- a/api/lint-baseline.txt +++ b/api/lint-baseline.txt @@ -537,6 +537,9 @@ MissingNullability: android.icu.util.VersionInfo#UNICODE_12_1: 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 5463cbab4e20..971ea08651b4 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -3350,8 +3350,14 @@ package android.hardware.usb { 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 000000000000..844e72ecf07c --- /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 000000000000..9ef63f6587f0 --- /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 000000000000..2c4c120672ab --- /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 e289a2775b79..0d5e45f9e5d4 100644 --- a/core/java/android/app/compat/CompatChanges.java +++ b/core/java/android/app/compat/CompatChanges.java @@ -19,14 +19,8 @@ package android.app.compat; 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 @@ import com.android.internal.compat.IPlatformCompat; */ @SystemApi public final class CompatChanges { + private static final ChangeIdStateCache QUERY_CACHE = new ChangeIdStateCache(); private CompatChanges() {} /** @@ -69,17 +64,8 @@ public final class CompatChanges { */ 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 @@ public final class CompatChanges { * @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 72eea849cbf4..7e8a7decb9e6 100644 --- a/core/java/android/app/job/JobInfo.java +++ b/core/java/android/app/job/JobInfo.java @@ -625,10 +625,6 @@ public class JobInfo implements Parcelable { 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 @@ public class JobInfo implements Parcelable { 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 d859a3af73a2..8b6840b09b0f 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -6632,7 +6632,7 @@ public class Intent implements Parcelable, Cloneable { 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 0a76bedcd66e..7714dd80f910 100644 --- a/core/java/android/debug/AdbManager.java +++ b/core/java/android/debug/AdbManager.java @@ -31,6 +31,114 @@ import android.os.RemoteException; 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 51eb7fc2d804..0bd9f19f91fe 100644 --- a/core/java/android/debug/AdbManagerInternal.java +++ b/core/java/android/debug/AdbManagerInternal.java @@ -42,7 +42,7 @@ public abstract class AdbManagerInternal { /** * 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 000000000000..69046150d0ee --- /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 c48fc07791c0..aea7633d91dc 100644 --- a/core/java/android/debug/IAdbManager.aidl +++ b/core/java/android/debug/IAdbManager.aidl @@ -43,6 +43,62 @@ interface IAdbManager { 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 77211fc93693..f018813408c4 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 000000000000..2d5b446b4f8f --- /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 b0d0b4c16873..f540bfb2e0fc 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 @@ public class UsbManager { * 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 @@ public class UsbManager { * 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 @@ public class UsbManager { 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 @@ public class UsbManager { */ @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 42b4da14d879..f19a3410d673 100644 --- a/core/java/android/net/Ikev2VpnProfile.java +++ b/core/java/android/net/Ikev2VpnProfile.java @@ -25,7 +25,10 @@ import static com.android.internal.util.Preconditions.checkStringNotEmpty; 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 @@ import java.util.Objects; * 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 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { 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 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { @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 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { 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 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { } /** + * 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 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { /** @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 f19ba0f5ef51..2041cfb22ea8 100644 --- a/core/java/android/net/VpnManager.java +++ b/core/java/android/net/VpnManager.java @@ -126,7 +126,11 @@ public class VpnManager { 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 e6ad917fa019..c2f67947a60b 100644 --- a/core/java/android/os/BaseBundle.java +++ b/core/java/android/os/BaseBundle.java @@ -365,12 +365,16 @@ public class BaseBundle { } /** + * 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 @@ public class BaseBundle { 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 cbf531c5730a..17851adac51b 100644 --- a/core/java/android/os/image/DynamicSystemManager.java +++ b/core/java/android/os/image/DynamicSystemManager.java @@ -19,6 +19,7 @@ package android.os.image; 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 @@ public class DynamicSystemManager { 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 cc32f998d0c2..a1f927266de3 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 @@ interface IDynamicSystemService * @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 15b1d75e88d0..9c8ab0c773c8 100644 --- a/core/java/com/android/internal/app/IBatteryStats.aidl +++ b/core/java/com/android/internal/app/IBatteryStats.aidl @@ -133,7 +133,7 @@ interface IBatteryStats { 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 bbae0273ef4e..23b1ab52cb21 100644 --- a/core/java/com/android/internal/net/VpnProfile.java +++ b/core/java/com/android/internal/net/VpnProfile.java @@ -18,6 +18,7 @@ package com.android.internal.net; 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 @@ public final class VpnProfile implements Cloneable, Parcelable { 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 183c0fb70e27..3d6f233679d7 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -996,6 +996,8 @@ public class BatteryStatsImpl extends BatteryStats { private int mMinLearnedBatteryCapacity = -1; private int mMaxLearnedBatteryCapacity = -1; + private long mBatteryTimeToFullSeconds = -1; + private long[] mCpuFreqs; @VisibleForTesting @@ -12218,7 +12220,7 @@ public class BatteryStatsImpl extends BatteryStats { @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 @@ public class BatteryStatsImpl extends BatteryStats { mMinLearnedBatteryCapacity = Math.min(mMinLearnedBatteryCapacity, chargeFullUAh); } mMaxLearnedBatteryCapacity = Math.max(mMaxLearnedBatteryCapacity, chargeFullUAh); + + mBatteryTimeToFullSeconds = chargeTimeToFullSeconds; } public static boolean isOnBattery(int plugType, int status) { @@ -12570,19 +12574,10 @@ public class BatteryStatsImpl extends BatteryStats { // 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 @@ public class BatteryStatsImpl extends BatteryStats { 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 16c0b5600f17..13d0c5c831b6 100644 --- a/core/java/com/android/internal/os/RuntimeInit.java +++ b/core/java/com/android/internal/os/RuntimeInit.java @@ -19,8 +19,6 @@ package com.android.internal.os; 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 android.util.Slog; 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 @@ public class RuntimeInit { 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 @@ public class RuntimeInit { 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 @@ public class RuntimeInit { 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 72eb32a05da9..5f196a0e4c1c 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -121,6 +121,25 @@ public final class Zygote { */ 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 decc92cfc0c4..300f71af5dd5 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -781,6 +781,10 @@ public class ZygoteInit { 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 5b80af51bb90..5c3640e3b9a0 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -282,14 +282,6 @@ static void com_android_internal_os_RuntimeInit_nativeSetExitWithoutCleanup(JNIE 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 @@ int register_com_android_internal_os_RuntimeInit(JNIEnv* env) (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 7e4a16dc6dcc..ea58cbd99179 100644 --- a/core/jni/com_android_internal_os_Zygote.cpp +++ b/core/jni/com_android_internal_os_Zygote.cpp @@ -315,6 +315,8 @@ enum MountExternalKind { 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 @@ static void SpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray gids, } } + 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 c217caf1f264..5690af59236f 100644 --- a/core/proto/android/app/settings_enums.proto +++ b/core/proto/android/app/settings_enums.proto @@ -2437,4 +2437,10 @@ enum PageId { // > 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 0a2c59f765f3..a5f6564b73a6 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 e4dc99347802..4cc70ba6448d 100644 --- a/core/tests/coretests/src/android/os/BundleTest.java +++ b/core/tests/coretests/src/android/os/BundleTest.java @@ -30,12 +30,23 @@ import org.junit.runner.RunWith; * 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 @@ public class BundleTest { 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 @@ public class BundleTest { 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 000000000000..c4080e822a3f --- /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 6fd47c42bcba..66a59e09c42f 100644 --- a/media/java/android/media/RingtoneManager.java +++ b/media/java/android/media/RingtoneManager.java @@ -509,15 +509,19 @@ public class RingtoneManager { * @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 d4c4a62932e6..7d199850a39e 100644 --- a/media/java/android/media/tv/TvTrackInfo.java +++ b/media/java/android/media/tv/TvTrackInfo.java @@ -18,6 +18,7 @@ package android.media.tv; 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 @@ public final class TvTrackInfo implements Parcelable { 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 @@ public final class TvTrackInfo implements Parcelable { 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 @@ public final class TvTrackInfo implements Parcelable { 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 @@ public final class TvTrackInfo implements Parcelable { } /** + * 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 @@ public final class TvTrackInfo implements Parcelable { 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 @@ public final class TvTrackInfo implements Parcelable { 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 @@ public final class TvTrackInfo implements Parcelable { 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 @@ public final class TvTrackInfo implements Parcelable { } /** + * 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 @@ public final class TvTrackInfo implements Parcelable { * @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 000000000000..73254cdc79a9 --- /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 8922245ebb8f..40992b9286d3 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -43,6 +43,7 @@ java_library_static { "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 0816955ed531..b676f99eb40a 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.content.Intent; 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 @@ public final class BatteryService extends SystemService { 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 @@ public final class BatteryService extends SystemService { } private boolean shouldShutdownLocked() { + if (mHealthInfo2p1.batteryCapacityLevel != BatteryCapacityLevel.UNSUPPORTED) { + return (mHealthInfo2p1.batteryCapacityLevel == BatteryCapacityLevel.CRITICAL); + } if (mHealthInfo.batteryLevel > 0) { return false; } @@ -415,22 +421,23 @@ public final class BatteryService extends SystemService { } } - 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 @@ public final class BatteryService extends SystemService { 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 @@ public final class BatteryService extends SystemService { 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 5f032fc90dfe..52a2ca974d40 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -4783,7 +4783,7 @@ public class ConnectivityService extends IConnectivityManager.Stub return false; } - return vpn.startAlwaysOnVpn(); + return vpn.startAlwaysOnVpn(mKeyStore); } } @@ -4798,7 +4798,7 @@ public class ConnectivityService extends IConnectivityManager.Stub Slog.w(TAG, "User " + userId + " has no Vpn configuration"); return false; } - return vpn.isAlwaysOnPackageSupported(packageName); + return vpn.isAlwaysOnPackageSupported(packageName, mKeyStore); } } @@ -4819,11 +4819,11 @@ public class ConnectivityService extends IConnectivityManager.Stub 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 @@ public class ConnectivityService extends IConnectivityManager.Stub 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 @@ public class ConnectivityService extends IConnectivityManager.Stub 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 @@ public class ConnectivityService extends IConnectivityManager.Stub 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 c60460fccb76..41207c97492a 100644 --- a/services/core/java/com/android/server/DynamicSystemService.java +++ b/services/core/java/com/android/server/DynamicSystemService.java @@ -18,6 +18,7 @@ package com.android.server; 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 @@ public class DynamicSystemService extends IDynamicSystemService.Stub implements 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 4b48ef917744..da93998c83ba 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.pm.UserInfo; 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 @@ public class AdbDebuggingManager { } /** - * 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 c125b1baf860..f2a8615dca88 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.ArraySet; 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.File; 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 @@ public class AdbService extends IAdbManager.Stub { 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 @@ public class AdbService extends IAdbManager.Stub { } @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 @@ public class AdbService extends IAdbManager.Stub { } } - 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); - - // 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); + 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 in initAdbState", e); } + } - @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; - } - } + 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 @@ public class AdbService extends IAdbManager.Stub { 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 @@ public class AdbService extends IAdbManager.Stub { mDebuggingManager = new AdbDebuggingManager(context); } - mHandler = new AdbHandler(FgThread.get().getLooper()); - + initAdbState(); LocalServices.addService(AdbManagerInternal.class, new AdbManagerInternalImpl()); } @@ -219,7 +212,7 @@ public class AdbService extends IAdbManager.Stub { // 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 class AdbService extends IAdbManager.Stub { */ 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 @@ public class AdbService extends IAdbManager.Stub { 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 + } + + @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 + } - if (enable == mAdbEnabled) { + @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 8c60d0c759dc..a19ac5eb12db 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 @@ public final class BatteryStatsService extends IBatteryStats.Stub @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 @@ public final class BatteryStatsService extends IBatteryStats.Stub // 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 @@ public final class BatteryStatsService extends IBatteryStats.Stub 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 c2652c06e5a9..8520cb7c30b8 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.ActivityThread; 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.pm.dex.DexManager; 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 @@ public final class ProcessList { // 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 @@ public final class ProcessList { 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 f15d999e1006..cc5b7668e028 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 @@ final class CompatConfig { void addChange(CompatChange change) { synchronized (mChanges) { mChanges.put(change.getId(), change); + invalidateCache(); } } @@ -170,6 +172,7 @@ final class CompatConfig { addChange(c); } c.addPackageOverride(packageName, enabled); + invalidateCache(); } return alreadyKnown; } @@ -226,6 +229,7 @@ final class CompatConfig { // 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 @@ final class CompatConfig { addOverride(changeId, packageName, false); } + invalidateCache(); } } @@ -277,6 +282,7 @@ final class CompatConfig { throw new RuntimeException("Unable to call override validator!", e); } } + invalidateCache(); } } @@ -396,4 +402,8 @@ final class CompatConfig { 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 8692436f92e4..3c21d1a5169e 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 @@ public class Vpn { * 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 @@ public class Vpn { 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 @@ public class Vpn { mNetworkCapabilities.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN); updateCapabilities(null /* defaultNetwork */); - loadAlwaysOnPackage(); + loadAlwaysOnPackage(keyStore); } /** @@ -437,23 +437,36 @@ public class Vpn { /** * 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 @@ public class Vpn { } /** - * 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 @@ public class Vpn { } /** - * 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 @@ public class Vpn { } 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 @@ public class Vpn { } } - /** - * 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 @@ public class Vpn { 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 @@ public class Vpn { 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 @@ public class Vpn { } } - // 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 @@ public class Vpn { 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 @@ public class Vpn { } /** 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 @@ public class Vpn { // 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 @@ public class Vpn { 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 @@ public class Vpn { 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 @@ public class Vpn { 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 @@ public class Vpn { 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 @@ public class Vpn { // 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 e655b35d930f..6fa99235ceaa 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 @@ public final class JobStatus { 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 @@ public final class JobStatus { 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 a7f861836202..896bf676d45b 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { 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 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } /** - * 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 407f67e2fd8e..44f4ccf69cdd 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.mock; 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 @@ public class CompatConfigTest { // 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 ce5d6d9be770..717a78de7497 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 @@ public class PlatformCompatTest { @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 45b840ca976a..5bb75c92fd30 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.SharedPreferences; 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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser } @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 @@ public class UsbDeviceManager implements ActivityTaskManagerInternal.ScreenObser } 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 2c85b2759d55..bb5139929e14 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -45,10 +45,8 @@ import android.compat.annotation.UnsupportedAppUsage; 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 @@ public class TelephonyManager { } /** - * 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 5c18cdd51e07..d8339c8deefa 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -1622,16 +1622,6 @@ interface ITelephony { 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 39f1fc2d0a8d..48cb1cd84c07 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 final class BearerData { 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 @@ public final class BearerData { 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 @@ public final class BearerData { 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 @@ public final class BearerData { 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 final class BearerData { 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 @@ public final class BearerData { 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 d6a2176d7e81..2273bc61225c 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.assertNotNull; 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 @@ public class Ikev2VpnProfileTest { 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 8ed497b78b3c..77147c8a35af 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.SystemClock; 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 class ConnectivityServiceTest { 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 ac1c51837e93..1994d1f2ed45 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.ServiceInfo; 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.Looper; 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 androidx.test.runner.AndroidJUnit4; 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.InOrder; 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 @@ public class VpnTest { } 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 @@ public class VpnTest { @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 @@ public class VpnTest { 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 @@ public class VpnTest { 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 @@ public class VpnTest { 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 @@ public class VpnTest { 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 @@ public class VpnTest { 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 @@ public class VpnTest { 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 @@ public class VpnTest { 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 @@ public class VpnTest { 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 @@ public class VpnTest { 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 @@ public class VpnTest { 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 @@ public class VpnTest { .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 @@ public class VpnTest { .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 @@ public class VpnTest { .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 @@ public class VpnTest { 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) { |