Refactor: Added better support for work profiles.
fixed #114 Signed-off-by: HeCodes2Much <wayne6324@gmail.com>
This commit is contained in:
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user