Merge changes I2d6f80f0,I9c26852d
* changes:
Use device option to control BPF offload features
Add tether BPF offload config to device config and resource
diff --git a/Tethering/Android.bp b/Tethering/Android.bp
index 27297c4..eb72b81 100644
--- a/Tethering/Android.bp
+++ b/Tethering/Android.bp
@@ -27,7 +27,7 @@
"androidx.annotation_annotation",
"netd_aidl_interface-V3-java",
"netlink-client",
- "networkstack-aidl-interfaces-unstable-java",
+ "networkstack-aidl-interfaces-java",
"android.hardware.tetheroffload.config-V1.0-java",
"android.hardware.tetheroffload.control-V1.0-java",
"net-utils-framework-common",
@@ -50,6 +50,11 @@
cc_library {
name: "libtetherutilsjni",
sdk_version: "current",
+ apex_available: [
+ "//apex_available:platform", // Used by InProcessTethering
+ "com.android.tethering",
+ ],
+ min_sdk_version: "current",
srcs: [
"jni/android_net_util_TetheringUtils.cpp",
],
@@ -108,6 +113,8 @@
manifest: "AndroidManifest_InProcess.xml",
// InProcessTethering is a replacement for Tethering
overrides: ["Tethering"],
+ apex_available: ["com.android.tethering"],
+ min_sdk_version: "current",
}
// Updatable tethering packaged as an application
@@ -121,4 +128,5 @@
// The permission configuration *must* be included to ensure security of the device
required: ["NetworkPermissionConfig"],
apex_available: ["com.android.tethering"],
+ min_sdk_version: "current",
}
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index 24df5f6..67097a7 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -17,7 +17,7 @@
apex {
name: "com.android.tethering",
updatable: true,
- min_sdk_version: "R",
+ min_sdk_version: "current",
java_libs: ["framework-tethering"],
apps: ["Tethering"],
manifest: "manifest.json",
@@ -36,3 +36,12 @@
name: "com.android.tethering.certificate",
certificate: "com.android.tethering",
}
+
+override_apex {
+ name: "com.android.tethering.inprocess",
+ base: "com.android.tethering",
+ package_name: "com.android.tethering.inprocess",
+ apps: [
+ "InProcessTethering",
+ ],
+}
diff --git a/Tethering/common/TetheringLib/Android.bp b/Tethering/common/TetheringLib/Android.bp
index b520bc8..7ac9d25 100644
--- a/Tethering/common/TetheringLib/Android.bp
+++ b/Tethering/common/TetheringLib/Android.bp
@@ -37,6 +37,12 @@
cpp: {
enabled: false,
},
+ java: {
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.tethering",
+ ],
+ },
},
}
@@ -67,6 +73,7 @@
stubs_defaults {
name: "framework-tethering-stubs-defaults",
srcs: [":framework-tethering-srcs"],
+ dist: { dest: "framework-tethering.txt" },
}
filegroup {
@@ -123,16 +130,19 @@
name: "framework-tethering-stubs-publicapi",
srcs: [":framework-tethering-stubs-srcs-publicapi"],
defaults: ["framework-module-stubs-lib-defaults-publicapi"],
+ dist: { dest: "framework-tethering.jar" },
}
java_library {
name: "framework-tethering-stubs-systemapi",
srcs: [":framework-tethering-stubs-srcs-systemapi"],
defaults: ["framework-module-stubs-lib-defaults-systemapi"],
+ dist: { dest: "framework-tethering.jar" },
}
java_library {
name: "framework-tethering-stubs-module_libs_api",
srcs: [":framework-tethering-stubs-srcs-module_libs_api"],
defaults: ["framework-module-stubs-lib-defaults-module_libs_api"],
+ dist: { dest: "framework-tethering.jar" },
}
diff --git a/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java b/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
index 049a9f6..3c6e8d8 100644
--- a/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
+++ b/Tethering/src/com/android/networkstack/tethering/EntitlementManager.java
@@ -37,6 +37,7 @@
import android.content.IntentFilter;
import android.net.util.SharedLog;
import android.os.Bundle;
+import android.os.ConditionVariable;
import android.os.Handler;
import android.os.Parcel;
import android.os.PersistableBundle;
@@ -45,13 +46,12 @@
import android.os.SystemProperties;
import android.provider.Settings;
import android.telephony.CarrierConfigManager;
-import android.util.ArraySet;
import android.util.SparseIntArray;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.StateMachine;
import java.io.PrintWriter;
+import java.util.BitSet;
/**
* Re-check tethering provisioning for enabled downstream tether types.
@@ -73,39 +73,39 @@
private final ComponentName mSilentProvisioningService;
private static final int MS_PER_HOUR = 60 * 60 * 1000;
+ private static final int DUMP_TIMEOUT = 10_000;
- // The ArraySet contains enabled downstream types, ex:
+ // The BitSet is the bit map of each enabled downstream types, ex:
// {@link TetheringManager.TETHERING_WIFI}
// {@link TetheringManager.TETHERING_USB}
// {@link TetheringManager.TETHERING_BLUETOOTH}
- private final ArraySet<Integer> mCurrentTethers;
+ private final BitSet mCurrentDownstreams;
+ private final BitSet mExemptedDownstreams;
private final Context mContext;
- private final int mPermissionChangeMessageCode;
private final SharedLog mLog;
private final SparseIntArray mEntitlementCacheValue;
private final Handler mHandler;
- private final StateMachine mTetherMasterSM;
// Key: TetheringManager.TETHERING_*(downstream).
// Value: TetheringManager.TETHER_ERROR_{NO_ERROR or PROVISION_FAILED}(provisioning result).
- private final SparseIntArray mCellularPermitted;
+ private final SparseIntArray mCurrentEntitlementResults;
+ private final Runnable mPermissionChangeCallback;
private PendingIntent mProvisioningRecheckAlarm;
- private boolean mCellularUpstreamPermitted = true;
+ private boolean mLastCellularUpstreamPermitted = true;
private boolean mUsingCellularAsUpstream = false;
private boolean mNeedReRunProvisioningUi = false;
private OnUiEntitlementFailedListener mListener;
private TetheringConfigurationFetcher mFetcher;
- public EntitlementManager(Context ctx, StateMachine tetherMasterSM, SharedLog log,
- int permissionChangeMessageCode) {
-
+ public EntitlementManager(Context ctx, Handler h, SharedLog log,
+ Runnable callback) {
mContext = ctx;
mLog = log.forSubComponent(TAG);
- mCurrentTethers = new ArraySet<Integer>();
- mCellularPermitted = new SparseIntArray();
+ mCurrentDownstreams = new BitSet();
+ mExemptedDownstreams = new BitSet();
+ mCurrentEntitlementResults = new SparseIntArray();
mEntitlementCacheValue = new SparseIntArray();
- mTetherMasterSM = tetherMasterSM;
- mPermissionChangeMessageCode = permissionChangeMessageCode;
- mHandler = tetherMasterSM.getHandler();
+ mPermissionChangeCallback = callback;
+ mHandler = h;
mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_PROVISIONING_ALARM),
null, mHandler);
mSilentProvisioningService = ComponentName.unflattenFromString(
@@ -144,13 +144,35 @@
* Check if cellular upstream is permitted.
*/
public boolean isCellularUpstreamPermitted() {
- // If provisioning is required and EntitlementManager don't know any downstream,
- // cellular upstream should not be allowed.
final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration();
- if (mCurrentTethers.size() == 0 && isTetherProvisioningRequired(config)) {
- return false;
- }
- return mCellularUpstreamPermitted;
+
+ return isCellularUpstreamPermitted(config);
+ }
+
+ private boolean isCellularUpstreamPermitted(final TetheringConfiguration config) {
+ if (!isTetherProvisioningRequired(config)) return true;
+
+ // If provisioning is required and EntitlementManager doesn't know any downstreams, cellular
+ // upstream should not be enabled. Enable cellular upstream for exempted downstreams only
+ // when there is no non-exempted downstream.
+ if (mCurrentDownstreams.isEmpty()) return !mExemptedDownstreams.isEmpty();
+
+ return mCurrentEntitlementResults.indexOfValue(TETHER_ERROR_NO_ERROR) > -1;
+ }
+
+ /**
+ * Set exempted downstream type. If there is only exempted downstream type active,
+ * corresponding entitlement check will not be run and cellular upstream will be permitted
+ * by default. If a privileged app enables tethering without a provisioning check, and then
+ * another app enables tethering of the same type but does not disable the provisioning check,
+ * then the downstream immediately loses exempt status and a provisioning check is run.
+ * If any non-exempted downstream type is active, the cellular upstream will be gated by the
+ * result of entitlement check from non-exempted downstreams. If entitlement check is still
+ * in progress on non-exempt downstreams, ceullar upstream would default be disabled. When any
+ * non-exempted downstream gets positive entitlement result, ceullar upstream will be enabled.
+ */
+ public void setExemptedDownstreamType(final int type) {
+ mExemptedDownstreams.set(type, true);
}
/**
@@ -164,29 +186,24 @@
public void startProvisioningIfNeeded(int downstreamType, boolean showProvisioningUi) {
if (!isValidDownstreamType(downstreamType)) return;
- if (!mCurrentTethers.contains(downstreamType)) mCurrentTethers.add(downstreamType);
+ mCurrentDownstreams.set(downstreamType, true);
+
+ mExemptedDownstreams.set(downstreamType, false);
final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration();
- if (isTetherProvisioningRequired(config)) {
- // If provisioning is required and the result is not available yet,
- // cellular upstream should not be allowed.
- if (mCellularPermitted.size() == 0) {
- mCellularUpstreamPermitted = false;
- }
- // If upstream is not cellular, provisioning app would not be launched
- // till upstream change to cellular.
- if (mUsingCellularAsUpstream) {
- if (showProvisioningUi) {
- runUiTetherProvisioning(downstreamType, config.activeDataSubId);
- } else {
- runSilentTetherProvisioning(downstreamType, config.activeDataSubId);
- }
- mNeedReRunProvisioningUi = false;
+ if (!isTetherProvisioningRequired(config)) return;
+
+ // If upstream is not cellular, provisioning app would not be launched
+ // till upstream change to cellular.
+ if (mUsingCellularAsUpstream) {
+ if (showProvisioningUi) {
+ runUiTetherProvisioning(downstreamType, config.activeDataSubId);
} else {
- mNeedReRunProvisioningUi |= showProvisioningUi;
+ runSilentTetherProvisioning(downstreamType, config.activeDataSubId);
}
+ mNeedReRunProvisioningUi = false;
} else {
- mCellularUpstreamPermitted = true;
+ mNeedReRunProvisioningUi |= showProvisioningUi;
}
}
@@ -195,14 +212,15 @@
*
* @param type tethering type from TetheringManager.TETHERING_{@code *}
*/
- public void stopProvisioningIfNeeded(int type) {
- if (!isValidDownstreamType(type)) return;
+ public void stopProvisioningIfNeeded(int downstreamType) {
+ if (!isValidDownstreamType(downstreamType)) return;
- mCurrentTethers.remove(type);
+ mCurrentDownstreams.set(downstreamType, false);
// There are lurking bugs where the notion of "provisioning required" or
// "tethering supported" may change without without tethering being notified properly.
// Remove the mapping all the time no matter provisioning is required or not.
- removeDownstreamMapping(type);
+ removeDownstreamMapping(downstreamType);
+ mExemptedDownstreams.set(downstreamType, false);
}
/**
@@ -213,7 +231,7 @@
public void notifyUpstream(boolean isCellular) {
if (DBG) {
mLog.i("notifyUpstream: " + isCellular
- + ", mCellularUpstreamPermitted: " + mCellularUpstreamPermitted
+ + ", mLastCellularUpstreamPermitted: " + mLastCellularUpstreamPermitted
+ ", mNeedReRunProvisioningUi: " + mNeedReRunProvisioningUi);
}
mUsingCellularAsUpstream = isCellular;
@@ -231,7 +249,7 @@
}
private void maybeRunProvisioning(final TetheringConfiguration config) {
- if (mCurrentTethers.size() == 0 || !isTetherProvisioningRequired(config)) {
+ if (mCurrentDownstreams.isEmpty() || !isTetherProvisioningRequired(config)) {
return;
}
@@ -239,8 +257,9 @@
// are allowed. Therefore even if the silent check here ends in a failure and the UI later
// yields success, then the downstream that got a failure will re-evaluate as a result of
// the change and get the new correct value.
- for (Integer downstream : mCurrentTethers) {
- if (mCellularPermitted.indexOfKey(downstream) < 0) {
+ for (int downstream = mCurrentDownstreams.nextSetBit(0); downstream >= 0;
+ downstream = mCurrentDownstreams.nextSetBit(downstream + 1)) {
+ if (mCurrentEntitlementResults.indexOfKey(downstream) < 0) {
if (mNeedReRunProvisioningUi) {
mNeedReRunProvisioningUi = false;
runUiTetherProvisioning(downstream, config.activeDataSubId);
@@ -286,7 +305,7 @@
mLog.log("reevaluateSimCardProvisioning() don't run in TetherMaster thread");
}
mEntitlementCacheValue.clear();
- mCellularPermitted.clear();
+ mCurrentEntitlementResults.clear();
// TODO: refine provisioning check to isTetherProvisioningRequired() ??
if (!config.hasMobileHotspotProvisionApp()
@@ -410,26 +429,25 @@
}
private void evaluateCellularPermission(final TetheringConfiguration config) {
- final boolean oldPermitted = mCellularUpstreamPermitted;
- mCellularUpstreamPermitted = (!isTetherProvisioningRequired(config)
- || mCellularPermitted.indexOfValue(TETHER_ERROR_NO_ERROR) > -1);
+ final boolean permitted = isCellularUpstreamPermitted(config);
if (DBG) {
- mLog.i("Cellular permission change from " + oldPermitted
- + " to " + mCellularUpstreamPermitted);
+ mLog.i("Cellular permission change from " + mLastCellularUpstreamPermitted
+ + " to " + permitted);
}
- if (mCellularUpstreamPermitted != oldPermitted) {
- mLog.log("Cellular permission change: " + mCellularUpstreamPermitted);
- mTetherMasterSM.sendMessage(mPermissionChangeMessageCode);
+ if (mLastCellularUpstreamPermitted != permitted) {
+ mLog.log("Cellular permission change: " + permitted);
+ mPermissionChangeCallback.run();
}
// Only schedule periodic re-check when tether is provisioned
// and the result is ok.
- if (mCellularUpstreamPermitted && mCellularPermitted.size() > 0) {
+ if (permitted && mCurrentEntitlementResults.size() > 0) {
scheduleProvisioningRechecks(config);
} else {
cancelTetherProvisioningRechecks();
}
+ mLastCellularUpstreamPermitted = permitted;
}
/**
@@ -441,10 +459,10 @@
*/
protected void addDownstreamMapping(int type, int resultCode) {
mLog.i("addDownstreamMapping: " + type + ", result: " + resultCode
- + " ,TetherTypeRequested: " + mCurrentTethers.contains(type));
- if (!mCurrentTethers.contains(type)) return;
+ + " ,TetherTypeRequested: " + mCurrentDownstreams.get(type));
+ if (!mCurrentDownstreams.get(type)) return;
- mCellularPermitted.put(type, resultCode);
+ mCurrentEntitlementResults.put(type, resultCode);
final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration();
evaluateCellularPermission(config);
}
@@ -455,7 +473,7 @@
*/
protected void removeDownstreamMapping(int type) {
mLog.i("removeDownstreamMapping: " + type);
- mCellularPermitted.delete(type);
+ mCurrentEntitlementResults.delete(type);
final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration();
evaluateCellularPermission(config);
}
@@ -488,18 +506,33 @@
* @param pw {@link PrintWriter} is used to print formatted
*/
public void dump(PrintWriter pw) {
- pw.print("mCellularUpstreamPermitted: ");
- pw.println(mCellularUpstreamPermitted);
- for (Integer type : mCurrentTethers) {
- pw.print("Type: ");
- pw.print(typeString(type));
- if (mCellularPermitted.indexOfKey(type) > -1) {
- pw.print(", Value: ");
- pw.println(errorString(mCellularPermitted.get(type)));
- } else {
- pw.println(", Value: empty");
+ final ConditionVariable mWaiting = new ConditionVariable();
+ mHandler.post(() -> {
+ pw.print("isCellularUpstreamPermitted: ");
+ pw.println(isCellularUpstreamPermitted());
+ for (int type = mCurrentDownstreams.nextSetBit(0); type >= 0;
+ type = mCurrentDownstreams.nextSetBit(type + 1)) {
+ pw.print("Type: ");
+ pw.print(typeString(type));
+ if (mCurrentEntitlementResults.indexOfKey(type) > -1) {
+ pw.print(", Value: ");
+ pw.println(errorString(mCurrentEntitlementResults.get(type)));
+ } else {
+ pw.println(", Value: empty");
+ }
}
+ mWaiting.open();
+ });
+ if (!mWaiting.block(DUMP_TIMEOUT)) {
+ pw.println("... dump timed out after " + DUMP_TIMEOUT + "ms");
}
+ pw.print("Exempted: [");
+ for (int type = mExemptedDownstreams.nextSetBit(0); type >= 0;
+ type = mExemptedDownstreams.nextSetBit(type + 1)) {
+ pw.print(typeString(type));
+ pw.print(", ");
+ }
+ pw.println("]");
}
private static String typeString(int type) {
diff --git a/Tethering/src/com/android/networkstack/tethering/OffloadController.java b/Tethering/src/com/android/networkstack/tethering/OffloadController.java
index c063863..88c77b0 100644
--- a/Tethering/src/com/android/networkstack/tethering/OffloadController.java
+++ b/Tethering/src/com/android/networkstack/tethering/OffloadController.java
@@ -26,6 +26,8 @@
import static android.net.netstats.provider.NetworkStatsProvider.QUOTA_UNLIMITED;
import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
+import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.usage.NetworkStatsManager;
@@ -77,8 +79,6 @@
private static final boolean DBG = false;
private static final String ANYIP = "0.0.0.0";
private static final ForwardedStats EMPTY_STATS = new ForwardedStats();
- @VisibleForTesting
- static final int DEFAULT_PERFORM_POLL_INTERVAL_MS = 5000;
@VisibleForTesting
enum StatsType {
@@ -135,11 +135,9 @@
private final Dependencies mDeps;
// TODO: Put more parameters in constructor into dependency object.
- static class Dependencies {
- int getPerformPollInterval() {
- // TODO: Consider make this configurable.
- return DEFAULT_PERFORM_POLL_INTERVAL_MS;
- }
+ interface Dependencies {
+ @NonNull
+ TetheringConfiguration getTetherConfig();
}
public OffloadController(Handler h, OffloadHardwareInterface hwi,
@@ -453,12 +451,16 @@
if (mHandler.hasCallbacks(mScheduledPollingTask)) {
mHandler.removeCallbacks(mScheduledPollingTask);
}
- mHandler.postDelayed(mScheduledPollingTask, mDeps.getPerformPollInterval());
+ mHandler.postDelayed(mScheduledPollingTask,
+ mDeps.getTetherConfig().getOffloadPollInterval());
}
private boolean isPollingStatsNeeded() {
return started() && mRemainingAlertQuota > 0
- && !TextUtils.isEmpty(currentUpstreamInterface());
+ && !TextUtils.isEmpty(currentUpstreamInterface())
+ && mDeps.getTetherConfig() != null
+ && mDeps.getTetherConfig().getOffloadPollInterval()
+ >= DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS;
}
private boolean maybeUpdateDataLimit(String iface) {
diff --git a/Tethering/src/com/android/networkstack/tethering/Tethering.java b/Tethering/src/com/android/networkstack/tethering/Tethering.java
index a31d547..bca86ca 100644
--- a/Tethering/src/com/android/networkstack/tethering/Tethering.java
+++ b/Tethering/src/com/android/networkstack/tethering/Tethering.java
@@ -62,7 +62,6 @@
import static com.android.networkstack.tethering.TetheringNotificationUpdater.DOWNSTREAM_NONE;
-import android.app.usage.NetworkStatsManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothPan;
import android.bluetooth.BluetoothProfile;
@@ -268,12 +267,15 @@
mTetherMasterSM = new TetherMasterSM("TetherMaster", mLooper, deps);
mTetherMasterSM.start();
- final NetworkStatsManager statsManager =
- (NetworkStatsManager) mContext.getSystemService(Context.NETWORK_STATS_SERVICE);
mHandler = mTetherMasterSM.getHandler();
- mOffloadController = new OffloadController(mHandler,
- mDeps.getOffloadHardwareInterface(mHandler, mLog), mContext.getContentResolver(),
- statsManager, mLog, new OffloadController.Dependencies());
+ mOffloadController = mDeps.getOffloadController(mHandler, mLog,
+ new OffloadController.Dependencies() {
+
+ @Override
+ public TetheringConfiguration getTetherConfig() {
+ return mConfig;
+ }
+ });
mUpstreamNetworkMonitor = mDeps.getUpstreamNetworkMonitor(mContext, mTetherMasterSM, mLog,
TetherMasterSM.EVENT_UPSTREAM_CALLBACK);
mForwardedDownstreams = new LinkedHashSet<>();
@@ -282,8 +284,9 @@
filter.addAction(ACTION_CARRIER_CONFIG_CHANGED);
// EntitlementManager will send EVENT_UPSTREAM_PERMISSION_CHANGED when cellular upstream
// permission is changed according to entitlement check result.
- mEntitlementMgr = mDeps.getEntitlementManager(mContext, mTetherMasterSM, mLog,
- TetherMasterSM.EVENT_UPSTREAM_PERMISSION_CHANGED);
+ mEntitlementMgr = mDeps.getEntitlementManager(mContext, mHandler, mLog,
+ () -> mTetherMasterSM.sendMessage(
+ TetherMasterSM.EVENT_UPSTREAM_PERMISSION_CHANGED));
mEntitlementMgr.setOnUiEntitlementFailedListener((int downstream) -> {
mLog.log("OBSERVED UiEnitlementFailed");
stopTethering(downstream);
@@ -513,8 +516,12 @@
}
mActiveTetheringRequests.put(request.tetheringType, request);
- mEntitlementMgr.startProvisioningIfNeeded(request.tetheringType,
- request.showProvisioningUi);
+ if (request.exemptFromEntitlementCheck) {
+ mEntitlementMgr.setExemptedDownstreamType(request.tetheringType);
+ } else {
+ mEntitlementMgr.startProvisioningIfNeeded(request.tetheringType,
+ request.showProvisioningUi);
+ }
enableTetheringInternal(request.tetheringType, true /* enabled */, listener);
});
}
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
index 9b54b5f..ce546c7 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringDependencies.java
@@ -16,6 +16,7 @@
package com.android.networkstack.tethering;
+import android.app.usage.NetworkStatsManager;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.net.INetd;
@@ -47,6 +48,19 @@
}
/**
+ * Get a reference to the offload controller to be used by tethering.
+ */
+ @NonNull
+ public OffloadController getOffloadController(@NonNull Handler h,
+ @NonNull SharedLog log, @NonNull OffloadController.Dependencies deps) {
+ final NetworkStatsManager statsManager =
+ (NetworkStatsManager) getContext().getSystemService(Context.NETWORK_STATS_SERVICE);
+ return new OffloadController(h, getOffloadHardwareInterface(h, log),
+ getContext().getContentResolver(), statsManager, log, deps);
+ }
+
+
+ /**
* Get a reference to the UpstreamNetworkMonitor to be used by tethering.
*/
public UpstreamNetworkMonitor getUpstreamNetworkMonitor(Context ctx, StateMachine target,
@@ -82,9 +96,9 @@
/**
* Get a reference to the EntitlementManager to be used by tethering.
*/
- public EntitlementManager getEntitlementManager(Context ctx, StateMachine target,
- SharedLog log, int what) {
- return new EntitlementManager(ctx, target, log, what);
+ public EntitlementManager getEntitlementManager(Context ctx, Handler h, SharedLog log,
+ Runnable callback) {
+ return new EntitlementManager(ctx, h, log, callback);
}
/**
diff --git a/Tethering/src/com/android/networkstack/tethering/TetheringService.java b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
index c82e2be..af349f2 100644
--- a/Tethering/src/com/android/networkstack/tethering/TetheringService.java
+++ b/Tethering/src/com/android/networkstack/tethering/TetheringService.java
@@ -16,6 +16,8 @@
package com.android.networkstack.tethering;
+import static android.Manifest.permission.ACCESS_NETWORK_STATE;
+import static android.Manifest.permission.TETHER_PRIVILEGED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.net.TetheringManager.TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION;
import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION;
@@ -151,7 +153,12 @@
@Override
public void startTethering(TetheringRequestParcel request, String callerPkg,
String callingAttributionTag, IIntResultListener listener) {
- if (checkAndNotifyCommonError(callerPkg, callingAttributionTag, listener)) return;
+ if (checkAndNotifyCommonError(callerPkg,
+ callingAttributionTag,
+ request.exemptFromEntitlementCheck /* onlyAllowPrivileged */,
+ listener)) {
+ return;
+ }
mTethering.startTethering(request, listener);
}
@@ -179,7 +186,7 @@
public void registerTetheringEventCallback(ITetheringEventCallback callback,
String callerPkg) {
try {
- if (!mService.hasTetherAccessPermission()) {
+ if (!hasTetherAccessPermission()) {
callback.onCallbackStopped(TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION);
return;
}
@@ -191,7 +198,7 @@
public void unregisterTetheringEventCallback(ITetheringEventCallback callback,
String callerPkg) {
try {
- if (!mService.hasTetherAccessPermission()) {
+ if (!hasTetherAccessPermission()) {
callback.onCallbackStopped(TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION);
return;
}
@@ -226,10 +233,18 @@
mTethering.dump(fd, writer, args);
}
- private boolean checkAndNotifyCommonError(String callerPkg, String callingAttributionTag,
- IIntResultListener listener) {
+ private boolean checkAndNotifyCommonError(final String callerPkg,
+ final String callingAttributionTag, final IIntResultListener listener) {
+ return checkAndNotifyCommonError(callerPkg, callingAttributionTag,
+ false /* onlyAllowPrivileged */, listener);
+ }
+
+ private boolean checkAndNotifyCommonError(final String callerPkg,
+ final String callingAttributionTag, final boolean onlyAllowPrivileged,
+ final IIntResultListener listener) {
try {
- if (!mService.hasTetherChangePermission(callerPkg, callingAttributionTag)) {
+ if (!hasTetherChangePermission(callerPkg, callingAttributionTag,
+ onlyAllowPrivileged)) {
listener.onResult(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION);
return true;
}
@@ -244,9 +259,10 @@
return false;
}
- private boolean checkAndNotifyCommonError(String callerPkg, String callingAttributionTag,
- ResultReceiver receiver) {
- if (!mService.hasTetherChangePermission(callerPkg, callingAttributionTag)) {
+ private boolean checkAndNotifyCommonError(final String callerPkg,
+ final String callingAttributionTag, final ResultReceiver receiver) {
+ if (!hasTetherChangePermission(callerPkg, callingAttributionTag,
+ false /* onlyAllowPrivileged */)) {
receiver.send(TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION, null);
return true;
}
@@ -258,6 +274,30 @@
return false;
}
+ private boolean hasTetherPrivilegedPermission() {
+ return mService.checkCallingOrSelfPermission(TETHER_PRIVILEGED) == PERMISSION_GRANTED;
+ }
+
+ private boolean hasTetherChangePermission(final String callerPkg,
+ final String callingAttributionTag, final boolean onlyAllowPrivileged) {
+ if (hasTetherPrivilegedPermission()) return true;
+
+ if (onlyAllowPrivileged || mTethering.isTetherProvisioningRequired()) return false;
+
+ int uid = Binder.getCallingUid();
+ // If callerPkg's uid is not same as Binder.getCallingUid(),
+ // checkAndNoteWriteSettingsOperation will return false and the operation will be
+ // denied.
+ return TetheringService.checkAndNoteWriteSettingsOperation(mService, uid, callerPkg,
+ callingAttributionTag, false /* throwException */);
+ }
+
+ private boolean hasTetherAccessPermission() {
+ if (hasTetherPrivilegedPermission()) return true;
+
+ return mService.checkCallingOrSelfPermission(
+ ACCESS_NETWORK_STATE) == PERMISSION_GRANTED;
+ }
}
// if ro.tether.denied = true we default to no tethering
@@ -274,26 +314,6 @@
return tetherEnabledInSettings && mTethering.hasTetherableConfiguration();
}
- private boolean hasTetherChangePermission(String callerPkg, String callingAttributionTag) {
- if (checkCallingOrSelfPermission(
- android.Manifest.permission.TETHER_PRIVILEGED) == PERMISSION_GRANTED) {
- return true;
- }
-
- if (mTethering.isTetherProvisioningRequired()) return false;
-
-
- int uid = Binder.getCallingUid();
- // If callerPkg's uid is not same as Binder.getCallingUid(),
- // checkAndNoteWriteSettingsOperation will return false and the operation will be denied.
- if (checkAndNoteWriteSettingsOperation(mContext, uid, callerPkg,
- callingAttributionTag, false /* throwException */)) {
- return true;
- }
-
- return false;
- }
-
/**
* Check if the package is a allowed to write settings. This also accounts that such an access
* happened.
@@ -308,21 +328,6 @@
throwException);
}
- private boolean hasTetherAccessPermission() {
- if (checkCallingOrSelfPermission(
- android.Manifest.permission.TETHER_PRIVILEGED) == PERMISSION_GRANTED) {
- return true;
- }
-
- if (checkCallingOrSelfPermission(
- android.Manifest.permission.ACCESS_NETWORK_STATE) == PERMISSION_GRANTED) {
- return true;
- }
-
- return false;
- }
-
-
/**
* An injection method for testing.
*/
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
index 8bd0edc..cdd0e24 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/EntitlementManagerTest.java
@@ -37,6 +37,8 @@
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -45,7 +47,7 @@
import android.content.res.Resources;
import android.net.util.SharedLog;
import android.os.Bundle;
-import android.os.Message;
+import android.os.Handler;
import android.os.PersistableBundle;
import android.os.ResultReceiver;
import android.os.SystemProperties;
@@ -56,26 +58,22 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.internal.util.State;
-import com.android.internal.util.StateMachine;
import com.android.internal.util.test.BroadcastInterceptingContext;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
-import java.util.ArrayList;
-
@RunWith(AndroidJUnit4.class)
@SmallTest
public final class EntitlementManagerTest {
- private static final int EVENT_EM_UPDATE = 1;
private static final String[] PROVISIONING_APP_NAME = {"some", "app"};
private static final String PROVISIONING_NO_UI_APP_NAME = "no_ui_app";
@@ -90,8 +88,8 @@
private final PersistableBundle mCarrierConfig = new PersistableBundle();
private final TestLooper mLooper = new TestLooper();
private Context mMockContext;
+ private Runnable mPermissionChangeCallback;
- private TestStateMachine mSM;
private WrappedEntitlementManager mEnMgr;
private TetheringConfiguration mConfig;
private MockitoSession mMockingSession;
@@ -112,9 +110,9 @@
public int uiProvisionCount = 0;
public int silentProvisionCount = 0;
- public WrappedEntitlementManager(Context ctx, StateMachine target,
- SharedLog log, int what) {
- super(ctx, target, log, what);
+ public WrappedEntitlementManager(Context ctx, Handler h, SharedLog log,
+ Runnable callback) {
+ super(ctx, h, log, callback);
}
public void reset() {
@@ -169,8 +167,9 @@
when(mLog.forSubComponent(anyString())).thenReturn(mLog);
mMockContext = new MockContext(mContext);
- mSM = new TestStateMachine();
- mEnMgr = new WrappedEntitlementManager(mMockContext, mSM, mLog, EVENT_EM_UPDATE);
+ mPermissionChangeCallback = spy(() -> { });
+ mEnMgr = new WrappedEntitlementManager(mMockContext, new Handler(mLooper.getLooper()), mLog,
+ mPermissionChangeCallback);
mEnMgr.setOnUiEntitlementFailedListener(mEntitlementFailedListener);
mConfig = new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
mEnMgr.setTetheringConfigurationFetcher(() -> {
@@ -180,10 +179,6 @@
@After
public void tearDown() throws Exception {
- if (mSM != null) {
- mSM.quit();
- mSM = null;
- }
mMockingSession.finishMocking();
}
@@ -350,68 +345,105 @@
mEnMgr.reset();
}
+ private void assertPermissionChangeCallback(InOrder inOrder) {
+ inOrder.verify(mPermissionChangeCallback, times(1)).run();
+ }
+
+ private void assertNoPermissionChange(InOrder inOrder) {
+ inOrder.verifyNoMoreInteractions();
+ }
+
@Test
public void verifyPermissionResult() {
+ final InOrder inOrder = inOrder(mPermissionChangeCallback);
setupForRequiredProvisioning();
mEnMgr.notifyUpstream(true);
mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISIONING_FAILED;
mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true);
mLooper.dispatchAll();
+ // Permitted: true -> false
+ assertPermissionChangeCallback(inOrder);
assertFalse(mEnMgr.isCellularUpstreamPermitted());
+
mEnMgr.stopProvisioningIfNeeded(TETHERING_WIFI);
mLooper.dispatchAll();
+ // Permitted: false -> false
+ assertNoPermissionChange(inOrder);
+
mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true);
mLooper.dispatchAll();
+ // Permitted: false -> true
+ assertPermissionChangeCallback(inOrder);
assertTrue(mEnMgr.isCellularUpstreamPermitted());
}
@Test
public void verifyPermissionIfAllNotApproved() {
+ final InOrder inOrder = inOrder(mPermissionChangeCallback);
setupForRequiredProvisioning();
mEnMgr.notifyUpstream(true);
mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISIONING_FAILED;
mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true);
mLooper.dispatchAll();
+ // Permitted: true -> false
+ assertPermissionChangeCallback(inOrder);
assertFalse(mEnMgr.isCellularUpstreamPermitted());
+
mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISIONING_FAILED;
mEnMgr.startProvisioningIfNeeded(TETHERING_USB, true);
mLooper.dispatchAll();
+ // Permitted: false -> false
+ assertNoPermissionChange(inOrder);
assertFalse(mEnMgr.isCellularUpstreamPermitted());
+
mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISIONING_FAILED;
mEnMgr.startProvisioningIfNeeded(TETHERING_BLUETOOTH, true);
mLooper.dispatchAll();
+ // Permitted: false -> false
+ assertNoPermissionChange(inOrder);
assertFalse(mEnMgr.isCellularUpstreamPermitted());
}
@Test
public void verifyPermissionIfAnyApproved() {
+ final InOrder inOrder = inOrder(mPermissionChangeCallback);
setupForRequiredProvisioning();
mEnMgr.notifyUpstream(true);
mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true);
mLooper.dispatchAll();
+ // Permitted: true -> true
+ assertNoPermissionChange(inOrder);
assertTrue(mEnMgr.isCellularUpstreamPermitted());
- mLooper.dispatchAll();
+
mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISIONING_FAILED;
mEnMgr.startProvisioningIfNeeded(TETHERING_USB, true);
mLooper.dispatchAll();
+ // Permitted: true -> true
+ assertNoPermissionChange(inOrder);
assertTrue(mEnMgr.isCellularUpstreamPermitted());
+
mEnMgr.stopProvisioningIfNeeded(TETHERING_WIFI);
mLooper.dispatchAll();
+ // Permitted: true -> false
+ assertPermissionChangeCallback(inOrder);
assertFalse(mEnMgr.isCellularUpstreamPermitted());
-
}
@Test
public void verifyPermissionWhenProvisioningNotStarted() {
+ final InOrder inOrder = inOrder(mPermissionChangeCallback);
assertTrue(mEnMgr.isCellularUpstreamPermitted());
+ assertNoPermissionChange(inOrder);
setupForRequiredProvisioning();
assertFalse(mEnMgr.isCellularUpstreamPermitted());
+ assertNoPermissionChange(inOrder);
}
@Test
public void testRunTetherProvisioning() {
+ final InOrder inOrder = inOrder(mPermissionChangeCallback);
setupForRequiredProvisioning();
// 1. start ui provisioning, upstream is mobile
mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
@@ -421,16 +453,22 @@
mLooper.dispatchAll();
assertEquals(1, mEnMgr.uiProvisionCount);
assertEquals(0, mEnMgr.silentProvisionCount);
+ // Permitted: true -> true
+ assertNoPermissionChange(inOrder);
assertTrue(mEnMgr.isCellularUpstreamPermitted());
mEnMgr.reset();
+
// 2. start no-ui provisioning
mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, false);
mLooper.dispatchAll();
assertEquals(0, mEnMgr.uiProvisionCount);
assertEquals(1, mEnMgr.silentProvisionCount);
+ // Permitted: true -> true
+ assertNoPermissionChange(inOrder);
assertTrue(mEnMgr.isCellularUpstreamPermitted());
mEnMgr.reset();
+
// 3. tear down mobile, then start ui provisioning
mEnMgr.notifyUpstream(false);
mLooper.dispatchAll();
@@ -438,44 +476,58 @@
mLooper.dispatchAll();
assertEquals(0, mEnMgr.uiProvisionCount);
assertEquals(0, mEnMgr.silentProvisionCount);
+ assertNoPermissionChange(inOrder);
mEnMgr.reset();
+
// 4. switch upstream back to mobile
mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
mEnMgr.notifyUpstream(true);
mLooper.dispatchAll();
assertEquals(1, mEnMgr.uiProvisionCount);
assertEquals(0, mEnMgr.silentProvisionCount);
+ // Permitted: true -> true
+ assertNoPermissionChange(inOrder);
assertTrue(mEnMgr.isCellularUpstreamPermitted());
mEnMgr.reset();
+
// 5. tear down mobile, then switch SIM
mEnMgr.notifyUpstream(false);
mLooper.dispatchAll();
mEnMgr.reevaluateSimCardProvisioning(mConfig);
assertEquals(0, mEnMgr.uiProvisionCount);
assertEquals(0, mEnMgr.silentProvisionCount);
+ assertNoPermissionChange(inOrder);
mEnMgr.reset();
+
// 6. switch upstream back to mobile again
mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISIONING_FAILED;
mEnMgr.notifyUpstream(true);
mLooper.dispatchAll();
assertEquals(0, mEnMgr.uiProvisionCount);
assertEquals(3, mEnMgr.silentProvisionCount);
+ // Permitted: true -> false
+ assertPermissionChangeCallback(inOrder);
assertFalse(mEnMgr.isCellularUpstreamPermitted());
mEnMgr.reset();
+
// 7. start ui provisioning, upstream is mobile, downstream is ethernet
mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
mEnMgr.startProvisioningIfNeeded(TETHERING_ETHERNET, true);
mLooper.dispatchAll();
assertEquals(1, mEnMgr.uiProvisionCount);
assertEquals(0, mEnMgr.silentProvisionCount);
+ // Permitted: false -> true
+ assertPermissionChangeCallback(inOrder);
assertTrue(mEnMgr.isCellularUpstreamPermitted());
mEnMgr.reset();
+
// 8. downstream is invalid
mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR;
mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI_P2P, true);
mLooper.dispatchAll();
assertEquals(0, mEnMgr.uiProvisionCount);
assertEquals(0, mEnMgr.silentProvisionCount);
+ assertNoPermissionChange(inOrder);
mEnMgr.reset();
}
@@ -492,31 +544,32 @@
verify(mEntitlementFailedListener, times(1)).onUiEntitlementFailed(TETHERING_WIFI);
}
- public class TestStateMachine extends StateMachine {
- public final ArrayList<Message> messages = new ArrayList<>();
- private final State
- mLoggingState = new EntitlementManagerTest.TestStateMachine.LoggingState();
+ @Test
+ public void testsetExemptedDownstreamType() throws Exception {
+ setupForRequiredProvisioning();
+ // Cellular upstream is not permitted when no entitlement result.
+ assertFalse(mEnMgr.isCellularUpstreamPermitted());
- class LoggingState extends State {
- @Override public void enter() {
- messages.clear();
- }
+ // If there is exempted downstream and no other non-exempted downstreams, cellular is
+ // permitted.
+ mEnMgr.setExemptedDownstreamType(TETHERING_WIFI);
+ assertTrue(mEnMgr.isCellularUpstreamPermitted());
- @Override public void exit() {
- messages.clear();
- }
+ // If second downstream run entitlement check fail, cellular upstream is not permitted.
+ mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISIONING_FAILED;
+ mEnMgr.notifyUpstream(true);
+ mLooper.dispatchAll();
+ mEnMgr.startProvisioningIfNeeded(TETHERING_USB, true);
+ mLooper.dispatchAll();
+ assertFalse(mEnMgr.isCellularUpstreamPermitted());
- @Override public boolean processMessage(Message msg) {
- messages.add(msg);
- return false;
- }
- }
+ // When second downstream is down, exempted downstream can use cellular upstream.
+ assertEquals(1, mEnMgr.uiProvisionCount);
+ verify(mEntitlementFailedListener).onUiEntitlementFailed(TETHERING_USB);
+ mEnMgr.stopProvisioningIfNeeded(TETHERING_USB);
+ assertTrue(mEnMgr.isCellularUpstreamPermitted());
- public TestStateMachine() {
- super("EntitlementManagerTest.TestStateMachine", mLooper.getLooper());
- addState(mLoggingState);
- setInitialState(mLoggingState);
- super.start();
- }
+ mEnMgr.stopProvisioningIfNeeded(TETHERING_WIFI);
+ assertFalse(mEnMgr.isCellularUpstreamPermitted());
}
}
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
index f71cd21..b291438 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/OffloadControllerTest.java
@@ -26,10 +26,10 @@
import static android.net.RouteInfo.RTN_UNICAST;
import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
-import static com.android.networkstack.tethering.OffloadController.DEFAULT_PERFORM_POLL_INTERVAL_MS;
import static com.android.networkstack.tethering.OffloadController.StatsType.STATS_PER_IFACE;
import static com.android.networkstack.tethering.OffloadController.StatsType.STATS_PER_UID;
import static com.android.networkstack.tethering.OffloadHardwareInterface.ForwardedStats;
+import static com.android.networkstack.tethering.TetheringConfiguration.DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS;
import static com.android.testutils.MiscAssertsKt.assertContainsAll;
import static com.android.testutils.MiscAssertsKt.assertThrows;
import static com.android.testutils.NetworkStatsUtilsKt.assertNetworkStatsEquals;
@@ -109,6 +109,7 @@
@Mock private ApplicationInfo mApplicationInfo;
@Mock private Context mContext;
@Mock private NetworkStatsManager mStatsManager;
+ @Mock private TetheringConfiguration mTetherConfig;
// Late init since methods must be called by the thread that created this object.
private TestableNetworkStatsProviderCbBinder mTetherStatsProviderCb;
private OffloadController.OffloadTetheringStatsProvider mTetherStatsProvider;
@@ -120,8 +121,8 @@
private final TestLooper mTestLooper = new TestLooper();
private OffloadController.Dependencies mDeps = new OffloadController.Dependencies() {
@Override
- int getPerformPollInterval() {
- return -1; // Disabled.
+ public TetheringConfiguration getTetherConfig() {
+ return mTetherConfig;
}
};
@@ -133,6 +134,7 @@
mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
when(mContext.getContentResolver()).thenReturn(mContentResolver);
FakeSettingsProvider.clearSettingsProvider();
+ when(mTetherConfig.getOffloadPollInterval()).thenReturn(-1); // Disabled.
}
@After public void tearDown() throws Exception {
@@ -153,12 +155,7 @@
}
private void setOffloadPollInterval(int interval) {
- mDeps = new OffloadController.Dependencies() {
- @Override
- int getPerformPollInterval() {
- return interval;
- }
- };
+ when(mTetherConfig.getOffloadPollInterval()).thenReturn(interval);
}
private void waitForIdle() {
@@ -364,9 +361,9 @@
stacked.setInterfaceName("stacked");
stacked.addLinkAddress(new LinkAddress("192.0.2.129/25"));
stacked.addRoute(new RouteInfo(null, InetAddress.getByName("192.0.2.254"), null,
- RTN_UNICAST));
+ RTN_UNICAST));
stacked.addRoute(new RouteInfo(null, InetAddress.getByName("fe80::bad:f00"), null,
- RTN_UNICAST));
+ RTN_UNICAST));
assertTrue(lp.addStackedLink(stacked));
offload.setUpstreamLinkProperties(lp);
// No change in local addresses means no call to setLocalPrefixes().
@@ -785,7 +782,7 @@
public void testOnSetAlert() throws Exception {
setupFunctioningHardwareInterface();
enableOffload();
- setOffloadPollInterval(DEFAULT_PERFORM_POLL_INTERVAL_MS);
+ setOffloadPollInterval(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS);
final OffloadController offload = makeOffloadController();
offload.start();
@@ -807,14 +804,14 @@
when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(
new ForwardedStats(0, 0));
mTetherStatsProvider.onSetAlert(100);
- mTestLooper.moveTimeForward(DEFAULT_PERFORM_POLL_INTERVAL_MS);
+ mTestLooper.moveTimeForward(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS);
waitForIdle();
mTetherStatsProviderCb.assertNoCallback();
// Verify that notifyAlertReached fired when quota is reached.
when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(
new ForwardedStats(50, 50));
- mTestLooper.moveTimeForward(DEFAULT_PERFORM_POLL_INTERVAL_MS);
+ mTestLooper.moveTimeForward(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS);
waitForIdle();
mTetherStatsProviderCb.expectNotifyAlertReached();
@@ -822,7 +819,7 @@
// any stats since the polling is stopped.
reset(mHardware);
mTetherStatsProvider.onSetAlert(NetworkStatsProvider.QUOTA_UNLIMITED);
- mTestLooper.moveTimeForward(DEFAULT_PERFORM_POLL_INTERVAL_MS);
+ mTestLooper.moveTimeForward(DEFAULT_TETHER_OFFLOAD_POLL_INTERVAL_MS);
waitForIdle();
mTetherStatsProviderCb.assertNoCallback();
verify(mHardware, never()).getForwardedStats(any());
diff --git a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
index 4471f64..85c2f2b 100644
--- a/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
+++ b/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringTest.java
@@ -57,6 +57,7 @@
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.notNull;
+import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.any;
@@ -150,6 +151,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.util.ArrayList;
@@ -170,6 +173,8 @@
private static final String TEST_P2P_IFNAME = "test_p2p-p2p0-0";
private static final String TEST_NCM_IFNAME = "test_ncm0";
private static final String TETHERING_NAME = "Tethering";
+ private static final String[] PROVISIONING_APP_NAME = {"some", "app"};
+ private static final String PROVISIONING_NO_UI_APP_NAME = "no_ui_app";
private static final int DHCPSERVER_START_TIMEOUT_MS = 1000;
@@ -212,6 +217,9 @@
private Tethering mTethering;
private PhoneStateListener mPhoneStateListener;
private InterfaceConfigurationParcel mInterfaceConfiguration;
+ private TetheringConfiguration mConfig;
+ private EntitlementManager mEntitleMgr;
+ private OffloadController mOffloadCtrl;
private class TestContext extends BroadcastInterceptingContext {
TestContext(Context base) {
@@ -297,8 +305,9 @@
}
}
- private class MockTetheringConfiguration extends TetheringConfiguration {
- MockTetheringConfiguration(Context ctx, SharedLog log, int id) {
+ // MyTetheringConfiguration is used to override static method for testing.
+ private class MyTetheringConfiguration extends TetheringConfiguration {
+ MyTetheringConfiguration(Context ctx, SharedLog log, int id) {
super(ctx, log, id);
}
@@ -328,6 +337,15 @@
}
@Override
+ public OffloadController getOffloadController(Handler h, SharedLog log,
+ OffloadController.Dependencies deps) {
+ mOffloadCtrl = spy(super.getOffloadController(h, log, deps));
+ // Return real object here instead of mock because
+ // testReportFailCallbackIfOffloadNotSupported depend on real OffloadController object.
+ return mOffloadCtrl;
+ }
+
+ @Override
public UpstreamNetworkMonitor getUpstreamNetworkMonitor(Context ctx,
StateMachine target, SharedLog log, int what) {
mUpstreamNetworkMonitorMasterSM = target;
@@ -352,6 +370,13 @@
}
@Override
+ public EntitlementManager getEntitlementManager(Context ctx, Handler h, SharedLog log,
+ Runnable callback) {
+ mEntitleMgr = spy(super.getEntitlementManager(ctx, h, log, callback));
+ return mEntitleMgr;
+ }
+
+ @Override
public boolean isTetheringSupported() {
return true;
}
@@ -359,7 +384,8 @@
@Override
public TetheringConfiguration generateTetheringConfiguration(Context ctx, SharedLog log,
int subId) {
- return new MockTetheringConfiguration(ctx, log, subId);
+ mConfig = spy(new MyTetheringConfiguration(ctx, log, subId));
+ return mConfig;
}
@Override
@@ -516,16 +542,16 @@
}
private TetheringRequestParcel createTetheringRequestParcel(final int type) {
- return createTetheringRequestParcel(type, null, null);
+ return createTetheringRequestParcel(type, null, null, false);
}
private TetheringRequestParcel createTetheringRequestParcel(final int type,
- final LinkAddress serverAddr, final LinkAddress clientAddr) {
+ final LinkAddress serverAddr, final LinkAddress clientAddr, final boolean exempt) {
final TetheringRequestParcel request = new TetheringRequestParcel();
request.tetheringType = type;
request.localIPv4Address = serverAddr;
request.staticClientAddress = clientAddr;
- request.exemptFromEntitlementCheck = false;
+ request.exemptFromEntitlementCheck = exempt;
request.showProvisioningUi = false;
return request;
@@ -1636,7 +1662,7 @@
// Enable USB tethering and check that Tethering starts USB.
mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB,
- null, null), firstResult);
+ null, null, false), firstResult);
mLooper.dispatchAll();
firstResult.assertHasResult();
verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_RNDIS);
@@ -1644,7 +1670,7 @@
// Enable USB tethering again with the same request and expect no change to USB.
mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB,
- null, null), secondResult);
+ null, null, false), secondResult);
mLooper.dispatchAll();
secondResult.assertHasResult();
verify(mUsbManager, never()).setCurrentFunctions(UsbManager.FUNCTION_NONE);
@@ -1653,7 +1679,7 @@
// Enable USB tethering with a different request and expect that USB is stopped and
// started.
mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB,
- serverLinkAddr, clientLinkAddr), thirdResult);
+ serverLinkAddr, clientLinkAddr, false), thirdResult);
mLooper.dispatchAll();
thirdResult.assertHasResult();
verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_NONE);
@@ -1677,7 +1703,7 @@
final ArgumentCaptor<DhcpServingParamsParcel> dhcpParamsCaptor =
ArgumentCaptor.forClass(DhcpServingParamsParcel.class);
mTethering.startTethering(createTetheringRequestParcel(TETHERING_USB,
- serverLinkAddr, clientLinkAddr), null);
+ serverLinkAddr, clientLinkAddr, false), null);
mLooper.dispatchAll();
verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_RNDIS);
mTethering.interfaceStatusChanged(TEST_USB_IFNAME, true);
@@ -1726,6 +1752,82 @@
verify(mNotificationUpdater, never()).onUpstreamCapabilitiesChanged(any());
}
+ @Test
+ public void testDumpTetheringLog() throws Exception {
+ final FileDescriptor mockFd = mock(FileDescriptor.class);
+ final PrintWriter mockPw = mock(PrintWriter.class);
+ runUsbTethering(null);
+ mLooper.startAutoDispatch();
+ mTethering.dump(mockFd, mockPw, new String[0]);
+ verify(mConfig).dump(any());
+ verify(mEntitleMgr).dump(any());
+ verify(mOffloadCtrl).dump(any());
+ mLooper.stopAutoDispatch();
+ }
+
+ @Test
+ public void testExemptFromEntitlementCheck() throws Exception {
+ setupForRequiredProvisioning();
+ final TetheringRequestParcel wifiNotExemptRequest =
+ createTetheringRequestParcel(TETHERING_WIFI, null, null, false);
+ mTethering.startTethering(wifiNotExemptRequest, null);
+ mLooper.dispatchAll();
+ verify(mEntitleMgr).startProvisioningIfNeeded(TETHERING_WIFI, false);
+ verify(mEntitleMgr, never()).setExemptedDownstreamType(TETHERING_WIFI);
+ assertFalse(mEntitleMgr.isCellularUpstreamPermitted());
+ mTethering.stopTethering(TETHERING_WIFI);
+ mLooper.dispatchAll();
+ verify(mEntitleMgr).stopProvisioningIfNeeded(TETHERING_WIFI);
+ reset(mEntitleMgr);
+
+ setupForRequiredProvisioning();
+ final TetheringRequestParcel wifiExemptRequest =
+ createTetheringRequestParcel(TETHERING_WIFI, null, null, true);
+ mTethering.startTethering(wifiExemptRequest, null);
+ mLooper.dispatchAll();
+ verify(mEntitleMgr, never()).startProvisioningIfNeeded(TETHERING_WIFI, false);
+ verify(mEntitleMgr).setExemptedDownstreamType(TETHERING_WIFI);
+ assertTrue(mEntitleMgr.isCellularUpstreamPermitted());
+ mTethering.stopTethering(TETHERING_WIFI);
+ mLooper.dispatchAll();
+ verify(mEntitleMgr).stopProvisioningIfNeeded(TETHERING_WIFI);
+ reset(mEntitleMgr);
+
+ // If one app enables tethering without provisioning check first, then another app enables
+ // tethering of the same type but does not disable the provisioning check.
+ setupForRequiredProvisioning();
+ mTethering.startTethering(wifiExemptRequest, null);
+ mLooper.dispatchAll();
+ verify(mEntitleMgr, never()).startProvisioningIfNeeded(TETHERING_WIFI, false);
+ verify(mEntitleMgr).setExemptedDownstreamType(TETHERING_WIFI);
+ assertTrue(mEntitleMgr.isCellularUpstreamPermitted());
+ reset(mEntitleMgr);
+ setupForRequiredProvisioning();
+ mTethering.startTethering(wifiNotExemptRequest, null);
+ mLooper.dispatchAll();
+ verify(mEntitleMgr).startProvisioningIfNeeded(TETHERING_WIFI, false);
+ verify(mEntitleMgr, never()).setExemptedDownstreamType(TETHERING_WIFI);
+ assertFalse(mEntitleMgr.isCellularUpstreamPermitted());
+ mTethering.stopTethering(TETHERING_WIFI);
+ mLooper.dispatchAll();
+ verify(mEntitleMgr).stopProvisioningIfNeeded(TETHERING_WIFI);
+ reset(mEntitleMgr);
+ }
+
+ private void setupForRequiredProvisioning() {
+ // Produce some acceptable looking provision app setting if requested.
+ when(mResources.getStringArray(R.array.config_mobile_hotspot_provision_app))
+ .thenReturn(PROVISIONING_APP_NAME);
+ when(mResources.getString(R.string.config_mobile_hotspot_provision_app_no_ui))
+ .thenReturn(PROVISIONING_NO_UI_APP_NAME);
+ // Act like the CarrierConfigManager is present and ready unless told otherwise.
+ when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE))
+ .thenReturn(mCarrierConfigManager);
+ when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(mCarrierConfig);
+ mCarrierConfig.putBoolean(CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL, true);
+ mCarrierConfig.putBoolean(CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
+ sendConfigurationChanged();
+ }
// TODO: Test that a request for hotspot mode doesn't interfere with an
// already operating tethering mode interface.
}