Merge "Add entitlement check for usb tethering"
diff --git a/src/com/android/settings/connecteddevice/usb/UsbDefaultFragment.java b/src/com/android/settings/connecteddevice/usb/UsbDefaultFragment.java
index 476f105..5057abe 100644
--- a/src/com/android/settings/connecteddevice/usb/UsbDefaultFragment.java
+++ b/src/com/android/settings/connecteddevice/usb/UsbDefaultFragment.java
@@ -16,9 +16,14 @@
 
 package com.android.settings.connecteddevice.usb;
 
+import static android.net.ConnectivityManager.TETHERING_USB;
+
 import android.content.Context;
 import android.graphics.drawable.Drawable;
+import android.hardware.usb.UsbManager;
+import android.net.ConnectivityManager;
 import android.os.Bundle;
+import android.util.Log;
 
 import androidx.annotation.VisibleForTesting;
 
@@ -40,11 +45,18 @@
 public class UsbDefaultFragment extends RadioButtonPickerFragment {
     @VisibleForTesting
     UsbBackend mUsbBackend;
+    @VisibleForTesting
+    ConnectivityManager mConnectivityManager;
+    @VisibleForTesting
+    OnStartTetheringCallback mOnStartTetheringCallback = new OnStartTetheringCallback();
+    @VisibleForTesting
+    long mPreviousFunctions;
 
     @Override
     public void onAttach(Context context) {
         super.onAttach(context);
         mUsbBackend = new UsbBackend(context);
+        mConnectivityManager = context.getSystemService(ConnectivityManager.class);
     }
 
     @Override
@@ -105,9 +117,37 @@
     @Override
     protected boolean setDefaultKey(String key) {
         long functions = UsbBackend.usbFunctionsFromString(key);
+        mPreviousFunctions = mUsbBackend.getCurrentFunctions();
         if (!Utils.isMonkeyRunning()) {
-            mUsbBackend.setDefaultUsbFunctions(functions);
+            if (functions == UsbManager.FUNCTION_RNDIS) {
+                // We need to have entitlement check for usb tethering, so use API in
+                // ConnectivityManager.
+                mConnectivityManager.startTethering(TETHERING_USB, true /* showProvisioningUi */,
+                        mOnStartTetheringCallback);
+            } else {
+                mUsbBackend.setDefaultUsbFunctions(functions);
+            }
+
         }
         return true;
     }
+
+    @VisibleForTesting
+    final class OnStartTetheringCallback extends
+            ConnectivityManager.OnStartTetheringCallback {
+
+        @Override
+        public void onTetheringStarted() {
+            super.onTetheringStarted();
+            // Set default usb functions again to make internal data persistent
+            mUsbBackend.setDefaultUsbFunctions(UsbManager.FUNCTION_RNDIS);
+        }
+
+        @Override
+        public void onTetheringFailed() {
+            super.onTetheringFailed();
+            mUsbBackend.setDefaultUsbFunctions(mPreviousFunctions);
+            updateCandidates();
+        }
+    }
 }
\ No newline at end of file
diff --git a/src/com/android/settings/connecteddevice/usb/UsbDetailsFunctionsController.java b/src/com/android/settings/connecteddevice/usb/UsbDetailsFunctionsController.java
index e30237d..f74dc0f 100644
--- a/src/com/android/settings/connecteddevice/usb/UsbDetailsFunctionsController.java
+++ b/src/com/android/settings/connecteddevice/usb/UsbDetailsFunctionsController.java
@@ -16,10 +16,14 @@
 
 package com.android.settings.connecteddevice.usb;
 
+import static android.net.ConnectivityManager.TETHERING_USB;
+
 import android.content.Context;
 import android.hardware.usb.UsbManager;
 import android.hardware.usb.UsbPort;
+import android.net.ConnectivityManager;
 
+import androidx.annotation.VisibleForTesting;
 import androidx.preference.PreferenceCategory;
 import androidx.preference.PreferenceScreen;
 
@@ -47,10 +51,18 @@
     }
 
     private PreferenceCategory mProfilesContainer;
