Mask private profile apps while private space is locked
This commit is contained in:
@@ -37,6 +37,7 @@ dependencies {
|
||||
implementation(libs.bundles.kotlin)
|
||||
implementation(libs.androidx.core)
|
||||
implementation(libs.androidx.appcompat)
|
||||
implementation(libs.androidx.compose.materialicons)
|
||||
|
||||
implementation(libs.bundles.androidx.lifecycle)
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import de.mm20.launcher2.search.SearchableSerializer
|
||||
|
||||
class FakeApp: Application {
|
||||
override val componentName: ComponentName = ComponentName(randomString(), randomString())
|
||||
override val isSystemApp: Boolean = false
|
||||
override val isSuspended: Boolean = false
|
||||
override val user: UserHandle = Process.myUserHandle()
|
||||
override val versionName: String = "1.0"
|
||||
|
||||
@@ -7,11 +7,26 @@ import android.content.pm.LauncherApps
|
||||
import android.os.UserManager
|
||||
import android.util.Log
|
||||
import androidx.core.content.getSystemService
|
||||
import de.mm20.launcher2.ktx.isAtLeastApiLevel
|
||||
import de.mm20.launcher2.search.SavableSearchable
|
||||
import de.mm20.launcher2.search.SearchableDeserializer
|
||||
import de.mm20.launcher2.search.SearchableSerializer
|
||||
import org.json.JSONObject
|
||||
|
||||
internal class LockedPrivateProfileAppSerializer : SearchableSerializer {
|
||||
override fun serialize(searchable: SavableSearchable): String {
|
||||
searchable as LockedPrivateProfileApp
|
||||
val json = JSONObject()
|
||||
json.put("package", searchable.componentName.packageName)
|
||||
json.put("activity", searchable.componentName.className)
|
||||
json.put("user", searchable.userSerialNumber)
|
||||
return json.toString()
|
||||
}
|
||||
|
||||
override val typePrefix: String
|
||||
get() = "app"
|
||||
}
|
||||
|
||||
class LauncherAppSerializer : SearchableSerializer {
|
||||
override fun serialize(searchable: SavableSearchable): String {
|
||||
searchable as LauncherApp
|
||||
@@ -33,9 +48,26 @@ class LauncherAppDeserializer(val context: Context) : SearchableDeserializer {
|
||||
val userManager = context.getSystemService<UserManager>()!!
|
||||
val userSerial = json.optLong("user")
|
||||
val user = userManager.getUserForSerialNumber(userSerial) ?: return null
|
||||
|
||||
val pkg = json.getString("package")
|
||||
val activity = json.getString("activity")
|
||||
|
||||
val componentName = ComponentName(pkg, activity)
|
||||
|
||||
if (isAtLeastApiLevel(35)) {
|
||||
val launcherUser = launcherApps.getLauncherUserInfo(user) ?: return null
|
||||
if (launcherUser.userType == UserManager.USER_TYPE_PROFILE_PRIVATE && userManager.isQuietModeEnabled(user)) {
|
||||
return LockedPrivateProfileApp(
|
||||
label = context.getString(R.string.app_label_locked_profile),
|
||||
componentName = componentName,
|
||||
user = user,
|
||||
userSerialNumber = userSerial
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val intent = Intent().also {
|
||||
it.component = ComponentName(pkg, json.getString("activity"))
|
||||
it.component = componentName
|
||||
}
|
||||
try {
|
||||
val launcherActivityInfo = launcherApps.resolveActivity(intent, user) ?: return null
|
||||
|
||||
@@ -60,7 +60,7 @@ internal data class LauncherApp(
|
||||
|
||||
private val isMainProfile = launcherActivityInfo.user == Process.myUserHandle()
|
||||
|
||||
override val isSystemApp: Boolean = launcherActivityInfo.applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM != 0
|
||||
private val isSystemApp: Boolean = launcherActivityInfo.applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM != 0
|
||||
|
||||
override val canUninstall: Boolean
|
||||
get() = !isSystemApp && isMainProfile
|
||||
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
package de.mm20.launcher2.applications
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.pm.LauncherApps
|
||||
import android.os.Bundle
|
||||
import android.os.UserHandle
|
||||
import android.os.UserManager
|
||||
import android.util.Log
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Lock
|
||||
import androidx.core.content.getSystemService
|
||||
import de.mm20.launcher2.icons.ColorLayer
|
||||
import de.mm20.launcher2.icons.LauncherIcon
|
||||
import de.mm20.launcher2.icons.StaticLauncherIcon
|
||||
import de.mm20.launcher2.icons.VectorLayer
|
||||
import de.mm20.launcher2.ktx.isAtLeastApiLevel
|
||||
import de.mm20.launcher2.search.Application
|
||||
import de.mm20.launcher2.search.SavableSearchable
|
||||
import de.mm20.launcher2.search.SearchableSerializer
|
||||
|
||||
internal data class LockedPrivateProfileApp(
|
||||
override val label: String,
|
||||
override val componentName: ComponentName,
|
||||
override val user: UserHandle,
|
||||
internal val userSerialNumber: Long,
|
||||
): Application {
|
||||
override val isSuspended: Boolean = false
|
||||
override val versionName: String? = null
|
||||
override val canUninstall: Boolean = false
|
||||
|
||||
override val isPrivate: Boolean = true
|
||||
|
||||
override fun uninstall(context: Context) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
override fun openAppDetails(context: Context) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
override val domain: String = LauncherApp.Domain
|
||||
override val canShareApk: Boolean = false
|
||||
|
||||
override val key: String = "${domain}://${componentName.packageName}:${componentName.className}:${userSerialNumber}"
|
||||
|
||||
override fun overrideLabel(label: String): SavableSearchable {
|
||||
// We don't expose custom labels for locked apps
|
||||
return this
|
||||
}
|
||||
|
||||
override fun launch(context: Context, options: Bundle?): Boolean {
|
||||
if (!isAtLeastApiLevel(35)) return false
|
||||
|
||||
val userManager = context.getSystemService<UserManager>() ?: return false
|
||||
|
||||
if (userManager.isQuietModeEnabled(user)) {
|
||||
userManager.requestQuietModeEnabled(false, user)
|
||||
return true
|
||||
}
|
||||
|
||||
val launcherApps = context.getSystemService<LauncherApps>() ?: return false
|
||||
|
||||
if (isAtLeastApiLevel(31)) {
|
||||
options?.putInt("android.activity.splashScreenStyle", 1)
|
||||
}
|
||||
|
||||
try {
|
||||
launcherApps.startMainActivity(
|
||||
componentName,
|
||||
user,
|
||||
null,
|
||||
options
|
||||
)
|
||||
} catch (e: SecurityException) {
|
||||
Log.e("MM20", "Could not launch app", e)
|
||||
return false
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Log.e("MM20", "Could not launch app", e)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun getPlaceholderIcon(context: Context): StaticLauncherIcon {
|
||||
return StaticLauncherIcon(
|
||||
foregroundLayer = VectorLayer(
|
||||
vector = Icons.Rounded.Lock,
|
||||
),
|
||||
backgroundLayer = ColorLayer(0)
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun loadIcon(context: Context, size: Int, themed: Boolean): LauncherIcon? {
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
override fun getSerializer(): SearchableSerializer {
|
||||
return LockedPrivateProfileAppSerializer()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user