From a08328403be84d85c006f801169a3feed0d956a4 Mon Sep 17 00:00:00 2001 From: YuutaW <17158086+Trumeet@users.noreply.github.com> Date: Sun, 24 Feb 2019 11:59:17 -0800 Subject: First Commit Signed-off-by: YuutaW <17158086+Trumeet@users.noreply.github.com> --- .../moe/yuuta/workmode/access/WorkModeAccessor.kt | 317 +++++++++++++++++++++ 1 file changed, 317 insertions(+) create mode 100644 app/src/main/java/moe/yuuta/workmode/access/WorkModeAccessor.kt (limited to 'app/src/main/java/moe/yuuta/workmode/access/WorkModeAccessor.kt') 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) { + 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, suspended: Boolean, + appExtras: Bundle, launcherExtras: Bundle, + dialogMessage: String): Array = + pmAccess.setPackagesSuspended( + packageNames, + suspended, + BundleUtils.toPersistableBundle(appExtras), + BundleUtils.toPersistableBundle(launcherExtras), + dialogMessage + ) + + private fun getPackagesSuspendedByWorkMode(): List = + 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, suspended: Boolean): Array = + 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 -- cgit v1.2.3