DEV Community

Devika Android
Devika Android

Posted on

Modern Android File Manager Implementation Using MediaStore and PackageManager

SYSTEM_ALERT_WINDOW: Allows the app to display content on top of other apps (like chat heads or floating windows).
FOREGROUND_SERVICE: Lets the app run important tasks in the foreground with a visible notification so the system doesn’t kill it.
FOREGROUND_SERVICE_DATA_SYNC: Specifically allows a foreground service to perform data synchronization tasks (like uploading or downloading files).
POST_NOTIFICATIONS: Allows the app to send and display notifications to the user (required from Android 13+).
These permissions are mainly used for background tasks, overlays, and showing notifications while the app is actively running important processes.

   //sdp
   implementation("com.intuit.sdp:sdp-android:1.1.1")

// Glide (latest stable major — Glide 5.x)
   implementation("com.github.bumptech.glide:glide:5.0.5")
   kapt("com.github.bumptech.glide:compiler:5.0.5")

 // Lifecycle ViewModel & Runtime (latest stable Lifecycle)
   implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.10.0")
   implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.10.0")

Enter fullscreen mode Exit fullscreen mode
   <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
   <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
   <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
   <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
   <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
   <uses-permission
       android:name="android.permission.PACKAGE_USAGE_STATS"
       tools:ignore="ProtectedPermissions" /><queries>
       <package android:name="com.google.android.youtube" />
       <package android:name="com.android.launcher3" />
       <package android:name="com.instagram.android" />
       <package android:name="com.whatsapp" />
   </queries>


if do this
   <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
   <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
   <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
   <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
Enter fullscreen mode Exit fullscreen mode

This code is used to check and request Usage Access Permission, not normal storage permission.
hasUsagePermission() checks whether the app has permission to access app usage statistics using AppOpsManager.OPSTR_GET_USAGE_STATS.
If permission is not granted, requestUsagePermission() opens the Usage Access Settings screen using Settings.ACTION_USAGE_ACCESS_SETTINGS.
The user must manually enable the permission from system settings (it cannot be granted automatically)
This permission (PACKAGE_USAGE_STATS) is mainly used to track which apps are used and for how long.
It is not required for accessing files from storage.

  if (!Common.hasUsagePermission(requireActivity())) {
           Common.requestUsagePermission(requireActivity())
       }

  fun requestUsagePermission(context: Context) {
       context.startActivity(Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS))
   }

   fun hasUsagePermission(context: Context): Boolean {
       val appOps = context.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager
       val mode = appOps.checkOpNoThrow(
           AppOpsManager.OPSTR_GET_USAGE_STATS, android.os.Process.myUid(), context.packageName
       )
       return mode == AppOpsManager.MODE_ALLOWED
   }
MANAGE_EXTERNAL_STORAGE permission is not required for this use case.
Use READ_MEDIA_IMAGES / VIDEO / AUDIO (Android 13+) or READ_EXTERNAL_STORAGE (Android 12 and below).
Your structure is clean and suitable for a basic File Manager app.
binding.btnFileManager.id -> {

               if (hasStoragePermission()) {

                   if (!Environment.isExternalStorageManager()) {
                       val intent = Intent(
                           Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION
                       )
                       startActivity(intent)
                   } else {
                       requireActivity().startActivity(
                           Intent(
                               requireActivity(),
                               FileManagerActivity::class.java
                           )
                       )
                   }


               } else {
                   requestPermissions(requireActivity())
                   Toast.makeText(requireActivity(), "Please allow Permission", Toast.LENGTH_SHORT)
                       .show()
               }
           }

