Get entitlement configuration from intent extra

Tethering resource configuration is move from framwork to tethering
module. The resource would not be accessible from outside of tethering
module.
List the replacements of framework resources usage and intent extra:
1. R.string.config_mobile_hotspot_provision_response
    --> android.net.extra.TETHER_PROVISIONING_RESPONSE.
2. R.string.config_mobile_hotspot_provision_app_no_ui
    --> android.net.extra.TETHER_UI_PROVISIONING_APP_NAME
3. R.array.config_mobile_hotspot_provision_app
    --> android.net.extra.TETHER_SILENT_PROVISIONING_ACTION
Besides, the current active subId would put in
android.net.extra.TETHER_SUBID

Note: They are not APIs because of API freeze. Now both tethering module
and Settings define these strings independently. Will replace hard code
string as tethering module-lib APIs in b/159085857.

Also move the entitlement response intent registeration from onCreated
to onStartCommand, this can avoid wrong intent registeration if subId
changed between onCreate and when the intent arrived.

Bug: 146918263
Test: atest TetherServiceTest
      atest TetherProvisioningActivityTest

Change-Id: I3d06df01302a9c1f0893712d9250fe394dc66588
Merged-In: I3d06df01302a9c1f0893712d9250fe394dc66588
diff --git a/src/com/android/settings/network/TetherProvisioningActivity.java b/src/com/android/settings/network/TetherProvisioningActivity.java
index bb61546..047c90d 100644
--- a/src/com/android/settings/network/TetherProvisioningActivity.java
+++ b/src/com/android/settings/network/TetherProvisioningActivity.java
@@ -18,7 +18,6 @@
 
 import static android.net.TetheringConstants.EXTRA_ADD_TETHER_TYPE;
 import static android.net.TetheringConstants.EXTRA_PROVISION_CALLBACK;
-import static android.net.TetheringConstants.EXTRA_RUN_PROVISION;
 import static android.net.TetheringManager.TETHERING_INVALID;
 import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
 import static android.net.TetheringManager.TETHER_ERROR_PROVISIONING_FAILED;
@@ -28,7 +27,6 @@
 import android.app.Activity;
 import android.content.Intent;
 import android.content.pm.PackageManager;
-import android.content.res.Resources;
 import android.os.Bundle;
 import android.os.ResultReceiver;
 import android.os.UserHandle;
@@ -37,8 +35,6 @@
 
 import androidx.annotation.VisibleForTesting;
 
