diff options
8 files changed, 689 insertions, 192 deletions
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 71879194ccf6..9aedb7618142 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/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index 37fe3d094179..0b1a98ee6c55 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.Handler; 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 @@ public class Tethering extends BaseNetworkObserver { 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 class Tethering extends BaseNetworkObserver { } 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 @@ public class Tethering extends BaseNetworkObserver { ? 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 @@ public class Tethering extends BaseNetworkObserver { 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 @@ public class Tethering extends BaseNetworkObserver { case WifiManager.WIFI_AP_STATE_FAILED: default: disableWifiIpServingLocked(ifname, curState); + mEntitlementMgr.stopProvisioningIfNeeded(TETHERING_WIFI); break; } } @@ -1090,6 +1032,8 @@ public class Tethering extends BaseNetworkObserver { // 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 @@ public class Tethering extends BaseNetworkObserver { } break; } + case EVENT_UPSTREAM_PERMISSION_CHANGED: case CMD_UPSTREAM_CHANGED: updateUpstreamWanted(); if (!mUpstreamWanted) break; @@ -1694,7 +1639,8 @@ public class Tethering extends BaseNetworkObserver { } 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 @@ public class Tethering extends BaseNetworkObserver { 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 70ab38983446..5c4539741cae 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 @@ package com.android.server.connectivity.tethering; 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 android.net.ConnectivityManager.TETHER_ERROR_PROVISION_FAILED; 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.ArraySet; 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 @@ public class EntitlementManager { } /** - * Tell EntitlementManager that a given type of tethering has been enabled + * Check if cellular upstream is permitted. + */ + public boolean isCellularUpstreamPermitted() { + return mCellularUpstreamPermitted; + } + + /** + * This is called when tethering starts. + * Launch provisioning app if upstream is cellular. * - * @param type Tethering type + * @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 startTethering(int type) { - synchronized (mCurrentTethers) { - mCurrentTethers.add(type); + 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 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 stopTethering(int type) { - synchronized (mCurrentTethers) { - mCurrentTethers.remove(type); + 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 @@ public class EntitlementManager { } /** - * 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 (!mConfig.hasMobileHotspotProvisionApp()) return; - if (carrierConfigAffirmsEntitlementCheckNotRequired()) return; + 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(); - final ArraySet<Integer> reevaluateType; - synchronized (mCurrentTethers) { - reevaluateType = new ArraySet<Integer>(mCurrentTethers); + // TODO: refine provisioning check to isTetherProvisioningRequired() ?? + if (!mConfig.hasMobileHotspotProvisionApp() + || carrierConfigAffirmsEntitlementCheckNotRequired()) { + evaluateCellularPermission(); + return; } - for (Integer type : reevaluateType) { - startProvisionIntent(type); + + if (mUsingCellularAsUpstream) { + handleMaybeRunProvisioning(); } } @@ -189,7 +331,14 @@ public class EntitlementManager { 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 class EntitlementManager { } } - 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 @@ public class EntitlementManager { } } - // 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; + + 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 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); + private void cancelTetherProvisioningRechecks() { + if (mProvisioningRecheckAlarm != null) { + AlarmManager alarmManager = (AlarmManager) mContext.getSystemService( + Context.ALARM_SERVICE); + alarmManager.cancel(mProvisioningRecheckAlarm); + mProvisioningRecheckAlarm = null; } } - 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 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 @@ public class EntitlementManager { * @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 935b79546d63..8427b6eceab9 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_upstream_types; 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 class TetheringConfiguration { public final String[] provisioningApp; public final String provisioningAppNoUi; + public final int provisioningCheckPeriod; public final int subId; @@ -121,6 +123,9 @@ public class TetheringConfiguration { 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 @@ public class TetheringConfiguration { } } + 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 173d7860e4ac..a0aad7c50481 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 @@ public class TetheringDependencies { * 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 3ac311b3e13a..3a9e21f943d8 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 @@ public class UpstreamNetworkMonitor { 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 @@ public class UpstreamNetworkMonitor { mLog = log.forSubComponent(TAG); mWhat = what; mLocalPrefixes = new HashSet<>(); + mIsDefaultCellularUpstream = false; } @VisibleForTesting @@ -122,7 +122,15 @@ public class UpstreamNetworkMonitor { 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 @@ public class UpstreamNetworkMonitor { mDefaultNetworkCallback = new UpstreamNetworkCallback(CALLBACK_DEFAULT_INTERNET); cm().requestNetwork(trackDefaultRequest, mDefaultNetworkCallback, mHandler); } + if (mEntitlementMgr == null) { + mEntitlementMgr = entitle; + } } public void startObserveAllNetworks() { @@ -168,11 +179,15 @@ public class UpstreamNetworkMonitor { } 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 @@ public class UpstreamNetworkMonitor { // 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 @@ public class UpstreamNetworkMonitor { 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 @@ public class UpstreamNetworkMonitor { 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 class UpstreamNetworkMonitor { 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 class UpstreamNetworkMonitor { 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 @@ public class UpstreamNetworkMonitor { } 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 @@ public class UpstreamNetworkMonitor { ConnectivityManager.getNetworkTypeName(type)); continue; } + if (!isCellularUpstreamPermitted && isCellular(nc)) { + continue; + } + nc.setSingleUid(Process.myUid()); for (NetworkState value : netStates) { 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 bac509802258..9eab4bec2973 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 @@ 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"; @Mock private CarrierConfigManager mCarrierConfigManager; @Mock private Context mContext; @@ -108,10 +110,12 @@ public final class EntitlementManagerTest { 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 @@ public final class EntitlementManagerTest { 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 @@ public final class EntitlementManagerTest { 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 @@ public final class EntitlementManagerTest { // 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 @@ public final class EntitlementManagerTest { } }; mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true); + mLooper.dispatchAll(); callbackTimeoutHelper(mCallbacklatch); assertFalse(mEnMgr.everRunUiEntitlement); @@ -254,6 +272,7 @@ public final class EntitlementManagerTest { } }; 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 @@ public final class EntitlementManagerTest { } }; 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 @@ public final class EntitlementManagerTest { } }; 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 @@ public final class EntitlementManagerTest { } }; 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 5a1f853e75a9..0d276cbd1b85 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 @@ public class UpstreamNetworkMonitorTest { 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 @@ public class UpstreamNetworkMonitorTest { 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 @@ public class UpstreamNetworkMonitorTest { @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 class UpstreamNetworkMonitorTest { 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 @@ public class UpstreamNetworkMonitorTest { @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 @@ public class UpstreamNetworkMonitorTest { 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 @@ public class UpstreamNetworkMonitorTest { 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 @@ public class UpstreamNetworkMonitorTest { 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 @@ public class UpstreamNetworkMonitorTest { 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 @@ public class UpstreamNetworkMonitorTest { 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); |