Skip to content

Commit fec516c

Browse files
aitorvskarlenDimla
andauthored
Implement pm.safeGetInstalledApplications to avoid binder crash when too many apps are installed (#7026)
Task/Issue URL: https://app.asana.com/1/137249556945/project/1202552961248957/task/1211783744622977?focus=true ### Description See attached task description ### Steps to test this PR Smoke test AppTP and VPN - Exclusion list - App TP trackers (main feed and recent activity) - Install new app --------- Co-authored-by: Karl Dimla <klmbdimla@gmail.com>
1 parent 205204e commit fec516c

File tree

15 files changed

+62
-12
lines changed

15 files changed

+62
-12
lines changed

app-tracking-protection/vpn-impl/src/main/java/com/duckduckgo/mobile/android/vpn/apps/TrackingProtectionAppsRepository.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import android.content.pm.ApplicationInfo
2121
import android.content.pm.PackageManager
2222
import com.duckduckgo.common.utils.DispatcherProvider
2323
import com.duckduckgo.common.utils.extensions.isDdgApp
24+
import com.duckduckgo.common.utils.extensions.safeGetInstalledApplications
2425
import com.duckduckgo.di.scopes.AppScope
2526
import com.duckduckgo.mobile.android.vpn.apps.TrackingProtectionAppsRepository.ProtectionState
2627
import com.duckduckgo.mobile.android.vpn.apps.TrackingProtectionAppsRepository.ProtectionState.PROTECTED
@@ -113,15 +114,15 @@ class RealTrackingProtectionAppsRepository @Inject constructor(
113114

114115
private fun refreshInstalledApps() {
115116
logcat { "Excluded Apps: refreshInstalledApps" }
116-
installedApps = packageManager.getInstalledApplications(PackageManager.GET_META_DATA)
117+
installedApps = packageManager.safeGetInstalledApplications(context)
117118
.asSequence()
118119
.filterNot { shouldNotBeShown(it) }
119120
}
120121

121122
override suspend fun getExclusionAppsList(): List<String> = withContext(dispatcherProvider.io()) {
122123
val exclusionList = appTrackerRepository.getAppExclusionList()
123124
val manualExclusionList = appTrackerRepository.getManualAppExclusionList()
124-
return@withContext packageManager.getInstalledApplications(PackageManager.GET_META_DATA)
125+
return@withContext packageManager.safeGetInstalledApplications(context)
125126
.asSequence()
126127
.filter { isExcludedFromAppTP(it, exclusionList, manualExclusionList) }
127128
.sortedBy { it.name }

app-tracking-protection/vpn-impl/src/test/java/com/duckduckgo/mobile/android/vpn/apps/TrackingProtectionAppsRepositoryTest.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.duckduckgo.mobile.android.vpn.apps
1818

19+
import android.annotation.SuppressLint
1920
import android.content.pm.ApplicationInfo
2021
import android.content.pm.PackageManager
2122
import android.content.pm.PackageManager.NameNotFoundException
@@ -49,6 +50,7 @@ class TrackingProtectionAppsRepositoryTest {
4950
private lateinit var trackingProtectionAppsRepository: TrackingProtectionAppsRepository
5051

5152
@Before
53+
@SuppressLint("DenyListedApi")
5254
fun setup() {
5355
whenever(packageManager.getInstalledApplications(PackageManager.GET_META_DATA)).thenReturn(INSTALLED_APPS.asApplicationInfo())
5456
whenever(packageManager.getApplicationLabel(any())).thenReturn("App Name")

app-tracking-protection/vpn-internal/src/main/java/com/duckduckgo/vpn/internal/feature/VpnInternalSettingsActivity.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ package com.duckduckgo.vpn.internal.feature
1818

1919
import android.content.Intent
2020
import android.content.pm.ApplicationInfo
21-
import android.content.pm.PackageManager
2221
import android.os.Bundle
2322
import android.widget.CompoundButton
2423
import androidx.core.view.isVisible
@@ -35,6 +34,7 @@ import com.duckduckgo.common.ui.viewbinding.viewBinding
3534
import com.duckduckgo.common.utils.ConflatedJob
3635
import com.duckduckgo.common.utils.DispatcherProvider
3736
import com.duckduckgo.common.utils.extensions.isDdgApp
37+
import com.duckduckgo.common.utils.extensions.safeGetInstalledApplications
3838
import com.duckduckgo.di.scopes.ActivityScope
3939
import com.duckduckgo.mobile.android.app.tracking.AppTrackingProtection
4040
import com.duckduckgo.mobile.android.vpn.apps.isSystemApp
@@ -164,7 +164,7 @@ class VpnInternalSettingsActivity : DuckDuckGoActivity() {
164164
}
165165

166166
private fun refreshInstalledApps() {
167-
installedApps = packageManager.getInstalledApplications(PackageManager.GET_META_DATA)
167+
installedApps = packageManager.safeGetInstalledApplications(this.applicationContext)
168168
.asSequence()
169169
.filterNot { shouldNotBeShown(it) }
170170
}

app-tracking-protection/vpn-internal/src/main/java/com/duckduckgo/vpn/internal/feature/rules/ExceptionRulesDebugActivity.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import com.duckduckgo.common.ui.DuckDuckGoActivity
2929
import com.duckduckgo.common.ui.viewbinding.viewBinding
3030
import com.duckduckgo.common.utils.ConflatedJob
3131
import com.duckduckgo.common.utils.DispatcherProvider
32+
import com.duckduckgo.common.utils.extensions.safeGetInstalledApplications
3233
import com.duckduckgo.di.scopes.ActivityScope
3334
import com.duckduckgo.mobile.android.vpn.stats.AppTrackerBlockingStatsRepository
3435
import com.duckduckgo.mobile.android.vpn.store.VpnDatabase
@@ -113,7 +114,7 @@ class ExceptionRulesDebugActivity : DuckDuckGoActivity(), RuleTrackerView.RuleTr
113114
}
114115

115116
private fun getAppTrackers(): List<InstalledAppTrackers> {
116-
return packageManager.getInstalledApplications(PackageManager.GET_META_DATA)
117+
return packageManager.safeGetInstalledApplications(this.applicationContext)
117118
.asSequence()
118119
.map { InstalledApp(it.packageName, packageManager.getApplicationLabel(it).toString()) }
119120
.map {

app/src/main/java/com/duckduckgo/app/systemsearch/DeviceAppLookup.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.duckduckgo.app.systemsearch
1818

19+
import android.annotation.SuppressLint
1920
import android.content.Intent
2021
import android.content.pm.PackageManager
2122
import android.content.pm.PackageManager.GET_META_DATA
@@ -92,6 +93,7 @@ interface DeviceAppListProvider {
9293
suspend fun get(): List<DeviceApp>
9394
}
9495

96+
@SuppressLint("DenyListedApi")
9597
class InstalledDeviceAppListProvider(
9698
private val packageManager: PackageManager,
9799
private val dispatcherProvider: DispatcherProvider,

common/common-utils/src/main/java/com/duckduckgo/common/utils/extensions/PackageManagerExtension.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616

1717
package com.duckduckgo.common.utils.extensions
1818

19+
import android.annotation.SuppressLint
20+
import android.content.Context
21+
import android.content.pm.ApplicationInfo
1922
import android.content.pm.PackageManager
2023
import android.graphics.drawable.Drawable
2124

@@ -24,3 +27,13 @@ fun PackageManager.safeGetApplicationIcon(packageName: String): Drawable? {
2427
getApplicationIcon(packageName)
2528
}.getOrNull()
2629
}
30+
31+
@SuppressLint("DenyListedApi")
32+
fun PackageManager.safeGetInstalledApplications(context: Context): List<ApplicationInfo> {
33+
return try {
34+
this.getInstalledApplications(PackageManager.GET_META_DATA)
35+
} catch (_: Throwable) {
36+
// at least we know our app is installed :)
37+
listOf(this.getApplicationInfo(context.packageName, PackageManager.GET_META_DATA))
38+
}
39+
}

lint-rules/src/main/java/com/duckduckgo/lint/DenyListedApiDetector.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ import java.util.EnumSet
4040
*/
4141
internal class DenyListedApiDetector : Detector(), SourceCodeScanner, XmlScanner {
4242
private val config = DenyListConfig(
43+
DenyListedEntry(
44+
className = "android.content.pm.PackageManager",
45+
functionName = "getInstalledApplications",
46+
errorMessage = "Use PackageManager.safeGetInstalledApplications() instead"
47+
),
4348
DenyListedEntry(
4449
className = "kotlinx.coroutines.flow.FlowKt__ReduceKt",
4550
functionName = "first",

network-protection/network-protection-impl/src/main/java/com/duckduckgo/networkprotection/impl/autoexclude/AutoExcludeAppsRepository.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616

1717
package com.duckduckgo.networkprotection.impl.autoexclude
1818

19+
import android.content.Context
1920
import android.content.pm.PackageManager
2021
import com.duckduckgo.app.di.AppCoroutineScope
2122
import com.duckduckgo.common.utils.DispatcherProvider
23+
import com.duckduckgo.common.utils.extensions.safeGetInstalledApplications
2224
import com.duckduckgo.di.scopes.AppScope
2325
import com.duckduckgo.networkprotection.store.db.AutoExcludeDao
2426
import com.duckduckgo.networkprotection.store.db.FlaggedIncompatibleApp
@@ -93,6 +95,7 @@ class RealAutoExcludeAppsRepository @Inject constructor(
9395
@AppCoroutineScope private val appCoroutineScope: CoroutineScope,
9496
private val autoExcludeDao: AutoExcludeDao,
9597
private val packageManager: PackageManager,
98+
private val context: Context,
9699
) : AutoExcludeAppsRepository {
97100

98101
private val autoExcludeList: Deferred<List<VpnIncompatibleApp>> = appCoroutineScope.async(start = CoroutineStart.LAZY) {
@@ -133,7 +136,7 @@ class RealAutoExcludeAppsRepository @Inject constructor(
133136

134137
override suspend fun getInstalledIncompatibleApps(): List<VpnIncompatibleApp> {
135138
return withContext(dispatcherProvider.io()) {
136-
val installedApps = packageManager.getInstalledApplications(PackageManager.GET_META_DATA).map { it.packageName }
139+
val installedApps = packageManager.safeGetInstalledApplications(context).map { it.packageName }
137140

138141
autoExcludeList.await().filter {
139142
installedApps.contains(it.packageName)

network-protection/network-protection-impl/src/main/java/com/duckduckgo/networkprotection/impl/exclusion/NetpExclusionListRepository.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616

1717
package com.duckduckgo.networkprotection.impl.exclusion
1818

19+
import android.content.Context
1920
import android.content.pm.ApplicationInfo
2021
import android.content.pm.PackageManager
2122
import com.duckduckgo.common.utils.DispatcherProvider
23+
import com.duckduckgo.common.utils.extensions.safeGetInstalledApplications
2224
import com.duckduckgo.di.scopes.AppScope
2325
import com.duckduckgo.networkprotection.impl.autoexclude.AutoExcludeAppsRepository
2426
import com.duckduckgo.networkprotection.impl.settings.NetPSettingsLocalConfig
@@ -42,6 +44,7 @@ class RealNetPExclusionListRepository @Inject constructor(
4244
private val netPSettingsLocalConfig: NetPSettingsLocalConfig,
4345
private val dispatcherProvider: DispatcherProvider,
4446
private val packageManager: PackageManager,
47+
private val context: Context,
4548
) : NetPExclusionListRepository {
4649
override suspend fun getExcludedAppPackages(): List<String> {
4750
return withContext(dispatcherProvider.io()) {
@@ -52,7 +55,7 @@ class RealNetPExclusionListRepository @Inject constructor(
5255
emptyList()
5356
}
5457

55-
packageManager.getInstalledApplications(PackageManager.GET_META_DATA).asSequence()
58+
packageManager.safeGetInstalledApplications(context).asSequence()
5659
.filter { isExcludedFromVpn(it, manuallyExcludedApps, autoExcludeApps) }
5760
.sortedBy { it.name }
5861
.map { it.packageName }

network-protection/network-protection-impl/src/main/java/com/duckduckgo/networkprotection/impl/exclusion/systemapps/SystemAppsExclusionRepository.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@
1616

1717
package com.duckduckgo.networkprotection.impl.exclusion.systemapps
1818

19+
import android.content.Context
1920
import android.content.SharedPreferences
2021
import android.content.pm.ApplicationInfo
2122
import android.content.pm.PackageManager
2223
import androidx.core.content.edit
2324
import com.duckduckgo.common.utils.DispatcherProvider
25+
import com.duckduckgo.common.utils.extensions.safeGetInstalledApplications
2426
import com.duckduckgo.data.store.api.SharedPreferencesProvider
2527
import com.duckduckgo.di.scopes.AppScope
2628
import com.duckduckgo.feature.toggles.api.Toggle.State
@@ -68,6 +70,7 @@ class RealSystemAppsExclusionRepository @Inject constructor(
6870
private val packageManager: PackageManager,
6971
private val systemAppOverridesProvider: SystemAppOverridesProvider,
7072
private val dispatcherProvider: DispatcherProvider,
73+
private val context: Context,
7174
) : SystemAppsExclusionRepository {
7275
private val preferences: SharedPreferences by lazy {
7376
sharedPreferencesProvider.getSharedPreferences(
@@ -200,7 +203,7 @@ class RealSystemAppsExclusionRepository @Inject constructor(
200203
}
201204

202205
private fun getOtherSystemApps(): Set<String> {
203-
return packageManager.getInstalledApplications(PackageManager.GET_META_DATA)
206+
return packageManager.safeGetInstalledApplications(context)
204207
.asSequence()
205208
.filter { it.isSystemApp() && !it.isSystemAppOveridden() && !it.isCategorized() }
206209
.map { it.packageName }

0 commit comments

Comments
 (0)