-import com.android.settings.Utils;
-
 /**
  * Activity which acts as a proxy to the tether provisioning app for sanity checks and permission
  * restrictions. Specifically, the provisioning apps require
@@ -53,7 +49,10 @@
     @VisibleForTesting
     static final int PROVISION_REQUEST = 0;
     @VisibleForTesting
-    static final String EXTRA_SUBID = "subId";
+    static final String EXTRA_TETHER_SUBID = "android.net.extra.TETHER_SUBID";
+    @VisibleForTesting
+    public static final String EXTRA_TETHER_UI_PROVISIONING_APP_NAME =
+            "android.net.extra.TETHER_UI_PROVISIONING_APP_NAME";
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -62,7 +61,8 @@
 
         final int tetherType = getIntent().getIntExtra(EXTRA_ADD_TETHER_TYPE, TETHERING_INVALID);
 
-        final int tetherSubId = getIntent().getIntExtra(EXTRA_SUBID, INVALID_SUBSCRIPTION_ID);
+        final int tetherSubId = getIntent().getIntExtra(
+                EXTRA_TETHER_SUBID, INVALID_SUBSCRIPTION_ID);
         final int subId = SubscriptionManager.getActiveDataSubscriptionId();
         if (tetherSubId != subId) {
             Log.e(TAG, "This Provisioning request is outdated, current subId: " + subId);
@@ -70,11 +70,11 @@
             finish();
             return;
         }
-        String[] provisionApp = getIntent().getStringArrayExtra(EXTRA_RUN_PROVISION);
-        if (provisionApp == null || provisionApp.length < 2) {
-            final Resources res = Utils.getResourcesForSubId(this, subId);
-            provisionApp = res.getStringArray(
-                    com.android.internal.R.array.config_mobile_hotspot_provision_app);
+        String[] provisionApp = getIntent().getStringArrayExtra(
+                EXTRA_TETHER_UI_PROVISIONING_APP_NAME);
+        if (provisionApp == null || provisionApp.length != 2) {
+            Log.e(TAG, "Unexpected provision app configuration");
+            return;
         }
         final Intent intent = new Intent(Intent.ACTION_MAIN);
         intent.setClassName(provisionApp[0], provisionApp[1]);
diff --git a/src/com/android/settings/wifi/tether/TetherService.java b/src/com/android/settings/wifi/tether/TetherService.java
index 72ea1a9..5902719 100644
--- a/src/com/android/settings/wifi/tether/TetherService.java
+++ b/src/com/android/settings/wifi/tether/TetherService.java
@@ -41,7 +41,6 @@
 import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
 import android.net.TetheringManager;
 import android.os.IBinder;
 import android.os.ResultReceiver;
@@ -52,10 +51,9 @@
 
 import androidx.annotation.VisibleForTesting;
 
-import com.android.settings.Utils;
-
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 
 public class TetherService extends Service {
     private static final String TAG = "TetherService";
@@ -64,7 +62,13 @@
     @VisibleForTesting
     public static final String EXTRA_RESULT = "EntitlementResult";
     @VisibleForTesting
-    public static final String EXTRA_SUBID = "subId";
+    public static final String EXTRA_TETHER_SUBID = "android.net.extra.TETHER_SUBID";
+    @VisibleForTesting
+    public static final String EXTRA_TETHER_PROVISIONING_RESPONSE =
+            "android.net.extra.TETHER_PROVISIONING_RESPONSE";
+    @VisibleForTesting
+    public static final String EXTRA_TETHER_SILENT_PROVISIONING_ACTION =
+            "android.net.extra.TETHER_SILENT_PROVISIONING_ACTION";
 
     // Activity results to match the activity provision protocol.
     // Default to something not ok.
@@ -79,6 +83,11 @@
 
     private int mCurrentTypeIndex;
     private boolean mInProvisionCheck;
+    /** Intent action received from the provisioning app when entitlement check completes. */
+    private String mExpectedProvisionResponseAction = null;
+    /** Intent action sent to the provisioning app to request an entitlement check. */
+    private String mProvisionAction;
+    private int mSubId = INVALID_SUBSCRIPTION_ID;
     private TetherServiceWrapper mWrapper;
     private ArrayList<Integer> mCurrentTethers;
     private ArrayMap<Integer, List<ResultReceiver>> mPendingCallbacks;
@@ -92,10 +101,6 @@
     public void onCreate() {
         super.onCreate();
         if (DEBUG) Log.d(TAG, "Creating TetherService");
-        String provisionResponse = getResourceForActiveDataSubId().getString(
-                com.android.internal.R.string.config_mobile_hotspot_provision_response);
-        registerReceiver(mReceiver, new IntentFilter(provisionResponse),
-                android.Manifest.permission.TETHER_PRIVILEGED, null);
         SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE);
         mCurrentTethers = stringToTethers(prefs.getString(KEY_TETHERS, ""));
         mCurrentTypeIndex = 0;
@@ -106,10 +111,28 @@
         mPendingCallbacks.put(TETHERING_ETHERNET, new ArrayList<ResultReceiver>());
     }
 
+    // Registers the broadcast receiver for the specified response action, first unregistering
+    // the receiver if it was registered for a different response action.
+    private void maybeRegisterReceiver(final String responseAction) {
+        if (Objects.equals(responseAction, mExpectedProvisionResponseAction)) return;
+
+        if (mExpectedProvisionResponseAction != null) unregisterReceiver(mReceiver);
+
+        registerReceiver(mReceiver, new IntentFilter(responseAction),
+                android.Manifest.permission.TETHER_PRIVILEGED, null /* handler */);
+        mExpectedProvisionResponseAction = responseAction;
+        if (DEBUG) Log.d(TAG, "registerReceiver " + responseAction);
+    }
+
+    private int stopSelfAndStartNotSticky() {
+        stopSelf();
+        return START_NOT_STICKY;
+    }
+
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
-        if (intent.hasExtra(EXTRA_SUBID)) {
-            final int tetherSubId = intent.getIntExtra(EXTRA_SUBID, INVALID_SUBSCRIPTION_ID);
+        if (intent.hasExtra(EXTRA_TETHER_SUBID)) {
+            final int tetherSubId = intent.getIntExtra(EXTRA_TETHER_SUBID, INVALID_SUBSCRIPTION_ID);
             final int subId = getTetherServiceWrapper().getActiveDataSubscriptionId();
             if (tetherSubId != subId) {
                 Log.e(TAG, "This Provisioning request is outdated, current subId: " + subId);
@@ -118,7 +141,9 @@
                 }
                 return START_NOT_STICKY;
             }
+            mSubId = subId;
         }