fun hasStoragePermission(): Boolean {

       return if (Build.VERSION.SDK_INT >= 33) {

           ContextCompat.checkSelfPermission(
               requireActivity(),
               Manifest.permission.READ_MEDIA_IMAGES
           ) == PackageManager.PERMISSION_GRANTED &&

                   ContextCompat.checkSelfPermission(
                       requireActivity(),
                       Manifest.permission.READ_MEDIA_VIDEO
                   ) == PackageManager.PERMISSION_GRANTED &&

                   ContextCompat.checkSelfPermission(
                       requireActivity(),
                       Manifest.permission.READ_MEDIA_AUDIO
                   ) == PackageManager.PERMISSION_GRANTED

       } else {
           ContextCompat.checkSelfPermission(
               requireActivity(),
               Manifest.permission.READ_EXTERNAL_STORAGE
           ) == PackageManager.PERMISSION_GRANTED
       }
   }


   fun requestPermissions(activity: Activity) {

       if (Build.VERSION.SDK_INT >= 33) {
           activity.requestPermissions(
               arrayOf(
                   Manifest.permission.READ_MEDIA_IMAGES,
                   Manifest.permission.READ_MEDIA_VIDEO,
                   Manifest.permission.READ_MEDIA_AUDIO,
                   Manifest.permission.READ_EXTERNAL_STORAGE
               ),
               100
           )

       } else {
           activity.requestPermissions(
               arrayOf(
                   Manifest.permission.READ_EXTERNAL_STORAGE
               ),
               100
           )
       }
   }
Enter fullscreen mode Exit fullscreen mode

MediaStore to get images, videos, audio, PDF, and APK files count and total size.
getCategoryStats() calculates file count and total storage size using MediaStore.MediaColumns.SIZE.
APK files are filtered using MIME type "application/vnd.android.package-archive".
getAppStats() correctly separates Installed Apps and System Apps using PackageManager.

