Merge "Fix "USB tethering" will auto turn on then off when turn off "USB tethering"" into sc-dev
diff --git a/src/com/android/settings/TetherSettings.java b/src/com/android/settings/TetherSettings.java
index 6d98f36..ab1c437 100644
--- a/src/com/android/settings/TetherSettings.java
+++ b/src/com/android/settings/TetherSettings.java
@@ -45,6 +45,7 @@
 import android.provider.SearchIndexableResource;
 import android.text.TextUtils;
 import android.util.FeatureFlagUtils;
+import android.util.Log;
 
 import androidx.annotation.VisibleForTesting;
 import androidx.preference.Preference;
@@ -85,6 +86,7 @@
     static final String KEY_TETHER_PREFS_TOP_INTRO = "tether_prefs_top_intro";
 
     private static final String TAG = "TetheringSettings";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private RestrictedSwitchPreference mUsbTether;
 
@@ -94,7 +96,6 @@
 
     private BroadcastReceiver mTetherChangeReceiver;
 
-    private String[] mUsbRegexs;
     private String[] mBluetoothRegexs;
     private String mEthernetRegex;
     private AtomicReference<BluetoothPan> mBluetoothPan = new AtomicReference<>();
@@ -103,7 +104,6 @@
     private OnStartTetheringCallback mStartTetheringCallback;
     private ConnectivityManager mCm;
     private EthernetManager mEm;
-    private TetheringManager mTm;
     private TetheringEventCallback mTetheringEventCallback;
     private EthernetListener mEthernetListener;
 
@@ -119,6 +119,13 @@
     private boolean mDataSaverEnabled;
     private Preference mDataSaverFooter;
 
