package moe.yuuta.workmode.utils import android.annotation.SystemApi import android.content.Context import android.content.Intent import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.content.pm.PackageManager.GET_DISABLED_COMPONENTS import android.content.pm.ResolveInfo import android.os.Bundle import android.os.Process import android.view.ViewGroup import android.widget.LinearLayout import androidx.core.view.children import com.google.android.material.tabs.TabLayout import moe.yuuta.workmode.BuildConfig import moe.yuuta.workmode.suspend.data.PersistableSuspendedApp import moe.yuuta.workmode.suspend.data.TransferableSuspendedApp import java.util.function.Predicate import java.util.stream.Collectors object Utils { private val PROTECTED_UIDS = arrayOf(Process.SYSTEM_UID, Process.myUid(), Process.PHONE_UID, 0, 2000, 1007, 1010, 1013, 1019, 1016, 1017, 1027, 1002, 1023, 1032, 1037, 1041, 1047, 1053, 1061, 1067, 1068, 9999) private val PROTECTED_PACKAGES = arrayOf( "android", BuildConfig.APPLICATION_ID ) private val PROTECTED_PACKAGES_WIDE_MATCH = arrayOf( "com.android." ) private val WHITELIST_PKGS = arrayOf( "com.android.chrome" ) fun buildGeneralApplicationInfoFilter(context: Context): Predicate { val i = Intent(Intent.ACTION_MAIN) i.addCategory(Intent.CATEGORY_HOME) val launchers = context.packageManager.queryIntentActivities(i, 0) .stream() .map { return@map it.resolvePackageName } .collect(Collectors.toList()) return object : Predicate { override fun test(it: ApplicationInfo): Boolean { for (pkg in WHITELIST_PKGS) if (pkg == it.packageName) { return true } for (pkg in PROTECTED_PACKAGES) if (pkg == it.packageName) { return false } for (pkg in PROTECTED_PACKAGES_WIDE_MATCH) if (it.packageName.startsWith(pkg)) { return false } for (uid in PROTECTED_UIDS) if (uid == it.uid) { return false } if (launchers.contains(it.packageName)) { return false } if (it.uid < Process.FIRST_APPLICATION_UID || it.uid > Process.LAST_APPLICATION_UID) { return false } return context.packageManager.getLaunchIntentForPackage(it.packageName) != null } } } fun buildGeneralSuspendedAppInfoFilter(context: Context): Predicate { val i = Intent(Intent.ACTION_MAIN) i.addCategory(Intent.CATEGORY_HOME) val launchers = context.packageManager.queryIntentActivitiesAsUser(i, 0, context.userId) .stream() .map { return@map it.resolvePackageName } .collect(Collectors.toList()) return object : Predicate { override fun test(it: TransferableSuspendedApp): Boolean { for (pkg in WHITELIST_PKGS) if (pkg == it.packageName) { return true } for (pkg in PROTECTED_PACKAGES) if (pkg == it.packageName) { return false } for (pkg in PROTECTED_PACKAGES_WIDE_MATCH) if (it.packageName.startsWith(pkg)) { return false } val itUid = context.packageManager.getPackageUidAsUser(it.packageName, GET_DISABLED_COMPONENTS, it.userId) for (uid in PROTECTED_UIDS) if (uid == itUid) { return false } if (launchers.contains(it.packageName)) { return false } if (itUid < Process.FIRST_APPLICATION_UID) { return false } return getLaunchIntentForPackageAsUser(it.packageName, context.packageManager, it.userId) != null } } } fun getLaunchIntentForPackageAsUser(packageName: String, pm: PackageManager, userId: Int): Intent? { // First see if the package has an INFO activity; the existence of // such an activity is implied to be the desired front-door for the // overall package (such as if it has multiple launcher entries). val intentToResolve = Intent(Intent.ACTION_MAIN) intentToResolve.addCategory(Intent.CATEGORY_INFO) intentToResolve.setPackage(packageName) var ris: List? = pm.queryIntentActivitiesAsUser(intentToResolve, 0, userId) // Otherwise, try to find a main launcher activity. if (ris == null || ris.size <= 0) { // reuse the intent instance intentToResolve.removeCategory(Intent.CATEGORY_INFO) intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER) intentToResolve.setPackage(packageName) ris = pm.queryIntentActivitiesAsUser(intentToResolve, 0, userId) } if (ris == null || ris.size <= 0) { return null } val intent = Intent(intentToResolve) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK intent.setClassName( ris[0].activityInfo.packageName, ris[0].activityInfo.name ) return intent } fun dumpExtras(bundle: Bundle?): String { val builder = StringBuilder() if (bundle != null) { for (key in bundle.keySet()) { val value = bundle.get(key) builder.append("value: ") builder.append(value?.toString()) builder.append(" key: ") builder.append(key) builder.append(" type: ") builder.append(value.javaClass.name) } } return builder.toString() } fun setViewTreeEnable(viewGroup: ViewGroup, isEnabled: Boolean) { for (child in viewGroup.children) { if (child is ViewGroup) setViewTreeEnable(child, isEnabled) else child.isEnabled = isEnabled } } /** * Thanks to https://stackoverflow.com/questions/31702725/disable-tablayout */ fun makeTabLayoutDisable(tabLayout: TabLayout, enable: Boolean) { val tabStrip = tabLayout.getChildAt(0) as LinearLayout for (i in 0 until tabStrip.childCount) { tabStrip.getChildAt(i).setOnTouchListener { v, event -> !enable } } } fun canSafelyLoadAppInfo(packageInfo: TransferableSuspendedApp, context: Context): Boolean { return Utils.canSafelyLoadAppInfo(packageInfo, Process.myUserHandle().hashCode(), context) } fun canSafelyLoadAppInfo(packageInfo: PersistableSuspendedApp, context: Context): Boolean { return packageInfo.userId == Process.myUserHandle().hashCode() || isAppInstalledInCurrentUser(packageInfo.packageName, context) } fun canSafelyLoadAppInfo(packageInfo: TransferableSuspendedApp, userId: Int, context: Context): Boolean { return packageInfo.userId == userId || isAppInstalledInUser(packageInfo.packageName, context, userId) } fun isAppInstalledInCurrentUser(packageName: String, context: Context): Boolean { return isAppInstalledInUser(packageName, context, Process.myUserHandle().hashCode()) } @SystemApi fun isAppInstalledInUser(packageName: String, context: Context, userId: Int): Boolean { try { context.packageManager.getPackageInfoAsUser(packageName, PackageManager.GET_DISABLED_COMPONENTS, userId) return true } catch (e: PackageManager.NameNotFoundException) { return false } } }