1 /*
<lambda>null2  * Copyright (C) 2023 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
18 
19 import android.bluetooth.BluetoothAdapter
20 import android.bluetooth.BluetoothManager
21 import android.bluetooth.BluetoothPan
22 import android.bluetooth.BluetoothProfile
23 import android.content.Context
24 import android.content.IntentFilter
25 import android.net.TetheringInterface
26 import android.net.TetheringManager
27 import com.android.settingslib.spaprivileged.framework.common.broadcastReceiverFlow
28 import kotlinx.coroutines.Dispatchers
29 import kotlinx.coroutines.ExperimentalCoroutinesApi
30 import kotlinx.coroutines.asExecutor
31 import kotlinx.coroutines.channels.awaitClose
32 import kotlinx.coroutines.flow.Flow
33 import kotlinx.coroutines.flow.callbackFlow
34 import kotlinx.coroutines.flow.combine
35 import kotlinx.coroutines.flow.conflate
36 import kotlinx.coroutines.flow.flatMapLatest
37 import kotlinx.coroutines.flow.flowOf
38 import kotlinx.coroutines.flow.flowOn
39 import kotlinx.coroutines.flow.merge
40 import kotlinx.coroutines.launch
41 
42 class TetheredRepository(private val context: Context) {
43     private val tetheringManager = context.getSystemService(TetheringManager::class.java)!!
44 
45     private val adapter = context.getSystemService(BluetoothManager::class.java)!!.adapter
46 
47     fun tetheredTypesFlow(): Flow<Set<Int>> =
48         combine(
49             tetheredInterfacesFlow(),
50             isBluetoothTetheringOnFlow(),
51         ) { tetheringInterfaces, isBluetoothTetheringOn ->
52             val mutableSet = tetheringInterfaces.map { it.type }.toMutableSet()
53             if (isBluetoothTetheringOn) mutableSet += TetheringManager.TETHERING_BLUETOOTH
54             mutableSet
55         }.conflate().flowOn(Dispatchers.Default)
56 
57     private fun tetheredInterfacesFlow(): Flow<Set<TetheringInterface>> = callbackFlow {
58         val callback = object : TetheringManager.TetheringEventCallback {
59             override fun onTetheredInterfacesChanged(interfaces: Set<TetheringInterface>) {
60                 trySend(interfaces)
61             }
62         }
63 
64         tetheringManager.registerTetheringEventCallback(Dispatchers.Default.asExecutor(), callback)
65 
66         awaitClose { tetheringManager.unregisterTetheringEventCallback(callback) }
67     }.conflate().flowOn(Dispatchers.Default)
68 
69     @OptIn(ExperimentalCoroutinesApi::class)
70     private fun isBluetoothTetheringOnFlow(): Flow<Boolean> =
71         merge(
72             flowOf(null), // kick an initial value
73             context.broadcastReceiverFlow(IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)),
74         ).flatMapLatest {
75             if (adapter?.getState() == BluetoothAdapter.STATE_ON) {
76                 isBluetoothPanTetheringOnFlow()
77             } else {
78                 flowOf(false)
79             }
80         }.conflate().flowOn(Dispatchers.Default)
81 
82     private fun isBluetoothPanTetheringOnFlow() = callbackFlow {
83         var connectedProxy: BluetoothProfile? = null
84 
85         val listener = object : BluetoothProfile.ServiceListener {
86             override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) {
87                 connectedProxy = proxy
88                 launch(Dispatchers.Default) {
89                     trySend((proxy as BluetoothPan).isTetheringOn)
90                 }
91             }
92 
93             override fun onServiceDisconnected(profile: Int) {}
94         }
95 
96         adapter?.getProfileProxy(context, listener, BluetoothProfile.PAN)
97 
98         awaitClose {
99             connectedProxy?.let { adapter?.closeProfileProxy(BluetoothProfile.PAN, it) }
100         }
101     }.conflate().flowOn(Dispatchers.Default)
102 }
103