class FileManagerActivity : AppCompatActivity() {
   private lateinit var binding: ActivityFileManagerBinding
   private var categoryList: List<CategoryModel> = ArrayList()
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       enableEdgeToEdge()
       binding = DataBindingUtil.setContentView(this, R.layout.activity_file_manager)
       ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
           val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
           v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
           insets
       }

       initView()
   }

   private fun initView() {

       val adapter = FileCatAdapter { itemData ->
           Log.e("TAG", "initView:gggg ${itemData.name} ")
           when (itemData.name.trim()) {
               "Images" -> {
                   val imageIntent = Intent(this, AllImagesActivity::class.java)
                   imageIntent.putExtra("content", "Images")
                   startActivity(imageIntent)
               }

               "Videos" -> {
                   val videoIntent = Intent(this, AllImagesActivity::class.java)
                   videoIntent.putExtra("content", "Videos")
                   startActivity(videoIntent)
               }

               "Audio" -> {
                   val audioIntent = Intent(this, AllImagesActivity::class.java)
                   audioIntent.putExtra("content", "Audio")
                   startActivity(audioIntent)
               }

               "PDF" -> {
                   val documentIntent = Intent(this, AllImagesActivity::class.java)
                   documentIntent.putExtra("content", "PDF")
                   startActivity(documentIntent)
               }

               "APK" -> {
                   val apkIntent = Intent(this, AllAppsActivity::class.java)
                   apkIntent.putExtra("content", "APK")
                   startActivity(apkIntent)
               }

               "Installed Apps" -> {
                   val installAppIntent = Intent(this, AllAppsActivity::class.java)
                   installAppIntent.putExtra("content", "Installed Apps")
                   startActivity(installAppIntent)
               }

               "System Apps" -> {
                   val systemAppsIntent = Intent(this, AllAppsActivity::class.java)
                   systemAppsIntent.putExtra("content", "System Apps")
                   startActivity(systemAppsIntent)
               }




           }

       }
       binding.rvCategories.adapter = adapter
       binding.rvCategories.layoutManager = GridLayoutManager(this, 2)

       lifecycleScope.launch(Dispatchers.Main) {
           categoryList = getAllCategories(this@FileManagerActivity)
           adapter.addAll(ArrayList(categoryList))
       }

   }

   suspend fun getAllCategories(context: Context): List<CategoryModel> {

       val imageStats = getCategoryStats(
           context,
           MediaStore.Images.Media.EXTERNAL_CONTENT_URI
       )

       val videoStats = getCategoryStats(
           context,
           MediaStore.Video.Media.EXTERNAL_CONTENT_URI
       )

       val audioStats = getCategoryStats(
           context,
           MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
       )

       val docUri = MediaStore.Files.getContentUri("external")

       val docSelection =
           "${MediaStore.Files.FileColumns.MIME_TYPE} IN (?,?,?,?)"

       val docArgs = arrayOf(
           "application/pdf",
          /* "application/msword",
           "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
           "text/plain"*/
       )

       val docStats = getCategoryStats(
           context,
           docUri,
           docSelection,
           docArgs
       )

       val apkSelection =
           "${MediaStore.Files.FileColumns.MIME_TYPE}=?"

       val apkArgs = arrayOf(
           "application/vnd.android.package-archive"
       )

       val apkStats = getCategoryStats(
           context,
           docUri,
           apkSelection,
           apkArgs
       )

       val appStats = getAppStats(context)

       return listOf(
           CategoryModel(
               "Images",
               imageStats.count,
               formatSize(imageStats.totalSize),
               R.drawable.ic_launcher_foreground
           ),
           CategoryModel(
               "Videos",
               videoStats.count,
               formatSize(videoStats.totalSize),
               R.drawable.ic_launcher_foreground
           ),
           CategoryModel(
               "Audio",
               audioStats.count,
               formatSize(audioStats.totalSize),
               R.drawable.ic_launcher_foreground
           ),
           CategoryModel(
               "PDF",
               docStats.count,
               formatSize(docStats.totalSize),
               R.drawable.ic_launcher_foreground
           ),
           CategoryModel(
               "APK",
               apkStats.count,
               formatSize(apkStats.totalSize),
               R.drawable.ic_launcher_foreground
           ),
           CategoryModel(
               "Installed Apps",
               appStats.userCount,
               "",
               R.drawable.ic_launcher_foreground
           ),
           CategoryModel(
               "System Apps",
               appStats.systemCount,
               "",
               R.drawable.ic_launcher_foreground
           )
       )
   }


   fun formatSize(size: Long): String {

       val kb = 1024
       val mb = kb * 1024
       val gb = mb * 1024

       return when {
           size >= gb -> String.format("%.2f GB", size.toFloat() / gb)
           size >= mb -> String.format("%.2f MB", size.toFloat() / mb)
           size >= kb -> String.format("%.2f KB", size.toFloat() / kb)
           else -> "$size Bytes"
       }
   }

   suspend fun getCategoryStats(
       context: Context,
       uri: Uri,
       selection: String? = null,
       selectionArgs: Array<String>? = null
   ): CategoryStats = withContext(Dispatchers.IO) {

       var totalSize = 0L
       var count = 0

       val projection = arrayOf(
           MediaStore.MediaColumns.SIZE
       )

       context.contentResolver.query(
           uri,
           projection,
           selection,
           selectionArgs,
           null
       )?.use { cursor ->

           val sizeColumn =
               cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.SIZE)

           while (cursor.moveToNext()) {
               totalSize += cursor.getLong(sizeColumn)
               count++
           }
       }

       CategoryStats(count, totalSize)
   }

   fun getAppStats(context: Context): AppStats {

       val pm = context.packageManager
       val apps = pm.getInstalledApplications(PackageManager.GET_META_DATA)

       var systemCount = 0
       var userCount = 0

       apps.forEach { appInfo ->

           val isSystem =
               (appInfo.flags and ApplicationInfo.FLAG_SYSTEM) != 0

           val isUpdatedSystem =
               (appInfo.flags and ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0

           if (isSystem && !isUpdatedSystem) {
               systemCount++
           } else {
               userCount++
           }
       }

       return AppStats(systemCount, userCount)
   }
}
Enter fullscreen mode Exit fullscreen mode

CategoryModel → Represents each main category (Images, Videos, APK, etc.) with name, file count, total formatted size, and icon.
CategoryStats → Used internally to store raw count and total size (Long) before formatting.
MediaModel → Represents individual media files (image, video, audio) with name, size, URI, type, and optional duration (for videos).
AppStats → Stores total number of system apps and user-installed apps.
AllAppsModel → Represents individual installed apps with app name, package name, icon, version name, and system/user flag.

data class CategoryModel(
   val name: String,
   val count: Int,
   val totalSize: String,
   val icon: Int
)

data class CategoryStats(
   val count: Int,
   val totalSize: Long
)

data class MediaModel(
   val name: String,
   val size: Long,
   val formattedSize: String,
   val uri: Uri,
   val type: MediaType? = null,
   val duration: Long? = null   // video ke liye
)

