1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.network.tether 18 19 import android.bluetooth.BluetoothAdapter 20 import android.bluetooth.BluetoothPan 21 import android.bluetooth.BluetoothProfile 22 import android.content.BroadcastReceiver 23 import android.content.Context 24 import android.content.Intent 25 import android.content.IntentFilter 26 import android.net.ConnectivityManager 27 import android.net.TetheringManager 28 import android.os.Handler 29 import android.os.Looper 30 import com.android.settings.R 31 import com.android.settings.datausage.DataSaverBackend 32 import com.android.settingslib.datastore.KeyValueStore 33 import com.android.settingslib.datastore.KeyedDataObservable 34 import com.android.settingslib.metadata.PreferenceAvailabilityProvider 35 import com.android.settingslib.metadata.PreferenceLifecycleContext 36 import com.android.settingslib.metadata.PreferenceLifecycleProvider 37 import com.android.settingslib.metadata.ReadWritePermit 38 import com.android.settingslib.metadata.SensitivityLevel 39 import com.android.settingslib.metadata.SwitchPreference 40 import java.util.concurrent.atomic.AtomicReference 41 42 // LINT.IfChange 43 @Suppress("DEPRECATION") 44 class BluetoothTetherSwitchPreference : 45 SwitchPreference(KEY, R.string.bluetooth_tether_checkbox_text), 46 PreferenceAvailabilityProvider, 47 PreferenceLifecycleProvider { 48 49 private var tetherChangeReceiver: BroadcastReceiver? = null 50 51 override val summary: Int 52 get() = R.string.bluetooth_tethering_subtext 53 54 override val keywords: Int 55 get() = R.string.keywords_hotspot_tethering 56 storagenull57 override fun storage(context: Context): KeyValueStore = BluetoothTetherStore(context) 58 59 override fun isAvailable(context: Context): Boolean { 60 BluetoothAdapter.getDefaultAdapter() ?: return false 61 val tetheringManager = context.getSystemService(TetheringManager::class.java) 62 val bluetoothRegexs = tetheringManager?.tetherableBluetoothRegexs 63 return bluetoothRegexs?.isNotEmpty() == true 64 } 65 isEnablednull66 override fun isEnabled(context: Context): Boolean { 67 val adapter = BluetoothAdapter.getDefaultAdapter() ?: return false 68 val btState = adapter.state 69 /* TODO: when bluetooth is off, btstate will be `state_turning_on` -> `state_off` -> 70 `state_turning_on` -> `state_on`, causing preference enable status incorrect. */ 71 when (btState) { 72 BluetoothAdapter.STATE_TURNING_OFF, 73 BluetoothAdapter.STATE_TURNING_ON -> return false 74 else -> {} 75 } 76 val dataSaverBackend = DataSaverBackend(context) 77 return !dataSaverBackend.isDataSaverEnabled 78 } 79 getReadPermitnull80 override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) = 81 ReadWritePermit.ALLOW 82 83 override fun getWritePermit( 84 context: Context, 85 value: Boolean?, 86 callingPid: Int, 87 callingUid: Int, 88 ) = ReadWritePermit.ALLOW 89 90 override val sensitivityLevel: Int 91 get() = SensitivityLevel.LOW_SENSITIVITY 92 93 override fun onCreate(context: PreferenceLifecycleContext) { 94 val receiver = 95 object : BroadcastReceiver() { 96 override fun onReceive(content: Context, intent: Intent) { 97 when (intent.action) { 98 TetheringManager.ACTION_TETHER_STATE_CHANGED, 99 Intent.ACTION_MEDIA_SHARED, 100 Intent.ACTION_MEDIA_UNSHARED, 101 BluetoothAdapter.ACTION_STATE_CHANGED, 102 BluetoothPan.ACTION_TETHERING_STATE_CHANGED -> 103 context.notifyPreferenceChange(KEY) 104 } 105 } 106 } 107 tetherChangeReceiver = receiver 108 var filter = IntentFilter(TetheringManager.ACTION_TETHER_STATE_CHANGED) 109 val intent = context.registerReceiver(receiver, filter) 110 111 filter = IntentFilter() 112 filter.addAction(Intent.ACTION_MEDIA_SHARED) 113 filter.addAction(Intent.ACTION_MEDIA_UNSHARED) 114 filter.addDataScheme("file") 115 context.registerReceiver(receiver, filter) 116 117 filter = IntentFilter() 118 filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED) 119 filter.addAction(BluetoothPan.ACTION_TETHERING_STATE_CHANGED) 120 context.registerReceiver(receiver, filter) 121 } 122 onDestroynull123 override fun onDestroy(context: PreferenceLifecycleContext) { 124 tetherChangeReceiver?.let { 125 context.unregisterReceiver(it) 126 tetherChangeReceiver = null 127 } 128 } 129 130 @Suppress("UNCHECKED_CAST") 131 private class BluetoothTetherStore(private val context: Context) : 132 KeyedDataObservable<String>(), KeyValueStore { 133 134 val bluetoothPan = AtomicReference<BluetoothPan>() 135 containsnull136 override fun contains(key: String) = key == KEY 137 138 override fun <T : Any> getValue(key: String, valueType: Class<T>): T? { 139 // TODO: support async operation in background thread 140 val adapter = BluetoothAdapter.getDefaultAdapter() ?: return false as T 141 if (bluetoothPan.get() == null) { 142 val profileServiceListener: BluetoothProfile.ServiceListener = 143 object : BluetoothProfile.ServiceListener { 144 override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) { 145 if (bluetoothPan.get() == null) { 146 bluetoothPan.set(proxy as BluetoothPan) 147 notifyChange(KEY, 0) 148 } 149 } 150 151 override fun onServiceDisconnected(profile: Int) { 152 /* Do nothing */ 153 } 154 } 155 // TODO: adapter.closeProfileProxy(bluetoothPan.get()) 156 adapter.getProfileProxy( 157 context.applicationContext, 158 profileServiceListener, 159 BluetoothProfile.PAN, 160 ) 161 } 162 163 val btState = adapter.state 164 val pan = bluetoothPan.get() 165 return ((btState == BluetoothAdapter.STATE_ON || 166 btState == BluetoothAdapter.STATE_TURNING_OFF) && pan != null && pan.isTetheringOn) 167 as T? 168 } 169 setValuenull170 override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) { 171 if (value == null) return 172 val connectivityManager = 173 context.getSystemService(ConnectivityManager::class.java) ?: return 174 if (value as Boolean) { 175 val handler by lazy { Handler(Looper.getMainLooper()) } 176 val startTetheringCallback = OnStartTetheringCallback() 177 fun startTethering() { 178 connectivityManager.startTethering( 179 ConnectivityManager.TETHERING_BLUETOOTH, 180 true, 181 startTetheringCallback, 182 handler, 183 ) 184 } 185 186 val adapter = BluetoothAdapter.getDefaultAdapter() 187 if (adapter.state == BluetoothAdapter.STATE_OFF) { 188 adapter.enable() 189 val filter = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED) 190 val tetherChangeReceiver = 191 object : BroadcastReceiver() { 192 override fun onReceive(context: Context, intent: Intent) { 193 if ( 194 intent.getIntExtra( 195 BluetoothAdapter.EXTRA_STATE, 196 BluetoothAdapter.ERROR, 197 ) == BluetoothAdapter.STATE_ON 198 ) { 199 startTethering() 200 context.unregisterReceiver(this) 201 } 202 } 203 } 204 val intent = context.registerReceiver(tetherChangeReceiver, filter) 205 if (intent != null) tetherChangeReceiver.onReceive(context, intent) 206 } else { 207 startTethering() 208 } 209 } else { 210 connectivityManager.stopTethering(ConnectivityManager.TETHERING_BLUETOOTH) 211 } 212 } 213 214 private inner class OnStartTetheringCallback : 215 ConnectivityManager.OnStartTetheringCallback() { onTetheringStartednull216 override fun onTetheringStarted() { 217 notifyChange(KEY, 0) 218 } 219 onTetheringFailednull220 override fun onTetheringFailed() { 221 notifyChange(KEY, 0) 222 } 223 } 224 } 225 226 companion object { 227 const val KEY = "enable_bluetooth_tethering" 228 } 229 } 230 // LINT.ThenChange(TetherSettings.java) 231