[Tether04] Migrate EntitlementManager into module
Bug: 136040414
Test: -build, flash, boot
-atest TetheringTests
-atest FrameworksNetTests
Change-Id: Ifdfc6cd95377351c37946a146b60896f07ece59d
This commit is contained in:
@@ -21,6 +21,7 @@ java_defaults {
|
|||||||
"src/**/*.java",
|
"src/**/*.java",
|
||||||
":framework-tethering-shared-srcs",
|
":framework-tethering-shared-srcs",
|
||||||
":services-tethering-shared-srcs",
|
":services-tethering-shared-srcs",
|
||||||
|
":servicescore-tethering-src",
|
||||||
],
|
],
|
||||||
static_libs: [
|
static_libs: [
|
||||||
"androidx.annotation_annotation",
|
"androidx.annotation_annotation",
|
||||||
@@ -67,9 +68,17 @@ android_app {
|
|||||||
|
|
||||||
// This group will be removed when tethering migration is done.
|
// This group will be removed when tethering migration is done.
|
||||||
filegroup {
|
filegroup {
|
||||||
name: "tethering-services-srcs",
|
name: "tethering-servicescore-srcs",
|
||||||
srcs: [
|
srcs: [
|
||||||
|
"src/com/android/server/connectivity/tethering/EntitlementManager.java",
|
||||||
"src/com/android/server/connectivity/tethering/TetheringConfiguration.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/DhcpServerCallbacks.java",
|
||||||
"src/android/net/dhcp/DhcpServingParamsParcelExt.java",
|
"src/android/net/dhcp/DhcpServingParamsParcelExt.java",
|
||||||
"src/android/net/ip/IpServer.java",
|
"src/android/net/ip/IpServer.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<Integer> 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<Integer>();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,10 @@
|
|||||||
android_test {
|
android_test {
|
||||||
name: "TetheringTests",
|
name: "TetheringTests",
|
||||||
certificate: "platform",
|
certificate: "platform",
|
||||||
srcs: ["src/**/*.java"],
|
srcs: [
|
||||||
|
":servicescore-tethering-src",
|
||||||
|
"src/**/*.java",
|
||||||
|
],
|
||||||
test_suites: ["device-tests"],
|
test_suites: ["device-tests"],
|
||||||
static_libs: [
|
static_libs: [
|
||||||
"androidx.test.rules",
|
"androidx.test.rules",
|
||||||
@@ -42,6 +45,7 @@ android_test {
|
|||||||
filegroup {
|
filegroup {
|
||||||
name: "tethering-tests-src",
|
name: "tethering-tests-src",
|
||||||
srcs: [
|
srcs: [
|
||||||
|
"src/com/android/server/connectivity/tethering/EntitlementManagerTest.java",
|
||||||
"src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java",
|
"src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java",
|
||||||
"src/android/net/dhcp/DhcpServingParamsParcelExtTest.java",
|
"src/android/net/dhcp/DhcpServingParamsParcelExtTest.java",
|
||||||
"src/android/net/ip/IpServerTest.java",
|
"src/android/net/ip/IpServerTest.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<Message> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user