mirror of
https://github.com/Hamza417/Inure.git
synced 2026-03-13 10:19:43 +08:00
rewrite: DataLoaderService to fix old optimization issues and crashes
(cherry picked from commit 12ed82e608)
This commit is contained in:
@@ -13,7 +13,7 @@ import app.simple.inure.themes.manager.ThemeManager;
|
||||
|
||||
public class LineNumberEditText extends TypeFaceEditText implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
private final String newline = System.getProperty("line.separator");
|
||||
private final String newline = System.lineSeparator();
|
||||
private Rect rect;
|
||||
private Paint paint;
|
||||
|
||||
|
||||
@@ -1,57 +1,36 @@
|
||||
package app.simple.inure.extensions.viewmodels
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.ServiceConnection
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.DeadObjectException
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import app.simple.inure.R
|
||||
import app.simple.inure.apk.utils.PackageUtils
|
||||
import app.simple.inure.apk.utils.PackageUtils.safeApplicationInfo
|
||||
import app.simple.inure.services.DataLoaderService
|
||||
import app.simple.inure.util.ArrayUtils
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
abstract class PackageUtilsViewModel(application: Application) : WrappedViewModel(application) {
|
||||
|
||||
private var apps: ArrayList<PackageInfo> = arrayListOf()
|
||||
private var uninstalledApps: ArrayList<PackageInfo> = arrayListOf()
|
||||
|
||||
private var dataLoaderService: DataLoaderService? = null
|
||||
private var serviceConnection: ServiceConnection? = null
|
||||
|
||||
@SuppressLint("StaticFieldLeak") // This is an application context
|
||||
private var dataLoaderService: DataLoaderService? = null
|
||||
private var broadcastReceiver: BroadcastReceiver? = null
|
||||
private var intentFilter: IntentFilter = IntentFilter()
|
||||
|
||||
init {
|
||||
intentFilter.addAction(DataLoaderService.APPS_LOADED)
|
||||
intentFilter.addAction(DataLoaderService.UNINSTALLED_APPS_LOADED)
|
||||
intentFilter.addAction(DataLoaderService.INSTALLED_APPS_LOADED)
|
||||
intentFilter.addAction(DataLoaderService.RELOAD_APPS)
|
||||
|
||||
serviceConnection = object : ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||
dataLoaderService = (service as DataLoaderService.LoaderBinder).getService()
|
||||
if (dataLoaderService!!.hasDataLoaded()) {
|
||||
apps = dataLoaderService!!.getInstalledApps()
|
||||
uninstalledApps = dataLoaderService!!.getUninstalledApps()
|
||||
|
||||
onAppsLoaded(apps)
|
||||
onUninstalledAppsLoaded(uninstalledApps)
|
||||
} else {
|
||||
dataLoaderService!!.startLoading()
|
||||
}
|
||||
observeServiceFlows()
|
||||
dataLoaderService?.refresh()
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
@@ -59,65 +38,35 @@ abstract class PackageUtilsViewModel(application: Application) : WrappedViewMode
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
broadcastReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
when (intent?.action) {
|
||||
DataLoaderService.APPS_LOADED -> {
|
||||
apps = dataLoaderService!!.getInstalledApps().clone() as ArrayList<PackageInfo>
|
||||
uninstalledApps = dataLoaderService!!.getUninstalledApps().clone() as ArrayList<PackageInfo>
|
||||
applicationContext().bindService(
|
||||
Intent(applicationContext(), DataLoaderService::class.java),
|
||||
serviceConnection!!,
|
||||
Context.BIND_AUTO_CREATE
|
||||
)
|
||||
}
|
||||
|
||||
onAppsLoaded(apps)
|
||||
onUninstalledAppsLoaded(uninstalledApps)
|
||||
}
|
||||
|
||||
DataLoaderService.UNINSTALLED_APPS_LOADED -> {
|
||||
uninstalledApps = dataLoaderService!!.getUninstalledApps().clone() as ArrayList<PackageInfo>
|
||||
onUninstalledAppsLoaded(uninstalledApps)
|
||||
}
|
||||
|
||||
DataLoaderService.INSTALLED_APPS_LOADED -> {
|
||||
apps = dataLoaderService!!.getInstalledApps().clone() as ArrayList<PackageInfo>
|
||||
onAppsLoaded(apps)
|
||||
}
|
||||
|
||||
DataLoaderService.RELOAD_APPS -> {
|
||||
Log.d("DataLoaderService", "Reloading apps")
|
||||
dataLoaderService!!.refresh()
|
||||
}
|
||||
private fun observeServiceFlows() {
|
||||
viewModelScope.launch {
|
||||
dataLoaderService?.installedApps?.collectLatest { apps ->
|
||||
if (apps != null) {
|
||||
onAppsLoaded(ArrayUtils.deepCopy(apps))
|
||||
}
|
||||
}
|
||||
}
|
||||
viewModelScope.launch {
|
||||
dataLoaderService?.uninstalledApps?.collectLatest { apps ->
|
||||
if (apps != null) {
|
||||
onUninstalledAppsLoaded(ArrayUtils.deepCopy(apps))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LocalBroadcastManager.getInstance(applicationContext()).registerReceiver(broadcastReceiver!!, intentFilter)
|
||||
applicationContext().bindService(
|
||||
Intent(applicationContext(), DataLoaderService::class.java), serviceConnection!!, Context.BIND_AUTO_CREATE)
|
||||
}
|
||||
|
||||
fun getInstalledApps(): ArrayList<PackageInfo> {
|
||||
return dataLoaderService!!.getInstalledApps()
|
||||
}
|
||||
|
||||
fun getUninstalledApps(): ArrayList<PackageInfo> {
|
||||
return dataLoaderService!!.getUninstalledApps()
|
||||
}
|
||||
|
||||
fun getCompleteApps(): List<PackageInfo> {
|
||||
return getInstalledApps() + getUninstalledApps()
|
||||
}
|
||||
|
||||
fun refreshPackageData() {
|
||||
dataLoaderService!!.refresh()
|
||||
}
|
||||
|
||||
fun refreshUninstalledPackageData() {
|
||||
dataLoaderService!!.refreshUninstalled()
|
||||
}
|
||||
|
||||
fun refreshInstalledPackageData() {
|
||||
dataLoaderService!!.refreshInstalled()
|
||||
dataLoaderService?.refresh()
|
||||
}
|
||||
|
||||
// Utility and helper functions remain unchanged
|
||||
protected fun PackageManager.isPackageInstalled(packageName: String): Boolean {
|
||||
while (true) {
|
||||
try {
|
||||
@@ -129,7 +78,7 @@ abstract class PackageUtilsViewModel(application: Application) : WrappedViewMode
|
||||
return true
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
return false
|
||||
} catch (e: DeadObjectException) {
|
||||
} catch (e: android.os.DeadObjectException) {
|
||||
Log.e("PackageUtilsViewModel", "isPackageInstalled: DeadObjectException")
|
||||
}
|
||||
}
|
||||
@@ -138,9 +87,9 @@ abstract class PackageUtilsViewModel(application: Application) : WrappedViewMode
|
||||
protected fun PackageManager.isPackageEnabled(packageName: String): Boolean {
|
||||
return try {
|
||||
getPackageInfo(packageName)!!.safeApplicationInfo.enabled
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
} catch (_: PackageManager.NameNotFoundException) {
|
||||
false
|
||||
} catch (e: NullPointerException) {
|
||||
} catch (_: NullPointerException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -149,17 +98,6 @@ abstract class PackageUtilsViewModel(application: Application) : WrappedViewMode
|
||||
return isPackageInstalled(packageName) && isPackageEnabled(packageName)
|
||||
}
|
||||
|
||||
protected fun PackageManager.getInstalledPackages(flags: Long = PackageUtils.flags): ArrayList<PackageInfo> {
|
||||
val packageInfoList = ArrayList<PackageInfo>()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
packageInfoList.addAll(getInstalledPackages(PackageManager.PackageInfoFlags.of(flags)))
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
packageInfoList.addAll(getInstalledPackages(flags.toInt()))
|
||||
}
|
||||
return ArrayUtils.deepCopy(packageInfoList)
|
||||
}
|
||||
|
||||
protected fun PackageManager.getPackageInfo(packageName: String): PackageInfo? {
|
||||
try {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
@@ -168,7 +106,7 @@ abstract class PackageUtilsViewModel(application: Application) : WrappedViewMode
|
||||
try {
|
||||
@Suppress("DEPRECATION")
|
||||
getPackageInfo(packageName, PackageUtils.flags.toInt())
|
||||
} catch (e: RuntimeException) {
|
||||
} catch (_: RuntimeException) {
|
||||
@Suppress("DEPRECATION")
|
||||
getPackageInfo(packageName, PackageManager.GET_META_DATA)
|
||||
}
|
||||
@@ -176,7 +114,6 @@ abstract class PackageUtilsViewModel(application: Application) : WrappedViewMode
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -192,67 +129,49 @@ abstract class PackageUtilsViewModel(application: Application) : WrappedViewMode
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the app's name from the package id of the same application
|
||||
* @param context of the given environment
|
||||
* @param applicationInfo is [ApplicationInfo] object containing app's
|
||||
* information
|
||||
* @return app's name as [String]
|
||||
*/
|
||||
protected fun getApplicationName(context: Context, applicationInfo: ApplicationInfo): String {
|
||||
while (true) {
|
||||
try {
|
||||
return context.packageManager.getApplicationLabel(applicationInfo).toString()
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
} catch (_: PackageManager.NameNotFoundException) {
|
||||
return context.getString(R.string.unknown)
|
||||
} catch (e: DeadObjectException) {
|
||||
} catch (_: android.os.DeadObjectException) {
|
||||
Log.e("PackageUtilsViewModel", "getApplicationName: DeadObjectException")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected fun ArrayList<PackageInfo>.loadPackageNames(): ArrayList<PackageInfo> {
|
||||
forEach {
|
||||
it.safeApplicationInfo.name = getApplicationName(applicationContext(), it.safeApplicationInfo)
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
open fun onUninstalledAppsLoaded(uninstalledApps: ArrayList<PackageInfo>) {
|
||||
// Log.d("PackageUtilsViewModel", "onUninstalledAppsLoaded: ${uninstalledApps.size}")
|
||||
// Override in subclasses
|
||||
}
|
||||
|
||||
open fun onAppsLoaded(apps: ArrayList<PackageInfo>) {
|
||||
// Log.d("PackageUtilsViewModel", "onAppsLoaded: ${apps.size}")
|
||||
// Override in subclasses
|
||||
}
|
||||
|
||||
open fun getInstalledApps(): ArrayList<PackageInfo> {
|
||||
return ArrayUtils.deepCopy(dataLoaderService?.installedApps?.value ?: ArrayList())
|
||||
}
|
||||
|
||||
open fun getUninstalledApps(): ArrayList<PackageInfo> {
|
||||
return ArrayUtils.deepCopy(dataLoaderService?.uninstalledApps?.value ?: ArrayList())
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
try {
|
||||
serviceConnection?.let {
|
||||
applicationContext().unbindService(it)
|
||||
app.applicationContext.unbindService(it)
|
||||
}
|
||||
} catch (e: java.lang.IllegalStateException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalArgumentException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
try {
|
||||
broadcastReceiver?.let {
|
||||
LocalBroadcastManager.getInstance(applicationContext()).unregisterReceiver(it)
|
||||
}
|
||||
} catch (e: java.lang.IllegalStateException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: IllegalArgumentException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: NullPointerException) {
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "PackageUtilsViewModel"
|
||||
}
|
||||
}
|
||||
@@ -1,265 +1,191 @@
|
||||
package app.simple.inure.services
|
||||
|
||||
import android.app.Service
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.LauncherApps
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.BadParcelableException
|
||||
import android.os.Binder
|
||||
import android.os.Build
|
||||
import android.os.DeadObjectException
|
||||
import android.os.DeadSystemException
|
||||
import android.os.IBinder
|
||||
import android.os.UserHandle
|
||||
import android.util.Log
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||
import app.simple.inure.R
|
||||
import app.simple.inure.apk.utils.PackageUtils.safeApplicationInfo
|
||||
import app.simple.inure.preferences.DevelopmentPreferences
|
||||
import app.simple.inure.util.ArrayUtils.clone
|
||||
import app.simple.inure.util.ArrayUtils.toArrayList
|
||||
import app.simple.inure.util.ConditionUtils.invert
|
||||
import app.simple.inure.util.NullSafety.isNotNull
|
||||
import app.simple.inure.utils.DebloatUtils
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.stream.Collectors
|
||||
|
||||
class DataLoaderService : Service() {
|
||||
|
||||
companion object {
|
||||
const val UNINSTALLED_APPS_LOADED = "uninstalled_apps_loaded"
|
||||
const val INSTALLED_APPS_LOADED = "installed_apps_loaded"
|
||||
const val APPS_LOADED = "apps_loaded"
|
||||
const val RELOAD_APPS = "reload_apps"
|
||||
const val RELOAD_QUICK_APPS = "reload_quick_apps"
|
||||
const val REFRESH = "refresh"
|
||||
|
||||
private const val TAG: String = "DataLoaderService"
|
||||
|
||||
private const val INSTALLED_FLAGS = PackageManager.GET_META_DATA
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private val UNINSTALLED_FLAGS = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
INSTALLED_FLAGS or PackageManager.MATCH_UNINSTALLED_PACKAGES
|
||||
} else {
|
||||
INSTALLED_FLAGS or PackageManager.GET_UNINSTALLED_PACKAGES
|
||||
}
|
||||
|
||||
const val REFRESH = "app.simple.inure.services.DataLoaderService.REFRESH"
|
||||
const val RELOAD_QUICK_APPS = "app.simple.inure.services.DataLoaderService.RELOAD_QUICK_APPS"
|
||||
}
|
||||
|
||||
private var apps: ArrayList<PackageInfo> = arrayListOf()
|
||||
private var uninstalledApps: ArrayList<PackageInfo> = arrayListOf()
|
||||
private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||
|
||||
private val _installedApps = MutableStateFlow<ArrayList<PackageInfo>?>(null)
|
||||
val installedApps: StateFlow<ArrayList<PackageInfo>?> = _installedApps
|
||||
|
||||
private val _uninstalledApps = MutableStateFlow<ArrayList<PackageInfo>?>(null)
|
||||
val uninstalledApps: StateFlow<ArrayList<PackageInfo>?> = _uninstalledApps
|
||||
|
||||
private var isLoading = false
|
||||
private var flags = PackageManager.GET_META_DATA
|
||||
|
||||
private var broadcastReceiver: BroadcastReceiver? = null
|
||||
private var intentFilter: IntentFilter = IntentFilter()
|
||||
private val launcherAppsService: LauncherApps by lazy {
|
||||
getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
|
||||
getSystemService(LAUNCHER_APPS_SERVICE) as LauncherApps
|
||||
}
|
||||
|
||||
private var launcherAppsCallback: LauncherApps.Callback? = null
|
||||
|
||||
inner class LoaderBinder : Binder() {
|
||||
fun getService(): DataLoaderService {
|
||||
return this@DataLoaderService
|
||||
}
|
||||
fun getService(): DataLoaderService = this@DataLoaderService
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder {
|
||||
return LoaderBinder()
|
||||
}
|
||||
override fun onBind(intent: Intent): IBinder = LoaderBinder()
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
Log.d(TAG, "onCreate: Dataloader service created")
|
||||
|
||||
broadcastReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
when (intent?.action) {
|
||||
REFRESH -> {
|
||||
refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
intentFilter.addAction(REFRESH)
|
||||
LocalBroadcastManager.getInstance(applicationContext).registerReceiver(broadcastReceiver!!, intentFilter)
|
||||
|
||||
if (launcherAppsCallback == null) {
|
||||
launcherAppsCallback = object : LauncherApps.Callback() {
|
||||
override fun onPackageRemoved(packageName: String?, user: UserHandle?) {
|
||||
Log.d(TAG, "onPackageRemoved: $packageName")
|
||||
refresh()
|
||||
}
|
||||
|
||||
override fun onPackageAdded(packageName: String?, user: UserHandle?) {
|
||||
Log.d(TAG, "onPackageAdded: $packageName")
|
||||
refresh()
|
||||
}
|
||||
|
||||
override fun onPackageChanged(packageName: String?, user: UserHandle?) {
|
||||
Log.d(TAG, "onPackageChanged: $packageName")
|
||||
refresh()
|
||||
}
|
||||
|
||||
override fun onPackagesAvailable(packageNames: Array<out String>?, user: UserHandle?, replacing: Boolean) {
|
||||
Log.d(TAG, "onPackagesAvailable: ${packageNames?.contentToString()}")
|
||||
refresh()
|
||||
}
|
||||
|
||||
override fun onPackagesUnavailable(packageNames: Array<out String>?, user: UserHandle?, replacing: Boolean) {
|
||||
Log.d(TAG, "onPackagesUnavailable: ${packageNames?.contentToString()}")
|
||||
refresh()
|
||||
}
|
||||
|
||||
override fun onPackageRemoved(packageName: String?, user: UserHandle?) = refresh()
|
||||
override fun onPackageAdded(packageName: String?, user: UserHandle?) = refresh()
|
||||
override fun onPackageChanged(packageName: String?, user: UserHandle?) = refresh()
|
||||
override fun onPackagesAvailable(packageNames: Array<out String>?, user: UserHandle?, replacing: Boolean) = refresh()
|
||||
override fun onPackagesUnavailable(packageNames: Array<out String>?, user: UserHandle?, replacing: Boolean) = refresh()
|
||||
fun refresh() {
|
||||
if (DevelopmentPreferences.get(DevelopmentPreferences.REFRESH_APPS_LIST_USING_LAUNCHER_SERVICE)) {
|
||||
this@DataLoaderService.refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// launcherAppsService.registerCallback(launcherAppsCallback!!)
|
||||
} else {
|
||||
Log.i(TAG, "onCreate: LauncherApps callback already initialized")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
||||
startLoading()
|
||||
refresh()
|
||||
return super.onStartCommand(intent, flags, startId)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
Log.d(TAG, "onDestroy: Dataloader service destroyed")
|
||||
LocalBroadcastManager.getInstance(applicationContext).unregisterReceiver(broadcastReceiver!!)
|
||||
serviceScope.cancel()
|
||||
if (launcherAppsCallback != null) {
|
||||
launcherAppsService.unregisterCallback(launcherAppsCallback)
|
||||
}
|
||||
}
|
||||
|
||||
fun getInstalledApps(): ArrayList<PackageInfo> {
|
||||
if (apps.isNotNull() && apps.isNotEmpty()) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return apps.clone() as ArrayList<PackageInfo>
|
||||
} else {
|
||||
apps = loadInstalledApps() as ArrayList<PackageInfo>
|
||||
return getInstalledApps()
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun getUninstalledApps(): ArrayList<PackageInfo> {
|
||||
return if (uninstalledApps.isNotNull() && uninstalledApps.isNotEmpty()) {
|
||||
uninstalledApps.clone() as ArrayList<PackageInfo>
|
||||
} else {
|
||||
loadUninstalledApps()
|
||||
uninstalledApps.clone() as ArrayList<PackageInfo>
|
||||
}
|
||||
}
|
||||
|
||||
fun startLoading() {
|
||||
fun refresh() {
|
||||
if (isLoading.invert()) {
|
||||
Log.i(TAG, "refresh: Loading installed and uninstalled apps")
|
||||
|
||||
isLoading = true
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
if (apps.isEmpty()) {
|
||||
apps = loadInstalledApps().clone()
|
||||
}
|
||||
|
||||
if (uninstalledApps.isEmpty()) {
|
||||
loadUninstalledApps()
|
||||
}
|
||||
|
||||
// onAppsLoaded(apps.toArrayList())
|
||||
// onUninstalledAppsLoaded(uninstalledApps.toArrayList())
|
||||
|
||||
// We will init bloat list here
|
||||
// Because I couldn't think of any other place
|
||||
DebloatUtils.initBloatAppsSet()
|
||||
serviceScope.launch {
|
||||
val installed = loadInstalledApps()
|
||||
val uninstalled = loadUninstalledApps()
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
LocalBroadcastManager.getInstance(applicationContext).sendBroadcast(Intent(APPS_LOADED))
|
||||
_installedApps.value = installed
|
||||
_uninstalledApps.value = uninstalled
|
||||
isLoading = false
|
||||
}
|
||||
Log.i(TAG, "refresh: Loaded ${installed.size} installed and ${uninstalled.size} uninstalled apps")
|
||||
|
||||
DebloatUtils.initBloatAppsSet()
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "refresh: Already loading apps, skipping refresh")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadInstalledApps(maxRetries: Int = 3): ArrayList<PackageInfo> {
|
||||
val result = ArrayList<PackageInfo>()
|
||||
repeat(maxRetries) { attempt ->
|
||||
try {
|
||||
val packageNames = packageManager.getInstalledPackages(0).map { it.packageName }
|
||||
packageNames.chunked(100).forEach { batch ->
|
||||
batch.forEach { pkg ->
|
||||
try {
|
||||
val info = packageManager.getPackageInfo(pkg, INSTALLED_FLAGS)
|
||||
result.add(info)
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
}
|
||||
}
|
||||
result.forEach {
|
||||
it.safeApplicationInfo.name = getApplicationName(applicationContext, it.safeApplicationInfo)
|
||||
}
|
||||
return result
|
||||
} catch (e: Exception) {
|
||||
result.clear()
|
||||
e.printStackTrace()
|
||||
if (attempt < maxRetries - 1) {
|
||||
delay(500L)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun refresh() {
|
||||
isLoading = false
|
||||
apps.clear()
|
||||
uninstalledApps.clear()
|
||||
startLoading()
|
||||
}
|
||||
private suspend fun loadUninstalledApps(maxRetries: Int = 3): ArrayList<PackageInfo> {
|
||||
val result = ArrayList<PackageInfo>()
|
||||
|
||||
fun hasDataLoaded(): Boolean {
|
||||
return apps.isNotEmpty() && uninstalledApps.isNotEmpty()
|
||||
}
|
||||
|
||||
private fun loadInstalledApps(): MutableList<PackageInfo> {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
repeat(maxRetries) { attempt ->
|
||||
try {
|
||||
return packageManager.getInstalledPackages(flags).loadPackageNames()
|
||||
} catch (e: DeadObjectException) {
|
||||
val packageNames = packageManager.getInstalledPackages(0).map { it.packageName }
|
||||
packageNames.chunked(100).forEach { batch ->
|
||||
batch.forEach { pkg ->
|
||||
try {
|
||||
val info = packageManager.getPackageInfo(pkg, UNINSTALLED_FLAGS)
|
||||
if (info.safeApplicationInfo.flags and ApplicationInfo.FLAG_INSTALLED == 0) {
|
||||
result.add(info)
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
}
|
||||
}
|
||||
result.forEach {
|
||||
it.safeApplicationInfo.name = getApplicationName(applicationContext, it.safeApplicationInfo)
|
||||
}
|
||||
return result
|
||||
} catch (e: Exception) {
|
||||
result.clear()
|
||||
e.printStackTrace()
|
||||
return mutableListOf()
|
||||
} catch (e: DeadSystemException) {
|
||||
e.printStackTrace()
|
||||
return mutableListOf()
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
return packageManager.getInstalledPackages(flags).loadPackageNames()
|
||||
} catch (e: DeadObjectException) {
|
||||
e.printStackTrace()
|
||||
return mutableListOf()
|
||||
if (attempt < maxRetries - 1) {
|
||||
delay(500L)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private fun loadUninstalledApps() {
|
||||
val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
this.flags or PackageManager.MATCH_UNINSTALLED_PACKAGES
|
||||
} else {
|
||||
this.flags or PackageManager.GET_UNINSTALLED_PACKAGES
|
||||
}
|
||||
|
||||
if (uninstalledApps.isNotEmpty()) return
|
||||
|
||||
try {
|
||||
uninstalledApps = packageManager.getInstalledPackages(flags).stream()
|
||||
.filter { packageInfo -> packageInfo.safeApplicationInfo.flags and ApplicationInfo.FLAG_INSTALLED == 0 }
|
||||
.collect(Collectors.toList())
|
||||
.loadPackageNames()
|
||||
.toArrayList()
|
||||
} catch (e: DeadObjectException) {
|
||||
uninstalledApps = arrayListOf()
|
||||
e.printStackTrace()
|
||||
} catch (e: BadParcelableException) {
|
||||
uninstalledApps = arrayListOf()
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableList<PackageInfo>.loadPackageNames(): MutableList<PackageInfo> {
|
||||
forEach {
|
||||
it.safeApplicationInfo.name = getApplicationName(application.applicationContext, it.safeApplicationInfo)
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the app's name from the package id of the same application
|
||||
* @param context of the given environment
|
||||
* @param applicationInfo is [ApplicationInfo] object containing app's
|
||||
* information
|
||||
* @return app's name as [String]
|
||||
*/
|
||||
private fun getApplicationName(context: Context, applicationInfo: ApplicationInfo): String? {
|
||||
return try {
|
||||
context.packageManager.getApplicationLabel(applicationInfo).toString()
|
||||
@@ -267,30 +193,4 @@ class DataLoaderService : Service() {
|
||||
context.getString(R.string.unknown)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onUninstalledAppsLoaded(uninstalledApps: ArrayList<PackageInfo>) {
|
||||
Log.d("DataLoaderService", "onUninstalledAppsLoaded: ${uninstalledApps.size}")
|
||||
LocalBroadcastManager.getInstance(applicationContext)
|
||||
.sendBroadcast(Intent(UNINSTALLED_APPS_LOADED))
|
||||
}
|
||||
|
||||
private fun onAppsLoaded(apps: ArrayList<PackageInfo>) {
|
||||
Log.d("DataLoaderService", "onAppsLoaded: ${apps.size}")
|
||||
LocalBroadcastManager.getInstance(applicationContext)
|
||||
.sendBroadcast(Intent(INSTALLED_APPS_LOADED))
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun refreshUninstalled() {
|
||||
uninstalledApps.clear()
|
||||
loadUninstalledApps()
|
||||
onUninstalledAppsLoaded(uninstalledApps.clone() as ArrayList<PackageInfo>)
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun refreshInstalled() {
|
||||
apps.clear()
|
||||
apps = loadInstalledApps() as ArrayList<PackageInfo>
|
||||
onAppsLoaded(apps.clone() as ArrayList<PackageInfo>)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,17 +3,11 @@ package app.simple.inure.ui.launcher
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.AppOpsManager
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.ServiceConnection
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.os.IBinder
|
||||
import android.os.Process
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
@@ -25,7 +19,6 @@ import androidx.core.app.AppOpsManagerCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||
import app.simple.inure.BuildConfig
|
||||
import app.simple.inure.R
|
||||
import app.simple.inure.apk.utils.PackageUtils.isPackageInstalled
|
||||
@@ -45,7 +38,6 @@ import app.simple.inure.preferences.MusicPreferences
|
||||
import app.simple.inure.preferences.SearchPreferences
|
||||
import app.simple.inure.preferences.SetupPreferences
|
||||
import app.simple.inure.preferences.TrialPreferences
|
||||
import app.simple.inure.services.DataLoaderService
|
||||
import app.simple.inure.ui.panels.Home
|
||||
import app.simple.inure.util.AppUtils
|
||||
import app.simple.inure.util.ConditionUtils.invert
|
||||
@@ -87,12 +79,6 @@ class SplashScreen : ScopedFragment() {
|
||||
private var isTagsLoaded = false
|
||||
private var isDebloatLoaded = false
|
||||
|
||||
private var serviceConnection: ServiceConnection? = null
|
||||
private var dataLoaderService: DataLoaderService? = null
|
||||
private var broadcastReceiver: BroadcastReceiver? = null
|
||||
|
||||
private var intentFilter: IntentFilter = IntentFilter()
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
return inflater.inflate(R.layout.fragment_splash_screen, container, false)
|
||||
}
|
||||
@@ -102,33 +88,6 @@ class SplashScreen : ScopedFragment() {
|
||||
startPostponedEnterTransition()
|
||||
clearSearchStates()
|
||||
|
||||
intentFilter.addAction(DataLoaderService.APPS_LOADED)
|
||||
intentFilter.addAction(DataLoaderService.UNINSTALLED_APPS_LOADED)
|
||||
intentFilter.addAction(DataLoaderService.INSTALLED_APPS_LOADED)
|
||||
|
||||
broadcastReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
if (intent?.action == DataLoaderService.APPS_LOADED) {
|
||||
proceed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
serviceConnection = object : ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||
dataLoaderService = (service as DataLoaderService.LoaderBinder).getService()
|
||||
if (dataLoaderService?.hasDataLoaded() == true) {
|
||||
proceed()
|
||||
} else {
|
||||
dataLoaderService?.startLoading()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
dataLoaderService = null
|
||||
}
|
||||
}
|
||||
|
||||
icon = view.findViewById(R.id.imageView)
|
||||
loaderImageView = view.findViewById(R.id.loader)
|
||||
daysLeft = view.findViewById(R.id.days_left)
|
||||
@@ -167,31 +126,26 @@ class SplashScreen : ScopedFragment() {
|
||||
}
|
||||
|
||||
requireArguments().getBoolean(BundleConstants.skip) -> { // Second check if setup is skipped
|
||||
startLoaderService()
|
||||
startLoader()
|
||||
}
|
||||
|
||||
!checkForPermission() -> {
|
||||
if (SetupPreferences.isDontShowAgain()) { // If setup not skipped open setup
|
||||
startLoaderService()
|
||||
startLoader()
|
||||
} else {
|
||||
openFragmentSlide(Setup.newInstance())
|
||||
}
|
||||
}
|
||||
|
||||
else -> { // Load all data
|
||||
startLoaderService()
|
||||
startLoader()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startLoaderService() {
|
||||
val intent = Intent(requireContext(), DataLoaderService::class.java)
|
||||
requireContext().bindService(intent, serviceConnection!!, Context.BIND_AUTO_CREATE)
|
||||
|
||||
if (BehaviourPreferences.isSkipLoading()) {
|
||||
proceed()
|
||||
}
|
||||
private fun startLoader() {
|
||||
proceed()
|
||||
|
||||
postDelayed(MAX_LOADING_TIME) { // Give the service 7 seconds to load
|
||||
/**
|
||||
@@ -438,25 +392,8 @@ class SplashScreen : ScopedFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
LocalBroadcastManager.getInstance(requireContext()).registerReceiver(broadcastReceiver!!, intentFilter)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
try {
|
||||
if (serviceConnection != null) {
|
||||
requireContext().unbindService(serviceConnection!!)
|
||||
}
|
||||
} catch (e: java.lang.IllegalArgumentException) {
|
||||
e.printStackTrace() // Should crash if moving to another [Setup] fragment
|
||||
}
|
||||
|
||||
if (broadcastReceiver != null) {
|
||||
LocalBroadcastManager.getInstance(requireContext()).unregisterReceiver(broadcastReceiver!!)
|
||||
}
|
||||
|
||||
handler.removeCallbacksAndMessages(null)
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ class Uninstalled : ScopedFragment() {
|
||||
}
|
||||
R.drawable.ic_refresh -> {
|
||||
showLoader(manualOverride = true)
|
||||
homeViewModel.refreshUninstalledPackageData()
|
||||
homeViewModel.refreshPackageData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user