data class AppStats(
   val systemCount: Int,
   val userCount: Int
)

data class AllAppsModel(
   val appName: String,
   val packageName: String,
   val icon: Drawable? = null,
   val versionName: String,
   val isSystemApp: Boolean
)

Enter fullscreen mode Exit fullscreen mode

File Count Adapter


class FileCatAdapter(val onClick: (itemData: CategoryModel) -> Unit) :
   RecyclerView.Adapter<FileCatAdapter.FileDataHolder>() {
   private var fileList: ArrayList<CategoryModel> = ArrayList()
   override fun onCreateViewHolder(
       parent: ViewGroup,
       viewType: Int
   ): FileDataHolder {
       val binding = FileDesignBinding.inflate(LayoutInflater.from(parent.context), parent, false)
       return FileDataHolder(binding)
   }

   override fun onBindViewHolder(
       holder: FileDataHolder,
       position: Int
   ) {
       val itemData = fileList[position]
       holder.setData(itemData)

       holder.itemView.setOnClickListener {
           onClick.invoke(itemData)
       }
   }

   override fun getItemCount(): Int {
       return fileList.size
   }

   fun addAll(fileList: ArrayList<CategoryModel>) {
       this.fileList = ArrayList()
       this.fileList.addAll(fileList)
       notifyDataSetChanged()
   }

   class FileDataHolder(val binding: FileDesignBinding) : RecyclerView.ViewHolder(binding.root) {

       fun setData(itemData: CategoryModel) {
           binding.tvTitle.text = itemData.name
           binding.tvCount.text = "${itemData.count}"
           binding.tvSize.text = itemData.totalSize
       }

   }
}

Enter fullscreen mode Exit fullscreen mode

This is a RecyclerView Adapter used to display file categories (Images, Videos, APK, etc.).
It takes a click listener lambda (onClick) to handle item clicks dynamically from the activity.

