Skip to content

Commit 931a741

Browse files
authored
Releases/v1.7.0 (#91)
## Improvements * Automatically detect changes to network state (#90)
1 parent 6e869b7 commit 931a741

File tree

7 files changed

+258
-75
lines changed

7 files changed

+258
-75
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
/.idea/navEditor.xml
99
/.idea/assetWizardSettings.xml
1010
/.idea/**
11+
/.kotlin
1112
.DS_Store
1213
/build
1314
/captures

build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
plugins {
2-
id 'com.android.application' version '8.8.2' apply false
3-
id 'com.android.library' version '8.8.2' apply false
2+
id 'com.android.application' version '8.13.2' apply false
3+
id 'com.android.library' version '8.13.2' apply false
44
id 'org.jetbrains.kotlin.android' version '2.2.10' apply false
55
id 'com.mux.gradle.android.mux-android-distribution' version '1.3.0' apply false
66
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#Thu Aug 01 16:07:05 PDT 2024
22
distributionBase=GRADLE_USER_HOME
33
distributionPath=wrapper/dists
4-
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
4+
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
55
zipStoreBase=GRADLE_USER_HOME
66
zipStorePath=wrapper/dists

library/build.gradle

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ plugins {
55
}
66

77
android {
8-
compileSdk 36
8+
compileSdk = 36
99

1010
namespace "com.mux.core_android"
1111

1212
defaultConfig {
13-
minSdk 16
13+
minSdk = 16
1414

1515
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
1616
consumerProguardFiles "consumer-rules.pro"
@@ -75,11 +75,12 @@ dependencies {
7575
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2"
7676
implementation 'androidx.annotation:annotation-jvm:1.9.1'
7777

78+
def muxCoreVersion = '8.+'
7879
// dynamic version/compileOnly so the player SDKs on top of this can update core independently
7980
//noinspection GradleDynamicVersion
80-
compileOnly "com.mux:stats.muxcore:8.+"
81+
compileOnly "com.mux:stats.muxcore:$muxCoreVersion"
8182
//noinspection GradleDynamicVersion
82-
testImplementation "com.mux:stats.muxcore:8.+"
83+
testImplementation "com.mux:stats.muxcore:$muxCoreVersion"
8384

8485
testImplementation 'androidx.test.ext:junit-ktx:1.2.1'
8586
testImplementation 'junit:junit:4.13.2'

library/src/main/java/com/mux/stats/sdk/muxstats/MuxDataSdk.kt

Lines changed: 27 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ abstract class MuxDataSdk<Player, PlayerView : View> @JvmOverloads protected con
6161
customOptions: CustomOptions = CustomOptions(),
6262
trackFirstFrame: Boolean = false,
6363
logLevel: LogcatLevel = LogcatLevel.NONE,
64+
networkChangeMonitor: NetworkChangeMonitor = NetworkChangeMonitor(context),
6465
makePlayerId: (context: Context, view: View?) -> String = Factory::generatePlayerId,
6566
makePlayerListener: (
6667
of: MuxDataSdk<Player, PlayerView>
@@ -108,6 +109,9 @@ abstract class MuxDataSdk<Player, PlayerView : View> @JvmOverloads protected con
108109
@Suppress("MemberVisibilityCanBePrivate")
109110
protected val collector: MuxStateCollector
110111

112+
@Suppress("MemberVisibilityCanBePrivate")
113+
protected val networkChangeMonitor: NetworkChangeMonitor
114+
111115
@Suppress("MemberVisibilityCanBePrivate")
112116
protected val displayDensity: Float get() = uiDelegate.displayDensity()
113117

@@ -313,6 +317,7 @@ abstract class MuxDataSdk<Player, PlayerView : View> @JvmOverloads protected con
313317
*/
314318
open fun release() {
315319
// NOTE: If you override this, you must call super()
320+
networkChangeMonitor.release()
316321
playerAdapter.unbindEverything()
317322
muxStats.release()
318323
}
@@ -400,6 +405,10 @@ abstract class MuxDataSdk<Player, PlayerView : View> @JvmOverloads protected con
400405
playerAdapter = makePlayerAdapter(
401406
player, uiDelegate, collector, playerBinding
402407
)
408+
networkChangeMonitor.setListener { networkType, lowData ->
409+
muxStats.networkChange(networkType, lowData)
410+
}
411+
this.networkChangeMonitor = networkChangeMonitor
403412

404413
muxStats.allowLogcatOutput(
405414
logLevel.oneOf(LogcatLevel.DEBUG, LogcatLevel.VERBOSE),
@@ -545,31 +554,29 @@ abstract class MuxDataSdk<Player, PlayerView : View> @JvmOverloads protected con
545554
connectionTypeApi16()
546555
}
547556

557+
override fun getIsNetworkInLowDataMode(): Boolean? {
558+
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.BAKLAVA) {
559+
return contextRef?.let { context ->
560+
val connectivityManager = context.getSystemService(ConnectivityManager::class.java)
561+
val nc: NetworkCapabilities? =
562+
connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
563+
return nc
564+
?.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED)
565+
?.not()
566+
}
567+
} else {
568+
return null
569+
}
570+
}
571+
548572
@RequiresApi(Build.VERSION_CODES.M)
549573
private fun connectionTypeApi23(): String? {
550574
// use let{} so we get both a null-check and a hard ref
551575
return contextRef?.let { context ->
552576
val connectivityManager = context.getSystemService(ConnectivityManager::class.java)
553577
val nc: NetworkCapabilities? =
554578
connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
555-
556-
when {
557-
nc == null -> {
558-
MuxLogger.w(TAG, "Could not get network capabilities")
559-
null
560-
}
561-
562-
nc.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) ->
563-
CONNECTION_TYPE_WIRED
564-
565-
nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ->
566-
CONNECTION_TYPE_WIFI
567-
568-
nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ->
569-
CONNECTION_TYPE_CELLULAR
570-
571-
else -> CONNECTION_TYPE_OTHER
572-
}
579+
nc?.toMuxConnectionType()
573580
}
574581
}
575582

