aboutsummaryrefslogtreecommitdiff
path: root/app/src/main/java/moe/yuuta/workmode/suspend
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/suspend
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/suspend')
-rw-r--r--app/src/main/java/moe/yuuta/workmode/suspend/AsyncSuspender.kt60
-rw-r--r--app/src/main/java/moe/yuuta/workmode/suspend/SuspendTile.kt10
-rw-r--r--app/src/main/java/moe/yuuta/workmode/suspend/SuspendedApp.kt38
-rw-r--r--app/src/main/java/moe/yuuta/workmode/suspend/Suspender.kt40
-rw-r--r--app/src/main/java/moe/yuuta/workmode/suspend/data/PersistableSuspendedApp.kt41
-rw-r--r--app/src/main/java/moe/yuuta/workmode/suspend/data/SuspendedStorage.kt38
-rw-r--r--app/src/main/java/moe/yuuta/workmode/suspend/data/TransferableSuspendedApp.kt101
7 files changed, 213 insertions, 115 deletions
diff --git a/app/src/main/java/moe/yuuta/workmode/suspend/AsyncSuspender.kt b/app/src/main/java/moe/yuuta/workmode/suspend/AsyncSuspender.kt
index 8cb4947..676d0ed 100644
--- a/app/src/main/java/moe/yuuta/workmode/suspend/AsyncSuspender.kt
+++ b/app/src/main/java/moe/yuuta/workmode/suspend/AsyncSuspender.kt
@@ -1,27 +1,51 @@
package moe.yuuta.workmode.suspend
import android.content.Context
-import moe.yuuta.workmode.async.Async
-import moe.yuuta.workmode.async.Callback
-import moe.yuuta.workmode.async.Runnable
-import moe.yuuta.workmode.async.Stoppable
+import android.os.Bundle
+import moe.yuuta.workmode.access.AccessorStarter
+import moe.yuuta.workmode.access.DumpResult
+import moe.yuuta.workmode.suspend.data.TransferableSuspendedApp
+import java.util.concurrent.CompletableFuture
+import java.util.function.Function
+import java.util.function.Supplier
/**
* An async suspender which wraps suspend tasks and run them in the background
*/
class AsyncSuspender(private val mContext: Context) {
- fun suspend(packageNames: Array<String>, suspended: Boolean, callback: Callback<Array<String>>): Stoppable =
- Async.beginTask(object : Runnable<Array<String>> {
- override fun run(): Array<String> = Suspender(mContext).suspend(packageNames, suspended)
- }, callback)
-
- fun isSuspended(packageNames: Array<String>, callback: Callback<Boolean>): Stoppable =
- Async.beginTask(object : Runnable<Boolean> {
- override fun run(): Boolean = Suspender(mContext).isSuspended(packageNames)
- }, callback)
-
- fun applyFromSettings(callback: Callback<Unit>): Stoppable =
- Async.beginTask(object : Runnable<Unit> {
- override fun run(): Unit = Suspender(mContext).applyFromSettings()
- }, callback)
+ fun isSuspended(packages: List<TransferableSuspendedApp>): CompletableFuture<Boolean> = generalCall(Function {
+ return@Function it.isPackageSuspended(packages)
+ })
+
+ fun suspend(packages: List<TransferableSuspendedApp>, suspended: Boolean): CompletableFuture<Array<String>> = generalCall(
+ Function {
+ return@Function it.suspend(packages, suspended)
+ })
+
+ fun getInstalledApplicationsAcrossUser(flags: Int): CompletableFuture<List<TransferableSuspendedApp>> = generalCall(
+ Function {
+ return@Function it.getInstalledApplicationsAcrossUser(flags)
+ })
+
+ private fun <T> generalCall(thenApply: java.util.function.Function<AccessorStarter, T>): CompletableFuture<T> {
+ return CompletableFuture.supplyAsync(Supplier {
+ return@Supplier AccessorStarter.start(mContext, true, thenApply)
+ })
+ }
+
+ fun applyFromSettings(): CompletableFuture<Unit> = generalCall(Function { t -> t.apply() })
+
+ fun getSuspendedPackageAppExtras(packageInfo: TransferableSuspendedApp): CompletableFuture<Bundle?> = generalCall(Function {
+ return@Function it.getSuspendedPackageAppExtras(packageInfo)
+ })
+
+ fun getSuspendedPackageLauncherExtras(packageInfo: TransferableSuspendedApp): CompletableFuture<Bundle?> = generalCall(Function {
+ return@Function it.getSuspendedPackageLauncherExtras(packageInfo)
+ })
+
+ fun dump(packageInfo: TransferableSuspendedApp): CompletableFuture<DumpResult> = generalCall(Function {
+ return@Function it.dump(packageInfo)
+ })
+
+ private data class DataWrapper<T>(val accessorStarter: AccessorStarter?, val data: T?)
} \ No newline at end of file
diff --git a/app/src/main/java/moe/yuuta/workmode/suspend/SuspendTile.kt b/app/src/main/java/moe/yuuta/workmode/suspend/SuspendTile.kt
index 23a3334..75b6ce4 100644
--- a/app/src/main/java/moe/yuuta/workmode/suspend/SuspendTile.kt
+++ b/app/src/main/java/moe/yuuta/workmode/suspend/SuspendTile.kt
@@ -3,10 +3,13 @@ package moe.yuuta.workmode.suspend
import android.content.Intent
import android.service.quicksettings.Tile
import android.service.quicksettings.TileService
+import com.elvishew.xlog.XLog
import moe.yuuta.workmode.suspend.data.Status
import moe.yuuta.workmode.suspend.data.SuspendedStorage
+import java.util.concurrent.TimeUnit
class SuspendTile : TileService() {
+ private val logger = XLog.tag("SuspendTile").build()
override fun onClick() {
val storage = SuspendedStorage.get(this)
storage.setStatus(
@@ -16,7 +19,12 @@ class SuspendTile : TileService() {
}
)
sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
- Suspender(this).applyFromSettings()
+ try {
+ AsyncSuspender(this).applyFromSettings()
+ .get(10, TimeUnit.SECONDS)
+ } catch (e: Throwable) {
+ logger.e("Cannot trigger", e)
+ }
}
override fun onStartListening() {
diff --git a/app/src/main/java/moe/yuuta/workmode/suspend/SuspendedApp.kt b/app/src/main/java/moe/yuuta/workmode/suspend/SuspendedApp.kt
deleted file mode 100644
index 9c6ddf7..0000000
--- a/app/src/main/java/moe/yuuta/workmode/suspend/SuspendedApp.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-package moe.yuuta.workmode.suspend
-
-import android.os.Bundle
-import android.os.PersistableBundle
-import moe.yuuta.workmode.BuildConfig
-
-/**
- * The data class of a suspended app. This is ONLY used in this app, which
- * can be understood as "a bridge between the extras stored in the system and
- * information which is used in this app"
- */
-data class SuspendedApp(
- val isSuspendedByWorkMode: Boolean, // The flag which is used to determine whatever is suspended by WorkMode
- val versionCodeSuspended: Int // The version code of this app when suspended the target
-) {
- companion object {
- // These values are stored by the system, should not be usually changed for migrating
- const val EXTRA_IS_SUSPENDED_BY_WORK_MODE = "moe.yuuta.workmode.EXTRA_IS_SUSPENDED_BY_WORK_MODE"
- const val EXTRA_VERSION_CODE = "moe.yuuta.workmode.EXTRA_VERSION_CODE"
-
- fun deserializeBundle(launcherExtras: Bundle?): SuspendedApp {
- if (launcherExtras == null) return SuspendedApp(false, -1)
- return SuspendedApp(
- launcherExtras.getBoolean(EXTRA_IS_SUSPENDED_BY_WORK_MODE, false),
- launcherExtras.getInt(EXTRA_VERSION_CODE, -1)
- )
- }
-
- fun getDefault(): SuspendedApp = SuspendedApp(true, BuildConfig.VERSION_CODE)
- }
-
- fun serializeBundle(): PersistableBundle {
- val bundle = PersistableBundle()
- bundle.putBoolean(EXTRA_IS_SUSPENDED_BY_WORK_MODE, isSuspendedByWorkMode)
- bundle.putInt(EXTRA_VERSION_CODE, versionCodeSuspended)
- return bundle
- }
-} \ No newline at end of file
diff --git a/app/src/main/java/moe/yuuta/workmode/suspend/Suspender.kt b/app/src/main/java/moe/yuuta/workmode/suspend/Suspender.kt
deleted file mode 100644
index 504fdba..0000000
--- a/app/src/main/java/moe/yuuta/workmode/suspend/Suspender.kt
+++ /dev/null
@@ -1,40 +0,0 @@
-package moe.yuuta.workmode.suspend
-
-import android.content.ComponentName
-import android.content.Context
-import android.os.PersistableBundle
-import android.service.quicksettings.TileService
-import moe.yuuta.workmode.R
-import moe.yuuta.workmode.access.ApplicationAccessorStarter
-import moe.yuuta.workmode.suspend.data.SuspendedStorage
-
-/**
- * The highest-level suspender to wrap all information needed to suspend or vice versa. This
- * should be called from UI components directly
- * Chain: UI -> Suspender -> AccessorStarter -> WorkModeAccessor -> AccessLayer -> Framework
- */
-class Suspender(private val mContext: Context) {
- fun suspend(packageNames: Array<String>, suspended: Boolean): Array<String> =
- ApplicationAccessorStarter(mContext).setPackagesSuspended(packageNames,
- suspended,
- PersistableBundle(),
- SuspendedApp.getDefault().serializeBundle(), // We use LauncherExtras because they are easy to read
- mContext.getString(R.string.suspended_message),
- true)
-
- fun isSuspended(packageNames: Array<String>): Boolean =
- ApplicationAccessorStarter(mContext).isPackageSuspended(packageNames, true)
-
- fun getPackagesSuspendedByWorkMode(): List<String> =
- ApplicationAccessorStarter(mContext).getPackagesSuspendedByWorkMode(true)
-
- fun applyFromSettings() {
- val storage = SuspendedStorage.get(mContext)
- storage.cleanList(mContext)
- val status = storage.getStatus()
- val listMode = storage.getListMode()
- val list = storage.getList()
- ApplicationAccessorStarter(mContext).apply(list.toTypedArray(), listMode, status, true)
- TileService.requestListeningState(mContext, ComponentName(mContext, SuspendTile::class.java))
- }
-} \ No newline at end of file
diff --git a/app/src/main/java/moe/yuuta/workmode/suspend/data/PersistableSuspendedApp.kt b/app/src/main/java/moe/yuuta/workmode/suspend/data/PersistableSuspendedApp.kt
new file mode 100644
index 0000000..ccfc008
--- /dev/null
+++ b/app/src/main/java/moe/yuuta/workmode/suspend/data/PersistableSuspendedApp.kt
@@ -0,0 +1,41 @@
+package moe.yuuta.workmode.suspend.data
+
+import android.os.Process
+import java.util.regex.Pattern
+
+/**
+ * The data of Suspended app which can be stored in {@link SuspendedStorage} only
+ */
+data class PersistableSuspendedApp(
+ val userId: Int,
+ val packageName: String
+) {
+ constructor(serializedString: String) : this(parseUserIDFromSerializedString(serializedString),
+ parsePackageNameFromSerializedString(serializedString))
+
+ override fun toString(): String =
+ String.format("%d|%s", userId, packageName)
+
+ /**
+ * Create a TransferableSuspendedApp with the simplest data.
+ */
+ fun copyToSimpleTransferableInfo(): TransferableSuspendedApp =
+ TransferableSuspendedApp(userId, packageName, null, -1, null)
+
+ companion object {
+ private fun parseFromSerializedString(serializedString: String): PersistableSuspendedApp {
+ val arr = serializedString.split(Pattern.compile("\\|"))
+ // Legacy
+ if (arr.size != 2) {
+ return PersistableSuspendedApp(Process.myUserHandle().hashCode(), serializedString)
+ }
+ return PersistableSuspendedApp(arr[0].toInt(), arr[1])
+ }
+
+ private fun parseUserIDFromSerializedString(serializedString: String): Int =
+ parseFromSerializedString(serializedString).userId
+
+ private fun parsePackageNameFromSerializedString(serializedString: String): String =
+ parseFromSerializedString(serializedString).packageName
+ }
+} \ No newline at end of file
diff --git a/app/src/main/java/moe/yuuta/workmode/suspend/data/SuspendedStorage.kt b/app/src/main/java/moe/yuuta/workmode/suspend/data/SuspendedStorage.kt
index 74c9b67..af3cd87 100644
--- a/app/src/main/java/moe/yuuta/workmode/suspend/data/SuspendedStorage.kt
+++ b/app/src/main/java/moe/yuuta/workmode/suspend/data/SuspendedStorage.kt
@@ -7,7 +7,6 @@ import com.crashlytics.android.answers.Answers
import com.crashlytics.android.answers.CustomEvent
import com.elvishew.xlog.XLog
import moe.yuuta.workmode.Setup
-import moe.yuuta.workmode.utils.Utils
import java.util.stream.Collectors
/**
@@ -31,9 +30,27 @@ class SuspendedStorage(mContext: Context) {
fun getStorage(): SharedPreferences = storage
- fun getList(): List<String> = (getStorage().getStringSet("list", setOf()) ?: listOf<String>()).toList()
+ fun getList(): List<PersistableSuspendedApp> =
+ _getList().stream()
+ .map {
+ return@map PersistableSuspendedApp(it)
+ }
+ .collect(Collectors.toList())
- fun setList(set: Set<String>) {
+ fun setList(set: Set<PersistableSuspendedApp>) {
+ _setList(set.stream()
+ .map {
+ return@map it.toString()
+ }
+ .collect(Collectors.toSet()))
+ }
+
+ /**
+ * Internal get list (string)
+ */
+ private fun _getList(): List<String> = (getStorage().getStringSet("list", setOf()) ?: listOf<String>()).toList()
+
+ private fun _setList(set: Set<String>) {
logger.d("s() $set")
getStorage().edit().putStringSet("list", set).apply()
}
@@ -68,21 +85,6 @@ class SuspendedStorage(mContext: Context) {
else -> ListMode.BLACKLIST
}
- fun cleanList(context: Context) {
- val installed = context.packageManager.getInstalledApplications(0)
- .stream()
- .filter(Utils.buildGeneralApplicationInfoFilter(context))
- .map {
- return@map it.packageName
- }
- .collect(Collectors.toList())
- setList(getList().stream()
- .filter {
- return@filter installed.contains(it)
- }
- .collect(Collectors.toSet()))
- }
-
// #Anti-Crack
fun reportCrack(id: String, reason: String) {
if (getList().isEmpty()) {
diff --git a/app/src/main/java/moe/yuuta/workmode/suspend/data/TransferableSuspendedApp.kt b/app/src/main/java/moe/yuuta/workmode/suspend/data/TransferableSuspendedApp.kt
new file mode 100644
index 0000000..03bf842
--- /dev/null
+++ b/app/src/main/java/moe/yuuta/workmode/suspend/data/TransferableSuspendedApp.kt
@@ -0,0 +1,101 @@
+package moe.yuuta.workmode.suspend.data
+
+import android.annotation.SystemApi
+import android.content.pm.PackageManager
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.os.Parcel
+import android.os.Parcelable
+import android.os.Process
+import android.text.TextUtils
+import com.elvishew.xlog.XLog
+
+/**
+ * The data of Suspended app which can be transfered through IPC only
+ */
+data class TransferableSuspendedApp(
+ val userId: Int,
+ val packageName: String,
+ // Null if it has the same user id with the host
+ var label: CharSequence?,
+ // -1 if not available
+ var uid: Int,
+ // Null if it has the same user id with the host
+ var icon: Bitmap?
+) : Parcelable {
+ constructor(parcel: Parcel) : this(
+ parcel.readInt(),
+ parcel.readString(),
+ TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel),
+ parcel.readInt(),
+ parcel.readParcelable<Bitmap>(Bitmap::class.java.classLoader)
+ )
+
+ fun trimToPersistable(): PersistableSuspendedApp =
+ PersistableSuspendedApp(userId, packageName)
+
+ /**
+ * Fill all fields.
+ */
+ @SystemApi
+ fun fillData(pm: PackageManager, ignoreLargeData: Boolean) {
+ val info = pm.getApplicationInfoAsUser(packageName,
+ 0,
+ userId)
+ label = if (ignoreLargeData) null
+ else pm.getApplicationLabel(info)
+ uid = info.uid
+ if (ignoreLargeData) {
+ icon = null
+ } else {
+ XLog.d("Loading icon for ${info.packageName}")
+ val res = pm.getResourcesForApplicationAsUser(info.packageName, userId)
+ icon = BitmapFactory.decodeResource(res, info.icon)
+ }
+ }
+
+ /**
+ * Remove unnecessary data for in-app transaction
+ */
+ fun trimData() {
+ label = null
+ uid = -1
+ icon = null
+ }
+
+ companion object {
+ fun of(packageName: String): TransferableSuspendedApp {
+ return of(packageName, Process.myUserHandle().hashCode())
+ }
+
+ fun of(packageName: String, userId: Int): TransferableSuspendedApp {
+ return PersistableSuspendedApp(userId, packageName).copyToSimpleTransferableInfo()
+ }
+
+ @JvmField
+ val CREATOR = object : Parcelable.Creator<TransferableSuspendedApp> {
+ override fun createFromParcel(parcel: Parcel): TransferableSuspendedApp {
+ return TransferableSuspendedApp(parcel)
+ }
+
+ override fun newArray(size: Int): Array<TransferableSuspendedApp?> {
+ return arrayOfNulls(size)
+ }
+ }
+ }
+
+ override fun writeToParcel(parcel: Parcel, flags: Int) {
+ parcel.writeInt(userId)
+ parcel.writeString(packageName)
+ TextUtils.writeToParcel(label, parcel, flags)
+ parcel.writeInt(uid)
+ parcel.writeParcelable(icon, 0)
+ }
+
+ override fun describeContents(): Int {
+ return 0
+ }
+
+ fun essentiallyEqual(that: TransferableSuspendedApp): Boolean = that.userId == this.userId &&
+ that.packageName == this.packageName
+} \ No newline at end of file