diff options
Diffstat (limited to 'app/src')
| -rw-r--r-- | app/src/main/AndroidManifest.xml | 4 | ||||
| -rw-r--r-- | app/src/main/java/app/olauncher/MainActivity.kt | 26 | ||||
| -rw-r--r-- | app/src/main/java/app/olauncher/MainViewModel.kt | 58 | ||||
| -rw-r--r-- | app/src/main/java/app/olauncher/data/AppModel.kt | 10 | ||||
| -rw-r--r-- | app/src/main/java/app/olauncher/helper/Utils.kt | 65 | ||||
| -rw-r--r-- | app/src/main/java/app/olauncher/ui/AppDrawerAdapter.kt | 105 | ||||
| -rw-r--r-- | app/src/main/java/app/olauncher/ui/AppDrawerFragment.kt | 46 | ||||
| -rw-r--r-- | app/src/main/res/layout/adapter_private_space_header.xml | 21 | ||||
| -rw-r--r-- | app/src/main/res/values/strings.xml | 1 |
9 files changed, 305 insertions, 31 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b81f8a8..91aa7af 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,7 +6,7 @@ <uses-permission android:name="android.permission.EXPAND_STATUS_BAR" /> <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" - tools:ignore="QueryAllPackagesPermission" /> + tools:ignore="PackageVisibilityPolicy,QueryAllPackagesPermission" /> <uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" /> <!--Used only for downloading wallpapers--> <uses-permission android:name="android.permission.INTERNET" /> @@ -15,6 +15,8 @@ <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" tools:ignore="ProtectedPermissions" /> + <!--Used for Private Space support on Android 15+--> + <uses-permission android:name="android.permission.ACCESS_HIDDEN_PROFILES" /> <application android:allowBackup="true" diff --git a/app/src/main/java/app/olauncher/MainActivity.kt b/app/src/main/java/app/olauncher/MainActivity.kt index 4f2b0f9..1f081b2 100644 --- a/app/src/main/java/app/olauncher/MainActivity.kt +++ b/app/src/main/java/app/olauncher/MainActivity.kt @@ -2,8 +2,10 @@ package app.olauncher import android.annotation.SuppressLint import android.app.Activity +import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import android.content.IntentFilter import android.content.pm.ActivityInfo import android.content.res.Configuration import android.os.Build @@ -51,6 +53,7 @@ class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding private var timerJob: Job? = null private var isResumed = false + private var profileReceiver: BroadcastReceiver? = null // override fun onBackPressed() { // if (navController.currentDestination?.id != R.id.mainFragment) @@ -105,6 +108,19 @@ class MainActivity : AppCompatActivity() { setupOrientation() window.addFlags(FLAG_LAYOUT_NO_LIMITS) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { + profileReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + viewModel.getPrivateSpaceAppList() + } + } + val filter = IntentFilter().apply { + addAction(Intent.ACTION_PROFILE_AVAILABLE) + addAction(Intent.ACTION_PROFILE_UNAVAILABLE) + } + registerReceiver(profileReceiver, filter) + } } override fun onStart() { @@ -346,6 +362,16 @@ class MainActivity : AppCompatActivity() { } } + override fun onDestroy() { + profileReceiver?.let { + try { + unregisterReceiver(it) + } catch (_: Exception) { + } + } + super.onDestroy() + } + @Deprecated("Deprecated in Java") override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) diff --git a/app/src/main/java/app/olauncher/MainViewModel.kt b/app/src/main/java/app/olauncher/MainViewModel.kt index 518cbc8..e513f7b 100644 --- a/app/src/main/java/app/olauncher/MainViewModel.kt +++ b/app/src/main/java/app/olauncher/MainViewModel.kt @@ -3,8 +3,11 @@ package app.olauncher import android.app.Application import android.content.ComponentName import android.content.Context +import android.content.Intent import android.content.pm.LauncherApps +import android.os.Build import android.os.UserHandle +import android.os.UserManager import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope @@ -21,9 +24,12 @@ import app.olauncher.helper.SingleLiveEvent import app.olauncher.helper.WallpaperWorker import app.olauncher.helper.formattedTimeSpent import app.olauncher.helper.getAppsList +import app.olauncher.helper.getPrivateSpaceApps +import app.olauncher.helper.getPrivateSpaceUserHandle import app.olauncher.helper.hasBeenMinutes import app.olauncher.helper.isOlauncherDefault import app.olauncher.helper.isPackageInstalled +import app.olauncher.helper.isPrivateSpaceLocked import app.olauncher.helper.showToast import app.olauncher.helper.usageStats.EventLogWrapper import kotlinx.coroutines.launch @@ -46,18 +52,25 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { val homeAppAlignment = MutableLiveData<Int>() val screenTimeValue = MutableLiveData<String>() + val privateSpaceApps = MutableLiveData<List<AppModel>?>() + val privateSpaceLocked = MutableLiveData<Boolean>() + val privateSpaceAvailable = MutableLiveData<Boolean>() + val showDialog = SingleLiveEvent<String>() val checkForMessages = SingleLiveEvent<Unit?>() val resetLauncherLiveData = SingleLiveEvent<Unit?>() val showRecentApps = SingleLiveEvent<Unit?>() fun selectedApp(appModel: AppModel, flag: Int) { + if (appModel is AppModel.PrivateSpaceHeader) return when (flag) { Constants.FLAG_LAUNCH_APP -> { when (appModel) { is AppModel.PinnedShortcut -> launchShortcut(appModel) is AppModel.App -> launchApp(appModel.appPackage, appModel.activityClassName, appModel.user) + + else -> {} } } @@ -97,6 +110,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { private fun saveHomeApp(appModel: AppModel, position: Int) { when (appModel) { + is AppModel.PrivateSpaceHeader -> return is AppModel.App -> { when (position) { 1 -> { @@ -254,6 +268,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { private fun saveSwipeApp(appModel: AppModel, isLeft: Boolean) { when (appModel) { + is AppModel.PrivateSpaceHeader -> return is AppModel.App -> { if (isLeft) { prefs.appNameSwipeLeft = appModel.appLabel @@ -372,6 +387,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { val apps = getAppsList(appContext, prefs, includeRegularApps = true, includeHiddenApps) appList.value = apps } + getPrivateSpaceAppList() } fun getHiddenApps() { @@ -439,6 +455,48 @@ class MainViewModel(application: Application) : AndroidViewModel(application) { prefs.screenTimeLastUpdated = endTime } + fun getPrivateSpaceAppList() { + viewModelScope.launch { + val handle = getPrivateSpaceUserHandle(appContext) + privateSpaceAvailable.value = handle != null + if (handle != null) { + privateSpaceLocked.value = isPrivateSpaceLocked(appContext, handle) + privateSpaceApps.value = getPrivateSpaceApps(appContext, prefs) + } else { + privateSpaceLocked.value = true + privateSpaceApps.value = emptyList() + } + } + } + + fun openPrivateSpaceSettings() { + try { + val intent = Intent("android.settings.PRIVATE_SPACE_SETTINGS") + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + appContext.startActivity(intent) + } catch (_: Exception) { + try { + val intent = Intent(android.provider.Settings.ACTION_SECURITY_SETTINGS) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + appContext.startActivity(intent) + } catch (_: Exception) { + appContext.showToast(appContext.getString(R.string.unable_to_open_app)) + } + } + } + + fun togglePrivateSpaceLock() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) return + val handle = getPrivateSpaceUserHandle(appContext) ?: return + try { + val userManager = appContext.getSystemService(Context.USER_SERVICE) as UserManager + val currentlyLocked = userManager.isQuietModeEnabled(handle) + userManager.requestQuietModeEnabled(!currentlyLocked, handle) + } catch (e: Exception) { + e.printStackTrace() + } + } + fun setDefaultClockApp() { viewModelScope.launch { try { diff --git a/app/src/main/java/app/olauncher/data/AppModel.kt b/app/src/main/java/app/olauncher/data/AppModel.kt index 71397a9..6df3083 100644 --- a/app/src/main/java/app/olauncher/data/AppModel.kt +++ b/app/src/main/java/app/olauncher/data/AppModel.kt @@ -28,6 +28,16 @@ sealed class AppModel : Comparable<AppModel> { override val user: UserHandle, ) : AppModel() + data class PrivateSpaceHeader( + val isLocked: Boolean = true, + override val user: UserHandle = android.os.Process.myUserHandle(), + ) : AppModel() { + override val appLabel: String = "" + override val key: CollationKey? = null + override val appPackage: String = "" + override val isNew: Boolean = false + } + override fun compareTo(other: AppModel): Int = when { key != null && other.key != null -> key!!.compareTo(other.key) else -> appLabel.compareTo(other.appLabel, true) diff --git a/app/src/main/java/app/olauncher/helper/Utils.kt b/app/src/main/java/app/olauncher/helper/Utils.kt index 372effe..d2f45cb 100644 --- a/app/src/main/java/app/olauncher/helper/Utils.kt +++ b/app/src/main/java/app/olauncher/helper/Utils.kt @@ -85,6 +85,7 @@ suspend fun getAppsList( val collator = Collator.getInstance() for (profile in userManager.userProfiles) { + if (isPrivateSpaceProfile(context, profile)) continue for (app in launcherApps.getActivityList(null, profile)) { val appLabelShown = prefs.getAppRenameLabel(app.applicationInfo.packageName) .ifBlank { app.label.toString() } @@ -189,10 +190,72 @@ private fun upgradeHiddenApps(prefs: Prefs) { fun isPackageInstalled(context: Context, packageName: String, userString: String): Boolean { val launcher = context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps val activityInfo = launcher.getActivityList(packageName, getUserHandleFromString(context, userString)) - if (activityInfo.size > 0) return true + if (activityInfo.isNotEmpty()) return true return false } +fun isPrivateSpaceProfile(context: Context, userHandle: UserHandle): Boolean { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) return false + return try { + val launcherApps = context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps + launcherApps.getLauncherUserInfo(userHandle)?.userType == "android.os.usertype.profile.PRIVATE" + } catch (e: Exception) { + false + } +} + +fun isPrivateSpaceLocked(context: Context, userHandle: UserHandle): Boolean { + return try { + val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager + userManager.isQuietModeEnabled(userHandle) + } catch (e: Exception) { + true + } +} + +fun getPrivateSpaceUserHandle(context: Context): UserHandle? { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) return null + val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager + for (profile in userManager.userProfiles) { + if (isPrivateSpaceProfile(context, profile)) return profile + } + return null +} + +suspend fun getPrivateSpaceApps( + context: Context, + prefs: Prefs, +): MutableList<AppModel> { + return withContext(Dispatchers.IO) { + val appList: MutableList<AppModel> = mutableListOf() + try { + val privateSpaceHandle = getPrivateSpaceUserHandle(context) ?: return@withContext appList + val launcherApps = context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps + val collator = Collator.getInstance() + + for (app in launcherApps.getActivityList(null, privateSpaceHandle)) { + if (app.applicationInfo.packageName == BuildConfig.APPLICATION_ID) continue + val appLabelShown = prefs.getAppRenameLabel(app.applicationInfo.packageName) + .ifBlank { app.label.toString() } + appList.add( + AppModel.App( + appLabel = appLabelShown, + key = collator.getCollationKey(app.label.toString()), + appPackage = app.applicationInfo.packageName, + activityClassName = app.componentName.className, + isNew = false, + user = privateSpaceHandle + ) + ) + } + appList.sortWith(compareBy(collator) { it.appLabel }) + } catch (e: Exception) { + e.printStackTrace() + } + appList + } +} + fun getUserHandleFromString(context: Context, userHandleString: String): UserHandle { val userManager = context.getSystemService(Context.USER_SERVICE) as UserManager for (userHandle in userManager.userProfiles) { diff --git a/app/src/main/java/app/olauncher/ui/AppDrawerAdapter.kt b/app/src/main/java/app/olauncher/ui/AppDrawerAdapter.kt index fd1d58e..01234a5 100644 --- a/app/src/main/java/app/olauncher/ui/AppDrawerAdapter.kt +++ b/app/src/main/java/app/olauncher/ui/AppDrawerAdapter.kt @@ -19,6 +19,7 @@ import app.olauncher.R import app.olauncher.data.AppModel import app.olauncher.data.Constants import app.olauncher.databinding.AdapterAppDrawerBinding +import app.olauncher.databinding.AdapterPrivateSpaceHeaderBinding import app.olauncher.helper.hideKeyboard import app.olauncher.helper.isSystemApp import app.olauncher.helper.showKeyboard @@ -32,9 +33,14 @@ class AppDrawerAdapter( private val appDeleteListener: (AppModel) -> Unit, private val appHideListener: (AppModel, Int) -> Unit, private val appRenameListener: (AppModel, String) -> Unit, -) : ListAdapter<AppModel, AppDrawerAdapter.ViewHolder>(DIFF_CALLBACK), Filterable { + private val privateSpaceToggleListener: () -> Unit = {}, + private val privateSpaceSettingsListener: () -> Unit = {}, +) : ListAdapter<AppModel, RecyclerView.ViewHolder>(DIFF_CALLBACK), Filterable { companion object { + const val VIEW_TYPE_APP = 0 + const val VIEW_TYPE_PRIVATE_HEADER = 1 + val DIFF_CALLBACK = object : DiffUtil.ItemCallback<AppModel>() { override fun areItemsTheSame(oldItem: AppModel, newItem: AppModel): Boolean = when { oldItem is AppModel.App && newItem is AppModel.App -> @@ -43,6 +49,8 @@ class AppDrawerAdapter( oldItem is AppModel.PinnedShortcut && newItem is AppModel.PinnedShortcut -> oldItem.shortcutId == newItem.shortcutId && oldItem.user == newItem.user + oldItem is AppModel.PrivateSpaceHeader && newItem is AppModel.PrivateSpaceHeader -> true + else -> false } @@ -59,30 +67,58 @@ class AppDrawerAdapter( var appsList: MutableList<AppModel> = mutableListOf() var appFilteredList: MutableList<AppModel> = mutableListOf() - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = - ViewHolder( - AdapterAppDrawerBinding.inflate( - LayoutInflater.from(parent.context), - parent, - false + override fun getItemViewType(position: Int): Int { + return when (appFilteredList.getOrNull(position)) { + is AppModel.PrivateSpaceHeader -> VIEW_TYPE_PRIVATE_HEADER + else -> VIEW_TYPE_APP + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + return when (viewType) { + VIEW_TYPE_PRIVATE_HEADER -> PrivateSpaceHeaderViewHolder( + AdapterPrivateSpaceHeaderBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) ) - ) - override fun onBindViewHolder(holder: ViewHolder, position: Int) { + else -> ViewHolder( + AdapterAppDrawerBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { try { - if (appFilteredList.size == 0 || position == RecyclerView.NO_POSITION) return + if (appFilteredList.isEmpty() || position == RecyclerView.NO_POSITION) return val appModel = appFilteredList[holder.bindingAdapterPosition] - holder.bind( - flag, - appLabelGravity, - myUserHandle, - appModel, - appClickListener, - appDeleteListener, - appInfoListener, - appHideListener, - appRenameListener - ) + when (holder) { + is PrivateSpaceHeaderViewHolder -> { + holder.bind( + appLabelGravity, + privateSpaceToggleListener, + privateSpaceSettingsListener, + ) + } + + is ViewHolder -> holder.bind( + flag, + appLabelGravity, + myUserHandle, + appModel, + appClickListener, + appDeleteListener, + appInfoListener, + appHideListener, + appRenameListener + ) + } } catch (e: Exception) { e.printStackTrace() } @@ -98,7 +134,7 @@ class AppDrawerAdapter( val appFilteredList = (if (charSearch.isNullOrBlank()) appsList else appsList.filter { app -> - appLabelMatches(app.appLabel, charSearch) + app !is AppModel.PrivateSpaceHeader && appLabelMatches(app.appLabel, charSearch) } as MutableList<AppModel>) val filterResults = FilterResults() @@ -125,7 +161,8 @@ class AppDrawerAdapter( && autoLaunch && isBangSearch.not() && flag == Constants.FLAG_LAUNCH_APP - && appFilteredList.size > 0 + && appFilteredList.isNotEmpty() + && appFilteredList[0] !is AppModel.PrivateSpaceHeader ) appClickListener(appFilteredList[0]) } catch (e: Exception) { e.printStackTrace() @@ -158,8 +195,24 @@ class AppDrawerAdapter( } fun launchFirstInList() { - if (appFilteredList.size > 0) - appClickListener(appFilteredList[0]) + val first = appFilteredList.firstOrNull { it !is AppModel.PrivateSpaceHeader } + if (first != null) appClickListener(first) + } + + class PrivateSpaceHeaderViewHolder(private val binding: AdapterPrivateSpaceHeaderBinding) : + RecyclerView.ViewHolder(binding.root) { + fun bind( + appLabelGravity: Int, + toggleListener: () -> Unit, + settingsListener: () -> Unit, + ) = with(binding) { + privateSpaceTitle.gravity = appLabelGravity + privateSpaceTitle.setOnClickListener { toggleListener() } + privateSpaceTitle.setOnLongClickListener { + settingsListener() + true + } + } } class ViewHolder(private val binding: AdapterAppDrawerBinding) : @@ -237,7 +290,7 @@ class AppDrawerAdapter( s: CharSequence?, start: Int, count: Int, - after: Int + after: Int, ) { } diff --git a/app/src/main/java/app/olauncher/ui/AppDrawerFragment.kt b/app/src/main/java/app/olauncher/ui/AppDrawerFragment.kt index 7e4cd83..1602a2b 100644 --- a/app/src/main/java/app/olauncher/ui/AppDrawerFragment.kt +++ b/app/src/main/java/app/olauncher/ui/AppDrawerFragment.kt @@ -39,6 +39,10 @@ class AppDrawerFragment : Fragment() { private var flag = Constants.FLAG_LAUNCH_APP private var canRename = false + private var currentAppList: List<AppModel>? = null + private var currentPrivateSpaceApps: List<AppModel>? = null + private var currentPrivateSpaceLocked: Boolean = true + private var currentPrivateSpaceAvailable: Boolean = false private val viewModel: MainViewModel by activityViewModels() private var _binding: FragmentAppDrawerBinding? = null @@ -128,6 +132,7 @@ class AppDrawerFragment : Fragment() { }, appDeleteListener = { appModel -> when (appModel) { + is AppModel.PrivateSpaceHeader -> {} is AppModel.PinnedShortcut -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { requireContext().deletePinnedShortcut( @@ -181,9 +186,17 @@ class AppDrawerFragment : Fragment() { val identifier = when (appModel) { is AppModel.PinnedShortcut -> appModel.shortcutId is AppModel.App -> appModel.appPackage + else -> return@AppDrawerAdapter } prefs.setAppRenameLabel(identifier, renameLabel) viewModel.getAppList() + }, + privateSpaceToggleListener = { + viewModel.togglePrivateSpaceLock() + }, + privateSpaceSettingsListener = { + viewModel.openPrivateSpaceSettings() + findNavController().popBackStack(R.id.mainFragment, false) } ) @@ -221,14 +234,41 @@ class AppDrawerFragment : Fragment() { } } else { viewModel.appList.observe(viewLifecycleOwner) { - it?.let { appModels -> - adapter.setAppList(appModels.toMutableList()) - adapter.filter.filter(binding.search.query) + currentAppList = it + updateCombinedAppList() + } + if (flag == Constants.FLAG_LAUNCH_APP) { + viewModel.privateSpaceAvailable.observe(viewLifecycleOwner) { + currentPrivateSpaceAvailable = it + updateCombinedAppList() + } + viewModel.privateSpaceLocked.observe(viewLifecycleOwner) { + currentPrivateSpaceLocked = it + updateCombinedAppList() + } + viewModel.privateSpaceApps.observe(viewLifecycleOwner) { + currentPrivateSpaceApps = it + updateCombinedAppList() } } } } + private fun updateCombinedAppList() { + val apps = currentAppList ?: return + val combined = apps.toMutableList() + + if (flag == Constants.FLAG_LAUNCH_APP && currentPrivateSpaceAvailable) { + combined.add(AppModel.PrivateSpaceHeader(isLocked = currentPrivateSpaceLocked)) + if (!currentPrivateSpaceLocked) { + currentPrivateSpaceApps?.let { combined.addAll(it) } + } + } + + adapter.setAppList(combined) + adapter.filter.filter(binding.search.query) + } + private fun initClickListeners() { binding.appRename.setOnClickListener { val name = binding.search.query.toString().trim() diff --git a/app/src/main/res/layout/adapter_private_space_header.xml b/app/src/main/res/layout/adapter_private_space_header.xml new file mode 100644 index 0000000..992ca2c --- /dev/null +++ b/app/src/main/res/layout/adapter_private_space_header.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center_vertical" + android:orientation="horizontal"> + + <TextView + android:id="@+id/privateSpaceTitle" + style="@style/TextLarge" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:alpha="0.8" + android:maxLines="1" + android:paddingHorizontal="20dp" + android:paddingVertical="@dimen/app_padding_vertical" + android:text="@string/private_space" /> + +</LinearLayout> + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 803af99..7c3b953 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -163,4 +163,5 @@ <string name="new_year_wish_1">It doesn\'t matter how January went. You still have the rest of the year to do better than ever.</string> <string name="cheers">Cheers!</string> <string name="notification_bar">Notification bar</string> + <string name="private_space">Private space</string> </resources>
\ No newline at end of file |