+
         if (intent.hasExtra(EXTRA_ADD_TETHER_TYPE)) {
             int type = intent.getIntExtra(EXTRA_ADD_TETHER_TYPE, TETHERING_INVALID);
             ResultReceiver callback = intent.getParcelableExtra(EXTRA_PROVISION_CALLBACK);
@@ -128,9 +153,9 @@
                     callbacksForType.add(callback);
                 } else {
                     // Invalid tether type. Just ignore this request and report failure.
+                    Log.e(TAG, "Invalid tethering type " + type + ", stopping");
                     callback.send(TETHER_ERROR_UNKNOWN_IFACE, null);
-                    stopSelf();
-                    return START_NOT_STICKY;
+                    return stopSelfAndStartNotSticky();
                 }
             }
 
@@ -140,6 +165,19 @@
             }
         }
 
+        mProvisionAction = intent.getStringExtra(EXTRA_TETHER_SILENT_PROVISIONING_ACTION);
+        if (mProvisionAction == null) {
+            Log.e(TAG, "null provisioning action, stop ");
+            return stopSelfAndStartNotSticky();
+        }
+
+        final String response = intent.getStringExtra(EXTRA_TETHER_PROVISIONING_RESPONSE);
+        if (response == null) {
+            Log.e(TAG, "null provisioning response, stop ");
+            return stopSelfAndStartNotSticky();
+        }
+        maybeRegisterReceiver(response);
+
         if (intent.hasExtra(EXTRA_REM_TETHER_TYPE)) {
             if (!mInProvisionCheck) {
                 int type = intent.getIntExtra(EXTRA_REM_TETHER_TYPE, TETHERING_INVALID);
@@ -158,8 +196,7 @@
         } else if (!mInProvisionCheck) {
             // If we aren't running any provisioning, no reason to stay alive.
             if (DEBUG) Log.d(TAG, "Stopping self.  startid: " + startId);
-            stopSelf();
-            return START_NOT_STICKY;
+            return stopSelfAndStartNotSticky();
         }
         // We want to be started if we are killed accidently, so that we can be sure we finish
         // the check.
@@ -175,7 +212,10 @@
         SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE);
         prefs.edit().putString(KEY_TETHERS, tethersToString(mCurrentTethers)).commit();
 
-        unregisterReceiver(mReceiver);
+        if (mExpectedProvisionResponseAction != null) {
+            unregisterReceiver(mReceiver);
+            mExpectedProvisionResponseAction = null;
+        }
         if (DEBUG) Log.d(TAG, "Destroying TetherService");
         super.onDestroy();
     }
