diff --git a/src/com/android/settings/connecteddevice/usb/UsbDefaultFragment.java b/src/com/android/settings/connecteddevice/usb/UsbDefaultFragment.java index 476f1051790..5057abe10e3 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 @@ import java.util.List; 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 @@ public class UsbDefaultFragment extends RadioButtonPickerFragment { @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 e30237dd37b..f74dc0f5c5f 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 @@ public class UsbDetailsFunctionsController extends UsbDetailsController } 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 @@ public class UsbDetailsFunctionsController extends UsbDetailsController @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 class UsbDetailsFunctionsController extends UsbDetailsController 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 8aec1f7af85..2c619dc4056 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.Test; 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 @@ public class UsbDefaultFragmentTest { MockitoAnnotations.initMocks(this); mFragment = new UsbDefaultFragment(); mFragment.mUsbBackend = mUsbBackend; + mFragment.mConnectivityManager = mConnectivityManager; } @Test @@ -102,12 +115,6 @@ public class UsbDefaultFragmentTest { verify(mUsbBackend).setDefaultUsbFunctions(UsbManager.FUNCTION_PTP); } - @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)); @@ -119,6 +126,39 @@ public class UsbDefaultFragmentTest { 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 888a1f3d597..679a2a9fbee 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.ArrayList; 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 @@ public class UsbDetailsFunctionsControllerTest { 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 @@ public class UsbDetailsFunctionsControllerTest { 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 class UsbDetailsFunctionsControllerTest { 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 @@ public class UsbDetailsFunctionsControllerTest { 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 prefs = getRadioPreferences(); @@ -132,7 +143,6 @@ public class UsbDetailsFunctionsControllerTest { 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 prefs = getRadioPreferences(); @@ -146,7 +156,6 @@ public class UsbDetailsFunctionsControllerTest { 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 class UsbDetailsFunctionsControllerTest { 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 class UsbDetailsFunctionsControllerTest { 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 @@ public class UsbDetailsFunctionsControllerTest { private List getRadioPreferences() { ArrayList 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); + } }