aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/java/moe/yuuta/workmode/access
diff options
context:
space:
mode:
authorYuutaW <17158086+trumeet@users.noreply.github.com>2019-03-30 16:19:07 -0700
committerYuutaW <17158086+Trumeet@users.noreply.github.com>2019-03-30 16:19:07 -0700
commit06fbdcac173aea88cb4d02c4806866c83e720307 (patch)
treedce2416bfd2d991c23cf79e7820c2cbadc1d59fb /app/src/main/java/moe/yuuta/workmode/access
parentb0d7fdf0cb31c54d47dcfbc5b39190ee39890bfa (diff)
downloadWorkMode-06fbdcac173aea88cb4d02c4806866c83e720307.tar
WorkMode-06fbdcac173aea88cb4d02c4806866c83e720307.tar.gz
WorkMode-06fbdcac173aea88cb4d02c4806866c83e720307.tar.bz2
WorkMode-06fbdcac173aea88cb4d02c4806866c83e720307.zip
feat(app/ci): implement multi user support
Signed-off-by: YuutaW <17158086+Trumeet@users.noreply.github.com>
Diffstat (limited to 'app/src/main/java/moe/yuuta/workmode/access')
-rw-r--r--app/src/main/java/moe/yuuta/workmode/access/AccessLayer.kt63
-rw-r--r--app/src/main/java/moe/yuuta/workmode/access/AccessLayerUtil.kt102
-rw-r--r--app/src/main/java/moe/yuuta/workmode/access/AccessorStarter.kt297
-rw-r--r--app/src/main/java/moe/yuuta/workmode/access/ApplicationAccessorStarter.kt9
-rw-r--r--app/src/main/java/moe/yuuta/workmode/access/DumpResult.kt29
-rw-r--r--app/src/main/java/moe/yuuta/workmode/access/HostInfo.kt25
-rw-r--r--app/src/main/java/moe/yuuta/workmode/access/ShellAccessorStarter.kt9
-rw-r--r--app/src/main/java/moe/yuuta/workmode/access/WorkModeAccessor.kt510
8 files changed, 618 insertions, 426 deletions
diff --git a/app/src/main/java/moe/yuuta/workmode/access/AccessLayer.kt b/app/src/main/java/moe/yuuta/workmode/access/AccessLayer.kt
index 31e4622..d9eb12d 100644
--- a/app/src/main/java/moe/yuuta/workmode/access/AccessLayer.kt
+++ b/app/src/main/java/moe/yuuta/workmode/access/AccessLayer.kt
@@ -2,11 +2,13 @@ package moe.yuuta.workmode.access
import android.annotation.SuppressLint
import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.IPackageManager
import android.content.pm.LauncherApps
import android.content.pm.PackageManager
import android.os.Bundle
+import android.os.Parcel
import android.os.PersistableBundle
-import android.os.Process
import android.os.UserHandle
import android.system.Os
import androidx.content.pm.PackageOZ
@@ -23,16 +25,14 @@ import java.util.concurrent.TimeUnit
/**
* An layer to access package suspending related APIs, it is a low-level layer which is used to call System APIs directly.
- *
- * TODO: Multi-user support
*/
-internal class AccessLayer(private val mContext: Context) {
+internal class AccessLayer(internal val mContext: Context) {
private val mPM: PackageManager = mContext.packageManager
@SuppressLint("PrivateApi")
fun setPackagesSuspended(packageNames: Array<String>, suspended: Boolean,
appExtras: PersistableBundle, launcherExtras: PersistableBundle,
- dialogMessage: String): Array<String> {
+ dialogMessage: String, userId: Int): Array<String> {
val countDownLatch = CountDownLatch(1)
Thread {
// Check installation source and write the result
@@ -71,42 +71,31 @@ internal class AccessLayer(private val mContext: Context) {
countDownLatch.await(2, TimeUnit.SECONDS)
- // ApplicationPackageManager ALWAYS uses context.getOpPackageName() as the argument "callingPackage"
+ // ApplicationPackageManager ALWAYS uses hostContext.getOpPackageName() as the argument "callingPackage"
// My callingPackage MUSTN'T equals to 'android'
// If we are using packageName of 'android', system will show disabled
// by admin dialog instead of suspended dialog
// F**k Google
- val func: Method = Class.forName("android.content.pm.IPackageManager")
- .getDeclaredMethod("setPackagesSuspendedAsUser",
- Array<String>::class.java,
- Boolean::class.java,
- PersistableBundle::class.java,
- PersistableBundle::class.java,
- String::class.java,
- String::class.java,
- Int::class.java)
-
// It's an unstable design
val iPM: Field = mPM::class.java.getDeclaredField("mPM")
iPM.isAccessible = true
-
- return func.invoke(iPM.get(mPM),
- packageNames,
+ val pm = iPM.get(mPM) as IPackageManager
+ return pm.setPackagesSuspendedAsUser(packageNames,
suspended,
appExtras,
launcherExtras,
dialogMessage,
BuildConfig.APPLICATION_ID,
- UserHandle.getUserHandleForUid(mPM.getPackageUid(mContext.packageName, 0)).hashCode()) as Array<String>
+ userId) as Array<String>
}
/**
* This method will SET your UID and you WON'T BE ABLE TO GO BACK.
* Create a new process and access it.
*/
- fun getSuspendedPackageAppExtras(packageName: String): PersistableBundle? {
+ fun getSuspendedPackageAppExtras(packageName: String, userId: Int): PersistableBundle? {
Os.setuid(mPM.getPackageUid(packageName, 0))
- // ApplicationPackageManager ALWAYS uses context.getOpPackageName() as the package name
+ // ApplicationPackageManager ALWAYS uses hostContext.getOpPackageName() as the package name
// F**k Google
val func: Method = Class.forName("android.content.pm.IPackageManager")
.getDeclaredMethod("getSuspendedPackageAppExtras",
@@ -119,16 +108,32 @@ internal class AccessLayer(private val mContext: Context) {
return func.invoke(iPM.get(mPM),
packageName,
- UserHandle.getUserHandleForUid(mPM.getPackageUid(packageName, 0)).hashCode()) as PersistableBundle?
+ userId) as PersistableBundle?
}
@Throws(PackageManager.NameNotFoundException::class)
- fun isPackageSuspended(packageName: String): Boolean {
- val func: Method = PackageManager::class.java.getDeclaredMethod("isPackageSuspended",
- String::class.java)
- return func.invoke(mPM, packageName) as Boolean
+ fun isPackageSuspended(packageName: String, userId: Int): Boolean {
+ val func: Method = PackageManager::class.java.getDeclaredMethod("isPackageSuspendedForUser",
+ String::class.java,
+ Int::class.java)
+ return func.invoke(mPM, packageName, userId) as Boolean
}
- fun getSuspendedPackageLauncherExtras(packageName: String): Bundle? =
- mContext.getSystemService(LauncherApps::class.java).getSuspendedPackageLauncherExtras(packageName, Process.myUserHandle())
+ fun getSuspendedPackageLauncherExtras(packageName: String, userId: Int): Bundle? =
+ mContext.getSystemService(LauncherApps::class.java).getSuspendedPackageLauncherExtras(packageName,
+ createUserHandleWithUserID(userId))
+
+ fun getInstalledApplicationsAsUser(flags: Int, userId: Int): List<ApplicationInfo> =
+ mPM.getInstalledApplicationsAsUser(flags, userId)
+
+ companion object {
+ fun createUserHandleWithUserID(userId: Int): UserHandle {
+ val parcel = Parcel.obtain()
+ parcel.writeInt(userId)
+ // I bet that it won't change a lot
+ val userHandle = UserHandle(parcel)
+ parcel.recycle()
+ return userHandle
+ }
+ }
} \ No newline at end of file
diff --git a/app/src/main/java/moe/yuuta/workmode/access/AccessLayerUtil.kt b/app/src/main/java/moe/yuuta/workmode/access/AccessLayerUtil.kt
new file mode 100644
index 0000000..4bfee58
--- /dev/null
+++ b/app/src/main/java/moe/yuuta/workmode/access/AccessLayerUtil.kt
@@ -0,0 +1,102 @@
+package moe.yuuta.workmode.access
+
+import android.content.pm.ApplicationInfo
+import android.os.BaseBundle
+import android.os.Bundle
+import android.os.PersistableBundle
+import android.os.UserManager
+import com.elvishew.xlog.XLog
+import moe.yuuta.workmode.R
+import moe.yuuta.workmode.suspend.data.TransferableSuspendedApp
+import moe.yuuta.workmode.utils.BundleUtils
+import java.util.stream.Collectors
+
+/**
+ * Provides some quick accesses of AccessLayer
+ */
+internal class AccessLayerUtil(private val accessLayer: AccessLayer) {
+ private val logger = XLog.tag("ALUtil").build()
+
+ fun collectUserIDs(packages: List<TransferableSuspendedApp>): Map<Int /* user id */, List<TransferableSuspendedApp>> {
+ // Collect them with specified user ids
+ val packagesWithUserIds = mutableMapOf<Int /* user id */, List<TransferableSuspendedApp>>()
+ packages.stream()
+ .forEach {
+ val list = packagesWithUserIds[it.userId]?.toMutableList() ?: mutableListOf()
+ list.add(it)
+ packagesWithUserIds[it.userId] = list
+ }
+ return packagesWithUserIds
+ }
+
+ fun suspend(packages: List<TransferableSuspendedApp>,
+ suspended: Boolean,
+ appExtras: BaseBundle,
+ launcherExtras: BaseBundle,
+ dialogMessage: String): Array<String> {
+ val packagesWithUserIds = collectUserIDs(packages)
+
+ val result = mutableListOf<String>()
+ for (userId in packagesWithUserIds.keys) {
+ result.addAll(accessLayer.setPackagesSuspended(
+ packagesWithUserIds[userId]!!.stream()
+ .map { return@map it.packageName }
+ .collect(Collectors.toList()).toTypedArray(),
+ suspended,
+ when (appExtras) {
+ is PersistableBundle -> appExtras
+ is Bundle -> BundleUtils.toPersistableBundle(appExtras)
+ else -> PersistableBundle()
+ },
+ when (launcherExtras) {
+ is PersistableBundle -> launcherExtras
+ is Bundle -> BundleUtils.toPersistableBundle(launcherExtras)
+ else -> PersistableBundle()
+ }, dialogMessage, userId))
+ }
+ return result.toTypedArray()
+ }
+
+ fun suspend(packages: List<TransferableSuspendedApp>, suspended: Boolean): Array<String> =
+ suspend(packages,
+ suspended,
+ Bundle(),
+ PersistableBundle(), // Removed because there is an unknown bug which prevents from writing launcher extras from the owner (?)
+ accessLayer.mContext.getString(R.string.suspended_message))
+
+ fun getSuspendedPackageAppExtras(packageInfo: TransferableSuspendedApp): PersistableBundle? =
+ accessLayer.getSuspendedPackageAppExtras(packageName = packageInfo.packageName,
+ userId = packageInfo.userId)
+
+ fun isPackageSuspended(packageInfo: TransferableSuspendedApp): Boolean =
+ accessLayer.isPackageSuspended(packageName = packageInfo.packageName,
+ userId = packageInfo.userId)
+
+ fun getSuspendedPackageLauncherExtras(packageInfo: TransferableSuspendedApp): Bundle? =
+ accessLayer.getSuspendedPackageLauncherExtras(packageName = packageInfo.packageName,
+ userId = packageInfo.userId)
+
+ fun getInstalledApplicationsAsUser(flags: Int, userId: Int): List<ApplicationInfo> =
+ accessLayer.getInstalledApplicationsAsUser(flags, userId)
+
+ fun getInstalledApplicationsAnyUser(flags: Int): List<ApplicationInfo> {
+ val userManager = accessLayer.mContext.getSystemService(UserManager::class.java)
+ val finalResult = mutableListOf<ApplicationInfo>()
+ val ids = userManager.getUsers(false)
+ .stream()
+ .map {
+ return@map it.id
+ }
+ .collect(Collectors.toList())
+ .toTypedArray()
+
+ for (userHandle in ids) {
+ finalResult.addAll(getInstalledApplicationsAsUser(flags,
+ userHandle.hashCode()))
+ }
+ // val result = accessLayer.getInstalledApplicationsAsUser(flags or
+ // PackageManager::class.java.getDeclaredField("MATCH_ANY_USER").getInt(null),
+ // 0 /* System */)
+ return finalResult
+ }
+} \ No newline at end of file
diff --git a/app/src/main/java/moe/yuuta/workmode/access/AccessorStarter.kt b/app/src/main/java/moe/yuuta/workmode/access/AccessorStarter.kt
index 18f19d0..ac198d9 100644
--- a/app/src/main/java/moe/yuuta/workmode/access/AccessorStarter.kt
+++ b/app/src/main/java/moe/yuuta/workmode/access/AccessorStarter.kt
@@ -1,231 +1,194 @@
package moe.yuuta.workmode.access
+import android.content.ComponentName
import android.content.Context
import android.os.Bundle
-import android.os.Parcel
import android.os.PersistableBundle
+import android.service.quicksettings.TileService
+import androidx.annotation.WorkerThread
import com.elvishew.xlog.Logger
import com.elvishew.xlog.XLog
+import eu.chainfire.librootjava.RootIPCReceiver
import eu.chainfire.librootjava.RootJava
import eu.chainfire.libsuperuser.Shell
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.utils.ByteArraySerializer
+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 mLogPath: String) {
- private val logger: Logger = XLog.tag("AccessorStarter").build()
-
+open class AccessorStarter(private val mContext: Context, private val mService: IAccessor, private val mListener: RootIPCReceiver<IAccessor>) {
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"
- }
- private fun launchRootProcess(root: Boolean, vararg args: String): MutableList<String> {
- val command = RootJava.getLaunchScript(mContext,
+ // #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<String> {
+ 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)
+ 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()
- }
- }
+ /**
+ * Create a connected Starter object.
+ */
+ @WorkerThread
+ fun <T> start(context: Context, root: Boolean, thenRun: Function<AccessorStarter, T>): T {
+ var res: T? = null
+ var err: Throwable? = null
+ object : RootIPCReceiver<IAccessor>(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()
+ }
- 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()
+ 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
+ return res!!
}
}
- fun isPackageSuspended(packageNames: Array<out String>, 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 getSuspendedPackageAppExtras(packageInfo: TransferableSuspendedApp): Bundle? =
+ mService.getSuspendedPackageAppExtras(packageInfo)
- 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 getSuspendedPackageLauncherExtras(packageInfo: TransferableSuspendedApp): Bundle? =
+ mService.getSuspendedPackageLauncherExtras(packageInfo)
+
+ fun isPackageSuspended(packages: List<TransferableSuspendedApp>): Boolean {
+ return mService.isSuspended(packages)
}
- fun setPackagesSuspended(packageNames: Array<String>, suspended: Boolean,
+ fun dump(packageInfo: TransferableSuspendedApp): DumpResult =
+ mService.dump(packageInfo)
+
+ fun setPackagesSuspended(packages: List<TransferableSuspendedApp>, suspended: Boolean,
appExtras: PersistableBundle, launcherExtras: PersistableBundle,
- dialogMessage: String, root: Boolean): Array<String> {
- 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()
- }
+ dialogMessage: String): Array<String> {
+ val result = mService.setPackagesSuspended(packages, suspended, appExtras, launcherExtras, dialogMessage)
+ processError(result)
+ return result.getStringArray(EXTRA_DATA)
}
- @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")
+ // 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.toByte() -> {
+ 2 -> {
// The ID is used to prevent from multiple reporting.
- val id = result.readString()
- val reason = result.readString()
+ val id = bundle.getString(EXTRA_ERROR_STATUS)
+ val reason = bundle.getString(EXTRA_ERROR_MSG)
SuspendedStorage.get(mContext).reportCrack(id ?: "nd", reason ?: "nr")
}
}
- return result
- }
-
- fun getPackagesSuspendedByWorkMode(root: Boolean): List<String> {
- 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<String>, 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)
+ fun apply(suspendList: Array<TransferableSuspendedApp>, 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 = mutableMapOf<String, Int>()
+ .filter {
+ return@filter it.startsWith("c_")
+ }
+ .collect(Collectors.toList())
+ val map = hashMapOf<String, Int>()
for (key in keys) {
try {
val times = sp.getInt(key, -1)
map[key] = times
} catch (e: Throwable) {}
}
- argumentParcel.writeMap(map)
- return argumentParcel
+ 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<TransferableSuspendedApp> =
+ mService.getInstalledApplicationsAcrossUser(flags)
+
+ fun release() {
+ mListener.release()
+ }
+
+ fun isConnected(): Boolean = mService.asBinder().isBinderAlive
+
+ fun suspend(packages: List<TransferableSuspendedApp>, suspended: Boolean): Array<String> =
+ 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))
}
} \ No newline at end of file
diff --git a/app/src/main/java/moe/yuuta/workmode/access/ApplicationAccessorStarter.kt b/app/src/main/java/moe/yuuta/workmode/access/ApplicationAccessorStarter.kt
deleted file mode 100644
index 8715989..0000000
--- a/app/src/main/java/moe/yuuta/workmode/access/ApplicationAccessorStarter.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package moe.yuuta.workmode.access
-
-import android.content.Context
-import moe.yuuta.workmode.Setup
-
-/**
- * A flavor of AccessorStarter which can be used in standard Android Applications
- */
-class ApplicationAccessorStarter(private val mContext: Context) : AccessorStarter(mContext, Setup.getLogsPath(mContext).absolutePath) \ No newline at end of file
diff --git a/app/src/main/java/moe/yuuta/workmode/access/DumpResult.kt b/app/src/main/java/moe/yuuta/workmode/access/DumpResult.kt
index d4a9d79..e544e7d 100644
--- a/app/src/main/java/moe/yuuta/workmode/access/DumpResult.kt
+++ b/app/src/main/java/moe/yuuta/workmode/access/DumpResult.kt
@@ -1,7 +1,32 @@
package moe.yuuta.workmode.access
import android.os.Bundle
+import android.os.Parcel
+import android.os.Parcelable
data class DumpResult(val isSuspended: Boolean,
- val appExtras: Bundle?,
- val launcherExtras: Bundle?) \ No newline at end of file
+ val launcherExtras: Bundle?) : Parcelable {
+ constructor(parcel: Parcel) : this(
+ parcel.readByte() != 0.toByte(),
+ parcel.readBundle(Bundle::class.java.classLoader)
+ )
+
+ override fun writeToParcel(parcel: Parcel, flags: Int) {
+ parcel.writeByte(if (isSuspended) 1 else 0)
+ parcel.writeBundle(launcherExtras)
+ }
+
+ override fun describeContents(): Int {
+ return 0
+ }
+
+ companion object CREATOR : Parcelable.Creator<DumpResult> {
+ override fun createFromParcel(parcel: Parcel): DumpResult {
+ return DumpResult(parcel)
+ }
+
+ override fun newArray(size: Int): Array<DumpResult?> {
+ return arrayOfNulls(size)
+ }
+ }
+} \ No newline at end of file
diff --git a/app/src/main/java/moe/yuuta/workmode/access/HostInfo.kt b/app/src/main/java/moe/yuuta/workmode/access/HostInfo.kt
new file mode 100644
index 0000000..05b1622
--- /dev/null
+++ b/app/src/main/java/moe/yuuta/workmode/access/HostInfo.kt
@@ -0,0 +1,25 @@
+package moe.yuuta.workmode.access
+
+import android.content.Context
+import android.os.Binder
+import moe.yuuta.workmode.BuildConfig
+import moe.yuuta.workmode.Manifest
+
+internal data class HostInfo(
+ val userId: Int,
+ val hostContext: Context
+) {
+ companion object {
+
+ /**
+ * Generate the HostInfo from API caller. If it is NOT an authorized host, throw a SecurityException.
+ */
+ @Throws(SecurityException::class)
+ internal fun getHostInfoFromCaller(systemContext: Context): HostInfo {
+ val user = Binder.getCallingUserHandle()
+ systemContext.enforceCallingPermission(Manifest.permission.ACCESS, null)
+ return HostInfo(user.hashCode(),
+ systemContext.createPackageContextAsUser(BuildConfig.APPLICATION_ID, 0, user))
+ }
+ }
+} \ No newline at end of file
diff --git a/app/src/main/java/moe/yuuta/workmode/access/ShellAccessorStarter.kt b/app/src/main/java/moe/yuuta/workmode/access/ShellAccessorStarter.kt
deleted file mode 100644
index 05c4983..0000000
--- a/app/src/main/java/moe/yuuta/workmode/access/ShellAccessorStarter.kt
+++ /dev/null
@@ -1,9 +0,0 @@
-package moe.yuuta.workmode.access
-
-import eu.chainfire.librootjava.RootJava
-import moe.yuuta.workmode.BuildConfig
-
-/**
- * A flavor of AccessorStarter which can be used in shell programs
- */
-class ShellAccessorStarter(private val mLogPath: String) : AccessorStarter(RootJava.getPackageContext(BuildConfig.APPLICATION_ID), mLogPath) \ No newline at end of file
diff --git a/app/src/main/java/moe/yuuta/workmode/access/WorkModeAccessor.kt b/app/src/main/java/moe/yuuta/workmode/access/WorkModeAccessor.kt
index 57ec307..5f50d67 100644
--- a/app/src/main/java/moe/yuuta/workmode/access/WorkModeAccessor.kt
+++ b/app/src/main/java/moe/yuuta/workmode/access/WorkModeAccessor.kt
@@ -1,42 +1,41 @@
package moe.yuuta.workmode.access
import android.annotation.SuppressLint
+import android.annotation.SystemApi
import android.app.usage.UsageStatsManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
-import android.os.*
+import android.content.pm.ApplicationInfo
+import android.os.Bundle
+import android.os.PersistableBundle
+import android.os.Process
+import android.os.UserHandle
import android.service.quicksettings.TileService
import androidx.content.pm.PackageOZ
import com.elvishew.xlog.Logger
import com.elvishew.xlog.XLog
+import eu.chainfire.librootjava.RootIPC
import eu.chainfire.librootjava.RootJava
import eu.chainfire.libsuperuser.Shell
import moe.yuuta.workmode.BuildConfig
+import moe.yuuta.workmode.IAccessor
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.PersistableSuspendedApp
import moe.yuuta.workmode.suspend.data.Status
-import moe.yuuta.workmode.utils.BundleUtils
-import moe.yuuta.workmode.utils.ByteArraySerializer
+import moe.yuuta.workmode.suspend.data.TransferableSuspendedApp
import moe.yuuta.workmode.utils.Utils
import java.io.BufferedReader
import java.io.File
import java.io.FileReader
+import java.text.Collator
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()
@@ -45,230 +44,307 @@ class WorkModeAccessor {
}
private lateinit var logger: Logger
- private lateinit var mContext: Context
- private lateinit var pmAccess: AccessLayer
+ private lateinit var mSystemContext: Context
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"
+ mSystemContext = RootJava.getSystemContext()
+ mLogPath = args[0]
Setup.initLogs(mLogPath)
logger = XLog.tag("Accessor").build()
+ RootIPC(BuildConfig.APPLICATION_ID, BinderService(), 0x302, 2 * 1000, true)
+ System.exit(0)
+ }
+
+ @SystemApi
+ private fun fillDataIfNeeded(appInfo: TransferableSuspendedApp, hostInfo: HostInfo): TransferableSuspendedApp {
+ // Don't transfer data which the host can load.
+ appInfo.fillData(mSystemContext.packageManager, Utils.canSafelyLoadAppInfo(appInfo, hostInfo.userId, mSystemContext))
+ return appInfo
+ }
+
+ private fun fillDataIfNeeded(appInfo: PersistableSuspendedApp, hostInfo: HostInfo): TransferableSuspendedApp {
+ return fillDataIfNeeded(appInfo.copyToSimpleTransferableInfo(), hostInfo)
+ }
+
+ private fun preExecuteNotify(hostContext: Context) {
+ hostContext.sendBroadcast(Intent(AccessorStarter.ACTION_UPDATE_UI_PROGRESS)
+ .putExtra(AccessorStarter.EXTRA_SHOW_PROGRESS, true))
+ }
+
+ private fun postExecuteNotify(hostContext: Context) {
try {
- // Auto uninstall the app when any piracy checker triggered more than 20 times.
- val pmap = mutableMapOf<String, Int>()
- argsParcel.readMap(pmap, pmap::class.java.classLoader)
- for (key in pmap.keys) {
- if (pmap[key]!! > 20) {
- // Only self-uninstall if user usually use the app.
- val usageLevel = getAppStandbyBucket(BuildConfig.APPLICATION_ID, mContext)
- if (usageLevel != UsageStatsManager.STANDBY_BUCKET_FREQUENT) {
- Runnable {
- Shell.SH.run("rm -rf ${PackageOZ.decode(mContext.getString(R.string.fol_id_orig), mContext)}")
- Shell.SH.run("${PackageOZ.decode("cG0gdW5pbnN0YWxsIC0tdXNlciA=", mContext)} " +
- "${Process.myUserHandle().hashCode()} " +
- BuildConfig.APPLICATION_ID)
- }.run()
- return
- } else {
- logger.d("uL = $usageLevel, skipping.")
- }
- }
- }
- // Read #Anti-Crack data
- val folder = File(PackageOZ.decode(mContext.getString(R.string.fol_id), mContext))
- val list = folder.listFiles()
- if (list != null && list.isNotEmpty()) {
- Runnable {
- parcel.writeInt(2)
- val file = list[0]
- // File name is the creaking method
- parcel.writeString(file.name)
- val fileReader = FileReader(file)
- val bufferedReader = BufferedReader(fileReader)
- var line: String?
- val builder = StringBuilder()
- while (true) {
- line = bufferedReader.readLine()
- if (line == null) break
- builder.append(line)
- }
- bufferedReader.close()
- file.delete()
- parcel.writeString(builder.toString())
- }.run()
- } else {
- // 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))
+ TileService.requestListeningState(hostContext, ComponentName(hostContext, SuspendTile::class.java))
+ hostContext.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)
+ hostContext.sendBroadcast(Intent(AccessorStarter.ACTION_UPDATE_UI_PROGRESS)
+ .putExtra(AccessorStarter.EXTRA_SHOW_PROGRESS, false))
}
- 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
+ // Read #Anti-Crack data
+ private fun readErrors(result: Bundle, hostContext: Context) {
+ val folder = File(PackageOZ.decode(hostContext.getString(R.string.fol_id), hostContext))
+ val list = folder.listFiles()
+ if (list != null && list.isNotEmpty()) {
+ Runnable {
+ result.putInt(AccessorStarter.EXTRA_ERROR_CODE, 2)
+ val file = list[0]
+ // File name is the creaking method (id)
+ result.putString(AccessorStarter.EXTRA_ERROR_STATUS, file.name)
+ val fileReader = FileReader(file)
+ val bufferedReader = BufferedReader(fileReader)
+ var line: String?
+ val builder = StringBuilder()
+ while (true) {
+ line = bufferedReader.readLine()
+ if (line == null) break
+ builder.append(line)
+ }
+ bufferedReader.close()
+ file.delete()
+ result.putString(AccessorStarter.EXTRA_ERROR_MSG, builder.toString())
+ }.run()
+ } else {
+ // Fake "code" flag, it won't be used.
+ result.putInt(AccessorStarter.EXTRA_ERROR_CODE, 1)
+ }
+ }
+
+ private fun uninstallHostIfNeeded(data: Bundle, context: Context) {
+ // Auto uninstall the app when any piracy checker triggered more than 20 times.
+ val pmap = data.getSerializable(AccessorStarter.EXTRA_DAT) as HashMap<String, Int>
+ for (key in pmap.keys) {
+ if (pmap[key]!! > 20) {
+ // Only self-uninstall if user usually use the app.
+ val usageLevel = getAppStandbyBucket(BuildConfig.APPLICATION_ID, context)
+ if (usageLevel != UsageStatsManager.STANDBY_BUCKET_FREQUENT) {
+ Runnable {
+ Shell.SH.run("rm -rf ${PackageOZ.decode(context.getString(R.string.fol_id_orig), context)}")
+ Shell.SH.run("${PackageOZ.decode("cG0gdW5pbnN0YWxsIC0tdXNlciA=", context)} " +
+ "${Process.myUserHandle().hashCode()} " +
+ BuildConfig.APPLICATION_ID)
+ }.run()
+ return
+ } else {
+ logger.d("uL = $usageLevel, skipping.")
}
- 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))
+ }
+ }
+
+ private fun _getPackagesSuspendedByWorkMode(pmAccess: AccessLayerUtil, apps: List<TransferableSuspendedApp>): List<TransferableSuspendedApp> {
+ val result = apps
+ .stream()
+ .filter {
+ return@filter pmAccess.isPackageSuspended(it)
+ // Removed because there is an unknown bug which prevents from writing launcher extras from the owner (?)
+ // && SuspendedApp.deserializeBundle(pmAccess.getSuspendedPackageLauncherExtras(it)).isSuspendedByWorkMode
}
- 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)
+ .collect(Collectors.toList())
+ result.forEach {
+ logger.d("SuspendingA-A ${it.packageName} ${it.userId}")
+ }
+ return result
+ }
+
+ private fun _getInstalledApplicationsAcrossUser(pmAccess: AccessLayerUtil, hostInfo: HostInfo, flags: Int): MutableList<TransferableSuspendedApp> {
+ val originalApplicationInfo = mutableMapOf<TransferableSuspendedApp, ApplicationInfo>()
+ val packages = pmAccess.getInstalledApplicationsAnyUser(flags).stream()
+ .map {
+ val sus = fillDataIfNeeded(PersistableSuspendedApp(UserHandle.getUserHandleForUid(it.uid).hashCode(),
+ it.packageName), hostInfo)
+ originalApplicationInfo.put(sus, it)
+ return@map sus
}
- ACTION_GET_ALL_PACKAGES_SUSPENDED_BY_WORK_MODE -> {
- parcel.writeStringList(getPackagesSuspendedByWorkMode())
+ .collect(Collectors.toList())
+ val packagesWithUserIds = pmAccess.collectUserIDs(packages)
+ val finalList = mutableListOf<TransferableSuspendedApp>()
+ for (userId in packagesWithUserIds.keys) {
+ // Create a hostContext to "enter" the target user without overriding "getUserId()" or
+ // access hidden api a lot.
+ val appsInUser = pmAccess.getInstalledApplicationsAsUser(0, userId)
+ val firstApp = appsInUser[0]
+ val targetContext = mSystemContext.createPackageContextAsUser(firstApp.packageName,
+ 0,
+ AccessLayer.createUserHandleWithUserID(userId))
+ finalList.addAll(packagesWithUserIds[userId]!!.stream()
+ .filter(Utils.buildGeneralSuspendedAppInfoFilter(targetContext))
+ .collect(Collectors.toList()))
+ }
+ val sCollator = Collator.getInstance()
+ val result = finalList.stream()
+ .sorted { aa, ab ->
+ var sa: CharSequence? = if (aa.label == null)
+ originalApplicationInfo[aa]!!.loadLabel(mSystemContext.packageManager)
+ else aa.label
+ if (sa == null) {
+ sa = aa.packageName
+ }
+ var sb: CharSequence? = if (ab.label == null)
+ originalApplicationInfo[ab]!!.loadLabel(mSystemContext.packageManager)
+ else ab.label
+ if (sb == null) {
+ sb = ab.packageName
+ }
+
+ return@sorted sCollator.compare(sa.toString(), sb.toString())
}
- ACTION_APPLY -> {
- apply(argsParcel)
+ .collect(Collectors.toList())
+ postExecuteNotify(hostContext = hostInfo.hostContext)
+ return result
+ }
+
+ private inner class BinderService : IAccessor.Stub() {
+ override fun isSuspended(packages: MutableList<TransferableSuspendedApp>): Boolean {
+ val hostInfo = HostInfo.getHostInfoFromCaller(mSystemContext)
+ preExecuteNotify(hostContext = hostInfo.hostContext)
+ val pmAccess = AccessLayerUtil(AccessLayer(hostInfo.hostContext))
+ var allSuspended = true
+ for (packageInfo in packages) {
+ if (!pmAccess.isPackageSuspended(packageInfo)) allSuspended = false
}
+ postExecuteNotify(hostContext = hostInfo.hostContext)
+ return allSuspended
}
- }
- 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
- )
+ override fun getSuspendedPackageAppExtras(packageInfo: TransferableSuspendedApp): Bundle {
+ val hostInfo = HostInfo.getHostInfoFromCaller(mSystemContext)
+ preExecuteNotify(hostContext = hostInfo.hostContext)
+ val pmAccess = AccessLayerUtil(AccessLayer(hostInfo.hostContext))
+ val bundle = pmAccess.getSuspendedPackageAppExtras(packageInfo)
+ postExecuteNotify(hostContext = hostInfo.hostContext)
+ return if (bundle != null) Bundle(bundle) else Bundle.EMPTY
+ }
- 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())
+ override fun getSuspendedPackageLauncherExtras(packageInfo: TransferableSuspendedApp): Bundle {
+ val hostInfo = HostInfo.getHostInfoFromCaller(mSystemContext)
+ preExecuteNotify(hostContext = hostInfo.hostContext)
+ val pmAccess = AccessLayerUtil(AccessLayer(hostInfo.hostContext))
+ val result = pmAccess.getSuspendedPackageLauncherExtras(packageInfo) ?: Bundle.EMPTY
+ postExecuteNotify(hostContext = hostInfo.hostContext)
+ return result
+ }
+
+ override fun dump(packageInfo: TransferableSuspendedApp): DumpResult {
+ val hostInfo = HostInfo.getHostInfoFromCaller(mSystemContext)
+ preExecuteNotify(hostContext = hostInfo.hostContext)
+ val pmAccess = AccessLayerUtil(AccessLayer(hostInfo.hostContext))
+ logger.d("Installed applications (all users, debug): ${pmAccess.getInstalledApplicationsAnyUser(0)}")
+ val result = DumpResult(pmAccess.isPackageSuspended(packageInfo),
+ pmAccess.getSuspendedPackageLauncherExtras(packageInfo) ?: Bundle.EMPTY)
+ postExecuteNotify(hostContext = hostInfo.hostContext)
+ return result
+ }
+
+ override fun setPackagesSuspended(
+ packages: MutableList<TransferableSuspendedApp>,
+ suspended: Boolean,
+ appExtras: PersistableBundle,
+ launcherExtras: PersistableBundle,
+ dialogMessage: String
+ ): Bundle {
+ val hostInfo = HostInfo.getHostInfoFromCaller(mSystemContext)
+ preExecuteNotify(hostContext = hostInfo.hostContext)
+ val pmAccess = AccessLayerUtil(AccessLayer(hostInfo.hostContext))
+ logger.d("Running suspend: $suspended on ${packages.size} packages.")
+ val result = Bundle()
+ result.putStringArray(AccessorStarter.EXTRA_DATA,
+ pmAccess.suspend(packages, suspended, appExtras, launcherExtras, dialogMessage))
+ readErrors(result, hostContext = hostInfo.hostContext)
+ postExecuteNotify(hostContext = hostInfo.hostContext)
+ return result
+ }
- 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
- // }
+ override fun apply(data: Bundle, ourList: Array<out TransferableSuspendedApp>, rawListMode: Int, rawStatus: Int): Bundle {
+ val hostInfo = HostInfo.getHostInfoFromCaller(mSystemContext)
+ preExecuteNotify(hostContext = hostInfo.hostContext)
+ val pmAccess = AccessLayerUtil(AccessLayer(hostInfo.hostContext))
+ uninstallHostIfNeeded(data, hostInfo.hostContext)
+ val result = Bundle()
+ // 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.
+ // 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)
+ // 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 systemAllAppList = _getInstalledApplicationsAcrossUser(pmAccess, hostInfo, 0)
+ val systemSuspendedList = _getPackagesSuspendedByWorkMode(pmAccess, systemAllAppList)
+ val listMode = when (rawListMode) {
+ 1 -> ListMode.BLACKLIST
+ 2 -> ListMode.WHITELIST
+ else -> throw IllegalArgumentException("Unexpected list mode")
+ }
+ val status = when (rawStatus) {
+ 1 -> Status.ON
+ 2 -> Status.OFF
+ else -> throw IllegalArgumentException("Unexpected status")
+ }
+
+ systemSuspendedList
.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")
- }
+ .forEach {
+ logger.d("SYS ${it.packageName} ${it.userId}")
+ }
- val tasks = systemAllAppList.stream()
+ 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)
+ val systemSuspended = systemSuspendedList.any { a ->
+ return@any a.essentiallyEqual(it)
+ }
+ val inOurList = ourList.any { a ->
+ return@any a.essentiallyEqual(it)
+ }
when (listMode) {
ListMode.BLACKLIST -> {
if (systemSuspended) {
if (inOurList) {
if (status == Status.ON) {
+ logger.d("Ignoring $systemSuspended $inOurList ${it.packageName} (${it.userId}) because of A")
return@filter false
} else {
+ logger.d("Including $systemSuspended $inOurList ${it.packageName} (${it.userId}) because of B")
return@filter true
}
} else {
if (status == Status.ON) {
+ logger.d("Including $systemSuspended $inOurList ${it.packageName} (${it.userId}) because of C")
return@filter true
} else {
+ logger.d("Ignoring $systemSuspended $inOurList ${it.packageName} (${it.userId}) because of D")
return@filter false
}
}
} else {
if (status == Status.ON) {
+ logger.d("Ignoring/Including $systemSuspended $inOurList ${it.packageName} (${it.userId}) because of E ($inOurList)")
return@filter inOurList
} else {
+ logger.d("Ignoring $systemSuspended $inOurList ${it.packageName} (${it.userId}) because of F")
return@filter false
}
}
@@ -276,18 +352,23 @@ class WorkModeAccessor {
ListMode.WHITELIST -> {
if (systemSuspended) {
if (inOurList) {
+ logger.d("Including $systemSuspended $inOurList ${it.packageName} (${it.userId}) because of B1")
return@filter true
} else {
if (status == Status.ON) {
+ logger.d("Ignoring $systemSuspended $inOurList ${it.packageName} (${it.userId}) because of B2")
return@filter false
} else {
+ logger.d("Including $systemSuspended $inOurList ${it.packageName} (${it.userId}) because of B3")
return@filter true
}
}
} else {
if (status == Status.ON) {
+ logger.d("Ignoring/Including $systemSuspended $inOurList ${it.packageName} (${it.userId}) because of B4 (${!inOurList})")
return@filter !inOurList
} else {
+ logger.d("Ignoring $systemSuspended $inOurList ${it.packageName} (${it.userId}) because of B5")
return@filter false
}
}
@@ -296,7 +377,9 @@ class WorkModeAccessor {
}
// Now, map them and determine that whatever a package should be suspended or un-suspended.
.map {
- val systemSuspended = systemSuspendedList.contains(it)
+ val systemSuspended = systemSuspendedList.any { a ->
+ return@any a.essentiallyEqual(it)
+ }
when (listMode) {
ListMode.BLACKLIST -> {
if (systemSuspended) {
@@ -322,46 +405,53 @@ class WorkModeAccessor {
}
// Collect them, we will execute later.
.collect(Collectors.toList())
- // Suspend first
- if (status == Status.ON) {
- val suspendList = tasks.stream()
+ // Suspend first
+ if (status == Status.ON) {
+ val suspendList = tasks.stream()
.filter {
return@filter it.suspend
}
.map {
- return@map it.packageName
+ return@map it.packageInfo
}
.collect(Collectors.toList())
- if (suspendList.size > 0) {
- suspend(suspendList.toTypedArray(),
+ logger.d("Applying settings: suspend $suspendList")
+ if (suspendList.size > 0) {
+ pmAccess.suspend(suspendList,
true)
+ }
}
- }
- // Then unsuspand
- val unsuspendList = tasks.stream()
+ // Then unsuspand
+ val unsuspendList = tasks.stream()
.filter {
return@filter !it.suspend
}
.map {
- return@map it.packageName
+ return@map it.packageInfo
}
.collect(Collectors.toList())
- if (unsuspendList.size > 0) {
- suspend(unsuspendList.toTypedArray(),
+ logger.d("Applying settings: unsuspend $unsuspendList")
+ if (unsuspendList.size > 0) {
+ pmAccess.suspend(unsuspendList,
false)
+ }
+ readErrors(result, hostContext = hostInfo.hostContext)
+ postExecuteNotify(hostContext = hostInfo.hostContext)
+ return result
}
- }
- 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))
+ override fun getInstalledApplicationsAcrossUser(flags: Int): MutableList<TransferableSuspendedApp> {
+ val hostInfo = HostInfo.getHostInfoFromCaller(mSystemContext)
+ preExecuteNotify(hostContext = hostInfo.hostContext)
+ val result = _getInstalledApplicationsAcrossUser(AccessLayerUtil(AccessLayer(hostInfo.hostContext)), hostInfo, flags)
+ postExecuteNotify(hostContext = hostInfo.hostContext)
+ return result
+ }
+ }
}
private data class SuspendTask(
- val packageName: String,
+ val packageInfo: TransferableSuspendedApp,
val suspend: Boolean
)