/* * 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.bluetooth.BluetoothPan import android.bluetooth.BluetoothProfile import android.content.Context import android.content.IntentFilter import android.net.TetheringInterface import android.net.TetheringManager import com.android.settingslib.spaprivileged.framework.common.broadcastReceiverFlow import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.asExecutor import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.merge import kotlinx.coroutines.launch class TetheredRepository(private val context: Context) { private val tetheringManager = context.getSystemService(TetheringManager::class.java)!! private val adapter = context.getSystemService(BluetoothManager::class.java)!!.adapter fun tetheredTypesFlow(): Flow> = combine( tetheredInterfacesFlow(), isBluetoothTetheringOnFlow(), ) { tetheringInterfaces, isBluetoothTetheringOn -> val mutableSet = tetheringInterfaces.map { it.type }.toMutableSet() if (isBluetoothTetheringOn) mutableSet += TetheringManager.TETHERING_BLUETOOTH mutableSet }.conflate().flowOn(Dispatchers.Default) private fun tetheredInterfacesFlow(): Flow> = callbackFlow { val callback = object : TetheringManager.TetheringEventCallback { override fun onTetheredInterfacesChanged(interfaces: Set) { trySend(interfaces) } } tetheringManager.registerTetheringEventCallback(Dispatchers.Default.asExecutor(), callback) awaitClose { tetheringManager.unregisterTetheringEventCallback(callback) } }.conflate().flowOn(Dispatchers.Default) @OptIn(ExperimentalCoroutinesApi::class) private fun isBluetoothTetheringOnFlow(): Flow = merge( flowOf(null), // kick an initial value context.broadcastReceiverFlow(IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)), ).flatMapLatest { if (adapter?.getState() == BluetoothAdapter.STATE_ON) { isBluetoothPanTetheringOnFlow() } else { flowOf(false) } }.conflate().flowOn(Dispatchers.Default) private fun isBluetoothPanTetheringOnFlow() = callbackFlow { var connectedProxy: BluetoothProfile? = null val listener = object : BluetoothProfile.ServiceListener { override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) { connectedProxy = proxy launch(Dispatchers.Default) { trySend((proxy as BluetoothPan).isTetheringOn) } } override fun onServiceDisconnected(profile: Int) {} } adapter?.getProfileProxy(context, listener, BluetoothProfile.PAN) awaitClose { connectedProxy?.let { adapter?.closeProfileProxy(BluetoothProfile.PAN, it) } } }.conflate().flowOn(Dispatchers.Default) }