Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.raygun.raygun4android.sample

import android.content.Context
import android.os.SystemClock
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.raygun.raygun4android.network.RaygunNetworkUtils
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import java.io.File

@RunWith(AndroidJUnit4::class)
class RaygunNetworkUtilsTest {
private companion object {
const val EXPECTED_PREFS_FILE = "device_id.xml"
const val EXPECTED_PREFS_DEVICE_ID = "device_id"
}

@Test
fun deviceUuidIsAvailableImmediatelyAndPersistedToDisk() =
runBlocking {
val context = InstrumentationRegistry.getInstrumentation().targetContext
val preferences = context.getSharedPreferences(EXPECTED_PREFS_FILE, Context.MODE_PRIVATE)
assertTrue(preferences.edit().clear().commit())

try {
val firstUuid = RaygunNetworkUtils.getDeviceUuid(context)

assertEquals(firstUuid, preferences.getString(EXPECTED_PREFS_DEVICE_ID, null))
assertTrue(uuidWasWrittenToDisk(context, firstUuid))
} finally {
assertTrue(preferences.edit().clear().commit())
}
}

private fun uuidWasWrittenToDisk(
context: Context,
uuid: String,
): Boolean {
// SharedPreferences appends ".xml" to the supplied preferences name.
val preferencesFile =
File(context.applicationInfo.dataDir, "shared_prefs/$EXPECTED_PREFS_FILE.xml")

repeat(100) {
val persisted =
runCatching { preferencesFile.readText().contains(uuid) }.getOrDefault(false)
if (persisted) {
return true
}
SystemClock.sleep(10)
}

return false
}
}
4 changes: 2 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ agp = "9.2.1"
android-material = "1.14.0"
androidx-appcompat = "1.7.1"
androidx-constraintlayout = "2.2.1"
androidx-core = "1.18.0"
androidx-test-espresso = "3.7.0"
androidx-test-ext = "1.3.0"
androidx-test-rules = "1.7.0"
Expand All @@ -19,14 +18,14 @@ mockito = "5.23.0"
mockito-kotlin = "6.3.0"
okhttp = "5.4.0"
raygun = "6.0.1"
robolectric = "4.16.1"
spotless = "8.6.0"
timber = "5.0.1"

[libraries]
androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" }
androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "androidx-constraintlayout" }
androidx-core = { module = "androidx.core:core", version.ref = "androidx-core" }
androidx-test-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidx-test-espresso" }
androidx-test-espresso-intents = { module = "androidx.test.espresso:espresso-intents", version.ref = "androidx-test-espresso" }
androidx-test-ext-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-test-ext" }
Expand All @@ -43,6 +42,7 @@ mockito-core = { module = "org.mockito:mockito-core", version.ref = "mockito" }
mockito-kotlin = { module = "org.mockito.kotlin:mockito-kotlin", version.ref = "mockito-kotlin" }
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
raygun = { module = "com.raygun:raygun4android", version.ref = "raygun" }
robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" }
timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" }

[bundles]
Expand Down
244 changes: 244 additions & 0 deletions gradle/verification-metadata.xml

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion provider/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ tasks.withType<Zip>().configureEach {
}

dependencies {
implementation(libs.androidx.core)
implementation(libs.androidx.appcompat)
implementation(libs.gson)
implementation(libs.okhttp)
Expand All @@ -71,6 +70,7 @@ dependencies {
testImplementation(libs.junit)
testImplementation(libs.mockito.core)
testImplementation(libs.mockito.kotlin)
testImplementation(libs.robolectric)
}

// Maven Central publishing configuration
Expand Down
47 changes: 37 additions & 10 deletions provider/gradle.lockfile

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import android.annotation.SuppressLint
import android.content.Context
import android.provider.Settings
import androidx.annotation.VisibleForTesting
import androidx.core.content.edit
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.net.HttpURLConnection
Expand Down Expand Up @@ -63,7 +62,7 @@ object RaygunNetworkUtils {
UUID.randomUUID().toString()
}

prefs.edit { putString(PREFS_DEVICE_ID, id) }
prefs.edit().putString(PREFS_DEVICE_ID, id).apply()
return@withContext id
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import android.content.pm.PackageManager
import android.telephony.TelephonyManager
import androidx.annotation.RequiresApi
import androidx.annotation.RequiresPermission
import androidx.core.content.ContextCompat

// Utils class to obtain the mobile network type using the TelephonyManager API.
// Requires READ_PHONE_STATE permission in app, otherwise it will return "Unknown".
Expand All @@ -18,31 +17,34 @@ object TelephonyUtils {
// Requires permission READ_PHONE_STATE.
// Returns "Unknown" if permission is not granted or if the network type cannot be determined.
Comment thread
TheRealAgentK marked this conversation as resolved.
@SuppressLint("MissingPermission")
fun getNetworkType(context: Context): String {
val permissionCheck =
ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE)
if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
return UNKNOWN
}
val type =
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
networkType24(context)
fun getNetworkType(context: Context): String =
try {
val permissionCheck = context.checkSelfPermission(Manifest.permission.READ_PHONE_STATE)
if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
UNKNOWN
} else {
networkTypeLegacy(context)
val type =
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
networkType24(context)
} else {
networkTypeLegacy(context)
}
type?.let(::networkTypeToString) ?: UNKNOWN
}
return networkTypeToString(type)
}
} catch (_: SecurityException) {
UNKNOWN
}