+    @VisibleForTesting
+    String[] mUsbRegexs;
+    @VisibleForTesting
+    Context mContext;
+    @VisibleForTesting
+    TetheringManager mTm;
+
     @Override
     public int getMetricsCategory() {
         return SettingsEnums.TETHER;
@@ -140,7 +147,8 @@
         super.onCreate(icicle);
 
         addPreferencesFromResource(R.xml.tether_prefs);
-        mDataSaverBackend = new DataSaverBackend(getContext());
+        mContext = getContext();
+        mDataSaverBackend = new DataSaverBackend(mContext);
         mDataSaverEnabled = mDataSaverBackend.isDataSaverEnabled();
         mDataSaverFooter = findPreference(KEY_DATA_SAVER_FOOTER);
 
@@ -169,7 +177,7 @@
 
         mUsbRegexs = mTm.getTetherableUsbRegexs();
         mBluetoothRegexs = mTm.getTetherableBluetoothRegexs();
-        mEthernetRegex = getContext().getResources().getString(
+        mEthernetRegex = mContext.getResources().getString(
                 com.android.internal.R.string.config_ethernet_iface_regex);
 
         final boolean usbAvailable = mUsbRegexs.length != 0;
@@ -237,8 +245,7 @@
     @VisibleForTesting
     void setTopIntroPreferenceTitle() {
         final Preference topIntroPreference = findPreference(KEY_TETHER_PREFS_TOP_INTRO);
-        final WifiManager wifiManager =
-                (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
+        final WifiManager wifiManager = mContext.getSystemService(WifiManager.class);
         if (wifiManager.isStaApConcurrencySupported()) {
             topIntroPreference.setTitle(R.string.tethering_footer_info_sta_ap_concurrency);
         } else {
@@ -250,27 +257,32 @@
         @Override
         public void onReceive(Context content, Intent intent) {
             String action = intent.getAction();
-            // TODO: stop using ACTION_TETHER_STATE_CHANGED and use mTetheringEventCallback instead.
+            if (DEBUG) {
+                Log.d(TAG, "onReceive() action : " + action);
+            }
+            // TODO(b/194961339): Stop using ACTION_TETHER_STATE_CHANGED and use
+            //  mTetheringEventCallback instead.
             if (action.equals(TetheringManager.ACTION_TETHER_STATE_CHANGED)) {
                 // TODO - this should understand the interface types
                 ArrayList<String> available = intent.getStringArrayListExtra(
                         TetheringManager.EXTRA_AVAILABLE_TETHER);
                 ArrayList<String> active = intent.getStringArrayListExtra(
                         TetheringManager.EXTRA_ACTIVE_TETHER);
-                ArrayList<String> errored = intent.getStringArrayListExtra(
-                        TetheringManager.EXTRA_ERRORED_TETHER);
-                updateState(available.toArray(new String[available.size()]),
-                        active.toArray(new String[active.size()]),
-                        errored.toArray(new String[errored.size()]));
+                updateBluetoothState();
+                updateEthernetState(available.toArray(new String[available.size()]),
+                        active.toArray(new String[active.size()]));
             } else if (action.equals(Intent.ACTION_MEDIA_SHARED)) {
                 mMassStorageActive = true;
-                updateState();
+                updateBluetoothAndEthernetState();
+                updateUsbPreference();
             } else if (action.equals(Intent.ACTION_MEDIA_UNSHARED)) {
                 mMassStorageActive = false;
-                updateState();
+                updateBluetoothAndEthernetState();
+                updateUsbPreference();
             } else if (action.equals(UsbManager.ACTION_USB_STATE)) {
                 mUsbConnected = intent.getBooleanExtra(UsbManager.USB_CONNECTED, false);
-                updateState();
+                updateBluetoothAndEthernetState();
+                updateUsbPreference();
             } else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
                 if (mBluetoothEnableForTether) {
                     switch (intent
@@ -289,9 +301,9 @@
                             // ignore transition states
                     }
                 }
-                updateState();
+                updateBluetoothAndEthernetState();
             } else if (action.equals(BluetoothPan.ACTION_TETHERING_STATE_CHANGED)) {
-                updateState();
+                updateBluetoothAndEthernetState();
             }
         }
     }
@@ -320,7 +332,8 @@
         if (mEm != null)
             mEm.addListener(mEthernetListener);
 
-        updateState();
+        updateUsbState();
+        updateBluetoothAndEthernetState();
     }
 
     @Override
@@ -366,60 +379,60 @@
         if (intent != null) mTetherChangeReceiver.onReceive(activity, intent);
     }
 
-    private void updateState() {
-        final TetheringManager tm = getContext().getSystemService(TetheringManager.class);
-        final String[] available = tm.getTetherableIfaces();
-        final String[] tethered = tm.getTetheredIfaces();
-        final String[] errored = tm.getTetheringErroredIfaces();
-        updateState(available, tethered, errored);
+    // TODO(b/194961339): Separate the updateBluetoothAndEthernetState() to two methods,
+    //  updateBluetoothAndEthernetState() and updateBluetoothAndEthernetPreference().
+    //  Because we should update the state when only receiving tethering
+    //  state changes and update preference when usb or media share changed.
+    private void updateBluetoothAndEthernetState() {
+        String[] tethered = mTm.getTetheredIfaces();
+        updateBluetoothAndEthernetState(tethered);
     }
 
-    private void updateState(String[] available, String[] tethered,
-            String[] errored) {
-        updateUsbState(available, tethered, errored);
+    private void updateBluetoothAndEthernetState(String[] tethered) {
+        String[] available = mTm.getTetherableIfaces();
         updateBluetoothState();
         updateEthernetState(available, tethered);
     }
 
+    private void updateUsbState() {
+        String[] tethered = mTm.getTetheredIfaces();
+        updateUsbState(tethered);
+    }
+
     @VisibleForTesting
-    void updateUsbState(String[] available, String[] tethered,
-            String[] errored) {
-        boolean usbAvailable = mUsbConnected && !mMassStorageActive;
-        int usbError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
-        for (String s : available) {
-            for (String regex : mUsbRegexs) {
-                if (s.matches(regex)) {
-                    if (usbError == ConnectivityManager.TETHER_ERROR_NO_ERROR) {
-                        usbError = mTm.getLastTetherError(s);
-                    }
-                }
-            }
-        }
+    void updateUsbState(String[] tethered) {
         boolean usbTethered = false;
         for (String s : tethered) {
             for (String regex : mUsbRegexs) {
                 if (s.matches(regex)) usbTethered = true;
             }
         }
-        boolean usbErrored = false;
-        for (String s: errored) {
-            for (String regex : mUsbRegexs) {
-                if (s.matches(regex)) usbErrored = true;
-            }
+        if (DEBUG) {
+            Log.d(TAG, "updateUsbState() mUsbConnected : " + mUsbConnected
+                    + ", mMassStorageActive : " + mMassStorageActive
+                    + ", usbTethered : " + usbTethered);
         }
-
         if (usbTethered) {
             mUsbTether.setEnabled(!mDataSaverEnabled);
             mUsbTether.setChecked(true);
-        } else if (usbAvailable) {
-            mUsbTether.setEnabled(!mDataSaverEnabled);
+            mUsbTether.setDisabledByAdmin(
+                    checkIfUsbDataSignalingIsDisabled(mContext, UserHandle.myUserId()));
+        } else {
             mUsbTether.setChecked(false);
+            updateUsbPreference();
+        }
+    }
+
+    private void updateUsbPreference() {
+        boolean usbAvailable = mUsbConnected && !mMassStorageActive;
+
+        if (usbAvailable) {
+            mUsbTether.setEnabled(!mDataSaverEnabled);
         } else {
             mUsbTether.setEnabled(false);
-            mUsbTether.setChecked(false);
         }
         mUsbTether.setDisabledByAdmin(
-                checkIfUsbDataSignalingIsDisabled(getContext(), UserHandle.myUserId()));
+                checkIfUsbDataSignalingIsDisabled(mContext, UserHandle.myUserId()));
     }
 
     @VisibleForTesting
@@ -439,7 +452,11 @@
 
     private void updateBluetoothState() {
         final int btState = getBluetoothState();
+        if (DEBUG) {
+            Log.d(TAG, "updateBluetoothState() btState : " + btState);
+        }
         if (btState == BluetoothAdapter.ERROR) {
+            Log.w(TAG, "updateBluetoothState() Bluetooth state is error!");
             return;
         }
 
@@ -460,7 +477,6 @@
 
     @VisibleForTesting
     void updateEthernetState(String[] available, String[] tethered) {
-
         boolean isAvailable = false;
         boolean isTethered = false;
 
@@ -472,6 +488,11 @@
             if (s.matches(mEthernetRegex)) isTethered = true;
         }
 
+        if (DEBUG) {
+            Log.d(TAG, "updateEthernetState() isAvailable : " + isAvailable
+                    + ", isTethered : " + isTethered);
+        }
+
         if (isTethered) {
             mEthernetTether.setEnabled(!mDataSaverEnabled);
             mEthernetTether.setChecked(true);
@@ -608,7 +629,7 @@
         private void update() {
             TetherSettings settings = mTetherSettings.get();
             if (settings != null) {
-                settings.updateState();
+                settings.updateBluetoothAndEthernetState();
             }
         }
     }
@@ -616,13 +637,16 @@
     private final class TetheringEventCallback implements TetheringManager.TetheringEventCallback {
         @Override
         public void onTetheredInterfacesChanged(List<String> interfaces) {
-            updateState();
+            Log.d(TAG, "onTetheredInterfacesChanged() interfaces : " + interfaces.toString());
+            String[] tethered = interfaces.toArray(new String[interfaces.size()]);
+            updateUsbState(tethered);
+            updateBluetoothAndEthernetState(tethered);
         }
     }
 
     private final class EthernetListener implements EthernetManager.Listener {
         public void onAvailabilityChanged(String iface, boolean isAvailable) {
-            mHandler.post(TetherSettings.this::updateState);
+            mHandler.post(() -> updateBluetoothAndEthernetState());
         }
     }
 }
diff --git a/tests/robotests/src/com/android/settings/TetherSettingsTest.java b/tests/robotests/src/com/android/settings/TetherSettingsTest.java
index 0948cfa..71cb9d2 100644
--- a/tests/robotests/src/com/android/settings/TetherSettingsTest.java
+++ b/tests/robotests/src/com/android/settings/TetherSettingsTest.java
@@ -16,14 +16,20 @@
 
 package com.android.settings;
 
+import static android.content.Intent.ACTION_MEDIA_SHARED;
+import static android.content.Intent.ACTION_MEDIA_UNSHARED;
+import static android.hardware.usb.UsbManager.ACTION_USB_STATE;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -33,6 +39,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.hardware.usb.UsbManager;
 import android.net.ConnectivityManager;
 import android.net.TetheringManager;
 import android.net.wifi.WifiManager;
@@ -45,6 +52,7 @@
 import androidx.preference.SwitchPreference;
 
 import com.android.settings.core.FeatureFlags;
+import com.android.settingslib.RestrictedSwitchPreference;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -71,7 +79,7 @@
     private TetheringManager mTetheringManager;
 
     @Before
-    public void setUp() {
+    public void setUp() throws Exception {
         mContext = spy(RuntimeEnvironment.application);
 
         MockitoAnnotations.initMocks(this);
@@ -81,6 +89,8 @@
                 .when(mContext).getSystemService(Context.USER_SERVICE);
         doReturn(mTetheringManager)
                 .when(mContext).getSystemService(Context.TETHERING_SERVICE);
+        doReturn(mContext).when(mContext).createPackageContextAsUser(
+                any(String.class), anyInt(), any(UserHandle.class));
 
         setupIsTetherAvailable(true);
 
@@ -161,7 +171,7 @@
     @Test
     public void testSetFooterPreferenceTitle_isStaApConcurrencySupported_showStaApString() {
         final TetherSettings spyTetherSettings = spy(new TetherSettings());
-        when(spyTetherSettings.getContext()).thenReturn(mContext);
+        spyTetherSettings.mContext = mContext;
         final Preference mockPreference = mock(Preference.class);
         when(spyTetherSettings.findPreference(TetherSettings.KEY_TETHER_PREFS_TOP_INTRO))
             .thenReturn(mockPreference);
@@ -178,7 +188,8 @@
     @Test
     public void testBluetoothState_updateBluetoothState_bluetoothTetheringStateOn() {
         final TetherSettings spyTetherSettings = spy(new TetherSettings());
-        when(spyTetherSettings.getContext()).thenReturn(mContext);
+        spyTetherSettings.mContext = mContext;
+        spyTetherSettings.mTm = mTetheringManager;
         final SwitchPreference mockSwitchPreference = mock(SwitchPreference.class);
         when(spyTetherSettings.findPreference(TetherSettings.KEY_ENABLE_BLUETOOTH_TETHERING))
             .thenReturn(mockSwitchPreference);
@@ -210,7 +221,8 @@
     @Test
     public void testBluetoothState_updateBluetoothState_bluetoothTetheringStateOff() {
         final TetherSettings spyTetherSettings = spy(new TetherSettings());
-        when(spyTetherSettings.getContext()).thenReturn(mContext);
+        spyTetherSettings.mContext = mContext;
+        spyTetherSettings.mTm = mTetheringManager;
         final SwitchPreference mockSwitchPreference = mock(SwitchPreference.class);
         when(spyTetherSettings.findPreference(TetherSettings.KEY_ENABLE_BLUETOOTH_TETHERING))
             .thenReturn(mockSwitchPreference);
@@ -239,14 +251,110 @@
         verify(mockSwitchPreference).setChecked(false);
     }
 
+    @Test
+    public void updateState_usbTetheringIsEnabled_checksUsbTethering() {
+        String [] tethered = {"rndis0"};
+        TetherSettings spyTetherSettings = spy(new TetherSettings());
+        RestrictedSwitchPreference tetheringPreference = mock(RestrictedSwitchPreference.class);
+        when(spyTetherSettings.findPreference(TetherSettings.KEY_USB_TETHER_SETTINGS))
+                .thenReturn(tetheringPreference);
+        spyTetherSettings.mContext = mContext;
+        spyTetherSettings.mTm = mTetheringManager;
+        spyTetherSettings.setupTetherPreference();
+        spyTetherSettings.mUsbRegexs = tethered;
+
+        spyTetherSettings.updateUsbState(tethered);
+
+        verify(tetheringPreference).setEnabled(true);
+        verify(tetheringPreference).setChecked(true);
+    }
+
+    @Test
+    public void updateState_usbTetheringIsDisabled_unchecksUsbTethering() {
+        String [] tethered = {"rndis0"};
+        TetherSettings spyTetherSettings = spy(new TetherSettings());
+        RestrictedSwitchPreference tetheringPreference = mock(RestrictedSwitchPreference.class);
+        when(spyTetherSettings.findPreference(TetherSettings.KEY_USB_TETHER_SETTINGS))
+                .thenReturn(tetheringPreference);
+        spyTetherSettings.mContext = mContext;
+        spyTetherSettings.mTm = mTetheringManager;
+        spyTetherSettings.setupTetherPreference();
+        spyTetherSettings.mUsbRegexs = tethered;
+
+        spyTetherSettings.updateUsbState(new String[0]);
+
+        verify(tetheringPreference).setEnabled(false);
+        verify(tetheringPreference).setChecked(false);
+    }
+
+    @Test
+    public void onReceive_usbIsConnected_tetheringPreferenceIsEnabled() {
+        RestrictedSwitchPreference tetheringPreference = mock(RestrictedSwitchPreference.class);
+        FragmentActivity mockActivity = mock(FragmentActivity.class);
+        ArgumentCaptor<BroadcastReceiver> captor = ArgumentCaptor.forClass(BroadcastReceiver.class);
+        setupUsbStateComponents(tetheringPreference, captor, mockActivity);
+
+        BroadcastReceiver receiver = captor.getValue();
+        Intent usbStateChanged = new Intent(ACTION_USB_STATE);
+        usbStateChanged.putExtra(UsbManager.USB_CONNECTED, true);
+        receiver.onReceive(mockActivity, usbStateChanged);
+
+        verify(tetheringPreference).setEnabled(true);
+    }
+
+    @Test
+    public void onReceive_usbIsDisconnected_tetheringPreferenceIsDisabled() {
+        RestrictedSwitchPreference tetheringPreference = mock(RestrictedSwitchPreference.class);
+        FragmentActivity mockActivity = mock(FragmentActivity.class);
+        ArgumentCaptor<BroadcastReceiver> captor = ArgumentCaptor.forClass(BroadcastReceiver.class);
+        setupUsbStateComponents(tetheringPreference, captor, mockActivity);
+
+        BroadcastReceiver receiver = captor.getValue();
+        Intent usbStateChanged = new Intent(ACTION_USB_STATE);
+        usbStateChanged.putExtra(UsbManager.USB_CONNECTED, false);
+        receiver.onReceive(mockActivity, usbStateChanged);
+
+        verify(tetheringPreference).setEnabled(false);
+    }
+
+    @Test
+    public void onReceive_mediaIsShared_tetheringPreferenceIsDisabled() {
+        RestrictedSwitchPreference tetheringPreference = mock(RestrictedSwitchPreference.class);
+        FragmentActivity mockActivity = mock(FragmentActivity.class);
+        ArgumentCaptor<BroadcastReceiver> captor = ArgumentCaptor.forClass(BroadcastReceiver.class);
+        setupUsbStateComponents(tetheringPreference, captor, mockActivity);
+
+        BroadcastReceiver receiver = captor.getValue();
+        Intent mediaIsShared = new Intent(ACTION_MEDIA_SHARED);
+        receiver.onReceive(mockActivity, mediaIsShared);
+
+        verify(tetheringPreference).setEnabled(false);
+    }
+
+    @Test
+    public void onReceive_mediaIsUnshared_tetheringPreferenceIsEnabled() {
+        RestrictedSwitchPreference tetheringPreference = mock(RestrictedSwitchPreference.class);
+        FragmentActivity mockActivity = mock(FragmentActivity.class);
+        ArgumentCaptor<BroadcastReceiver> captor = ArgumentCaptor.forClass(BroadcastReceiver.class);
+        setupUsbStateComponents(tetheringPreference, captor, mockActivity);
+
+        BroadcastReceiver receiver = captor.getValue();
+        Intent mediaIsShared = new Intent(ACTION_MEDIA_UNSHARED);
+        Intent usbStateChanged = new Intent(ACTION_USB_STATE);
+        usbStateChanged.putExtra(UsbManager.USB_CONNECTED, true);
+        receiver.onReceive(mockActivity, usbStateChanged);
+        receiver.onReceive(mockActivity, mediaIsShared);
+
+        verify(tetheringPreference, times(2)).setEnabled(true);
+    }
+
     private void updateOnlyBluetoothState(TetherSettings tetherSettings) {
         doReturn(mTetheringManager).when(tetherSettings)
             .getSystemService(Context.TETHERING_SERVICE);
         when(mTetheringManager.getTetherableIfaces()).thenReturn(new String[0]);
         when(mTetheringManager.getTetheredIfaces()).thenReturn(new String[0]);
         when(mTetheringManager.getTetheringErroredIfaces()).thenReturn(new String[0]);
-        doNothing().when(tetherSettings).updateUsbState(any(String[].class), any(String[].class),
-                any(String[].class));
+        doNothing().when(tetherSettings).updateUsbState(any(String[].class));
         doNothing().when(tetherSettings).updateEthernetState(any(String[].class),
                 any(String[].class));
     }
@@ -266,4 +374,24 @@
                 UserManager.DISALLOW_CONFIG_TETHERING, UserHandle.of(userId)))
                 .thenReturn(!returnValue);
     }
+
+    private void setupUsbStateComponents(RestrictedSwitchPreference preference,
+            ArgumentCaptor<BroadcastReceiver> captor, FragmentActivity activity) {
+        TetherSettings spyTetherSettings = spy(new TetherSettings());
+        SwitchPreference mockSwitchPreference = mock(SwitchPreference.class);
+
+        when(spyTetherSettings.findPreference(TetherSettings.KEY_USB_TETHER_SETTINGS))
+                .thenReturn(preference);
+        when(spyTetherSettings.findPreference(TetherSettings.KEY_ENABLE_BLUETOOTH_TETHERING))
+                .thenReturn(mockSwitchPreference);
+        spyTetherSettings.mContext = mContext;
+        spyTetherSettings.mTm = mTetheringManager;
+        when(spyTetherSettings.getActivity()).thenReturn(activity);
+        when(activity.registerReceiver(captor.capture(), any(IntentFilter.class)))
+                .thenReturn(null);
+
+        spyTetherSettings.setupTetherPreference();
+        spyTetherSettings.registerReceiver();
+        updateOnlyBluetoothState(spyTetherSettings);
+    }
 }