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
This commit is contained in:
Jacky Wang
2024-12-10 07:11:09 +00:00
committed by Android (Google) Code Review
13 changed files with 361 additions and 150 deletions

View File

@@ -16,6 +16,7 @@
<PreferenceScreen <PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto" xmlns:settings="http://schemas.android.com/apk/res-auto"
android:key="bluetooth_switchbar_screen"
android:title="@string/bluetooth_settings_title"> android:title="@string/bluetooth_settings_title">
<SwitchPreferenceCompat <SwitchPreferenceCompat

View File

@@ -15,8 +15,10 @@
*/ */
package com.android.settings.connecteddevice; package com.android.settings.connecteddevice;
import android.app.Activity;
import android.app.settings.SettingsEnums; import android.app.settings.SettingsEnums;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
@@ -31,6 +33,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.SettingsActivity; import com.android.settings.SettingsActivity;
import com.android.settings.Utils; import com.android.settings.Utils;
import com.android.settings.bluetooth.AlwaysDiscoverable;
import com.android.settings.bluetooth.BluetoothDeviceRenamePreferenceController; import com.android.settings.bluetooth.BluetoothDeviceRenamePreferenceController;
import com.android.settings.bluetooth.BluetoothSwitchPreferenceController; import com.android.settings.bluetooth.BluetoothSwitchPreferenceController;
import com.android.settings.dashboard.DashboardFragment; import com.android.settings.dashboard.DashboardFragment;
@@ -50,7 +53,6 @@ import com.android.settingslib.widget.FooterPreference;
public class BluetoothDashboardFragment extends DashboardFragment { public class BluetoothDashboardFragment extends DashboardFragment {
private static final String TAG = "BluetoothDashboardFrag"; 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 String SLICE_ACTION = "com.android.settings.SEARCH_RESULT_TRAMPOLINE";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -58,6 +60,8 @@ public class BluetoothDashboardFragment extends DashboardFragment {
private SettingsMainSwitchBar mSwitchBar; private SettingsMainSwitchBar mSwitchBar;
private BluetoothSwitchPreferenceController mController; private BluetoothSwitchPreferenceController mController;
private @Nullable AlwaysDiscoverable mAlwaysDiscoverable;
@Override @Override
public int getMetricsCategory() { public int getMetricsCategory() {
return SettingsEnums.BLUETOOTH_FRAGMENT; return SettingsEnums.BLUETOOTH_FRAGMENT;
@@ -81,7 +85,9 @@ public class BluetoothDashboardFragment extends DashboardFragment {
@Override @Override
public void onCreate(Bundle icicle) { public void onCreate(Bundle icicle) {
super.onCreate(icicle); super.onCreate(icicle);
mFooterPreference = findPreference(KEY_BLUETOOTH_SCREEN_FOOTER); if (!isCatalystEnabled()) {
mFooterPreference = findPreference(BluetoothFooterPreference.KEY);
}
} }
@Override @Override
@@ -93,6 +99,9 @@ public class BluetoothDashboardFragment extends DashboardFragment {
@Override @Override
public void onActivityCreated(Bundle savedInstanceState) { public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState); super.onActivityCreated(savedInstanceState);
if (isCatalystEnabled()) {
return;
}
String callingAppPackageName = PasswordUtils.getCallingAppPackageName( String callingAppPackageName = PasswordUtils.getCallingAppPackageName(
getActivity().getActivityToken()); getActivity().getActivityToken());
String action = getIntent() != null ? getIntent().getAction() : ""; 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 @VisibleForTesting
boolean isAlwaysDiscoverable(String callingAppPackageName, String action) { boolean isAlwaysDiscoverable(@Nullable String callingAppPackageName, @Nullable String action) {
return TextUtils.equals(SLICE_ACTION, action) ? false return TextUtils.equals(SLICE_ACTION, action) ? false
: TextUtils.equals(Utils.SETTINGS_PACKAGE_NAME, callingAppPackageName) : TextUtils.equals(Utils.SETTINGS_PACKAGE_NAME, callingAppPackageName)
|| TextUtils.equals(Utils.SYSTEMUI_PACKAGE_NAME, callingAppPackageName); || TextUtils.equals(Utils.SYSTEMUI_PACKAGE_NAME, callingAppPackageName);

View File

@@ -17,7 +17,10 @@ package com.android.settings.connecteddevice
import android.content.Context import android.content.Context
import com.android.settings.R import com.android.settings.R
import com.android.settings.Settings.BluetoothDashboardActivity
import com.android.settings.flags.Flags 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.ProvidePreferenceScreen
import com.android.settingslib.metadata.preferenceHierarchy import com.android.settingslib.metadata.preferenceHierarchy
import com.android.settingslib.preference.PreferenceScreenCreator import com.android.settingslib.preference.PreferenceScreenCreator
@@ -39,7 +42,15 @@ class BluetoothDashboardScreen : PreferenceScreenCreator {
override fun fragmentClass() = BluetoothDashboardFragment::class.java 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 { companion object {
const val KEY = "bluetooth_switchbar_screen" const val KEY = "bluetooth_switchbar_screen"

View File

@@ -0,0 +1,96 @@
/*
* 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"
}
}

View File

@@ -1,99 +0,0 @@
/*
* 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.bluetooth.BluetoothAdapter
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import com.android.settings.R
import com.android.settings.widget.MainSwitchBarMetadata
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.ReadWritePermit
class BluetoothMainSwitchPreference(private val bluetoothAdapter: BluetoothAdapter?) :
MainSwitchBarMetadata, PreferenceLifecycleProvider {
private lateinit var broadcastReceiver: BroadcastReceiver
override val key
get() = "use_bluetooth"
override val title
get() = R.string.bluetooth_main_switch_title
override fun getReadPermit(context: Context, myUid: Int, callingUid: Int) =
ReadWritePermit.ALLOW
override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) =
ReadWritePermit.ALLOW
override fun storage(context: Context) = BluetoothStateStore(bluetoothAdapter)
override fun onStart(context: PreferenceLifecycleContext) {
broadcastReceiver =
object : BroadcastReceiver() {
override fun onReceive(receiverContext: Context, intent: Intent) {
context.notifyPreferenceChange(key)
}
}
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 isEnabled(context: Context): Boolean {
return bluetoothAdapter?.state.let {
it == BluetoothAdapter.STATE_ON || it == BluetoothAdapter.STATE_OFF
}
}
@Suppress("UNCHECKED_CAST")
class BluetoothStateStore(private val bluetoothAdapter: BluetoothAdapter?) :
NoOpKeyedObservable<String>(), KeyValueStore {
override fun contains(key: String) = true
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
}
override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) {
if (value is Boolean) {
if (value) {
bluetoothAdapter?.enable()
} else {
bluetoothAdapter?.disable()
}
}
}
}
}

View File

@@ -0,0 +1,175 @@
/*
* 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.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.metadata.PreferenceMetadata
import com.android.settingslib.metadata.ReadWritePermit
import com.android.settingslib.metadata.SensitivityLevel
@SuppressLint("MissingPermission")
class BluetoothPreference(private val bluetoothDataStore: BluetoothDataStore) :
MainSwitchBarMetadata, PreferenceRestrictionMixin, Preference.OnPreferenceChangeListener {
override val key
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) =
when {
isSatelliteOn(context, 3000) ||
(value == true &&
!WirelessUtils.isRadioAllowed(context, Settings.Global.RADIO_BLUETOOTH)) ->
ReadWritePermit.DISALLOW
else -> ReadWritePermit.ALLOW
}
override val sensitivityLevel
get() = SensitivityLevel.LOW_SENSITIVITY
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
}
}
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
}
// 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")
private class BluetoothStorage(
private val context: Context,
override val bluetoothAdapter: BluetoothAdapter?,
) : AbstractKeyedDataObservable<String>(), BluetoothDataStore {
private var broadcastReceiver: BroadcastReceiver? = null
override fun contains(key: String) = key == KEY && bluetoothAdapter != null
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
}
@Suppress("DEPRECATION")
override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) {
if (value is Boolean) {
if (value) {
bluetoothAdapter?.enable()
} else {
bluetoothAdapter?.disable()
}
}
}
@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?
}

View File

@@ -26,13 +26,13 @@ import android.os.UserManager
import android.provider.Settings import android.provider.Settings
import android.telephony.PhoneStateListener import android.telephony.PhoneStateListener
import android.telephony.TelephonyManager import android.telephony.TelephonyManager
import android.util.Log
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.preference.Preference import androidx.preference.Preference
import com.android.settings.AirplaneModeEnabler import com.android.settings.AirplaneModeEnabler
import com.android.settings.PreferenceRestrictionMixin import com.android.settings.PreferenceRestrictionMixin
import com.android.settings.R import com.android.settings.R
import com.android.settings.Utils import com.android.settings.Utils
import com.android.settings.network.SatelliteRepository.Companion.isSatelliteOn
import com.android.settingslib.RestrictedSwitchPreference import com.android.settingslib.RestrictedSwitchPreference
import com.android.settingslib.datastore.AbstractKeyedDataObservable import com.android.settingslib.datastore.AbstractKeyedDataObservable
import com.android.settingslib.datastore.DataChangeReason import com.android.settingslib.datastore.DataChangeReason
@@ -45,8 +45,6 @@ import com.android.settingslib.metadata.PreferenceLifecycleProvider
import com.android.settingslib.metadata.ReadWritePermit import com.android.settingslib.metadata.ReadWritePermit
import com.android.settingslib.metadata.SensitivityLevel import com.android.settingslib.metadata.SensitivityLevel
import com.android.settingslib.metadata.SwitchPreference import com.android.settingslib.metadata.SwitchPreference
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
// LINT.IfChange // LINT.IfChange
class AirplaneModePreference : class AirplaneModePreference :
@@ -113,9 +111,7 @@ class AirplaneModePreference :
context.getSystemService(TelephonyManager::class.java)?.let { context.getSystemService(TelephonyManager::class.java)?.let {
phoneStateListener = phoneStateListener =
object : PhoneStateListener(Looper.getMainLooper()) { object : PhoneStateListener(Looper.getMainLooper()) {
@Deprecated("Deprecated in Java")
override fun onRadioPowerStateChanged(state: Int) { override fun onRadioPowerStateChanged(state: Int) {
Log.d(TAG, "onRadioPowerStateChanged(), state=$state")
notifyChange(KEY, DataChangeReason.UPDATE) notifyChange(KEY, DataChangeReason.UPDATE)
} }
} }
@@ -163,17 +159,6 @@ class AirplaneModePreference :
context.getSystemService(TelephonyManager::class.java), context.getSystemService(TelephonyManager::class.java),
) )
private fun isSatelliteOn(context: Context): Boolean {
try {
return SatelliteRepository(context)
.requestIsSessionStarted(Executors.newSingleThreadExecutor())
.get(2000, TimeUnit.MILLISECONDS)
} catch (e: Exception) {
Log.e(TAG, "Error to get satellite status : $e")
}
return false
}
private fun showEcmDialog(context: PreferenceLifecycleContext) { private fun showEcmDialog(context: PreferenceLifecycleContext) {
val intent = val intent =
Intent(TelephonyManager.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null) Intent(TelephonyManager.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null)
@@ -192,7 +177,6 @@ class AirplaneModePreference :
} }
companion object { companion object {
const val TAG = "AirplaneModePreference"
const val KEY = Settings.Global.AIRPLANE_MODE_ON const val KEY = Settings.Global.AIRPLANE_MODE_ON
const val DEFAULT_VALUE = false const val DEFAULT_VALUE = false
const val REQUEST_CODE_EXIT_ECM = 1 const val REQUEST_CODE_EXIT_ECM = 1

View File

@@ -17,9 +17,12 @@ package com.android.settings.network
import android.content.Context import android.content.Context
import com.android.settings.R import com.android.settings.R
import com.android.settings.Settings.NetworkDashboardActivity
import com.android.settings.datausage.DataSaverScreen import com.android.settings.datausage.DataSaverScreen
import com.android.settings.flags.Flags import com.android.settings.flags.Flags
import com.android.settings.utils.makeLaunchIntent
import com.android.settingslib.metadata.PreferenceIconProvider import com.android.settingslib.metadata.PreferenceIconProvider
import com.android.settingslib.metadata.PreferenceMetadata
import com.android.settingslib.metadata.ProvidePreferenceScreen import com.android.settingslib.metadata.ProvidePreferenceScreen
import com.android.settingslib.metadata.preferenceHierarchy import com.android.settingslib.metadata.preferenceHierarchy
import com.android.settingslib.preference.PreferenceScreenCreator import com.android.settingslib.preference.PreferenceScreenCreator
@@ -44,6 +47,9 @@ class NetworkDashboardScreen : PreferenceScreenCreator, PreferenceIconProvider {
override fun fragmentClass() = NetworkDashboardFragment::class.java override fun fragmentClass() = NetworkDashboardFragment::class.java
override fun getLaunchIntent(context: Context, metadata: PreferenceMetadata?) =
makeLaunchIntent(context, NetworkDashboardActivity::class.java, metadata?.key)
override fun getPreferenceHierarchy(context: Context) = override fun getPreferenceHierarchy(context: Context) =
preferenceHierarchy(this) { preferenceHierarchy(this) {
+MobileNetworkListScreen.KEY order -15 +MobileNetworkListScreen.KEY order -15

View File

@@ -26,6 +26,8 @@ import androidx.concurrent.futures.CallbackToFutureAdapter
import com.google.common.util.concurrent.Futures.immediateFuture import com.google.common.util.concurrent.Futures.immediateFuture
import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.ListenableFuture
import java.util.concurrent.Executor import java.util.concurrent.Executor
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.asExecutor import kotlinx.coroutines.asExecutor
@@ -203,5 +205,15 @@ class SatelliteRepository(
fun setIsSessionStartedForTesting(isEnabled: Boolean) { fun setIsSessionStartedForTesting(isEnabled: Boolean) {
this.isSessionStarted = isEnabled this.isSessionStarted = isEnabled
} }
fun isSatelliteOn(context: Context, timeoutMs: Long = 2000): Boolean =
try {
SatelliteRepository(context)
.requestIsSessionStarted(Executors.newSingleThreadExecutor())
.get(timeoutMs, TimeUnit.MILLISECONDS)
} catch (e: Exception) {
Log.e(TAG, "Error to get satellite status : $e")
false
}
} }
} }

View File

@@ -26,7 +26,7 @@ import com.android.settingslib.widget.MainSwitchBar
/** Preference abstraction of the [MainSwitchBar] in settings activity. */ /** Preference abstraction of the [MainSwitchBar] in settings activity. */
class MainSwitchBarPreference(context: Context, private val metadata: MainSwitchBarMetadata) : class MainSwitchBarPreference(context: Context, private val metadata: MainSwitchBarMetadata) :
TwoStatePreference(context), OnCheckedChangeListener { TwoStatePreference(context), OnCheckedChangeListener, MainSwitchBar.PreChangeListener {
private val mainSwitchBar: MainSwitchBar = (context as SettingsActivity).switchBar private val mainSwitchBar: MainSwitchBar = (context as SettingsActivity).switchBar
@@ -62,9 +62,12 @@ class MainSwitchBarPreference(context: Context, private val metadata: MainSwitch
override fun onAttached() { override fun onAttached() {
super.onAttached() super.onAttached()
mainSwitchBar.setPreChangeListener(this)
mainSwitchBar.addOnSwitchChangeListener(this) mainSwitchBar.addOnSwitchChangeListener(this)
} }
override fun preChange(isCheck: Boolean) = callChangeListener(isCheck)
override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) { override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) {
// prevent user from toggling the switch before data store operation is done // prevent user from toggling the switch before data store operation is done
isEnabled = false isEnabled = false
@@ -74,6 +77,7 @@ class MainSwitchBarPreference(context: Context, private val metadata: MainSwitch
override fun onDetached() { override fun onDetached() {
mainSwitchBar.removeOnSwitchChangeListener(this) mainSwitchBar.removeOnSwitchChangeListener(this)
mainSwitchBar.setPreChangeListener(null)
super.onDetached() super.onDetached()
} }
} }

View File

@@ -109,7 +109,7 @@ public class SettingsMainSwitchBar extends MainSwitchBar {
return true; return true;
} }
return mSwitch.performClick(); return callPreChangeListener() && mSwitch.performClick();
} }
@Override @Override

View File

@@ -23,13 +23,12 @@ import android.content.IntentFilter
import android.net.wifi.WifiManager import android.net.wifi.WifiManager
import android.os.UserManager import android.os.UserManager
import android.provider.Settings import android.provider.Settings
import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.Preference.OnPreferenceChangeListener import androidx.preference.Preference.OnPreferenceChangeListener
import com.android.settings.PreferenceRestrictionMixin import com.android.settings.PreferenceRestrictionMixin
import com.android.settings.R import com.android.settings.R
import com.android.settings.network.SatelliteRepository import com.android.settings.network.SatelliteRepository.Companion.isSatelliteOn
import com.android.settings.network.SatelliteWarningDialogActivity import com.android.settings.network.SatelliteWarningDialogActivity
import com.android.settingslib.RestrictedSwitchPreference import com.android.settingslib.RestrictedSwitchPreference
import com.android.settingslib.WirelessUtils import com.android.settingslib.WirelessUtils
@@ -42,8 +41,6 @@ import com.android.settingslib.metadata.ReadWritePermit
import com.android.settingslib.metadata.SensitivityLevel import com.android.settingslib.metadata.SensitivityLevel
import com.android.settingslib.metadata.SwitchPreference import com.android.settingslib.metadata.SwitchPreference
import com.android.settingslib.preference.SwitchPreferenceBinding import com.android.settingslib.preference.SwitchPreferenceBinding
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
// LINT.IfChange // LINT.IfChange
class WifiSwitchPreference : class WifiSwitchPreference :
@@ -75,7 +72,7 @@ class WifiSwitchPreference :
val context = preference.context val context = preference.context
// Show dialog and do nothing under satellite mode. // Show dialog and do nothing under satellite mode.
if (context.isSatelliteOn()) { if (isSatelliteOn(context)) {
context.startActivity( context.startActivity(
Intent(context, SatelliteWarningDialogActivity::class.java) Intent(context, SatelliteWarningDialogActivity::class.java)
.putExtra( .putExtra(
@@ -100,7 +97,7 @@ class WifiSwitchPreference :
override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) = override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) =
when { when {
(value == true && !context.isRadioAllowed()) || context.isSatelliteOn() -> (value == true && !context.isRadioAllowed()) || isSatelliteOn(context) ->
ReadWritePermit.DISALLOW ReadWritePermit.DISALLOW
else -> ReadWritePermit.ALLOW else -> ReadWritePermit.ALLOW
} }
@@ -155,22 +152,11 @@ class WifiSwitchPreference :
} }
companion object { companion object {
const val TAG = "WifiSwitchPreference"
const val KEY = "main_toggle_wifi" const val KEY = "main_toggle_wifi"
private fun Context.isRadioAllowed() = private fun Context.isRadioAllowed() =
WirelessUtils.isRadioAllowed(this, Settings.Global.RADIO_WIFI) WirelessUtils.isRadioAllowed(this, Settings.Global.RADIO_WIFI)
private fun Context.isSatelliteOn() =
try {
SatelliteRepository(this)
.requestIsSessionStarted(Executors.newSingleThreadExecutor())
.get(2000, TimeUnit.MILLISECONDS)
} catch (e: Exception) {
Log.e(TAG, "Error to get satellite status : $e")
false
}
private val Intent.wifiState private val Intent.wifiState
get() = getIntExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN) get() = getIntExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN)
} }

View File

@@ -31,45 +31,42 @@ import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
class BluetoothMainSwitchPreferenceTest { class BluetoothPreferenceTest {
@get:Rule val setFlagsRule = SetFlagsRule() @get:Rule val setFlagsRule = SetFlagsRule()
private val context: Context = ApplicationProvider.getApplicationContext() private val context: Context = ApplicationProvider.getApplicationContext()
private lateinit var bluetoothAdapter: BluetoothAdapter private lateinit var bluetoothAdapter: BluetoothAdapter
private lateinit var bluetoothMainSwitchPreference: BluetoothMainSwitchPreference private lateinit var bluetoothPreference: BluetoothPreference
@Before @Before
fun setUp() { fun setUp() {
bluetoothAdapter = spy(BluetoothAdapter.getDefaultAdapter()) bluetoothAdapter = spy(BluetoothAdapter.getDefaultAdapter())
whenever(bluetoothAdapter.state).thenReturn(BluetoothAdapter.STATE_ON) whenever(bluetoothAdapter.state).thenReturn(BluetoothAdapter.STATE_ON)
bluetoothMainSwitchPreference = BluetoothMainSwitchPreference(bluetoothAdapter) bluetoothPreference =
BluetoothPreference(BluetoothPreference.createDataStore(context, bluetoothAdapter))
} }
@Test @Test
fun isEnabled_bluetoothOn_returnTrue() { fun isEnabled_bluetoothOn_returnTrue() {
assertThat(bluetoothMainSwitchPreference.isEnabled(context)).isTrue() assertThat(bluetoothPreference.isEnabled(context)).isTrue()
} }
@Test @Test
fun isEnabled_bluetoothTurningOn_returnFalse() { fun isEnabled_bluetoothTurningOn_returnFalse() {
whenever(bluetoothAdapter.state).thenReturn(BluetoothAdapter.STATE_TURNING_ON) whenever(bluetoothAdapter.state).thenReturn(BluetoothAdapter.STATE_TURNING_ON)
assertThat(bluetoothMainSwitchPreference.isEnabled(context)).isFalse() assertThat(bluetoothPreference.isEnabled(context)).isFalse()
} }
@Test @Test
fun storageSetOff_turnOff() { fun storageSetOff_turnOff() {
bluetoothMainSwitchPreference bluetoothPreference.storage(context).setBoolean(bluetoothPreference.key, false)
.storage(context)
.setBoolean(bluetoothMainSwitchPreference.key, false)
verify(bluetoothAdapter).disable() verify(bluetoothAdapter).disable()
} }
@Test @Test
fun storageSetOn_turnOn() { fun storageSetOn_turnOn() {
bluetoothMainSwitchPreference bluetoothPreference.storage(context).setBoolean(bluetoothPreference.key, true)
.storage(context)
.setBoolean(bluetoothMainSwitchPreference.key, true)
verify(bluetoothAdapter).enable() verify(bluetoothAdapter).enable()
} }