diff --git a/app/src/main/java/com/github/droidworksstudio/common/ContextExtensions.kt b/app/src/main/java/com/github/droidworksstudio/common/ContextExtensions.kt index e4b2682..bfa5bfb 100644 --- a/app/src/main/java/com/github/droidworksstudio/common/ContextExtensions.kt +++ b/app/src/main/java/com/github/droidworksstudio/common/ContextExtensions.kt @@ -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()) } } } diff --git a/app/src/main/java/com/github/droidworksstudio/launcher/Application.kt b/app/src/main/java/com/github/droidworksstudio/launcher/Application.kt index b9aeef3..9fbc6e1 100644 --- a/app/src/main/java/com/github/droidworksstudio/launcher/Application.kt +++ b/app/src/main/java/com/github/droidworksstudio/launcher/Application.kt @@ -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) diff --git a/app/src/main/java/com/github/droidworksstudio/launcher/data/dao/AppInfoDAO.kt b/app/src/main/java/com/github/droidworksstudio/launcher/data/dao/AppInfoDAO.kt index d96ba90..39323f7 100644 --- a/app/src/main/java/com/github/droidworksstudio/launcher/data/dao/AppInfoDAO.kt +++ b/app/src/main/java/com/github/droidworksstudio/launcher/data/dao/AppInfoDAO.kt @@ -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 - @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) { diff --git a/app/src/main/java/com/github/droidworksstudio/launcher/data/entities/AppInfo.kt b/app/src/main/java/com/github/droidworksstudio/launcher/data/entities/AppInfo.kt index 45d2336..4662b0f 100644 --- a/app/src/main/java/com/github/droidworksstudio/launcher/data/entities/AppInfo.kt +++ b/app/src/main/java/com/github/droidworksstudio/launcher/data/entities/AppInfo.kt @@ -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, diff --git a/app/src/main/java/com/github/droidworksstudio/launcher/helper/contextProvider/kt/ContextProvider.kt b/app/src/main/java/com/github/droidworksstudio/launcher/helper/contextProvider/kt/ContextProvider.kt new file mode 100644 index 0000000..ad6df49 --- /dev/null +++ b/app/src/main/java/com/github/droidworksstudio/launcher/helper/contextProvider/kt/ContextProvider.kt @@ -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 + } +} + + diff --git a/app/src/main/java/com/github/droidworksstudio/launcher/repository/AppInfoRepository.kt b/app/src/main/java/com/github/droidworksstudio/launcher/repository/AppInfoRepository.kt index 19359d3..36db6c0 100644 --- a/app/src/main/java/com/github/droidworksstudio/launcher/repository/AppInfoRepository.kt +++ b/app/src/main/java/com/github/droidworksstudio/launcher/repository/AppInfoRepository.kt @@ -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 = withContext(Dispatchers.IO) { + val packages = mutableSetOf() - val packages = HashSet() - 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 = withContext(Dispatchers.IO) { @@ -125,9 +145,7 @@ class AppInfoRepository @Inject constructor( val newAppList: List = 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) diff --git a/app/src/main/java/com/github/droidworksstudio/launcher/ui/drawer/DrawFragment.kt b/app/src/main/java/com/github/droidworksstudio/launcher/ui/drawer/DrawFragment.kt index f185a62..2dbf0ff 100644 --- a/app/src/main/java/com/github/droidworksstudio/launcher/ui/drawer/DrawFragment.kt +++ b/app/src/main/java/com/github/droidworksstudio/launcher/ui/drawer/DrawFragment.kt @@ -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() { diff --git a/app/src/main/java/com/github/droidworksstudio/launcher/ui/drawer/DrawViewHolder.kt b/app/src/main/java/com/github/droidworksstudio/launcher/ui/drawer/DrawViewHolder.kt index 950b0d0..4453df9 100644 --- a/app/src/main/java/com/github/droidworksstudio/launcher/ui/drawer/DrawViewHolder.kt +++ b/app/src/main/java/com/github/droidworksstudio/launcher/ui/drawer/DrawViewHolder.kt @@ -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 } }