[BR14] Tracker Data Saver status in ConnectivityService
BYPASS_INCLUSIVE_LANGUAGE_REASON=Using public API
Test: atest ConnectivityCoverageTests:android.net.connectivity.com.android.server.CSBpfNetMapsTest
(on U and V devices respectively)
Bug: 297836825
Fix: 314858283
Change-Id: Ibeac8cd3a33468c6539234255328affc6b3f8ffe
This commit is contained in:
@@ -16,8 +16,6 @@
|
||||
package android.net;
|
||||
|
||||
import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
|
||||
import static android.content.pm.ApplicationInfo.FLAG_PERSISTENT;
|
||||
import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
|
||||
import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1;
|
||||
import static android.net.NetworkRequest.Type.BACKGROUND_REQUEST;
|
||||
import static android.net.NetworkRequest.Type.LISTEN;
|
||||
@@ -27,8 +25,6 @@ import static android.net.NetworkRequest.Type.TRACK_DEFAULT;
|
||||
import static android.net.NetworkRequest.Type.TRACK_SYSTEM_DEFAULT;
|
||||
import static android.net.QosCallback.QosCallbackRegistrationException;
|
||||
|
||||
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
|
||||
|
||||
import android.annotation.CallbackExecutor;
|
||||
import android.annotation.FlaggedApi;
|
||||
import android.annotation.IntDef;
|
||||
@@ -41,16 +37,12 @@ import android.annotation.SdkConstant.SdkConstantType;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.SystemApi;
|
||||
import android.annotation.SystemService;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Application;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.compat.annotation.UnsupportedAppUsage;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.net.ConnectivityDiagnosticsManager.DataStallReport.DetectionMethod;
|
||||
import android.net.IpSecManager.UdpEncapsulationSocket;
|
||||
import android.net.SocketKeepalive.Callback;
|
||||
@@ -82,8 +74,6 @@ import android.util.Range;
|
||||
import android.util.SparseIntArray;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.modules.utils.build.SdkLevel;
|
||||
|
||||
import libcore.net.event.NetworkEventDispatcher;
|
||||
|
||||
@@ -105,7 +95,6 @@ import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.RejectedExecutionException;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* Class that answers queries about the state of network connectivity. It also
|
||||
@@ -6261,90 +6250,6 @@ public class ConnectivityManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to track data saver status.
|
||||
*
|
||||
* The class will fetch current data saver status from {@link NetworkPolicyManager} when
|
||||
* initialized, and listening for status changed intent to cache the latest status.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@TargetApi(Build.VERSION_CODES.TIRAMISU) // RECEIVER_NOT_EXPORTED requires T.
|
||||
@VisibleForTesting(visibility = PRIVATE)
|
||||
public static class DataSaverStatusTracker extends BroadcastReceiver {
|
||||
private static final Object sDataSaverStatusTrackerLock = new Object();
|
||||
|
||||
private static volatile DataSaverStatusTracker sInstance;
|
||||
|
||||
/**
|
||||
* Gets a static instance of the class.
|
||||
*
|
||||
* @param context A {@link Context} for initialization. Note that since the data saver
|
||||
* status is global on a device, passing any context is equivalent.
|
||||
* @return The static instance of a {@link DataSaverStatusTracker}.
|
||||
*/
|
||||
public static DataSaverStatusTracker getInstance(@NonNull Context context) {
|
||||
if (sInstance == null) {
|
||||
synchronized (sDataSaverStatusTrackerLock) {
|
||||
if (sInstance == null) {
|
||||
sInstance = new DataSaverStatusTracker(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
private final NetworkPolicyManager mNpm;
|
||||
// The value updates on the caller's binder thread or UI thread.
|
||||
private final AtomicBoolean mIsDataSaverEnabled;
|
||||
|
||||
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
|
||||
public DataSaverStatusTracker(final Context context) {
|
||||
// To avoid leaks, take the application context.
|
||||
final Context appContext;
|
||||
if (context instanceof Application) {
|
||||
appContext = context;
|
||||
} else {
|
||||
appContext = context.getApplicationContext();
|
||||
}
|
||||
|
||||
if ((appContext.getApplicationInfo().flags & FLAG_PERSISTENT) == 0
|
||||
&& (appContext.getApplicationInfo().flags & FLAG_SYSTEM) == 0) {
|
||||
throw new IllegalStateException("Unexpected caller: "
|
||||
+ appContext.getApplicationInfo().packageName);
|
||||
}
|
||||
|
||||
mNpm = appContext.getSystemService(NetworkPolicyManager.class);
|
||||
final IntentFilter filter = new IntentFilter(
|
||||
ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED);
|
||||
// The receiver should not receive broadcasts from other Apps.
|
||||
appContext.registerReceiver(this, filter, Context.RECEIVER_NOT_EXPORTED);
|
||||
mIsDataSaverEnabled = new AtomicBoolean();
|
||||
updateDataSaverEnabled();
|
||||
}
|
||||
|
||||
// Runs on caller's UI thread.
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (intent.getAction().equals(ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED)) {
|
||||
updateDataSaverEnabled();
|
||||
} else {
|
||||
throw new IllegalStateException("Unexpected intent " + intent);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean getDataSaverEnabled() {
|
||||
return mIsDataSaverEnabled.get();
|
||||
}
|
||||
|
||||
private void updateDataSaverEnabled() {
|
||||
// Uid doesn't really matter, but use a fixed UID to make things clearer.
|
||||
final int dataSaverForCallerUid = mNpm.getRestrictBackgroundStatus(Process.SYSTEM_UID);
|
||||
mIsDataSaverEnabled.set(dataSaverForCallerUid
|
||||
!= ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the network is blocked for the given uid and metered condition.
|
||||
*
|
||||
@@ -6370,17 +6275,12 @@ public class ConnectivityManager {
|
||||
@RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)
|
||||
public boolean isUidNetworkingBlocked(int uid, boolean isNetworkMetered) {
|
||||
final BpfNetMapsReader reader = BpfNetMapsReader.getInstance();
|
||||
|
||||
final boolean isDataSaverEnabled;
|
||||
if (SdkLevel.isAtLeastV()) {
|
||||
isDataSaverEnabled = reader.getDataSaverEnabled();
|
||||
} else {
|
||||
final DataSaverStatusTracker dataSaverStatusTracker =
|
||||
DataSaverStatusTracker.getInstance(mContext);
|
||||
isDataSaverEnabled = dataSaverStatusTracker.getDataSaverEnabled();
|
||||
}
|
||||
|
||||
return reader.isUidNetworkingBlocked(uid, isNetworkMetered, isDataSaverEnabled);
|
||||
// Note that before V, the data saver status in bpf is written by ConnectivityService
|
||||
// when receiving {@link #ACTION_RESTRICT_BACKGROUND_CHANGED}. Thus,
|
||||
// the status is not synchronized.
|
||||
// On V+, the data saver status is set by platform code when enabling/disabling
|
||||
// data saver, which is synchronized.
|
||||
return reader.isUidNetworkingBlocked(uid, isNetworkMetered, reader.getDataSaverEnabled());
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
|
||||
@@ -32,6 +32,7 @@ import static android.net.ConnectivityDiagnosticsManager.DataStallReport.DETECTI
|
||||
import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_DNS_CONSECUTIVE_TIMEOUTS;
|
||||
import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS;
|
||||
import static android.net.ConnectivityDiagnosticsManager.DataStallReport.KEY_TCP_PACKET_FAIL_RATE;
|
||||
import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
|
||||
import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_MASK;
|
||||
import static android.net.ConnectivityManager.BLOCKED_REASON_LOCKDOWN_VPN;
|
||||
import static android.net.ConnectivityManager.BLOCKED_REASON_NONE;
|
||||
@@ -1770,6 +1771,19 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
mUserAllContext.registerReceiver(mPackageIntentReceiver, packageIntentFilter,
|
||||
null /* broadcastPermission */, mHandler);
|
||||
|
||||
// This is needed for pre-V devices to propagate the data saver status
|
||||
// to the BPF map. This isn't supported before Android T because BPF maps are
|
||||
// unsupported, and it's also unnecessary on Android V and later versions,
|
||||
// as the platform code handles data saver bit updates. Additionally, checking
|
||||
// the initial data saver status here is superfluous because the intent won't
|
||||
// be sent until the system is ready.
|
||||
if (mDeps.isAtLeastT() && !mDeps.isAtLeastV()) {
|
||||
final IntentFilter dataSaverIntentFilter =
|
||||
new IntentFilter(ACTION_RESTRICT_BACKGROUND_CHANGED);
|
||||
mUserAllContext.registerReceiver(mDataSaverReceiver, dataSaverIntentFilter,
|
||||
null /* broadcastPermission */, mHandler);
|
||||
}
|
||||
|
||||
// TrackMultiNetworkActivities feature should be enabled by trunk stable flag.
|
||||
// But reading the trunk stable flags from mainline modules is not supported yet.
|
||||
// So enabling this feature on V+ release.
|
||||
@@ -7051,6 +7065,32 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
}
|
||||
};
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
|
||||
private final BroadcastReceiver mDataSaverReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (mDeps.isAtLeastV()) {
|
||||
throw new IllegalStateException(
|
||||
"data saver status should be updated from platform");
|
||||
}
|
||||
ensureRunningOnConnectivityServiceThread();
|
||||
switch (intent.getAction()) {
|
||||
case ACTION_RESTRICT_BACKGROUND_CHANGED:
|
||||
// If the uid is present in the deny list, the API will consistently
|
||||
// return ENABLED. To retrieve the global switch status, the system
|
||||
// uid is chosen because it will never be included in the deny list.
|
||||
final int dataSaverForSystemUid =
|
||||
mPolicyManager.getRestrictBackgroundStatus(Process.SYSTEM_UID);
|
||||
final boolean isDataSaverEnabled = (dataSaverForSystemUid
|
||||
!= ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED);
|
||||
mBpfNetMaps.setDataSaverEnabled(isDataSaverEnabled);
|
||||
break;
|
||||
default:
|
||||
Log.wtf(TAG, "received unexpected intent: " + intent.getAction());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final HashMap<Messenger, NetworkProviderInfo> mNetworkProviderInfos = new HashMap<>();
|
||||
private final HashMap<NetworkRequest, NetworkRequestInfo> mNetworkRequests = new HashMap<>();
|
||||
|
||||
@@ -12985,7 +13025,7 @@ public class ConnectivityService extends IConnectivityManager.Stub
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.TIRAMISU)
|
||||
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
|
||||
@Override
|
||||
public void setDataSaverEnabled(final boolean enable) {
|
||||
enforceNetworkStackOrSettingsPermission();
|
||||
|
||||
@@ -16,13 +16,6 @@
|
||||
|
||||
package android.net;
|
||||
|
||||
import static android.content.Context.RECEIVER_NOT_EXPORTED;
|
||||
import static android.content.pm.ApplicationInfo.FLAG_PERSISTENT;
|
||||
import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
|
||||
import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
|
||||
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
|
||||
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
|
||||
import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED;
|
||||
import static android.net.ConnectivityManager.TYPE_NONE;
|
||||
import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
|
||||
import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
|
||||
@@ -46,7 +39,6 @@ import static android.net.NetworkRequest.Type.TRACK_SYSTEM_DEFAULT;
|
||||
|
||||
import static com.android.testutils.MiscAsserts.assertThrows;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
@@ -70,10 +62,7 @@ import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.net.ConnectivityManager.DataSaverStatusTracker;
|
||||
import android.net.ConnectivityManager.NetworkCallback;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.os.Bundle;
|
||||
@@ -527,44 +516,6 @@ public class ConnectivityManagerTest {
|
||||
+ " attempts", ref.get());
|
||||
}
|
||||
|
||||
@DevSdkIgnoreRule.IgnoreAfter(VERSION_CODES.UPSIDE_DOWN_CAKE)
|
||||
@Test
|
||||
public void testDataSaverStatusTracker() {
|
||||
mockService(NetworkPolicyManager.class, Context.NETWORK_POLICY_SERVICE, mNpm);
|
||||
// Mock proper application info.
|
||||
doReturn(mCtx).when(mCtx).getApplicationContext();
|
||||
final ApplicationInfo mockAppInfo = new ApplicationInfo();
|
||||
mockAppInfo.flags = FLAG_PERSISTENT | FLAG_SYSTEM;
|
||||
doReturn(mockAppInfo).when(mCtx).getApplicationInfo();
|
||||
// Enable data saver.
|
||||
doReturn(RESTRICT_BACKGROUND_STATUS_ENABLED).when(mNpm)
|
||||
.getRestrictBackgroundStatus(anyInt());
|
||||
|
||||
final DataSaverStatusTracker tracker = new DataSaverStatusTracker(mCtx);
|
||||
// Verify the data saver status is correct right after initialization.
|
||||
assertTrue(tracker.getDataSaverEnabled());
|
||||
|
||||
// Verify the tracker register receiver with expected intent filter.
|
||||
final ArgumentCaptor<IntentFilter> intentFilterCaptor =
|
||||
ArgumentCaptor.forClass(IntentFilter.class);
|
||||
verify(mCtx).registerReceiver(
|
||||
any(), intentFilterCaptor.capture(), eq(RECEIVER_NOT_EXPORTED));
|
||||
assertEquals(ACTION_RESTRICT_BACKGROUND_CHANGED,
|
||||
intentFilterCaptor.getValue().getAction(0));
|
||||
|
||||
// Mock data saver status changed event and verify the tracker tracks the
|
||||
// status accordingly.
|
||||
doReturn(RESTRICT_BACKGROUND_STATUS_DISABLED).when(mNpm)
|
||||
.getRestrictBackgroundStatus(anyInt());
|
||||
tracker.onReceive(mCtx, new Intent(ACTION_RESTRICT_BACKGROUND_CHANGED));
|
||||
assertFalse(tracker.getDataSaverEnabled());
|
||||
|
||||
doReturn(RESTRICT_BACKGROUND_STATUS_WHITELISTED).when(mNpm)
|
||||
.getRestrictBackgroundStatus(anyInt());
|
||||
tracker.onReceive(mCtx, new Intent(ACTION_RESTRICT_BACKGROUND_CHANGED));
|
||||
assertTrue(tracker.getDataSaverEnabled());
|
||||
}
|
||||
|
||||
private <T> void mockService(Class<T> clazz, String name, T service) {
|
||||
doReturn(service).when(mCtx).getSystemService(name);
|
||||
doReturn(name).when(mCtx).getSystemServiceName(clazz);
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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 com.android.server
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED
|
||||
import android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED
|
||||
import android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED
|
||||
import android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED
|
||||
import android.os.Build
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.testutils.DevSdkIgnoreRule
|
||||
import com.android.testutils.DevSdkIgnoreRule.IgnoreAfter
|
||||
import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
|
||||
import com.android.testutils.DevSdkIgnoreRunner
|
||||
import com.android.testutils.visibleOnHandlerThread
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentMatchers.anyBoolean
|
||||
import org.mockito.ArgumentMatchers.anyInt
|
||||
import org.mockito.Mockito.doReturn
|
||||
import org.mockito.Mockito.inOrder
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.verify
|
||||
|
||||
@RunWith(DevSdkIgnoreRunner::class)
|
||||
@SmallTest
|
||||
@IgnoreUpTo(Build.VERSION_CODES.S_V2) // Bpf only supports in T+.
|
||||
class CSBpfNetMapsTest : CSTest() {
|
||||
@get:Rule
|
||||
val ignoreRule = DevSdkIgnoreRule()
|
||||
|
||||
@IgnoreAfter(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
|
||||
@Test
|
||||
fun testCSTrackDataSaverBeforeV() {
|
||||
val inOrder = inOrder(bpfNetMaps)
|
||||
mockDataSaverStatus(RESTRICT_BACKGROUND_STATUS_WHITELISTED)
|
||||
inOrder.verify(bpfNetMaps).setDataSaverEnabled(true)
|
||||
mockDataSaverStatus(RESTRICT_BACKGROUND_STATUS_DISABLED)
|
||||
inOrder.verify(bpfNetMaps).setDataSaverEnabled(false)
|
||||
mockDataSaverStatus(RESTRICT_BACKGROUND_STATUS_ENABLED)
|
||||
inOrder.verify(bpfNetMaps).setDataSaverEnabled(true)
|
||||
}
|
||||
|
||||
// Data Saver Status is updated from platform code in V+.
|
||||
@IgnoreUpTo(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
|
||||
@Test
|
||||
fun testCSTrackDataSaverAboveU() {
|
||||
listOf(RESTRICT_BACKGROUND_STATUS_WHITELISTED, RESTRICT_BACKGROUND_STATUS_ENABLED,
|
||||
RESTRICT_BACKGROUND_STATUS_DISABLED).forEach {
|
||||
mockDataSaverStatus(it)
|
||||
verify(bpfNetMaps, never()).setDataSaverEnabled(anyBoolean())
|
||||
}
|
||||
}
|
||||
|
||||
private fun mockDataSaverStatus(status: Int) {
|
||||
doReturn(status).`when`(context.networkPolicyManager).getRestrictBackgroundStatus(anyInt())
|
||||
// While the production code dispatches the intent on the handler thread,
|
||||
// The test would dispatch the intent in the caller thread. Make it dispatch
|
||||
// on the handler thread to match production behavior.
|
||||
visibleOnHandlerThread(csHandler) {
|
||||
context.sendBroadcast(Intent(ACTION_RESTRICT_BACKGROUND_CHANGED))
|
||||
}
|
||||
waitForIdle()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user