diff options
author | YuutaW <17158086+trumeet@users.noreply.github.com> | 2019-03-30 16:19:07 -0700 |
---|---|---|
committer | YuutaW <17158086+Trumeet@users.noreply.github.com> | 2019-03-30 16:19:07 -0700 |
commit | 06fbdcac173aea88cb4d02c4806866c83e720307 (patch) | |
tree | dce2416bfd2d991c23cf79e7820c2cbadc1d59fb /app/src/main/java/moe/yuuta/workmode/suspend | |
parent | b0d7fdf0cb31c54d47dcfbc5b39190ee39890bfa (diff) | |
download | WorkMode-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')
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 |