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

Commit fdbfa691 authored by Jacky Wang's avatar Jacky Wang Committed by Android (Google) Code Review
Browse files

Merge changes from topic "catalyst" into main

* changes:
  [Catalyst] Migrate bluetooth screen to catalyst
  [Catalyst] Migrate BluetoothFooterPreference
  [Catalyst] Fully migrate BluetoothMainSwitchPreference
  [Catalyst] Add SatelliteRepository.isSatelliteOn helper method
  [Catalyst] Support callChangeListener for MainSwitchBarPreference
parents 05783a58 efd08c4b
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@
<PreferenceScreen
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:settings="http://schemas.android.com/apk/res-auto"
    android:key="bluetooth_switchbar_screen"
    android:title="@string/bluetooth_settings_title">

    <SwitchPreferenceCompat
+41 −3
Original line number Diff line number Diff line
@@ -15,8 +15,10 @@
 */
package com.android.settings.connecteddevice;

import android.app.Activity;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
@@ -31,6 +33,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.Utils;
import com.android.settings.bluetooth.AlwaysDiscoverable;
import com.android.settings.bluetooth.BluetoothDeviceRenamePreferenceController;
import com.android.settings.bluetooth.BluetoothSwitchPreferenceController;
import com.android.settings.dashboard.DashboardFragment;
@@ -50,7 +53,6 @@ import com.android.settingslib.widget.FooterPreference;
public class BluetoothDashboardFragment extends DashboardFragment {

    private static final String TAG = "BluetoothDashboardFrag";
    private static final String KEY_BLUETOOTH_SCREEN_FOOTER = "bluetooth_screen_footer";
    private static final String SLICE_ACTION = "com.android.settings.SEARCH_RESULT_TRAMPOLINE";
    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);