@@ -220,26 +260,26 @@
     }
 
     private void startProvisioning(int index) {
-        if (index < mCurrentTethers.size()) {
-            Intent intent = getProvisionBroadcastIntent(index);
-            setEntitlementAppActive(index);
+        if (index >= mCurrentTethers.size()) return;
 
-            if (DEBUG) Log.d(TAG, "Sending provisioning broadcast: " + intent.getAction()
+        Intent intent = getProvisionBroadcastIntent(index);
+        setEntitlementAppActive(index);
+
+        if (DEBUG) {
+            Log.d(TAG, "Sending provisioning broadcast: " + intent.getAction()
                     + " type: " + mCurrentTethers.get(index));
-
-            sendBroadcast(intent);
-            mInProvisionCheck = true;
         }
+
+        sendBroadcast(intent);
+        mInProvisionCheck = true;
     }
 
     private Intent getProvisionBroadcastIntent(int index) {
-        String provisionAction = getResourceForActiveDataSubId().getString(
-                com.android.internal.R.string.config_mobile_hotspot_provision_app_no_ui);
-        final int subId = getTetherServiceWrapper().getActiveDataSubscriptionId();
-        Intent intent = new Intent(provisionAction);
+        if (mProvisionAction == null) Log.wtf(TAG, "null provisioning action");
+        Intent intent = new Intent(mProvisionAction);
         int type = mCurrentTethers.get(index);
         intent.putExtra(TETHER_CHOICE, type);
-        intent.putExtra(EXTRA_SUBSCRIPTION_INDEX, subId);
+        intent.putExtra(EXTRA_SUBSCRIPTION_INDEX, mSubId);
         intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND
                 | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
 
@@ -282,27 +322,30 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             if (DEBUG) Log.d(TAG, "Got provision result " + intent);
-            String provisionResponse = getResourceForActiveDataSubId().getString(
-                    com.android.internal.R.string.config_mobile_hotspot_provision_response);
 
-            if (provisionResponse.equals(intent.getAction())) {
-                if (!mInProvisionCheck) {
-                    Log.e(TAG, "Unexpected provision response " + intent);
-                    return;
-                }
-                int checkType = mCurrentTethers.get(mCurrentTypeIndex);
-                mInProvisionCheck = false;
-                int result = intent.getIntExtra(EXTRA_RESULT, RESULT_DEFAULT);
-                if (result != RESULT_OK) disableTethering(checkType);
-                fireCallbacksForType(checkType, result);
+            if (!intent.getAction().equals(mExpectedProvisionResponseAction)) {
+                Log.e(TAG, "Received provisioning response for unexpected action="
+                        + intent.getAction() + ", expected=" + mExpectedProvisionResponseAction);
+                return;
+            }
 
-                if (++mCurrentTypeIndex >= mCurrentTethers.size()) {
-                    // We are done with all checks, time to die.
-                    stopSelf();
-                } else {
-                    // Start the next check in our list.
-                    startProvisioning(mCurrentTypeIndex);
-                }
+            if (!mInProvisionCheck) {
+                Log.e(TAG, "Unexpected provisioning response when not in provisioning check"
+                        + intent);
+                return;
+            }
+            int checkType = mCurrentTethers.get(mCurrentTypeIndex);
+            mInProvisionCheck = false;
+            int result = intent.getIntExtra(EXTRA_RESULT, RESULT_DEFAULT);
+            if (result != RESULT_OK) disableTethering(checkType);
+            fireCallbacksForType(checkType, result);
+
+            if (++mCurrentTypeIndex >= mCurrentTethers.size()) {
+                // We are done with all checks, time to die.
+                stopSelf();
+            } else {
+                // Start the next check in our list.
+                startProvisioning(mCurrentTypeIndex);
             }
         }
     };
@@ -321,8 +364,7 @@
 
     /**
      * A static helper class used for tests. UsageStatsManager cannot be mocked out because
-     * it's marked final. Static method SubscriptionManager#getResourcesForSubId also cannot
-     * be mocked. This class can be mocked out instead.
+     * it's marked final. This class can be mocked out instead.
      */
     @VisibleForTesting
     public static class TetherServiceWrapper {
@@ -341,10 +383,4 @@
             return SubscriptionManager.getActiveDataSubscriptionId();
         }
     }
-
-    @VisibleForTesting
-    Resources getResourceForActiveDataSubId() {
-        final int subId = getTetherServiceWrapper().getActiveDataSubscriptionId();
-        return Utils.getResourcesForSubId(this, subId);
-    }
 }
diff --git a/tests/unit/src/com/android/settings/network/TetherProvisioningActivityTest.java b/tests/unit/src/com/android/settings/network/TetherProvisioningActivityTest.java
index f762888..5c8bf7c 100644
--- a/tests/unit/src/com/android/settings/network/TetherProvisioningActivityTest.java
+++ b/tests/unit/src/com/android/settings/network/TetherProvisioningActivityTest.java
@@ -18,9 +18,10 @@
 
 import static android.net.TetheringConstants.EXTRA_ADD_TETHER_TYPE;
 import static android.net.TetheringConstants.EXTRA_PROVISION_CALLBACK;
-import static android.net.TetheringConstants.EXTRA_RUN_PROVISION;
 import static android.net.TetheringManager.TETHERING_WIFI;
 
+import static com.android.settings.network.TetherProvisioningActivity.EXTRA_TETHER_SUBID;
+import static com.android.settings.network.TetherProvisioningActivity.EXTRA_TETHER_UI_PROVISIONING_APP_NAME;
 import static com.android.settings.network.TetherProvisioningActivity.PROVISION_REQUEST;
 
 import static org.junit.Assert.assertEquals;
@@ -72,7 +73,7 @@
                 new Intent(Settings.ACTION_TETHER_PROVISIONING_UI)
                         .putExtra(EXTRA_ADD_TETHER_TYPE, TETHERING_WIFI)
                         .putExtra(EXTRA_PROVISION_CALLBACK, receiver)
-                        .putExtra(TetherProvisioningActivity.EXTRA_SUBID, 10000))) {
+                        .putExtra(TetherProvisioningActivity.EXTRA_TETHER_SUBID, 10000))) {
             assertEquals(TetheringManager.TETHER_ERROR_PROVISIONING_FAILED, receiver.get());
             //assertEquals(Lifecycle.State.DESTROYED, scenario.getState());
         }
