aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/java/moe/yuuta/workmode/access/WorkModeAccessor.kt
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java/moe/yuuta/workmode/access/WorkModeAccessor.kt')
-rw-r--r--app/src/main/java/moe/yuuta/workmode/access/WorkModeAccessor.kt317
1 files changed, 317 insertions, 0 deletions
diff --git a/app/src/main/java/moe/yuuta/workmode/access/WorkModeAccessor.kt b/app/src/main/java/moe/yuuta/workmode/access/WorkModeAccessor.kt
new file mode 100644
index 0000000..d8f17cd
--- /dev/null
+++ b/app/src/main/java/moe/yuuta/workmode/access/WorkModeAccessor.kt
@@ -0,0 +1,317 @@
+package moe.yuuta.workmode.access
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.os.Parcel
+import android.os.PersistableBundle
+import android.service.quicksettings.TileService
+import com.elvishew.xlog.Logger
+import com.elvishew.xlog.XLog
+import eu.chainfire.librootjava.RootJava
+import moe.yuuta.workmode.BuildConfig
+import moe.yuuta.workmode.R
+import moe.yuuta.workmode.Setup
+import moe.yuuta.workmode.suspend.SuspendTile
+import moe.yuuta.workmode.suspend.SuspendedApp
+import moe.yuuta.workmode.suspend.data.ListMode
+import moe.yuuta.workmode.suspend.data.Status
+import moe.yuuta.workmode.utils.BundleUtils
+import moe.yuuta.workmode.utils.ByteArraySerializer
+import moe.yuuta.workmode.utils.Utils
+import java.util.stream.Collectors
+
+class WorkModeAccessor {
+ companion object {
+ const val ACTION_GET_APP_EXTRAS = "get_app_extras"
+ const val ACTION_IS_SUSPENDED = "is_suspended"
+ const val ACTION_GET_LAUNCHER_EXTRAS = "get_launcher_extras"
+ const val ACTION_SET_SUSPENDED = "set_suspended"
+ const val ACTION_DUMP = "dump"
+ const val ACTION_GET_ALL_PACKAGES_SUSPENDED_BY_WORK_MODE = "get_all_packages_suspended_by_work_mode"
+ const val ACTION_APPLY = "apply"
+
+ @JvmStatic
+ fun main(vararg args: String) {
+ RootJava.restoreOriginalLdLibraryPath()
+ WorkModeAccessor().go(args)
+ }
+ }
+
+ private lateinit var logger: Logger
+ private lateinit var mContext: Context
+ private lateinit var pmAccess: AccessLayer
+ private lateinit var mLogPath: String
+
+ private fun go(args: Array<out String>) {
+ mContext = RootJava.getPackageContext(BuildConfig.APPLICATION_ID)
+ pmAccess = AccessLayer(mContext)
+ mContext.sendBroadcast(Intent(AccessorStarter.ACTION_UPDATE_UI_PROGRESS)
+ .putExtra(AccessorStarter.EXTRA_SHOW_PROGRESS, true))
+ var parcel = Parcel.obtain()
+ val argsByteArray = ByteArraySerializer.deserialize(args[0])
+ val argsParcel = Parcel.obtain()
+ argsParcel.unmarshall(argsByteArray, 0, argsByteArray.size)
+ argsParcel.setDataPosition(0)
+ mLogPath = argsParcel.readString() ?: "/data/adb"
+ Setup.initLogs(mLogPath)
+ logger = XLog.tag("Accessor").build()
+ try {
+ // General successful flag: 1 = success; 0 = unsuccessful
+ parcel.writeByte(1)
+ runGo(argsParcel, parcel)
+ } catch (e: Throwable) {
+ logger.e("Unexpected exception caused in accessor", e)
+ parcel.recycle()
+ // Re-mark it as unsuccessful
+ parcel = Parcel.obtain()
+ parcel.writeByte(0)
+ parcel.writeSerializable(e)
+ }
+ try {
+ TileService.requestListeningState(mContext, ComponentName(mContext, SuspendTile::class.java))
+ mContext.sendBroadcast(Intent(AccessorStarter.ACTION_UPDATE_UI_STATE)
+ .setPackage(BuildConfig.APPLICATION_ID))
+ } catch (e: Throwable) {
+ logger.e("Unable to refresh tile", e)
+ }
+ System.out.println(ByteArraySerializer.serialize(parcel.marshall()))
+ parcel.recycle()
+ mContext.sendBroadcast(Intent(AccessorStarter.ACTION_UPDATE_UI_PROGRESS)
+ .putExtra(AccessorStarter.EXTRA_SHOW_PROGRESS, false))
+ System.exit(0)
+ }
+
+ private fun runGo(argsParcel: Parcel, parcel: Parcel) {
+ when(argsParcel.readString()) {
+ ACTION_GET_APP_EXTRAS -> {
+ val bundle = pmAccess.getSuspendedPackageAppExtras(argsParcel.readString() ?: "android")
+ parcel.writeBundle(if (bundle != null) Bundle(bundle) else Bundle.EMPTY)
+ }
+ ACTION_IS_SUSPENDED -> {
+ val packageNames = argsParcel.createStringArray() ?: arrayOf("android")
+ var allSuspended = true
+ for (packageName in packageNames) {
+ if (!pmAccess.isPackageSuspended(packageName)) allSuspended = false
+ }
+ parcel.writeByte(if (allSuspended) 1 else 0)
+ }
+ ACTION_GET_LAUNCHER_EXTRAS -> {
+ parcel.writeBundle(pmAccess.getSuspendedPackageLauncherExtras(argsParcel.readString() ?: "android") ?: Bundle.EMPTY)
+ }
+ ACTION_SET_SUSPENDED -> {
+ val packageNames = argsParcel.createStringArray() ?: arrayOf("android")
+ val suspended = argsParcel.readByte() == 1.toByte()
+ logger.d("Running suspend: $suspended on ${packageNames.size} packages.")
+ val appExtras = argsParcel.readBundle()
+ val launcherExtras = argsParcel.readBundle()
+ val dialogMessage = argsParcel.readString() ?: "WorkMode"
+ argsParcel.recycle()
+ parcel.writeStringArray(suspend(packageNames, suspended, appExtras, launcherExtras, dialogMessage))
+ }
+ ACTION_DUMP -> {
+ val pkg = argsParcel.readString() ?: "android"
+ parcel.writeByte(if (pmAccess.isPackageSuspended(pkg)) 1 else 0)
+ parcel.writeBundle(ShellAccessorStarter(mLogPath).getSuspendedPackageAppExtras(pkg, false))
+ parcel.writeBundle(pmAccess.getSuspendedPackageLauncherExtras(pkg) ?: Bundle.EMPTY)
+ }
+ ACTION_GET_ALL_PACKAGES_SUSPENDED_BY_WORK_MODE -> {
+ parcel.writeStringList(getPackagesSuspendedByWorkMode())
+ }
+ ACTION_APPLY -> {
+ apply(argsParcel)
+ }
+ }
+ }
+
+ private fun suspend(packageNames: Array<String>, suspended: Boolean,
+ appExtras: Bundle, launcherExtras: Bundle,
+ dialogMessage: String): Array<String> =
+ pmAccess.setPackagesSuspended(
+ packageNames,
+ suspended,
+ BundleUtils.toPersistableBundle(appExtras),
+ BundleUtils.toPersistableBundle(launcherExtras),
+ dialogMessage
+ )
+
+ private fun getPackagesSuspendedByWorkMode(): List<String> =
+ mContext.packageManager.getInstalledApplications(0)
+ .stream()
+ .filter(Utils.buildGeneralApplicationInfoFilter(mContext))
+ .filter {
+ return@filter pmAccess.isPackageSuspended(it.packageName) &&
+ SuspendedApp.deserializeBundle(pmAccess.getSuspendedPackageLauncherExtras(it.packageName)).isSuspendedByWorkMode
+ }
+ .map {
+ return@map it.packageName
+ }
+ .collect(Collectors.toList())
+
+ private fun apply(args: Parcel) {
+ // Compare system's list and ours.
+ // Blacklist:
+ // System suspended -> {
+ // in our list -> ON - don't care; OFF - unsuspend
+ // not in our list -> unsuspend
+ // }
+ // System not suspended -> {
+ // in our list -> ON - suspend; OFF - don't care
+ // not in our list -> don't care
+ // }
+ // Whitelist:
+ // System suspended -> {
+ // in our whitelist -> unsuspend
+ // not in our whitelist -> ON - don't care; OFF - unsuspend
+ // }
+ // System not suspended -> {
+ // in our whitelist -> don't care
+ // not in our whitelist -> ON - suspend; OFF - don't care
+ // }
+
+ // This is the plan for Off->On or On->On situations. If we are heading
+ // Off, we just ignore all tasks which is going to suspend an app. Because
+ // we need to restore.
+
+ // We use these two lists to determine whatever an app is suspended
+ // It it is suspended but not appears in systemSuspendedList, we know that
+ // it is suspended by other apps, like D**ital Wellbeing, we can just override it.
+ val systemSuspendedList = getPackagesSuspendedByWorkMode()
+ val systemAllAppList = mContext.packageManager.getInstalledApplications(0)
+ .stream()
+ .filter(Utils.buildGeneralApplicationInfoFilter(mContext))
+ .map { return@map it.packageName }
+ .collect(Collectors.toList())
+ val ourList = args.createStringArrayList()
+ val listMode = when (args.readInt()) {
+ 1 -> ListMode.BLACKLIST
+ 2 -> ListMode.WHITELIST
+ else -> throw IllegalArgumentException("Unexpected list mode")
+ }
+ val status = when (args.readInt()) {
+ 1 -> Status.ON
+ 2 -> Status.OFF
+ else -> throw IllegalArgumentException("Unexpected status")
+ }
+
+ val tasks = systemAllAppList.stream()
+ // Filter "don't care" situations, do not map them here.
+ .filter {
+ val systemSuspended = systemSuspendedList.contains(it)
+ val inOurList = ourList.contains(it)
+ when (listMode) {
+ ListMode.BLACKLIST -> {
+ if (systemSuspended) {
+ if (inOurList) {
+ if (status == Status.ON) {
+ return@filter false
+ } else {
+ return@filter true
+ }
+ } else {
+ if (status == Status.ON) {
+ return@filter true
+ } else {
+ return@filter false
+ }
+ }
+ } else {
+ if (status == Status.ON) {
+ return@filter inOurList
+ } else {
+ return@filter false
+ }
+ }
+ }
+ ListMode.WHITELIST -> {
+ if (systemSuspended) {
+ if (inOurList) {
+ return@filter true
+ } else {
+ if (status == Status.ON) {
+ return@filter false
+ } else {
+ return@filter true
+ }
+ }
+ } else {
+ if (status == Status.ON) {
+ return@filter !inOurList
+ } else {
+ return@filter false
+ }
+ }
+ }
+ }
+ }
+ // Now, map them and determine that whatever a package should be suspended or un-suspended.
+ .map {
+ val systemSuspended = systemSuspendedList.contains(it)
+ when (listMode) {
+ ListMode.BLACKLIST -> {
+ if (systemSuspended) {
+ // It must be off (if in our list),
+ // or need to un-suspend (whatever on or off)
+ return@map SuspendTask(it, false)
+ } else {
+ // It must be on
+ return@map SuspendTask(it, true)
+ }
+ }
+ ListMode.WHITELIST -> {
+ if (systemSuspended) {
+ // It must be un-suspended (if in our list)
+ // (or off)
+ return@map SuspendTask(it, false)
+ } else {
+ // It must be on
+ return@map SuspendTask(it, true)
+ }
+ }
+ }
+ }
+ // Collect them, we will execute later.
+ .collect(Collectors.toList())
+ // Suspend first
+ if (status == Status.ON) {
+ val suspendList = tasks.stream()
+ .filter {
+ return@filter it.suspend
+ }
+ .map {
+ return@map it.packageName
+ }
+ .collect(Collectors.toList())
+ if (suspendList.size > 0) {
+ suspend(suspendList.toTypedArray(),
+ true)
+ }
+ }
+ // Then unsuspand
+ val unsuspendList = tasks.stream()
+ .filter {
+ return@filter !it.suspend
+ }
+ .map {
+ return@map it.packageName
+ }
+ .collect(Collectors.toList())
+ if (unsuspendList.size > 0) {
+ suspend(unsuspendList.toTypedArray(),
+ false)
+ }
+ }
+
+ private fun suspend(packageNames: Array<String>, suspended: Boolean): Array<String> =
+ pmAccess.setPackagesSuspended(packageNames,
+ suspended,
+ PersistableBundle(),
+ SuspendedApp.getDefault().serializeBundle(), // We use LauncherExtras because they are easy to read
+ mContext.getString(R.string.suspended_message))
+}
+
+private data class SuspendTask(
+ val packageName: String,
+ val suspend: Boolean
+) \ No newline at end of file