From 17f4fd5a08de49a9bd0573421cebda5e1fa1f16b Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Mon, 16 Oct 2023 19:02:48 +0800 Subject: [PATCH] BluetoothWiFiResetPreferenceController Fix: 280864229 Test: manually rotate the screen Test: unit test Change-Id: I950ebae1c371ce05dd17710788eda3dc8bdfd2ca --- res/xml/reset_dashboard_fragment.xml | 5 +- ...luetoothWiFiResetPreferenceController.java | 188 ------------------ .../BluetoothWiFiResetPreferenceController.kt | 126 ++++++++++++ .../settings/spa/app/ResetAppPreferences.kt | 6 +- .../spa/preference/ComposePreference.kt | 2 + ...oothWiFiResetPreferenceControllerTest.java | 113 ----------- ...etoothWiFiResetPreferenceControllerTest.kt | 120 +++++++++++ 7 files changed, 250 insertions(+), 310 deletions(-) delete mode 100644 src/com/android/settings/network/BluetoothWiFiResetPreferenceController.java create mode 100644 src/com/android/settings/network/BluetoothWiFiResetPreferenceController.kt delete mode 100644 tests/robotests/src/com/android/settings/network/BluetoothWiFiResetPreferenceControllerTest.java create mode 100644 tests/spa_unit/src/com/android/settings/network/BluetoothWiFiResetPreferenceControllerTest.kt diff --git a/res/xml/reset_dashboard_fragment.xml b/res/xml/reset_dashboard_fragment.xml index 08852c92c86..cd1c671477b 100644 --- a/res/xml/reset_dashboard_fragment.xml +++ b/res/xml/reset_dashboard_fragment.xml @@ -30,11 +30,8 @@ android:fragment="com.android.settings.ResetNetwork" /> - diff --git a/src/com/android/settings/network/BluetoothWiFiResetPreferenceController.java b/src/com/android/settings/network/BluetoothWiFiResetPreferenceController.java deleted file mode 100644 index f0f5d732c2a..00000000000 --- a/src/com/android/settings/network/BluetoothWiFiResetPreferenceController.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (C) 2022 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 android.app.ProgressDialog; -import android.app.settings.SettingsEnums; -import android.content.Context; -import android.content.DialogInterface; -import android.os.Looper; -import android.text.TextUtils; -import android.util.Log; -import android.widget.Toast; - -import androidx.annotation.VisibleForTesting; -import androidx.appcompat.app.AlertDialog; -import androidx.preference.Preference; - -import com.android.settings.R; -import com.android.settings.ResetNetworkRequest; -import com.android.settings.core.BasePreferenceController; -import com.android.settings.overlay.FeatureFactory; -import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.atomic.AtomicReference; - -/** - * This is to show a preference regarding resetting Bluetooth and Wi-Fi. - */ -public class BluetoothWiFiResetPreferenceController extends BasePreferenceController - implements DialogInterface.OnClickListener, DialogInterface.OnDismissListener { - - private static final String TAG = "BtWiFiResetPreferenceController"; - - private final NetworkResetRestrictionChecker mRestrictionChecker; - - private DialogInterface mResetDialog; - private ProgressDialog mProgressDialog; - private ExecutorService mExecutorService; - - /** - * Constructer. - * @param context Context - * @param preferenceKey is the key for Preference - */ - public BluetoothWiFiResetPreferenceController(Context context, String preferenceKey) { - super(context, preferenceKey); - - // restriction check - mRestrictionChecker = new NetworkResetRestrictionChecker(context); - } - - @Override - public int getAvailabilityStatus() { - return mRestrictionChecker.hasUserRestriction() ? - CONDITIONALLY_UNAVAILABLE : AVAILABLE; - } - - @Override - public boolean handlePreferenceTreeClick(Preference preference) { - if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) { - return false; - } - buildResetDialog(preference); - return true; - } - - /** - * This is a pop-up dialog showing detail of this reset option. - */ - void buildResetDialog(Preference preference) { - if (mResetDialog != null) { - return; - } - mResetDialog = new AlertDialog.Builder(mContext) - .setTitle(R.string.reset_bluetooth_wifi_title) - .setMessage(R.string.reset_bluetooth_wifi_desc) - .setPositiveButton(R.string.reset_bluetooth_wifi_button_text, this) - .setNegativeButton(R.string.cancel, null /* OnClickListener */) - .setOnDismissListener(this) - .show(); - } - - public void onDismiss(DialogInterface dialog) { - if (mResetDialog == dialog) { - mResetDialog = null; - } - } - - /** - * User pressed confirmation button, for starting reset operation. - */ - public void onClick(DialogInterface dialog, int which) { - if (mResetDialog != dialog) { - return; - } - - // User confirm the reset operation - MetricsFeatureProvider provider = FeatureFactory.getFeatureFactory() - .getMetricsFeatureProvider(); - provider.action(mContext, SettingsEnums.RESET_BLUETOOTH_WIFI_CONFIRM, true); - - // Non-cancelable progress dialog - mProgressDialog = getProgressDialog(mContext); - mProgressDialog.show(); - - // Run reset in background thread - mExecutorService = Executors.newSingleThreadExecutor(); - mExecutorService.execute(() -> { - final AtomicReference exceptionDuringReset = - new AtomicReference(); - try { - resetOperation().run(); - } catch (Exception exception) { - exceptionDuringReset.set(exception); - } - mContext.getMainExecutor().execute(() -> endOfReset(exceptionDuringReset.get())); - }); - } - - @VisibleForTesting - protected ProgressDialog getProgressDialog(Context context) { - final ProgressDialog progressDialog = new ProgressDialog(context); - progressDialog.setIndeterminate(true); - progressDialog.setCancelable(false); - progressDialog.setMessage( - context.getString(R.string.main_clear_progress_text)); - return progressDialog; - } - - @VisibleForTesting - protected Runnable resetOperation() throws Exception { - if (SubscriptionUtil.isSimHardwareVisible(mContext)) { - return new ResetNetworkRequest( - ResetNetworkRequest.RESET_WIFI_MANAGER | - ResetNetworkRequest.RESET_WIFI_P2P_MANAGER | - ResetNetworkRequest.RESET_BLUETOOTH_MANAGER) - .toResetNetworkOperationBuilder(mContext, Looper.getMainLooper()) - .build(); - } - - /** - * For device without SIMs visible to the user - */ - return new ResetNetworkRequest( - ResetNetworkRequest.RESET_CONNECTIVITY_MANAGER | - ResetNetworkRequest.RESET_VPN_MANAGER | - ResetNetworkRequest.RESET_WIFI_MANAGER | - ResetNetworkRequest.RESET_WIFI_P2P_MANAGER | - ResetNetworkRequest.RESET_BLUETOOTH_MANAGER) - .toResetNetworkOperationBuilder(mContext, Looper.getMainLooper()) - .resetTelephonyAndNetworkPolicyManager(ResetNetworkRequest.ALL_SUBSCRIPTION_ID) - .build(); - } - - @VisibleForTesting - protected void endOfReset(Exception exceptionDuringReset) { - if (mExecutorService != null) { - mExecutorService.shutdown(); - mExecutorService = null; - } - if (mProgressDialog != null) { - mProgressDialog.dismiss(); - mProgressDialog = null; - } - if (exceptionDuringReset == null) { - Toast.makeText(mContext, R.string.reset_bluetooth_wifi_complete_toast, - Toast.LENGTH_SHORT).show(); - } else { - Log.e(TAG, "Exception during reset", exceptionDuringReset); - } - } -} diff --git a/src/com/android/settings/network/BluetoothWiFiResetPreferenceController.kt b/src/com/android/settings/network/BluetoothWiFiResetPreferenceController.kt new file mode 100644 index 00000000000..2047ed92e68 --- /dev/null +++ b/src/com/android/settings/network/BluetoothWiFiResetPreferenceController.kt @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2023 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 android.app.settings.SettingsEnums +import android.content.Context +import android.os.Looper +import android.os.UserManager +import android.util.Log +import android.widget.Toast +import androidx.annotation.VisibleForTesting +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.res.stringResource +import com.android.settings.R +import com.android.settings.ResetNetworkRequest +import com.android.settings.overlay.FeatureFactory.Companion.featureFactory +import com.android.settings.spa.preference.ComposePreferenceController +import com.android.settingslib.spa.widget.dialog.AlertDialogButton +import com.android.settingslib.spa.widget.dialog.rememberAlertDialogPresenter +import com.android.settingslib.spa.widget.preference.PreferenceModel +import com.android.settingslib.spaprivileged.model.enterprise.Restrictions +import com.android.settingslib.spaprivileged.template.preference.RestrictedPreference +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +/** + * This is to show a preference regarding resetting Bluetooth and Wi-Fi. + */ +class BluetoothWiFiResetPreferenceController(context: Context, preferenceKey: String) : + ComposePreferenceController(context, preferenceKey) { + + private val restrictionChecker = NetworkResetRestrictionChecker(context) + + override fun getAvailabilityStatus() = + if (restrictionChecker.hasUserRestriction()) CONDITIONALLY_UNAVAILABLE else AVAILABLE + + @Composable + override fun Content() { + val coroutineScope = rememberCoroutineScope() + val dialogPresenter = rememberAlertDialogPresenter( + confirmButton = AlertDialogButton( + text = stringResource(R.string.reset_bluetooth_wifi_button_text), + ) { reset(coroutineScope) }, + dismissButton = AlertDialogButton(text = stringResource(R.string.cancel)), + title = stringResource(R.string.reset_bluetooth_wifi_title), + ) { + Text(stringResource(R.string.reset_bluetooth_wifi_desc)) + } + + RestrictedPreference( + model = object : PreferenceModel { + override val title = stringResource(R.string.reset_bluetooth_wifi_title) + override val onClick = dialogPresenter::open + }, + restrictions = Restrictions(keys = listOf(UserManager.DISALLOW_NETWORK_RESET)), + ) + } + + /** + * User pressed confirmation button, for starting reset operation. + */ + private fun reset(coroutineScope: CoroutineScope) { + // User confirm the reset operation + featureFactory.metricsFeatureProvider + .action(mContext, SettingsEnums.RESET_BLUETOOTH_WIFI_CONFIRM, true) + + // Run reset in background thread + coroutineScope.launch { + try { + withContext(Dispatchers.Default) { + resetOperation().run() + } + } catch (e: Exception) { + Log.e(TAG, "Exception during reset", e) + return@launch + } + Toast.makeText( + mContext, + R.string.reset_bluetooth_wifi_complete_toast, + Toast.LENGTH_SHORT, + ).show() + } + } + + @VisibleForTesting + fun resetOperation(): Runnable = if (SubscriptionUtil.isSimHardwareVisible(mContext)) { + ResetNetworkRequest( + ResetNetworkRequest.RESET_WIFI_MANAGER or + ResetNetworkRequest.RESET_WIFI_P2P_MANAGER or + ResetNetworkRequest.RESET_BLUETOOTH_MANAGER + ) + .toResetNetworkOperationBuilder(mContext, Looper.getMainLooper()) + } else { // For device without SIMs visible to the user + ResetNetworkRequest( + ResetNetworkRequest.RESET_CONNECTIVITY_MANAGER or + ResetNetworkRequest.RESET_VPN_MANAGER or + ResetNetworkRequest.RESET_WIFI_MANAGER or + ResetNetworkRequest.RESET_WIFI_P2P_MANAGER or + ResetNetworkRequest.RESET_BLUETOOTH_MANAGER + ) + .toResetNetworkOperationBuilder(mContext, Looper.getMainLooper()) + .resetTelephonyAndNetworkPolicyManager(ResetNetworkRequest.ALL_SUBSCRIPTION_ID) + }.build() + + private companion object { + private const val TAG = "BluetoothWiFiResetPref" + } +} diff --git a/src/com/android/settings/spa/app/ResetAppPreferences.kt b/src/com/android/settings/spa/app/ResetAppPreferences.kt index 12dd7090ea1..34c4145e03f 100644 --- a/src/com/android/settings/spa/app/ResetAppPreferences.kt +++ b/src/com/android/settings/spa/app/ResetAppPreferences.kt @@ -16,7 +16,6 @@ package com.android.settings.spa.app -import android.os.UserHandle import android.os.UserManager import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -37,10 +36,7 @@ fun MoreOptionsScope.ResetAppPreferences(onClick: () -> Unit) { RestrictedMenuItem( text = stringResource(R.string.reset_app_preferences), restrictions = remember { - Restrictions( - userId = UserHandle.myUserId(), - keys = listOf(UserManager.DISALLOW_APPS_CONTROL), - ) + Restrictions(keys = listOf(UserManager.DISALLOW_APPS_CONTROL)) }, onClick = onClick, ) diff --git a/src/com/android/settings/spa/preference/ComposePreference.kt b/src/com/android/settings/spa/preference/ComposePreference.kt index 0c9abad2d26..aec85a9b324 100644 --- a/src/com/android/settings/spa/preference/ComposePreference.kt +++ b/src/com/android/settings/spa/preference/ComposePreference.kt @@ -40,6 +40,8 @@ class ComposePreference @JvmOverloads constructor( override fun onBindViewHolder(holder: PreferenceViewHolder) { super.onBindViewHolder(holder) + holder.isDividerAllowedAbove = false + holder.isDividerAllowedBelow = false (holder.itemView as ComposeView).apply { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) diff --git a/tests/robotests/src/com/android/settings/network/BluetoothWiFiResetPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/network/BluetoothWiFiResetPreferenceControllerTest.java deleted file mode 100644 index 3aea4a87cb7..00000000000 --- a/tests/robotests/src/com/android/settings/network/BluetoothWiFiResetPreferenceControllerTest.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2022 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 com.google.common.truth.Truth.assertThat; -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.content.res.Resources; -import android.net.ConnectivityManager; -import android.os.UserManager; -import android.telephony.TelephonyManager; - -import com.android.settings.R; - -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.RuntimeEnvironment; -import org.robolectric.shadows.ShadowToast; - -@RunWith(RobolectricTestRunner.class) -public class BluetoothWiFiResetPreferenceControllerTest { - - private static final String PREFERENCE_KEY = "network_reset_bluetooth_wifi_pref"; - - @Mock - private UserManager mUserManager; - @Mock - private Resources mResources; - @Mock - private ConnectivityManager mConnectivityManager; - @Mock - private TelephonyManager mTelephonyManager; - - private Context mContext; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - mContext = spy(RuntimeEnvironment.application); - when(mContext.getResources()).thenReturn(mResources); - - mockService(Context.CONNECTIVITY_SERVICE, ConnectivityManager.class, - mConnectivityManager); - mockService(Context.TELEPHONY_SERVICE, TelephonyManager.class, mTelephonyManager); - } - - @Test - public void getAvailabilityStatus_returnAvailable_asOwnerUser() { - mockService(Context.USER_SERVICE, UserManager.class, mUserManager); - doReturn(true).when(mUserManager).isAdminUser(); - - BluetoothWiFiResetPreferenceController target = - new BluetoothWiFiResetPreferenceController(mContext, PREFERENCE_KEY); - - assertThat(target.getAvailabilityStatus()).isEqualTo( - BluetoothWiFiResetPreferenceController.AVAILABLE); - } - - @Test - public void resetOperation_notResetConnectivity_onDeviceWithSimVisible() { - mockService(Context.CONNECTIVITY_SERVICE, ConnectivityManager.class, - mConnectivityManager); - when(mResources.getBoolean(R.bool.config_show_sim_info)).thenReturn(true); - - BluetoothWiFiResetPreferenceController target = - new BluetoothWiFiResetPreferenceController(mContext, PREFERENCE_KEY); - - try { - target.resetOperation().run(); - } catch (Exception exception) {} - verify(mConnectivityManager, never()).factoryReset(); - } - - @Test - public void endOfReset_toastMessage_whenSuccess() { - String testText = "reset_bluetooth_wifi_complete_toast"; - when(mResources.getString(R.string.reset_bluetooth_wifi_complete_toast)).thenReturn(testText); - BluetoothWiFiResetPreferenceController target = - new BluetoothWiFiResetPreferenceController(mContext, PREFERENCE_KEY); - - target.endOfReset(null); - - assertThat(ShadowToast.getTextOfLatestToast()).isEqualTo(testText); - } - - private void mockService(String serviceName, Class serviceClass, T service) { - when(mContext.getSystemServiceName(serviceClass)).thenReturn(serviceName); - when(mContext.getSystemService(serviceName)).thenReturn(service); - } -} diff --git a/tests/spa_unit/src/com/android/settings/network/BluetoothWiFiResetPreferenceControllerTest.kt b/tests/spa_unit/src/com/android/settings/network/BluetoothWiFiResetPreferenceControllerTest.kt new file mode 100644 index 00000000000..210a0c7457c --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/network/BluetoothWiFiResetPreferenceControllerTest.kt @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2023 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 android.bluetooth.BluetoothAdapter +import android.bluetooth.BluetoothManager +import android.content.Context +import android.content.res.Resources +import android.net.ConnectivityManager +import android.net.NetworkPolicyManager +import android.net.VpnManager +import android.os.UserManager +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settings.R +import com.android.settings.core.BasePreferenceController.AVAILABLE +import com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.spy +import org.mockito.kotlin.stub +import org.mockito.kotlin.verify + +@RunWith(AndroidJUnit4::class) +class BluetoothWiFiResetPreferenceControllerTest { + + private val mockUserManager = mock() + private val mockBluetoothAdapter = mock() + private val mockBluetoothManager = mock { + on { adapter } doReturn mockBluetoothAdapter + } + private val mockConnectivityManager = mock() + private val mockNetworkPolicyManager = mock() + private val mockVpnManager = mock() + private val mockResources = mock() + + private val context: Context = spy(ApplicationProvider.getApplicationContext()) { + on { getSystemService(Context.USER_SERVICE) } doReturn mockUserManager + on { getSystemService(Context.BLUETOOTH_SERVICE) } doReturn mockBluetoothManager + on { getSystemService(Context.CONNECTIVITY_SERVICE) } doReturn mockConnectivityManager + on { getSystemService(Context.NETWORK_POLICY_SERVICE) } doReturn mockNetworkPolicyManager + on { getSystemService(Context.VPN_MANAGEMENT_SERVICE) } doReturn mockVpnManager + on { resources } doReturn mockResources + } + + private val controller = BluetoothWiFiResetPreferenceController(context, TEST_KEY) + + @Test + fun getAvailabilityStatus_isAdminUser_returnAvailable() { + mockUserManager.stub { + on { isAdminUser } doReturn true + } + + val availabilityStatus = controller.getAvailabilityStatus() + + assertThat(availabilityStatus).isEqualTo(AVAILABLE) + } + + @Test + fun getAvailabilityStatus_notAdminUser_returnConditionallyUnavailable() { + mockUserManager.stub { + on { isAdminUser } doReturn false + } + + val availabilityStatus = controller.getAvailabilityStatus() + + assertThat(availabilityStatus).isEqualTo(CONDITIONALLY_UNAVAILABLE) + } + + @Test + fun resetOperation_resetBluetooth() { + controller.resetOperation().run() + + verify(mockBluetoothAdapter).clearBluetooth() + } + + @Test + fun resetOperation_onDeviceWithSimVisible_notResetConnectivity() { + mockResources.stub { + on { getBoolean(R.bool.config_show_sim_info) } doReturn true + } + + controller.resetOperation().run() + + verify(mockConnectivityManager, never()).factoryReset() + } + + @Test + fun resetOperation_onDeviceWithSimInvisible_resetVpn() { + mockResources.stub { + on { getBoolean(R.bool.config_show_sim_info) } doReturn false + } + + controller.resetOperation().run() + + verify(mockVpnManager).factoryReset() + } + + private companion object { + const val TEST_KEY = "test_key" + } +}