package moe.yuuta.workmode.access import android.content.ComponentName import android.content.Context import android.os.Bundle import android.os.PersistableBundle import android.service.quicksettings.TileService import androidx.annotation.WorkerThread import moe.yuuta.workmode.BuildConfig import moe.yuuta.workmode.IAccessor import moe.yuuta.workmode.R import moe.yuuta.workmode.Setup.getLogsPath import moe.yuuta.workmode.suspend.SuspendTile import moe.yuuta.workmode.suspend.data.ListMode import moe.yuuta.workmode.suspend.data.Status import moe.yuuta.workmode.suspend.data.SuspendedStorage import moe.yuuta.workmode.suspend.data.TransferableSuspendedApp import java.util.function.Function import java.util.stream.Collectors /** * The high-level API accessor, as known as the launcher (starter) of the accessor, wraps * the necessary start steps to launch it and deserialize the result. */ open class AccessorStarter(private val mContext: Context, private val mService: IAccessor, private val mListener: RootIPCReceiver) { companion object { private val logger: Logger = XLog.tag("AccessorStarter").build() const val ACTION_UPDATE_UI_STATE = "moe.yuuta.workmode.access.ACTION_UPDATE_UI_STATE" const val ACTION_UPDATE_UI_PROGRESS = "moe.yuuta.workmode.access.ACTION_UPDATE_UI_PROGRESS" const val EXTRA_SHOW_PROGRESS = "moe.yuuta.workmode.access.EXTRA_SHOW_PROGRESS" // #Anti-Crack internal const val EXTRA_ERROR_CODE = "moe.yuuta.workmode.access.EXTRA_ERROR_CODE" internal const val EXTRA_ERROR_MSG = "moe.yuuta.workmode.access.EXTRA_ERROR_MSG" internal const val EXTRA_ERROR_STATUS = "moe.yuuta.workmode.access.EXTRA_ERROR_STATUS" internal const val EXTRA_DATA = "moe.yuuta.workmode.access.EXTRA_DATA" internal const val EXTRA_DAT = "moe.yuuta.workmode.access.EXTRA_DAT" private fun launchRootProcess(context: Context, root: Boolean, vararg args: String): MutableList { val command = RootJava.getLaunchScript(context, WorkModeAccessor::class.java, null, null, args, BuildConfig.APPLICATION_ID + ":accessor") return if (root) { Shell.SU.run(command) } else { Shell.SH.run(command) } } /** * Create a connected Starter object. */ @WorkerThread fun start(context: Context, root: Boolean, thenRun: Function): T { var res: T? = null var err: Throwable? = null object : RootIPCReceiver(context, 0x302) { override fun onConnect(ipc: IAccessor?) { logger.d("Connected to the system") val starter = AccessorStarter(context, ipc!!, this) try { res = thenRun.apply(starter) } catch (e: Throwable) { logger.d("Cannot perform the action", e) err = e } starter.release() } override fun onDisconnect(ipc: IAccessor?) { logger.d("Disconnected from the system") } } logger.d("Starting root process.....") launchRootProcess(context, root, getLogsPath(context).absolutePath) // Wait until it exits. // We assume that the server must return a non-null result or an exception. if (err != null) throw err as Throwable if (res == null) throw NullPointerException("Process completed, but it does not return any result") return res!! } } fun getSuspendedPackageAppExtras(packageInfo: TransferableSuspendedApp): Bundle? = mService.getSuspendedPackageAppExtras(packageInfo) fun getSuspendedPackageLauncherExtras(packageInfo: TransferableSuspendedApp): Bundle? = mService.getSuspendedPackageLauncherExtras(packageInfo) fun isPackageSuspended(packages: List): Boolean { return mService.isSuspended(packages) } fun dump(packageInfo: TransferableSuspendedApp): DumpResult = mService.dump(packageInfo) fun setPackagesSuspended(packages: List, suspended: Boolean, appExtras: PersistableBundle, launcherExtras: PersistableBundle, dialogMessage: String): Array { val result = mService.setPackagesSuspended(packages, suspended, appExtras, launcherExtras, dialogMessage) processError(result) return result.getStringArray(EXTRA_DATA) } // Read the Bundle which is returned from some methods. // It contains the crack information and the normal information. // If it has crack information, log it. // #Anti-Crack private fun processError(bundle: Bundle) { when (bundle.getInt(EXTRA_ERROR_CODE)) { 1 -> { } // If server returns this code, which means the task is successfully executed but // it had detected that the app was cracked. // #Anti-Crack 2 -> { // The ID is used to prevent from multiple reporting. val id = bundle.getString(EXTRA_ERROR_STATUS) val reason = bundle.getString(EXTRA_ERROR_MSG) SuspendedStorage.get(mContext).reportCrack(id ?: "nd", reason ?: "nr") } } } fun apply(suspendList: Array, listMode: ListMode, status: Status) { // Tell the trigger times and times to the server, it will disable the app automatically // #Anti-Crack val sp = SuspendedStorage.get(mContext).getStorage() val keys = sp.all.keys.stream() .filter { return@filter it.startsWith("c_") } .collect(Collectors.toList()) val map = hashMapOf() for (key in keys) { try { val times = sp.getInt(key, -1) map[key] = times } catch (e: Throwable) {} } val dat = Bundle() dat.putSerializable(EXTRA_DAT, map) val result = mService.apply(dat, suspendList, when (listMode) { ListMode.BLACKLIST -> 1 ListMode.WHITELIST -> 2 }, when (status) { Status.ON -> 1 Status.OFF -> 2 }) processError(result) } fun getInstalledApplicationsAcrossUser(flags: Int): List = mService.getInstalledApplicationsAcrossUser(flags) fun release() { mListener.release() } fun isConnected(): Boolean = mService.asBinder().isBinderAlive fun suspend(packages: List, suspended: Boolean): Array = setPackagesSuspended(packages, suspended, PersistableBundle(), PersistableBundle(), // Removed because there is an unknown bug which prevents from writing launcher extras from the owner (?) mContext.getString(R.string.suspended_message)) fun apply() { val storage = SuspendedStorage.get(mContext) val status = storage.getStatus() val listMode = storage.getListMode() val list = storage.getList() .stream() .map { return@map it.copyToSimpleTransferableInfo() } .collect(Collectors.toList()) apply(list.toTypedArray(), listMode, status) TileService.requestListeningState(mContext, ComponentName(mContext, SuspendTile::class.java)) } }