Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 17f4fd5a authored by Chaohui Wang's avatar Chaohui Wang
Browse files

BluetoothWiFiResetPreferenceController

Fix: 280864229
Test: manually rotate the screen
Test: unit test
Change-Id: I950ebae1c371ce05dd17710788eda3dc8bdfd2ca
parent f17e4138
Loading
Loading
Loading
Loading
+1 −4
Original line number Diff line number Diff line
@@ -30,11 +30,8 @@
        android:fragment="com.android.settings.ResetNetwork" />

    <!-- Bluetooth and WiFi reset -->
    <com.android.settingslib.RestrictedPreference
    <com.android.settings.spa.preference.ComposePreference
        android:key="network_reset_bluetooth_wifi_pref"
        android:title="@string/reset_bluetooth_wifi_title"
        settings:userRestriction="no_network_reset"
        settings:useAdminDisabledSummary="true"
        settings:controller="com.android.settings.network.BluetoothWiFiResetPreferenceController" />

    <!-- Reset app preferences -->
+0 −188
Original line number Diff line number Diff line
/*
 * 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<Exception> exceptionDuringReset =
                    new AtomicReference<Exception>();
            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);
        }
    }
}
+126 −0
Original line number Diff line number Diff line
/*
 * 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"
    }
}
+1 −5
Original line number Diff line number Diff line
@@ -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,
    )
+2 −0
Original line number Diff line number Diff line
@@ -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)
Loading