class AllImagesActivity : AppCompatActivity() {
   private lateinit var binding: ActivityAllImagesBinding

   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       enableEdgeToEdge()
       binding = DataBindingUtil.setContentView(this, R.layout.activity_all_images)
       ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
           val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
           v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
           insets
       }

       initView()
   }

   private fun initView() {
       val adapter = AllImagesAdapter()
       binding.rvAllImages.adapter = adapter
       binding.rvAllImages.layoutManager = GridLayoutManager(this, 2)

       val content = intent.getStringExtra("content")
       Log.e("TAG", "initView:33 $content")

       when (content) {
           "Images" -> {
               lifecycleScope.launch(Dispatchers.Main) {
                   val imageList = getAllImages(this@AllImagesActivity)
                   adapter.addAll(ArrayList(imageList))
               }
           }

           "Videos" -> {
               lifecycleScope.launch(Dispatchers.Main) {
                   val videoList = getAllVideos(this@AllImagesActivity)
                   adapter.addAll(ArrayList(videoList))
               }
           }

           "Audio" -> {
               lifecycleScope.launch(Dispatchers.Main) {
                   val audioList = getAllAudio(this@AllImagesActivity)
                   adapter.addAll(ArrayList(audioList))
               }
           }

           "PDF" -> {
               lifecycleScope.launch(Dispatchers.Main) {
                   val documentList = getAllDocuments(this@AllImagesActivity)
                   adapter.addAll(ArrayList(documentList))
               }
           }
       }


   }

   suspend fun getAllImages(context: Context): List<MediaModel> =
       withContext(Dispatchers.IO) {

           val imageList = mutableListOf<MediaModel>()

           val projection = arrayOf(
               MediaStore.Images.Media._ID,
               MediaStore.Images.Media.DISPLAY_NAME,
               MediaStore.Images.Media.SIZE
           )

           val sortOrder =
               "${MediaStore.Images.Media.DATE_ADDED} DESC"

           context.contentResolver.query(
               MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
               projection,
               null,
               null,
               sortOrder
           )?.use { cursor ->

               val idColumn =
                   cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
               val nameColumn =
                   cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)
               val sizeColumn =
                   cursor.getColumnIndexOrThrow(MediaStore.Images.Media.SIZE)

               while (cursor.moveToNext()) {

                   val id = cursor.getLong(idColumn)
                   val name = cursor.getString(nameColumn)
                   val size = cursor.getLong(sizeColumn)

                   val contentUri = ContentUris.withAppendedId(
                       MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                       id
                   )

                   imageList.add(
                       MediaModel(
                           name = name ?: "Unknown",
                           size = size,
                           formattedSize = Common.formatSize(size),
                           uri = contentUri,
                           type = MediaType.IMAGE,
                           null
                       )
                   )
               }
           }

           imageList
       }


   suspend fun getAllVideos(context: Context): List<MediaModel> =
       withContext(Dispatchers.IO) {

           val list = mutableListOf<MediaModel>()

           val projection = arrayOf(
               MediaStore.Video.Media._ID,
               MediaStore.Video.Media.DISPLAY_NAME,
               MediaStore.Video.Media.SIZE,
               MediaStore.Video.Media.DURATION
           )

           context.contentResolver.query(
               MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
               projection,
               null,
               null,
               "${MediaStore.Video.Media.DATE_ADDED} DESC"
           )?.use { cursor ->

               val idCol = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID)
               val nameCol = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME)
               val sizeCol = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE)
               val durationCol = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION)

               while (cursor.moveToNext()) {

                   val id = cursor.getLong(idCol)
                   val name = cursor.getString(nameCol)
                   val size = cursor.getLong(sizeCol)
                   val duration = cursor.getLong(durationCol)

                   val uri = ContentUris.withAppendedId(
                       MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
                       id
                   )

                   list.add(
                       MediaModel(
                           name = name ?: "Unknown",
                           size = size,
                           formattedSize = Common.formatSize(size),
                           uri = uri,
                           type = MediaType.VIDEO,
                           duration = duration
                       )
                   )
               }
           }

           list
       }


   suspend fun getAllAudio(context: Context): List<MediaModel> =
       withContext(Dispatchers.IO) {

           val list = mutableListOf<MediaModel>()

           val projection = arrayOf(
               MediaStore.Audio.Media._ID,
               MediaStore.Audio.Media.DISPLAY_NAME,
               MediaStore.Audio.Media.SIZE,
               MediaStore.Audio.Media.DURATION
           )

           context.contentResolver.query(
               MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
               projection,
               null,
               null,
               "${MediaStore.Audio.Media.DATE_ADDED} DESC"
           )?.use { cursor ->

               val idCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID)
               val nameCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DISPLAY_NAME)
               val sizeCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.SIZE)
               val durationCol = cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.DURATION)

               while (cursor.moveToNext()) {

                   val id = cursor.getLong(idCol)
                   val name = cursor.getString(nameCol)
                   val size = cursor.getLong(sizeCol)
                   val duration = cursor.getLong(durationCol)

                   val uri = ContentUris.withAppendedId(
                       MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
                       id
                   )

                   list.add(
                       MediaModel(
                           name = name ?: "Unknown",
                           size = size,
                           formattedSize = Common.formatSize(size),
                           uri = uri,
                           type = MediaType.AUDIO,
                           duration = duration
                       )
                   )
               }
           }

           list
       }


   suspend fun getAllDocuments(context: Context): List<MediaModel> =
       withContext(Dispatchers.IO) {

           val list = mutableListOf<MediaModel>()

           val uri = MediaStore.Files.getContentUri("external")

           val projection = arrayOf(
               MediaStore.Files.FileColumns._ID,
               MediaStore.Files.FileColumns.DISPLAY_NAME,
               MediaStore.Files.FileColumns.SIZE,
               MediaStore.Files.FileColumns.MIME_TYPE
           )

           val selection =
               "${MediaStore.Files.FileColumns.MIME_TYPE}=?"

           val selectionArgs = arrayOf("application/pdf")

           context.contentResolver.query(
               uri,
               projection,
               selection,
               selectionArgs,
               "${MediaStore.Files.FileColumns.DATE_ADDED} DESC"
           )?.use { cursor ->

               val idCol = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID)
               val nameCol =
                   cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DISPLAY_NAME)
               val sizeCol = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.SIZE)

               while (cursor.moveToNext()) {

                   val id = cursor.getLong(idCol)
                   val name = cursor.getString(nameCol)
                   val size = cursor.getLong(sizeCol)

                   val contentUri = ContentUris.withAppendedId(uri, id)

                   list.add(
                       MediaModel(
                           name = name ?: "Unknown",
                           size = size,
                           formattedSize = Common.formatSize(size),
                           uri = contentUri,
                           type = MediaType.DOCUMENT
                       )
                   )
               }
           }

           list
       }


}