@@ -82,12 +83,13 @@
     public void testOnCreate_FinishWithUnavailableProvisioningApp() throws Exception {
         final WrappedReceiver receiver = new WrappedReceiver();
         final int subId = SubscriptionManager.getActiveDataSubscriptionId();
+        final String[] emptyProvisioningApp = { "", "" };
         try (ActivityScenario<TetherProvisioningActivity> scenario = ActivityScenario.launch(
                 new Intent(Settings.ACTION_TETHER_PROVISIONING_UI)
                         .putExtra(EXTRA_ADD_TETHER_TYPE, TETHERING_WIFI)
                         .putExtra(EXTRA_PROVISION_CALLBACK, receiver)
-                        .putExtra(TetherProvisioningActivity.EXTRA_SUBID, subId)
-                        .putExtra(EXTRA_RUN_PROVISION, new String[] { "", "" }))) {
+                        .putExtra(EXTRA_TETHER_SUBID, subId)
+                        .putExtra(EXTRA_TETHER_UI_PROVISIONING_APP_NAME, emptyProvisioningApp))) {
             assertEquals(TetheringManager.TETHER_ERROR_PROVISIONING_FAILED, receiver.get());
             assertEquals(Lifecycle.State.DESTROYED, scenario.getState());
         }
@@ -105,8 +107,8 @@
                 new Intent(Settings.ACTION_TETHER_PROVISIONING_UI)
                         .putExtra(EXTRA_ADD_TETHER_TYPE, TETHERING_WIFI)
                         .putExtra(EXTRA_PROVISION_CALLBACK, receiver)
-                        .putExtra(TetherProvisioningActivity.EXTRA_SUBID, subId)
-                        .putExtra(EXTRA_RUN_PROVISION, provisionApp))) {
+                        .putExtra(EXTRA_TETHER_SUBID, subId)
+                        .putExtra(EXTRA_TETHER_UI_PROVISIONING_APP_NAME, provisionApp))) {
             scenario.onActivity(activity -> {
                 assertFalse(activity.isFinishing());
                 activity.onActivityResult(PROVISION_REQUEST, Activity.RESULT_OK, null /* intent */);
diff --git a/tests/unit/src/com/android/settings/wifi/tether/TetherServiceTest.java b/tests/unit/src/com/android/settings/wifi/tether/TetherServiceTest.java
index 19f29c0..be030bf 100644
--- a/tests/unit/src/com/android/settings/wifi/tether/TetherServiceTest.java
+++ b/tests/unit/src/com/android/settings/wifi/tether/TetherServiceTest.java
@@ -27,6 +27,10 @@
 import static android.net.TetheringManager.TETHER_ERROR_PROVISIONING_FAILED;
 import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
+import static com.android.settings.wifi.tether.TetherService.EXTRA_TETHER_PROVISIONING_RESPONSE;
+import static com.android.settings.wifi.tether.TetherService.EXTRA_TETHER_SILENT_PROVISIONING_ACTION;
+import static com.android.settings.wifi.tether.TetherService.EXTRA_TETHER_SUBID;
+
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.verify;
@@ -228,8 +232,10 @@
         Intent intent = new Intent();
         intent.putExtra(EXTRA_ADD_TETHER_TYPE, TETHERING_WIFI);
         intent.putExtra(EXTRA_RUN_PROVISION, true);
+        intent.putExtra(EXTRA_TETHER_SILENT_PROVISIONING_ACTION, TEST_NO_UI_ACTION);
         intent.putExtra(EXTRA_PROVISION_CALLBACK, mResultReceiver);
-        intent.putExtra(TetherService.EXTRA_SUBID, 1 /* Tested subId number */);
+        intent.putExtra(EXTRA_TETHER_SUBID, 1 /* Tested subId number */);
+        intent.putExtra(EXTRA_TETHER_PROVISIONING_RESPONSE, TEST_RESPONSE_ACTION);
         startService(intent);
 
         SystemClock.sleep(PROVISION_TIMEOUT);
@@ -242,8 +248,10 @@
         Intent intent = new Intent();
         intent.putExtra(EXTRA_ADD_TETHER_TYPE, type);
         intent.putExtra(EXTRA_RUN_PROVISION, true);
+        intent.putExtra(EXTRA_TETHER_SILENT_PROVISIONING_ACTION, TEST_NO_UI_ACTION);
         intent.putExtra(EXTRA_PROVISION_CALLBACK, mResultReceiver);
-        intent.putExtra(TetherService.EXTRA_SUBID, INVALID_SUBSCRIPTION_ID);
+        intent.putExtra(EXTRA_TETHER_SUBID, INVALID_SUBSCRIPTION_ID);
+        intent.putExtra(EXTRA_TETHER_PROVISIONING_RESPONSE, TEST_RESPONSE_ACTION);
         startService(intent);
     }