mirror of
https://github.com/topjohnwu/Magisk.git
synced 2024-12-25 21:27:39 +00:00
Updated Hide screen with new arch
This commit is contained in:
parent
cda14af208
commit
e81f00ef1a
@ -1,6 +1,7 @@
|
|||||||
package com.topjohnwu.magisk.di
|
package com.topjohnwu.magisk.di
|
||||||
|
|
||||||
import com.topjohnwu.magisk.ui.MainViewModel
|
import com.topjohnwu.magisk.ui.MainViewModel
|
||||||
|
import com.topjohnwu.magisk.ui.hide.HideViewModel
|
||||||
import com.topjohnwu.magisk.ui.home.HomeViewModel
|
import com.topjohnwu.magisk.ui.home.HomeViewModel
|
||||||
import com.topjohnwu.magisk.ui.superuser.SuperuserViewModel
|
import com.topjohnwu.magisk.ui.superuser.SuperuserViewModel
|
||||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||||
@ -11,4 +12,5 @@ val viewModelModules = module {
|
|||||||
viewModel { MainViewModel() }
|
viewModel { MainViewModel() }
|
||||||
viewModel { HomeViewModel(get(), get()) }
|
viewModel { HomeViewModel(get(), get()) }
|
||||||
viewModel { SuperuserViewModel(get(), get(), get()) }
|
viewModel { SuperuserViewModel(get(), get(), get()) }
|
||||||
|
viewModel { HideViewModel(get(), get()) }
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
package com.topjohnwu.magisk.model.entity
|
||||||
|
|
||||||
|
import android.content.pm.ApplicationInfo
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import com.topjohnwu.magisk.utils.packageInfo
|
||||||
|
import com.topjohnwu.magisk.utils.processes
|
||||||
|
|
||||||
|
class HideAppInfo(
|
||||||
|
val info: ApplicationInfo,
|
||||||
|
val name: String,
|
||||||
|
val icon: Drawable
|
||||||
|
) {
|
||||||
|
|
||||||
|
val processes = info.packageInfo?.processes?.distinct() ?: listOf(info.packageName)
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package com.topjohnwu.magisk.model.entity
|
||||||
|
|
||||||
|
class HideTarget(line: String) {
|
||||||
|
|
||||||
|
private val split = line.split(Regex("\\|"), 2)
|
||||||
|
|
||||||
|
val packageName = split[0]
|
||||||
|
val process = split.getOrElse(1) { packageName }
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,92 @@
|
|||||||
|
package com.topjohnwu.magisk.model.entity.recycler
|
||||||
|
|
||||||
|
import com.skoumal.teanity.databinding.ComparableRvItem
|
||||||
|
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
|
||||||
|
import com.skoumal.teanity.rxbus.RxBus
|
||||||
|
import com.skoumal.teanity.util.DiffObservableList
|
||||||
|
import com.skoumal.teanity.util.KObservableField
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.model.entity.HideAppInfo
|
||||||
|
import com.topjohnwu.magisk.model.entity.HideTarget
|
||||||
|
import com.topjohnwu.magisk.model.entity.state.IndeterminateState
|
||||||
|
import com.topjohnwu.magisk.model.events.HideProcessEvent
|
||||||
|
import com.topjohnwu.magisk.utils.inject
|
||||||
|
import com.topjohnwu.magisk.utils.toggle
|
||||||
|
|
||||||
|
class HideRvItem(val item: HideAppInfo, targets: List<HideTarget>) :
|
||||||
|
ComparableRvItem<HideRvItem>() {
|
||||||
|
|
||||||
|
override val layoutRes: Int = R.layout.item_hide_app
|
||||||
|
|
||||||
|
val packageName = item.info.packageName.orEmpty()
|
||||||
|
val items = DiffObservableList(callback).also {
|
||||||
|
val items = item.processes.map {
|
||||||
|
val isHidden = targets.any { target ->
|
||||||
|
packageName == target.packageName && it == target.process
|
||||||
|
}
|
||||||
|
HideProcessRvItem(packageName, it, isHidden)
|
||||||
|
}
|
||||||
|
it.update(items)
|
||||||
|
}
|
||||||
|
val isHiddenState = KObservableField(currentState)
|
||||||
|
val isExpanded = KObservableField(false)
|
||||||
|
|
||||||
|
private val itemsProcess get() = items.filterIsInstance<HideProcessRvItem>()
|
||||||
|
|
||||||
|
private val currentState
|
||||||
|
get() = when (itemsProcess.count { it.isHidden.value }) {
|
||||||
|
items.size -> IndeterminateState.CHECKED
|
||||||
|
in 1 until items.size -> IndeterminateState.INDETERMINATE
|
||||||
|
else -> IndeterminateState.UNCHECKED
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
itemsProcess.forEach {
|
||||||
|
it.isHidden.addOnPropertyChangedCallback { isHiddenState.value = currentState }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggle() {
|
||||||
|
val desiredState = when (isHiddenState.value) {
|
||||||
|
IndeterminateState.INDETERMINATE,
|
||||||
|
IndeterminateState.UNCHECKED -> true
|
||||||
|
IndeterminateState.CHECKED -> false
|
||||||
|
}
|
||||||
|
itemsProcess.forEach { it.isHidden.value = desiredState }
|
||||||
|
isHiddenState.value = currentState
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggleExpansion() {
|
||||||
|
if (items.size <= 1) return
|
||||||
|
isExpanded.toggle()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun contentSameAs(other: HideRvItem): Boolean = items.all { other.items.contains(it) }
|
||||||
|
override fun itemSameAs(other: HideRvItem): Boolean = item.info == other.item.info
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class HideProcessRvItem(
|
||||||
|
val packageName: String,
|
||||||
|
val process: String,
|
||||||
|
isHidden: Boolean
|
||||||
|
) : ComparableRvItem<HideProcessRvItem>() {
|
||||||
|
|
||||||
|
override val layoutRes: Int = R.layout.item_hide_process
|
||||||
|
|
||||||
|
val isHidden = KObservableField(isHidden)
|
||||||
|
|
||||||
|
private val rxBus: RxBus by inject()
|
||||||
|
|
||||||
|
init {
|
||||||
|
this.isHidden.addOnPropertyChangedCallback {
|
||||||
|
rxBus.post(HideProcessEvent(this@HideProcessRvItem))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggle() = isHidden.toggle()
|
||||||
|
|
||||||
|
override fun contentSameAs(other: HideProcessRvItem): Boolean = itemSameAs(other)
|
||||||
|
override fun itemSameAs(other: HideProcessRvItem): Boolean =
|
||||||
|
packageName == other.packageName && process == other.process
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package com.topjohnwu.magisk.model.entity.state
|
||||||
|
|
||||||
|
enum class IndeterminateState {
|
||||||
|
INDETERMINATE, CHECKED, UNCHECKED
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
package com.topjohnwu.magisk.model.events
|
||||||
|
|
||||||
|
import com.skoumal.teanity.rxbus.RxBus
|
||||||
|
import com.topjohnwu.magisk.model.entity.recycler.HideProcessRvItem
|
||||||
|
|
||||||
|
class HideProcessEvent(val item: HideProcessRvItem) : RxBus.Event
|
116
app/src/main/java/com/topjohnwu/magisk/ui/hide/HideViewModel.kt
Normal file
116
app/src/main/java/com/topjohnwu/magisk/ui/hide/HideViewModel.kt
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
package com.topjohnwu.magisk.ui.hide
|
||||||
|
|
||||||
|
import android.content.pm.ApplicationInfo
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import com.skoumal.teanity.databinding.ComparableRvItem
|
||||||
|
import com.skoumal.teanity.extensions.addOnPropertyChangedCallback
|
||||||
|
import com.skoumal.teanity.extensions.subscribeK
|
||||||
|
import com.skoumal.teanity.rxbus.RxBus
|
||||||
|
import com.skoumal.teanity.util.DiffObservableList
|
||||||
|
import com.skoumal.teanity.util.KObservableField
|
||||||
|
import com.topjohnwu.magisk.App
|
||||||
|
import com.topjohnwu.magisk.BR
|
||||||
|
import com.topjohnwu.magisk.model.entity.HideAppInfo
|
||||||
|
import com.topjohnwu.magisk.model.entity.HideTarget
|
||||||
|
import com.topjohnwu.magisk.model.entity.recycler.HideProcessRvItem
|
||||||
|
import com.topjohnwu.magisk.model.entity.recycler.HideRvItem
|
||||||
|
import com.topjohnwu.magisk.model.events.HideProcessEvent
|
||||||
|
import com.topjohnwu.magisk.ui.base.MagiskViewModel
|
||||||
|
import com.topjohnwu.magisk.utils.Utils
|
||||||
|
import com.topjohnwu.magisk.utils.toSingle
|
||||||
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import io.reactivex.Single
|
||||||
|
import me.tatarka.bindingcollectionadapter2.OnItemBind
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
class HideViewModel(
|
||||||
|
private val packageManager: PackageManager,
|
||||||
|
rxBus: RxBus
|
||||||
|
) : MagiskViewModel() {
|
||||||
|
|
||||||
|
val query = KObservableField("")
|
||||||
|
val isShowSystem = KObservableField(false)
|
||||||
|
|
||||||
|
private val allItems = DiffObservableList(ComparableRvItem.callback)
|
||||||
|
val items = DiffObservableList(ComparableRvItem.callback)
|
||||||
|
val itemBinding = OnItemBind<ComparableRvItem<*>> { itemBinding, _, item ->
|
||||||
|
item.bind(itemBinding)
|
||||||
|
itemBinding.bindExtra(BR.viewModel, this@HideViewModel)
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
rxBus.register<HideProcessEvent>()
|
||||||
|
.subscribeK { toggleItem(it.item) }
|
||||||
|
.add()
|
||||||
|
|
||||||
|
isShowSystem.addOnPropertyChangedCallback { query() }
|
||||||
|
query.addOnPropertyChangedCallback { query() }
|
||||||
|
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun refresh() {
|
||||||
|
// fetching this for every item is nonsensical, so we add .cache() so the response is all
|
||||||
|
// the same for every single mapped item, it only actually executes the whole thing the
|
||||||
|
// first time around.
|
||||||
|
val hideTargets = Shell.su("magiskhide --ls").toSingle()
|
||||||
|
.map { it.exec().out }
|
||||||
|
.flattenAsFlowable { it }
|
||||||
|
.map { HideTarget(it) }
|
||||||
|
.toList()
|
||||||
|
.cache()
|
||||||
|
|
||||||
|
Single.fromCallable { packageManager.getInstalledApplications(0) }
|
||||||
|
.flattenAsFlowable { it }
|
||||||
|
.filter { it.enabled && !blacklist.contains(it.packageName) }
|
||||||
|
.map {
|
||||||
|
val label = Utils.getAppLabel(it, packageManager)
|
||||||
|
val icon = it.loadIcon(packageManager)
|
||||||
|
HideAppInfo(it, label, icon)
|
||||||
|
}
|
||||||
|
.filter { it.processes.isNotEmpty() }
|
||||||
|
.map { HideRvItem(it, hideTargets.blockingGet()) }
|
||||||
|
.toList()
|
||||||
|
.map { it.sortBy { it.item.info.name }; it }
|
||||||
|
.applyViewModel(this)
|
||||||
|
.subscribeK(onError = Timber::e) {
|
||||||
|
allItems.update(it)
|
||||||
|
query()
|
||||||
|
}
|
||||||
|
.add()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun query(showSystem: Boolean = isShowSystem.value, query: String = this.query.value) {
|
||||||
|
allItems.toSingle()
|
||||||
|
.map { it.filterIsInstance<HideRvItem>() }
|
||||||
|
.flattenAsFlowable { it }
|
||||||
|
.filter { it.item.name.contains(query) || it.item.processes.any { it.contains(query) } }
|
||||||
|
.filter { if (showSystem) true else it.item.info.flags and ApplicationInfo.FLAG_SYSTEM == 0 }
|
||||||
|
.toList()
|
||||||
|
.subscribeK { items.update(it) }
|
||||||
|
.add()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun toggleItem(item: HideProcessRvItem) {
|
||||||
|
val state = if (item.isHidden.value) "add" else "rm"
|
||||||
|
"magiskhide --%s %s %s".format(state, item.packageName, item.process)
|
||||||
|
.let { Shell.su(it) }
|
||||||
|
.toSingle()
|
||||||
|
.map { it.submit() }
|
||||||
|
.subscribeK()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val blacklist = listOf(
|
||||||
|
App.self.packageName,
|
||||||
|
"android",
|
||||||
|
"com.android.chrome",
|
||||||
|
"com.chrome.beta",
|
||||||
|
"com.chrome.dev",
|
||||||
|
"com.chrome.canary",
|
||||||
|
"com.android.webview",
|
||||||
|
"com.google.android.webview"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,100 +0,0 @@
|
|||||||
package com.topjohnwu.magisk.ui.hide;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.SearchView;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.Config;
|
|
||||||
import com.topjohnwu.magisk.R;
|
|
||||||
import com.topjohnwu.magisk.model.adapters.ApplicationAdapter;
|
|
||||||
import com.topjohnwu.magisk.ui.base.BaseFragment;
|
|
||||||
import com.topjohnwu.magisk.utils.Event;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
|
||||||
import butterknife.BindView;
|
|
||||||
|
|
||||||
public class MagiskHideFragment extends BaseFragment {
|
|
||||||
|
|
||||||
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
|
|
||||||
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
|
||||||
|
|
||||||
private SearchView search;
|
|
||||||
private ApplicationAdapter adapter;
|
|
||||||
private SearchView.OnQueryTextListener searchListener;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setHasOptionsMenu(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
|
||||||
View view = inflater.inflate(R.layout.fragment_magisk_hide, container, false);
|
|
||||||
unbinder = new MagiskHideFragment_ViewBinding(this, view);
|
|
||||||
|
|
||||||
adapter = new ApplicationAdapter(requireActivity());
|
|
||||||
recyclerView.setAdapter(adapter);
|
|
||||||
|
|
||||||
mSwipeRefreshLayout.setRefreshing(true);
|
|
||||||
mSwipeRefreshLayout.setOnRefreshListener(adapter::refresh);
|
|
||||||
|
|
||||||
searchListener = new SearchView.OnQueryTextListener() {
|
|
||||||
@Override
|
|
||||||
public boolean onQueryTextSubmit(String query) {
|
|
||||||
adapter.filter(query);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onQueryTextChange(String newText) {
|
|
||||||
adapter.filter(newText);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
requireActivity().setTitle(R.string.magiskhide);
|
|
||||||
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
|
||||||
inflater.inflate(R.menu.menu_magiskhide, menu);
|
|
||||||
search = (SearchView) menu.findItem(R.id.app_search).getActionView();
|
|
||||||
search.setOnQueryTextListener(searchListener);
|
|
||||||
menu.findItem(R.id.show_system).setChecked(Config.get(Config.Key.SHOW_SYSTEM_APP));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
if (item.getItemId() == R.id.show_system) {
|
|
||||||
boolean showSystem = !item.isChecked();
|
|
||||||
item.setChecked(showSystem);
|
|
||||||
Config.set(Config.Key.SHOW_SYSTEM_APP, showSystem);
|
|
||||||
adapter.setShowSystem(showSystem);
|
|
||||||
adapter.filter(search.getQuery().toString());
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int[] getListeningEvents() {
|
|
||||||
return new int[] {Event.MAGISK_HIDE_DONE};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onEvent(int event) {
|
|
||||||
mSwipeRefreshLayout.setRefreshing(false);
|
|
||||||
adapter.filter(search.getQuery().toString());
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,64 @@
|
|||||||
|
package com.topjohnwu.magisk.ui.hide
|
||||||
|
|
||||||
|
import android.view.Menu
|
||||||
|
import android.view.MenuInflater
|
||||||
|
import android.view.MenuItem
|
||||||
|
import android.widget.SearchView
|
||||||
|
import com.topjohnwu.magisk.Config
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.databinding.FragmentMagiskHideBinding
|
||||||
|
import com.topjohnwu.magisk.ui.base.MagiskFragment
|
||||||
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
|
|
||||||
|
class MagiskHideFragment : MagiskFragment<HideViewModel, FragmentMagiskHideBinding>(),
|
||||||
|
SearchView.OnQueryTextListener {
|
||||||
|
|
||||||
|
override val layoutRes: Int = R.layout.fragment_magisk_hide
|
||||||
|
override val viewModel: HideViewModel by viewModel()
|
||||||
|
|
||||||
|
override fun onStart() {
|
||||||
|
super.onStart()
|
||||||
|
setHasOptionsMenu(true)
|
||||||
|
requireActivity().setTitle(R.string.magiskhide)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
||||||
|
inflater.inflate(R.menu.menu_magiskhide, menu)
|
||||||
|
menu.apply {
|
||||||
|
(findItem(R.id.app_search).actionView as? SearchView)
|
||||||
|
?.setOnQueryTextListener(this@MagiskHideFragment)
|
||||||
|
|
||||||
|
val showSystem = Config.get<Boolean>(Config.Key.SHOW_SYSTEM_APP)
|
||||||
|
|
||||||
|
findItem(R.id.show_system).isChecked = showSystem
|
||||||
|
viewModel.isShowSystem.value = showSystem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
|
if (item.itemId == R.id.show_system) {
|
||||||
|
val showSystem = !item.isChecked
|
||||||
|
item.isChecked = showSystem
|
||||||
|
Config.set(Config.Key.SHOW_SYSTEM_APP, showSystem)
|
||||||
|
viewModel.isShowSystem.value = showSystem
|
||||||
|
//adapter!!.setShowSystem(showSystem)
|
||||||
|
//adapter!!.filter(search!!.query.toString())
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onQueryTextSubmit(query: String?): Boolean {
|
||||||
|
viewModel.query.value = query.orEmpty()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onQueryTextChange(query: String?): Boolean {
|
||||||
|
viewModel.query.value = query.orEmpty()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/*override fun onEvent(event: Int) {
|
||||||
|
//mSwipeRefreshLayout!!.isRefreshing = false
|
||||||
|
adapter!!.filter(search!!.query.toString())
|
||||||
|
}*/
|
||||||
|
}
|
@ -8,6 +8,8 @@ import androidx.appcompat.widget.Toolbar
|
|||||||
import androidx.databinding.BindingAdapter
|
import androidx.databinding.BindingAdapter
|
||||||
import androidx.drawerlayout.widget.DrawerLayout
|
import androidx.drawerlayout.widget.DrawerLayout
|
||||||
import com.google.android.material.navigation.NavigationView
|
import com.google.android.material.navigation.NavigationView
|
||||||
|
import com.topjohnwu.magisk.R
|
||||||
|
import com.topjohnwu.magisk.model.entity.state.IndeterminateState
|
||||||
|
|
||||||
|
|
||||||
@BindingAdapter("onNavigationClick")
|
@BindingAdapter("onNavigationClick")
|
||||||
@ -35,3 +37,23 @@ fun setImageResource(view: AppCompatImageView, @DrawableRes resId: Int) {
|
|||||||
fun setTint(view: AppCompatImageView, @ColorInt tint: Int) {
|
fun setTint(view: AppCompatImageView, @ColorInt tint: Int) {
|
||||||
view.setColorFilter(tint)
|
view.setColorFilter(tint)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@BindingAdapter("isChecked")
|
||||||
|
fun setChecked(view: AppCompatImageView, isChecked: Boolean) {
|
||||||
|
val state = when (isChecked) {
|
||||||
|
true -> IndeterminateState.CHECKED
|
||||||
|
else -> IndeterminateState.UNCHECKED
|
||||||
|
}
|
||||||
|
setChecked(view, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
@BindingAdapter("isChecked")
|
||||||
|
fun setChecked(view: AppCompatImageView, isChecked: IndeterminateState) {
|
||||||
|
view.setImageResource(
|
||||||
|
when (isChecked) {
|
||||||
|
IndeterminateState.INDETERMINATE -> R.drawable.ic_indeterminate
|
||||||
|
IndeterminateState.CHECKED -> R.drawable.ic_checked
|
||||||
|
IndeterminateState.UNCHECKED -> R.drawable.ic_unchecked
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
50
app/src/main/java/com/topjohnwu/magisk/utils/XAndroid.kt
Normal file
50
app/src/main/java/com/topjohnwu/magisk/utils/XAndroid.kt
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package com.topjohnwu.magisk.utils
|
||||||
|
|
||||||
|
import android.content.pm.ApplicationInfo
|
||||||
|
import android.content.pm.ComponentInfo
|
||||||
|
import android.content.pm.PackageInfo
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.content.pm.PackageManager.*
|
||||||
|
|
||||||
|
val PackageInfo.processes
|
||||||
|
get() = activities?.processNames.orEmpty() +
|
||||||
|
services?.processNames.orEmpty() +
|
||||||
|
receivers?.processNames.orEmpty() +
|
||||||
|
providers?.processNames.orEmpty()
|
||||||
|
|
||||||
|
val Array<out ComponentInfo>.processNames get() = mapNotNull { it.processName }
|
||||||
|
|
||||||
|
val ApplicationInfo.packageInfo: PackageInfo?
|
||||||
|
get() {
|
||||||
|
val pm: PackageManager by inject()
|
||||||
|
|
||||||
|
return try {
|
||||||
|
val request = GET_ACTIVITIES or
|
||||||
|
GET_SERVICES or
|
||||||
|
GET_RECEIVERS or
|
||||||
|
GET_PROVIDERS
|
||||||
|
pm.getPackageInfo(packageName, request)
|
||||||
|
} catch (e1: Exception) {
|
||||||
|
try {
|
||||||
|
pm.activities(packageName).apply {
|
||||||
|
services = pm.services(packageName)
|
||||||
|
receivers = pm.receivers(packageName)
|
||||||
|
providers = pm.providers(packageName)
|
||||||
|
}
|
||||||
|
} catch (e2: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun PackageManager.activities(packageName: String) =
|
||||||
|
getPackageInfo(packageName, GET_ACTIVITIES)
|
||||||
|
|
||||||
|
fun PackageManager.services(packageName: String) =
|
||||||
|
getPackageInfo(packageName, GET_SERVICES).services
|
||||||
|
|
||||||
|
fun PackageManager.receivers(packageName: String) =
|
||||||
|
getPackageInfo(packageName, GET_RECEIVERS).receivers
|
||||||
|
|
||||||
|
fun PackageManager.providers(packageName: String) =
|
||||||
|
getPackageInfo(packageName, GET_PROVIDERS).providers
|
20
app/src/main/java/com/topjohnwu/magisk/utils/XKoin.kt
Normal file
20
app/src/main/java/com/topjohnwu/magisk/utils/XKoin.kt
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package com.topjohnwu.magisk.utils
|
||||||
|
|
||||||
|
import org.koin.core.context.GlobalContext
|
||||||
|
import org.koin.core.parameter.ParametersDefinition
|
||||||
|
import org.koin.core.qualifier.Qualifier
|
||||||
|
import org.koin.core.scope.Scope
|
||||||
|
|
||||||
|
fun getKoin() = GlobalContext.get().koin
|
||||||
|
|
||||||
|
inline fun <reified T : Any> inject(
|
||||||
|
qualifier: Qualifier? = null,
|
||||||
|
scope: Scope? = null,
|
||||||
|
noinline parameters: ParametersDefinition? = null
|
||||||
|
) = lazy { get<T>(qualifier, scope, parameters) }
|
||||||
|
|
||||||
|
inline fun <reified T : Any> get(
|
||||||
|
qualifier: Qualifier? = null,
|
||||||
|
scope: Scope? = null,
|
||||||
|
noinline parameters: ParametersDefinition? = null
|
||||||
|
): T = getKoin().get(qualifier, scope, parameters)
|
5
app/src/main/java/com/topjohnwu/magisk/utils/XRx.kt
Normal file
5
app/src/main/java/com/topjohnwu/magisk/utils/XRx.kt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package com.topjohnwu.magisk.utils
|
||||||
|
|
||||||
|
import io.reactivex.Single
|
||||||
|
|
||||||
|
fun <T : Any> T.toSingle() = Single.just(this)
|
11
app/src/main/res/drawable/ic_checked.xml
Normal file
11
app/src/main/res/drawable/ic_checked.xml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000"
|
||||||
|
android:pathData="M20,12A8,8 0 0,1 12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4C12.76,4 13.5,4.11 14.2,4.31L15.77,2.74C14.61,2.26 13.34,2 12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12M7.91,10.08L6.5,11.5L11,16L21,6L19.59,4.58L11,13.17L7.91,10.08Z" />
|
||||||
|
</vector>
|
10
app/src/main/res/drawable/ic_indeterminate.xml
Normal file
10
app/src/main/res/drawable/ic_indeterminate.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000"
|
||||||
|
android:pathData="M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M7,13H17V11H7" />
|
||||||
|
</vector>
|
10
app/src/main/res/drawable/ic_unchecked.xml
Normal file
10
app/src/main/res/drawable/ic_unchecked.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000"
|
||||||
|
android:pathData="M12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z" />
|
||||||
|
</vector>
|
@ -1,25 +1,36 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:id="@+id/swipeRefreshLayout"
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="fill_parent"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<LinearLayout
|
<data>
|
||||||
|
|
||||||
|
<variable
|
||||||
|
name="viewModel"
|
||||||
|
type="com.topjohnwu.magisk.ui.hide.HideViewModel" />
|
||||||
|
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical">
|
app:onRefreshListener="@{() -> viewModel.refresh()}"
|
||||||
|
app:refreshing="@{viewModel.loading}">
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/recyclerView"
|
dividerColor="@{@android:color/transparent}"
|
||||||
|
dividerSize="@{@dimen/margin_generic}"
|
||||||
|
itemBinding="@{viewModel.itemBinding}"
|
||||||
|
items="@{viewModel.items}"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:dividerHeight="@dimen/card_divider_space"
|
android:orientation="vertical"
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
android:padding="@dimen/margin_generic"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
tools:listitem="@layout/item_hide_app" />
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||||
|
|
||||||
|
</layout>
|
||||||
|
|
||||||
|
124
app/src/main/res/layout/item_hide_app.xml
Normal file
124
app/src/main/res/layout/item_hide_app.xml
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<variable
|
||||||
|
name="item"
|
||||||
|
type="com.topjohnwu.magisk.model.entity.recycler.HideRvItem" />
|
||||||
|
|
||||||
|
<variable
|
||||||
|
name="viewModel"
|
||||||
|
type="com.topjohnwu.magisk.ui.hide.HideViewModel" />
|
||||||
|
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<com.google.android.material.card.MaterialCardView
|
||||||
|
style="@style/Widget.Card"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:foreground="?attr/selectableItemBackground"
|
||||||
|
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||||
|
android:onClick="@{() -> item.toggleExpansion()}"
|
||||||
|
app:cardElevation="@dimen/card_elevation">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:padding="@dimen/margin_generic_half">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
|
android:id="@+id/hide_app_icon"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:gravity="end"
|
||||||
|
android:src="@{item.item.icon}"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/hide_app_processes"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/hide_app_name"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:src="@drawable/ic_magisk" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
android:id="@+id/hide_app_name"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/margin_generic"
|
||||||
|
android:layout_marginEnd="@dimen/margin_generic"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:text="@{item.item.name}"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
|
android:textIsSelectable="false"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/hide_app_package"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/hide_app_checkbox"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/hide_app_icon"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintVertical_chainStyle="packed"
|
||||||
|
tools:text="Magisk" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
android:id="@+id/hide_app_package"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:text="@{item.packageName}"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:textColor="@android:color/tertiary_text_dark"
|
||||||
|
android:textIsSelectable="false"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/hide_app_processes"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/hide_app_arrow"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/hide_app_name"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/hide_app_name"
|
||||||
|
tools:text="com.topjohnwu.magisk" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
|
android:id="@+id/hide_app_arrow"
|
||||||
|
gone="@{item.items.size == 1}"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_marginStart="5dp"
|
||||||
|
android:layout_marginEnd="5dp"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:rotation="@{item.isExpanded() ? 180 : 0}"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/hide_app_package"
|
||||||
|
app:layout_constraintEnd_toEndOf="@+id/hide_app_name"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/hide_app_package"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/hide_app_package"
|
||||||
|
app:srcCompat="@drawable/ic_arrow"
|
||||||
|
app:tint="?attr/imageColorTint" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
|
android:id="@+id/hide_app_checkbox"
|
||||||
|
style="@style/Widget.Icon"
|
||||||
|
isChecked="@{item.isHiddenState}"
|
||||||
|
android:onClick="@{() -> item.toggle()}"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/hide_app_processes"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/hide_app_name"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:tint="?attr/imageColorTint"
|
||||||
|
tools:src="@drawable/ic_checked" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/hide_app_processes"
|
||||||
|
gone="@{!item.isExpanded}"
|
||||||
|
itemBinding="@{viewModel.itemBinding}"
|
||||||
|
items="@{item.items}"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingTop="@dimen/margin_generic_half"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
tools:listitem="@layout/item_hide_process" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
|
</layout>
|
51
app/src/main/res/layout/item_hide_process.xml
Normal file
51
app/src/main/res/layout/item_hide_process.xml
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<variable
|
||||||
|
name="item"
|
||||||
|
type="com.topjohnwu.magisk.model.entity.recycler.HideProcessRvItem" />
|
||||||
|
|
||||||
|
<variable
|
||||||
|
name="viewModel"
|
||||||
|
type="com.topjohnwu.magisk.ui.hide.HideViewModel" />
|
||||||
|
|
||||||
|
</data>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:onClick="@{() -> item.toggle()}">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatTextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="@dimen/margin_generic"
|
||||||
|
android:ellipsize="marquee"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:text="@{item.process}"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:textColor="@android:color/tertiary_text_dark"
|
||||||
|
android:textIsSelectable="false"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/hide_process_icon"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:text="com.topjohnwu.magisk.process" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
|
android:id="@+id/hide_process_icon"
|
||||||
|
style="@style/Widget.Icon"
|
||||||
|
isChecked="@{item.isHidden}"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:tint="?attr/imageColorTint"
|
||||||
|
tools:src="@drawable/ic_checked" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</layout>
|
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
|||||||
#Tue Mar 26 00:03:20 EDT 2019
|
#Fri Apr 19 09:51:32 CEST 2019
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.3-rc-2-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-5.3.1-all.zip
|
||||||
|
Loading…
x
Reference in New Issue
Block a user