summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xapi/system-current.txt31
-rw-r--r--api/test-current.txt1
-rw-r--r--core/java/android/app/PropertyInvalidatedCache.java428
-rw-r--r--core/java/android/app/compat/ChangeIdStateCache.java86
-rw-r--r--core/java/android/app/compat/ChangeIdStateQuery.java87
-rw-r--r--core/java/android/app/compat/CompatChanges.java32
-rw-r--r--core/java/android/net/ConnectivityManager.java20
-rw-r--r--core/java/android/os/image/DynamicSystemManager.java18
-rw-r--r--core/java/android/os/image/IDynamicSystemService.aidl10
-rw-r--r--core/res/AndroidManifest.xml1
-rw-r--r--core/tests/systemproperties/src/android/os/PropertyInvalidatedCacheTest.java168
-rw-r--r--services/core/java/com/android/server/ConnectivityService.java7
-rw-r--r--services/core/java/com/android/server/DynamicSystemService.java10
-rw-r--r--services/core/java/com/android/server/compat/CompatConfig.java10
-rw-r--r--services/core/java/com/android/server/compat/PlatformCompat.java2
-rw-r--r--services/core/java/com/android/server/connectivity/Vpn.java107
-rw-r--r--services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java1
-rw-r--r--tests/net/java/com/android/server/ConnectivityServiceTest.java119
-rw-r--r--tests/net/java/com/android/server/connectivity/VpnTest.java60
20 files changed, 1105 insertions, 95 deletions
diff --git a/api/system-current.txt b/api/system-current.txt
index 202d51ff9d92..5463cbab4e20 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -130,6 +130,7 @@ package android {
field public static final String NETWORK_FACTORY = "android.permission.NETWORK_FACTORY";
field public static final String NETWORK_MANAGED_PROVISIONING = "android.permission.NETWORK_MANAGED_PROVISIONING";
field public static final String NETWORK_SCAN = "android.permission.NETWORK_SCAN";
+ field public static final String NETWORK_SETTINGS = "android.permission.NETWORK_SETTINGS";
field public static final String NETWORK_SETUP_WIZARD = "android.permission.NETWORK_SETUP_WIZARD";
field public static final String NETWORK_SIGNAL_STRENGTH_WAKEUP = "android.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP";
field public static final String NETWORK_STACK = "android.permission.NETWORK_STACK";
@@ -4405,13 +4406,13 @@ package android.net {
public class ConnectivityManager {
method @NonNull @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) public android.net.SocketKeepalive createNattKeepalive(@NonNull android.net.Network, @NonNull android.os.ParcelFileDescriptor, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback);
method @NonNull @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull java.net.Socket, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback);
- method @Deprecated @RequiresPermission("android.permission.NETWORK_SETTINGS") public String getCaptivePortalServerUrl();
+ method @Deprecated @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public String getCaptivePortalServerUrl();
method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void getLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEntitlementResultListener);
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported();
method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public int registerNetworkProvider(@NonNull android.net.NetworkProvider);
method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEventCallback);
method @Deprecated public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, int, int, @NonNull android.os.Handler);
- method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void setAirplaneMode(boolean);
+ method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void setAirplaneMode(boolean);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public boolean shouldAvoidBadWifi();
method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void startCaptivePortalApp(@NonNull android.net.Network, @NonNull android.os.Bundle);
method @Deprecated @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback);
@@ -5784,13 +5785,13 @@ package android.net.wifi {
public class WifiManager {
method @RequiresPermission("android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE") public void addOnWifiUsabilityStatsListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.OnWifiUsabilityStatsListener);
- method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void connect(@NonNull android.net.wifi.WifiConfiguration, @Nullable android.net.wifi.WifiManager.ActionListener);
- method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void connect(int, @Nullable android.net.wifi.WifiManager.ActionListener);
- method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void disable(int, @Nullable android.net.wifi.WifiManager.ActionListener);
- method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void forget(int, @Nullable android.net.wifi.WifiManager.ActionListener);
- method @NonNull @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.util.Pair<android.net.wifi.WifiConfiguration,java.util.Map<java.lang.Integer,java.util.List<android.net.wifi.ScanResult>>>> getAllMatchingWifiConfigs(@NonNull java.util.List<android.net.wifi.ScanResult>);
- method @NonNull @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.Map<android.net.wifi.hotspot2.OsuProvider,java.util.List<android.net.wifi.ScanResult>> getMatchingOsuProviders(@Nullable java.util.List<android.net.wifi.ScanResult>);
- method @NonNull @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.Map<android.net.wifi.hotspot2.OsuProvider,android.net.wifi.hotspot2.PasspointConfiguration> getMatchingPasspointConfigsForOsuProviders(@NonNull java.util.Set<android.net.wifi.hotspot2.OsuProvider>);
+ method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void connect(@NonNull android.net.wifi.WifiConfiguration, @Nullable android.net.wifi.WifiManager.ActionListener);
+ method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void connect(int, @Nullable android.net.wifi.WifiManager.ActionListener);
+ method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void disable(int, @Nullable android.net.wifi.WifiManager.ActionListener);
+ method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void forget(int, @Nullable android.net.wifi.WifiManager.ActionListener);
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.util.Pair<android.net.wifi.WifiConfiguration,java.util.Map<java.lang.Integer,java.util.List<android.net.wifi.ScanResult>>>> getAllMatchingWifiConfigs(@NonNull java.util.List<android.net.wifi.ScanResult>);
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.Map<android.net.wifi.hotspot2.OsuProvider,java.util.List<android.net.wifi.ScanResult>> getMatchingOsuProviders(@Nullable java.util.List<android.net.wifi.ScanResult>);
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.Map<android.net.wifi.hotspot2.OsuProvider,android.net.wifi.hotspot2.PasspointConfiguration> getMatchingPasspointConfigsForOsuProviders(@NonNull java.util.Set<android.net.wifi.hotspot2.OsuProvider>);
method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_WIFI_STATE, android.Manifest.permission.READ_WIFI_CREDENTIAL}) public java.util.List<android.net.wifi.WifiConfiguration> getPrivilegedConfiguredNetworks();
method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public android.net.wifi.WifiConfiguration getWifiApConfiguration();
method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public int getWifiApState();
@@ -5798,17 +5799,17 @@ package android.net.wifi {
method public boolean isPortableHotspotSupported();
method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public boolean isWifiApEnabled();
method public boolean isWifiScannerSupported();
- method @RequiresPermission("android.permission.NETWORK_SETTINGS") public void registerSoftApCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.SoftApCallback);
+ method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void registerSoftApCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.SoftApCallback);
method @RequiresPermission("android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE") public void removeOnWifiUsabilityStatsListener(@NonNull android.net.wifi.WifiManager.OnWifiUsabilityStatsListener);
- method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void save(@NonNull android.net.wifi.WifiConfiguration, @Nullable android.net.wifi.WifiManager.ActionListener);
+ method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void save(@NonNull android.net.wifi.WifiConfiguration, @Nullable android.net.wifi.WifiManager.ActionListener);
method @RequiresPermission("android.permission.WIFI_SET_DEVICE_MOBILITY_STATE") public void setDeviceMobilityState(int);
method @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE) public boolean setWifiApConfiguration(android.net.wifi.WifiConfiguration);
- method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startEasyConnectAsConfiguratorInitiator(@NonNull String, int, int, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.EasyConnectStatusCallback);
- method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startEasyConnectAsEnrolleeInitiator(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.EasyConnectStatusCallback);
+ method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startEasyConnectAsConfiguratorInitiator(@NonNull String, int, int, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.EasyConnectStatusCallback);
+ method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startEasyConnectAsEnrolleeInitiator(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.EasyConnectStatusCallback);
method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public boolean startScan(android.os.WorkSource);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public boolean startSoftAp(@Nullable android.net.wifi.WifiConfiguration);
- method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startSubscriptionProvisioning(@NonNull android.net.wifi.hotspot2.OsuProvider, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.hotspot2.ProvisioningCallback);
- method @RequiresPermission(anyOf={"android.permission.NETWORK_SETTINGS", android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void stopEasyConnectSession();
+ method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void startSubscriptionProvisioning(@NonNull android.net.wifi.hotspot2.OsuProvider, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.hotspot2.ProvisioningCallback);
+ method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void stopEasyConnectSession();
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public boolean stopSoftAp();
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void updateInterfaceIpState(@Nullable String, int);
method @RequiresPermission("android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE") public void updateWifiUsabilityScore(int, int, int);
diff --git a/api/test-current.txt b/api/test-current.txt
index b3e80a98a2e2..d0c13724b363 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -14,6 +14,7 @@ package android {
field public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES";
field public static final String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS";
field public static final String MANAGE_ROLLBACKS = "android.permission.MANAGE_ROLLBACKS";
+ field public static final String NETWORK_SETTINGS = "android.permission.NETWORK_SETTINGS";
field public static final String NETWORK_STACK = "android.permission.NETWORK_STACK";
field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
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/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index cb3140487f35..5405ad37ffda 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -4716,19 +4716,19 @@ public class ConnectivityManager {
/**
* Returns the {@code uid} of the owner of a network connection.
*
- * @param protocol The protocol of the connection. Only {@code IPPROTO_TCP} and
- * {@code IPPROTO_UDP} currently supported.
+ * @param protocol The protocol of the connection. Only {@code IPPROTO_TCP} and {@code
+ * IPPROTO_UDP} currently supported.
* @param local The local {@link InetSocketAddress} of a connection.
* @param remote The remote {@link InetSocketAddress} of a connection.
- *
* @return {@code uid} if the connection is found and the app has permission to observe it
- * (e.g., if it is associated with the calling VPN app's tunnel) or
- * {@link android.os.Process#INVALID_UID} if the connection is not found.
- * Throws {@link SecurityException} if the caller is not the active VPN for the current user.
- * Throws {@link IllegalArgumentException} if an unsupported protocol is requested.
- */
- public int getConnectionOwnerUid(int protocol, @NonNull InetSocketAddress local,
- @NonNull InetSocketAddress remote) {
+ * (e.g., if it is associated with the calling VPN app's VpnService tunnel) or {@link
+ * android.os.Process#INVALID_UID} if the connection is not found.
+ * @throws {@link SecurityException} if the caller is not the active VpnService for the current
+ * user.
+ * @throws {@link IllegalArgumentException} if an unsupported protocol is requested.
+ */
+ public int getConnectionOwnerUid(
+ int protocol, @NonNull InetSocketAddress local, @NonNull InetSocketAddress remote) {
ConnectionInfo connectionInfo = new ConnectionInfo(protocol, local, remote);
try {
return mService.getConnectionOwnerUid(connectionInfo);
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/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 64dcfa7d1266..0a2c59f765f3 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1640,6 +1640,7 @@
<!-- Allows Settings and SystemUI to call methods in Networking services
<p>Not for use by third-party or privileged applications.
+ @SystemApi @TestApi
@hide This should only be used by Settings and SystemUI.
-->
<permission android:name="android.permission.NETWORK_SETTINGS"
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/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 1ce9d9157a54..5f032fc90dfe 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -7528,6 +7528,13 @@ public class ConnectivityService extends IConnectivityManager.Stub
*/
public int getConnectionOwnerUid(ConnectionInfo connectionInfo) {
final Vpn vpn = enforceActiveVpnOrNetworkStackPermission();
+
+ // Only VpnService based VPNs should be able to get this information.
+ if (vpn != null && vpn.getActiveAppVpnType() != VpnManager.TYPE_VPN_SERVICE) {
+ throw new SecurityException(
+ "getConnectionOwnerUid() not allowed for non-VpnService VPNs");
+ }
+
if (connectionInfo.protocol != IPPROTO_TCP && connectionInfo.protocol != IPPROTO_UDP) {
throw new IllegalArgumentException("Unsupported protocol " + connectionInfo.protocol);
}
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/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/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index a5d9aa2c9a46..85b8ec699f02 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -234,8 +234,8 @@ public class PlatformCompat extends IPlatformCompat.Stub {
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- checkCompatChangeReadAndLogPermission();
if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, "platform_compat", pw)) return;
+ checkCompatChangeReadAndLogPermission();
mCompatConfig.dumpConfig(pw);
}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 7f6dc55a369d..8692436f92e4 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -794,10 +794,10 @@ public class Vpn {
// ignore
}
mContext.unbindService(mConnection);
- mConnection = null;
+ cleanupVpnStateLocked();
} else if (mVpnRunner != null) {
+ // cleanupVpnStateLocked() is called from mVpnRunner.exit()
mVpnRunner.exit();
- mVpnRunner = null;
}
try {
@@ -1104,7 +1104,6 @@ public class Vpn {
*/
public synchronized ParcelFileDescriptor establish(VpnConfig config) {
// Check if the caller is already prepared.
- UserManager mgr = UserManager.get(mContext);
if (Binder.getCallingUid() != mOwnerUID) {
return null;
}
@@ -1118,10 +1117,7 @@ public class Vpn {
long token = Binder.clearCallingIdentity();
try {
// Restricted users are not allowed to create VPNs, they are tied to Owner
- UserInfo user = mgr.getUserInfo(mUserHandle);
- if (user.isRestricted()) {
- throw new SecurityException("Restricted users cannot establish VPNs");
- }
+ enforceNotRestrictedUser();
ResolveInfo info = AppGlobals.getPackageManager().resolveService(intent,
null, 0, mUserHandle);
@@ -1543,24 +1539,30 @@ public class Vpn {
public void interfaceRemoved(String interfaze) {
synchronized (Vpn.this) {
if (interfaze.equals(mInterface) && jniCheck(interfaze) == 0) {
- mStatusIntent = null;
- mNetworkCapabilities.setUids(null);
- mConfig = null;
- mInterface = null;
if (mConnection != null) {
mContext.unbindService(mConnection);
- mConnection = null;
- agentDisconnect();
+ cleanupVpnStateLocked();
} else if (mVpnRunner != null) {
- // agentDisconnect must be called from mVpnRunner.exit()
+ // cleanupVpnStateLocked() is called from mVpnRunner.exit()
mVpnRunner.exit();
- mVpnRunner = null;
}
}
}
}
};
+ private void cleanupVpnStateLocked() {
+ mStatusIntent = null;
+ mNetworkCapabilities.setUids(null);
+ mConfig = null;
+ mInterface = null;
+
+ // Unconditionally clear both VpnService and VpnRunner fields.
+ mVpnRunner = null;
+ mConnection = null;
+ agentDisconnect();
+ }
+
private void enforceControlPermission() {
mContext.enforceCallingPermission(Manifest.permission.CONTROL_VPN, "Unauthorized Caller");
}
@@ -1673,6 +1675,25 @@ public class Vpn {
}
/**
+ * Gets the currently running App-based VPN type
+ *
+ * @return the {@link VpnManager.VpnType}. {@link VpnManager.TYPE_VPN_NONE} if not running an
+ * app-based VPN. While VpnService-based VPNs are always app VPNs and LegacyVpn is always
+ * Settings-based, the Platform VPNs can be initiated by both apps and Settings.
+ */
+ public synchronized int getActiveAppVpnType() {
+ if (VpnConfig.LEGACY_VPN.equals(mPackage)) {
+ return VpnManager.TYPE_VPN_NONE;
+ }
+
+ if (mVpnRunner != null && mVpnRunner instanceof IkeV2VpnRunner) {
+ return VpnManager.TYPE_VPN_PLATFORM;
+ } else {
+ return VpnManager.TYPE_VPN_SERVICE;
+ }
+ }
+
+ /**
* @param uid The target uid.
*
* @return {@code true} if {@code uid} is included in one of the mBlockedUidsAsToldToNetd
@@ -1800,6 +1821,17 @@ public class Vpn {
throw new IllegalStateException("Unable to find IPv4 default gateway");
}
+ private void enforceNotRestrictedUser() {
+ Binder.withCleanCallingIdentity(() -> {
+ final UserManager mgr = UserManager.get(mContext);
+ final UserInfo user = mgr.getUserInfo(mUserHandle);
+
+ if (user.isRestricted()) {
+ throw new SecurityException("Restricted users cannot configure VPNs");
+ }
+ });
+ }
+
/**
* Start legacy VPN, controlling native daemons as needed. Creates a
* secondary thread to perform connection work, returning quickly.
@@ -2020,7 +2052,25 @@ public class Vpn {
public abstract void run();
- protected abstract void exit();
+ /**
+ * Disconnects the NetworkAgent and cleans up all state related to the VpnRunner.
+ *
+ * <p>All outer Vpn instance state is cleaned up in cleanupVpnStateLocked()
+ */
+ protected abstract void exitVpnRunner();
+
+ /**
+ * Triggers the cleanup of the VpnRunner, and additionally cleans up Vpn instance-wide state
+ *
+ * <p>This method ensures that simple calls to exit() will always clean up global state
+ * properly.
+ */
+ protected final void exit() {
+ synchronized (Vpn.this) {
+ exitVpnRunner();
+ cleanupVpnStateLocked();
+ }
+ }
}
interface IkeV2VpnRunnerCallback {
@@ -2350,17 +2400,6 @@ public class Vpn {
}
/**
- * Triggers cleanup of outer class' state
- *
- * <p>Can be called from any thread, as it does not mutate state in the Ikev2VpnRunner.
- */
- private void cleanupVpnState() {
- synchronized (Vpn.this) {
- agentDisconnect();
- }
- }
-
- /**
* Cleans up all Ikev2VpnRunner internal state
*
* <p>This method MUST always be called on the mExecutor thread in order to ensure
@@ -2379,10 +2418,7 @@ public class Vpn {
}
@Override
- public void exit() {
- // Cleanup outer class' state immediately, otherwise race conditions may ensue.
- cleanupVpnState();
-
+ public void exitVpnRunner() {
mExecutor.execute(() -> {
shutdownVpnRunner();
});
@@ -2481,10 +2517,9 @@ public class Vpn {
/** Tears down this LegacyVpn connection */
@Override
- public void exit() {
+ public void exitVpnRunner() {
// We assume that everything is reset after stopping the daemons.
interrupt();
- agentDisconnect();
try {
mContext.unregisterReceiver(mBroadcastReceiver);
} catch (IllegalArgumentException e) {}
@@ -2757,6 +2792,7 @@ public class Vpn {
checkNotNull(keyStore, "KeyStore missing");
verifyCallingUidAndPackage(packageName);
+ enforceNotRestrictedUser();
final byte[] encodedProfile = profile.encode();
if (encodedProfile.length > MAX_VPN_PROFILE_SIZE_BYTES) {
@@ -2792,6 +2828,7 @@ public class Vpn {
checkNotNull(keyStore, "KeyStore missing");
verifyCallingUidAndPackage(packageName);
+ enforceNotRestrictedUser();
Binder.withCleanCallingIdentity(
() -> {
@@ -2834,6 +2871,8 @@ public class Vpn {
checkNotNull(packageName, "No package name provided");
checkNotNull(keyStore, "KeyStore missing");
+ enforceNotRestrictedUser();
+
// Prepare VPN for startup
if (!prepare(packageName, null /* newPackage */, VpnManager.TYPE_VPN_PLATFORM)) {
throw new SecurityException("User consent not granted for package " + packageName);
@@ -2899,6 +2938,8 @@ public class Vpn {
public synchronized void stopVpnProfile(@NonNull String packageName) {
checkNotNull(packageName, "No package name provided");
+ enforceNotRestrictedUser();
+
// 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) {
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/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 6fb4612245f1..8ed497b78b3c 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -78,6 +78,7 @@ import static android.net.NetworkPolicyManager.RULE_NONE;
import static android.net.NetworkPolicyManager.RULE_REJECT_ALL;
import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
import static android.net.RouteInfo.RTN_UNREACHABLE;
+import static android.system.OsConstants.IPPROTO_TCP;
import static com.android.server.ConnectivityServiceTestUtilsKt.transportToLegacyType;
import static com.android.testutils.ConcurrentUtilsKt.await;
@@ -138,6 +139,7 @@ import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.location.LocationManager;
import android.net.CaptivePortalData;
+import android.net.ConnectionInfo;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.ConnectivityManager.PacketKeepalive;
@@ -153,6 +155,7 @@ import android.net.INetworkMonitorCallbacks;
import android.net.INetworkPolicyListener;
import android.net.INetworkPolicyManager;
import android.net.INetworkStatsService;
+import android.net.InetAddresses;
import android.net.InterfaceConfiguration;
import android.net.IpPrefix;
import android.net.IpSecManager;
@@ -176,6 +179,7 @@ import android.net.RouteInfo;
import android.net.SocketKeepalive;
import android.net.UidRange;
import android.net.Uri;
+import android.net.VpnManager;
import android.net.metrics.IpConnectivityLog;
import android.net.shared.NetworkMonitorUtils;
import android.net.shared.PrivateDnsConfig;
@@ -272,6 +276,7 @@ import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate;
+import java.util.function.Supplier;
import kotlin.reflect.KClass;
@@ -445,15 +450,21 @@ public class ConnectivityServiceTest {
return mPackageManager;
}
+ private int checkMockedPermission(String permission, Supplier<Integer> ifAbsent) {
+ final Integer granted = mMockedPermissions.get(permission);
+ return granted != null ? granted : ifAbsent.get();
+ }
+
@Override
public int checkPermission(String permission, int pid, int uid) {
- final Integer granted = mMockedPermissions.get(permission);
- if (granted == null) {
- // All non-mocked permissions should be held by the test or unnecessary: check as
- // normal to make sure the code does not rely on unexpected permissions.
- return super.checkPermission(permission, pid, uid);
- }
- return granted;
+ return checkMockedPermission(
+ permission, () -> super.checkPermission(permission, pid, uid));
+ }
+
+ @Override
+ public int checkCallingOrSelfPermission(String permission) {
+ return checkMockedPermission(
+ permission, () -> super.checkCallingOrSelfPermission(permission));
}
@Override
@@ -1002,6 +1013,7 @@ public class ConnectivityServiceTest {
// Careful ! This is different from mNetworkAgent, because MockNetworkAgent does
// not inherit from NetworkAgent.
private TestNetworkAgentWrapper mMockNetworkAgent;
+ private int mVpnType = VpnManager.TYPE_VPN_SERVICE;
private VpnInfo mVpnInfo;
@@ -1022,6 +1034,10 @@ public class ConnectivityServiceTest {
updateCapabilities(null /* defaultNetwork */);
}
+ public void setVpnType(int vpnType) {
+ mVpnType = vpnType;
+ }
+
@Override
public int getNetId() {
if (mMockNetworkAgent == null) {
@@ -1040,6 +1056,11 @@ public class ConnectivityServiceTest {
return mConnected; // Similar trickery
}
+ @Override
+ public int getActiveAppVpnType() {
+ return mVpnType;
+ }
+
private void connect(boolean isAlwaysMetered) {
mNetworkCapabilities.set(mMockNetworkAgent.getNetworkCapabilities());
mConnected = true;
@@ -6429,6 +6450,90 @@ public class ConnectivityServiceTest {
assertEquals(Process.INVALID_UID, newNc.getOwnerUid());
}
+ private void setupConnectionOwnerUid(int vpnOwnerUid, @VpnManager.VpnType int vpnType)
+ throws Exception {
+ final Set<UidRange> vpnRange = Collections.singleton(UidRange.createForUser(VPN_USER));
+ establishVpn(new LinkProperties(), vpnOwnerUid, vpnRange);
+ mMockVpn.setVpnType(vpnType);
+
+ final VpnInfo vpnInfo = new VpnInfo();
+ vpnInfo.ownerUid = vpnOwnerUid;
+ mMockVpn.setVpnInfo(vpnInfo);
+ }
+
+ private void setupConnectionOwnerUidAsVpnApp(int vpnOwnerUid, @VpnManager.VpnType int vpnType)
+ throws Exception {
+ setupConnectionOwnerUid(vpnOwnerUid, vpnType);
+
+ // Test as VPN app
+ mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED);
+ mServiceContext.setPermission(
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, PERMISSION_DENIED);
+ }
+
+ private ConnectionInfo getTestConnectionInfo() throws Exception {
+ return new ConnectionInfo(
+ IPPROTO_TCP,
+ new InetSocketAddress(InetAddresses.parseNumericAddress("1.2.3.4"), 1234),
+ new InetSocketAddress(InetAddresses.parseNumericAddress("2.3.4.5"), 2345));
+ }
+
+ @Test
+ public void testGetConnectionOwnerUidPlatformVpn() throws Exception {
+ final int myUid = Process.myUid();
+ setupConnectionOwnerUidAsVpnApp(myUid, VpnManager.TYPE_VPN_PLATFORM);
+
+ try {
+ mService.getConnectionOwnerUid(getTestConnectionInfo());
+ fail("Expected SecurityException for non-VpnService app");
+ } catch (SecurityException expected) {
+ }
+ }
+
+ @Test
+ public void testGetConnectionOwnerUidVpnServiceWrongUser() throws Exception {
+ final int myUid = Process.myUid();
+ setupConnectionOwnerUidAsVpnApp(myUid + 1, VpnManager.TYPE_VPN_SERVICE);
+
+ try {
+ mService.getConnectionOwnerUid(getTestConnectionInfo());
+ fail("Expected SecurityException for non-VpnService app");
+ } catch (SecurityException expected) {
+ }
+ }
+
+ @Test
+ public void testGetConnectionOwnerUidVpnServiceDoesNotThrow() throws Exception {
+ final int myUid = Process.myUid();
+ setupConnectionOwnerUidAsVpnApp(myUid, VpnManager.TYPE_VPN_SERVICE);
+
+ // TODO: Test the returned UID
+ mService.getConnectionOwnerUid(getTestConnectionInfo());
+ }
+
+ @Test
+ public void testGetConnectionOwnerUidVpnServiceNetworkStackDoesNotThrow() throws Exception {
+ final int myUid = Process.myUid();
+ setupConnectionOwnerUid(myUid, VpnManager.TYPE_VPN_SERVICE);
+ mServiceContext.setPermission(
+ android.Manifest.permission.NETWORK_STACK, PERMISSION_GRANTED);
+
+ // TODO: Test the returned UID
+ mService.getConnectionOwnerUid(getTestConnectionInfo());
+ }
+
+ @Test
+ public void testGetConnectionOwnerUidVpnServiceMainlineNetworkStackDoesNotThrow()
+ throws Exception {
+ final int myUid = Process.myUid();
+ setupConnectionOwnerUid(myUid, VpnManager.TYPE_VPN_SERVICE);
+ mServiceContext.setPermission(
+ NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, PERMISSION_GRANTED);
+
+ // TODO: Test the returned UID
+ mService.getConnectionOwnerUid(getTestConnectionInfo());
+ }
+
private TestNetworkAgentWrapper establishVpn(
LinkProperties lp, int ownerUid, Set<UidRange> vpnRange) throws Exception {
final TestNetworkAgentWrapper
diff --git a/tests/net/java/com/android/server/connectivity/VpnTest.java b/tests/net/java/com/android/server/connectivity/VpnTest.java
index eb78529e8715..ac1c51837e93 100644
--- a/tests/net/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/net/java/com/android/server/connectivity/VpnTest.java
@@ -656,8 +656,12 @@ public class VpnTest {
}
private Vpn createVpnAndSetupUidChecks(int... grantedOps) throws Exception {
- final Vpn vpn = createVpn(primaryUser.id);
- setMockedUsers(primaryUser);
+ return createVpnAndSetupUidChecks(primaryUser, grantedOps);
+ }
+
+ private Vpn createVpnAndSetupUidChecks(UserInfo user, int... grantedOps) throws Exception {
+ final Vpn vpn = createVpn(user.id);
+ setMockedUsers(user);
when(mPackageManager.getPackageUidAsUser(eq(TEST_VPN_PKG), anyInt()))
.thenReturn(Process.myUid());
@@ -726,6 +730,19 @@ public class VpnTest {
}
@Test
+ public void testProvisionVpnProfileRestrictedUser() throws Exception {
+ final Vpn vpn =
+ createVpnAndSetupUidChecks(
+ restrictedProfileA, AppOpsManager.OP_ACTIVATE_PLATFORM_VPN);
+
+ try {
+ vpn.provisionVpnProfile(TEST_VPN_PKG, mVpnProfile, mKeyStore);
+ fail("Expected SecurityException due to restricted user");
+ } catch (SecurityException expected) {
+ }
+ }
+
+ @Test
public void testDeleteVpnProfile() throws Exception {
final Vpn vpn = createVpnAndSetupUidChecks();
@@ -736,6 +753,19 @@ public class VpnTest {
}
@Test
+ public void testDeleteVpnProfileRestrictedUser() throws Exception {
+ final Vpn vpn =
+ createVpnAndSetupUidChecks(
+ restrictedProfileA, AppOpsManager.OP_ACTIVATE_PLATFORM_VPN);
+
+ try {
+ vpn.deleteVpnProfile(TEST_VPN_PKG, mKeyStore);
+ fail("Expected SecurityException due to restricted user");
+ } catch (SecurityException expected) {
+ }
+ }
+
+ @Test
public void testGetVpnProfilePrivileged() throws Exception {
final Vpn vpn = createVpnAndSetupUidChecks();
@@ -820,6 +850,32 @@ public class VpnTest {
}
@Test
+ public void testStartVpnProfileRestrictedUser() throws Exception {
+ final Vpn vpn =
+ createVpnAndSetupUidChecks(
+ restrictedProfileA, AppOpsManager.OP_ACTIVATE_PLATFORM_VPN);
+
+ try {
+ vpn.startVpnProfile(TEST_VPN_PKG, mKeyStore);
+ fail("Expected SecurityException due to restricted user");
+ } catch (SecurityException expected) {
+ }
+ }
+
+ @Test
+ public void testStopVpnProfileRestrictedUser() throws Exception {
+ final Vpn vpn =
+ createVpnAndSetupUidChecks(
+ restrictedProfileA, AppOpsManager.OP_ACTIVATE_PLATFORM_VPN);
+
+ try {
+ vpn.stopVpnProfile(TEST_VPN_PKG);
+ fail("Expected SecurityException due to restricted user");
+ } catch (SecurityException expected) {
+ }
+ }
+
+ @Test
public void testSetPackageAuthorizationVpnService() throws Exception {
final Vpn vpn = createVpnAndSetupUidChecks();