+    private ConnectivityManager mConnectivityManager;
+    @VisibleForTesting
+    OnStartTetheringCallback mOnStartTetheringCallback;
+    @VisibleForTesting
+    long mPreviousFunction;
 
     public UsbDetailsFunctionsController(Context context, UsbDetailsFragment fragment,
             UsbBackend backend) {
         super(context, fragment, backend);
+        mConnectivityManager = context.getSystemService(ConnectivityManager.class);
+        mOnStartTetheringCallback = new OnStartTetheringCallback();
+        mPreviousFunction = mUsbBackend.getCurrentFunctions();
     }
 
     @Override
@@ -97,9 +109,28 @@
 
     @Override
     public void onRadioButtonClicked(RadioButtonPreference preference) {
-        long function = UsbBackend.usbFunctionsFromString(preference.getKey());
-        if (function != mUsbBackend.getCurrentFunctions() && !Utils.isMonkeyRunning()) {
-            mUsbBackend.setCurrentFunctions(function);
+        final long function = UsbBackend.usbFunctionsFromString(preference.getKey());
+        final long previousFunction = mUsbBackend.getCurrentFunctions();
+        if (function != previousFunction && !Utils.isMonkeyRunning()) {
+            mPreviousFunction = previousFunction;
+
+            if (function == UsbManager.FUNCTION_RNDIS) {
+                //Update the UI in advance to make it looks smooth
+                final RadioButtonPreference prevPref =
+                        (RadioButtonPreference) mProfilesContainer.findPreference(
+                                UsbBackend.usbFunctionsToString(mPreviousFunction));
+                if (prevPref != null) {
+                    prevPref.setChecked(false);
+                    preference.setChecked(true);
+                }
+
+                // We need to have entitlement check for usb tethering, so use API in
+                // ConnectivityManager.
+                mConnectivityManager.startTethering(TETHERING_USB, true /* showProvisioningUi */,
+                        mOnStartTetheringCallback);
+            } else {
+                mUsbBackend.setCurrentFunctions(function);
+            }
         }
     }
 
@@ -112,4 +143,15 @@
     public String getPreferenceKey() {
         return "usb_details_functions";
     }
+
+    @VisibleForTesting
+    final class OnStartTetheringCallback extends
+            ConnectivityManager.OnStartTetheringCallback {
+
+        @Override
+        public void onTetheringFailed() {
+            super.onTetheringFailed();
+            mUsbBackend.setCurrentFunctions(mPreviousFunction);
+        }
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbDefaultFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbDefaultFragmentTest.java
index 8aec1f7..2c619dc 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbDefaultFragmentTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbDefaultFragmentTest.java
@@ -16,13 +16,21 @@
 
 package com.android.settings.connecteddevice.usb;
 
+import static android.net.ConnectivityManager.TETHERING_USB;
+
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 import android.hardware.usb.UsbManager;
+import android.net.ConnectivityManager;
 
 import com.android.settings.testutils.SettingsRobolectricTestRunner;
 import com.android.settings.testutils.shadow.ShadowUtils;
@@ -32,13 +40,17 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.robolectric.Robolectric;
 import org.robolectric.annotation.Config;
+import org.robolectric.util.FragmentTestUtil;
 
 @RunWith(SettingsRobolectricTestRunner.class)
 public class UsbDefaultFragmentTest {
 
     @Mock
     private UsbBackend mUsbBackend;
+    @Mock
+    private ConnectivityManager mConnectivityManager;
 
     private UsbDefaultFragment mFragment;
 
@@ -47,6 +59,7 @@
         MockitoAnnotations.initMocks(this);
         mFragment = new UsbDefaultFragment();
         mFragment.mUsbBackend = mUsbBackend;
+        mFragment.mConnectivityManager = mConnectivityManager;
     }
 
     @Test
@@ -103,12 +116,6 @@
     }
 
     @Test
-    public void setDefaultKey_isRndis_shouldSetRndis() {
-        mFragment.setDefaultKey(UsbBackend.usbFunctionsToString(UsbManager.FUNCTION_RNDIS));
-        verify(mUsbBackend).setDefaultUsbFunctions(UsbManager.FUNCTION_RNDIS);
-    }
-
-    @Test
     public void setDefaultKey_isMidi_shouldSetMidi() {
         mFragment.setDefaultKey(UsbBackend.usbFunctionsToString(UsbManager.FUNCTION_MIDI));
         verify(mUsbBackend).setDefaultUsbFunctions(UsbManager.FUNCTION_MIDI);
@@ -119,6 +126,39 @@
     public void setDefaultKey_isMonkey_shouldDoNothing() {
         ShadowUtils.setIsUserAMonkey(true);
         mFragment.setDefaultKey(UsbBackend.usbFunctionsToString(UsbManager.FUNCTION_MTP));
-        verifyZeroInteractions(mUsbBackend);
+
+        verify(mUsbBackend, never()).setDefaultUsbFunctions(anyLong());
+    }
+
+    @Test
+    public void setDefaultKey_functionRndis_startTetheringInvoked() {
+        doReturn(UsbManager.FUNCTION_MTP).when(mUsbBackend).getCurrentFunctions();
+
+        mFragment.setDefaultKey(UsbBackend.usbFunctionsToString(UsbManager.FUNCTION_RNDIS));
+
+        verify(mConnectivityManager).startTethering(TETHERING_USB, true,
+                mFragment.mOnStartTetheringCallback);
+        assertThat(mFragment.mPreviousFunctions).isEqualTo(
+                UsbManager.FUNCTION_MTP);
+    }
+
+    @Test
+    public void setDefaultKey_functionOther_setCurrentFunctionInvoked() {
+        doReturn(UsbManager.FUNCTION_MTP).when(mUsbBackend).getCurrentFunctions();
+
+        mFragment.setDefaultKey(UsbBackend.usbFunctionsToString(UsbManager.FUNCTION_PTP));
+
+        verify(mUsbBackend).setDefaultUsbFunctions(UsbManager.FUNCTION_PTP);
+        assertThat(mFragment.mPreviousFunctions).isEqualTo(
+                UsbManager.FUNCTION_MTP);
+    }
+
+    @Test
+    public void onTetheringStarted_setDefaultUsbFunctions() {
+        mFragment.mPreviousFunctions = UsbManager.FUNCTION_PTP;
+
+        mFragment.mOnStartTetheringCallback.onTetheringStarted();
+
+        verify(mUsbBackend).setDefaultUsbFunctions(UsbManager.FUNCTION_RNDIS);
     }
 }
diff --git a/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbDetailsFunctionsControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbDetailsFunctionsControllerTest.java
index 888a1f3..679a2a9 100644
--- a/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbDetailsFunctionsControllerTest.java
+++ b/tests/robotests/src/com/android/settings/connecteddevice/usb/UsbDetailsFunctionsControllerTest.java
@@ -16,20 +16,21 @@
 
 package com.android.settings.connecteddevice.usb;
 
+import static android.net.ConnectivityManager.TETHERING_USB;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
 import android.hardware.usb.UsbManager;
 import android.hardware.usb.UsbPort;
-
-import androidx.fragment.app.FragmentActivity;
-import androidx.preference.PreferenceCategory;
-import androidx.preference.PreferenceManager;
-import androidx.preference.PreferenceScreen;
+import android.net.ConnectivityManager;
 
 import com.android.settings.testutils.SettingsRobolectricTestRunner;
 import com.android.settings.testutils.shadow.ShadowUtils;
@@ -48,15 +49,21 @@
 import java.util.Iterator;
 import java.util.List;
 
+import androidx.fragment.app.FragmentActivity;
+import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceScreen;
+
 @RunWith(SettingsRobolectricTestRunner.class)
 public class UsbDetailsFunctionsControllerTest {
 
     private UsbDetailsFunctionsController mDetailsFunctionsController;
     private Context mContext;
     private Lifecycle mLifecycle;
-    private PreferenceCategory mPreference;
+    private PreferenceCategory mPreferenceCategory;
     private PreferenceManager mPreferenceManager;
     private PreferenceScreen mScreen;
+    private RadioButtonPreference mRadioButtonPreference;
 
     @Mock
     private UsbBackend mUsbBackend;
@@ -64,12 +71,14 @@
     private UsbDetailsFragment mFragment;
     @Mock
     private FragmentActivity mActivity;
+    @Mock
+    private ConnectivityManager mConnectivityManager;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        mContext = RuntimeEnvironment.application;
+        mContext = spy(RuntimeEnvironment.application);
         mLifecycle = new Lifecycle(() -> mLifecycle);
         mPreferenceManager = new PreferenceManager(mContext);
         mScreen = mPreferenceManager.createPreferenceScreen(mContext);
@@ -79,12 +88,16 @@
         when(mFragment.getContext()).thenReturn(mContext);
         when(mFragment.getPreferenceManager()).thenReturn(mPreferenceManager);
         when(mFragment.getPreferenceScreen()).thenReturn(mScreen);
+        when(mContext.getSystemService(ConnectivityManager.class)).thenReturn(mConnectivityManager);
 
         mDetailsFunctionsController = new UsbDetailsFunctionsController(mContext, mFragment,
                 mUsbBackend);
-        mPreference = new PreferenceCategory(mContext);
-        mPreference.setKey(mDetailsFunctionsController.getPreferenceKey());
-        mScreen.addPreference(mPreference);
+        mPreferenceCategory = new PreferenceCategory(mContext);
+        mPreferenceCategory.setKey(mDetailsFunctionsController.getPreferenceKey());
+        mScreen.addPreference(mPreferenceCategory);
+        mDetailsFunctionsController.displayPreference(mScreen);
+
+        mRadioButtonPreference = new RadioButtonPreference(mContext);
     }
 
     @Test
@@ -106,10 +119,9 @@
     public void displayRefresh_disconnected_shouldDisable() {
         when(mUsbBackend.areFunctionsSupported(anyLong())).thenReturn(true);
 
-        mDetailsFunctionsController.displayPreference(mScreen);
         mDetailsFunctionsController.refresh(false, UsbManager.FUNCTION_NONE,
                 UsbPort.POWER_ROLE_SINK, UsbPort.DATA_ROLE_DEVICE);
-        assertThat(mPreference.isEnabled()).isFalse();
+        assertThat(mPreferenceCategory.isEnabled()).isFalse();
     }
 
     @Test
@@ -119,7 +131,6 @@
         when(mUsbBackend.areFunctionsSupported(UsbManager.FUNCTION_PTP)).thenReturn(false);
         when(mUsbBackend.areFunctionsSupported(UsbManager.FUNCTION_RNDIS)).thenReturn(false);
 
-        mDetailsFunctionsController.displayPreference(mScreen);
         mDetailsFunctionsController.refresh(true, UsbManager.FUNCTION_NONE, UsbPort.POWER_ROLE_SINK,
                 UsbPort.DATA_ROLE_DEVICE);
         List<RadioButtonPreference> prefs = getRadioPreferences();
@@ -132,7 +143,6 @@
     public void displayRefresh_mtpEnabled_shouldCheckSwitches() {
         when(mUsbBackend.areFunctionsSupported(anyLong())).thenReturn(true);
 
-        mDetailsFunctionsController.displayPreference(mScreen);
         mDetailsFunctionsController.refresh(true, UsbManager.FUNCTION_MTP, UsbPort.POWER_ROLE_SINK,
                 UsbPort.DATA_ROLE_DEVICE);
         List<RadioButtonPreference> prefs = getRadioPreferences();
@@ -146,7 +156,6 @@
     public void onClickMtp_noneEnabled_shouldEnableMtp() {
         when(mUsbBackend.areFunctionsSupported(anyLong())).thenReturn(true);
 
-        mDetailsFunctionsController.displayPreference(mScreen);
         mDetailsFunctionsController.refresh(true, UsbManager.FUNCTION_NONE, UsbPort.POWER_ROLE_SINK,
                 UsbPort.DATA_ROLE_DEVICE);
         when(mUsbBackend.getCurrentFunctions()).thenReturn(UsbManager.FUNCTION_NONE);
@@ -165,7 +174,6 @@
     public void onClickMtp_ptpEnabled_shouldEnableMtp() {
         when(mUsbBackend.areFunctionsSupported(anyLong())).thenReturn(true);
 
-        mDetailsFunctionsController.displayPreference(mScreen);
         mDetailsFunctionsController.refresh(true, UsbManager.FUNCTION_PTP, UsbPort.POWER_ROLE_SINK,
                 UsbPort.DATA_ROLE_DEVICE);
         when(mUsbBackend.getCurrentFunctions()).thenReturn(UsbManager.FUNCTION_PTP);
@@ -187,7 +195,6 @@
     public void onClickNone_mtpEnabled_shouldDisableMtp() {
         when(mUsbBackend.areFunctionsSupported(anyLong())).thenReturn(true);
 
-        mDetailsFunctionsController.displayPreference(mScreen);
         mDetailsFunctionsController.refresh(true, UsbManager.FUNCTION_MTP, UsbPort.POWER_ROLE_SINK,
                 UsbPort.DATA_ROLE_DEVICE);
         when(mUsbBackend.getCurrentFunctions()).thenReturn(UsbManager.FUNCTION_MTP);
@@ -211,9 +218,55 @@
 
     private List<RadioButtonPreference> getRadioPreferences() {
         ArrayList<RadioButtonPreference> result = new ArrayList<>();
-        for (int i = 0; i < mPreference.getPreferenceCount(); i++) {
-            result.add((RadioButtonPreference) mPreference.getPreference(i));
+        for (int i = 0; i < mPreferenceCategory.getPreferenceCount(); i++) {
+            result.add((RadioButtonPreference) mPreferenceCategory.getPreference(i));
         }
         return result;
     }
+
+    @Test
+    public void onRadioButtonClicked_functionRndis_startTetheringInvoked() {
+        mRadioButtonPreference.setKey(UsbBackend.usbFunctionsToString(UsbManager.FUNCTION_RNDIS));
+        doReturn(UsbManager.FUNCTION_MTP).when(mUsbBackend).getCurrentFunctions();
+
+        mDetailsFunctionsController.onRadioButtonClicked(mRadioButtonPreference);
+
+        verify(mConnectivityManager).startTethering(TETHERING_USB, true,
+                mDetailsFunctionsController.mOnStartTetheringCallback);
+        assertThat(mDetailsFunctionsController.mPreviousFunction).isEqualTo(
+                UsbManager.FUNCTION_MTP);
+    }
+
+    @Test
+    public void onRadioButtonClicked_functionOther_setCurrentFunctionInvoked() {
+        mRadioButtonPreference.setKey(UsbBackend.usbFunctionsToString(UsbManager.FUNCTION_PTP));
+        doReturn(UsbManager.FUNCTION_MTP).when(mUsbBackend).getCurrentFunctions();
+
+        mDetailsFunctionsController.onRadioButtonClicked(mRadioButtonPreference);
+
+        verify(mUsbBackend).setCurrentFunctions(UsbManager.FUNCTION_PTP);
+        assertThat(mDetailsFunctionsController.mPreviousFunction).isEqualTo(
+                UsbManager.FUNCTION_MTP);
+    }
+
+    @Test
+    public void onRadioButtonClicked_clickSameButton_doNothing() {
+        mRadioButtonPreference.setKey(UsbBackend.usbFunctionsToString(UsbManager.FUNCTION_PTP));
+        doReturn(UsbManager.FUNCTION_PTP).when(mUsbBackend).getCurrentFunctions();
+
+        mDetailsFunctionsController.onRadioButtonClicked(mRadioButtonPreference);
+
+        verify(mUsbBackend, never()).setCurrentFunctions(UsbManager.FUNCTION_PTP);
+        verify(mConnectivityManager, never()).startTethering(TETHERING_USB, true,
+                mDetailsFunctionsController.mOnStartTetheringCallback);
+    }
+
+    @Test
+    public void onTetheringFailed_resetPreviousFunctions() {
+        mDetailsFunctionsController.mPreviousFunction = UsbManager.FUNCTION_PTP;
+
+        mDetailsFunctionsController.mOnStartTetheringCallback.onTetheringFailed();
+
+        verify(mUsbBackend).setCurrentFunctions(UsbManager.FUNCTION_PTP);
+    }
 }