@@ -580,34 +587,7 @@ abstract class MuxDataSdk<Player, PlayerView : View> @JvmOverloads protected con
580587
val connectivityMgr = context
581588
.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
582589
val activeNetwork: NetworkInfo? = connectivityMgr.activeNetworkInfo
583-
584-
if (activeNetwork == null) {
585-
MuxLogger.w(TAG, "Couldn't obtain network info")
586-
null
587-
} else {
588-
when (activeNetwork.type) {
589-
ConnectivityManager.TYPE_ETHERNET -> {
590-
CONNECTION_TYPE_WIRED
591-
}
592-
593-
ConnectivityManager.TYPE_WIFI -> {
594-
CONNECTION_TYPE_WIFI
595-
}
596-
597-
ConnectivityManager.TYPE_MOBILE,
598-
ConnectivityManager.TYPE_MOBILE_DUN,
599-
ConnectivityManager.TYPE_MOBILE_HIPRI,
600-
ConnectivityManager.TYPE_MOBILE_SUPL,
601-
ConnectivityManager.TYPE_WIMAX,
602-
ConnectivityManager.TYPE_MOBILE_MMS -> {
603-
CONNECTION_TYPE_CELLULAR
604-
}
605-
606-
else -> {
607-
CONNECTION_TYPE_OTHER
608-
}
609-
}
610-
}
590+
activeNetwork?.toMuxConnectionType()
611591
} // contextRef?.let {...
612592
}
613593

@@ -658,16 +638,11 @@ abstract class MuxDataSdk<Player, PlayerView : View> @JvmOverloads protected con
658638
private fun getPackageInfoLegacy(ctx: Context): PackageInfo =
659639
ctx.packageManager.getPackageInfo(ctx.packageName, 0)
660640

661-
@TargetApi(Build.VERSION_CODES.TIRAMISU)
641+
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
662642
private fun getPackageInfoApi33(ctx: Context): PackageInfo =
663643
ctx.packageManager.getPackageInfo(ctx.packageName, PackageManager.PackageInfoFlags.of(0))
664644