private fun telephonyManager(context: Context): TelephonyManager =
context.applicationContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
private fun telephonyManager(context: Context): TelephonyManager? =
context.applicationContext?.getSystemService(Context.TELEPHONY_SERVICE) as? TelephonyManager

@Suppress("DEPRECATION")
@RequiresPermission(Manifest.permission.READ_PHONE_STATE)
private fun networkTypeLegacy(context: Context): Int = telephonyManager(context).networkType
private fun networkTypeLegacy(context: Context): Int? = telephonyManager(context)?.networkType

@RequiresPermission(Manifest.permission.READ_PHONE_STATE)
@RequiresApi(android.os.Build.VERSION_CODES.N)
private fun networkType24(context: Context): Int = telephonyManager(context).dataNetworkType
private fun networkType24(context: Context): Int? = telephonyManager(context)?.dataNetworkType

// The CDMA-era NETWORK_TYPE_* constants below (1xRTT, CDMA, EHRPD,
// EVDO_0/A/B) were deprecated in API 30 but are intentionally kept in
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
package com.raygun.raygun4android.network

import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.telephony.TelephonyManager
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyNoMoreInteractions
import org.mockito.kotlin.whenever
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config

/**
* Locks in the mapping behaviour of [TelephonyUtils.networkTypeToString].
Expand All @@ -12,7 +23,71 @@ import org.junit.Test
* payloads render identically.
*/
@Suppress("DEPRECATION")
@RunWith(RobolectricTestRunner::class)
class TelephonyUtilsTest {
@Test
fun deniedPhoneStatePermissionReturnsUnknown() {
val context = mock<Context>()
whenever(context.checkSelfPermission(Manifest.permission.READ_PHONE_STATE))
.thenReturn(PackageManager.PERMISSION_DENIED)

assertEquals("Unknown", TelephonyUtils.getNetworkType(context))
verify(context).checkSelfPermission(Manifest.permission.READ_PHONE_STATE)
verifyNoMoreInteractions(context)
}

@Test
@Config(sdk = [Build.VERSION_CODES.M])
fun grantedPhoneStatePermissionReturnsLegacyNetworkTypeOnApi23() {
val context = mock<Context>()
val telephonyManager = mock<TelephonyManager>()
whenever(context.checkSelfPermission(Manifest.permission.READ_PHONE_STATE))
.thenReturn(PackageManager.PERMISSION_GRANTED)
whenever(context.applicationContext).thenReturn(context)
whenever(context.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(telephonyManager)
whenever(telephonyManager.networkType).thenReturn(TelephonyManager.NETWORK_TYPE_LTE)

assertEquals("LTE", TelephonyUtils.getNetworkType(context))
}

@Test
@Config(sdk = [Build.VERSION_CODES.N])
fun grantedPhoneStatePermissionReturnsDataNetworkTypeOnApi24() {
val context = mock<Context>()
val telephonyManager = mock<TelephonyManager>()
whenever(context.checkSelfPermission(Manifest.permission.READ_PHONE_STATE))
.thenReturn(PackageManager.PERMISSION_GRANTED)
whenever(context.applicationContext).thenReturn(context)
whenever(context.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(telephonyManager)
whenever(telephonyManager.dataNetworkType).thenReturn(TelephonyManager.NETWORK_TYPE_LTE)

assertEquals("LTE", TelephonyUtils.getNetworkType(context))
}

@Test
@Config(sdk = [Build.VERSION_CODES.N])
fun unavailableTelephonyServiceReturnsUnknown() {
val context = mock<Context>()
whenever(context.checkSelfPermission(Manifest.permission.READ_PHONE_STATE))
.thenReturn(PackageManager.PERMISSION_GRANTED)
whenever(context.applicationContext).thenReturn(context)
whenever(context.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(null)

assertEquals("Unknown", TelephonyUtils.getNetworkType(context))
}

@Test
@Config(sdk = [Build.VERSION_CODES.N])
fun securityExceptionReturnsUnknown() {
val context = mock<Context>()
whenever(context.checkSelfPermission(Manifest.permission.READ_PHONE_STATE))
.thenReturn(PackageManager.PERMISSION_GRANTED)
whenever(context.applicationContext).thenReturn(context)
whenever(context.getSystemService(Context.TELEPHONY_SERVICE)).thenThrow(SecurityException())

assertEquals("Unknown", TelephonyUtils.getNetworkType(context))
}

@Test
fun mapsCurrentNetworkTypes() {
assertEquals("EDGE", TelephonyUtils.networkTypeToString(TelephonyManager.NETWORK_TYPE_EDGE))
Expand Down
Loading