@@ -471,8 +471,13 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
471471 list.add(buildItem(raw))
472472 }
473473
474- // ✅ Sort by normalized label
475- list.sortWith(compareBy { normalize(getLabel(it)) })
474+ val pinnedMap = items.associateWith { isPinned(it) } // Map<T, Boolean>
475+
476+ // ✅ Sort pinned first, then by normalized label
477+ list.sortWith(
478+ compareByDescending<R > { pinnedMap[items[list.indexOf(it)]] == true }
479+ .thenBy { normalize(getLabel(it)) }
480+ )
476481
477482 // ✅ Build scroll index (safe, no `continue`)
478483 list.forEachIndexed { index, item ->
@@ -487,8 +492,14 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
487492 }
488493
489494 /* *
490- * Build app list on IO dispatcher. This function is still suspend and safe to call
491- * from background, but it ensures all heavy operations happen on Dispatchers.IO.
495+ * Build app list on IO dispatcher. This function is suspend and safe to call
496+ * from a background thread, but it ensures all heavy operations happen on Dispatchers.IO.
497+ *
498+ * Features:
499+ * - Fetches recent apps and user profile apps in parallel.
500+ * - Filters hidden/pinned apps before creating AppListItem objects.
501+ * - Updates profile counters efficiently.
502+ * - Optimized for memory and CPU usage on devices with many apps.
492503 */
493504 suspend fun getAppsList (
494505 context : Context ,
@@ -503,109 +514,121 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
503514 val seenAppKeys = mutableSetOf<String >()
504515 val userManager = context.getSystemService(Context .USER_SERVICE ) as UserManager
505516 val profiles = userManager.userProfiles.toList()
517+ val privateManager = PrivateSpaceManager (context)
506518
507519 fun appKey (pkg : String , cls : String , profileHash : Int ) = " $pkg |$cls |$profileHash "
508520 fun isHidden (pkg : String , key : String ) =
509521 listOf (pkg, key, " $pkg |${key.hashCode()} " ).any { it in hiddenAppsSet }
510522
511- AppLogger .d(
512- " AppListDebug" ,
513- " 🔄 getAppsList called with: includeRegular=$includeRegularApps , includeHidden=$includeHiddenApps , includeRecent=$includeRecentApps "
523+ // Lightweight intermediate storage
524+ data class RawApp (
525+ val pkg : String ,
526+ val cls : String ,
527+ val label : String ,
528+ val user : UserHandle ,
529+ val profileType : String ,
530+ val category : AppCategory
514531 )
515532
516- val allApps = mutableListOf<AppListItem >()
533+ val rawApps = mutableListOf<RawApp >()
517534
518- // 🔹 Add recent apps
535+ // 🔹 Recent apps
519536 if (prefs.recentAppsDisplayed && includeRecentApps) {
520537 runCatching {
521- AppUsageMonitor .createInstance(context).getLastTenAppsUsed(context).forEach { (pkg, name, activity) ->
522- val key = appKey(pkg, activity, 0 )
523- if (seenAppKeys.add(key)) {
524- allApps.add(
525- AppListItem (
526- activityLabel = name,
527- activityPackage = pkg,
528- activityClass = activity,
529- user = Process .myUserHandle(),
530- profileType = " SYSTEM" ,
531- customLabel = prefs.getAppAlias(pkg).ifEmpty { name },
532- customTag = prefs.getAppTag(pkg, null ),
533- category = AppCategory .RECENT
538+ AppUsageMonitor .createInstance(context)
539+ .getLastTenAppsUsed(context)
540+ .forEach { (pkg, name, activity) ->
541+ val key = appKey(pkg, activity, 0 )
542+ if (seenAppKeys.add(key)) {
543+ rawApps.add(
544+ RawApp (
545+ pkg = pkg,
546+ cls = activity,
547+ label = name,
548+ user = Process .myUserHandle(),
549+ profileType = " SYSTEM" ,
550+ category = AppCategory .RECENT
551+ )
534552 )
535- )
553+ }
536554 }
537- }
538555 }.onFailure { t ->
539556 AppLogger .e(" AppListDebug" , " Failed to add recent apps: ${t.message} " , t)
540557 }
541558 }
542559
543- // 🔹 Process user profiles in parallel
560+ // 🔹 Profile apps in parallel
561+ val launcherApps = context.getSystemService(Context .LAUNCHER_APPS_SERVICE ) as LauncherApps
544562 val deferreds = profiles.map { profile ->
545563 async {
546- val privateManager = PrivateSpaceManager (context)
547564 if (privateManager.isPrivateSpaceProfile(profile) && privateManager.isPrivateSpaceLocked()) {
548565 AppLogger .d(" AppListDebug" , " 🔒 Skipping locked private profile: $profile " )
549- return @async emptyList<AppListItem >()
550- }
551-
552- val profileType = when {
553- privateManager.isPrivateSpaceProfile(profile) -> " PRIVATE"
554- profile != Process .myUserHandle() -> " WORK"
555- else -> " SYSTEM"
556- }
557-
558- val launcherApps = context.getSystemService(Context .LAUNCHER_APPS_SERVICE ) as LauncherApps
559- val activities = runCatching { launcherApps.getActivityList(null , profile) }.getOrElse {
560- AppLogger .e(" AppListDebug" , " Failed to get activities for $profile : ${it.message} " , it)
561- emptyList()
562- }
566+ emptyList<RawApp >()
567+ } else {
568+ val profileType = when {
569+ privateManager.isPrivateSpaceProfile(profile) -> " PRIVATE"
570+ profile != Process .myUserHandle() -> " WORK"
571+ else -> " SYSTEM"
572+ }
563573
564- activities.mapNotNull { info ->
565- val pkg = info.applicationInfo.packageName
566- val cls = info.componentName.className
567- val label = info.label.toString()
568- if (pkg == BuildConfig .APPLICATION_ID ) return @mapNotNull null
569-
570- val key = appKey(pkg, cls, profile.hashCode())
571- if (! seenAppKeys.add(key)) return @mapNotNull null
572- if ((isHidden(pkg, key) && ! includeHiddenApps) || (! isHidden(pkg, key) && ! includeRegularApps))
573- return @mapNotNull null
574-
575- val category = if (pkg in pinnedPackages) AppCategory .PINNED else AppCategory .REGULAR
576- AppListItem (
577- activityLabel = label,
578- activityPackage = pkg,
579- activityClass = cls,
580- user = profile,
581- profileType = profileType,
582- customLabel = prefs.getAppAlias(pkg),
583- customTag = prefs.getAppTag(pkg, profile),
584- category = category
585- )
574+ runCatching { launcherApps.getActivityList(null , profile) }
575+ .getOrElse {
576+ AppLogger .e(" AppListDebug" , " Failed to get activities for $profile : ${it.message} " , it)
577+ emptyList()
578+ }
579+ .mapNotNull { info ->
580+ val pkg = info.applicationInfo.packageName
581+ val cls = info.componentName.className
582+ if (pkg == BuildConfig .APPLICATION_ID ) return @mapNotNull null
583+ val key = appKey(pkg, cls, profile.hashCode())
584+ if (! seenAppKeys.add(key)) return @mapNotNull null
585+ if ((isHidden(pkg, key) && ! includeHiddenApps) || (! isHidden(pkg, key) && ! includeRegularApps))
586+ return @mapNotNull null
587+
588+ val category = if (pkg in pinnedPackages) AppCategory .PINNED else AppCategory .REGULAR
589+ RawApp (pkg, cls, info.label.toString(), profile, profileType, category)
590+ }
586591 }
587592 }
588593 }
589594
590- val profileApps = deferreds.flatMap { it.await() }
591- allApps.addAll(profileApps)
595+ deferreds.forEach { rawApps.addAll(it.await()) }
592596
593597 // 🔹 Update profile counters
594598 listOf (" SYSTEM" , " PRIVATE" , " WORK" , " USER" ).forEach { type ->
595- val count = allApps.count { it.profileType == type }
596- prefs.setProfileCounter(type, count)
599+ prefs.setProfileCounter(type, rawApps.count { it.profileType == type })
600+ }
601+
602+ // 🔹 Convert RawApp → AppListItem
603+ val allApps = rawApps.map { raw ->
604+ AppListItem (
605+ activityLabel = raw.label,
606+ activityPackage = raw.pkg,
607+ activityClass = raw.cls,
608+ user = raw.user,
609+ profileType = raw.profileType,
610+ customLabel = prefs.getAppAlias(raw.pkg),
611+ customTag = prefs.getAppTag(raw.pkg, raw.user),
612+ category = raw.category
613+ )
597614 }
615+ // 🔹 Sort pinned apps first, then regular/recent alphabetically
616+ .sortedWith(
617+ compareByDescending<AppListItem > { it.category == AppCategory .PINNED }
618+ .thenBy { normalizeForSort(it.label) }
619+ )
620+ .toMutableList()
598621
599- // 🔹 Finalize list using buildList()
622+ // 🔹 Build scroll map and finalize
600623 buildList(
601624 items = allApps,
602- seenKey = mutableSetOf (), // ✅ fresh set (don’t reuse seenAppKeys!)
625+ seenKey = mutableSetOf (),
603626 scrollMapLiveData = _appScrollMap ,
604627 includeHidden = includeHiddenApps,
605628 getKey = { " ${it.activityPackage} |${it.activityClass} |${it.user.hashCode()} " },
606629 isHidden = { it.activityPackage in hiddenAppsSet },
607630 isPinned = { it.activityPackage in pinnedPackages },
608- buildItem = { it }, // Already an AppListItem
631+ buildItem = { it },
609632 getLabel = { it.label },
610633 normalize = ::normalizeForSort
611634 )
0 commit comments