[BR06] Support check whether network is blocked by data saver

This change adds a DataSaverStatusTracker, which is a helper
class to continuously track data saver status through NPMS
public API and intents. ConnectivityManager#isUidNetworkingBlocked
would use this cached information along with bpf maps to decide
whether networking of an uid is blocked.

Test: atest FrameworksNetTests:android.net.connectivity.android.net.BpfNetMapsReaderTest
Test: atest ConnectivityCoverageTests:android.net.connectivity.android.net.ConnectivityManagerTest
Bug: 297836825
Change-Id: I7e13191759430f3ea1f4dec7facc02f16be7146d
This commit is contained in:
Junyu Lai 2023-09-06 19:10:02 +08:00
commit c3dc5b6222

View file

@ -16,6 +16,8 @@
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;
@ -25,6 +27,8 @@ 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;
@ -37,12 +41,16 @@ 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;
@ -74,6 +82,7 @@ import android.util.Range;
import android.util.SparseIntArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import libcore.net.event.NetworkEventDispatcher;
@ -95,6 +104,7 @@ 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
@ -6199,13 +6209,99 @@ public class ConnectivityManager {
}
/**
* Return whether the network is blocked for the given uid.
* 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.
*
* Similar to {@link NetworkPolicyManager#isUidNetworkingBlocked}, but directly reads the BPF
* maps and therefore considerably faster. For use by the NetworkStack process only.
*
* @param uid The target uid.
* @return True if all networking is blocked. Otherwise, false.
* @param isNetworkMetered Whether the target network is metered.
*
* @return True if all networking with the given condition is blocked. Otherwise, false.
* @throws IllegalStateException if the map cannot be opened.
* @throws ServiceSpecificException if the read fails.
* @hide
@ -6219,13 +6315,16 @@ public class ConnectivityManager {
// @SystemApi(client = MODULE_LIBRARIES)
@RequiresApi(Build.VERSION_CODES.TIRAMISU) // BPF maps were only mainlined in T
@RequiresPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)
public boolean isUidNetworkingBlocked(int uid) {
public boolean isUidNetworkingBlocked(int uid, boolean isNetworkMetered) {
final BpfNetMapsReader reader = BpfNetMapsReader.getInstance();
return reader.isUidBlockedByFirewallChains(uid);
final boolean isDataSaverEnabled;
// TODO: For U-QPR3+ devices, get data saver status from bpf configuration map directly.
final DataSaverStatusTracker dataSaverStatusTracker =
DataSaverStatusTracker.getInstance(mContext);
isDataSaverEnabled = dataSaverStatusTracker.getDataSaverEnabled();
// TODO: If isNetworkMetered is true, check the data saver switch, penalty box
// and happy box rules.
return reader.isUidNetworkingBlocked(uid, isNetworkMetered, isDataSaverEnabled);
}
/** @hide */