diff --git a/res/xml/network_and_internet.xml b/res/xml/network_and_internet.xml index 76537466e85..257363a16f4 100644 --- a/res/xml/network_and_internet.xml +++ b/res/xml/network_and_internet.xml @@ -79,6 +79,18 @@ settings:userRestriction="no_config_tethering" settings:useAdminDisabledSummary="true" /> + + mBluetoothPan; + private final BluetoothAdapter mBluetoothAdapter; + @VisibleForTesting + final BluetoothProfile.ServiceListener mBtProfileServiceListener = + new BluetoothProfile.ServiceListener() { + @Override + public void onServiceConnected(int profile, BluetoothProfile proxy) { + mBluetoothPan.set((BluetoothPan) proxy); + } + + @Override + public void onServiceDisconnected(int profile) { + mBluetoothPan.set(null); + } + }; + + private MasterSwitchPreference mPreference; + + @VisibleForTesting(otherwise = VisibleForTesting.NONE) + AllInOneTetherPreferenceController() { + super(null /*context*/, "test"); + mAdminDisallowedTetherConfig = false; + mBluetoothPan = new AtomicReference<>(); + mBluetoothAdapter = null; + } + + public AllInOneTetherPreferenceController(Context context, String key) { + super(context, key); + mBluetoothPan = new AtomicReference<>(); + mAdminDisallowedTetherConfig = checkIfRestrictionEnforced( + context, DISALLOW_CONFIG_TETHERING, UserHandle.myUserId()) != null; + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = screen.findPreference(mPreferenceKey); + if (mPreference != null && !mAdminDisallowedTetherConfig) { + // Grey out if provisioning is not available. + mPreference.setEnabled(!TetherSettings.isProvisioningNeededButUnavailable(mContext)); + } + } + + @Override + public int getAvailabilityStatus() { + if (!TetherUtil.isTetherAvailable(mContext) + || !FeatureFlagUtils.isEnabled(mContext, FeatureFlags.TETHER_ALL_IN_ONE)) { + return CONDITIONALLY_UNAVAILABLE; + } else { + return AVAILABLE; + } + } + + @Override + public CharSequence getSummary() { + if (mPreference != null && mPreference.isChecked()) { + // TODO(b/149256198) update summary accordingly. + return "Tethering"; + } + + return "Not sharing internet with other devices"; + } + + @OnLifecycleEvent(Event.ON_CREATE) + public void onCreate() { + if (mBluetoothAdapter != null + && mBluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) { + mBluetoothAdapter.getProfileProxy(mContext, mBtProfileServiceListener, + BluetoothProfile.PAN); + } + } + + @OnLifecycleEvent(Event.ON_DESTROY) + public void onDestroy() { + final BluetoothProfile profile = mBluetoothPan.getAndSet(null); + if (profile != null && mBluetoothAdapter != null) { + mBluetoothAdapter.closeProfileProxy(BluetoothProfile.PAN, profile); + } + } + + void init(Lifecycle lifecycle) { + lifecycle.addObserver(this); + } + + void initEnabler(Lifecycle lifecycle) { + if (mPreference != null) { + TetherEnabler tetherEnabler = new TetherEnabler( + mContext, new MasterSwitchController(mPreference), mBluetoothPan); + if (lifecycle != null) { + lifecycle.addObserver(tetherEnabler); + } + } else { + Log.e(TAG, "TetherEnabler is not initialized"); + } + } +} diff --git a/src/com/android/settings/network/NetworkDashboardFragment.java b/src/com/android/settings/network/NetworkDashboardFragment.java index 71d70660ccf..b1685fd3fcb 100644 --- a/src/com/android/settings/network/NetworkDashboardFragment.java +++ b/src/com/android/settings/network/NetworkDashboardFragment.java @@ -20,6 +20,7 @@ import static com.android.settings.network.MobilePlanPreferenceController.MANAGE import android.app.Dialog; import android.app.settings.SettingsEnums; import android.content.Context; +import android.os.Bundle; import android.util.Log; import androidx.appcompat.app.AlertDialog; @@ -65,6 +66,13 @@ public class NetworkDashboardFragment extends DashboardFragment implements use(MultiNetworkHeaderController.class).init(getSettingsLifecycle()); use(AirplaneModePreferenceController.class).setFragment(this); + use(AllInOneTetherPreferenceController.class).init(getSettingsLifecycle()); + } + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + super.onCreatePreferences(savedInstanceState, rootKey); + use(AllInOneTetherPreferenceController.class).initEnabler(getSettingsLifecycle()); } @Override diff --git a/src/com/android/settings/network/TetherPreferenceController.java b/src/com/android/settings/network/TetherPreferenceController.java index 8fc05aabb8e..d18d897393e 100644 --- a/src/com/android/settings/network/TetherPreferenceController.java +++ b/src/com/android/settings/network/TetherPreferenceController.java @@ -39,7 +39,6 @@ import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; -import com.android.settings.AllInOneTetherSettings; import com.android.settings.R; import com.android.settings.TetherSettings; import com.android.settings.core.FeatureFlags; @@ -112,16 +111,13 @@ public class TetherPreferenceController extends AbstractPreferenceController imp // Grey out if provisioning is not available. mPreference.setEnabled(!TetherSettings.isProvisioningNeededButUnavailable(mContext)); - - if (FeatureFlagUtils.isEnabled(mContext, FeatureFlags.TETHER_ALL_IN_ONE)) { - mPreference.setFragment(AllInOneTetherSettings.class.getName()); - } } } @Override public boolean isAvailable() { - return TetherUtil.isTetherAvailable(mContext); + return TetherUtil.isTetherAvailable(mContext) + && !FeatureFlagUtils.isEnabled(mContext, FeatureFlags.TETHER_ALL_IN_ONE); } @Override diff --git a/tests/robotests/src/com/android/settings/network/AllInOneTetherPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/network/AllInOneTetherPreferenceControllerTest.java new file mode 100644 index 00000000000..4fbc4736adc --- /dev/null +++ b/tests/robotests/src/com/android/settings/network/AllInOneTetherPreferenceControllerTest.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.network; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothPan; +import android.bluetooth.BluetoothProfile; +import android.content.Context; + +import com.android.settings.widget.MasterSwitchPreference; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.util.ReflectionHelpers; + +import java.util.concurrent.atomic.AtomicReference; + +@RunWith(RobolectricTestRunner.class) +public class AllInOneTetherPreferenceControllerTest { + + @Mock + private Context mContext; + @Mock + private BluetoothAdapter mBluetoothAdapter; + @Mock + private MasterSwitchPreference mPreference; + + private AllInOneTetherPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mController = spy(AllInOneTetherPreferenceController.class); + ReflectionHelpers.setField(mController, "mContext", mContext); + ReflectionHelpers.setField(mController, "mBluetoothAdapter", mBluetoothAdapter); + ReflectionHelpers.setField(mController, "mPreference", mPreference); + } + + @Test + public void lifeCycle_onCreate_shouldInitBluetoothPan() { + when(mBluetoothAdapter.getState()).thenReturn(BluetoothAdapter.STATE_ON); + mController.onCreate(); + + verify(mBluetoothAdapter).getState(); + verify(mBluetoothAdapter).getProfileProxy(mContext, mController.mBtProfileServiceListener, + BluetoothProfile.PAN); + } + + @Test + public void lifeCycle_onCreate_shouldNotInitBluetoothPanWhenBluetoothOff() { + when(mBluetoothAdapter.getState()).thenReturn(BluetoothAdapter.STATE_OFF); + mController.onCreate(); + + verify(mBluetoothAdapter).getState(); + verifyNoMoreInteractions(mBluetoothAdapter); + } + + @Test + public void goThroughLifecycle_shouldDestroyBluetoothProfile() { + final BluetoothPan pan = mock(BluetoothPan.class); + final AtomicReference panRef = + ReflectionHelpers.getField(mController, "mBluetoothPan"); + panRef.set(pan); + + mController.onDestroy(); + + verify(mBluetoothAdapter).closeProfileProxy(BluetoothProfile.PAN, pan); + } +}