enum class MediaType {
   IMAGE,
   VIDEO,
   AUDIO,
   DOCUMENT
}

Enter fullscreen mode Exit fullscreen mode

AllImagesActivity displays media files (Images, Videos, Audio, PDF) based on the selected category from intent.
It uses RecyclerView with GridLayoutManager to show items in a 2-column grid.

class AllImagesAdapter : RecyclerView.Adapter<AllImagesAdapter.ImageViewHolder>() {
   private var allImageList: ArrayList<MediaModel> = ArrayList()
   override fun onCreateViewHolder(
       parent: ViewGroup,
       viewType: Int
   ): ImageViewHolder {
       val binding = ImageDesignBinding.inflate(LayoutInflater.from(parent.context), parent, false)
       return ImageViewHolder(binding)
   }

   override fun onBindViewHolder(
       holder: ImageViewHolder,
       position: Int
   ) {
       val itemData = allImageList[position]
       holder.setData(itemData)
   }

   override fun getItemCount(): Int {
       return allImageList.size
   }

   fun addAll(fileList: ArrayList<MediaModel>) {
       this.allImageList = ArrayList()
       this.allImageList.addAll(fileList)
       notifyDataSetChanged()
   }

   class ImageViewHolder(val binding: ImageDesignBinding) : RecyclerView.ViewHolder(binding.root) {
       fun setData(itemData: MediaModel) {
           binding.tvTitle.text = itemData.name
           binding.tvSize.text = itemData.formattedSize
           Glide.with(binding.ivImage.context).load(itemData.uri).into(binding.ivImage)
       }
   }
}

Enter fullscreen mode Exit fullscreen mode

AllAppsActivity displays APK files, Installed Apps, and System Apps based on the selected category from intent.
It uses a RecyclerView with GridLayoutManager (3 columns) to show app items.


