Merge "To add the Android.bp of ExternalStorageProvider"
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 9ab7c21..564e918 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -3061,7 +3061,9 @@
* frameworks/base/packages/NetworkStack/
*/
message NetworkStackReported {
- optional int32 eventId = 1;
+ // The id that indicates the event reported from NetworkStack.
+ optional int32 event_id = 1;
+ // The data for the reported events.
optional android.stats.connectivity.NetworkStackEventData network_stack_event = 2 [(log_mode) = MODE_BYTES];
}
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index ae93cf0..4a64128 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -1934,6 +1934,8 @@
@NonNull Callback callback) {
ParcelFileDescriptor dup;
try {
+ // Dup is needed here as the pfd inside the socket is owned by the IpSecService,
+ // which cannot be obtained by the app process.
dup = ParcelFileDescriptor.dup(socket.getFileDescriptor());
} catch (IOException ignored) {
// Construct an invalid fd, so that if the user later calls start(), it will fail with
@@ -1975,6 +1977,7 @@
@NonNull Callback callback) {
ParcelFileDescriptor dup;
try {
+ // TODO: Consider remove unnecessary dup.
dup = pfd.dup();
} catch (IOException ignored) {
// Construct an invalid fd, so that if the user later calls start(), it will fail with
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index a7d2ee9..b90a60e 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -373,7 +373,8 @@
* A callback to be invoked when the system successfully delivers your {@link NdefMessage}
* to another device.
* @see #setOnNdefPushCompleteCallback
- * @deprecated this feature is deprecated.
+ * @deprecated this feature is deprecated. File sharing can work using other technology like
+ * Bluetooth.
*/
@java.lang.Deprecated
public interface OnNdefPushCompleteCallback {
@@ -398,7 +399,8 @@
* content currently visible to the user. Alternatively, you can call {@link
* #setNdefPushMessage setNdefPushMessage()} if the {@link NdefMessage} always contains the
* same data.
- * @deprecated this feature is deprecated.
+ * @deprecated this feature is deprecated. File sharing can work using other technology like
+ * Bluetooth.
*/
@java.lang.Deprecated
public interface CreateNdefMessageCallback {
@@ -427,7 +429,8 @@
/**
- * @deprecated this feature is deprecated.
+ * @deprecated this feature is deprecated. File sharing can work using other technology like
+ * Bluetooth.
*/
@java.lang.Deprecated
public interface CreateBeamUrisCallback {
@@ -981,7 +984,8 @@
* @param uris an array of Uri(s) to push over Android Beam
* @param activity activity for which the Uri(s) will be pushed
* @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
- * @deprecated this feature is deprecated.
+ * @deprecated this feature is deprecated. File sharing can work using other technology like
+ * Bluetooth.
*/
@java.lang.Deprecated
public void setBeamPushUris(Uri[] uris, Activity activity) {
@@ -1068,7 +1072,8 @@
* @param callback callback, or null to disable
* @param activity activity for which the Uri(s) will be pushed
* @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
- * @deprecated this feature is deprecated.
+ * @deprecated this feature is deprecated. File sharing can work using other technology like
+ * Bluetooth.
*/
@java.lang.Deprecated
public void setBeamPushUrisCallback(CreateBeamUrisCallback callback, Activity activity) {
@@ -1157,7 +1162,8 @@
* to only register one at a time, and to do so in that activity's
* {@link Activity#onCreate}
* @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
- * @deprecated this feature is deprecated.
+ * @deprecated this feature is deprecated. File sharing can work using other technology like
+ * Bluetooth.
*/
@java.lang.Deprecated
public void setNdefPushMessage(NdefMessage message, Activity activity,
@@ -1275,7 +1281,8 @@
* to only register one at a time, and to do so in that activity's
* {@link Activity#onCreate}
* @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
- * @deprecated this feature is deprecated.
+ * @deprecated this feature is deprecated. File sharing can work using other technology like
+ * Bluetooth.
*/
@java.lang.Deprecated
public void setNdefPushMessageCallback(CreateNdefMessageCallback callback, Activity activity,
@@ -1361,7 +1368,8 @@
* to only register one at a time, and to do so in that activity's
* {@link Activity#onCreate}
* @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
- * @deprecated this feature is deprecated.
+ * @deprecated this feature is deprecated. File sharing can work using other technology like
+ * Bluetooth.
*/
@java.lang.Deprecated
public void setOnNdefPushCompleteCallback(OnNdefPushCompleteCallback callback,
@@ -1577,7 +1585,8 @@
* @param activity the current foreground Activity that has registered data to share
* @return whether the Beam animation was successfully invoked
* @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
- * @deprecated this feature is deprecated.
+ * @deprecated this feature is deprecated. File sharing can work using other technology like
+ * Bluetooth.
*/
@java.lang.Deprecated
public boolean invokeBeam(Activity activity) {
@@ -1821,7 +1830,8 @@
* @see android.provider.Settings#ACTION_NFCSHARING_SETTINGS
* @return true if NDEF Push feature is enabled
* @throws UnsupportedOperationException if FEATURE_NFC is unavailable.
- * @deprecated this feature is deprecated.
+ * @deprecated this feature is deprecated. File sharing can work using other technology like
+ * Bluetooth.
*/
@java.lang.Deprecated
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 7187919..9aedb76 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -608,6 +608,9 @@
<protected-broadcast android:name="android.provider.action.DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL" />
+ <!-- For tether entitlement recheck-->
+ <protected-broadcast
+ android:name="com.android.server.connectivity.tethering.PROVISIONING_RECHECK_ALARM" />
<!-- ====================================================================== -->
<!-- RUNTIME PERMISSIONS -->
<!-- ====================================================================== -->
diff --git a/core/xsd/permission.xsd b/core/xsd/permission.xsd
index d90863b..1228124 100644
--- a/core/xsd/permission.xsd
+++ b/core/xsd/permission.xsd
@@ -60,8 +60,6 @@
<xs:attribute name="uid" type="xs:int"/>
</xs:complexType>
<xs:complexType name="split-permission">
- <xs:attribute name="name" type="xs:string"/>
- <xs:attribute name="targetSdk" type="xs:int"/>
<xs:sequence>
<xs:element name="library" maxOccurs="unbounded">
<xs:complexType>
@@ -69,6 +67,8 @@
</xs:complexType>
</xs:element>
</xs:sequence>
+ <xs:attribute name="name" type="xs:string"/>
+ <xs:attribute name="targetSdk" type="xs:int"/>
</xs:complexType>
<xs:complexType name="library">
<xs:attribute name="name" type="xs:string"/>
@@ -124,7 +124,6 @@
<xs:attribute name="package" type="xs:string"/>
</xs:complexType>
<xs:complexType name="privapp-permissions">
- <xs:attribute name="package" type="xs:string"/>
<xs:sequence>
<xs:element name="permission" maxOccurs="unbounded">
<xs:complexType>
@@ -137,9 +136,9 @@
</xs:complexType>
</xs:element>
</xs:sequence>
+ <xs:attribute name="package" type="xs:string"/>
</xs:complexType>
<xs:complexType name="oem-permissions">
- <xs:attribute name="package" type="xs:string"/>
<xs:sequence>
<xs:element name="permission" maxOccurs="unbounded">
<xs:complexType>
@@ -152,6 +151,7 @@
</xs:complexType>
</xs:element>
</xs:sequence>
+ <xs:attribute name="package" type="xs:string"/>
</xs:complexType>
<xs:complexType name="hidden-api-whitelisted-app">
<xs:attribute name="package" type="xs:string"/>
diff --git a/packages/NetworkStack/Android.bp b/packages/NetworkStack/Android.bp
index 52534a8..0bd5c5f 100644
--- a/packages/NetworkStack/Android.bp
+++ b/packages/NetworkStack/Android.bp
@@ -49,6 +49,9 @@
// Resources already included in NetworkStackBase
resource_dirs: [],
jarjar_rules: "jarjar-rules-shared.txt",
+ optimize: {
+ proguard_flags_files: ["proguard.flags"],
+ },
// The permission configuration *must* be included to ensure security of the device
required: ["NetworkStackPermissionStub"],
}
diff --git a/packages/NetworkStack/proguard.flags b/packages/NetworkStack/proguard.flags
new file mode 100644
index 0000000..c60f6c3
--- /dev/null
+++ b/packages/NetworkStack/proguard.flags
@@ -0,0 +1,9 @@
+-keepclassmembers class android.net.ip.IpClient {
+ static final int CMD_*;
+ static final int EVENT_*;
+}
+
+-keepclassmembers class android.net.dhcp.DhcpClient {
+ static final int CMD_*;
+ static final int EVENT_*;
+}
diff --git a/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java b/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java
index c6dd0117..79d6a55 100644
--- a/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java
+++ b/packages/NetworkStack/src/android/net/dhcp/DhcpClient.java
@@ -126,6 +126,7 @@
// DhcpClient uses IpClient's handler.
private static final int PUBLIC_BASE = IpClient.DHCPCLIENT_CMD_BASE;
+ // Below constants are picked up by MessageUtils and exempt from ProGuard optimization.
/* Commands from controller to start/stop DHCP */
public static final int CMD_START_DHCP = PUBLIC_BASE + 1;
public static final int CMD_STOP_DHCP = PUBLIC_BASE + 2;
diff --git a/packages/NetworkStack/src/android/net/ip/IpClient.java b/packages/NetworkStack/src/android/net/ip/IpClient.java
index 346ac68..7a06af4 100644
--- a/packages/NetworkStack/src/android/net/ip/IpClient.java
+++ b/packages/NetworkStack/src/android/net/ip/IpClient.java
@@ -282,6 +282,7 @@
public static final String DUMP_ARG_CONFIRM = "confirm";
+ // Below constants are picked up by MessageUtils and exempt from ProGuard optimization.
private static final int CMD_TERMINATE_AFTER_STOP = 1;
private static final int CMD_STOP = 2;
private static final int CMD_START = 3;
diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
index ce887eb..d7a57b9 100644
--- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
+++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
@@ -154,12 +154,19 @@
// keepalives are sent cannot be reused by another app even if the fd gets closed by
// the user. A null is acceptable here for backward compatibility of PacketKeepalive
// API.
- // TODO: don't accept null fd after legacy packetKeepalive API is removed.
try {
if (fd != null) {
mFd = Os.dup(fd);
} else {
- Log.d(TAG, "uid/pid " + mUid + "/" + mPid + " calls with null fd");
+ Log.d(TAG, toString() + " calls with null fd");
+ if (!mPrivileged) {
+ throw new SecurityException(
+ "null fd is not allowed for unprivileged access.");
+ }
+ if (mType == TYPE_TCP) {
+ throw new IllegalArgumentException(
+ "null fd is not allowed for tcp socket keepalives.");
+ }
mFd = null;
}
} catch (ErrnoException e) {
@@ -480,7 +487,6 @@
}
} else {
// Keepalive successfully stopped, or error.
- ki.mStartedState = KeepaliveInfo.NOT_STARTED;
if (reason == SUCCESS) {
// The message indicated success stopping : don't call handleStopKeepalive.
if (DBG) Log.d(TAG, "Successfully stopped keepalive " + slot + " on " + nai.name());
@@ -490,6 +496,7 @@
handleStopKeepalive(nai, slot, reason);
if (DBG) Log.d(TAG, "Keepalive " + slot + " on " + nai.name() + " error " + reason);
}
+ ki.mStartedState = KeepaliveInfo.NOT_STARTED;
}
}
@@ -531,7 +538,8 @@
try {
ki = new KeepaliveInfo(cb, nai, packet, intervalSeconds,
KeepaliveInfo.TYPE_NATT, fd);
- } catch (InvalidSocketException e) {
+ } catch (InvalidSocketException | IllegalArgumentException | SecurityException e) {
+ Log.e(TAG, "Fail to construct keepalive", e);
notifyErrorCallback(cb, ERROR_INVALID_SOCKET);
return;
}
@@ -570,7 +578,8 @@
try {
ki = new KeepaliveInfo(cb, nai, packet, intervalSeconds,
KeepaliveInfo.TYPE_TCP, fd);
- } catch (InvalidSocketException e) {
+ } catch (InvalidSocketException | IllegalArgumentException | SecurityException e) {
+ Log.e(TAG, "Fail to construct keepalive e=" + e);
notifyErrorCallback(cb, ERROR_INVALID_SOCKET);
return;
}
diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index 828a1e5..ac3d6de 100644
--- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
@@ -19,6 +19,7 @@
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static android.telephony.SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
import android.app.Notification;
import android.app.NotificationManager;
@@ -26,9 +27,12 @@
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
+import android.net.NetworkSpecifier;
+import android.net.StringNetworkSpecifier;
import android.net.wifi.WifiInfo;
import android.os.UserHandle;
import android.telephony.AccessNetworkConstants.TransportType;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Slog;
@@ -195,7 +199,20 @@
title = r.getString(R.string.network_available_sign_in, 0);
// TODO: Change this to pull from NetworkInfo once a printable
// name has been added to it
- details = mTelephonyManager.getNetworkOperatorName();
+ NetworkSpecifier specifier = nai.networkCapabilities.getNetworkSpecifier();
+ int subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
+ if (specifier instanceof StringNetworkSpecifier) {
+ try {
+ subId = Integer.parseInt(
+ ((StringNetworkSpecifier) specifier).specifier);
+ } catch (NumberFormatException e) {
+ Slog.e(TAG, "NumberFormatException on "
+ + ((StringNetworkSpecifier) specifier).specifier);
+ }
+ }
+
+ details = mTelephonyManager.createForSubscriptionId(subId)
+ .getNetworkOperatorName();
break;
default:
title = r.getString(R.string.network_available_sign_in, 0);
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 37fe3d0..0b1a98e 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -82,7 +82,6 @@
import android.os.INetworkManagementService;
import android.os.Looper;
import android.os.Message;
-import android.os.Parcel;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ResultReceiver;
@@ -230,8 +229,11 @@
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_CARRIER_CONFIG_CHANGED);
- mEntitlementMgr = mDeps.getEntitlementManager(mContext, mTetherMasterSM,
- mLog, systemProperties);
+ // 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, systemProperties);
+
mCarrierConfigChange = new VersionedBroadcastListener(
"CarrierConfigChangeListener", mContext, mHandler, filter,
(Intent ignored) -> {
@@ -363,55 +365,28 @@
}
public void startTethering(int type, ResultReceiver receiver, boolean showProvisioningUi) {
- mEntitlementMgr.startTethering(type);
- if (!mEntitlementMgr.isTetherProvisioningRequired()) {
- enableTetheringInternal(type, true, receiver);
- return;
- }
-
- final ResultReceiver proxyReceiver = getProxyReceiver(type, receiver);
- if (showProvisioningUi) {
- mEntitlementMgr.runUiTetherProvisioningAndEnable(type, proxyReceiver);
- } else {
- mEntitlementMgr.runSilentTetherProvisioningAndEnable(type, proxyReceiver);
- }
+ mEntitlementMgr.startProvisioningIfNeeded(type, showProvisioningUi);
+ enableTetheringInternal(type, true /* enabled */, receiver);
}
public void stopTethering(int type) {
- enableTetheringInternal(type, false, null);
- mEntitlementMgr.stopTethering(type);
- if (mEntitlementMgr.isTetherProvisioningRequired()) {
- // There are lurking bugs where the notion of "provisioning required" or
- // "tethering supported" may change without notifying tethering properly, then
- // tethering can't shutdown correctly.
- // TODO: cancel re-check all the time
- if (mDeps.isTetheringSupported()) {
- mEntitlementMgr.cancelTetherProvisioningRechecks(type);
- }
- }
+ enableTetheringInternal(type, false /* disabled */, null);
+ mEntitlementMgr.stopProvisioningIfNeeded(type);
}
/**
- * Enables or disables tethering for the given type. This should only be called once
- * provisioning has succeeded or is not necessary. It will also schedule provisioning rechecks
- * for the specified interface.
+ * Enables or disables tethering for the given type. If provisioning is required, it will
+ * schedule provisioning rechecks for the specified interface.
*/
private void enableTetheringInternal(int type, boolean enable, ResultReceiver receiver) {
- boolean isProvisioningRequired = enable && mEntitlementMgr.isTetherProvisioningRequired();
int result;
switch (type) {
case TETHERING_WIFI:
result = setWifiTethering(enable);
- if (isProvisioningRequired && result == TETHER_ERROR_NO_ERROR) {
- mEntitlementMgr.scheduleProvisioningRechecks(type);
- }
sendTetherResult(receiver, result);
break;
case TETHERING_USB:
result = setUsbTethering(enable);
- if (isProvisioningRequired && result == TETHER_ERROR_NO_ERROR) {
- mEntitlementMgr.scheduleProvisioningRechecks(type);
- }
sendTetherResult(receiver, result);
break;
case TETHERING_BLUETOOTH:
@@ -469,46 +444,11 @@
? TETHER_ERROR_NO_ERROR
: TETHER_ERROR_MASTER_ERROR;
sendTetherResult(receiver, result);
- if (enable && mEntitlementMgr.isTetherProvisioningRequired()) {
- mEntitlementMgr.scheduleProvisioningRechecks(TETHERING_BLUETOOTH);
- }
adapter.closeProfileProxy(BluetoothProfile.PAN, proxy);
}
}, BluetoothProfile.PAN);
}
- /**
- * Creates a proxy {@link ResultReceiver} which enables tethering if the provisioning result
- * is successful before firing back up to the wrapped receiver.
- *
- * @param type The type of tethering being enabled.
- * @param receiver A ResultReceiver which will be called back with an int resultCode.
- * @return The proxy receiver.
- */
- private ResultReceiver getProxyReceiver(final int type, final ResultReceiver receiver) {
- ResultReceiver rr = new ResultReceiver(null) {
- @Override
- protected void onReceiveResult(int resultCode, Bundle resultData) {
- // If provisioning is successful, enable tethering, otherwise just send the error.
- if (resultCode == TETHER_ERROR_NO_ERROR) {
- enableTetheringInternal(type, true, receiver);
- } else {
- sendTetherResult(receiver, resultCode);
- }
- mEntitlementMgr.updateEntitlementCacheValue(type, resultCode);
- }
- };
-
- // The following is necessary to avoid unmarshalling issues when sending the receiver
- // across processes.
- Parcel parcel = Parcel.obtain();
- rr.writeToParcel(parcel,0);
- parcel.setDataPosition(0);
- ResultReceiver receiverForSending = ResultReceiver.CREATOR.createFromParcel(parcel);
- parcel.recycle();
- return receiverForSending;
- }
-
public int tether(String iface) {
return tether(iface, IpServer.STATE_TETHERED);
}
@@ -787,6 +727,7 @@
if (!usbConnected && mRndisEnabled) {
// Turn off tethering if it was enabled and there is a disconnect.
tetherMatchingInterfaces(IpServer.STATE_AVAILABLE, TETHERING_USB);
+ mEntitlementMgr.stopProvisioningIfNeeded(TETHERING_USB);
} else if (usbConfigured && rndisEnabled) {
// Tether if rndis is enabled and usb is configured.
tetherMatchingInterfaces(IpServer.STATE_TETHERED, TETHERING_USB);
@@ -813,6 +754,7 @@
case WifiManager.WIFI_AP_STATE_FAILED:
default:
disableWifiIpServingLocked(ifname, curState);
+ mEntitlementMgr.stopProvisioningIfNeeded(TETHERING_WIFI);
break;
}
}
@@ -1090,6 +1032,8 @@
// we treated the error and want now to clear it
static final int CMD_CLEAR_ERROR = BASE_MASTER + 6;
static final int EVENT_IFACE_UPDATE_LINKPROPERTIES = BASE_MASTER + 7;
+ // Events from EntitlementManager to choose upstream again.
+ static final int EVENT_UPSTREAM_PERMISSION_CHANGED = BASE_MASTER + 8;
private final State mInitialState;
private final State mTetherModeAliveState;
@@ -1504,6 +1448,7 @@
}
break;
}
+ case EVENT_UPSTREAM_PERMISSION_CHANGED:
case CMD_UPSTREAM_CHANGED:
updateUpstreamWanted();
if (!mUpstreamWanted) break;
@@ -1694,7 +1639,8 @@
}
public void systemReady() {
- mUpstreamNetworkMonitor.startTrackDefaultNetwork(mDeps.getDefaultNetworkRequest());
+ mUpstreamNetworkMonitor.startTrackDefaultNetwork(mDeps.getDefaultNetworkRequest(),
+ mEntitlementMgr);
}
/** Get the latest value of the tethering entitlement check. */
@@ -1755,6 +1701,11 @@
cfg.dump(pw);
pw.decreaseIndent();
+ pw.println("Entitlement:");
+ pw.increaseIndent();
+ mEntitlementMgr.dump(pw);
+ pw.decreaseIndent();
+
synchronized (mPublicSync) {
pw.println("Tether state:");
pw.increaseIndent();
diff --git a/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java b/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java
index 70ab389..5c45397 100644
--- a/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java
+++ b/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java
@@ -18,9 +18,11 @@
import static android.net.ConnectivityManager.EXTRA_ADD_TETHER_TYPE;
import static android.net.ConnectivityManager.EXTRA_PROVISION_CALLBACK;
-import static android.net.ConnectivityManager.EXTRA_REM_TETHER_TYPE;
import static android.net.ConnectivityManager.EXTRA_RUN_PROVISION;
-import static android.net.ConnectivityManager.EXTRA_SET_ALARM;
+import static android.net.ConnectivityManager.TETHERING_BLUETOOTH;
+import static android.net.ConnectivityManager.TETHERING_INVALID;
+import static android.net.ConnectivityManager.TETHERING_USB;
+import static android.net.ConnectivityManager.TETHERING_WIFI;
import static android.net.ConnectivityManager.TETHER_ERROR_ENTITLEMENT_UNKONWN;
import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
import static android.net.ConnectivityManager.TETHER_ERROR_PROVISION_FAILED;
@@ -28,17 +30,24 @@
import static com.android.internal.R.string.config_wifi_tether_enable;
import android.annotation.Nullable;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.res.Resources;
import android.net.util.SharedLog;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
import android.os.Parcel;
import android.os.PersistableBundle;
import android.os.ResultReceiver;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
import android.telephony.CarrierConfigManager;
@@ -46,48 +55,78 @@
import android.util.Log;
import android.util.SparseIntArray;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.StateMachine;
import com.android.server.connectivity.MockableSystemProperties;
+import java.io.PrintWriter;
+
/**
- * This class encapsulates entitlement/provisioning mechanics
- * provisioning check only applies to the use of the mobile network as an upstream
+ * Re-check tethering provisioning for enabled downstream tether types.
+ * Reference ConnectivityManager.TETHERING_{@code *} for each tether type.
*
+ * All methods of this class must be accessed from the thread of tethering
+ * state machine.
* @hide
*/
public class EntitlementManager {
private static final String TAG = EntitlementManager.class.getSimpleName();
private static final boolean DBG = false;
+ @VisibleForTesting
+ protected static final String DISABLE_PROVISIONING_SYSPROP_KEY = "net.tethering.noprovisioning";
+ private static final String ACTION_PROVISIONING_ALARM =
+ "com.android.server.connectivity.tethering.PROVISIONING_RECHECK_ALARM";
+
// {@link ComponentName} of the Service used to run tether provisioning.
private static final ComponentName TETHER_SERVICE = ComponentName.unflattenFromString(
Resources.getSystem().getString(config_wifi_tether_enable));
- protected static final String DISABLE_PROVISIONING_SYSPROP_KEY = "net.tethering.noprovisioning";
+ private static final int MS_PER_HOUR = 60 * 60 * 1000;
+ private static final int EVENT_START_PROVISIONING = 0;
+ private static final int EVENT_STOP_PROVISIONING = 1;
+ private static final int EVENT_UPSTREAM_CHANGED = 2;
+ private static final int EVENT_MAYBE_RUN_PROVISIONING = 3;
+ private static final int EVENT_GET_ENTITLEMENT_VALUE = 4;
+
// The ArraySet contains enabled downstream types, ex:
// {@link ConnectivityManager.TETHERING_WIFI}
// {@link ConnectivityManager.TETHERING_USB}
// {@link ConnectivityManager.TETHERING_BLUETOOTH}
- @GuardedBy("mCurrentTethers")
private final ArraySet<Integer> mCurrentTethers;
private final Context mContext;
+ private final int mPermissionChangeMessageCode;
private final MockableSystemProperties mSystemProperties;
private final SharedLog mLog;
- private final Handler mMasterHandler;
private final SparseIntArray mEntitlementCacheValue;
- @Nullable
- private TetheringConfiguration mConfig;
+ private final EntitlementHandler mHandler;
+ private @Nullable TetheringConfiguration mConfig;
+ private final StateMachine mTetherMasterSM;
+ // Key: ConnectivityManager.TETHERING_*(downstream).
+ // Value: ConnectivityManager.TETHER_ERROR_{NO_ERROR or PROVISION_FAILED}(provisioning result).
+ private final SparseIntArray mCellularPermitted;
+ private PendingIntent mProvisioningRecheckAlarm;
+ private boolean mCellularUpstreamPermitted = true;
+ private boolean mUsingCellularAsUpstream = false;
+ private boolean mNeedReRunProvisioningUi = false;
public EntitlementManager(Context ctx, StateMachine tetherMasterSM, SharedLog log,
- MockableSystemProperties systemProperties) {
+ int permissionChangeMessageCode, MockableSystemProperties systemProperties) {
+
mContext = ctx;
mLog = log.forSubComponent(TAG);
mCurrentTethers = new ArraySet<Integer>();
+ mCellularPermitted = new SparseIntArray();
mSystemProperties = systemProperties;
mEntitlementCacheValue = new SparseIntArray();
- mMasterHandler = tetherMasterSM.getHandler();
+ mTetherMasterSM = tetherMasterSM;
+ mPermissionChangeMessageCode = permissionChangeMessageCode;
+ final Handler masterHandler = tetherMasterSM.getHandler();
+ // Create entitlement's own handler which is associated with TetherMaster thread
+ // let all entitlement processes run in the same thread.
+ mHandler = new EntitlementHandler(masterHandler.getLooper());
+ mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_PROVISIONING_ALARM),
+ null, mHandler);
}
/**
@@ -99,24 +138,118 @@
}
/**
- * Tell EntitlementManager that a given type of tethering has been enabled
- *
- * @param type Tethering type
+ * Check if cellular upstream is permitted.
*/
- public void startTethering(int type) {
- synchronized (mCurrentTethers) {
- mCurrentTethers.add(type);
+ public boolean isCellularUpstreamPermitted() {
+ return mCellularUpstreamPermitted;
+ }
+
+ /**
+ * This is called when tethering starts.
+ * Launch provisioning app if upstream is cellular.
+ *
+ * @param downstreamType tethering type from ConnectivityManager.TETHERING_{@code *}
+ * @param showProvisioningUi a boolean indicating whether to show the
+ * provisioning app UI if there is one.
+ */
+ public void startProvisioningIfNeeded(int downstreamType, boolean showProvisioningUi) {
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_START_PROVISIONING,
+ downstreamType, encodeBool(showProvisioningUi)));
+ }
+
+ private void handleStartProvisioningIfNeeded(int type, boolean showProvisioningUi) {
+ if (!isValidDownstreamType(type)) return;
+
+ if (!mCurrentTethers.contains(type)) mCurrentTethers.add(type);
+
+ if (isTetherProvisioningRequired()) {
+ // 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(type);
+ } else {
+ runSilentTetherProvisioning(type);
+ }
+ mNeedReRunProvisioningUi = false;
+ } else {
+ mNeedReRunProvisioningUi |= showProvisioningUi;
+ }
+ } else {
+ mCellularUpstreamPermitted = true;
}
}
/**
* Tell EntitlementManager that a given type of tethering has been disabled
*
- * @param type Tethering type
+ * @param type tethering type from ConnectivityManager.TETHERING_{@code *}
*/
- public void stopTethering(int type) {
- synchronized (mCurrentTethers) {
- mCurrentTethers.remove(type);
+ public void stopProvisioningIfNeeded(int type) {
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_STOP_PROVISIONING, type, 0));
+ }
+
+ private void handleStopProvisioningIfNeeded(int type) {
+ if (!isValidDownstreamType(type)) return;
+
+ mCurrentTethers.remove(type);
+ // 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);
+ }
+
+ /**
+ * Notify EntitlementManager if upstream is cellular or not.
+ *
+ * @param isCellular whether tethering upstream is cellular.
+ */
+ public void notifyUpstream(boolean isCellular) {
+ mHandler.sendMessage(mHandler.obtainMessage(
+ EVENT_UPSTREAM_CHANGED, encodeBool(isCellular), 0));
+ }
+
+ private void handleNotifyUpstream(boolean isCellular) {
+ if (DBG) {
+ Log.d(TAG, "notifyUpstream: " + isCellular
+ + ", mCellularUpstreamPermitted: " + mCellularUpstreamPermitted
+ + ", mNeedReRunProvisioningUi: " + mNeedReRunProvisioningUi);
+ }
+ mUsingCellularAsUpstream = isCellular;
+
+ if (mUsingCellularAsUpstream) {
+ handleMaybeRunProvisioning();
+ }
+ }
+
+ /** Run provisioning if needed */
+ public void maybeRunProvisioning() {
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_MAYBE_RUN_PROVISIONING));
+ }
+
+ private void handleMaybeRunProvisioning() {
+ if (mCurrentTethers.size() == 0 || !isTetherProvisioningRequired()) {
+ return;
+ }
+
+ // Whenever any entitlement value changes, all downstreams will re-evaluate whether they
+ // 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) {
+ if (mNeedReRunProvisioningUi) {
+ mNeedReRunProvisioningUi = false;
+ runUiTetherProvisioning(downstream);
+ } else {
+ runSilentTetherProvisioning(downstream);
+ }
+ }
}
}
@@ -138,23 +271,32 @@
}
/**
- * Re-check tethering provisioning for enabled downstream tether types.
+ * Re-check tethering provisioning for all enabled tether types.
* Reference ConnectivityManager.TETHERING_{@code *} for each tether type.
+ *
+ * Note: this method is only called from TetherMaster on the handler thread.
+ * If there are new callers from different threads, the logic should move to
+ * masterHandler to avoid race conditions.
*/
public void reevaluateSimCardProvisioning() {
- synchronized (mEntitlementCacheValue) {
- mEntitlementCacheValue.clear();
+ if (DBG) Log.d(TAG, "reevaluateSimCardProvisioning");
+
+ if (!mHandler.getLooper().isCurrentThread()) {
+ // Except for test, this log should not appear in normal flow.
+ mLog.log("reevaluateSimCardProvisioning() don't run in TetherMaster thread");
+ }
+ mEntitlementCacheValue.clear();
+ mCellularPermitted.clear();
+
+ // TODO: refine provisioning check to isTetherProvisioningRequired() ??
+ if (!mConfig.hasMobileHotspotProvisionApp()
+ || carrierConfigAffirmsEntitlementCheckNotRequired()) {
+ evaluateCellularPermission();
+ return;
}
- if (!mConfig.hasMobileHotspotProvisionApp()) return;
- if (carrierConfigAffirmsEntitlementCheckNotRequired()) return;
-
- final ArraySet<Integer> reevaluateType;
- synchronized (mCurrentTethers) {
- reevaluateType = new ArraySet<Integer>(mCurrentTethers);
- }
- for (Integer type : reevaluateType) {
- startProvisionIntent(type);
+ if (mUsingCellularAsUpstream) {
+ handleMaybeRunProvisioning();
}
}
@@ -189,7 +331,14 @@
return !isEntitlementCheckRequired;
}
- public void runSilentTetherProvisioningAndEnable(int type, ResultReceiver receiver) {
+ /**
+ * Run no UI tethering provisioning check.
+ * @param type tethering type from ConnectivityManager.TETHERING_{@code *}
+ */
+ protected void runSilentTetherProvisioning(int type) {
+ if (DBG) Log.d(TAG, "runSilentTetherProvisioning: " + type);
+ ResultReceiver receiver = buildProxyReceiver(type, null);
+
Intent intent = new Intent();
intent.putExtra(EXTRA_ADD_TETHER_TYPE, type);
intent.putExtra(EXTRA_RUN_PROVISION, true);
@@ -203,12 +352,20 @@
}
}
- public void runUiTetherProvisioningAndEnable(int type, ResultReceiver receiver) {
+ /**
+ * Run the UI-enabled tethering provisioning check.
+ * @param type tethering type from ConnectivityManager.TETHERING_{@code *}
+ */
+ @VisibleForTesting
+ protected void runUiTetherProvisioning(int type) {
+ ResultReceiver receiver = buildProxyReceiver(type, null);
runUiTetherProvisioning(type, receiver);
}
@VisibleForTesting
protected void runUiTetherProvisioning(int type, ResultReceiver receiver) {
+ if (DBG) Log.d(TAG, "runUiTetherProvisioning: " + type);
+
Intent intent = new Intent(Settings.ACTION_TETHER_PROVISIONING);
intent.putExtra(EXTRA_ADD_TETHER_TYPE, type);
intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver);
@@ -221,56 +378,206 @@
}
}
- // Used by the SIM card change observation code.
- // TODO: De-duplicate with above code, where possible.
- private void startProvisionIntent(int tetherType) {
- final Intent startProvIntent = new Intent();
- startProvIntent.putExtra(EXTRA_ADD_TETHER_TYPE, tetherType);
- startProvIntent.putExtra(EXTRA_RUN_PROVISION, true);
- startProvIntent.setComponent(TETHER_SERVICE);
- mContext.startServiceAsUser(startProvIntent, UserHandle.CURRENT);
- }
+ // Not needed to check if this don't run on the handler thread because it's private.
+ private void scheduleProvisioningRechecks() {
+ if (mProvisioningRecheckAlarm == null) {
+ final int period = mConfig.provisioningCheckPeriod;
+ if (period <= 0) return;
- public void scheduleProvisioningRechecks(int type) {
- Intent intent = new Intent();
- intent.putExtra(EXTRA_ADD_TETHER_TYPE, type);
- intent.putExtra(EXTRA_SET_ALARM, true);
- intent.setComponent(TETHER_SERVICE);
- final long ident = Binder.clearCallingIdentity();
- try {
- mContext.startServiceAsUser(intent, UserHandle.CURRENT);
- } finally {
- Binder.restoreCallingIdentity(ident);
+ Intent intent = new Intent(ACTION_PROVISIONING_ALARM);
+ mProvisioningRecheckAlarm = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+ AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(
+ Context.ALARM_SERVICE);
+ long periodMs = period * MS_PER_HOUR;
+ long firstAlarmTime = SystemClock.elapsedRealtime() + periodMs;
+ alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, firstAlarmTime, periodMs,
+ mProvisioningRecheckAlarm);
}
}
- public void cancelTetherProvisioningRechecks(int type) {
- Intent intent = new Intent();
- intent.putExtra(EXTRA_REM_TETHER_TYPE, type);
- intent.setComponent(TETHER_SERVICE);
- final long ident = Binder.clearCallingIdentity();
- try {
- mContext.startServiceAsUser(intent, UserHandle.CURRENT);
- } finally {
- Binder.restoreCallingIdentity(ident);
+ private void cancelTetherProvisioningRechecks() {
+ if (mProvisioningRecheckAlarm != null) {
+ AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(
+ Context.ALARM_SERVICE);
+ alarmManager.cancel(mProvisioningRecheckAlarm);
+ mProvisioningRecheckAlarm = null;
+ }
+ }
+
+ private void evaluateCellularPermission() {
+ final boolean oldPermitted = mCellularUpstreamPermitted;
+ mCellularUpstreamPermitted = (!isTetherProvisioningRequired()
+ || mCellularPermitted.indexOfValue(TETHER_ERROR_NO_ERROR) > -1);
+
+ if (DBG) {
+ Log.d(TAG, "Cellular permission change from " + oldPermitted
+ + " to " + mCellularUpstreamPermitted);
+ }
+
+ if (mCellularUpstreamPermitted != oldPermitted) {
+ mLog.log("Cellular permission change: " + mCellularUpstreamPermitted);
+ mTetherMasterSM.sendMessage(mPermissionChangeMessageCode);
+ }
+ // Only schedule periodic re-check when tether is provisioned
+ // and the result is ok.
+ if (mCellularUpstreamPermitted && mCellularPermitted.size() > 0) {
+ scheduleProvisioningRechecks();
+ } else {
+ cancelTetherProvisioningRechecks();
+ }
+ }
+
+ /**
+ * Add the mapping between provisioning result and tethering type.
+ * Notify UpstreamNetworkMonitor if Cellular permission changes.
+ *
+ * @param type tethering type from ConnectivityManager.TETHERING_{@code *}
+ * @param resultCode Provisioning result
+ */
+ protected void addDownstreamMapping(int type, int resultCode) {
+ if (DBG) {
+ Log.d(TAG, "addDownstreamMapping: " + type + ", result: " + resultCode
+ + " ,TetherTypeRequested: " + mCurrentTethers.contains(type));
+ }
+ if (!mCurrentTethers.contains(type)) return;
+
+ mCellularPermitted.put(type, resultCode);
+ evaluateCellularPermission();
+ }
+
+ /**
+ * Remove the mapping for input tethering type.
+ * @param type tethering type from ConnectivityManager.TETHERING_{@code *}
+ */
+ protected void removeDownstreamMapping(int type) {
+ if (DBG) Log.d(TAG, "removeDownstreamMapping: " + type);
+ mCellularPermitted.delete(type);
+ evaluateCellularPermission();
+ }
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (ACTION_PROVISIONING_ALARM.equals(intent.getAction())) {
+ mLog.log("Received provisioning alarm");
+ reevaluateSimCardProvisioning();
+ }
+ }
+ };
+
+ private class EntitlementHandler extends Handler {
+ EntitlementHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case EVENT_START_PROVISIONING:
+ handleStartProvisioningIfNeeded(msg.arg1, toBool(msg.arg2));
+ break;
+ case EVENT_STOP_PROVISIONING:
+ handleStopProvisioningIfNeeded(msg.arg1);
+ break;
+ case EVENT_UPSTREAM_CHANGED:
+ handleNotifyUpstream(toBool(msg.arg1));
+ break;
+ case EVENT_MAYBE_RUN_PROVISIONING:
+ handleMaybeRunProvisioning();
+ break;
+ case EVENT_GET_ENTITLEMENT_VALUE:
+ handleGetLatestTetheringEntitlementValue(msg.arg1, (ResultReceiver) msg.obj,
+ toBool(msg.arg2));
+ break;
+ default:
+ mLog.log("Unknown event: " + msg.what);
+ break;
+ }
+ }
+ }
+
+ private static boolean toBool(int encodedBoolean) {
+ return encodedBoolean != 0;
+ }
+
+ private static int encodeBool(boolean b) {
+ return b ? 1 : 0;
+ }
+
+ private static boolean isValidDownstreamType(int type) {
+ switch (type) {
+ case TETHERING_BLUETOOTH:
+ case TETHERING_USB:
+ case TETHERING_WIFI:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Dump the infromation of EntitlementManager.
+ * @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");
+ }
+ }
+ }
+
+ private static String typeString(int type) {
+ switch (type) {
+ case TETHERING_BLUETOOTH: return "TETHERING_BLUETOOTH";
+ case TETHERING_INVALID: return "TETHERING_INVALID";
+ case TETHERING_USB: return "TETHERING_USB";
+ case TETHERING_WIFI: return "TETHERING_WIFI";
+ default:
+ return String.format("TETHERING UNKNOWN TYPE (%d)", type);
+ }
+ }
+
+ private static String errorString(int value) {
+ switch (value) {
+ case TETHER_ERROR_ENTITLEMENT_UNKONWN: return "TETHER_ERROR_ENTITLEMENT_UNKONWN";
+ case TETHER_ERROR_NO_ERROR: return "TETHER_ERROR_NO_ERROR";
+ case TETHER_ERROR_PROVISION_FAILED: return "TETHER_ERROR_PROVISION_FAILED";
+ default:
+ return String.format("UNKNOWN ERROR (%d)", value);
}
}
private ResultReceiver buildProxyReceiver(int type, final ResultReceiver receiver) {
- ResultReceiver rr = new ResultReceiver(mMasterHandler) {
+ ResultReceiver rr = new ResultReceiver(mHandler) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
int updatedCacheValue = updateEntitlementCacheValue(type, resultCode);
- receiver.send(updatedCacheValue, null);
+ addDownstreamMapping(type, updatedCacheValue);
+ if (receiver != null) receiver.send(updatedCacheValue, null);
}
};
return writeToParcel(rr);
}
+ // Instances of ResultReceiver need to be public classes for remote processes to be able
+ // to load them (otherwise, ClassNotFoundException). For private classes, this method
+ // performs a trick : round-trip parceling any instance of ResultReceiver will return a
+ // vanilla instance of ResultReceiver sharing the binder token with the original receiver.
+ // The binder token has a reference to the original instance of the private class and will
+ // still call its methods, and can be sent over. However it cannot be used for anything
+ // else than sending over a Binder call.
+ // While round-trip parceling is not great, there is currently no other way of generating
+ // a vanilla instance of ResultReceiver because all its fields are private.
private ResultReceiver writeToParcel(final ResultReceiver receiver) {
- // This is necessary to avoid unmarshalling issues when sending the receiver
- // across processes.
Parcel parcel = Parcel.obtain();
receiver.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
@@ -286,34 +593,37 @@
* @param resultCode last entitlement value
* @return the last updated entitlement value
*/
- public int updateEntitlementCacheValue(int type, int resultCode) {
+ private int updateEntitlementCacheValue(int type, int resultCode) {
if (DBG) {
Log.d(TAG, "updateEntitlementCacheValue: " + type + ", result: " + resultCode);
}
- synchronized (mEntitlementCacheValue) {
- if (resultCode == TETHER_ERROR_NO_ERROR) {
- mEntitlementCacheValue.put(type, resultCode);
- return resultCode;
- } else {
- mEntitlementCacheValue.put(type, TETHER_ERROR_PROVISION_FAILED);
- return TETHER_ERROR_PROVISION_FAILED;
- }
+ if (resultCode == TETHER_ERROR_NO_ERROR) {
+ mEntitlementCacheValue.put(type, resultCode);
+ return resultCode;
+ } else {
+ mEntitlementCacheValue.put(type, TETHER_ERROR_PROVISION_FAILED);
+ return TETHER_ERROR_PROVISION_FAILED;
}
}
/** Get the last value of the tethering entitlement check. */
public void getLatestTetheringEntitlementResult(int downstream, ResultReceiver receiver,
boolean showEntitlementUi) {
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_GET_ENTITLEMENT_VALUE,
+ downstream, encodeBool(showEntitlementUi), receiver));
+
+ }
+
+ private void handleGetLatestTetheringEntitlementValue(int downstream, ResultReceiver receiver,
+ boolean showEntitlementUi) {
+
if (!isTetherProvisioningRequired()) {
receiver.send(TETHER_ERROR_NO_ERROR, null);
return;
}
- final int cacheValue;
- synchronized (mEntitlementCacheValue) {
- cacheValue = mEntitlementCacheValue.get(
+ final int cacheValue = mEntitlementCacheValue.get(
downstream, TETHER_ERROR_ENTITLEMENT_UNKONWN);
- }
if (cacheValue == TETHER_ERROR_NO_ERROR || !showEntitlementUi) {
receiver.send(cacheValue, null);
} else {
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
index 935b795..8427b6e 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java
@@ -30,6 +30,7 @@
import static com.android.internal.R.array.config_tether_usb_regexs;
import static com.android.internal.R.array.config_tether_wifi_regexs;
import static com.android.internal.R.bool.config_tether_upstream_automatic;
+import static com.android.internal.R.integer.config_mobile_hotspot_provision_check_period;
import static com.android.internal.R.string.config_mobile_hotspot_provision_app_no_ui;
import android.content.ContentResolver;
@@ -94,6 +95,7 @@
public final String[] provisioningApp;
public final String provisioningAppNoUi;
+ public final int provisioningCheckPeriod;
public final int subId;
@@ -121,6 +123,9 @@
provisioningApp = getResourceStringArray(res, config_mobile_hotspot_provision_app);
provisioningAppNoUi = getProvisioningAppNoUi(res);
+ provisioningCheckPeriod = getResourceInteger(res,
+ config_mobile_hotspot_provision_check_period,
+ 0 /* No periodic re-check */);
configLog.log(toString());
}
@@ -311,6 +316,14 @@
}
}
+ private static int getResourceInteger(Resources res, int resId, int defaultValue) {
+ try {
+ return res.getInteger(resId);
+ } catch (Resources.NotFoundException e404) {
+ return defaultValue;
+ }
+ }
+
private static boolean getEnableLegacyDhcpServer(Context ctx) {
final ContentResolver cr = ctx.getContentResolver();
final int intVal = Settings.Global.getInt(cr, TETHER_ENABLE_LEGACY_DHCP_SERVER, 0);
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java
index 173d786..a0aad7c 100644
--- a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java
+++ b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java
@@ -83,8 +83,8 @@
* Get a reference to the EntitlementManager to be used by tethering.
*/
public EntitlementManager getEntitlementManager(Context ctx, StateMachine target,
- SharedLog log, MockableSystemProperties systemProperties) {
- return new EntitlementManager(ctx, target, log, systemProperties);
+ SharedLog log, int what, MockableSystemProperties systemProperties) {
+ return new EntitlementManager(ctx, target, log, what, systemProperties);
}
/**
diff --git a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
index 3ac311b..3a9e21f 100644
--- a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
@@ -16,36 +16,32 @@
package com.android.server.connectivity.tethering;
-import static android.net.ConnectivityManager.getNetworkTypeName;
-import static android.net.ConnectivityManager.TYPE_NONE;
import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
+import static android.net.ConnectivityManager.TYPE_NONE;
+import static android.net.ConnectivityManager.getNetworkTypeName;
import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import android.content.Context;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Process;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.IpPrefix;
-import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.NetworkState;
-import android.net.util.NetworkConstants;
import android.net.util.PrefixUtils;
import android.net.util.SharedLog;
+import android.os.Handler;
+import android.os.Process;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.StateMachine;
-import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
@@ -97,10 +93,13 @@
private final HashMap<Network, NetworkState> mNetworkMap = new HashMap<>();
private HashSet<IpPrefix> mLocalPrefixes;
private ConnectivityManager mCM;
+ private EntitlementManager mEntitlementMgr;
private NetworkCallback mListenAllCallback;
private NetworkCallback mDefaultNetworkCallback;
private NetworkCallback mMobileNetworkCallback;
private boolean mDunRequired;
+ // Whether the current default upstream is mobile or not.
+ private boolean mIsDefaultCellularUpstream;
// The current system default network (not really used yet).
private Network mDefaultInternetNetwork;
// The current upstream network used for tethering.
@@ -113,6 +112,7 @@
mLog = log.forSubComponent(TAG);
mWhat = what;
mLocalPrefixes = new HashSet<>();
+ mIsDefaultCellularUpstream = false;
}
@VisibleForTesting
@@ -122,7 +122,15 @@
mCM = cm;
}
- public void startTrackDefaultNetwork(NetworkRequest defaultNetworkRequest) {
+ /**
+ * Tracking the system default network. This method should be called when system is ready.
+ *
+ * @param defaultNetworkRequest should be the same as ConnectivityService default request
+ * @param entitle a EntitlementManager object to communicate between EntitlementManager and
+ * UpstreamNetworkMonitor
+ */
+ public void startTrackDefaultNetwork(NetworkRequest defaultNetworkRequest,
+ EntitlementManager entitle) {
// This is not really a "request", just a way of tracking the system default network.
// It's guaranteed not to actually bring up any networks because it's the same request
// as the ConnectivityService default request, and thus shares fate with it. We can't
@@ -133,6 +141,9 @@
mDefaultNetworkCallback = new UpstreamNetworkCallback(CALLBACK_DEFAULT_INTERNET);
cm().requestNetwork(trackDefaultRequest, mDefaultNetworkCallback, mHandler);
}
+ if (mEntitlementMgr == null) {
+ mEntitlementMgr = entitle;
+ }
}
public void startObserveAllNetworks() {
@@ -168,11 +179,15 @@
}
public void registerMobileNetworkRequest() {
+ if (!isCellularUpstreamPermitted()) {
+ mLog.i("registerMobileNetworkRequest() is not permitted");
+ releaseMobileNetworkRequest();
+ return;
+ }
if (mMobileNetworkCallback != null) {
mLog.e("registerMobileNetworkRequest() already registered");
return;
}
-
// The following use of the legacy type system cannot be removed until
// after upstream selection no longer finds networks by legacy type.
// See also http://b/34364553 .
@@ -206,29 +221,32 @@
// becomes available and useful we (a) file a request to keep it up as
// necessary and (b) change all upstream tracking state accordingly (by
// passing LinkProperties up to Tethering).
- //
- // Next TODO: return NetworkState instead of just the type.
public NetworkState selectPreferredUpstreamType(Iterable<Integer> preferredTypes) {
final TypeStatePair typeStatePair = findFirstAvailableUpstreamByType(
- mNetworkMap.values(), preferredTypes);
+ mNetworkMap.values(), preferredTypes, isCellularUpstreamPermitted());
mLog.log("preferred upstream type: " + getNetworkTypeName(typeStatePair.type));
switch (typeStatePair.type) {
case TYPE_MOBILE_DUN:
case TYPE_MOBILE_HIPRI:
+ // Tethering just selected mobile upstream in spite of the default network being
+ // not mobile. This can happen because of the priority list.
+ // Notify EntitlementManager to check permission for using mobile upstream.
+ if (!mIsDefaultCellularUpstream) {
+ mEntitlementMgr.maybeRunProvisioning();
+ }
// If we're on DUN, put our own grab on it.
registerMobileNetworkRequest();
break;
case TYPE_NONE:
+ // If we found NONE and mobile upstream is permitted we don't want to do this
+ // as we want any previous requests to keep trying to bring up something we can use.
+ if (!isCellularUpstreamPermitted()) releaseMobileNetworkRequest();
break;
default:
- /* If we've found an active upstream connection that's not DUN/HIPRI
- * we should stop any outstanding DUN/HIPRI requests.
- *
- * If we found NONE we don't want to do this as we want any previous
- * requests to keep trying to bring up something we can use.
- */
+ // If we've found an active upstream connection that's not DUN/HIPRI
+ // we should stop any outstanding DUN/HIPRI requests.
releaseMobileNetworkRequest();
break;
}
@@ -241,10 +259,12 @@
final NetworkState dfltState = (mDefaultInternetNetwork != null)
? mNetworkMap.get(mDefaultInternetNetwork)
: null;
- if (!mDunRequired) return dfltState;
-
if (isNetworkUsableAndNotCellular(dfltState)) return dfltState;
+ if (!isCellularUpstreamPermitted()) return null;
+
+ if (!mDunRequired) return dfltState;
+
// Find a DUN network. Note that code in Tethering causes a DUN request
// to be filed, but this might be moved into this class in future.
return findFirstDunNetwork(mNetworkMap.values());
@@ -258,6 +278,15 @@
return (Set<IpPrefix>) mLocalPrefixes.clone();
}
+ private boolean isCellularUpstreamPermitted() {
+ if (mEntitlementMgr != null) {
+ return mEntitlementMgr.isCellularUpstreamPermitted();
+ } else {
+ // This flow should only happens in testing.
+ return true;
+ }
+ }
+
private void handleAvailable(Network network) {
if (mNetworkMap.containsKey(network)) return;
@@ -388,8 +417,14 @@
public void onCapabilitiesChanged(Network network, NetworkCapabilities newNc) {
if (mCallbackType == CALLBACK_DEFAULT_INTERNET) {
mDefaultInternetNetwork = network;
+ final boolean newIsCellular = isCellular(newNc);
+ if (mIsDefaultCellularUpstream != newIsCellular) {
+ mIsDefaultCellularUpstream = newIsCellular;
+ mEntitlementMgr.notifyUpstream(newIsCellular);
+ }
return;
}
+
handleNetCap(network, newNc);
}
@@ -424,8 +459,11 @@
public void onLost(Network network) {
if (mCallbackType == CALLBACK_DEFAULT_INTERNET) {
mDefaultInternetNetwork = null;
+ mIsDefaultCellularUpstream = false;
+ mEntitlementMgr.notifyUpstream(false);
return;
}
+
handleLost(network);
// Any non-LISTEN_ALL callback will necessarily concern a network that will
// also match the LISTEN_ALL callback by construction of the LISTEN_ALL callback.
@@ -454,7 +492,8 @@
}
private static TypeStatePair findFirstAvailableUpstreamByType(
- Iterable<NetworkState> netStates, Iterable<Integer> preferredTypes) {
+ Iterable<NetworkState> netStates, Iterable<Integer> preferredTypes,
+ boolean isCellularUpstreamPermitted) {
final TypeStatePair result = new TypeStatePair();
for (int type : preferredTypes) {
@@ -466,6 +505,10 @@
ConnectivityManager.getNetworkTypeName(type));
continue;
}
+ if (!isCellularUpstreamPermitted && isCellular(nc)) {
+ continue;
+ }
+
nc.setSingleUid(Process.myUid());
for (NetworkState value : netStates) {
diff --git a/services/net/Android.bp b/services/net/Android.bp
index 67fbdc4..7ef0ac4 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -69,7 +69,7 @@
srcs: [
":framework-annotations",
"java/android/net/IpMemoryStoreClient.java",
- "java/android/net/ipmemorystore/**.java",
+ "java/android/net/ipmemorystore/**/*.java",
],
static_libs: [
"ipmemorystore-aidl-interfaces-java",
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index a95db22..6f48da3 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -4066,8 +4066,6 @@
// TODO: 1. Move this outside of ConnectivityServiceTest.
// 2. Make test to verify that Nat-T keepalive socket is created by IpSecService.
// 3. Mock ipsec service.
- // 4. Find a free port instead of a fixed port.
- final int srcPort = 12345;
final InetAddress myIPv4 = InetAddress.getByName("192.0.2.129");
final InetAddress notMyIPv4 = InetAddress.getByName("192.0.2.35");
final InetAddress myIPv6 = InetAddress.getByName("2001:db8::1");
@@ -4078,7 +4076,8 @@
final int invalidKaInterval = 9;
final IpSecManager mIpSec = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE);
- final UdpEncapsulationSocket testSocket = mIpSec.openUdpEncapsulationSocket(srcPort);
+ final UdpEncapsulationSocket testSocket = mIpSec.openUdpEncapsulationSocket();
+ final int srcPort = testSocket.getPort();
LinkProperties lp = new LinkProperties();
lp.setInterfaceName("wlan12");
@@ -4198,6 +4197,7 @@
// Check that keepalive slots start from 1 and increment. The first one gets slot 1.
mWiFiNetworkAgent.setExpectedKeepaliveSlot(1);
+ int srcPort2 = 0;
try (SocketKeepalive ka = mCm.createSocketKeepalive(
myNet, testSocket, myIPv4, dstIPv4, executor, callback)) {
ka.start(validKaInterval);
@@ -4205,7 +4205,8 @@
// The second one gets slot 2.
mWiFiNetworkAgent.setExpectedKeepaliveSlot(2);
- final UdpEncapsulationSocket testSocket2 = mIpSec.openUdpEncapsulationSocket(6789);
+ final UdpEncapsulationSocket testSocket2 = mIpSec.openUdpEncapsulationSocket();
+ srcPort2 = testSocket2.getPort();
TestSocketKeepaliveCallback callback2 = new TestSocketKeepaliveCallback(executor);
try (SocketKeepalive ka2 = mCm.createSocketKeepalive(
myNet, testSocket2, myIPv4, dstIPv4, executor, callback2)) {
@@ -4223,6 +4224,10 @@
}
}
+ // Check that there is no port leaked after all keepalives and sockets are closed.
+ assertFalse(isUdpPortInUse(srcPort));
+ assertFalse(isUdpPortInUse(srcPort2));
+
mWiFiNetworkAgent.disconnect();
waitFor(mWiFiNetworkAgent.getDisconnectedCV());
mWiFiNetworkAgent = null;
@@ -4305,7 +4310,6 @@
}
private void doTestNattSocketKeepalivesFdWithExecutor(Executor executor) throws Exception {
- final int srcPort = 12345;
final InetAddress myIPv4 = InetAddress.getByName("192.0.2.129");
final InetAddress anyIPv4 = InetAddress.getByName("0.0.0.0");
final InetAddress dstIPv4 = InetAddress.getByName("8.8.8.8");
@@ -4324,7 +4328,8 @@
// Prepare the target file descriptor, keep only one instance.
final IpSecManager mIpSec = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE);
- final UdpEncapsulationSocket testSocket = mIpSec.openUdpEncapsulationSocket(srcPort);
+ final UdpEncapsulationSocket testSocket = mIpSec.openUdpEncapsulationSocket();
+ final int srcPort = testSocket.getPort();
final ParcelFileDescriptor testPfd =
ParcelFileDescriptor.dup(testSocket.getFileDescriptor());
testSocket.close();
diff --git a/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java b/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java
index bac5098..9eab4be 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java
@@ -16,6 +16,7 @@
package com.android.server.connectivity.tethering;
+import static android.net.ConnectivityManager.TETHERING_BLUETOOTH;
import static android.net.ConnectivityManager.TETHERING_USB;
import static android.net.ConnectivityManager.TETHERING_WIFI;
import static android.net.ConnectivityManager.TETHER_ERROR_ENTITLEMENT_UNKONWN;
@@ -72,6 +73,7 @@
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";
@Mock private CarrierConfigManager mCarrierConfigManager;
@Mock private Context mContext;
@@ -108,10 +110,12 @@
public class WrappedEntitlementManager extends EntitlementManager {
public int fakeEntitlementResult = TETHER_ERROR_ENTITLEMENT_UNKONWN;
public boolean everRunUiEntitlement = false;
+ public int uiProvisionCount = 0;
+ public int silentProvisionCount = 0;
public WrappedEntitlementManager(Context ctx, StateMachine target,
- SharedLog log, MockableSystemProperties systemProperties) {
- super(ctx, target, log, systemProperties);
+ SharedLog log, int what, MockableSystemProperties systemProperties) {
+ super(ctx, target, log, what, systemProperties);
}
@Override
@@ -119,6 +123,16 @@
everRunUiEntitlement = true;
receiver.send(fakeEntitlementResult, null);
}
+
+ @Override
+ protected void runUiTetherProvisioning(int type) {
+ uiProvisionCount++;
+ }
+
+ @Override
+ protected void runSilentTetherProvisioning(int type) {
+ silentProvisionCount++;
+ }
}
@Before
@@ -141,7 +155,8 @@
mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
mMockContext = new MockContext(mContext);
mSM = new TestStateMachine();
- mEnMgr = new WrappedEntitlementManager(mMockContext, mSM, mLog, mSystemProperties);
+ mEnMgr = new WrappedEntitlementManager(mMockContext, mSM, mLog, EVENT_EM_UPDATE,
+ mSystemProperties);
mEnMgr.updateConfiguration(
new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID));
}
@@ -158,7 +173,9 @@
// Produce some acceptable looking provision app setting if requested.
when(mResources.getStringArray(R.array.config_mobile_hotspot_provision_app))
.thenReturn(PROVISIONING_APP_NAME);
- // Don't disable tethering provisioning unless requested.
+ when(mResources.getString(R.string.config_mobile_hotspot_provision_app_no_ui))
+ .thenReturn(PROVISIONING_NO_UI_APP_NAME);
+ // Don't disable tethering provisioning unless requested.
when(mSystemProperties.getBoolean(eq(EntitlementManager.DISABLE_PROVISIONING_SYSPROP_KEY),
anyBoolean())).thenReturn(false);
// Act like the CarrierConfigManager is present and ready unless told otherwise.
@@ -238,6 +255,7 @@
}
};
mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true);
+ mLooper.dispatchAll();
callbackTimeoutHelper(mCallbacklatch);
assertFalse(mEnMgr.everRunUiEntitlement);
@@ -254,6 +272,7 @@
}
};
mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, false);
+ mLooper.dispatchAll();
callbackTimeoutHelper(mCallbacklatch);
assertFalse(mEnMgr.everRunUiEntitlement);
// 3. No cache value and ui entitlement check is needed.
@@ -281,6 +300,7 @@
}
};
mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, false);
+ mLooper.dispatchAll();
callbackTimeoutHelper(mCallbacklatch);
assertFalse(mEnMgr.everRunUiEntitlement);
// 5. Cache value is TETHER_ERROR_PROVISION_FAILED and ui entitlement check is needed.
@@ -308,6 +328,7 @@
}
};
mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true);
+ mLooper.dispatchAll();
callbackTimeoutHelper(mCallbacklatch);
assertFalse(mEnMgr.everRunUiEntitlement);
// 7. Test get value for other downstream type.
@@ -320,19 +341,128 @@
}
};
mEnMgr.getLatestTetheringEntitlementResult(TETHERING_USB, receiver, false);
+ mLooper.dispatchAll();
callbackTimeoutHelper(mCallbacklatch);
assertFalse(mEnMgr.everRunUiEntitlement);
}
void callbackTimeoutHelper(final CountDownLatch latch) throws Exception {
if (!latch.await(1, TimeUnit.SECONDS)) {
- fail("Timout, fail to recieve callback");
+ fail("Timout, fail to receive callback");
}
}
+
+ @Test
+ public void verifyPermissionResult() {
+ setupForRequiredProvisioning();
+ mEnMgr.notifyUpstream(true);
+ mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog,
+ INVALID_SUBSCRIPTION_ID));
+ mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true);
+ mLooper.dispatchAll();
+ mEnMgr.addDownstreamMapping(TETHERING_WIFI, TETHER_ERROR_PROVISION_FAILED);
+ assertFalse(mEnMgr.isCellularUpstreamPermitted());
+ mEnMgr.stopProvisioningIfNeeded(TETHERING_WIFI);
+ mLooper.dispatchAll();
+ mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true);
+ mLooper.dispatchAll();
+ mEnMgr.addDownstreamMapping(TETHERING_WIFI, TETHER_ERROR_NO_ERROR);
+ assertTrue(mEnMgr.isCellularUpstreamPermitted());
+ }
+
+ @Test
+ public void verifyPermissionIfAllNotApproved() {
+ setupForRequiredProvisioning();
+ mEnMgr.notifyUpstream(true);
+ mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog,
+ INVALID_SUBSCRIPTION_ID));
+ mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true);
+ mLooper.dispatchAll();
+ mEnMgr.addDownstreamMapping(TETHERING_WIFI, TETHER_ERROR_PROVISION_FAILED);
+ assertFalse(mEnMgr.isCellularUpstreamPermitted());
+ mEnMgr.startProvisioningIfNeeded(TETHERING_USB, true);
+ mLooper.dispatchAll();
+ mEnMgr.addDownstreamMapping(TETHERING_USB, TETHER_ERROR_PROVISION_FAILED);
+ assertFalse(mEnMgr.isCellularUpstreamPermitted());
+ mEnMgr.startProvisioningIfNeeded(TETHERING_BLUETOOTH, true);
+ mLooper.dispatchAll();
+ mEnMgr.addDownstreamMapping(TETHERING_BLUETOOTH, TETHER_ERROR_PROVISION_FAILED);
+ assertFalse(mEnMgr.isCellularUpstreamPermitted());
+ }
+
+ @Test
+ public void verifyPermissionIfAnyApproved() {
+ setupForRequiredProvisioning();
+ mEnMgr.notifyUpstream(true);
+ mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog,
+ INVALID_SUBSCRIPTION_ID));
+ mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true);
+ mLooper.dispatchAll();
+ mEnMgr.addDownstreamMapping(TETHERING_WIFI, TETHER_ERROR_NO_ERROR);
+ assertTrue(mEnMgr.isCellularUpstreamPermitted());
+ mLooper.dispatchAll();
+ mEnMgr.startProvisioningIfNeeded(TETHERING_USB, true);
+ mLooper.dispatchAll();
+ mEnMgr.addDownstreamMapping(TETHERING_USB, TETHER_ERROR_PROVISION_FAILED);
+ assertTrue(mEnMgr.isCellularUpstreamPermitted());
+ mEnMgr.stopProvisioningIfNeeded(TETHERING_WIFI);
+ mLooper.dispatchAll();
+ assertFalse(mEnMgr.isCellularUpstreamPermitted());
+
+ }
+
+ @Test
+ public void testRunTetherProvisioning() {
+ setupForRequiredProvisioning();
+ mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog,
+ INVALID_SUBSCRIPTION_ID));
+ // 1. start ui provisioning, upstream is mobile
+ mEnMgr.notifyUpstream(true);
+ mLooper.dispatchAll();
+ mEnMgr.startProvisioningIfNeeded(TETHERING_USB, true);
+ mLooper.dispatchAll();
+ assertTrue(mEnMgr.uiProvisionCount == 1);
+ assertTrue(mEnMgr.silentProvisionCount == 0);
+ mEnMgr.addDownstreamMapping(TETHERING_USB, TETHER_ERROR_PROVISION_FAILED);
+ // 2. start no-ui provisioning
+ mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, false);
+ mLooper.dispatchAll();
+ assertTrue(mEnMgr.silentProvisionCount == 1);
+ assertTrue(mEnMgr.uiProvisionCount == 1);
+ mEnMgr.addDownstreamMapping(TETHERING_WIFI, TETHER_ERROR_PROVISION_FAILED);
+ // 3. tear down mobile, then start ui provisioning
+ mEnMgr.notifyUpstream(false);
+ mLooper.dispatchAll();
+ mEnMgr.startProvisioningIfNeeded(TETHERING_BLUETOOTH, true);
+ mLooper.dispatchAll();
+ assertTrue(mEnMgr.uiProvisionCount == 1);
+ assertTrue(mEnMgr.silentProvisionCount == 1);
+ // 4. switch upstream back to mobile
+ mEnMgr.notifyUpstream(true);
+ mLooper.dispatchAll();
+ assertTrue(mEnMgr.uiProvisionCount == 2);
+ assertTrue(mEnMgr.silentProvisionCount == 1);
+ mEnMgr.addDownstreamMapping(TETHERING_BLUETOOTH, TETHER_ERROR_PROVISION_FAILED);
+ // 5. tear down mobile, then switch SIM
+ mEnMgr.notifyUpstream(false);
+ mLooper.dispatchAll();
+ mEnMgr.reevaluateSimCardProvisioning();
+ assertTrue(mEnMgr.uiProvisionCount == 2);
+ assertTrue(mEnMgr.silentProvisionCount == 1);
+ // 6. switch upstream back to mobile again
+ mEnMgr.notifyUpstream(true);
+ mLooper.dispatchAll();
+ assertTrue(mEnMgr.uiProvisionCount == 2);
+ assertTrue(mEnMgr.silentProvisionCount == 4);
+ mEnMgr.addDownstreamMapping(TETHERING_USB, TETHER_ERROR_PROVISION_FAILED);
+ mEnMgr.addDownstreamMapping(TETHERING_WIFI, TETHER_ERROR_PROVISION_FAILED);
+ mEnMgr.addDownstreamMapping(TETHERING_BLUETOOTH, TETHER_ERROR_PROVISION_FAILED);
+ }
+
public class TestStateMachine extends StateMachine {
public final ArrayList<Message> messages = new ArrayList<>();
- private final State mLoggingState =
- new EntitlementManagerTest.TestStateMachine.LoggingState();
+ private final State
+ mLoggingState = new EntitlementManagerTest.TestStateMachine.LoggingState();
class LoggingState extends State {
@Override public void enter() {
diff --git a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
index 5a1f853..0d276cb 100644
--- a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
@@ -90,6 +90,7 @@
private static final NetworkRequest mDefaultRequest = new NetworkRequest.Builder().build();
@Mock private Context mContext;
+ @Mock private EntitlementManager mEntitleMgr;
@Mock private IConnectivityManager mCS;
@Mock private SharedLog mLog;
@@ -103,6 +104,7 @@
reset(mCS);
reset(mLog);
when(mLog.forSubComponent(anyString())).thenReturn(mLog);
+ when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true);
mCM = spy(new TestConnectivityManager(mContext, mCS));
mSM = new TestStateMachine();
@@ -138,7 +140,7 @@
@Test
public void testDefaultNetworkIsTracked() throws Exception {
assertTrue(mCM.hasNoCallbacks());
- mUNM.startTrackDefaultNetwork(mDefaultRequest);
+ mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr);
mUNM.startObserveAllNetworks();
assertEquals(1, mCM.trackingDefault.size());
@@ -151,7 +153,7 @@
public void testListensForAllNetworks() throws Exception {
assertTrue(mCM.listening.isEmpty());
- mUNM.startTrackDefaultNetwork(mDefaultRequest);
+ mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr);
mUNM.startObserveAllNetworks();
assertFalse(mCM.listening.isEmpty());
assertTrue(mCM.isListeningForAll());
@@ -162,7 +164,7 @@
@Test
public void testCallbacksRegistered() {
- mUNM.startTrackDefaultNetwork(mDefaultRequest);
+ mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr);
verify(mCM, times(1)).requestNetwork(
eq(mDefaultRequest), any(NetworkCallback.class), any(Handler.class));
mUNM.startObserveAllNetworks();
@@ -285,7 +287,7 @@
final Collection<Integer> preferredTypes = new ArrayList<>();
preferredTypes.add(TYPE_WIFI);
- mUNM.startTrackDefaultNetwork(mDefaultRequest);
+ mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr);
mUNM.startObserveAllNetworks();
// There are no networks, so there is nothing to select.
assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
@@ -319,6 +321,14 @@
NetworkRequest netReq = (NetworkRequest) mCM.requested.values().toArray()[0];
assertTrue(netReq.networkCapabilities.hasTransport(TRANSPORT_CELLULAR));
assertFalse(netReq.networkCapabilities.hasCapability(NET_CAPABILITY_DUN));
+ // mobile is not permitted, we should not use HIPRI.
+ when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(false);
+ assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
+ assertEquals(0, mCM.requested.size());
+ // mobile change back to permitted, HIRPI should come back
+ when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true);
+ assertSatisfiesLegacyType(TYPE_MOBILE_HIPRI,
+ mUNM.selectPreferredUpstreamType(preferredTypes));
wifiAgent.fakeConnect();
// WiFi is up, and we should prefer it over cell.
@@ -347,11 +357,19 @@
netReq = (NetworkRequest) mCM.requested.values().toArray()[0];
assertTrue(netReq.networkCapabilities.hasTransport(TRANSPORT_CELLULAR));
assertTrue(netReq.networkCapabilities.hasCapability(NET_CAPABILITY_DUN));
+ // mobile is not permitted, we should not use DUN.
+ when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(false);
+ assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes));
+ assertEquals(0, mCM.requested.size());
+ // mobile change back to permitted, DUN should come back
+ when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true);
+ assertSatisfiesLegacyType(TYPE_MOBILE_DUN,
+ mUNM.selectPreferredUpstreamType(preferredTypes));
}
@Test
public void testGetCurrentPreferredUpstream() throws Exception {
- mUNM.startTrackDefaultNetwork(mDefaultRequest);
+ mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr);
mUNM.startObserveAllNetworks();
mUNM.updateMobileRequiresDun(false);
@@ -361,37 +379,46 @@
mCM.makeDefaultNetwork(cellAgent);
assertEquals(cellAgent.networkId, mUNM.getCurrentPreferredUpstream().network);
- // [1] WiFi connects but not validated/promoted to default -> mobile selected.
+ // [1] Mobile connects but not permitted -> null selected
+ when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(false);
+ assertEquals(null, mUNM.getCurrentPreferredUpstream());
+ when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true);
+
+ // [2] WiFi connects but not validated/promoted to default -> mobile selected.
final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI);
wifiAgent.fakeConnect();
assertEquals(cellAgent.networkId, mUNM.getCurrentPreferredUpstream().network);
- // [2] WiFi validates and is promoted to the default network -> WiFi selected.
+ // [3] WiFi validates and is promoted to the default network -> WiFi selected.
mCM.makeDefaultNetwork(wifiAgent);
assertEquals(wifiAgent.networkId, mUNM.getCurrentPreferredUpstream().network);
- // [3] DUN required, no other changes -> WiFi still selected
+ // [4] DUN required, no other changes -> WiFi still selected
mUNM.updateMobileRequiresDun(true);
assertEquals(wifiAgent.networkId, mUNM.getCurrentPreferredUpstream().network);
- // [4] WiFi no longer validated, mobile becomes default, DUN required -> null selected.
+ // [5] WiFi no longer validated, mobile becomes default, DUN required -> null selected.
mCM.makeDefaultNetwork(cellAgent);
assertEquals(null, mUNM.getCurrentPreferredUpstream());
// TODO: make sure that a DUN request has been filed. This is currently
// triggered by code over in Tethering, but once that has been moved
// into UNM we should test for this here.
- // [5] DUN network arrives -> DUN selected
+ // [6] DUN network arrives -> DUN selected
final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
dunAgent.networkCapabilities.addCapability(NET_CAPABILITY_DUN);
dunAgent.networkCapabilities.removeCapability(NET_CAPABILITY_INTERNET);
dunAgent.fakeConnect();
assertEquals(dunAgent.networkId, mUNM.getCurrentPreferredUpstream().network);
+
+ // [7] Mobile is not permitted -> null selected
+ when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(false);
+ assertEquals(null, mUNM.getCurrentPreferredUpstream());
}
@Test
public void testLocalPrefixes() throws Exception {
- mUNM.startTrackDefaultNetwork(mDefaultRequest);
+ mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr);
mUNM.startObserveAllNetworks();
// [0] Test minimum set of local prefixes.
@@ -492,6 +519,26 @@
assertTrue(local.isEmpty());
}
+ @Test
+ public void testSelectMobileWhenMobileIsNotDefault() {
+ final Collection<Integer> preferredTypes = new ArrayList<>();
+ // Mobile has higher pirority than wifi.
+ preferredTypes.add(TYPE_MOBILE_HIPRI);
+ preferredTypes.add(TYPE_WIFI);
+ mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr);
+ mUNM.startObserveAllNetworks();
+ // Setup wifi and make wifi as default network.
+ final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI);
+ wifiAgent.fakeConnect();
+ mCM.makeDefaultNetwork(wifiAgent);
+ // Setup mobile network.
+ final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);
+ cellAgent.fakeConnect();
+
+ assertSatisfiesLegacyType(TYPE_MOBILE_HIPRI,
+ mUNM.selectPreferredUpstreamType(preferredTypes));
+ verify(mEntitleMgr, times(1)).maybeRunProvisioning();
+ }
private void assertSatisfiesLegacyType(int legacyType, NetworkState ns) {
if (legacyType == TYPE_NONE) {
assertTrue(ns == null);