package moe.yuuta.workmode.access import android.content.Context import android.os.Bundle import android.os.Parcel import android.os.PersistableBundle import com.elvishew.xlog.Logger import com.elvishew.xlog.XLog import eu.chainfire.librootjava.RootJava import eu.chainfire.libsuperuser.Shell import moe.yuuta.workmode.BuildConfig 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.utils.ByteArraySerializer 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 mLogPath: String) { private val logger: Logger = XLog.tag("AccessorStarter").build() companion object { 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" } private fun launchRootProcess(root: Boolean, vararg args: String): MutableList { val command = RootJava.getLaunchScript(mContext, WorkModeAccessor::class.java, null, null, args, BuildConfig.APPLICATION_ID + ":accessor") return if (root) { Shell.SU.run(command) } else { Shell.SH.run(command) } } fun getSuspendedPackageAppExtras(packageName: String, root: Boolean): Bundle? { val argumentParcel: Parcel = obtainArgumentParcel() try { argumentParcel.writeString(WorkModeAccessor.ACTION_GET_APP_EXTRAS) argumentParcel.writeString(packageName) val marshalledResult = launchRootProcess(root, ByteArraySerializer.serialize(argumentParcel.marshall()))[0] val result = deserialize(ByteArraySerializer.deserialize(marshalledResult)) val bundle = result.readBundle() result.recycle() return bundle } finally { argumentParcel.recycle() } } fun getSuspendedPackageLauncherExtras(packageName: String, root: Boolean): Bundle? { val argumentParcel: Parcel = obtainArgumentParcel() try { argumentParcel.writeString(WorkModeAccessor.ACTION_GET_LAUNCHER_EXTRAS) argumentParcel.writeString(packageName) val marshalledResult = launchRootProcess(root, ByteArraySerializer.serialize(argumentParcel.marshall()))[0] val result = deserialize(ByteArraySerializer.deserialize(marshalledResult)) val bundle = result.readBundle() result.recycle() return bundle } finally { argumentParcel.recycle() } } fun isPackageSuspended(packageNames: Array, root: Boolean): Boolean { val argumentParcel: Parcel = obtainArgumentParcel() try { argumentParcel.writeString(WorkModeAccessor.ACTION_IS_SUSPENDED) argumentParcel.writeStringArray(packageNames) val marshalledResult = launchRootProcess(root, WorkModeAccessor.ACTION_IS_SUSPENDED, ByteArraySerializer.serialize(argumentParcel.marshall()))[0] val result = deserialize(ByteArraySerializer.deserialize(marshalledResult)) val isSuspended = result.readByte() == 1.toByte() result.recycle() return isSuspended } finally { argumentParcel.recycle() } } fun dump(packageName: String, root: Boolean): DumpResult { val argumentParcel: Parcel = obtainArgumentParcel() try { argumentParcel.writeString(WorkModeAccessor.ACTION_DUMP) argumentParcel.writeString(packageName) val marshalledResult = launchRootProcess(root, ByteArraySerializer.serialize(argumentParcel.marshall()))[0] val result = deserialize(ByteArraySerializer.deserialize(marshalledResult)) val data = DumpResult( result.readByte() == 1.toByte(), result.readBundle(), result.readBundle() ) result.recycle() return data } finally { argumentParcel.recycle() } } fun setPackagesSuspended(packageNames: Array, suspended: Boolean, appExtras: PersistableBundle, launcherExtras: PersistableBundle, dialogMessage: String, root: Boolean): Array { val argumentParcel: Parcel = obtainArgumentParcel() try { argumentParcel.writeString(WorkModeAccessor.ACTION_SET_SUSPENDED) argumentParcel.writeStringArray(packageNames) argumentParcel.writeByte(if (suspended) 1 else 0) argumentParcel.writeBundle(Bundle(appExtras)) argumentParcel.writeBundle(Bundle(launcherExtras)) argumentParcel.writeString(dialogMessage) val marshalledResult = launchRootProcess(root, ByteArraySerializer.serialize(argumentParcel.marshall()))[0] val result = deserialize(ByteArraySerializer.deserialize(marshalledResult)) val rs = result.createStringArray() ?: arrayOf() result.recycle() return rs } finally { argumentParcel.recycle() } } @Throws(Throwable::class) private fun deserialize(byteArray: ByteArray): Parcel { val result = Parcel.obtain() result.unmarshall(byteArray, 0, byteArray.size) // Thanks to https://github.com/jiaminghan/droidplanner-master/blob/743b5436df6311cbbbfdecd21f796e2b948cbac7/Android/src/com/o3dr/services/android/lib/util/ParcelableUtils.java#L35 result.setDataPosition(0) when (result.readByte()) { 1.toByte() -> { } 0.toByte() -> { val throwable = result.readSerializable() as Throwable? if (throwable != null) { throw throwable } throw RuntimeException("Unsuccessful result with unknown stacktrace") } // If server returns this code, which means the task is successfully executed but // it had detected that the app was cracked. // #Anti-Crack 2.toByte() -> { // The ID is used to prevent from multiple reporting. val id = result.readString() val reason = result.readString() SuspendedStorage(mContext).reportCrack(id ?: "nd", reason ?: "nr") } } return result } fun getPackagesSuspendedByWorkMode(root: Boolean): List { val argumentParcel: Parcel = obtainArgumentParcel() try { argumentParcel.writeString(WorkModeAccessor.ACTION_GET_ALL_PACKAGES_SUSPENDED_BY_WORK_MODE) val marshalledResult = launchRootProcess(root, ByteArraySerializer.serialize(argumentParcel.marshall()))[0] val result = deserialize(ByteArraySerializer.deserialize(marshalledResult)) val data = result.createStringArrayList() result.recycle() return data ?: listOf() } finally { argumentParcel.recycle() } } fun apply(suspendList: Array, listMode: ListMode, status: Status, root: Boolean) { val argumentParcel: Parcel = obtainArgumentParcel() try { argumentParcel.writeString(WorkModeAccessor.ACTION_APPLY) argumentParcel.writeStringArray(suspendList) argumentParcel.writeInt(when (listMode) { ListMode.BLACKLIST -> 1 ListMode.WHITELIST -> 2 }) argumentParcel.writeInt(when (status) { Status.ON -> 1 Status.OFF -> 2 }) val marshalledResult = launchRootProcess(root, ByteArraySerializer.serialize(argumentParcel.marshall()))[0] val result = deserialize(ByteArraySerializer.deserialize(marshalledResult)) result.recycle() } finally { argumentParcel.recycle() } } private fun obtainArgumentParcel(): Parcel { val argumentParcel: Parcel = Parcel.obtain() argumentParcel.writeString(mLogPath) // Tell the trigger times and times to the server, it will disable the app automatically // #Anti-Crack val sp = SuspendedStorage(mContext).getStorage() val keys = sp.all.keys.stream() .filter { return@filter it.startsWith("c_") } .collect(Collectors.toList()) val map = mutableMapOf() for (key in keys) { try { val times = sp.getInt(key, -1) map[key] = times } catch (e: Throwable) {} } argumentParcel.writeMap(map) return argumentParcel } }