diff options
| author | 2019-03-28 03:12:28 -0700 | |
|---|---|---|
| committer | 2019-03-28 03:12:28 -0700 | |
| commit | 068c1bbf8ff8b47465763f09d2331d1e85a40644 (patch) | |
| tree | a01b5f4572e399b45678d724261f20ce968b615b | |
| parent | 7561d4667c61dbf8df50eb4f89a9da12ac3da926 (diff) | |
| parent | eea398a690496127b3626917bd800a5d57ae911c (diff) | |
Merge "Only apply entitlement check to cellular upstream"
am: eea398a690
Change-Id: Ia9c0ad34c388fb3fb70f7ac1c572d4c1a95a36b2
8 files changed, 689 insertions, 192 deletions
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 71879194ccf6..9aedb7618142 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -608,6 +608,9 @@      <protected-broadcast android:name="android.provider.action.DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL" /> +    <!-- For tether entitlement recheck--> +    <protected-broadcast +        android:name="com.android.server.connectivity.tethering.PROVISIONING_RECHECK_ALARM" />      <!-- ====================================================================== -->      <!--                          RUNTIME PERMISSIONS                           -->      <!-- ====================================================================== --> diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index 37fe3d094179..0b1a98ee6c55 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -82,7 +82,6 @@ import android.os.Handler;  import android.os.INetworkManagementService;  import android.os.Looper;  import android.os.Message; -import android.os.Parcel;  import android.os.RemoteCallbackList;  import android.os.RemoteException;  import android.os.ResultReceiver; @@ -230,8 +229,11 @@ public class Tethering extends BaseNetworkObserver {          IntentFilter filter = new IntentFilter();          filter.addAction(ACTION_CARRIER_CONFIG_CHANGED); -        mEntitlementMgr = mDeps.getEntitlementManager(mContext, mTetherMasterSM, -                mLog, systemProperties); +        // EntitlementManager will send EVENT_UPSTREAM_PERMISSION_CHANGED when cellular upstream +        // permission is changed according to entitlement check result. +        mEntitlementMgr = mDeps.getEntitlementManager(mContext, mTetherMasterSM, mLog, +                TetherMasterSM.EVENT_UPSTREAM_PERMISSION_CHANGED, systemProperties); +          mCarrierConfigChange = new VersionedBroadcastListener(                  "CarrierConfigChangeListener", mContext, mHandler, filter,                  (Intent ignored) -> { @@ -363,55 +365,28 @@ public class Tethering extends BaseNetworkObserver {      }      public void startTethering(int type, ResultReceiver receiver, boolean showProvisioningUi) { -        mEntitlementMgr.startTethering(type); -        if (!mEntitlementMgr.isTetherProvisioningRequired()) { -            enableTetheringInternal(type, true, receiver); -            return; -        } - -        final ResultReceiver proxyReceiver = getProxyReceiver(type, receiver); -        if (showProvisioningUi) { -            mEntitlementMgr.runUiTetherProvisioningAndEnable(type, proxyReceiver); -        } else { -            mEntitlementMgr.runSilentTetherProvisioningAndEnable(type, proxyReceiver); -        } +        mEntitlementMgr.startProvisioningIfNeeded(type, showProvisioningUi); +        enableTetheringInternal(type, true /* enabled */, receiver);      }      public void stopTethering(int type) { -        enableTetheringInternal(type, false, null); -        mEntitlementMgr.stopTethering(type); -        if (mEntitlementMgr.isTetherProvisioningRequired()) { -            // There are lurking bugs where the notion of "provisioning required" or -            // "tethering supported" may change without notifying tethering properly, then -            // tethering can't shutdown correctly. -            // TODO: cancel re-check all the time -            if (mDeps.isTetheringSupported()) { -                mEntitlementMgr.cancelTetherProvisioningRechecks(type); -            } -        } +        enableTetheringInternal(type, false /* disabled */, null); +        mEntitlementMgr.stopProvisioningIfNeeded(type);      }      /** -     * Enables or disables tethering for the given type. This should only be called once -     * provisioning has succeeded or is not necessary. It will also schedule provisioning rechecks -     * for the specified interface. +     * Enables or disables tethering for the given type. If provisioning is required, it will +     * schedule provisioning rechecks for the specified interface.       */      private void enableTetheringInternal(int type, boolean enable, ResultReceiver receiver) { -        boolean isProvisioningRequired = enable && mEntitlementMgr.isTetherProvisioningRequired();          int result;          switch (type) {              case TETHERING_WIFI:                  result = setWifiTethering(enable); -                if (isProvisioningRequired && result == TETHER_ERROR_NO_ERROR) { -                    mEntitlementMgr.scheduleProvisioningRechecks(type); -                }                  sendTetherResult(receiver, result);                  break;              case TETHERING_USB:                  result = setUsbTethering(enable); -                if (isProvisioningRequired && result == TETHER_ERROR_NO_ERROR) { -                    mEntitlementMgr.scheduleProvisioningRechecks(type); -                }                  sendTetherResult(receiver, result);                  break;              case TETHERING_BLUETOOTH: @@ -469,46 +444,11 @@ public class Tethering extends BaseNetworkObserver {                          ? TETHER_ERROR_NO_ERROR                          : TETHER_ERROR_MASTER_ERROR;                  sendTetherResult(receiver, result); -                if (enable && mEntitlementMgr.isTetherProvisioningRequired()) { -                    mEntitlementMgr.scheduleProvisioningRechecks(TETHERING_BLUETOOTH); -                }                  adapter.closeProfileProxy(BluetoothProfile.PAN, proxy);              }          }, BluetoothProfile.PAN);      } -    /** -     * Creates a proxy {@link ResultReceiver} which enables tethering if the provisioning result -     * is successful before firing back up to the wrapped receiver. -     * -     * @param type The type of tethering being enabled. -     * @param receiver A ResultReceiver which will be called back with an int resultCode. -     * @return The proxy receiver. -     */ -    private ResultReceiver getProxyReceiver(final int type, final ResultReceiver receiver) { -        ResultReceiver rr = new ResultReceiver(null) { -            @Override -            protected void onReceiveResult(int resultCode, Bundle resultData) { -                // If provisioning is successful, enable tethering, otherwise just send the error. -                if (resultCode == TETHER_ERROR_NO_ERROR) { -                    enableTetheringInternal(type, true, receiver); -                } else { -                    sendTetherResult(receiver, resultCode); -                } -                mEntitlementMgr.updateEntitlementCacheValue(type, resultCode); -            } -        }; - -        // The following is necessary to avoid unmarshalling issues when sending the receiver -        // across processes. -        Parcel parcel = Parcel.obtain(); -        rr.writeToParcel(parcel,0); -        parcel.setDataPosition(0); -        ResultReceiver receiverForSending = ResultReceiver.CREATOR.createFromParcel(parcel); -        parcel.recycle(); -        return receiverForSending; -    } -      public int tether(String iface) {          return tether(iface, IpServer.STATE_TETHERED);      } @@ -787,6 +727,7 @@ public class Tethering extends BaseNetworkObserver {                  if (!usbConnected && mRndisEnabled) {                      // Turn off tethering if it was enabled and there is a disconnect.                      tetherMatchingInterfaces(IpServer.STATE_AVAILABLE, TETHERING_USB); +                    mEntitlementMgr.stopProvisioningIfNeeded(TETHERING_USB);                  } else if (usbConfigured && rndisEnabled) {                      // Tether if rndis is enabled and usb is configured.                      tetherMatchingInterfaces(IpServer.STATE_TETHERED, TETHERING_USB); @@ -813,6 +754,7 @@ public class Tethering extends BaseNetworkObserver {                      case WifiManager.WIFI_AP_STATE_FAILED:                      default:                          disableWifiIpServingLocked(ifname, curState); +                        mEntitlementMgr.stopProvisioningIfNeeded(TETHERING_WIFI);                          break;                  }              } @@ -1090,6 +1032,8 @@ public class Tethering extends BaseNetworkObserver {          // we treated the error and want now to clear it          static final int CMD_CLEAR_ERROR                        = BASE_MASTER + 6;          static final int EVENT_IFACE_UPDATE_LINKPROPERTIES      = BASE_MASTER + 7; +        // Events from EntitlementManager to choose upstream again. +        static final int EVENT_UPSTREAM_PERMISSION_CHANGED      = BASE_MASTER + 8;          private final State mInitialState;          private final State mTetherModeAliveState; @@ -1504,6 +1448,7 @@ public class Tethering extends BaseNetworkObserver {                          }                          break;                      } +                    case EVENT_UPSTREAM_PERMISSION_CHANGED:                      case CMD_UPSTREAM_CHANGED:                          updateUpstreamWanted();                          if (!mUpstreamWanted) break; @@ -1694,7 +1639,8 @@ public class Tethering extends BaseNetworkObserver {      }      public void systemReady() { -        mUpstreamNetworkMonitor.startTrackDefaultNetwork(mDeps.getDefaultNetworkRequest()); +        mUpstreamNetworkMonitor.startTrackDefaultNetwork(mDeps.getDefaultNetworkRequest(), +                mEntitlementMgr);      }      /** Get the latest value of the tethering entitlement check. */ @@ -1755,6 +1701,11 @@ public class Tethering extends BaseNetworkObserver {          cfg.dump(pw);          pw.decreaseIndent(); +        pw.println("Entitlement:"); +        pw.increaseIndent(); +        mEntitlementMgr.dump(pw); +        pw.decreaseIndent(); +          synchronized (mPublicSync) {              pw.println("Tether state:");              pw.increaseIndent(); diff --git a/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java b/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java index 70ab38983446..5c4539741cae 100644 --- a/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java +++ b/services/core/java/com/android/server/connectivity/tethering/EntitlementManager.java @@ -18,9 +18,11 @@ package com.android.server.connectivity.tethering;  import static android.net.ConnectivityManager.EXTRA_ADD_TETHER_TYPE;  import static android.net.ConnectivityManager.EXTRA_PROVISION_CALLBACK; -import static android.net.ConnectivityManager.EXTRA_REM_TETHER_TYPE;  import static android.net.ConnectivityManager.EXTRA_RUN_PROVISION; -import static android.net.ConnectivityManager.EXTRA_SET_ALARM; +import static android.net.ConnectivityManager.TETHERING_BLUETOOTH; +import static android.net.ConnectivityManager.TETHERING_INVALID; +import static android.net.ConnectivityManager.TETHERING_USB; +import static android.net.ConnectivityManager.TETHERING_WIFI;  import static android.net.ConnectivityManager.TETHER_ERROR_ENTITLEMENT_UNKONWN;  import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;  import static android.net.ConnectivityManager.TETHER_ERROR_PROVISION_FAILED; @@ -28,17 +30,24 @@ import static android.net.ConnectivityManager.TETHER_ERROR_PROVISION_FAILED;  import static com.android.internal.R.string.config_wifi_tether_enable;  import android.annotation.Nullable; +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver;  import android.content.ComponentName;  import android.content.Context;  import android.content.Intent; +import android.content.IntentFilter;  import android.content.res.Resources;  import android.net.util.SharedLog;  import android.os.Binder;  import android.os.Bundle;  import android.os.Handler; +import android.os.Looper; +import android.os.Message;  import android.os.Parcel;  import android.os.PersistableBundle;  import android.os.ResultReceiver; +import android.os.SystemClock;  import android.os.UserHandle;  import android.provider.Settings;  import android.telephony.CarrierConfigManager; @@ -46,48 +55,78 @@ import android.util.ArraySet;  import android.util.Log;  import android.util.SparseIntArray; -import com.android.internal.annotations.GuardedBy;  import com.android.internal.annotations.VisibleForTesting;  import com.android.internal.util.StateMachine;  import com.android.server.connectivity.MockableSystemProperties; +import java.io.PrintWriter; +  /** - * This class encapsulates entitlement/provisioning mechanics - * provisioning check only applies to the use of the mobile network as an upstream + * Re-check tethering provisioning for enabled downstream tether types. + * Reference ConnectivityManager.TETHERING_{@code *} for each tether type.   * + * All methods of this class must be accessed from the thread of tethering + * state machine.   * @hide   */  public class EntitlementManager {      private static final String TAG = EntitlementManager.class.getSimpleName();      private static final boolean DBG = false; +    @VisibleForTesting +    protected static final String DISABLE_PROVISIONING_SYSPROP_KEY = "net.tethering.noprovisioning"; +    private static final String ACTION_PROVISIONING_ALARM = +            "com.android.server.connectivity.tethering.PROVISIONING_RECHECK_ALARM"; +      // {@link ComponentName} of the Service used to run tether provisioning.      private static final ComponentName TETHER_SERVICE = ComponentName.unflattenFromString(              Resources.getSystem().getString(config_wifi_tether_enable)); -    protected static final String DISABLE_PROVISIONING_SYSPROP_KEY = "net.tethering.noprovisioning"; +    private static final int MS_PER_HOUR = 60 * 60 * 1000; +    private static final int EVENT_START_PROVISIONING = 0; +    private static final int EVENT_STOP_PROVISIONING = 1; +    private static final int EVENT_UPSTREAM_CHANGED = 2; +    private static final int EVENT_MAYBE_RUN_PROVISIONING = 3; +    private static final int EVENT_GET_ENTITLEMENT_VALUE = 4; +      // The ArraySet contains enabled downstream types, ex:      // {@link ConnectivityManager.TETHERING_WIFI}      // {@link ConnectivityManager.TETHERING_USB}      // {@link ConnectivityManager.TETHERING_BLUETOOTH} -    @GuardedBy("mCurrentTethers")      private final ArraySet<Integer> mCurrentTethers;      private final Context mContext; +    private final int mPermissionChangeMessageCode;      private final MockableSystemProperties mSystemProperties;      private final SharedLog mLog; -    private final Handler mMasterHandler;      private final SparseIntArray mEntitlementCacheValue; -    @Nullable -    private TetheringConfiguration mConfig; +    private final EntitlementHandler mHandler; +    private @Nullable TetheringConfiguration mConfig; +    private final StateMachine mTetherMasterSM; +    // Key: ConnectivityManager.TETHERING_*(downstream). +    // Value: ConnectivityManager.TETHER_ERROR_{NO_ERROR or PROVISION_FAILED}(provisioning result). +    private final SparseIntArray mCellularPermitted; +    private PendingIntent mProvisioningRecheckAlarm; +    private boolean mCellularUpstreamPermitted = true; +    private boolean mUsingCellularAsUpstream = false; +    private boolean mNeedReRunProvisioningUi = false;      public EntitlementManager(Context ctx, StateMachine tetherMasterSM, SharedLog log, -            MockableSystemProperties systemProperties) { +            int permissionChangeMessageCode, MockableSystemProperties systemProperties) { +          mContext = ctx;          mLog = log.forSubComponent(TAG);          mCurrentTethers = new ArraySet<Integer>(); +        mCellularPermitted = new SparseIntArray();          mSystemProperties = systemProperties;          mEntitlementCacheValue = new SparseIntArray(); -        mMasterHandler = tetherMasterSM.getHandler(); +        mTetherMasterSM = tetherMasterSM; +        mPermissionChangeMessageCode = permissionChangeMessageCode; +        final Handler masterHandler = tetherMasterSM.getHandler(); +        // Create entitlement's own handler which is associated with TetherMaster thread +        // let all entitlement processes run in the same thread. +        mHandler = new EntitlementHandler(masterHandler.getLooper()); +        mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_PROVISIONING_ALARM), +                null, mHandler);      }      /** @@ -99,24 +138,118 @@ public class EntitlementManager {      }      /** -     * Tell EntitlementManager that a given type of tethering has been enabled +     * Check if cellular upstream is permitted. +     */ +    public boolean isCellularUpstreamPermitted() { +        return mCellularUpstreamPermitted; +    } + +    /** +     * This is called when tethering starts. +     * Launch provisioning app if upstream is cellular.       * -     * @param type Tethering type +     * @param downstreamType tethering type from ConnectivityManager.TETHERING_{@code *} +     * @param showProvisioningUi a boolean indicating whether to show the +     *        provisioning app UI if there is one.       */ -    public void startTethering(int type) { -        synchronized (mCurrentTethers) { -            mCurrentTethers.add(type); +    public void startProvisioningIfNeeded(int downstreamType, boolean showProvisioningUi) { +        mHandler.sendMessage(mHandler.obtainMessage(EVENT_START_PROVISIONING, +                downstreamType, encodeBool(showProvisioningUi))); +    } + +    private void handleStartProvisioningIfNeeded(int type, boolean showProvisioningUi) { +        if (!isValidDownstreamType(type)) return; + +        if (!mCurrentTethers.contains(type)) mCurrentTethers.add(type); + +        if (isTetherProvisioningRequired()) { +            // If provisioning is required and the result is not available yet, +            // cellular upstream should not be allowed. +            if (mCellularPermitted.size() == 0) { +                mCellularUpstreamPermitted = false; +            } +            // If upstream is not cellular, provisioning app would not be launched +            // till upstream change to cellular. +            if (mUsingCellularAsUpstream) { +                if (showProvisioningUi) { +                    runUiTetherProvisioning(type); +                } else { +                    runSilentTetherProvisioning(type); +                } +                mNeedReRunProvisioningUi = false; +            } else { +                mNeedReRunProvisioningUi |= showProvisioningUi; +            } +        } else { +            mCellularUpstreamPermitted = true;          }      }      /**       * Tell EntitlementManager that a given type of tethering has been disabled       * -     * @param type Tethering type +     * @param type tethering type from ConnectivityManager.TETHERING_{@code *} +     */ +    public void stopProvisioningIfNeeded(int type) { +        mHandler.sendMessage(mHandler.obtainMessage(EVENT_STOP_PROVISIONING, type, 0)); +    } + +    private void handleStopProvisioningIfNeeded(int type) { +        if (!isValidDownstreamType(type)) return; + +        mCurrentTethers.remove(type); +        // There are lurking bugs where the notion of "provisioning required" or +        // "tethering supported" may change without without tethering being notified properly. +        // Remove the mapping all the time no matter provisioning is required or not. +        removeDownstreamMapping(type); +    } + +    /** +     * Notify EntitlementManager if upstream is cellular or not. +     * +     * @param isCellular whether tethering upstream is cellular.       */ -    public void stopTethering(int type) { -        synchronized (mCurrentTethers) { -            mCurrentTethers.remove(type); +    public void notifyUpstream(boolean isCellular) { +        mHandler.sendMessage(mHandler.obtainMessage( +                EVENT_UPSTREAM_CHANGED, encodeBool(isCellular), 0)); +    } + +    private void handleNotifyUpstream(boolean isCellular) { +        if (DBG) { +            Log.d(TAG, "notifyUpstream: " + isCellular +                    + ", mCellularUpstreamPermitted: " + mCellularUpstreamPermitted +                    + ", mNeedReRunProvisioningUi: " + mNeedReRunProvisioningUi); +        } +        mUsingCellularAsUpstream = isCellular; + +        if (mUsingCellularAsUpstream) { +            handleMaybeRunProvisioning(); +        } +    } + +    /** Run provisioning if needed */ +    public void maybeRunProvisioning() { +        mHandler.sendMessage(mHandler.obtainMessage(EVENT_MAYBE_RUN_PROVISIONING)); +    } + +    private void handleMaybeRunProvisioning() { +        if (mCurrentTethers.size() == 0 || !isTetherProvisioningRequired()) { +            return; +        } + +        // Whenever any entitlement value changes, all downstreams will re-evaluate whether they +        // are allowed. Therefore even if the silent check here ends in a failure and the UI later +        // yields success, then the downstream that got a failure will re-evaluate as a result of +        // the change and get the new correct value. +        for (Integer downstream : mCurrentTethers) { +            if (mCellularPermitted.indexOfKey(downstream) < 0) { +                if (mNeedReRunProvisioningUi) { +                    mNeedReRunProvisioningUi = false; +                    runUiTetherProvisioning(downstream); +                } else { +                    runSilentTetherProvisioning(downstream); +                } +            }          }      } @@ -138,23 +271,32 @@ public class EntitlementManager {      }      /** -     * Re-check tethering provisioning for enabled downstream tether types. +     * Re-check tethering provisioning for all enabled tether types.       * Reference ConnectivityManager.TETHERING_{@code *} for each tether type. +     * +     * Note: this method is only called from TetherMaster on the handler thread. +     * If there are new callers from different threads, the logic should move to +     * masterHandler to avoid race conditions.       */      public void reevaluateSimCardProvisioning() { -        synchronized (mEntitlementCacheValue) { -            mEntitlementCacheValue.clear(); -        } +        if (DBG) Log.d(TAG, "reevaluateSimCardProvisioning"); -        if (!mConfig.hasMobileHotspotProvisionApp()) return; -        if (carrierConfigAffirmsEntitlementCheckNotRequired()) return; +        if (!mHandler.getLooper().isCurrentThread()) { +            // Except for test, this log should not appear in normal flow. +            mLog.log("reevaluateSimCardProvisioning() don't run in TetherMaster thread"); +        } +        mEntitlementCacheValue.clear(); +        mCellularPermitted.clear(); -        final ArraySet<Integer> reevaluateType; -        synchronized (mCurrentTethers) { -            reevaluateType = new ArraySet<Integer>(mCurrentTethers); +        // TODO: refine provisioning check to isTetherProvisioningRequired() ?? +        if (!mConfig.hasMobileHotspotProvisionApp() +                || carrierConfigAffirmsEntitlementCheckNotRequired()) { +            evaluateCellularPermission(); +            return;          } -        for (Integer type : reevaluateType) { -            startProvisionIntent(type); + +        if (mUsingCellularAsUpstream) { +            handleMaybeRunProvisioning();          }      } @@ -189,7 +331,14 @@ public class EntitlementManager {          return !isEntitlementCheckRequired;      } -    public void runSilentTetherProvisioningAndEnable(int type, ResultReceiver receiver) { +    /** +     * Run no UI tethering provisioning check. +     * @param type tethering type from ConnectivityManager.TETHERING_{@code *} +     */ +    protected void runSilentTetherProvisioning(int type) { +        if (DBG) Log.d(TAG, "runSilentTetherProvisioning: " + type); +        ResultReceiver receiver = buildProxyReceiver(type, null); +          Intent intent = new Intent();          intent.putExtra(EXTRA_ADD_TETHER_TYPE, type);          intent.putExtra(EXTRA_RUN_PROVISION, true); @@ -203,12 +352,20 @@ public class EntitlementManager {          }      } -    public void runUiTetherProvisioningAndEnable(int type, ResultReceiver receiver) { +    /** +     * Run the UI-enabled tethering provisioning check. +     * @param type tethering type from ConnectivityManager.TETHERING_{@code *} +     */ +    @VisibleForTesting +    protected void runUiTetherProvisioning(int type) { +        ResultReceiver receiver = buildProxyReceiver(type, null);          runUiTetherProvisioning(type, receiver);      }      @VisibleForTesting      protected void runUiTetherProvisioning(int type, ResultReceiver receiver) { +        if (DBG) Log.d(TAG, "runUiTetherProvisioning: " + type); +          Intent intent = new Intent(Settings.ACTION_TETHER_PROVISIONING);          intent.putExtra(EXTRA_ADD_TETHER_TYPE, type);          intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver); @@ -221,56 +378,206 @@ public class EntitlementManager {          }      } -    // Used by the SIM card change observation code. -    // TODO: De-duplicate with above code, where possible. -    private void startProvisionIntent(int tetherType) { -        final Intent startProvIntent = new Intent(); -        startProvIntent.putExtra(EXTRA_ADD_TETHER_TYPE, tetherType); -        startProvIntent.putExtra(EXTRA_RUN_PROVISION, true); -        startProvIntent.setComponent(TETHER_SERVICE); -        mContext.startServiceAsUser(startProvIntent, UserHandle.CURRENT); +    // Not needed to check if this don't run on the handler thread because it's private. +    private void scheduleProvisioningRechecks() { +        if (mProvisioningRecheckAlarm == null) { +            final int period = mConfig.provisioningCheckPeriod; +            if (period <= 0) return; + +            Intent intent = new Intent(ACTION_PROVISIONING_ALARM); +            mProvisioningRecheckAlarm = PendingIntent.getBroadcast(mContext, 0, intent, 0); +            AlarmManager alarmManager = (AlarmManager) mContext.getSystemService( +                    Context.ALARM_SERVICE); +            long periodMs = period * MS_PER_HOUR; +            long firstAlarmTime = SystemClock.elapsedRealtime() + periodMs; +            alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME, firstAlarmTime, periodMs, +                    mProvisioningRecheckAlarm); +        }      } -    public void scheduleProvisioningRechecks(int type) { -        Intent intent = new Intent(); -        intent.putExtra(EXTRA_ADD_TETHER_TYPE, type); -        intent.putExtra(EXTRA_SET_ALARM, true); -        intent.setComponent(TETHER_SERVICE); -        final long ident = Binder.clearCallingIdentity(); -        try { -            mContext.startServiceAsUser(intent, UserHandle.CURRENT); -        } finally { -            Binder.restoreCallingIdentity(ident); +    private void cancelTetherProvisioningRechecks() { +        if (mProvisioningRecheckAlarm != null) { +            AlarmManager alarmManager = (AlarmManager) mContext.getSystemService( +                    Context.ALARM_SERVICE); +            alarmManager.cancel(mProvisioningRecheckAlarm); +            mProvisioningRecheckAlarm = null;          }      } -    public void cancelTetherProvisioningRechecks(int type) { -        Intent intent = new Intent(); -        intent.putExtra(EXTRA_REM_TETHER_TYPE, type); -        intent.setComponent(TETHER_SERVICE); -        final long ident = Binder.clearCallingIdentity(); -        try { -            mContext.startServiceAsUser(intent, UserHandle.CURRENT); -        } finally { -            Binder.restoreCallingIdentity(ident); +    private void evaluateCellularPermission() { +        final boolean oldPermitted = mCellularUpstreamPermitted; +        mCellularUpstreamPermitted = (!isTetherProvisioningRequired() +                || mCellularPermitted.indexOfValue(TETHER_ERROR_NO_ERROR) > -1); + +        if (DBG) { +            Log.d(TAG, "Cellular permission change from " + oldPermitted +                    + " to " + mCellularUpstreamPermitted); +        } + +        if (mCellularUpstreamPermitted != oldPermitted) { +            mLog.log("Cellular permission change: " + mCellularUpstreamPermitted); +            mTetherMasterSM.sendMessage(mPermissionChangeMessageCode); +        } +        // Only schedule periodic re-check when tether is provisioned +        // and the result is ok. +        if (mCellularUpstreamPermitted && mCellularPermitted.size() > 0) { +            scheduleProvisioningRechecks(); +        } else { +            cancelTetherProvisioningRechecks(); +        } +    } + +    /** +     * Add the mapping between provisioning result and tethering type. +     * Notify UpstreamNetworkMonitor if Cellular permission changes. +     * +     * @param type tethering type from ConnectivityManager.TETHERING_{@code *} +     * @param resultCode Provisioning result +     */ +    protected void addDownstreamMapping(int type, int resultCode) { +        if (DBG) { +            Log.d(TAG, "addDownstreamMapping: " + type + ", result: " + resultCode +                    + " ,TetherTypeRequested: " + mCurrentTethers.contains(type)); +        } +        if (!mCurrentTethers.contains(type)) return; + +        mCellularPermitted.put(type, resultCode); +        evaluateCellularPermission(); +    } + +    /** +     * Remove the mapping for input tethering type. +     * @param type tethering type from ConnectivityManager.TETHERING_{@code *} +     */ +    protected void removeDownstreamMapping(int type) { +        if (DBG) Log.d(TAG, "removeDownstreamMapping: " + type); +        mCellularPermitted.delete(type); +        evaluateCellularPermission(); +    } + +    private final BroadcastReceiver mReceiver = new BroadcastReceiver() { +        @Override +        public void onReceive(Context context, Intent intent) { +            if (ACTION_PROVISIONING_ALARM.equals(intent.getAction())) { +                mLog.log("Received provisioning alarm"); +                reevaluateSimCardProvisioning(); +            } +        } +    }; + +    private class EntitlementHandler extends Handler { +        EntitlementHandler(Looper looper) { +            super(looper); +        } + +        @Override +        public void handleMessage(Message msg) { +            switch (msg.what) { +                case EVENT_START_PROVISIONING: +                    handleStartProvisioningIfNeeded(msg.arg1, toBool(msg.arg2)); +                    break; +                case EVENT_STOP_PROVISIONING: +                    handleStopProvisioningIfNeeded(msg.arg1); +                    break; +                case EVENT_UPSTREAM_CHANGED: +                    handleNotifyUpstream(toBool(msg.arg1)); +                    break; +                case EVENT_MAYBE_RUN_PROVISIONING: +                    handleMaybeRunProvisioning(); +                    break; +                case EVENT_GET_ENTITLEMENT_VALUE: +                    handleGetLatestTetheringEntitlementValue(msg.arg1, (ResultReceiver) msg.obj, +                            toBool(msg.arg2)); +                    break; +                default: +                    mLog.log("Unknown event: " + msg.what); +                    break; +            } +        } +    } + +    private static boolean toBool(int encodedBoolean) { +        return encodedBoolean != 0; +    } + +    private static int encodeBool(boolean b) { +        return b ? 1 : 0; +    } + +    private static boolean isValidDownstreamType(int type) { +        switch (type) { +            case TETHERING_BLUETOOTH: +            case TETHERING_USB: +            case TETHERING_WIFI: +                return true; +            default: +                return false; +        } +    } + +    /** +     * Dump the infromation of EntitlementManager. +     * @param pw {@link PrintWriter} is used to print formatted +     */ +    public void dump(PrintWriter pw) { +        pw.print("mCellularUpstreamPermitted: "); +        pw.println(mCellularUpstreamPermitted); +        for (Integer type : mCurrentTethers) { +            pw.print("Type: "); +            pw.print(typeString(type)); +            if (mCellularPermitted.indexOfKey(type) > -1) { +                pw.print(", Value: "); +                pw.println(errorString(mCellularPermitted.get(type))); +            } else { +                pw.println(", Value: empty"); +            } +        } +    } + +    private static String typeString(int type) { +        switch (type) { +            case TETHERING_BLUETOOTH: return "TETHERING_BLUETOOTH"; +            case TETHERING_INVALID: return "TETHERING_INVALID"; +            case TETHERING_USB: return "TETHERING_USB"; +            case TETHERING_WIFI: return "TETHERING_WIFI"; +            default: +                return String.format("TETHERING UNKNOWN TYPE (%d)", type); +        } +    } + +    private static String errorString(int value) { +        switch (value) { +            case TETHER_ERROR_ENTITLEMENT_UNKONWN: return "TETHER_ERROR_ENTITLEMENT_UNKONWN"; +            case TETHER_ERROR_NO_ERROR: return "TETHER_ERROR_NO_ERROR"; +            case TETHER_ERROR_PROVISION_FAILED: return "TETHER_ERROR_PROVISION_FAILED"; +            default: +                return String.format("UNKNOWN ERROR (%d)", value);          }      }      private ResultReceiver buildProxyReceiver(int type, final ResultReceiver receiver) { -        ResultReceiver rr = new ResultReceiver(mMasterHandler) { +        ResultReceiver rr = new ResultReceiver(mHandler) {              @Override              protected void onReceiveResult(int resultCode, Bundle resultData) {                  int updatedCacheValue = updateEntitlementCacheValue(type, resultCode); -                receiver.send(updatedCacheValue, null); +                addDownstreamMapping(type, updatedCacheValue); +                if (receiver != null) receiver.send(updatedCacheValue, null);              }          };          return writeToParcel(rr);      } +    // Instances of ResultReceiver need to be public classes for remote processes to be able +    // to load them (otherwise, ClassNotFoundException). For private classes, this method +    // performs a trick : round-trip parceling any instance of ResultReceiver will return a +    // vanilla instance of ResultReceiver sharing the binder token with the original receiver. +    // The binder token has a reference to the original instance of the private class and will +    // still call its methods, and can be sent over. However it cannot be used for anything +    // else than sending over a Binder call. +    // While round-trip parceling is not great, there is currently no other way of generating +    // a vanilla instance of ResultReceiver because all its fields are private.      private ResultReceiver writeToParcel(final ResultReceiver receiver) { -        // This is necessary to avoid unmarshalling issues when sending the receiver -        // across processes.          Parcel parcel = Parcel.obtain();          receiver.writeToParcel(parcel, 0);          parcel.setDataPosition(0); @@ -286,34 +593,37 @@ public class EntitlementManager {       * @param resultCode last entitlement value       * @return the last updated entitlement value       */ -    public int updateEntitlementCacheValue(int type, int resultCode) { +    private int updateEntitlementCacheValue(int type, int resultCode) {          if (DBG) {              Log.d(TAG, "updateEntitlementCacheValue: " + type + ", result: " + resultCode);          } -        synchronized (mEntitlementCacheValue) { -            if (resultCode == TETHER_ERROR_NO_ERROR) { -                mEntitlementCacheValue.put(type, resultCode); -                return resultCode; -            } else { -                mEntitlementCacheValue.put(type, TETHER_ERROR_PROVISION_FAILED); -                return TETHER_ERROR_PROVISION_FAILED; -            } +        if (resultCode == TETHER_ERROR_NO_ERROR) { +            mEntitlementCacheValue.put(type, resultCode); +            return resultCode; +        } else { +            mEntitlementCacheValue.put(type, TETHER_ERROR_PROVISION_FAILED); +            return TETHER_ERROR_PROVISION_FAILED;          }      }      /** Get the last value of the tethering entitlement check. */      public void getLatestTetheringEntitlementResult(int downstream, ResultReceiver receiver,              boolean showEntitlementUi) { +        mHandler.sendMessage(mHandler.obtainMessage(EVENT_GET_ENTITLEMENT_VALUE, +                downstream, encodeBool(showEntitlementUi), receiver)); + +    } + +    private void handleGetLatestTetheringEntitlementValue(int downstream, ResultReceiver receiver, +            boolean showEntitlementUi) { +          if (!isTetherProvisioningRequired()) {              receiver.send(TETHER_ERROR_NO_ERROR, null);              return;          } -        final int cacheValue; -        synchronized (mEntitlementCacheValue) { -            cacheValue = mEntitlementCacheValue.get( +        final int cacheValue = mEntitlementCacheValue.get(                  downstream, TETHER_ERROR_ENTITLEMENT_UNKONWN); -        }          if (cacheValue == TETHER_ERROR_NO_ERROR || !showEntitlementUi) {              receiver.send(cacheValue, null);          } else { diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java index 935b79546d63..8427b6eceab9 100644 --- a/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java +++ b/services/core/java/com/android/server/connectivity/tethering/TetheringConfiguration.java @@ -30,6 +30,7 @@ import static com.android.internal.R.array.config_tether_upstream_types;  import static com.android.internal.R.array.config_tether_usb_regexs;  import static com.android.internal.R.array.config_tether_wifi_regexs;  import static com.android.internal.R.bool.config_tether_upstream_automatic; +import static com.android.internal.R.integer.config_mobile_hotspot_provision_check_period;  import static com.android.internal.R.string.config_mobile_hotspot_provision_app_no_ui;  import android.content.ContentResolver; @@ -94,6 +95,7 @@ public class TetheringConfiguration {      public final String[] provisioningApp;      public final String provisioningAppNoUi; +    public final int provisioningCheckPeriod;      public final int subId; @@ -121,6 +123,9 @@ public class TetheringConfiguration {          provisioningApp = getResourceStringArray(res, config_mobile_hotspot_provision_app);          provisioningAppNoUi = getProvisioningAppNoUi(res); +        provisioningCheckPeriod = getResourceInteger(res, +                config_mobile_hotspot_provision_check_period, +                0 /* No periodic re-check */);          configLog.log(toString());      } @@ -311,6 +316,14 @@ public class TetheringConfiguration {          }      } +    private static int getResourceInteger(Resources res, int resId, int defaultValue) { +        try { +            return res.getInteger(resId); +        } catch (Resources.NotFoundException e404) { +            return defaultValue; +        } +    } +      private static boolean getEnableLegacyDhcpServer(Context ctx) {          final ContentResolver cr = ctx.getContentResolver();          final int intVal = Settings.Global.getInt(cr, TETHER_ENABLE_LEGACY_DHCP_SERVER, 0); diff --git a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java index 173d7860e4ac..a0aad7c50481 100644 --- a/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java +++ b/services/core/java/com/android/server/connectivity/tethering/TetheringDependencies.java @@ -83,8 +83,8 @@ public class TetheringDependencies {       * Get a reference to the EntitlementManager to be used by tethering.       */      public EntitlementManager getEntitlementManager(Context ctx, StateMachine target, -            SharedLog log, MockableSystemProperties systemProperties) { -        return new EntitlementManager(ctx, target, log, systemProperties); +            SharedLog log, int what, MockableSystemProperties systemProperties) { +        return new EntitlementManager(ctx, target, log, what, systemProperties);      }      /** diff --git a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java index 3ac311b3e13a..3a9e21f943d8 100644 --- a/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java +++ b/services/core/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java @@ -16,36 +16,32 @@  package com.android.server.connectivity.tethering; -import static android.net.ConnectivityManager.getNetworkTypeName; -import static android.net.ConnectivityManager.TYPE_NONE;  import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;  import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI; +import static android.net.ConnectivityManager.TYPE_NONE; +import static android.net.ConnectivityManager.getNetworkTypeName;  import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;  import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;  import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;  import android.content.Context; -import android.os.Handler; -import android.os.Looper; -import android.os.Process;  import android.net.ConnectivityManager;  import android.net.ConnectivityManager.NetworkCallback;  import android.net.IpPrefix; -import android.net.LinkAddress;  import android.net.LinkProperties;  import android.net.Network;  import android.net.NetworkCapabilities;  import android.net.NetworkRequest;  import android.net.NetworkState; -import android.net.util.NetworkConstants;  import android.net.util.PrefixUtils;  import android.net.util.SharedLog; +import android.os.Handler; +import android.os.Process;  import android.util.Log;  import com.android.internal.annotations.VisibleForTesting;  import com.android.internal.util.StateMachine; -import java.util.Collections;  import java.util.HashMap;  import java.util.HashSet;  import java.util.Set; @@ -97,10 +93,13 @@ public class UpstreamNetworkMonitor {      private final HashMap<Network, NetworkState> mNetworkMap = new HashMap<>();      private HashSet<IpPrefix> mLocalPrefixes;      private ConnectivityManager mCM; +    private EntitlementManager mEntitlementMgr;      private NetworkCallback mListenAllCallback;      private NetworkCallback mDefaultNetworkCallback;      private NetworkCallback mMobileNetworkCallback;      private boolean mDunRequired; +    // Whether the current default upstream is mobile or not. +    private boolean mIsDefaultCellularUpstream;      // The current system default network (not really used yet).      private Network mDefaultInternetNetwork;      // The current upstream network used for tethering. @@ -113,6 +112,7 @@ public class UpstreamNetworkMonitor {          mLog = log.forSubComponent(TAG);          mWhat = what;          mLocalPrefixes = new HashSet<>(); +        mIsDefaultCellularUpstream = false;      }      @VisibleForTesting @@ -122,7 +122,15 @@ public class UpstreamNetworkMonitor {          mCM = cm;      } -    public void startTrackDefaultNetwork(NetworkRequest defaultNetworkRequest) { +    /** +     * Tracking the system default network. This method should be called when system is ready. +     * +     * @param defaultNetworkRequest should be the same as ConnectivityService default request +     * @param entitle a EntitlementManager object to communicate between EntitlementManager and +     * UpstreamNetworkMonitor +     */ +    public void startTrackDefaultNetwork(NetworkRequest defaultNetworkRequest, +            EntitlementManager entitle) {          // This is not really a "request", just a way of tracking the system default network.          // It's guaranteed not to actually bring up any networks because it's the same request          // as the ConnectivityService default request, and thus shares fate with it. We can't @@ -133,6 +141,9 @@ public class UpstreamNetworkMonitor {              mDefaultNetworkCallback = new UpstreamNetworkCallback(CALLBACK_DEFAULT_INTERNET);              cm().requestNetwork(trackDefaultRequest, mDefaultNetworkCallback, mHandler);          } +        if (mEntitlementMgr == null) { +            mEntitlementMgr = entitle; +        }      }      public void startObserveAllNetworks() { @@ -168,11 +179,15 @@ public class UpstreamNetworkMonitor {      }      public void registerMobileNetworkRequest() { +        if (!isCellularUpstreamPermitted()) { +            mLog.i("registerMobileNetworkRequest() is not permitted"); +            releaseMobileNetworkRequest(); +            return; +        }          if (mMobileNetworkCallback != null) {              mLog.e("registerMobileNetworkRequest() already registered");              return;          } -          // The following use of the legacy type system cannot be removed until          // after upstream selection no longer finds networks by legacy type.          // See also http://b/34364553 . @@ -206,29 +221,32 @@ public class UpstreamNetworkMonitor {      // becomes available and useful we (a) file a request to keep it up as      // necessary and (b) change all upstream tracking state accordingly (by      // passing LinkProperties up to Tethering). -    // -    // Next TODO: return NetworkState instead of just the type.      public NetworkState selectPreferredUpstreamType(Iterable<Integer> preferredTypes) {          final TypeStatePair typeStatePair = findFirstAvailableUpstreamByType( -                mNetworkMap.values(), preferredTypes); +                mNetworkMap.values(), preferredTypes, isCellularUpstreamPermitted());          mLog.log("preferred upstream type: " + getNetworkTypeName(typeStatePair.type));          switch (typeStatePair.type) {              case TYPE_MOBILE_DUN:              case TYPE_MOBILE_HIPRI: +                // Tethering just selected mobile upstream in spite of the default network being +                // not mobile. This can happen because of the priority list. +                // Notify EntitlementManager to check permission for using mobile upstream. +                if (!mIsDefaultCellularUpstream) { +                    mEntitlementMgr.maybeRunProvisioning(); +                }                  // If we're on DUN, put our own grab on it.                  registerMobileNetworkRequest();                  break;              case TYPE_NONE: +                // If we found NONE and mobile upstream is permitted we don't want to do this +                // as we want any previous requests to keep trying to bring up something we can use. +                if (!isCellularUpstreamPermitted()) releaseMobileNetworkRequest();                  break;              default: -                /* If we've found an active upstream connection that's not DUN/HIPRI -                 * we should stop any outstanding DUN/HIPRI requests. -                 * -                 * If we found NONE we don't want to do this as we want any previous -                 * requests to keep trying to bring up something we can use. -                 */ +                // If we've found an active upstream connection that's not DUN/HIPRI +                // we should stop any outstanding DUN/HIPRI requests.                  releaseMobileNetworkRequest();                  break;          } @@ -241,10 +259,12 @@ public class UpstreamNetworkMonitor {          final NetworkState dfltState = (mDefaultInternetNetwork != null)                  ? mNetworkMap.get(mDefaultInternetNetwork)                  : null; -        if (!mDunRequired) return dfltState; -          if (isNetworkUsableAndNotCellular(dfltState)) return dfltState; +        if (!isCellularUpstreamPermitted()) return null; + +        if (!mDunRequired) return dfltState; +          // Find a DUN network. Note that code in Tethering causes a DUN request          // to be filed, but this might be moved into this class in future.          return findFirstDunNetwork(mNetworkMap.values()); @@ -258,6 +278,15 @@ public class UpstreamNetworkMonitor {          return (Set<IpPrefix>) mLocalPrefixes.clone();      } +    private boolean isCellularUpstreamPermitted() { +        if (mEntitlementMgr != null) { +            return mEntitlementMgr.isCellularUpstreamPermitted(); +        } else { +            // This flow should only happens in testing. +            return true; +        } +    } +      private void handleAvailable(Network network) {          if (mNetworkMap.containsKey(network)) return; @@ -388,8 +417,14 @@ public class UpstreamNetworkMonitor {          public void onCapabilitiesChanged(Network network, NetworkCapabilities newNc) {              if (mCallbackType == CALLBACK_DEFAULT_INTERNET) {                  mDefaultInternetNetwork = network; +                final boolean newIsCellular = isCellular(newNc); +                if (mIsDefaultCellularUpstream != newIsCellular) { +                    mIsDefaultCellularUpstream = newIsCellular; +                    mEntitlementMgr.notifyUpstream(newIsCellular); +                }                  return;              } +              handleNetCap(network, newNc);          } @@ -424,8 +459,11 @@ public class UpstreamNetworkMonitor {          public void onLost(Network network) {              if (mCallbackType == CALLBACK_DEFAULT_INTERNET) {                  mDefaultInternetNetwork = null; +                mIsDefaultCellularUpstream = false; +                mEntitlementMgr.notifyUpstream(false);                  return;              } +              handleLost(network);              // Any non-LISTEN_ALL callback will necessarily concern a network that will              // also match the LISTEN_ALL callback by construction of the LISTEN_ALL callback. @@ -454,7 +492,8 @@ public class UpstreamNetworkMonitor {      }      private static TypeStatePair findFirstAvailableUpstreamByType( -            Iterable<NetworkState> netStates, Iterable<Integer> preferredTypes) { +            Iterable<NetworkState> netStates, Iterable<Integer> preferredTypes, +            boolean isCellularUpstreamPermitted) {          final TypeStatePair result = new TypeStatePair();          for (int type : preferredTypes) { @@ -466,6 +505,10 @@ public class UpstreamNetworkMonitor {                         ConnectivityManager.getNetworkTypeName(type));                  continue;              } +            if (!isCellularUpstreamPermitted && isCellular(nc)) { +                continue; +            } +              nc.setSingleUid(Process.myUid());              for (NetworkState value : netStates) { diff --git a/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java b/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java index bac509802258..9eab4bec2973 100644 --- a/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java +++ b/tests/net/java/com/android/server/connectivity/tethering/EntitlementManagerTest.java @@ -16,6 +16,7 @@  package com.android.server.connectivity.tethering; +import static android.net.ConnectivityManager.TETHERING_BLUETOOTH;  import static android.net.ConnectivityManager.TETHERING_USB;  import static android.net.ConnectivityManager.TETHERING_WIFI;  import static android.net.ConnectivityManager.TETHER_ERROR_ENTITLEMENT_UNKONWN; @@ -72,6 +73,7 @@ public final class EntitlementManagerTest {      private static final int EVENT_EM_UPDATE = 1;      private static final String[] PROVISIONING_APP_NAME = {"some", "app"}; +    private static final String PROVISIONING_NO_UI_APP_NAME = "no_ui_app";      @Mock private CarrierConfigManager mCarrierConfigManager;      @Mock private Context mContext; @@ -108,10 +110,12 @@ public final class EntitlementManagerTest {      public class WrappedEntitlementManager extends EntitlementManager {          public int fakeEntitlementResult = TETHER_ERROR_ENTITLEMENT_UNKONWN;          public boolean everRunUiEntitlement = false; +        public int uiProvisionCount = 0; +        public int silentProvisionCount = 0;          public WrappedEntitlementManager(Context ctx, StateMachine target, -                SharedLog log, MockableSystemProperties systemProperties) { -            super(ctx, target, log, systemProperties); +                SharedLog log, int what, MockableSystemProperties systemProperties) { +            super(ctx, target, log, what, systemProperties);          }          @Override @@ -119,6 +123,16 @@ public final class EntitlementManagerTest {              everRunUiEntitlement = true;              receiver.send(fakeEntitlementResult, null);          } + +        @Override +        protected void runUiTetherProvisioning(int type) { +            uiProvisionCount++; +        } + +        @Override +        protected void runSilentTetherProvisioning(int type) { +            silentProvisionCount++; +        }      }      @Before @@ -141,7 +155,8 @@ public final class EntitlementManagerTest {          mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());          mMockContext = new MockContext(mContext);          mSM = new TestStateMachine(); -        mEnMgr = new WrappedEntitlementManager(mMockContext, mSM, mLog, mSystemProperties); +        mEnMgr = new WrappedEntitlementManager(mMockContext, mSM, mLog, EVENT_EM_UPDATE, +                mSystemProperties);          mEnMgr.updateConfiguration(                  new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID));      } @@ -158,7 +173,9 @@ public final class EntitlementManagerTest {          // Produce some acceptable looking provision app setting if requested.          when(mResources.getStringArray(R.array.config_mobile_hotspot_provision_app))                  .thenReturn(PROVISIONING_APP_NAME); -        // Don't disable tethering provisioning unless requested. +        when(mResources.getString(R.string.config_mobile_hotspot_provision_app_no_ui)) +                .thenReturn(PROVISIONING_NO_UI_APP_NAME); +       // Don't disable tethering provisioning unless requested.          when(mSystemProperties.getBoolean(eq(EntitlementManager.DISABLE_PROVISIONING_SYSPROP_KEY),                  anyBoolean())).thenReturn(false);          // Act like the CarrierConfigManager is present and ready unless told otherwise. @@ -238,6 +255,7 @@ public final class EntitlementManagerTest {              }          };          mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true); +        mLooper.dispatchAll();          callbackTimeoutHelper(mCallbacklatch);          assertFalse(mEnMgr.everRunUiEntitlement); @@ -254,6 +272,7 @@ public final class EntitlementManagerTest {              }          };          mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, false); +        mLooper.dispatchAll();          callbackTimeoutHelper(mCallbacklatch);          assertFalse(mEnMgr.everRunUiEntitlement);          // 3. No cache value and ui entitlement check is needed. @@ -281,6 +300,7 @@ public final class EntitlementManagerTest {              }          };          mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, false); +        mLooper.dispatchAll();          callbackTimeoutHelper(mCallbacklatch);          assertFalse(mEnMgr.everRunUiEntitlement);          // 5. Cache value is TETHER_ERROR_PROVISION_FAILED and ui entitlement check is needed. @@ -308,6 +328,7 @@ public final class EntitlementManagerTest {              }          };          mEnMgr.getLatestTetheringEntitlementResult(TETHERING_WIFI, receiver, true); +        mLooper.dispatchAll();          callbackTimeoutHelper(mCallbacklatch);          assertFalse(mEnMgr.everRunUiEntitlement);          // 7. Test get value for other downstream type. @@ -320,19 +341,128 @@ public final class EntitlementManagerTest {              }          };          mEnMgr.getLatestTetheringEntitlementResult(TETHERING_USB, receiver, false); +        mLooper.dispatchAll();          callbackTimeoutHelper(mCallbacklatch);          assertFalse(mEnMgr.everRunUiEntitlement);      }      void callbackTimeoutHelper(final CountDownLatch latch) throws Exception {          if (!latch.await(1, TimeUnit.SECONDS)) { -            fail("Timout, fail to recieve callback"); +            fail("Timout, fail to receive callback");          }      } + +    @Test +    public void verifyPermissionResult() { +        setupForRequiredProvisioning(); +        mEnMgr.notifyUpstream(true); +        mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog, +                  INVALID_SUBSCRIPTION_ID)); +        mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true); +        mLooper.dispatchAll(); +        mEnMgr.addDownstreamMapping(TETHERING_WIFI, TETHER_ERROR_PROVISION_FAILED); +        assertFalse(mEnMgr.isCellularUpstreamPermitted()); +        mEnMgr.stopProvisioningIfNeeded(TETHERING_WIFI); +        mLooper.dispatchAll(); +        mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true); +        mLooper.dispatchAll(); +        mEnMgr.addDownstreamMapping(TETHERING_WIFI, TETHER_ERROR_NO_ERROR); +        assertTrue(mEnMgr.isCellularUpstreamPermitted()); +    } + +    @Test +    public void verifyPermissionIfAllNotApproved() { +        setupForRequiredProvisioning(); +        mEnMgr.notifyUpstream(true); +        mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog, +                  INVALID_SUBSCRIPTION_ID)); +        mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true); +        mLooper.dispatchAll(); +        mEnMgr.addDownstreamMapping(TETHERING_WIFI, TETHER_ERROR_PROVISION_FAILED); +        assertFalse(mEnMgr.isCellularUpstreamPermitted()); +        mEnMgr.startProvisioningIfNeeded(TETHERING_USB, true); +        mLooper.dispatchAll(); +        mEnMgr.addDownstreamMapping(TETHERING_USB, TETHER_ERROR_PROVISION_FAILED); +        assertFalse(mEnMgr.isCellularUpstreamPermitted()); +        mEnMgr.startProvisioningIfNeeded(TETHERING_BLUETOOTH, true); +        mLooper.dispatchAll(); +        mEnMgr.addDownstreamMapping(TETHERING_BLUETOOTH, TETHER_ERROR_PROVISION_FAILED); +        assertFalse(mEnMgr.isCellularUpstreamPermitted()); +    } + +    @Test +    public void verifyPermissionIfAnyApproved() { +        setupForRequiredProvisioning(); +        mEnMgr.notifyUpstream(true); +        mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog, +                  INVALID_SUBSCRIPTION_ID)); +        mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, true); +        mLooper.dispatchAll(); +        mEnMgr.addDownstreamMapping(TETHERING_WIFI, TETHER_ERROR_NO_ERROR); +        assertTrue(mEnMgr.isCellularUpstreamPermitted()); +        mLooper.dispatchAll(); +        mEnMgr.startProvisioningIfNeeded(TETHERING_USB, true); +        mLooper.dispatchAll(); +        mEnMgr.addDownstreamMapping(TETHERING_USB, TETHER_ERROR_PROVISION_FAILED); +        assertTrue(mEnMgr.isCellularUpstreamPermitted()); +        mEnMgr.stopProvisioningIfNeeded(TETHERING_WIFI); +        mLooper.dispatchAll(); +        assertFalse(mEnMgr.isCellularUpstreamPermitted()); + +    } + +    @Test +    public void testRunTetherProvisioning() { +        setupForRequiredProvisioning(); +        mEnMgr.updateConfiguration(new TetheringConfiguration(mMockContext, mLog, +                INVALID_SUBSCRIPTION_ID)); +        // 1. start ui provisioning, upstream is mobile +        mEnMgr.notifyUpstream(true); +        mLooper.dispatchAll(); +        mEnMgr.startProvisioningIfNeeded(TETHERING_USB, true); +        mLooper.dispatchAll(); +        assertTrue(mEnMgr.uiProvisionCount == 1); +        assertTrue(mEnMgr.silentProvisionCount == 0); +        mEnMgr.addDownstreamMapping(TETHERING_USB, TETHER_ERROR_PROVISION_FAILED); +        // 2. start no-ui provisioning +        mEnMgr.startProvisioningIfNeeded(TETHERING_WIFI, false); +        mLooper.dispatchAll(); +        assertTrue(mEnMgr.silentProvisionCount == 1); +        assertTrue(mEnMgr.uiProvisionCount == 1); +        mEnMgr.addDownstreamMapping(TETHERING_WIFI, TETHER_ERROR_PROVISION_FAILED); +        // 3. tear down mobile, then start ui provisioning +        mEnMgr.notifyUpstream(false); +        mLooper.dispatchAll(); +        mEnMgr.startProvisioningIfNeeded(TETHERING_BLUETOOTH, true); +        mLooper.dispatchAll(); +        assertTrue(mEnMgr.uiProvisionCount == 1); +        assertTrue(mEnMgr.silentProvisionCount == 1); +        // 4. switch upstream back to mobile +        mEnMgr.notifyUpstream(true); +        mLooper.dispatchAll(); +        assertTrue(mEnMgr.uiProvisionCount == 2); +        assertTrue(mEnMgr.silentProvisionCount == 1); +        mEnMgr.addDownstreamMapping(TETHERING_BLUETOOTH, TETHER_ERROR_PROVISION_FAILED); +        // 5. tear down mobile, then switch SIM +        mEnMgr.notifyUpstream(false); +        mLooper.dispatchAll(); +        mEnMgr.reevaluateSimCardProvisioning(); +        assertTrue(mEnMgr.uiProvisionCount == 2); +        assertTrue(mEnMgr.silentProvisionCount == 1); +        // 6. switch upstream back to mobile again +        mEnMgr.notifyUpstream(true); +        mLooper.dispatchAll(); +        assertTrue(mEnMgr.uiProvisionCount == 2); +        assertTrue(mEnMgr.silentProvisionCount == 4); +        mEnMgr.addDownstreamMapping(TETHERING_USB, TETHER_ERROR_PROVISION_FAILED); +        mEnMgr.addDownstreamMapping(TETHERING_WIFI, TETHER_ERROR_PROVISION_FAILED); +        mEnMgr.addDownstreamMapping(TETHERING_BLUETOOTH, TETHER_ERROR_PROVISION_FAILED); +    } +      public class TestStateMachine extends StateMachine {          public final ArrayList<Message> messages = new ArrayList<>(); -        private final State mLoggingState = -                new EntitlementManagerTest.TestStateMachine.LoggingState(); +        private final State +                mLoggingState = new EntitlementManagerTest.TestStateMachine.LoggingState();          class LoggingState extends State {              @Override public void enter() { diff --git a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java index 5a1f853e75a9..0d276cbd1b85 100644 --- a/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java +++ b/tests/net/java/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java @@ -90,6 +90,7 @@ public class UpstreamNetworkMonitorTest {      private static final NetworkRequest mDefaultRequest = new NetworkRequest.Builder().build();      @Mock private Context mContext; +    @Mock private EntitlementManager mEntitleMgr;      @Mock private IConnectivityManager mCS;      @Mock private SharedLog mLog; @@ -103,6 +104,7 @@ public class UpstreamNetworkMonitorTest {          reset(mCS);          reset(mLog);          when(mLog.forSubComponent(anyString())).thenReturn(mLog); +        when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true);          mCM = spy(new TestConnectivityManager(mContext, mCS));          mSM = new TestStateMachine(); @@ -138,7 +140,7 @@ public class UpstreamNetworkMonitorTest {      @Test      public void testDefaultNetworkIsTracked() throws Exception {          assertTrue(mCM.hasNoCallbacks()); -        mUNM.startTrackDefaultNetwork(mDefaultRequest); +        mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr);          mUNM.startObserveAllNetworks();          assertEquals(1, mCM.trackingDefault.size()); @@ -151,7 +153,7 @@ public class UpstreamNetworkMonitorTest {      public void testListensForAllNetworks() throws Exception {          assertTrue(mCM.listening.isEmpty()); -        mUNM.startTrackDefaultNetwork(mDefaultRequest); +        mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr);          mUNM.startObserveAllNetworks();          assertFalse(mCM.listening.isEmpty());          assertTrue(mCM.isListeningForAll()); @@ -162,7 +164,7 @@ public class UpstreamNetworkMonitorTest {      @Test      public void testCallbacksRegistered() { -        mUNM.startTrackDefaultNetwork(mDefaultRequest); +        mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr);          verify(mCM, times(1)).requestNetwork(                  eq(mDefaultRequest), any(NetworkCallback.class), any(Handler.class));          mUNM.startObserveAllNetworks(); @@ -285,7 +287,7 @@ public class UpstreamNetworkMonitorTest {          final Collection<Integer> preferredTypes = new ArrayList<>();          preferredTypes.add(TYPE_WIFI); -        mUNM.startTrackDefaultNetwork(mDefaultRequest); +        mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr);          mUNM.startObserveAllNetworks();          // There are no networks, so there is nothing to select.          assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes)); @@ -319,6 +321,14 @@ public class UpstreamNetworkMonitorTest {          NetworkRequest netReq = (NetworkRequest) mCM.requested.values().toArray()[0];          assertTrue(netReq.networkCapabilities.hasTransport(TRANSPORT_CELLULAR));          assertFalse(netReq.networkCapabilities.hasCapability(NET_CAPABILITY_DUN)); +        // mobile is not permitted, we should not use HIPRI. +        when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(false); +        assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes)); +        assertEquals(0, mCM.requested.size()); +        // mobile change back to permitted, HIRPI should come back +        when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true); +        assertSatisfiesLegacyType(TYPE_MOBILE_HIPRI, +                mUNM.selectPreferredUpstreamType(preferredTypes));          wifiAgent.fakeConnect();          // WiFi is up, and we should prefer it over cell. @@ -347,11 +357,19 @@ public class UpstreamNetworkMonitorTest {          netReq = (NetworkRequest) mCM.requested.values().toArray()[0];          assertTrue(netReq.networkCapabilities.hasTransport(TRANSPORT_CELLULAR));          assertTrue(netReq.networkCapabilities.hasCapability(NET_CAPABILITY_DUN)); +        // mobile is not permitted, we should not use DUN. +        when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(false); +        assertSatisfiesLegacyType(TYPE_NONE, mUNM.selectPreferredUpstreamType(preferredTypes)); +        assertEquals(0, mCM.requested.size()); +        // mobile change back to permitted, DUN should come back +        when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true); +        assertSatisfiesLegacyType(TYPE_MOBILE_DUN, +                mUNM.selectPreferredUpstreamType(preferredTypes));      }      @Test      public void testGetCurrentPreferredUpstream() throws Exception { -        mUNM.startTrackDefaultNetwork(mDefaultRequest); +        mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr);          mUNM.startObserveAllNetworks();          mUNM.updateMobileRequiresDun(false); @@ -361,37 +379,46 @@ public class UpstreamNetworkMonitorTest {          mCM.makeDefaultNetwork(cellAgent);          assertEquals(cellAgent.networkId, mUNM.getCurrentPreferredUpstream().network); -        // [1] WiFi connects but not validated/promoted to default -> mobile selected. +        // [1] Mobile connects but not permitted -> null selected +        when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(false); +        assertEquals(null, mUNM.getCurrentPreferredUpstream()); +        when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(true); + +        // [2] WiFi connects but not validated/promoted to default -> mobile selected.          final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI);          wifiAgent.fakeConnect();          assertEquals(cellAgent.networkId, mUNM.getCurrentPreferredUpstream().network); -        // [2] WiFi validates and is promoted to the default network -> WiFi selected. +        // [3] WiFi validates and is promoted to the default network -> WiFi selected.          mCM.makeDefaultNetwork(wifiAgent);          assertEquals(wifiAgent.networkId, mUNM.getCurrentPreferredUpstream().network); -        // [3] DUN required, no other changes -> WiFi still selected +        // [4] DUN required, no other changes -> WiFi still selected          mUNM.updateMobileRequiresDun(true);          assertEquals(wifiAgent.networkId, mUNM.getCurrentPreferredUpstream().network); -        // [4] WiFi no longer validated, mobile becomes default, DUN required -> null selected. +        // [5] WiFi no longer validated, mobile becomes default, DUN required -> null selected.          mCM.makeDefaultNetwork(cellAgent);          assertEquals(null, mUNM.getCurrentPreferredUpstream());          // TODO: make sure that a DUN request has been filed. This is currently          // triggered by code over in Tethering, but once that has been moved          // into UNM we should test for this here. -        // [5] DUN network arrives -> DUN selected +        // [6] DUN network arrives -> DUN selected          final TestNetworkAgent dunAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR);          dunAgent.networkCapabilities.addCapability(NET_CAPABILITY_DUN);          dunAgent.networkCapabilities.removeCapability(NET_CAPABILITY_INTERNET);          dunAgent.fakeConnect();          assertEquals(dunAgent.networkId, mUNM.getCurrentPreferredUpstream().network); + +        // [7] Mobile is not permitted -> null selected +        when(mEntitleMgr.isCellularUpstreamPermitted()).thenReturn(false); +        assertEquals(null, mUNM.getCurrentPreferredUpstream());      }      @Test      public void testLocalPrefixes() throws Exception { -        mUNM.startTrackDefaultNetwork(mDefaultRequest); +        mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr);          mUNM.startObserveAllNetworks();          // [0] Test minimum set of local prefixes. @@ -492,6 +519,26 @@ public class UpstreamNetworkMonitorTest {          assertTrue(local.isEmpty());      } +    @Test +    public void testSelectMobileWhenMobileIsNotDefault() { +        final Collection<Integer> preferredTypes = new ArrayList<>(); +        // Mobile has higher pirority than wifi. +        preferredTypes.add(TYPE_MOBILE_HIPRI); +        preferredTypes.add(TYPE_WIFI); +        mUNM.startTrackDefaultNetwork(mDefaultRequest, mEntitleMgr); +        mUNM.startObserveAllNetworks(); +        // Setup wifi and make wifi as default network. +        final TestNetworkAgent wifiAgent = new TestNetworkAgent(mCM, TRANSPORT_WIFI); +        wifiAgent.fakeConnect(); +        mCM.makeDefaultNetwork(wifiAgent); +        // Setup mobile network. +        final TestNetworkAgent cellAgent = new TestNetworkAgent(mCM, TRANSPORT_CELLULAR); +        cellAgent.fakeConnect(); + +        assertSatisfiesLegacyType(TYPE_MOBILE_HIPRI, +                mUNM.selectPreferredUpstreamType(preferredTypes)); +        verify(mEntitleMgr, times(1)).maybeRunProvisioning(); +    }      private void assertSatisfiesLegacyType(int legacyType, NetworkState ns) {          if (legacyType == TYPE_NONE) {              assertTrue(ns == null);  |