class AllAppsActivity : AppCompatActivity() {
   private lateinit var binding: ActivityAllAppsBinding

   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       enableEdgeToEdge()
       binding = DataBindingUtil.setContentView(this, R.layout.activity_all_apps)
       ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
           val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
           v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
           insets
       }

       initView()
   }

   private fun initView() {
       val content = intent.getStringExtra("content")

       val adapter = AllAppsAdapter()
       binding.rvAllApps.adapter = adapter
       binding.rvAllApps.layoutManager = GridLayoutManager(this, 3)

       when (content) {
           "APK" -> {
               val apkList = getAllApkFiles()
               adapter.addAll(ArrayList(apkList))
           }

           "Installed Apps" -> {
               lifecycleScope.launch(Dispatchers.Main) {
                   val apkList = getInstalledApps(this@AllAppsActivity)
                   adapter.addAll(ArrayList(apkList))
               }
           }

           "System Apps" -> {
               lifecycleScope.launch(Dispatchers.Main) {
                   val apkList = getSystemApps(this@AllAppsActivity)
                   adapter.addAll(ArrayList(apkList))
               }
           }
       }

   }


   fun getAllApkFiles(): List<AllAppsModel> {

       val root = Environment.getExternalStorageDirectory()
       val apkList = mutableListOf<AllAppsModel>()

       root.walkTopDown().forEach { file ->

           if (file.isFile && file.extension.equals("apk", true)) {

               apkList.add(
                   AllAppsModel(
                       appName = file.nameWithoutExtension,
                       packageName = file.absolutePath,
                       icon = null,
                       versionName = "",
                       isSystemApp = false
                   )
               )
           }
       }

       return apkList
   }

   suspend fun getInstalledApps(context: Context): List<AllAppsModel> =
       withContext(Dispatchers.IO) {

           val pm = context.packageManager
           val apps = pm.getInstalledApplications(PackageManager.GET_META_DATA)

           val appList = mutableListOf<AllAppsModel>()

           apps.forEach { appInfo ->

               val packageInfo =
                   pm.getPackageInfo(appInfo.packageName, 0)

               val apkFile = File(appInfo.sourceDir)
               val appSize = if (apkFile.exists()) apkFile.length() else 0L

               val isSystem =
                   (appInfo.flags and ApplicationInfo.FLAG_SYSTEM) != 0

               val isUpdatedSystem =
                   (appInfo.flags and ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0

               val finalIsSystem = isSystem && !isUpdatedSystem

               appList.add(
                   AllAppsModel(
                       appName = pm.getApplicationLabel(appInfo).toString(),
                       packageName = appInfo.packageName,
                       icon = pm.getApplicationIcon(appInfo),
                       versionName = packageInfo.versionName ?: "",
                       /*apkPath = appInfo.sourceDir,
                       size = appSize,*/
                       isSystemApp = finalIsSystem
                   )
               )
           }

           appList.sortedBy { it.appName.lowercase() }
       }

   suspend fun getSystemApps(context: Context): List<AllAppsModel> =
       withContext(Dispatchers.IO) {

           val pm = context.packageManager
           val apps = pm.getInstalledApplications(PackageManager.GET_META_DATA)

           val systemApps = mutableListOf<AllAppsModel>()

           apps.forEach { appInfo ->

               val isSystem =
                   (appInfo.flags and ApplicationInfo.FLAG_SYSTEM) != 0

               val isUpdatedSystem =
                   (appInfo.flags and ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0

               // Pure system apps only
               if (isSystem && !isUpdatedSystem) {

                   val packageInfo =
                       pm.getPackageInfo(appInfo.packageName, 0)

                   val apkFile = File(appInfo.sourceDir)
                   val size = if (apkFile.exists()) apkFile.length() else 0L

                   systemApps.add(
                       AllAppsModel(
                           appName = pm.getApplicationLabel(appInfo).toString(),
                           packageName = appInfo.packageName,
                           icon = pm.getApplicationIcon(appInfo),
                           versionName = packageInfo.versionName ?: "",
                           /*   apkPath = appInfo.sourceDir,
                              size = size,*/
                           isSystemApp = true
                       )
                   )
               }
           }

           systemApps.sortedBy { it.appName.lowercase() }
       }


}

Enter fullscreen mode Exit fullscreen mode

AllAppsAdapter is a RecyclerView Adapter used to display APK files and installed/system apps.
It stores a list of AllAppsModel and binds app data using ViewBinding (AppsDesignBinding).

class AllAppsAdapter : RecyclerView.Adapter<AllAppsAdapter.AppsViewHolder>() {

   private var allAppsList: ArrayList<AllAppsModel> = ArrayList()

   override fun onCreateViewHolder(
       parent: ViewGroup,
       viewType: Int
   ): AppsViewHolder {
       val binding = AppsDesignBinding.inflate(LayoutInflater.from(parent.context), parent, false)
       return AppsViewHolder(binding)
   }

   override fun onBindViewHolder(
       holder: AppsViewHolder,
       position: Int
   ) {
       val itemData = allAppsList[position]
       holder.setData(itemData)
   }

   override fun getItemCount(): Int {
       return allAppsList.size
   }

   fun addAll(fileList: ArrayList<AllAppsModel>) {
       this.allAppsList = ArrayList()
       this.allAppsList.addAll(fileList)
       notifyDataSetChanged()
   }

   class AppsViewHolder(val binding: AppsDesignBinding) : RecyclerView.ViewHolder(binding.root) {
       fun setData(itemData: AllAppsModel) {
           binding.tvAppName.text = itemData.appName
//            Glide.with(binding.ivImage.context).load(itemData.uri).into(binding.ivImage)
       }
   }
}

Enter fullscreen mode Exit fullscreen mode

This File Manager app is built using a clean and structured architecture with Activities, Adapters, and Data Models. It uses MediaStore to fetch images, videos, audio, documents, and APK files along with their count and total storage size. Installed and system apps are retrieved using PackageManager with proper filtering logic. RecyclerView with GridLayoutManager is used to display categories, media files, and apps efficiently. Coroutines with Dispatchers.IO ensure background processing for smooth performance. Overall, the implementation is modern, organized, and suitable for a functional Android File Manager application.

Top comments (0)