From f303c5a881e87227f2d9b06ebec0b68b5c5be7a4 Mon Sep 17 00:00:00 2001 From: markchien Date: Mon, 23 Sep 2019 20:29:54 +0800 Subject: [Tether04] Migrate EntitlementManager into module Bug: 136040414 Test: -build, flash, boot -atest TetheringTests -atest FrameworksNetTests Change-Id: Ifdfc6cd95377351c37946a146b60896f07ece59d Merged-In: Ifdfc6cd95377351c37946a146b60896f07ece59d --- packages/Tethering/Android.bp | 11 +- .../connectivity/tethering/EntitlementManager.java | 678 ++++++++++++++++++++ packages/Tethering/tests/unit/Android.bp | 6 +- .../tethering/EntitlementManagerTest.java | 508 +++++++++++++++ services/core/Android.bp | 9 + .../connectivity/tethering/EntitlementManager.java | 681 --------------------- services/net/Android.bp | 2 +- .../tethering/EntitlementManagerTest.java | 508 --------------- 8 files changed, 1211 insertions(+), 1192 deletions(-) create mode 100644 packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java create mode 100644 packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java delete mode 100644 services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java delete mode 100644 tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp index ca69c187089d..2bfe287c82b3 100644 --- a/packages/Tethering/Android.bp +++ b/packages/Tethering/Android.bp @@ -21,6 +21,7 @@ java_defaults { "src/**/*.java", ":framework-tethering-shared-srcs", ":services-tethering-shared-srcs", + ":servicescore-tethering-src", ], static_libs: [ "androidx.annotation_annotation", @@ -67,9 +68,17 @@ android_app { // This group will be removed when tethering migration is done. filegroup { - name: "tethering-services-srcs", + name: "tethering-servicescore-srcs", srcs: [ + "src/com/android/server/connectivity/tethering/EntitlementManager.java", "src/com/android/server/connectivity/tethering/TetheringConfiguration.java", + ], +} + +// This group will be removed when tethering migration is done. +filegroup { + name: "tethering-servicesnet-srcs", + srcs: [ "src/android/net/dhcp/DhcpServerCallbacks.java", "src/android/net/dhcp/DhcpServingParamsParcelExt.java", "src/android/net/ip/IpServer.java", diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java b/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java new file mode 100644 index 000000000000..6b0f1de7ce85 --- /dev/null +++ b/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java @@ -0,0 +1,678 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.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_RUN_PROVISION; +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; + +import static com.android.internal.R.string.config_wifi_tether_enable; + +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; +import android.util.ArraySet; +import android.util.SparseIntArray; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.StateMachine; +import com.android.server.connectivity.MockableSystemProperties; + +import java.io.PrintWriter; + +/** + * 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"; + private static final String EXTRA_SUBID = "subId"; + + // {@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)); + 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} + private final ArraySet mCurrentTethers; + private final Context mContext; + private final int mPermissionChangeMessageCode; + private final MockableSystemProperties mSystemProperties; + private final SharedLog mLog; + private final SparseIntArray mEntitlementCacheValue; + private final EntitlementHandler mHandler; + 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; + private OnUiEntitlementFailedListener mListener; + private TetheringConfigurationFetcher mFetcher; + + public EntitlementManager(Context ctx, StateMachine tetherMasterSM, SharedLog log, + int permissionChangeMessageCode, MockableSystemProperties systemProperties) { + mContext = ctx; + mLog = log.forSubComponent(TAG); + mCurrentTethers = new ArraySet(); + mCellularPermitted = new SparseIntArray(); + mSystemProperties = systemProperties; + mEntitlementCacheValue = new SparseIntArray(); + 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); + } + + public void setOnUiEntitlementFailedListener(final OnUiEntitlementFailedListener listener) { + mListener = listener; + } + + /** Callback fired when UI entitlement failed. */ + public interface OnUiEntitlementFailedListener { + /** + * Ui entitlement check fails in |downstream|. + * + * @param downstream tethering type from ConnectivityManager.TETHERING_{@code *}. + */ + void onUiEntitlementFailed(int downstream); + } + + public void setTetheringConfigurationFetcher(final TetheringConfigurationFetcher fetcher) { + mFetcher = fetcher; + } + + /** Interface to fetch TetheringConfiguration. */ + public interface TetheringConfigurationFetcher { + /** + * Fetch current tethering configuration. This will be called to ensure whether entitlement + * check is needed. + * @return TetheringConfiguration instance. + */ + TetheringConfiguration fetchTetheringConfiguration(); + } + + /** + * Check if cellular upstream is permitted. + */ + public boolean isCellularUpstreamPermitted() { + // If provisioning is required and EntitlementManager don't know any downstream, + // cellular upstream should not be allowed. + final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration(); + if (mCurrentTethers.size() == 0 && isTetherProvisioningRequired(config)) { + return false; + } + return mCellularUpstreamPermitted; + } + + /** + * 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); + + final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration(); + if (isTetherProvisioningRequired(config)) { + // If provisioning is required and the result is not available yet, + // cellular upstream should not be allowed. + if (mCellularPermitted.size() == 0) { + mCellularUpstreamPermitted = false; + } + // If upstream is not cellular, provisioning app would not be launched + // till upstream change to cellular. + if (mUsingCellularAsUpstream) { + if (showProvisioningUi) { + runUiTetherProvisioning(type, config.subId); + } else { + runSilentTetherProvisioning(type, config.subId); + } + mNeedReRunProvisioningUi = false; + } else { + mNeedReRunProvisioningUi |= showProvisioningUi; + } + } else { + mCellularUpstreamPermitted = true; + } + } + + /** + * Tell EntitlementManager that a given type of tethering has been disabled + * + * @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 notifyUpstream(boolean isCellular) { + mHandler.sendMessage(mHandler.obtainMessage( + EVENT_UPSTREAM_CHANGED, encodeBool(isCellular), 0)); + } + + private void handleNotifyUpstream(boolean isCellular) { + if (DBG) { + mLog.i("notifyUpstream: " + isCellular + + ", mCellularUpstreamPermitted: " + mCellularUpstreamPermitted + + ", mNeedReRunProvisioningUi: " + mNeedReRunProvisioningUi); + } + mUsingCellularAsUpstream = isCellular; + + if (mUsingCellularAsUpstream) { + final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration(); + handleMaybeRunProvisioning(config); + } + } + + /** Run provisioning if needed */ + public void maybeRunProvisioning() { + mHandler.sendMessage(mHandler.obtainMessage(EVENT_MAYBE_RUN_PROVISIONING)); + } + + private void handleMaybeRunProvisioning(final TetheringConfiguration config) { + if (mCurrentTethers.size() == 0 || !isTetherProvisioningRequired(config)) { + 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, config.subId); + } else { + runSilentTetherProvisioning(downstream, config.subId); + } + } + } + } + + /** + * Check if the device requires a provisioning check in order to enable tethering. + * + * @param config an object that encapsulates the various tethering configuration elements. + * @return a boolean - {@code true} indicating tether provisioning is required by the carrier. + */ + @VisibleForTesting + protected boolean isTetherProvisioningRequired(final TetheringConfiguration config) { + if (mSystemProperties.getBoolean(DISABLE_PROVISIONING_SYSPROP_KEY, false) + || config.provisioningApp.length == 0) { + return false; + } + if (carrierConfigAffirmsEntitlementCheckNotRequired(config)) { + return false; + } + return (config.provisioningApp.length == 2); + } + + /** + * Re-check tethering provisioning for all enabled tether types. + * Reference ConnectivityManager.TETHERING_{@code *} for each tether type. + * + * @param config an object that encapsulates the various tethering configuration elements. + * 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(final TetheringConfiguration config) { + if (DBG) mLog.i("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 (!config.hasMobileHotspotProvisionApp() + || carrierConfigAffirmsEntitlementCheckNotRequired(config)) { + evaluateCellularPermission(config); + return; + } + + if (mUsingCellularAsUpstream) { + handleMaybeRunProvisioning(config); + } + } + + /** + * Get carrier configuration bundle. + * @param config an object that encapsulates the various tethering configuration elements. + * */ + public PersistableBundle getCarrierConfig(final TetheringConfiguration config) { + final CarrierConfigManager configManager = (CarrierConfigManager) mContext + .getSystemService(Context.CARRIER_CONFIG_SERVICE); + if (configManager == null) return null; + + final PersistableBundle carrierConfig = configManager.getConfigForSubId(config.subId); + + if (CarrierConfigManager.isConfigForIdentifiedCarrier(carrierConfig)) { + return carrierConfig; + } + + return null; + } + + // The logic here is aimed solely at confirming that a CarrierConfig exists + // and affirms that entitlement checks are not required. + // + // TODO: find a better way to express this, or alter the checking process + // entirely so that this is more intuitive. + private boolean carrierConfigAffirmsEntitlementCheckNotRequired( + final TetheringConfiguration config) { + // Check carrier config for entitlement checks + final PersistableBundle carrierConfig = getCarrierConfig(config); + if (carrierConfig == null) return false; + + // A CarrierConfigManager was found and it has a config. + final boolean isEntitlementCheckRequired = carrierConfig.getBoolean( + CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL); + return !isEntitlementCheckRequired; + } + + /** + * Run no UI tethering provisioning check. + * @param type tethering type from ConnectivityManager.TETHERING_{@code *} + * @param subId default data subscription ID. + */ + @VisibleForTesting + protected void runSilentTetherProvisioning(int type, int subId) { + if (DBG) mLog.i("runSilentTetherProvisioning: " + type); + // For silent provisioning, settings would stop tethering when entitlement fail. + ResultReceiver receiver = buildProxyReceiver(type, false/* notifyFail */, null); + + Intent intent = new Intent(); + intent.putExtra(EXTRA_ADD_TETHER_TYPE, type); + intent.putExtra(EXTRA_RUN_PROVISION, true); + intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver); + intent.putExtra(EXTRA_SUBID, subId); + intent.setComponent(TETHER_SERVICE); + final long ident = Binder.clearCallingIdentity(); + try { + mContext.startServiceAsUser(intent, UserHandle.CURRENT); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void runUiTetherProvisioning(int type, int subId) { + ResultReceiver receiver = buildProxyReceiver(type, true/* notifyFail */, null); + runUiTetherProvisioning(type, subId, receiver); + } + + /** + * Run the UI-enabled tethering provisioning check. + * @param type tethering type from ConnectivityManager.TETHERING_{@code *} + * @param subId default data subscription ID. + * @param receiver to receive entitlement check result. + */ + @VisibleForTesting + protected void runUiTetherProvisioning(int type, int subId, ResultReceiver receiver) { + if (DBG) mLog.i("runUiTetherProvisioning: " + type); + + Intent intent = new Intent(Settings.ACTION_TETHER_PROVISIONING); + intent.putExtra(EXTRA_ADD_TETHER_TYPE, type); + intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver); + intent.putExtra(EXTRA_SUBID, subId); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + final long ident = Binder.clearCallingIdentity(); + try { + mContext.startActivityAsUser(intent, UserHandle.CURRENT); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + // Not needed to check if this don't run on the handler thread because it's private. + private void scheduleProvisioningRechecks(final TetheringConfiguration config) { + if (mProvisioningRecheckAlarm == null) { + final int period = config.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); + } + } + + private void cancelTetherProvisioningRechecks() { + if (mProvisioningRecheckAlarm != null) { + AlarmManager alarmManager = (AlarmManager) mContext.getSystemService( + Context.ALARM_SERVICE); + alarmManager.cancel(mProvisioningRecheckAlarm); + mProvisioningRecheckAlarm = null; + } + } + + private void evaluateCellularPermission(final TetheringConfiguration config) { + final boolean oldPermitted = mCellularUpstreamPermitted; + mCellularUpstreamPermitted = (!isTetherProvisioningRequired(config) + || mCellularPermitted.indexOfValue(TETHER_ERROR_NO_ERROR) > -1); + + if (DBG) { + mLog.i("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(config); + } 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) { + mLog.i("addDownstreamMapping: " + type + ", result: " + resultCode + + " ,TetherTypeRequested: " + mCurrentTethers.contains(type)); + if (!mCurrentTethers.contains(type)) return; + + mCellularPermitted.put(type, resultCode); + final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration(); + evaluateCellularPermission(config); + } + + /** + * Remove the mapping for input tethering type. + * @param type tethering type from ConnectivityManager.TETHERING_{@code *} + */ + protected void removeDownstreamMapping(int type) { + mLog.i("removeDownstreamMapping: " + type); + mCellularPermitted.delete(type); + final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration(); + evaluateCellularPermission(config); + } + + 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"); + final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration(); + reevaluateSimCardProvisioning(config); + } + } + }; + + 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: + final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration(); + handleMaybeRunProvisioning(config); + 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, boolean notifyFail, + final ResultReceiver receiver) { + ResultReceiver rr = new ResultReceiver(mHandler) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + int updatedCacheValue = updateEntitlementCacheValue(type, resultCode); + addDownstreamMapping(type, updatedCacheValue); + if (updatedCacheValue == TETHER_ERROR_PROVISION_FAILED && notifyFail) { + mListener.onUiEntitlementFailed(type); + } + 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) { + Parcel parcel = Parcel.obtain(); + receiver.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + ResultReceiver receiverForSending = ResultReceiver.CREATOR.createFromParcel(parcel); + parcel.recycle(); + return receiverForSending; + } + + /** + * Update the last entitlement value to internal cache + * + * @param type tethering type from ConnectivityManager.TETHERING_{@code *} + * @param resultCode last entitlement value + * @return the last updated entitlement value + */ + private int updateEntitlementCacheValue(int type, int resultCode) { + if (DBG) { + mLog.i("updateEntitlementCacheValue: " + type + ", result: " + resultCode); + } + 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) { + final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration(); + if (!isTetherProvisioningRequired(config)) { + receiver.send(TETHER_ERROR_NO_ERROR, null); + return; + } + + final int cacheValue = mEntitlementCacheValue.get( + downstream, TETHER_ERROR_ENTITLEMENT_UNKONWN); + if (cacheValue == TETHER_ERROR_NO_ERROR || !showEntitlementUi) { + receiver.send(cacheValue, null); + } else { + ResultReceiver proxy = buildProxyReceiver(downstream, false/* notifyFail */, receiver); + runUiTetherProvisioning(downstream, config.subId, proxy); + } + } +} diff --git a/packages/Tethering/tests/unit/Android.bp b/packages/Tethering/tests/unit/Android.bp index da621076bb63..5564bd6ecbca 100644 --- a/packages/Tethering/tests/unit/Android.bp +++ b/packages/Tethering/tests/unit/Android.bp @@ -17,7 +17,10 @@ android_test { name: "TetheringTests", certificate: "platform", - srcs: ["src/**/*.java"], + srcs: [ + ":servicescore-tethering-src", + "src/**/*.java", + ], test_suites: ["device-tests"], static_libs: [ "androidx.test.rules", @@ -42,6 +45,7 @@ android_test { filegroup { name: "tethering-tests-src", srcs: [ + "src/com/android/server/connectivity/tethering/EntitlementManagerTest.java", "src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java", "src/android/net/dhcp/DhcpServingParamsParcelExtTest.java", "src/android/net/ip/IpServerTest.java", diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java new file mode 100644 index 000000000000..5217e26a40ef --- /dev/null +++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java @@ -0,0 +1,508 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.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; +import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR; +import static android.net.ConnectivityManager.TETHER_ERROR_PROVISION_FAILED; +import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.res.Resources; +import android.net.util.SharedLog; +import android.os.Bundle; +import android.os.Message; +import android.os.PersistableBundle; +import android.os.ResultReceiver; +import android.os.test.TestLooper; +import android.provider.Settings; +import android.telephony.CarrierConfigManager; +import android.test.mock.MockContentResolver; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.internal.R; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; +import com.android.internal.util.test.BroadcastInterceptingContext; +import com.android.internal.util.test.FakeSettingsProvider; +import com.android.server.connectivity.MockableSystemProperties; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public final class EntitlementManagerTest { + + private static final int EVENT_EM_UPDATE = 1; + private static final String[] PROVISIONING_APP_NAME = {"some", "app"}; + private static final String PROVISIONING_NO_UI_APP_NAME = "no_ui_app"; + + @Mock private CarrierConfigManager mCarrierConfigManager; + @Mock private Context mContext; + @Mock private MockableSystemProperties mSystemProperties; + @Mock private Resources mResources; + @Mock private SharedLog mLog; + @Mock private EntitlementManager.OnUiEntitlementFailedListener mEntitlementFailedListener; + + // Like so many Android system APIs, these cannot be mocked because it is marked final. + // We have to use the real versions. + private final PersistableBundle mCarrierConfig = new PersistableBundle(); + private final TestLooper mLooper = new TestLooper(); + private Context mMockContext; + private MockContentResolver mContentResolver; + + private TestStateMachine mSM; + private WrappedEntitlementManager mEnMgr; + private TetheringConfiguration mConfig; + + private class MockContext extends BroadcastInterceptingContext { + MockContext(Context base) { + super(base); + } + + @Override + public Resources getResources() { + return mResources; + } + + @Override + public ContentResolver getContentResolver() { + return mContentResolver; + } + } + + public class WrappedEntitlementManager extends EntitlementManager { + public int fakeEntitlementResult = TETHER_ERROR_ENTITLEMENT_UNKONWN; + public int uiProvisionCount = 0; + public int silentProvisionCount = 0; + + public WrappedEntitlementManager(Context ctx, StateMachine target, + SharedLog log, int what, MockableSystemProperties systemProperties) { + super(ctx, target, log, what, systemProperties); + } + + public void reset() { + fakeEntitlementResult = TETHER_ERROR_ENTITLEMENT_UNKONWN; + uiProvisionCount = 0; + silentProvisionCount = 0; + } + + @Override + protected void runUiTetherProvisioning(int type, int subId, ResultReceiver receiver) { + uiProvisionCount++; + receiver.send(fakeEntitlementResult, null); + } + + @Override + protected void runSilentTetherProvisioning(int type, int subId) { + silentProvisionCount++; + addDownstreamMapping(type, fakeEntitlementResult); + } + } + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + when(mResources.getStringArray(R.array.config_tether_dhcp_range)) + .thenReturn(new String[0]); + when(mResources.getStringArray(R.array.config_tether_usb_regexs)) + .thenReturn(new String[0]); + when(mResources.getStringArray(R.array.config_tether_wifi_regexs)) + .thenReturn(new String[0]); + when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs)) + .thenReturn(new String[0]); + when(mResources.getIntArray(R.array.config_tether_upstream_types)) + .thenReturn(new int[0]); + when(mLog.forSubComponent(anyString())).thenReturn(mLog); + + mContentResolver = new MockContentResolver(); + mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); + mMockContext = new MockContext(mContext); + mSM = new TestStateMachine(); + mEnMgr = new WrappedEntitlementManager(mMockContext, mSM, mLog, EVENT_EM_UPDATE, + mSystemProperties); + mEnMgr.setOnUiEntitlementFailedListener(mEntitlementFailedListener); + mConfig = new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID); + mEnMgr.setTetheringConfigurationFetcher(() -> { + return mConfig; + }); + } + + @After + public void tearDown() throws Exception { + if (mSM != null) { + mSM.quit(); + mSM = null; + } + } + + private void setupForRequiredProvisioning() { + // Produce some acceptable looking provision app setting if requested. + when(mResources.getStringArray(R.array.config_mobile_hotspot_provision_app)) + .thenReturn(PROVISIONING_APP_NAME); + when(mResources.getString(R.string.config_mobile_hotspot_provision_app_no_ui)) + .thenReturn(PROVISIONING_NO_UI_APP_NAME); + // 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. + when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE)) + .thenReturn(mCarrierConfigManager); + when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(mCarrierConfig); + mCarrierConfig.putBoolean(CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL, true); + mCarrierConfig.putBoolean(CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true); + mConfig = new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID); + } + + @Test + public void canRequireProvisioning() { + setupForRequiredProvisioning(); + assertTrue(mEnMgr.isTetherProvisioningRequired(mConfig)); + } + + @Test + public void toleratesCarrierConfigManagerMissing() { + setupForRequiredProvisioning(); + when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE)) + .thenReturn(null); + mConfig = new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID); + // Couldn't get the CarrierConfigManager, but still had a declared provisioning app. + // Therefore provisioning still be required. + assertTrue(mEnMgr.isTetherProvisioningRequired(mConfig)); + } + + @Test + public void toleratesCarrierConfigMissing() { + setupForRequiredProvisioning(); + when(mCarrierConfigManager.getConfig()).thenReturn(null); + mConfig = new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID); + // We still have a provisioning app configured, so still require provisioning. + assertTrue(mEnMgr.isTetherProvisioningRequired(mConfig)); + } + + @Test + public void toleratesCarrierConfigNotLoaded() { + setupForRequiredProvisioning(); + mCarrierConfig.putBoolean(CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, false); + // We still have a provisioning app configured, so still require provisioning. + assertTrue(mEnMgr.isTetherProvisioningRequired(mConfig)); + } + + @Test + public void provisioningNotRequiredWhenAppNotFound() { + setupForRequiredProvisioning(); + when(mResources.getStringArray(R.array.config_mobile_hotspot_provision_app)) + .thenReturn(null); + mConfig = new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID); + assertFalse(mEnMgr.isTetherProvisioningRequired(mConfig)); + when(mResources.getStringArray(R.array.config_mobile_hotspot_provision_app)) + .thenReturn(new String[] {"malformedApp"}); + mConfig = new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID); + assertFalse(mEnMgr.isTetherProvisioningRequired(mConfig)); + } + + @Test + public void testGetLastEntitlementCacheValue() throws Exception { + final CountDownLatch mCallbacklatch = new CountDownLatch(1); + // 1. Entitlement check is not required. + mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; + ResultReceiver receiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + assertEquals(TETHER_ERROR_NO_ERROR, resultCode); + mCallbacklatch.countDown(); + } + }; + mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true); + mLooper.dispatchAll(); + callbackTimeoutHelper(mCallbacklatch); + assertEquals(0, mEnMgr.uiProvisionCount); + mEnMgr.reset(); + + setupForRequiredProvisioning(); + // 2. No cache value and don't need to run entitlement check. + receiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + assertEquals(TETHER_ERROR_ENTITLEMENT_UNKONWN, resultCode); + mCallbacklatch.countDown(); + } + }; + mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, false); + mLooper.dispatchAll(); + callbackTimeoutHelper(mCallbacklatch); + assertEquals(0, mEnMgr.uiProvisionCount); + mEnMgr.reset(); + // 3. No cache value and ui entitlement check is needed. + mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; + receiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + assertEquals(TETHER_ERROR_PROVISION_FAILED, resultCode); + mCallbacklatch.countDown(); + } + }; + mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true); + mLooper.dispatchAll(); + callbackTimeoutHelper(mCallbacklatch); + assertEquals(1, mEnMgr.uiProvisionCount); + mEnMgr.reset(); + // 4. Cache value is TETHER_ERROR_PROVISION_FAILED and don't need to run entitlement check. + mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; + receiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + assertEquals(TETHER_ERROR_PROVISION_FAILED, resultCode); + mCallbacklatch.countDown(); + } + }; + mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, false); + mLooper.dispatchAll(); + callbackTimeoutHelper(mCallbacklatch); + assertEquals(0, mEnMgr.uiProvisionCount); + mEnMgr.reset(); + // 5. Cache value is TETHER_ERROR_PROVISION_FAILED and ui entitlement check is needed. + mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; + receiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + assertEquals(TETHER_ERROR_NO_ERROR, resultCode); + mCallbacklatch.countDown(); + } + }; + mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true); + mLooper.dispatchAll(); + callbackTimeoutHelper(mCallbacklatch); + assertEquals(1, mEnMgr.uiProvisionCount); + mEnMgr.reset(); + // 6. Cache value is TETHER_ERROR_NO_ERROR. + mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; + receiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + assertEquals(TETHER_ERROR_NO_ERROR, resultCode); + mCallbacklatch.countDown(); + } + }; + mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true); + mLooper.dispatchAll(); + callbackTimeoutHelper(mCallbacklatch); + assertEquals(0, mEnMgr.uiProvisionCount); + mEnMgr.reset(); + // 7. Test get value for other downstream type. + receiver = new ResultReceiver(null) { + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + assertEquals(TETHER_ERROR_ENTITLEMENT_UNKONWN, resultCode); + mCallbacklatch.countDown(); + } + }; + mEnMgr.getLatestTetheringEntitlementResult(TETHERING_USB, receiver, false); + mLooper.dispatchAll(); + callbackTimeoutHelper(mCallbacklatch); + assertEquals(0, mEnMgr.uiProvisionCount); + mEnMgr.reset(); + } + + void callbackTimeoutHelper(final CountDownLatch latch) throws Exception { + if (!latch.await(1, TimeUnit.SECONDS)) { + fail("Timout, fail to receive callback"); + } + } + + @Test + public void verifyPermissionResult() { + setupForRequiredProvisioning(); + mEnMgr.notifyUpstream(true); + mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; + mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true); + mLooper.dispatchAll(); + assertFalse(mEnMgr.isCellularUpstreamPermitted()); + mEnMgr.stopProvisioningIfNeeded(TETHERING_WIFI); + mLooper.dispatchAll(); + mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; + mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true); + mLooper.dispatchAll(); + assertTrue(mEnMgr.isCellularUpstreamPermitted()); + } + + @Test + public void verifyPermissionIfAllNotApproved() { + setupForRequiredProvisioning(); + mEnMgr.notifyUpstream(true); + mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; + mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true); + mLooper.dispatchAll(); + assertFalse(mEnMgr.isCellularUpstreamPermitted()); + mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; + mEnMgr.startProvisioningIfNeeded(TETHERING_USB, true); + mLooper.dispatchAll(); + assertFalse(mEnMgr.isCellularUpstreamPermitted()); + mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; + mEnMgr.startProvisioningIfNeeded(TETHERING_BLUETOOTH, true); + mLooper.dispatchAll(); + assertFalse(mEnMgr.isCellularUpstreamPermitted()); + } + + @Test + public void verifyPermissionIfAnyApproved() { + setupForRequiredProvisioning(); + mEnMgr.notifyUpstream(true); + mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; + mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true); + mLooper.dispatchAll(); + assertTrue(mEnMgr.isCellularUpstreamPermitted()); + mLooper.dispatchAll(); + mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; + mEnMgr.startProvisioningIfNeeded(TETHERING_USB, true); + mLooper.dispatchAll(); + assertTrue(mEnMgr.isCellularUpstreamPermitted()); + mEnMgr.stopProvisioningIfNeeded(TETHERING_WIFI); + mLooper.dispatchAll(); + assertFalse(mEnMgr.isCellularUpstreamPermitted()); + + } + + @Test + public void verifyPermissionWhenProvisioningNotStarted() { + assertTrue(mEnMgr.isCellularUpstreamPermitted()); + setupForRequiredProvisioning(); + assertFalse(mEnMgr.isCellularUpstreamPermitted()); + } + + @Test + public void testRunTetherProvisioning() { + setupForRequiredProvisioning(); + // 1. start ui provisioning, upstream is mobile + mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; + mEnMgr.notifyUpstream(true); + mLooper.dispatchAll(); + mEnMgr.startProvisioningIfNeeded(TETHERING_USB, true); + mLooper.dispatchAll(); + assertEquals(1, mEnMgr.uiProvisionCount); + assertEquals(0, mEnMgr.silentProvisionCount); + assertTrue(mEnMgr.isCellularUpstreamPermitted()); + mEnMgr.reset(); + // 2. start no-ui provisioning + mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; + mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, false); + mLooper.dispatchAll(); + assertEquals(0, mEnMgr.uiProvisionCount); + assertEquals(1, mEnMgr.silentProvisionCount); + assertTrue(mEnMgr.isCellularUpstreamPermitted()); + mEnMgr.reset(); + // 3. tear down mobile, then start ui provisioning + mEnMgr.notifyUpstream(false); + mLooper.dispatchAll(); + mEnMgr.startProvisioningIfNeeded(TETHERING_BLUETOOTH, true); + mLooper.dispatchAll(); + assertEquals(0, mEnMgr.uiProvisionCount); + assertEquals(0, mEnMgr.silentProvisionCount); + mEnMgr.reset(); + // 4. switch upstream back to mobile + mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; + mEnMgr.notifyUpstream(true); + mLooper.dispatchAll(); + assertEquals(1, mEnMgr.uiProvisionCount); + assertEquals(0, mEnMgr.silentProvisionCount); + assertTrue(mEnMgr.isCellularUpstreamPermitted()); + mEnMgr.reset(); + // 5. tear down mobile, then switch SIM + mEnMgr.notifyUpstream(false); + mLooper.dispatchAll(); + mEnMgr.reevaluateSimCardProvisioning(mConfig); + assertEquals(0, mEnMgr.uiProvisionCount); + assertEquals(0, mEnMgr.silentProvisionCount); + mEnMgr.reset(); + // 6. switch upstream back to mobile again + mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; + mEnMgr.notifyUpstream(true); + mLooper.dispatchAll(); + assertEquals(0, mEnMgr.uiProvisionCount); + assertEquals(3, mEnMgr.silentProvisionCount); + mEnMgr.reset(); + } + + @Test + public void testCallStopTetheringWhenUiProvisioningFail() { + setupForRequiredProvisioning(); + verify(mEntitlementFailedListener, times(0)).onUiEntitlementFailed(TETHERING_WIFI); + mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; + mEnMgr.notifyUpstream(true); + mLooper.dispatchAll(); + mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true); + mLooper.dispatchAll(); + assertEquals(1, mEnMgr.uiProvisionCount); + verify(mEntitlementFailedListener, times(1)).onUiEntitlementFailed(TETHERING_WIFI); + } + + public class TestStateMachine extends StateMachine { + public final ArrayList messages = new ArrayList<>(); + private final State + mLoggingState = new EntitlementManagerTest.TestStateMachine.LoggingState(); + + class LoggingState extends State { + @Override public void enter() { + messages.clear(); + } + + @Override public void exit() { + messages.clear(); + } + + @Override public boolean processMessage(Message msg) { + messages.add(msg); + return false; + } + } + + public TestStateMachine() { + super("EntitlementManagerTest.TestStateMachine", mLooper.getLooper()); + addState(mLoggingState); + setInitialState(mLoggingState); + super.start(); + } + } +} diff --git a/services/core/Android.bp b/services/core/Android.bp index 16432212d8e2..fcf012cd3a6b 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -20,6 +20,7 @@ java_library_static { ":vold_aidl", ":gsiservice_aidl", ":platform-compat-config", + ":tethering-servicescore-srcs", "java/com/android/server/EventLogTags.logtags", "java/com/android/server/am/EventLogTags.logtags", "java/com/android/server/policy/EventLogTags.logtags", @@ -81,3 +82,11 @@ prebuilt_etc { name: "gps_debug.conf", src: "java/com/android/server/location/gps_debug.conf", } + +// TODO: this should be removed after tethering migration done. +filegroup { + name: "servicescore-tethering-src", + srcs: [ + "java/com/android/server/connectivity/MockableSystemProperties.java", + ], +} diff --git a/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java b/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java deleted file mode 100644 index f952bcef5606..000000000000 --- a/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java +++ /dev/null @@ -1,681 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.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_RUN_PROVISION; -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; - -import static com.android.internal.R.string.config_wifi_tether_enable; - -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; -import android.util.ArraySet; -import android.util.SparseIntArray; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.util.StateMachine; -import com.android.server.connectivity.MockableSystemProperties; - -import java.io.PrintWriter; - -/** - * 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"; - private static final String EXTRA_SUBID = "subId"; - - // {@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)); - 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} - private final ArraySet mCurrentTethers; - private final Context mContext; - private final int mPermissionChangeMessageCode; - private final MockableSystemProperties mSystemProperties; - private final SharedLog mLog; - private final SparseIntArray mEntitlementCacheValue; - private final EntitlementHandler mHandler; - 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; - private OnUiEntitlementFailedListener mListener; - private TetheringConfigurationFetcher mFetcher; - - public EntitlementManager(Context ctx, StateMachine tetherMasterSM, SharedLog log, - int permissionChangeMessageCode, MockableSystemProperties systemProperties) { - - mContext = ctx; - mLog = log.forSubComponent(TAG); - mCurrentTethers = new ArraySet(); - mCellularPermitted = new SparseIntArray(); - mSystemProperties = systemProperties; - mEntitlementCacheValue = new SparseIntArray(); - 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); - } - - public void setOnUiEntitlementFailedListener(final OnUiEntitlementFailedListener listener) { - mListener = listener; - } - - /** Callback fired when UI entitlement failed. */ - public interface OnUiEntitlementFailedListener { - /** - * Ui entitlement check fails in |downstream|. - * - * @param downstream tethering type from ConnectivityManager.TETHERING_{@code *}. - */ - void onUiEntitlementFailed(int downstream); - } - - public void setTetheringConfigurationFetcher(final TetheringConfigurationFetcher fetcher) { - mFetcher = fetcher; - } - - /** Interface to fetch TetheringConfiguration. */ - public interface TetheringConfigurationFetcher { - /** - * Fetch current tethering configuration. This will be called to ensure whether entitlement - * check is needed. - * @return TetheringConfiguration instance. - */ - TetheringConfiguration fetchTetheringConfiguration(); - } - - /** - * Check if cellular upstream is permitted. - */ - public boolean isCellularUpstreamPermitted() { - // If provisioning is required and EntitlementManager don't know any downstream, - // cellular upstream should not be allowed. - final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration(); - if (mCurrentTethers.size() == 0 && isTetherProvisioningRequired(config)) { - return false; - } - return mCellularUpstreamPermitted; - } - - /** - * 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); - - final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration(); - if (isTetherProvisioningRequired(config)) { - // If provisioning is required and the result is not available yet, - // cellular upstream should not be allowed. - if (mCellularPermitted.size() == 0) { - mCellularUpstreamPermitted = false; - } - // If upstream is not cellular, provisioning app would not be launched - // till upstream change to cellular. - if (mUsingCellularAsUpstream) { - if (showProvisioningUi) { - runUiTetherProvisioning(type, config.subId); - } else { - runSilentTetherProvisioning(type, config.subId); - } - mNeedReRunProvisioningUi = false; - } else { - mNeedReRunProvisioningUi |= showProvisioningUi; - } - } else { - mCellularUpstreamPermitted = true; - } - } - - /** - * Tell EntitlementManager that a given type of tethering has been disabled - * - * @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 notifyUpstream(boolean isCellular) { - mHandler.sendMessage(mHandler.obtainMessage( - EVENT_UPSTREAM_CHANGED, encodeBool(isCellular), 0)); - } - - private void handleNotifyUpstream(boolean isCellular) { - if (DBG) { - mLog.i("notifyUpstream: " + isCellular - + ", mCellularUpstreamPermitted: " + mCellularUpstreamPermitted - + ", mNeedReRunProvisioningUi: " + mNeedReRunProvisioningUi); - } - mUsingCellularAsUpstream = isCellular; - - if (mUsingCellularAsUpstream) { - final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration(); - handleMaybeRunProvisioning(config); - } - } - - /** Run provisioning if needed */ - public void maybeRunProvisioning() { - mHandler.sendMessage(mHandler.obtainMessage(EVENT_MAYBE_RUN_PROVISIONING)); - } - - private void handleMaybeRunProvisioning(final TetheringConfiguration config) { - if (mCurrentTethers.size() == 0 || !isTetherProvisioningRequired(config)) { - 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, config.subId); - } else { - runSilentTetherProvisioning(downstream, config.subId); - } - } - } - } - - /** - * Check if the device requires a provisioning check in order to enable tethering. - * - * @param config an object that encapsulates the various tethering configuration elements. - * @return a boolean - {@code true} indicating tether provisioning is required by the carrier. - */ - @VisibleForTesting - protected boolean isTetherProvisioningRequired(final TetheringConfiguration config) { - if (mSystemProperties.getBoolean(DISABLE_PROVISIONING_SYSPROP_KEY, false) - || config.provisioningApp.length == 0) { - return false; - } - if (carrierConfigAffirmsEntitlementCheckNotRequired(config)) { - return false; - } - return (config.provisioningApp.length == 2); - } - - /** - * Re-check tethering provisioning for all enabled tether types. - * Reference ConnectivityManager.TETHERING_{@code *} for each tether type. - * - * @param config an object that encapsulates the various tethering configuration elements. - * 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(final TetheringConfiguration config) { - if (DBG) mLog.i("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 (!config.hasMobileHotspotProvisionApp() - || carrierConfigAffirmsEntitlementCheckNotRequired(config)) { - evaluateCellularPermission(config); - return; - } - - if (mUsingCellularAsUpstream) { - handleMaybeRunProvisioning(config); - } - } - - /** - * Get carrier configuration bundle. - * @param config an object that encapsulates the various tethering configuration elements. - * */ - public PersistableBundle getCarrierConfig(final TetheringConfiguration config) { - final CarrierConfigManager configManager = (CarrierConfigManager) mContext - .getSystemService(Context.CARRIER_CONFIG_SERVICE); - if (configManager == null) return null; - - final PersistableBundle carrierConfig = configManager.getConfigForSubId(config.subId); - - if (CarrierConfigManager.isConfigForIdentifiedCarrier(carrierConfig)) { - return carrierConfig; - } - - return null; - } - - // The logic here is aimed solely at confirming that a CarrierConfig exists - // and affirms that entitlement checks are not required. - // - // TODO: find a better way to express this, or alter the checking process - // entirely so that this is more intuitive. - private boolean carrierConfigAffirmsEntitlementCheckNotRequired( - final TetheringConfiguration config) { - // Check carrier config for entitlement checks - final PersistableBundle carrierConfig = getCarrierConfig(config); - if (carrierConfig == null) return false; - - // A CarrierConfigManager was found and it has a config. - final boolean isEntitlementCheckRequired = carrierConfig.getBoolean( - CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL); - return !isEntitlementCheckRequired; - } - - /** - * Run no UI tethering provisioning check. - * @param type tethering type from ConnectivityManager.TETHERING_{@code *} - * @param subId default data subscription ID. - */ - @VisibleForTesting - protected void runSilentTetherProvisioning(int type, int subId) { - if (DBG) mLog.i("runSilentTetherProvisioning: " + type); - // For silent provisioning, settings would stop tethering when entitlement fail. - ResultReceiver receiver = buildProxyReceiver(type, false/* notifyFail */, null); - - Intent intent = new Intent(); - intent.putExtra(EXTRA_ADD_TETHER_TYPE, type); - intent.putExtra(EXTRA_RUN_PROVISION, true); - intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver); - intent.putExtra(EXTRA_SUBID, subId); - intent.setComponent(TETHER_SERVICE); - final long ident = Binder.clearCallingIdentity(); - try { - mContext.startServiceAsUser(intent, UserHandle.CURRENT); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - - private void runUiTetherProvisioning(int type, int subId) { - ResultReceiver receiver = buildProxyReceiver(type, true/* notifyFail */, null); - runUiTetherProvisioning(type, subId, receiver); - } - - /** - * Run the UI-enabled tethering provisioning check. - * @param type tethering type from ConnectivityManager.TETHERING_{@code *} - * @param subId default data subscription ID. - * @param receiver to receive entitlement check result. - */ - @VisibleForTesting - protected void runUiTetherProvisioning(int type, int subId, ResultReceiver receiver) { - if (DBG) mLog.i("runUiTetherProvisioning: " + type); - - Intent intent = new Intent(Settings.ACTION_TETHER_PROVISIONING); - intent.putExtra(EXTRA_ADD_TETHER_TYPE, type); - intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver); - intent.putExtra(EXTRA_SUBID, subId); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - final long ident = Binder.clearCallingIdentity(); - try { - mContext.startActivityAsUser(intent, UserHandle.CURRENT); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - - // Not needed to check if this don't run on the handler thread because it's private. - private void scheduleProvisioningRechecks(final TetheringConfiguration config) { - if (mProvisioningRecheckAlarm == null) { - final int period = config.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); - } - } - - private void cancelTetherProvisioningRechecks() { - if (mProvisioningRecheckAlarm != null) { - AlarmManager alarmManager = (AlarmManager) mContext.getSystemService( - Context.ALARM_SERVICE); - alarmManager.cancel(mProvisioningRecheckAlarm); - mProvisioningRecheckAlarm = null; - } - } - - private void evaluateCellularPermission(final TetheringConfiguration config) { - final boolean oldPermitted = mCellularUpstreamPermitted; - mCellularUpstreamPermitted = (!isTetherProvisioningRequired(config) - || mCellularPermitted.indexOfValue(TETHER_ERROR_NO_ERROR) > -1); - - if (DBG) { - mLog.i("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(config); - } 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) { - mLog.i("addDownstreamMapping: " + type + ", result: " + resultCode - + " ,TetherTypeRequested: " + mCurrentTethers.contains(type)); - if (!mCurrentTethers.contains(type)) return; - - mCellularPermitted.put(type, resultCode); - final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration(); - evaluateCellularPermission(config); - } - - /** - * Remove the mapping for input tethering type. - * @param type tethering type from ConnectivityManager.TETHERING_{@code *} - */ - protected void removeDownstreamMapping(int type) { - mLog.i("removeDownstreamMapping: " + type); - mCellularPermitted.delete(type); - final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration(); - evaluateCellularPermission(config); - } - - 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"); - final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration(); - reevaluateSimCardProvisioning(config); - } - } - }; - - 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: - final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration(); - handleMaybeRunProvisioning(config); - 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, boolean notifyFail, - final ResultReceiver receiver) { - ResultReceiver rr = new ResultReceiver(mHandler) { - @Override - protected void onReceiveResult(int resultCode, Bundle resultData) { - int updatedCacheValue = updateEntitlementCacheValue(type, resultCode); - addDownstreamMapping(type, updatedCacheValue); - if (updatedCacheValue == TETHER_ERROR_PROVISION_FAILED && notifyFail) { - mListener.onUiEntitlementFailed(type); - } - 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) { - Parcel parcel = Parcel.obtain(); - receiver.writeToParcel(parcel, 0); - parcel.setDataPosition(0); - ResultReceiver receiverForSending = ResultReceiver.CREATOR.createFromParcel(parcel); - parcel.recycle(); - return receiverForSending; - } - - /** - * Update the last entitlement value to internal cache - * - * @param type tethering type from ConnectivityManager.TETHERING_{@code *} - * @param resultCode last entitlement value - * @return the last updated entitlement value - */ - private int updateEntitlementCacheValue(int type, int resultCode) { - if (DBG) { - mLog.i("updateEntitlementCacheValue: " + type + ", result: " + resultCode); - } - 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) { - - final TetheringConfiguration config = mFetcher.fetchTetheringConfiguration(); - if (!isTetherProvisioningRequired(config)) { - receiver.send(TETHER_ERROR_NO_ERROR, null); - return; - } - - final int cacheValue = mEntitlementCacheValue.get( - downstream, TETHER_ERROR_ENTITLEMENT_UNKONWN); - if (cacheValue == TETHER_ERROR_NO_ERROR || !showEntitlementUi) { - receiver.send(cacheValue, null); - } else { - ResultReceiver proxy = buildProxyReceiver(downstream, false/* notifyFail */, receiver); - runUiTetherProvisioning(downstream, config.subId, proxy); - } - } -} diff --git a/services/net/Android.bp b/services/net/Android.bp index 08cdbfc55cfe..80c1e135b04f 100644 --- a/services/net/Android.bp +++ b/services/net/Android.bp @@ -1,7 +1,7 @@ java_library_static { name: "services.net", srcs: [ - ":tethering-services-srcs", + ":tethering-servicesnet-srcs", "java/**/*.java", ], static_libs: [ diff --git a/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java b/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java deleted file mode 100644 index 5217e26a40ef..000000000000 --- a/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java +++ /dev/null @@ -1,508 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.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; -import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR; -import static android.net.ConnectivityManager.TETHER_ERROR_PROVISION_FAILED; -import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import static org.mockito.Matchers.anyBoolean; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.anyString; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.ContentResolver; -import android.content.Context; -import android.content.res.Resources; -import android.net.util.SharedLog; -import android.os.Bundle; -import android.os.Message; -import android.os.PersistableBundle; -import android.os.ResultReceiver; -import android.os.test.TestLooper; -import android.provider.Settings; -import android.telephony.CarrierConfigManager; -import android.test.mock.MockContentResolver; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import com.android.internal.R; -import com.android.internal.util.State; -import com.android.internal.util.StateMachine; -import com.android.internal.util.test.BroadcastInterceptingContext; -import com.android.internal.util.test.FakeSettingsProvider; -import com.android.server.connectivity.MockableSystemProperties; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.ArrayList; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -@RunWith(AndroidJUnit4.class) -@SmallTest -public final class EntitlementManagerTest { - - private static final int EVENT_EM_UPDATE = 1; - private static final String[] PROVISIONING_APP_NAME = {"some", "app"}; - private static final String PROVISIONING_NO_UI_APP_NAME = "no_ui_app"; - - @Mock private CarrierConfigManager mCarrierConfigManager; - @Mock private Context mContext; - @Mock private MockableSystemProperties mSystemProperties; - @Mock private Resources mResources; - @Mock private SharedLog mLog; - @Mock private EntitlementManager.OnUiEntitlementFailedListener mEntitlementFailedListener; - - // Like so many Android system APIs, these cannot be mocked because it is marked final. - // We have to use the real versions. - private final PersistableBundle mCarrierConfig = new PersistableBundle(); - private final TestLooper mLooper = new TestLooper(); - private Context mMockContext; - private MockContentResolver mContentResolver; - - private TestStateMachine mSM; - private WrappedEntitlementManager mEnMgr; - private TetheringConfiguration mConfig; - - private class MockContext extends BroadcastInterceptingContext { - MockContext(Context base) { - super(base); - } - - @Override - public Resources getResources() { - return mResources; - } - - @Override - public ContentResolver getContentResolver() { - return mContentResolver; - } - } - - public class WrappedEntitlementManager extends EntitlementManager { - public int fakeEntitlementResult = TETHER_ERROR_ENTITLEMENT_UNKONWN; - public int uiProvisionCount = 0; - public int silentProvisionCount = 0; - - public WrappedEntitlementManager(Context ctx, StateMachine target, - SharedLog log, int what, MockableSystemProperties systemProperties) { - super(ctx, target, log, what, systemProperties); - } - - public void reset() { - fakeEntitlementResult = TETHER_ERROR_ENTITLEMENT_UNKONWN; - uiProvisionCount = 0; - silentProvisionCount = 0; - } - - @Override - protected void runUiTetherProvisioning(int type, int subId, ResultReceiver receiver) { - uiProvisionCount++; - receiver.send(fakeEntitlementResult, null); - } - - @Override - protected void runSilentTetherProvisioning(int type, int subId) { - silentProvisionCount++; - addDownstreamMapping(type, fakeEntitlementResult); - } - } - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - - when(mResources.getStringArray(R.array.config_tether_dhcp_range)) - .thenReturn(new String[0]); - when(mResources.getStringArray(R.array.config_tether_usb_regexs)) - .thenReturn(new String[0]); - when(mResources.getStringArray(R.array.config_tether_wifi_regexs)) - .thenReturn(new String[0]); - when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs)) - .thenReturn(new String[0]); - when(mResources.getIntArray(R.array.config_tether_upstream_types)) - .thenReturn(new int[0]); - when(mLog.forSubComponent(anyString())).thenReturn(mLog); - - mContentResolver = new MockContentResolver(); - mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); - mMockContext = new MockContext(mContext); - mSM = new TestStateMachine(); - mEnMgr = new WrappedEntitlementManager(mMockContext, mSM, mLog, EVENT_EM_UPDATE, - mSystemProperties); - mEnMgr.setOnUiEntitlementFailedListener(mEntitlementFailedListener); - mConfig = new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID); - mEnMgr.setTetheringConfigurationFetcher(() -> { - return mConfig; - }); - } - - @After - public void tearDown() throws Exception { - if (mSM != null) { - mSM.quit(); - mSM = null; - } - } - - private void setupForRequiredProvisioning() { - // Produce some acceptable looking provision app setting if requested. - when(mResources.getStringArray(R.array.config_mobile_hotspot_provision_app)) - .thenReturn(PROVISIONING_APP_NAME); - when(mResources.getString(R.string.config_mobile_hotspot_provision_app_no_ui)) - .thenReturn(PROVISIONING_NO_UI_APP_NAME); - // 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. - when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE)) - .thenReturn(mCarrierConfigManager); - when(mCarrierConfigManager.getConfigForSubId(anyInt())).thenReturn(mCarrierConfig); - mCarrierConfig.putBoolean(CarrierConfigManager.KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL, true); - mCarrierConfig.putBoolean(CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true); - mConfig = new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID); - } - - @Test - public void canRequireProvisioning() { - setupForRequiredProvisioning(); - assertTrue(mEnMgr.isTetherProvisioningRequired(mConfig)); - } - - @Test - public void toleratesCarrierConfigManagerMissing() { - setupForRequiredProvisioning(); - when(mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE)) - .thenReturn(null); - mConfig = new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID); - // Couldn't get the CarrierConfigManager, but still had a declared provisioning app. - // Therefore provisioning still be required. - assertTrue(mEnMgr.isTetherProvisioningRequired(mConfig)); - } - - @Test - public void toleratesCarrierConfigMissing() { - setupForRequiredProvisioning(); - when(mCarrierConfigManager.getConfig()).thenReturn(null); - mConfig = new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID); - // We still have a provisioning app configured, so still require provisioning. - assertTrue(mEnMgr.isTetherProvisioningRequired(mConfig)); - } - - @Test - public void toleratesCarrierConfigNotLoaded() { - setupForRequiredProvisioning(); - mCarrierConfig.putBoolean(CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, false); - // We still have a provisioning app configured, so still require provisioning. - assertTrue(mEnMgr.isTetherProvisioningRequired(mConfig)); - } - - @Test - public void provisioningNotRequiredWhenAppNotFound() { - setupForRequiredProvisioning(); - when(mResources.getStringArray(R.array.config_mobile_hotspot_provision_app)) - .thenReturn(null); - mConfig = new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID); - assertFalse(mEnMgr.isTetherProvisioningRequired(mConfig)); - when(mResources.getStringArray(R.array.config_mobile_hotspot_provision_app)) - .thenReturn(new String[] {"malformedApp"}); - mConfig = new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID); - assertFalse(mEnMgr.isTetherProvisioningRequired(mConfig)); - } - - @Test - public void testGetLastEntitlementCacheValue() throws Exception { - final CountDownLatch mCallbacklatch = new CountDownLatch(1); - // 1. Entitlement check is not required. - mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; - ResultReceiver receiver = new ResultReceiver(null) { - @Override - protected void onReceiveResult(int resultCode, Bundle resultData) { - assertEquals(TETHER_ERROR_NO_ERROR, resultCode); - mCallbacklatch.countDown(); - } - }; - mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true); - mLooper.dispatchAll(); - callbackTimeoutHelper(mCallbacklatch); - assertEquals(0, mEnMgr.uiProvisionCount); - mEnMgr.reset(); - - setupForRequiredProvisioning(); - // 2. No cache value and don't need to run entitlement check. - receiver = new ResultReceiver(null) { - @Override - protected void onReceiveResult(int resultCode, Bundle resultData) { - assertEquals(TETHER_ERROR_ENTITLEMENT_UNKONWN, resultCode); - mCallbacklatch.countDown(); - } - }; - mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, false); - mLooper.dispatchAll(); - callbackTimeoutHelper(mCallbacklatch); - assertEquals(0, mEnMgr.uiProvisionCount); - mEnMgr.reset(); - // 3. No cache value and ui entitlement check is needed. - mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; - receiver = new ResultReceiver(null) { - @Override - protected void onReceiveResult(int resultCode, Bundle resultData) { - assertEquals(TETHER_ERROR_PROVISION_FAILED, resultCode); - mCallbacklatch.countDown(); - } - }; - mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true); - mLooper.dispatchAll(); - callbackTimeoutHelper(mCallbacklatch); - assertEquals(1, mEnMgr.uiProvisionCount); - mEnMgr.reset(); - // 4. Cache value is TETHER_ERROR_PROVISION_FAILED and don't need to run entitlement check. - mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; - receiver = new ResultReceiver(null) { - @Override - protected void onReceiveResult(int resultCode, Bundle resultData) { - assertEquals(TETHER_ERROR_PROVISION_FAILED, resultCode); - mCallbacklatch.countDown(); - } - }; - mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, false); - mLooper.dispatchAll(); - callbackTimeoutHelper(mCallbacklatch); - assertEquals(0, mEnMgr.uiProvisionCount); - mEnMgr.reset(); - // 5. Cache value is TETHER_ERROR_PROVISION_FAILED and ui entitlement check is needed. - mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; - receiver = new ResultReceiver(null) { - @Override - protected void onReceiveResult(int resultCode, Bundle resultData) { - assertEquals(TETHER_ERROR_NO_ERROR, resultCode); - mCallbacklatch.countDown(); - } - }; - mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true); - mLooper.dispatchAll(); - callbackTimeoutHelper(mCallbacklatch); - assertEquals(1, mEnMgr.uiProvisionCount); - mEnMgr.reset(); - // 6. Cache value is TETHER_ERROR_NO_ERROR. - mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; - receiver = new ResultReceiver(null) { - @Override - protected void onReceiveResult(int resultCode, Bundle resultData) { - assertEquals(TETHER_ERROR_NO_ERROR, resultCode); - mCallbacklatch.countDown(); - } - }; - mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true); - mLooper.dispatchAll(); - callbackTimeoutHelper(mCallbacklatch); - assertEquals(0, mEnMgr.uiProvisionCount); - mEnMgr.reset(); - // 7. Test get value for other downstream type. - receiver = new ResultReceiver(null) { - @Override - protected void onReceiveResult(int resultCode, Bundle resultData) { - assertEquals(TETHER_ERROR_ENTITLEMENT_UNKONWN, resultCode); - mCallbacklatch.countDown(); - } - }; - mEnMgr.getLatestTetheringEntitlementResult(TETHERING_USB, receiver, false); - mLooper.dispatchAll(); - callbackTimeoutHelper(mCallbacklatch); - assertEquals(0, mEnMgr.uiProvisionCount); - mEnMgr.reset(); - } - - void callbackTimeoutHelper(final CountDownLatch latch) throws Exception { - if (!latch.await(1, TimeUnit.SECONDS)) { - fail("Timout, fail to receive callback"); - } - } - - @Test - public void verifyPermissionResult() { - setupForRequiredProvisioning(); - mEnMgr.notifyUpstream(true); - mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; - mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true); - mLooper.dispatchAll(); - assertFalse(mEnMgr.isCellularUpstreamPermitted()); - mEnMgr.stopProvisioningIfNeeded(TETHERING_WIFI); - mLooper.dispatchAll(); - mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; - mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true); - mLooper.dispatchAll(); - assertTrue(mEnMgr.isCellularUpstreamPermitted()); - } - - @Test - public void verifyPermissionIfAllNotApproved() { - setupForRequiredProvisioning(); - mEnMgr.notifyUpstream(true); - mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; - mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true); - mLooper.dispatchAll(); - assertFalse(mEnMgr.isCellularUpstreamPermitted()); - mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; - mEnMgr.startProvisioningIfNeeded(TETHERING_USB, true); - mLooper.dispatchAll(); - assertFalse(mEnMgr.isCellularUpstreamPermitted()); - mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; - mEnMgr.startProvisioningIfNeeded(TETHERING_BLUETOOTH, true); - mLooper.dispatchAll(); - assertFalse(mEnMgr.isCellularUpstreamPermitted()); - } - - @Test - public void verifyPermissionIfAnyApproved() { - setupForRequiredProvisioning(); - mEnMgr.notifyUpstream(true); - mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; - mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true); - mLooper.dispatchAll(); - assertTrue(mEnMgr.isCellularUpstreamPermitted()); - mLooper.dispatchAll(); - mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; - mEnMgr.startProvisioningIfNeeded(TETHERING_USB, true); - mLooper.dispatchAll(); - assertTrue(mEnMgr.isCellularUpstreamPermitted()); - mEnMgr.stopProvisioningIfNeeded(TETHERING_WIFI); - mLooper.dispatchAll(); - assertFalse(mEnMgr.isCellularUpstreamPermitted()); - - } - - @Test - public void verifyPermissionWhenProvisioningNotStarted() { - assertTrue(mEnMgr.isCellularUpstreamPermitted()); - setupForRequiredProvisioning(); - assertFalse(mEnMgr.isCellularUpstreamPermitted()); - } - - @Test - public void testRunTetherProvisioning() { - setupForRequiredProvisioning(); - // 1. start ui provisioning, upstream is mobile - mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; - mEnMgr.notifyUpstream(true); - mLooper.dispatchAll(); - mEnMgr.startProvisioningIfNeeded(TETHERING_USB, true); - mLooper.dispatchAll(); - assertEquals(1, mEnMgr.uiProvisionCount); - assertEquals(0, mEnMgr.silentProvisionCount); - assertTrue(mEnMgr.isCellularUpstreamPermitted()); - mEnMgr.reset(); - // 2. start no-ui provisioning - mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; - mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, false); - mLooper.dispatchAll(); - assertEquals(0, mEnMgr.uiProvisionCount); - assertEquals(1, mEnMgr.silentProvisionCount); - assertTrue(mEnMgr.isCellularUpstreamPermitted()); - mEnMgr.reset(); - // 3. tear down mobile, then start ui provisioning - mEnMgr.notifyUpstream(false); - mLooper.dispatchAll(); - mEnMgr.startProvisioningIfNeeded(TETHERING_BLUETOOTH, true); - mLooper.dispatchAll(); - assertEquals(0, mEnMgr.uiProvisionCount); - assertEquals(0, mEnMgr.silentProvisionCount); - mEnMgr.reset(); - // 4. switch upstream back to mobile - mEnMgr.fakeEntitlementResult = TETHER_ERROR_NO_ERROR; - mEnMgr.notifyUpstream(true); - mLooper.dispatchAll(); - assertEquals(1, mEnMgr.uiProvisionCount); - assertEquals(0, mEnMgr.silentProvisionCount); - assertTrue(mEnMgr.isCellularUpstreamPermitted()); - mEnMgr.reset(); - // 5. tear down mobile, then switch SIM - mEnMgr.notifyUpstream(false); - mLooper.dispatchAll(); - mEnMgr.reevaluateSimCardProvisioning(mConfig); - assertEquals(0, mEnMgr.uiProvisionCount); - assertEquals(0, mEnMgr.silentProvisionCount); - mEnMgr.reset(); - // 6. switch upstream back to mobile again - mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; - mEnMgr.notifyUpstream(true); - mLooper.dispatchAll(); - assertEquals(0, mEnMgr.uiProvisionCount); - assertEquals(3, mEnMgr.silentProvisionCount); - mEnMgr.reset(); - } - - @Test - public void testCallStopTetheringWhenUiProvisioningFail() { - setupForRequiredProvisioning(); - verify(mEntitlementFailedListener, times(0)).onUiEntitlementFailed(TETHERING_WIFI); - mEnMgr.fakeEntitlementResult = TETHER_ERROR_PROVISION_FAILED; - mEnMgr.notifyUpstream(true); - mLooper.dispatchAll(); - mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true); - mLooper.dispatchAll(); - assertEquals(1, mEnMgr.uiProvisionCount); - verify(mEntitlementFailedListener, times(1)).onUiEntitlementFailed(TETHERING_WIFI); - } - - public class TestStateMachine extends StateMachine { - public final ArrayList messages = new ArrayList<>(); - private final State - mLoggingState = new EntitlementManagerTest.TestStateMachine.LoggingState(); - - class LoggingState extends State { - @Override public void enter() { - messages.clear(); - } - - @Override public void exit() { - messages.clear(); - } - - @Override public boolean processMessage(Message msg) { - messages.add(msg); - return false; - } - } - - public TestStateMachine() { - super("EntitlementManagerTest.TestStateMachine", mLooper.getLooper()); - addState(mLoggingState); - setInitialState(mLoggingState); - super.start(); - } - } -} -- cgit v1.2.3-59-g8ed1b