1 /*
2  * 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.systemui.statusbar.pipeline.wifi.data.repository.prod
18 
19 import android.annotation.SuppressLint
20 import android.net.wifi.ScanResult
21 import android.net.wifi.WifiManager
22 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
23 import com.android.systemui.dagger.qualifiers.Background
24 import com.android.systemui.dagger.qualifiers.Main
25 import com.android.systemui.log.table.TableLogBuffer
26 import com.android.systemui.log.table.logDiffsForTable
27 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
28 import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel
29 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
30 import java.util.concurrent.Executor
31 import kotlinx.coroutines.CoroutineDispatcher
32 import kotlinx.coroutines.CoroutineScope
33 import kotlinx.coroutines.asExecutor
34 import kotlinx.coroutines.channels.awaitClose
35 import kotlinx.coroutines.flow.SharingStarted
36 import kotlinx.coroutines.flow.StateFlow
37 import kotlinx.coroutines.flow.stateIn
38 
39 /**
40  * Object to provide shared helper functions between [WifiRepositoryImpl] and
41  * [WifiRepositoryViaTrackerLib].
42  */
43 object WifiRepositoryHelper {
44     /** Creates a flow that fetches the [DataActivityModel] from [WifiManager]. */
45     fun createActivityFlow(
46         wifiManager: WifiManager,
47         @Main mainExecutor: Executor,
48         scope: CoroutineScope,
49         tableLogBuffer: TableLogBuffer,
50         inputLogger: (String) -> Unit,
51     ): StateFlow<DataActivityModel> {
52         return conflatedCallbackFlow {
53                 val callback =
54                     WifiManager.TrafficStateCallback { state ->
55                         inputLogger.invoke(prettyPrintActivity(state))
56                         trySend(state.toWifiDataActivityModel())
57                     }
58                 wifiManager.registerTrafficStateCallback(mainExecutor, callback)
59                 awaitClose { wifiManager.unregisterTrafficStateCallback(callback) }
60             }
61             .logDiffsForTable(
62                 tableLogBuffer,
63                 columnPrefix = ACTIVITY_PREFIX,
64                 initialValue = ACTIVITY_DEFAULT,
65             )
66             .stateIn(
67                 scope,
68                 started = SharingStarted.WhileSubscribed(),
69                 initialValue = ACTIVITY_DEFAULT,
70             )
71     }
72 
73     /**
74      * Creates a flow that listens for new [ScanResult]s from [WifiManager]. Does not request a scan
75      */
76     fun createNetworkScanFlow(
77         wifiManager: WifiManager,
78         scope: CoroutineScope,
79         @Background dispatcher: CoroutineDispatcher,
80         inputLogger: () -> Unit,
81     ): StateFlow<List<WifiScanEntry>> {
82         return conflatedCallbackFlow {
83                 val callback =
84                     object : WifiManager.ScanResultsCallback() {
85                         @SuppressLint("MissingPermission")
86                         override fun onScanResultsAvailable() {
87                             inputLogger.invoke()
88                             trySend(wifiManager.scanResults.toModel())
89                         }
90                     }
91 
92                 wifiManager.registerScanResultsCallback(dispatcher.asExecutor(), callback)
93 
94                 awaitClose { wifiManager.unregisterScanResultsCallback(callback) }
95             }
96             .stateIn(scope, SharingStarted.Eagerly, emptyList())
97     }
98 
99     private fun List<ScanResult>.toModel(): List<WifiScanEntry> = map { WifiScanEntry(it.SSID) }
100 
101     // TODO(b/292534484): This print should only be done in [MessagePrinter] part of the log buffer.
102     private fun prettyPrintActivity(activity: Int): String {
103         return when (activity) {
104             WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE -> "NONE"
105             WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN -> "IN"
106             WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT -> "OUT"
107             WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT -> "INOUT"
108             else -> "INVALID"
109         }
110     }
111 
112     private const val ACTIVITY_PREFIX = "wifiActivity"
113     val ACTIVITY_DEFAULT = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
114 }
115