Refactor: Added better support for work profiles.

fixed #114

Signed-off-by: HeCodes2Much <wayne6324@gmail.com>
This commit is contained in:
HeCodes2Much
2024-08-24 20:15:54 +01:00
parent 7706c931a4
commit 9163334c9d
8 changed files with 158 additions and 63 deletions

View File

@@ -3,6 +3,7 @@ package com.github.droidworksstudio.common
import android.Manifest
import android.app.SearchManager
import android.content.ActivityNotFoundException
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.LauncherApps
@@ -33,9 +34,10 @@ import androidx.core.graphics.drawable.toBitmap
import androidx.core.os.ConfigurationCompat
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LifecycleOwner
import com.github.droidworksstudio.launcher.utils.Constants
import com.github.droidworksstudio.launcher.data.entities.AppInfo
import com.github.droidworksstudio.launcher.helper.PreferenceHelper
import com.github.droidworksstudio.launcher.ui.activities.LauncherActivity
import com.github.droidworksstudio.launcher.utils.Constants
import java.util.Calendar
import java.util.Date
import kotlin.math.pow
@@ -184,14 +186,48 @@ fun Context.appInfo(appInfo: AppInfo) {
}
fun Context.launchApp(appInfo: AppInfo) {
val intent = this.packageManager.getLaunchIntentForPackage(appInfo.packageName)
if (intent != null) {
this.startActivity(intent)
val packageName = appInfo.packageName
val primaryUserHandle = android.os.Process.myUserHandle()
val userHandle = getUserHandleFromId(appInfo.userHandle) ?: primaryUserHandle // Fallback to current user if not provided
// Get the LauncherApps service
val launcherApps = getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
// Attempt to get the launch intent for the package
val activityList = launcherApps.getActivityList(packageName, userHandle)
Log.d("launchApp", "launchApp: $packageName - $activityList")
if (activityList.isNotEmpty()) {
val componentName = ComponentName(packageName, activityList[0].name)
try {
// Start the app's main activity
launcherApps.startMainActivity(componentName, userHandle, null, null)
} catch (e: SecurityException) {
Log.e("launchApp", "SecurityException: ${e.message}")
showLongToast("Unable to launch app due to security restrictions")
} catch (e: Exception) {
Log.e("launchApp", "Exception: ${e.message}")
showLongToast("Unable to launch app")
}
} else {
showLongToast("Failed to open the application")
showLongToast("Failed to find the application activity")
}
}
fun Context.getUserHandleFromId(userId: Int): UserHandle? {
val userManager = getSystemService(Context.USER_SERVICE) as UserManager
// Get all available UserHandles
val userProfiles = userManager.userProfiles
// Iterate over user profiles
for (userProfile in userProfiles) {
// Check if the UserHandle matches the provided user ID
if (userProfile.hashCode() == userId) {
return userProfile
}
}
return null
}
fun Context.launchClock() {
try {
val intent = Intent(AlarmClock.ACTION_SHOW_ALARMS)
@@ -212,11 +248,11 @@ fun Context.launchCalendar() {
this.startActivity(Intent(Intent.ACTION_VIEW, builder.build()))
} catch (e: Exception) {
try {
val intent = Intent(Intent.ACTION_MAIN)
val intent = Intent(this, LauncherActivity::class.java)
intent.addCategory(Intent.CATEGORY_APP_CALENDAR)
this.startActivity(intent)
} catch (e: Exception) {
Log.d("openCalendar", e.toString())
Log.e("openCalendar", e.toString())
}
}
}

View File

@@ -4,6 +4,7 @@ import android.app.Application
import android.content.Context
import android.graphics.Typeface
import com.github.droidworksstudio.launcher.helper.PreferenceHelper
import com.github.droidworksstudio.launcher.helper.contextProvider.kt.ContextProvider
import dagger.hilt.android.HiltAndroidApp
import org.acra.ACRA
import org.acra.ReportField
@@ -23,6 +24,8 @@ class Application : Application() {
override fun onCreate() {
super.onCreate()
ContextProvider.init(this)
setCustomFont(applicationContext)
val pkgName = getString(R.string.app_name)

View File

@@ -1,7 +1,13 @@
package com.github.droidworksstudio.launcher.data.dao
import android.util.Log
import androidx.room.*
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.Update
import com.github.droidworksstudio.launcher.data.entities.AppInfo
import kotlinx.coroutines.flow.Flow
@@ -73,10 +79,10 @@ interface AppInfoDAO {
@Query("SELECT * FROM app WHERE is_favorite = 1")
fun getFavoriteAppInfo(): List<AppInfo>
@Query("SELECT * FROM app WHERE package_name = :packageName AND is_work = 0")
@Query("SELECT * FROM app WHERE package_name = :packageName AND user_handle = 0")
suspend fun getAppByPackageName(packageName: String): AppInfo?
@Query("SELECT * FROM app WHERE package_name = :packageName AND is_work = 1")
@Query("SELECT * FROM app WHERE package_name = :packageName AND user_handle != 0")
suspend fun getAppByPackageNameWork(packageName: String): AppInfo?
private fun logUpdate(message: String, appInfo: AppInfo) {

View File

@@ -26,8 +26,8 @@ data class AppInfo(
@ColumnInfo(name = "is_lock")
var lock: Boolean,
@ColumnInfo(name = "is_work")
var work: Boolean,
@ColumnInfo(name = "user_handle")
var userHandle: Int,
@ColumnInfo(name = "create_time")
var createTime: String,

View File

@@ -0,0 +1,14 @@
package com.github.droidworksstudio.launcher.helper.contextProvider.kt
import android.content.Context
object ContextProvider {
lateinit var applicationContext: Context
private set
fun init(context: Context) {
applicationContext = context
}
}

View File

@@ -8,9 +8,10 @@ import android.os.UserHandle
import android.os.UserManager
import android.util.Log
import androidx.annotation.RequiresApi
import com.github.droidworksstudio.launcher.utils.Constants
import com.github.droidworksstudio.launcher.data.dao.AppInfoDAO
import com.github.droidworksstudio.launcher.data.entities.AppInfo
import com.github.droidworksstudio.launcher.helper.contextProvider.kt.ContextProvider
import com.github.droidworksstudio.launcher.utils.Constants
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.firstOrNull
@@ -24,6 +25,9 @@ class AppInfoRepository @Inject constructor(
private val appDao: AppInfoDAO,
) {
private val context: Context
get() = ContextProvider.applicationContext
@Inject
lateinit var packageManager: PackageManager
@@ -93,16 +97,32 @@ class AppInfoRepository @Inject constructor(
appDao.updateLockApp(appInfo, appLock)
}
@RequiresApi(Build.VERSION_CODES.R)
private suspend fun getInstalledPackages(): Set<String> = withContext(Dispatchers.IO) {
val packages = mutableSetOf<String>()
val packages = HashSet<String>()
val apps = packageManager.getInstalledApplications(PackageManager.GET_META_DATA)
for (app in apps) {
packages.add(app.packageName)
// Obtain UserManager and LauncherApps services
val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager
val launcherApps = context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
// Iterate through each user profile
for (userHandle in userManager.userProfiles) {
try {
// Get the package list for the user profile
val apps = launcherApps.getActivityList(null, userHandle)
for (app in apps) {
packages.add(app.applicationInfo.packageName)
}
} catch (e: Exception) {
// Handle potential exceptions
Log.e("AppInfoRepository", "Error retrieving packages for user $userHandle", e)
}
}
packages
}
@RequiresApi(Build.VERSION_CODES.O)
suspend fun initInstalledAppInfo(context: Context): List<AppInfo> =
withContext(Dispatchers.IO) {
@@ -125,9 +145,7 @@ class AppInfoRepository @Inject constructor(
val newAppList: List<AppInfo> = userManager.userProfiles
.flatMap { profile ->
// Invoke the getIdentifier method on the UserHandle instance
val userId = getIdentifierMethod.invoke(profile) as Int
when (userId) {
when (val userId = getIdentifierMethod.invoke(profile) as Int) {
0 -> {
// Handle the case when profile is UserHandle{0}
launcherApps.getActivityList(null, profile)
@@ -142,7 +160,7 @@ class AppInfoRepository @Inject constructor(
hidden = false,
lock = false,
createTime = currentDateTime.toString(),
work = false,
userHandle = userId,
)
} else {
val existingApp = getAppByPackageName(packageName)
@@ -166,7 +184,7 @@ class AppInfoRepository @Inject constructor(
hidden = false,
lock = false,
createTime = currentDateTime.toString(),
work = true,
userHandle = userId,
)
} else {
val existingApp = getAppByPackageNameWork(packageName)

View File

@@ -12,8 +12,9 @@ import androidx.annotation.RequiresApi
import androidx.appcompat.widget.SearchView
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.coroutineScope
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.github.droidworksstudio.common.hideKeyboard
@@ -36,6 +37,7 @@ import com.github.droidworksstudio.launcher.listener.ScrollEventListener
import com.github.droidworksstudio.launcher.ui.bottomsheetdialog.AppInfoBottomSheetFragment
import com.github.droidworksstudio.launcher.viewmodel.AppViewModel
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import javax.inject.Inject
@@ -84,6 +86,7 @@ class DrawFragment : Fragment(),
}
@SuppressLint("NewApi")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
appHelper.dayNightMod(requireContext(), binding.drawBackground)
super.onViewCreated(view, savedInstanceState)
@@ -93,6 +96,9 @@ class DrawFragment : Fragment(),
setupSearch()
observeClickListener()
observeSwipeTouchListener()
// Initialize observation of drawer apps
observeDrawerApps()
}
private fun setupRecyclerView() {
@@ -106,13 +112,20 @@ class DrawFragment : Fragment(),
@RequiresApi(Build.VERSION_CODES.R)
private fun observeDrawerApps() {
// Start comparing installed app information
viewModel.compareInstalledAppInfo()
@Suppress("DEPRECATION")
viewLifecycleOwner.lifecycleScope.launchWhenCreated {
viewModel.drawApps.collect {
drawAdapter.submitList(it)
drawAdapter.updateDataWithStateFlow(it)
// Launch a coroutine tied to the lifecycle of the view
viewLifecycleOwner.lifecycleScope.launch {
// Use repeatOnLifecycle to manage the lifecycle state
repeatOnLifecycle(Lifecycle.State.CREATED) {
// Collect the drawer apps from the ViewModel
viewModel.drawApps.collect { apps ->
// Update the adapter with the new list of apps
drawAdapter.submitList(apps)
// Update the adapter's data with the new state flow
drawAdapter.updateDataWithStateFlow(apps)
}
}
}
}
@@ -175,45 +188,50 @@ class DrawFragment : Fragment(),
private fun checkAppThenRun(query: String) {
val searchQuery = "%$query%"
@Suppress("DEPRECATION")
viewLifecycleOwner.lifecycle.coroutineScope.launchWhenCreated {
val trimmedQuery = searchQuery.trim()
viewModel.searchAppInfo(trimmedQuery).collect { searchResults ->
val numberOfItemsLeft = searchResults.size
val appResults = searchResults.firstOrNull()
if (numberOfItemsLeft == 0 && !requireContext().searchOnPlayStore(
trimmedQuery
)
) {
requireContext().openSearch(trimmedQuery)
} else {
appResults?.let { appInfo ->
observeBioAuthCheck(appInfo)
// Launch a coroutine tied to the lifecycle of the view
viewLifecycleOwner.lifecycleScope.launch {
// Use repeatOnLifecycle to manage the lifecycle state
repeatOnLifecycle(Lifecycle.State.CREATED) {
val trimmedQuery = searchQuery.trim()
viewModel.searchAppInfo(trimmedQuery).collect { searchResults ->
val numberOfItemsLeft = searchResults.size
val appResults = searchResults.firstOrNull()
if (numberOfItemsLeft == 0 && !requireContext().searchOnPlayStore(trimmedQuery)) {
requireContext().openSearch(trimmedQuery)
} else {
appResults?.let { appInfo ->
observeBioAuthCheck(appInfo)
}
drawAdapter.submitList(searchResults)
}
drawAdapter.submitList(searchResults)
}
}
}
}
private fun searchApp(query: String) {
val searchQuery = "%$query%"
@Suppress("DEPRECATION")
viewLifecycleOwner.lifecycle.coroutineScope.launchWhenCreated {
viewModel.searchAppInfo(searchQuery).collect { searchResults ->
val numberOfItemsLeft = searchResults.size
val appResults = searchResults.firstOrNull()
when (numberOfItemsLeft) {
1 -> {
appResults?.let { appInfo ->
if (preferenceHelper.automaticOpenApp) observeBioAuthCheck(appInfo)
}
drawAdapter.submitList(searchResults)
}
else -> {
drawAdapter.submitList(searchResults)
// Launch a coroutine tied to the lifecycle of the view
viewLifecycleOwner.lifecycleScope.launch {
// Repeat the block when the lifecycle is at least CREATED
repeatOnLifecycle(Lifecycle.State.CREATED) {
// Collect search results from the ViewModel
viewModel.searchAppInfo(searchQuery).collect { searchResults ->
val numberOfItemsLeft = searchResults.size
val appResults = searchResults.firstOrNull()
when (numberOfItemsLeft) {
1 -> {
appResults?.let { appInfo ->
if (preferenceHelper.automaticOpenApp) observeBioAuthCheck(appInfo)
}
drawAdapter.submitList(searchResults)
}
else -> {
drawAdapter.submitList(searchResults)
}
}
}
}
@@ -244,14 +262,12 @@ class DrawFragment : Fragment(),
@RequiresApi(Build.VERSION_CODES.R)
override fun onResume() {
super.onResume()
observeDrawerApps()
if (preferenceHelper.automaticKeyboard) binding.searchViewText.showKeyboard()
}
@RequiresApi(Build.VERSION_CODES.R)
override fun onStart() {
super.onStart()
observeDrawerApps()
}
override fun onStop() {

View File

@@ -37,11 +37,11 @@ class DrawViewHolder(
appDrawName.setTextColor(preferenceHelper.appColor)
appDrawName.textSize = preferenceHelper.appTextSize
appDrawName.gravity = preferenceHelper.homeAppAlignment
Log.d("Tag", "Draw Adapter: ${appInfo.appName + appInfo.id} | ${appInfo.work}")
Log.d("Tag", "Draw Adapter: ${appInfo.appName + appInfo.id} | ${appInfo.userHandle}")
val icon = AppCompatResources.getDrawable(appDrawName.context, R.drawable.work_profile)
val px = preferenceHelper.appTextSize.toInt().dpToPx()
icon?.setBounds(0, 0, px, px)
if (appInfo.work) {
if (appInfo.userHandle > 0) {
val appLabelGravity = preferenceHelper.homeAppAlignment
if (appLabelGravity == Gravity.START) {
@@ -77,7 +77,9 @@ class DrawViewHolder(
}
itemView.setOnLongClickListener {
onAppLongClickedListener.onAppLongClicked(appInfo)
if (appInfo.userHandle <= 0) {
onAppLongClickedListener.onAppLongClicked(appInfo)
}
true
}
}