665645
companion object {
666-
const val CONNECTION_TYPE_CELLULAR = "cellular"
667-
const val CONNECTION_TYPE_WIFI = "wifi"
668-
const val CONNECTION_TYPE_WIRED = "wired"
669-
const val CONNECTION_TYPE_OTHER = "other"
670-
671646
private const val TAG = "MuxDevice"
672647
private const val MUX_DEVICE_ID = "MUX_DEVICE_ID"
673648

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
package com.mux.stats.sdk.muxstats
2+
3+
import android.content.BroadcastReceiver
4+
import android.content.Context
5+
import android.content.Intent
6+
import android.content.IntentFilter
7+
import android.net.ConnectivityManager
8+
import android.net.Network
9+
import android.net.NetworkCapabilities
10+
import android.net.NetworkInfo
11+
import android.os.Build
12+
import android.os.Handler
13+
import android.os.Looper
14+
import androidx.annotation.RequiresApi
15+
16+
/**
17+
* Listens for changes to network connection status from the system.
18+
*
19+
* The implementation may differ depending on android version
20+
*/
21+
interface NetworkChangeMonitor {
22+
companion object {
23+
const val CONNECTION_TYPE_CELLULAR = "cellular"
24+
const val CONNECTION_TYPE_WIFI = "wifi"
25+
const val CONNECTION_TYPE_WIRED = "wired"
26+
const val CONNECTION_TYPE_OTHER = "other"
27+
}
28+
29+
fun setListener(listener: NetworkChangedListener?)
30+
31+
fun release()
32+
33+
fun interface NetworkChangedListener {
34+
fun onNetworkChanged(networkType: String?, restrictedData: Boolean?)
35+
}
36+
}
37+
38+
@JvmSynthetic
39+
internal fun NetworkChangeMonitor(
40+
context: Context,
41+
): NetworkChangeMonitor {
42+
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
43+
NetworkChangeMonitorApi26(context.applicationContext)
44+
} else {
45+
NetworkChangeMonitorApi16(context.applicationContext)
46+
}
47+
}
48+
49+
@JvmSynthetic
50+
@RequiresApi(Build.VERSION_CODES.M)
51+
internal fun NetworkCapabilities.toMuxConnectionType(): String {
52+
return when {
53+
hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)
54+
-> NetworkChangeMonitor.CONNECTION_TYPE_WIRED
55+
56+
hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
57+
-> NetworkChangeMonitor.CONNECTION_TYPE_WIFI
58+
59+
hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
60+
-> NetworkChangeMonitor.CONNECTION_TYPE_CELLULAR
61+
62+
else -> NetworkChangeMonitor.CONNECTION_TYPE_OTHER
63+
}
64+
}
65+
66+
@Suppress("DEPRECATION")
67+
@JvmSynthetic
68+
internal fun NetworkInfo.toMuxConnectionType(): String {
69+
return when (type) {
70+
ConnectivityManager.TYPE_ETHERNET -> NetworkChangeMonitor.CONNECTION_TYPE_WIRED
71+
72+
ConnectivityManager.TYPE_WIFI -> NetworkChangeMonitor.CONNECTION_TYPE_WIFI
73+
74+
ConnectivityManager.TYPE_MOBILE,
75+
ConnectivityManager.TYPE_MOBILE_DUN,
76+
ConnectivityManager.TYPE_MOBILE_HIPRI,
77+
ConnectivityManager.TYPE_MOBILE_SUPL,
78+
ConnectivityManager.TYPE_WIMAX,
79+
ConnectivityManager.TYPE_MOBILE_MMS -> {
80+
NetworkChangeMonitor.CONNECTION_TYPE_CELLULAR
81+
}
82+
83+
else -> NetworkChangeMonitor.CONNECTION_TYPE_OTHER
84+
85+
}
86+
}
87+
88+
/**
89+
* Detects changes to network status using the old BroadcastReceiver method
90+
*
91+
* We use this all the way up to O, because NetworkCallback was not reliable before then
92+
*/
93+
@Suppress("DEPRECATION")
94+
private class NetworkChangeMonitorApi16(
95+
val appContext: Context,
96+
) : NetworkChangeMonitor {
97+
98+
private var connectivityReceiver: ConnectivityReceiver? = null
99+
private var lastSeenConnectionType: String? = null
100+
private var outsideListener: NetworkChangeMonitor.NetworkChangedListener? = null
101+
102+
private fun getConnectivityManager(context: Context): ConnectivityManager {
103+
return context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
104+
}
105+
106+
private fun currentConnectionType(): String? {
107+
val networkInfo = getConnectivityManager(appContext).activeNetworkInfo
108+
val connType = networkInfo?.toMuxConnectionType()
109+
return connType
110+
}
111+
112+
override fun setListener(listener: NetworkChangeMonitor.NetworkChangedListener?) {
113+
this.outsideListener = listener
114+
}
115+
116+
override fun release() {
117+
outsideListener = null
118+
connectivityReceiver?.let { appContext.unregisterReceiver(it) }
119+
connectivityReceiver = null
120+
}
121+
122+
init {
123+
this.connectivityReceiver = ConnectivityReceiver()
124+
val intentFilter = IntentFilter()
125+
intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION)
126+
appContext.registerReceiver(this.connectivityReceiver, intentFilter)
127+
}
128+
129+
inner class ConnectivityReceiver : BroadcastReceiver() {
130+
override fun onReceive(context: Context?, intent: Intent?) {
131+
// when using the system broadcasts, we can safely query ConnectivityManager synchronously
132+
val currentType = currentConnectionType()
133+
if (currentType != lastSeenConnectionType) {
134+
outsideListener?.onNetworkChanged(currentType, null)
135+
lastSeenConnectionType = currentType
136+
}
137+
}
138+
}
139+
}
140+
141+
/**
142+
* Detects Network changes using [ConnectivityManager.NetworkCallback]. The API is available way
143+
* before O, but isn't generally considered reliable before then. The callback method we need
144+
* (onCapabilitiesChanged) isn't guaranteed to be called, and asking for net capabilities
145+
* synchronously from onActive is a no-no because it is a source of data races. So we fall back to
146+
* the intent broadcasts L, M, and N still
147+
*/
148+
@RequiresApi(Build.VERSION_CODES.O)
149+
private class NetworkChangeMonitorApi26(
150+
val appContext: Context,
151+
) : NetworkChangeMonitor {
152+
153+
private val callbackHandler = Handler(Looper.getMainLooper())
154+
155+
private var outsideListener: NetworkChangeMonitor.NetworkChangedListener? = null
156+
private var defaultNetworkCallback: ConnectivityManager.NetworkCallback? = null
157+
158+
override fun setListener(listener: NetworkChangeMonitor.NetworkChangedListener?) {
159+
this.outsideListener = listener
160+
}
161+
162+
override fun release() {
163+
outsideListener = null
164+
defaultNetworkCallback?.let {
165+
val connectivityManager = getConnectivityManager(appContext)
166+
connectivityManager.unregisterNetworkCallback(it)
167+
}
168+
defaultNetworkCallback = null
169+
}
170+
171+
private fun getConnectivityManager(context: Context): ConnectivityManager {
172+
return context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
173+
}
174+
175+
private fun handleNetworkCapabilities(networkCapabilities: NetworkCapabilities) {
176+
val connType = networkCapabilities.toMuxConnectionType()
177+
val lowBandwidth = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA) {
178+
!networkCapabilities.hasCapability(
179+
NetworkCapabilities.NET_CAPABILITY_NOT_BANDWIDTH_CONSTRAINED
180+
)
181+
} else {
182+
null
183+
}
184+
185+
outsideListener?.onNetworkChanged(connType, lowBandwidth)
186+
}
187+
188+
init {
189+
val networkCallback = object : ConnectivityManager.NetworkCallback() {
190+
override fun onCapabilitiesChanged(
191+
network: Network,
192+
networkCapabilities: NetworkCapabilities
193+
) {
194+
callbackHandler.post { handleNetworkCapabilities(networkCapabilities) }
195+
}
196+
197+
override fun onLost(network: Network) {
198+
callbackHandler.post { outsideListener?.onNetworkChanged(null, null) }
199+
}
200+
}
201+
202+
getConnectivityManager(appContext).registerDefaultNetworkCallback(networkCallback)
203+
this.defaultNetworkCallback = networkCallback
204+
}
205+
}

0 commit comments

Comments
 (0)