@@ -58,6 +60,8 @@ public class BluetoothDashboardFragment extends DashboardFragment {
    private SettingsMainSwitchBar mSwitchBar;
    private BluetoothSwitchPreferenceController mController;

    private @Nullable AlwaysDiscoverable mAlwaysDiscoverable;

    @Override
    public int getMetricsCategory() {
        return SettingsEnums.BLUETOOTH_FRAGMENT;
@@ -81,7 +85,9 @@ public class BluetoothDashboardFragment extends DashboardFragment {
    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        mFooterPreference = findPreference(KEY_BLUETOOTH_SCREEN_FOOTER);
        if (!isCatalystEnabled()) {
            mFooterPreference = findPreference(BluetoothFooterPreference.KEY);
        }
    }

    @Override
@@ -93,6 +99,9 @@ public class BluetoothDashboardFragment extends DashboardFragment {
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if (isCatalystEnabled()) {
            return;
        }
        String callingAppPackageName = PasswordUtils.getCallingAppPackageName(
                getActivity().getActivityToken());
        String action = getIntent() != null ? getIntent().getAction() : "";
@@ -114,8 +123,37 @@ public class BluetoothDashboardFragment extends DashboardFragment {
        }
    }

    @Override
    public void onStart() {
        super.onStart();
        if (isCatalystEnabled()) {
            Activity activity = requireActivity();
            String callingAppPackageName = PasswordUtils.getCallingAppPackageName(
                    activity.getActivityToken());
            Intent intent = activity.getIntent();
            String action = intent != null ? intent.getAction() : "";
            if (DEBUG) {
                Log.d(TAG, "onActivityCreated() calling package name is : " + callingAppPackageName
                        + ", action : " + action);
            }
            if (isAlwaysDiscoverable(callingAppPackageName, action)) {
                mAlwaysDiscoverable = new AlwaysDiscoverable(activity);
                mAlwaysDiscoverable.start();
            }
        }
    }

    @Override
    public void onStop() {
        super.onStop();
        if (mAlwaysDiscoverable != null) {
            mAlwaysDiscoverable.stop();
            mAlwaysDiscoverable = null;
        }
    }

    @VisibleForTesting
    boolean isAlwaysDiscoverable(String callingAppPackageName, String action) {
    boolean isAlwaysDiscoverable(@Nullable String callingAppPackageName, @Nullable String action) {
        return TextUtils.equals(SLICE_ACTION, action) ? false
            : TextUtils.equals(Utils.SETTINGS_PACKAGE_NAME, callingAppPackageName)
                || TextUtils.equals(Utils.SYSTEMUI_PACKAGE_NAME, callingAppPackageName);
+12 −1
Original line number Diff line number Diff line
@@ -17,7 +17,10 @@ package com.android.settings.connecteddevice

import android.content.Context
import com.android.settings.R
import com.android.settings.Settings.BluetoothDashboardActivity
import com.android.settings.flags.Flags
import com.android.settings.utils.makeLaunchIntent
import com.android.settingslib.metadata.PreferenceMetadata
import com.android.settingslib.metadata.ProvidePreferenceScreen
import com.android.settingslib.metadata.preferenceHierarchy
import com.android.settingslib.preference.PreferenceScreenCreator
@@ -39,7 +42,15 @@ class BluetoothDashboardScreen : PreferenceScreenCreator {

    override fun fragmentClass() = BluetoothDashboardFragment::class.java

    override fun getPreferenceHierarchy(context: Context) = preferenceHierarchy(this) {}
    override fun getLaunchIntent(context: Context, metadata: PreferenceMetadata?) =
        makeLaunchIntent(context, BluetoothDashboardActivity::class.java, metadata?.key)

    override fun getPreferenceHierarchy(context: Context) =
        preferenceHierarchy(this) {
            val bluetoothDataStore = BluetoothPreference.createDataStore(context)
            +BluetoothPreference(bluetoothDataStore)
            +BluetoothFooterPreference(bluetoothDataStore)
        }

    companion object {
        const val KEY = "bluetooth_switchbar_screen"
+96 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.connecteddevice

import android.app.settings.SettingsEnums
import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.preference.Preference
import com.android.settings.R
import com.android.settings.bluetooth.Utils
import com.android.settings.core.SubSettingLauncher
import com.android.settings.location.BluetoothScanningFragment
import com.android.settingslib.metadata.PreferenceMetadata
import com.android.settingslib.metadata.PreferenceSummaryProvider
import com.android.settingslib.preference.PreferenceBinding
import com.android.settingslib.widget.FooterPreference

class BluetoothFooterPreference(private val bluetoothDataStore: BluetoothDataStore) :
    PreferenceMetadata, PreferenceBinding, PreferenceSummaryProvider {

    override val key: String
        get() = KEY

    override fun isIndexable(context: Context) = false

    override fun dependencies(context: Context) = arrayOf(BluetoothPreference.KEY)

    override fun intent(context: Context): Intent? = subSettingLauncher(context).toIntent()

    override fun createWidget(context: Context) = FooterPreference(context)

    override fun bind(preference: Preference, metadata: PreferenceMetadata) {
        super.bind(preference, metadata)
        preference.isSelectable = false
        val bluetoothDisabled = bluetoothDataStore.getBoolean(BluetoothPreference.KEY) != true
        val footerPreference = preference as FooterPreference
        val context = preference.context
        if (bluetoothDisabled && Utils.isBluetoothScanningEnabled(context)) {
            footerPreference.setLearnMoreText(context.getString(R.string.bluetooth_scan_change))
            footerPreference.setLearnMoreAction { subSettingLauncher(context).launch() }
        } else {
            footerPreference.setLearnMoreText("")
            footerPreference.setLearnMoreAction(null)
        }
    }

    private fun subSettingLauncher(context: Context) =
        SubSettingLauncher(context)
            .setDestination(BluetoothScanningFragment::class.java.name)
            .setSourceMetricsCategory(SettingsEnums.BLUETOOTH_FRAGMENT)

    override fun getSummary(context: Context): CharSequence? {
        val bluetoothDisabled = bluetoothDataStore.getBoolean(BluetoothPreference.KEY) != true
        val resId =
            if (bluetoothDisabled && Utils.isBluetoothScanningEnabled(context)) {
                when (isAutoOnFeatureAvailable()) {
                    true -> R.string.bluetooth_scanning_on_info_message_auto_on_available
                    else -> R.string.bluetooth_scanning_on_info_message
                }
            } else {
                when (isAutoOnFeatureAvailable()) {
                    true -> R.string.bluetooth_empty_list_bluetooth_off_auto_on_available
                    else -> R.string.bluetooth_empty_list_bluetooth_off
                }
            }
        return context.getString(resId)
    }

    private fun isAutoOnFeatureAvailable() =
        try {
            bluetoothDataStore.bluetoothAdapter?.isAutoOnSupported == true
        } catch (e: Exception) {
            Log.e(TAG, "isAutoOnSupported failed", e)
            false
        }

    companion object {
        const val KEY = "bluetooth_screen_footer"
        const val TAG = "BluetoothFooterPreference"
    }
}
+175 −0
Original line number Diff line number Diff line
@@ -16,76 +16,115 @@

package com.android.settings.connecteddevice

import android.annotation.SuppressLint
import android.bluetooth.BluetoothAdapter
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.UserManager
import android.provider.Settings
import android.widget.Toast
import androidx.preference.Preference
import com.android.settings.PreferenceRestrictionMixin
import com.android.settings.R
import com.android.settings.network.SatelliteRepository.Companion.isSatelliteOn
import com.android.settings.network.SatelliteWarningDialogActivity
import com.android.settings.widget.MainSwitchBarMetadata
import com.android.settingslib.WirelessUtils
import com.android.settingslib.datastore.AbstractKeyedDataObservable
import com.android.settingslib.datastore.DataChangeReason
import com.android.settingslib.datastore.KeyValueStore
import com.android.settingslib.datastore.NoOpKeyedObservable
import com.android.settingslib.metadata.PreferenceLifecycleContext
import com.android.settingslib.metadata.PreferenceLifecycleProvider
import com.android.settingslib.metadata.PreferenceMetadata
import com.android.settingslib.metadata.ReadWritePermit
import com.android.settingslib.metadata.SensitivityLevel

class BluetoothMainSwitchPreference(private val bluetoothAdapter: BluetoothAdapter?) :
    MainSwitchBarMetadata, PreferenceLifecycleProvider {

    private lateinit var broadcastReceiver: BroadcastReceiver
@SuppressLint("MissingPermission")
class BluetoothPreference(private val bluetoothDataStore: BluetoothDataStore) :
    MainSwitchBarMetadata, PreferenceRestrictionMixin, Preference.OnPreferenceChangeListener {

    override val key
        get() = "use_bluetooth"
        get() = KEY

    override val title
        get() = R.string.bluetooth_main_switch_title

    override val restrictionKeys: Array<String>
        get() = arrayOf(UserManager.DISALLOW_BLUETOOTH, UserManager.DISALLOW_CONFIG_BLUETOOTH)

    override fun getReadPermit(context: Context, myUid: Int, callingUid: Int) =
        ReadWritePermit.ALLOW

    override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) =
        ReadWritePermit.ALLOW
        when {
            isSatelliteOn(context, 3000) ||
                (value == true &&
                    !WirelessUtils.isRadioAllowed(context, Settings.Global.RADIO_BLUETOOTH)) ->
                ReadWritePermit.DISALLOW
            else -> ReadWritePermit.ALLOW
        }

    override fun storage(context: Context) = BluetoothStateStore(bluetoothAdapter)
    override val sensitivityLevel
        get() = SensitivityLevel.LOW_SENSITIVITY

    override fun onStart(context: PreferenceLifecycleContext) {
        broadcastReceiver =
            object : BroadcastReceiver() {
                override fun onReceive(receiverContext: Context, intent: Intent) {
                    context.notifyPreferenceChange(key)
    override fun storage(context: Context) = bluetoothDataStore

    override fun isEnabled(context: Context): Boolean {
        return super<PreferenceRestrictionMixin>.isEnabled(context) &&
            bluetoothDataStore.bluetoothAdapter?.state.let {
                it == BluetoothAdapter.STATE_ON || it == BluetoothAdapter.STATE_OFF
            }
    }
        context.registerReceiver(
            broadcastReceiver,
            IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED),
            Context.RECEIVER_EXPORTED_UNAUDITED
        )
    }

    override fun onStop(context: PreferenceLifecycleContext) {
        if (::broadcastReceiver.isInitialized) {
            context.unregisterReceiver(broadcastReceiver)
    override fun bind(preference: Preference, metadata: PreferenceMetadata) {
        super.bind(preference, metadata)
        preference.onPreferenceChangeListener = this
    }

    override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean {
        val context = preference.context

        if (isSatelliteOn(context, 3000)) {
            context.startActivity(
                Intent(context, SatelliteWarningDialogActivity::class.java)
                    .putExtra(
                        SatelliteWarningDialogActivity.EXTRA_TYPE_OF_SATELLITE_WARNING_DIALOG,
                        SatelliteWarningDialogActivity.TYPE_IS_BLUETOOTH,
                    )
            )
            return false
        }

    override fun isEnabled(context: Context): Boolean {
        return bluetoothAdapter?.state.let {
            it == BluetoothAdapter.STATE_ON || it == BluetoothAdapter.STATE_OFF
        // Show toast message if Bluetooth is not allowed in airplane mode
        if (
            newValue == true &&
                !WirelessUtils.isRadioAllowed(context, Settings.Global.RADIO_BLUETOOTH)
        ) {
            Toast.makeText(context, R.string.wifi_in_airplane_mode, Toast.LENGTH_SHORT).show()
            return false
        }

        return true
    }

    @Suppress("UNCHECKED_CAST")
    class BluetoothStateStore(private val bluetoothAdapter: BluetoothAdapter?) :
        NoOpKeyedObservable<String>(), KeyValueStore {
    private class BluetoothStorage(
        private val context: Context,
        override val bluetoothAdapter: BluetoothAdapter?,
    ) : AbstractKeyedDataObservable<String>(), BluetoothDataStore {

        private var broadcastReceiver: BroadcastReceiver? = null

        override fun contains(key: String) = true
        override fun contains(key: String) = key == KEY && bluetoothAdapter != null

        override fun <T : Any> getValue(key: String, valueType: Class<T>): T? {
        override fun <T : Any> getValue(key: String, valueType: Class<T>): T {
            return (bluetoothAdapter?.state.let {
                it == BluetoothAdapter.STATE_ON || it == BluetoothAdapter.STATE_TURNING_ON
            }) as T
            })
                as T
        }

        @Suppress("DEPRECATION")
        override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) {
            if (value is Boolean) {
                if (value) {
@@ -95,5 +134,42 @@ class BluetoothMainSwitchPreference(private val bluetoothAdapter: BluetoothAdapt
                }
            }
        }

        @SuppressLint("WrongConstant")
        override fun onFirstObserverAdded() {
            broadcastReceiver =
                object : BroadcastReceiver() {
                    override fun onReceive(context: Context, intent: Intent) {
                        notifyChange(KEY, DataChangeReason.UPDATE)
                    }
                }
            context.registerReceiver(
                broadcastReceiver,
                IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED),
                Context.RECEIVER_EXPORTED_UNAUDITED,
            )
        }

        override fun onLastObserverRemoved() {
            context.unregisterReceiver(broadcastReceiver)
        }
    }

    companion object {
        const val KEY = "use_bluetooth"

        @Suppress("DEPRECATION")
        fun createDataStore(context: Context) =
            createDataStore(context, BluetoothAdapter.getDefaultAdapter())

        fun createDataStore(
            context: Context,
            bluetoothAdapter: BluetoothAdapter?,
        ): BluetoothDataStore = BluetoothStorage(context, bluetoothAdapter)
    }
}

/** Datastore of the bluetooth preference. */
interface BluetoothDataStore : KeyValueStore {
    val bluetoothAdapter: BluetoothAdapter?
}
Loading