New MagiskHide UI
This commit is contained in:
parent
f2f4649ab0
commit
217564963d
@ -29,6 +29,8 @@ dependencies {
|
|||||||
implementation 'com.caverock:androidsvg-aar:1.3'
|
implementation 'com.caverock:androidsvg-aar:1.3'
|
||||||
implementation 'org.kamranzafar:jtar:2.3'
|
implementation 'org.kamranzafar:jtar:2.3'
|
||||||
implementation 'net.sourceforge.streamsupport:android-retrostreams:1.7.0'
|
implementation 'net.sourceforge.streamsupport:android-retrostreams:1.7.0'
|
||||||
|
implementation 'me.drakeet.multitype:multitype:3.5.0'
|
||||||
|
implementation 'com.github.sevar83:indeterminate-checkbox:1.0.5'
|
||||||
|
|
||||||
def androidXVersion = "1.0.0"
|
def androidXVersion = "1.0.0"
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||||
|
@ -0,0 +1,170 @@
|
|||||||
|
package com.topjohnwu.magisk.adapters;
|
||||||
|
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.buildware.widget.indeterm.IndeterminateCheckBox;
|
||||||
|
import com.topjohnwu.magisk.R;
|
||||||
|
import com.topjohnwu.superuser.Shell;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import java9.util.Comparators;
|
||||||
|
import me.drakeet.multitype.ItemViewBinder;
|
||||||
|
|
||||||
|
public class AppViewBinder extends ItemViewBinder<AppViewBinder.App, AppViewBinder.ViewHolder> {
|
||||||
|
|
||||||
|
private final List<Object> items;
|
||||||
|
|
||||||
|
AppViewBinder(List<Object> items) {
|
||||||
|
this.items = items;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected @NonNull
|
||||||
|
ViewHolder onCreateViewHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) {
|
||||||
|
return new ViewHolder(inflater.inflate(R.layout.list_item_hide_app, parent, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onBindViewHolder(@NonNull ViewHolder holder, @NonNull App app) {
|
||||||
|
IndeterminateCheckBox.OnStateChangedListener listener =
|
||||||
|
(IndeterminateCheckBox indeterminateCheckBox, @Nullable Boolean stat) -> {
|
||||||
|
if (stat != null && stat) {
|
||||||
|
for (ProcessViewBinder.Process process : app.processes) {
|
||||||
|
Shell.su("magiskhide --add " + process.fullname).submit();
|
||||||
|
process.hidden = true;
|
||||||
|
}
|
||||||
|
} else if (stat != null) {
|
||||||
|
for (ProcessViewBinder.Process process : app.processes) {
|
||||||
|
Shell.su("magiskhide --rm " + process.fullname).submit();
|
||||||
|
process.hidden = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
app.getStatBool(true);
|
||||||
|
};
|
||||||
|
holder.app_name.setText(app.name);
|
||||||
|
holder.app_icon.setImageDrawable(app.icon);
|
||||||
|
holder.package_name.setText(app.packageName);
|
||||||
|
holder.checkBox.setOnStateChangedListener(null);
|
||||||
|
holder.checkBox.setState(app.getStatBool(false));
|
||||||
|
holder.checkBox.setOnStateChangedListener(listener);
|
||||||
|
if (app.expand) {
|
||||||
|
holder.checkBox.setVisibility(View.GONE);
|
||||||
|
setBottomMargin(holder.itemView, 0);
|
||||||
|
} else {
|
||||||
|
holder.checkBox.setVisibility(View.VISIBLE);
|
||||||
|
setBottomMargin(holder.itemView, 2);
|
||||||
|
}
|
||||||
|
holder.itemView.setOnClickListener((v) -> {
|
||||||
|
int index = getPosition(holder);
|
||||||
|
if (app.expand) {
|
||||||
|
app.expand = false;
|
||||||
|
items.removeAll(app.processes);
|
||||||
|
getAdapter().notifyItemRangeRemoved(index + 1, app.processes.size());
|
||||||
|
setBottomMargin(holder.itemView, 2);
|
||||||
|
holder.checkBox.setOnStateChangedListener(null);
|
||||||
|
holder.checkBox.setVisibility(View.VISIBLE);
|
||||||
|
holder.checkBox.setState(app.getStatBool(true));
|
||||||
|
holder.checkBox.setOnStateChangedListener(listener);
|
||||||
|
} else {
|
||||||
|
holder.checkBox.setVisibility(View.GONE);
|
||||||
|
setBottomMargin(holder.itemView, 0);
|
||||||
|
app.expand = true;
|
||||||
|
items.addAll(index + 1, app.processes);
|
||||||
|
getAdapter().notifyItemRangeInserted(index + 1, app.processes.size());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setBottomMargin(View view, int dp) {
|
||||||
|
ViewGroup.LayoutParams params = view.getLayoutParams();
|
||||||
|
ViewGroup.MarginLayoutParams marginParams;
|
||||||
|
if (params instanceof ViewGroup.MarginLayoutParams) {
|
||||||
|
marginParams = (ViewGroup.MarginLayoutParams) params;
|
||||||
|
} else {
|
||||||
|
marginParams = new ViewGroup.MarginLayoutParams(params);
|
||||||
|
}
|
||||||
|
int px = (int) (0.5f + dp * Resources.getSystem().getDisplayMetrics().density);
|
||||||
|
marginParams.bottomMargin = px;
|
||||||
|
view.setLayoutParams(marginParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
|
@BindView(R.id.app_icon) ImageView app_icon;
|
||||||
|
@BindView(R.id.app_name) TextView app_name;
|
||||||
|
@BindView(R.id.package_name) TextView package_name;
|
||||||
|
@BindView(R.id.checkbox) IndeterminateCheckBox checkBox;
|
||||||
|
|
||||||
|
ViewHolder(View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
new AppViewBinder$ViewHolder_ViewBinding(this, itemView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class App implements Comparable<App> {
|
||||||
|
Drawable icon;
|
||||||
|
String name;
|
||||||
|
String packageName;
|
||||||
|
int flags;
|
||||||
|
List<ProcessViewBinder.Process> processes;
|
||||||
|
boolean expand = false;
|
||||||
|
int stat = -1;
|
||||||
|
|
||||||
|
App(Drawable icon, String name, String packageName, int flags) {
|
||||||
|
this.icon = icon;
|
||||||
|
this.name = name;
|
||||||
|
this.packageName = packageName;
|
||||||
|
this.flags=flags;
|
||||||
|
this.processes = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
int getStat(boolean update) {
|
||||||
|
if (stat > 1 && !update) return stat;
|
||||||
|
int n = 0;
|
||||||
|
for (ProcessViewBinder.Process process : processes) {
|
||||||
|
if (process.hidden) n++;
|
||||||
|
}
|
||||||
|
if (n == processes.size()) stat = 2;
|
||||||
|
else if (n > 0) stat = 1;
|
||||||
|
else stat = 0;
|
||||||
|
return stat;
|
||||||
|
}
|
||||||
|
|
||||||
|
Boolean getStatBool(boolean update) {
|
||||||
|
int stat = getStat(update);
|
||||||
|
switch (stat) {
|
||||||
|
case 2:
|
||||||
|
return true;
|
||||||
|
case 1:
|
||||||
|
return null;
|
||||||
|
case 0:
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(App o) {
|
||||||
|
Comparator<App> c;
|
||||||
|
c = Comparators.comparing((App t) -> t.stat);
|
||||||
|
c = Comparators.reversed(c);
|
||||||
|
c = Comparators.thenComparing(c, t -> t.name, String::compareToIgnoreCase);
|
||||||
|
return c.compare(this, o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,70 +6,64 @@ import android.content.pm.ComponentInfo;
|
|||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.PackageInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.widget.CheckBox;
|
|
||||||
import android.widget.ImageView;
|
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.WorkerThread;
|
import androidx.annotation.WorkerThread;
|
||||||
import androidx.collection.ArraySet;
|
import androidx.collection.ArraySet;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
|
||||||
|
|
||||||
import com.topjohnwu.magisk.App;
|
import com.topjohnwu.magisk.App;
|
||||||
import com.topjohnwu.magisk.Config;
|
import com.topjohnwu.magisk.Config;
|
||||||
import com.topjohnwu.magisk.R;
|
|
||||||
import com.topjohnwu.magisk.utils.Topic;
|
import com.topjohnwu.magisk.utils.Topic;
|
||||||
import com.topjohnwu.magisk.utils.Utils;
|
|
||||||
import com.topjohnwu.superuser.Shell;
|
import com.topjohnwu.superuser.Shell;
|
||||||
import com.topjohnwu.superuser.internal.UiThreadHandler;
|
import com.topjohnwu.superuser.internal.UiThreadHandler;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import butterknife.BindView;
|
|
||||||
import java9.util.Comparators;
|
|
||||||
import java9.util.stream.Collectors;
|
import java9.util.stream.Collectors;
|
||||||
import java9.util.stream.Stream;
|
import java9.util.stream.Stream;
|
||||||
import java9.util.stream.StreamSupport;
|
import java9.util.stream.StreamSupport;
|
||||||
|
import me.drakeet.multitype.MultiTypeAdapter;
|
||||||
|
|
||||||
public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.ViewHolder> {
|
import static com.topjohnwu.magisk.utils.Utils.getAppLabel;
|
||||||
|
|
||||||
|
|
||||||
|
public class ApplicationAdapter {
|
||||||
|
|
||||||
/* A list of apps that should not be shown as hide-able */
|
/* A list of apps that should not be shown as hide-able */
|
||||||
private static final List<String> HIDE_BLACKLIST = Arrays.asList(
|
private static final List<String> HIDE_BLACKLIST = Arrays.asList(
|
||||||
App.self.getPackageName(),
|
App.self.getPackageName(),
|
||||||
"android",
|
"android",
|
||||||
"com.android.chrome",
|
"com.android.chrome",
|
||||||
|
"com.chrome.beta",
|
||||||
|
"com.chrome.dev",
|
||||||
|
"com.chrome.canary",
|
||||||
|
"com.android.webview",
|
||||||
"com.google.android.webview"
|
"com.google.android.webview"
|
||||||
);
|
);
|
||||||
private static final String SAFETYNET_PROCESS = "com.google.android.gms.unstable";
|
private static final String SAFETYNET_PROCESS = "com.google.android.gms.unstable";
|
||||||
private static final String GMS_PACKAGE = "com.google.android.gms";
|
private static final String GMS_PACKAGE = "com.google.android.gms";
|
||||||
|
|
||||||
private List<HideAppInfo> fullList, showList;
|
private List<AppViewBinder.App> fullList;
|
||||||
private List<HideTarget> hideList;
|
private List<HideTarget> hideList;
|
||||||
|
private List<Object> showList;
|
||||||
|
private MultiTypeAdapter adapter;
|
||||||
private PackageManager pm;
|
private PackageManager pm;
|
||||||
private boolean showSystem;
|
private boolean showSystem;
|
||||||
|
|
||||||
public ApplicationAdapter(Context context) {
|
public ApplicationAdapter(Context context, MultiTypeAdapter adapter) {
|
||||||
showList = Collections.emptyList();
|
|
||||||
hideList = Collections.emptyList();
|
hideList = Collections.emptyList();
|
||||||
fullList = new ArrayList<>();
|
fullList = new ArrayList<>();
|
||||||
|
showList = new ArrayList<>();
|
||||||
|
this.adapter = adapter;
|
||||||
|
this.adapter.setItems(showList);
|
||||||
pm = context.getPackageManager();
|
pm = context.getPackageManager();
|
||||||
showSystem = Config.get(Config.Key.SHOW_SYSTEM_APP);
|
showSystem = Config.get(Config.Key.SHOW_SYSTEM_APP);
|
||||||
AsyncTask.SERIAL_EXECUTOR.execute(this::loadApps);
|
AsyncTask.SERIAL_EXECUTOR.execute(this::loadApps);
|
||||||
}
|
adapter.register(AppViewBinder.App.class, new AppViewBinder(showList));
|
||||||
|
adapter.register(ProcessViewBinder.Process.class, new ProcessViewBinder());
|
||||||
@NonNull
|
|
||||||
@Override
|
|
||||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
|
||||||
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_app, parent, false);
|
|
||||||
return new ViewHolder(v);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addProcesses(Set<String> set, ComponentInfo[] infos) {
|
private void addProcesses(Set<String> set, ComponentInfo[] infos) {
|
||||||
@ -109,6 +103,10 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
|||||||
for (ApplicationInfo info : installed) {
|
for (ApplicationInfo info : installed) {
|
||||||
// Do not show black-listed and disabled apps
|
// Do not show black-listed and disabled apps
|
||||||
if (!HIDE_BLACKLIST.contains(info.packageName) && info.enabled) {
|
if (!HIDE_BLACKLIST.contains(info.packageName) && info.enabled) {
|
||||||
|
AppViewBinder.App app = new AppViewBinder.App(info.loadIcon(pm),
|
||||||
|
getAppLabel(info, pm), info.packageName, info.flags);
|
||||||
|
fullList.add(app);
|
||||||
|
|
||||||
Set<String> set = new ArraySet<>();
|
Set<String> set = new ArraySet<>();
|
||||||
PackageInfo pkg = getPackageInfo(info.packageName);
|
PackageInfo pkg = getPackageInfo(info.packageName);
|
||||||
if (pkg != null) {
|
if (pkg != null) {
|
||||||
@ -119,9 +117,17 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
|||||||
} else {
|
} else {
|
||||||
set.add(info.packageName);
|
set.add(info.packageName);
|
||||||
}
|
}
|
||||||
fullList.addAll(StreamSupport.stream(set)
|
if (set.isEmpty()) fullList.remove(app);
|
||||||
.map(process -> new HideAppInfo(info, process))
|
for (String proc : set) {
|
||||||
.collect(Collectors.toList()));
|
boolean hidden = false;
|
||||||
|
for (HideTarget tgt : hideList) {
|
||||||
|
if (info.packageName.equals(tgt.pkg) && proc.equals(tgt.process))
|
||||||
|
hidden = true;
|
||||||
|
}
|
||||||
|
app.processes.add(new ProcessViewBinder.Process(proc, hidden, info.packageName));
|
||||||
|
}
|
||||||
|
app.getStat(true);
|
||||||
|
Collections.sort(app.processes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,122 +139,47 @@ public class ApplicationAdapter extends RecyclerView.Adapter<ApplicationAdapter.
|
|||||||
showSystem = b;
|
showSystem = b;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
|
||||||
HideAppInfo target = showList.get(position);
|
|
||||||
|
|
||||||
holder.appIcon.setImageDrawable(target.info.loadIcon(pm));
|
|
||||||
holder.appName.setText(target.name);
|
|
||||||
holder.process.setText(target.process);
|
|
||||||
if (!target.info.packageName.equals(target.process)) {
|
|
||||||
holder.appPackage.setVisibility(View.VISIBLE);
|
|
||||||
holder.appPackage.setText("(" + target.info.packageName + ")");
|
|
||||||
} else {
|
|
||||||
holder.appPackage.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
holder.checkBox.setOnCheckedChangeListener(null);
|
|
||||||
holder.checkBox.setChecked(target.hidden);
|
|
||||||
if (target.process.equals(SAFETYNET_PROCESS)) {
|
|
||||||
// Do not allow user to not hide SafetyNet
|
|
||||||
holder.checkBox.setOnCheckedChangeListener((v, c) -> holder.checkBox.setChecked(true));
|
|
||||||
} else {
|
|
||||||
holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> {
|
|
||||||
String pair = Utils.fmt("%s %s", target.info.packageName, target.process);
|
|
||||||
if (isChecked) {
|
|
||||||
Shell.su("magiskhide --add " + pair).submit();
|
|
||||||
target.hidden = true;
|
|
||||||
} else {
|
|
||||||
Shell.su("magiskhide --rm " + pair).submit();
|
|
||||||
target.hidden = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemCount() {
|
|
||||||
return showList.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
// True if not system app and have launch intent, or user already hidden it
|
// True if not system app and have launch intent, or user already hidden it
|
||||||
private boolean systemFilter(HideAppInfo target) {
|
private boolean systemFilter(AppViewBinder.App target) {
|
||||||
return showSystem || target.hidden ||
|
return showSystem || target.stat != 0 ||
|
||||||
((target.info.flags & ApplicationInfo.FLAG_SYSTEM) == 0 &&
|
((target.flags & ApplicationInfo.FLAG_SYSTEM) == 0 &&
|
||||||
pm.getLaunchIntentForPackage(target.info.packageName) != null);
|
pm.getLaunchIntentForPackage(target.packageName) != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean contains(String s, String filter) {
|
private boolean contains(String s, String filter) {
|
||||||
return s.toLowerCase().contains(filter);
|
return s.toLowerCase().contains(filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean nameFilter(HideAppInfo target, String filter) {
|
private boolean nameFilter(AppViewBinder.App target, String filter) {
|
||||||
if (filter == null || filter.isEmpty())
|
if (filter == null || filter.isEmpty())
|
||||||
return true;
|
return true;
|
||||||
filter = filter.toLowerCase();
|
filter = filter.toLowerCase();
|
||||||
return contains(target.name, filter) ||
|
return contains(target.name, filter) ||
|
||||||
contains(target.process, filter) ||
|
contains(target.packageName, filter);
|
||||||
contains(target.info.packageName, filter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void filter(String constraint) {
|
public void filter(String constraint) {
|
||||||
AsyncTask.SERIAL_EXECUTOR.execute(() -> {
|
AsyncTask.SERIAL_EXECUTOR.execute(() -> {
|
||||||
Stream<HideAppInfo> s = StreamSupport.stream(fullList)
|
Stream<AppViewBinder.App> s = StreamSupport.stream(fullList)
|
||||||
.filter(this::systemFilter)
|
.filter(this::systemFilter)
|
||||||
.filter(t -> nameFilter(t, constraint));
|
.filter(t -> nameFilter(t, constraint));
|
||||||
UiThreadHandler.run(() -> {
|
UiThreadHandler.run(() -> {
|
||||||
showList = s.collect(Collectors.toList());
|
showList.clear();
|
||||||
notifyDataSetChanged();
|
for (AppViewBinder.App target : s.collect(Collectors.toList())) {
|
||||||
|
if (target.expand) {
|
||||||
|
showList.add(target);
|
||||||
|
showList.addAll(target.processes);
|
||||||
|
} else showList.add(target);
|
||||||
|
}
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void refresh() {
|
public void refresh() {
|
||||||
AsyncTask.SERIAL_EXECUTOR.execute(this::loadApps);
|
Collections.sort(fullList);
|
||||||
}
|
Topic.publish(false, Topic.MAGISK_HIDE_DONE);
|
||||||
|
|
||||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
|
||||||
|
|
||||||
@BindView(R.id.app_icon) ImageView appIcon;
|
|
||||||
@BindView(R.id.app_name) TextView appName;
|
|
||||||
@BindView(R.id.process) TextView process;
|
|
||||||
@BindView(R.id.package_name) TextView appPackage;
|
|
||||||
@BindView(R.id.checkbox) CheckBox checkBox;
|
|
||||||
|
|
||||||
ViewHolder(View itemView) {
|
|
||||||
super(itemView);
|
|
||||||
new ApplicationAdapter$ViewHolder_ViewBinding(this, itemView);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class HideAppInfo implements Comparable<HideAppInfo> {
|
|
||||||
String process;
|
|
||||||
String name;
|
|
||||||
ApplicationInfo info;
|
|
||||||
boolean hidden;
|
|
||||||
|
|
||||||
HideAppInfo(ApplicationInfo info, String process) {
|
|
||||||
this.process = process;
|
|
||||||
this.info = info;
|
|
||||||
name = Utils.getAppLabel(info, pm);
|
|
||||||
for (HideTarget tgt : hideList) {
|
|
||||||
if (tgt.process.equals(process)) {
|
|
||||||
hidden = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compareTo(HideAppInfo o) {
|
|
||||||
Comparator<HideAppInfo> c;
|
|
||||||
c = Comparators.comparing((HideAppInfo t) -> t.hidden);
|
|
||||||
c = Comparators.reversed(c);
|
|
||||||
c = Comparators.thenComparing(c, t -> t.name, String::compareToIgnoreCase);
|
|
||||||
c = Comparators.thenComparing(c, t -> t.info.packageName);
|
|
||||||
c = Comparators.thenComparing(c, t -> t.process);
|
|
||||||
return c.compare(this, o);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class HideTarget {
|
class HideTarget {
|
||||||
|
@ -0,0 +1,80 @@
|
|||||||
|
package com.topjohnwu.magisk.adapters;
|
||||||
|
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.CheckBox;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.topjohnwu.magisk.R;
|
||||||
|
import com.topjohnwu.magisk.utils.Utils;
|
||||||
|
import com.topjohnwu.superuser.Shell;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
import butterknife.BindView;
|
||||||
|
import java9.util.Comparators;
|
||||||
|
import me.drakeet.multitype.ItemViewBinder;
|
||||||
|
|
||||||
|
public class ProcessViewBinder extends ItemViewBinder<ProcessViewBinder.Process, ProcessViewBinder.ViewHolder> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected @NonNull
|
||||||
|
ViewHolder onCreateViewHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) {
|
||||||
|
return new ViewHolder(inflater.inflate(R.layout.list_item_hide_process, parent, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onBindViewHolder(@NonNull ViewHolder holder, @NonNull Process process) {
|
||||||
|
holder.process.setText(process.name);
|
||||||
|
holder.checkbox.setOnCheckedChangeListener(null);
|
||||||
|
holder.checkbox.setChecked(process.hidden);
|
||||||
|
holder.checkbox.setOnCheckedChangeListener((v, isChecked) -> {
|
||||||
|
if (isChecked) {
|
||||||
|
Shell.su("magiskhide --add " + process.fullname).submit();
|
||||||
|
process.hidden = true;
|
||||||
|
} else {
|
||||||
|
Shell.su("magiskhide --rm " + process.fullname).submit();
|
||||||
|
process.hidden = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
|
@BindView(R.id.process) TextView process;
|
||||||
|
@BindView(R.id.checkbox) CheckBox checkbox;
|
||||||
|
|
||||||
|
ViewHolder(View itemView) {
|
||||||
|
super(itemView);
|
||||||
|
new ProcessViewBinder$ViewHolder_ViewBinding(this, itemView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Process implements Comparable<Process> {
|
||||||
|
String name;
|
||||||
|
boolean hidden;
|
||||||
|
String fullname;
|
||||||
|
String packageName;
|
||||||
|
|
||||||
|
Process(String name, boolean hidden, String packageName) {
|
||||||
|
this.name = name;
|
||||||
|
this.hidden = hidden;
|
||||||
|
this.packageName=packageName;
|
||||||
|
this.fullname = Utils.fmt("%s %s", packageName, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(Process o) {
|
||||||
|
Comparator<Process> c;
|
||||||
|
c = Comparators.comparing((Process t) -> !t.name.startsWith(t.packageName));
|
||||||
|
c = Comparators.thenComparing(c, t -> t.name, String::compareToIgnoreCase);
|
||||||
|
return c.compare(this, o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -21,6 +21,7 @@ import com.topjohnwu.magisk.components.BaseFragment;
|
|||||||
import com.topjohnwu.magisk.utils.Topic;
|
import com.topjohnwu.magisk.utils.Topic;
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
|
import me.drakeet.multitype.MultiTypeAdapter;
|
||||||
|
|
||||||
public class MagiskHideFragment extends BaseFragment implements Topic.Subscriber {
|
public class MagiskHideFragment extends BaseFragment implements Topic.Subscriber {
|
||||||
|
|
||||||
@ -28,7 +29,7 @@ public class MagiskHideFragment extends BaseFragment implements Topic.Subscriber
|
|||||||
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
||||||
|
|
||||||
private SearchView search;
|
private SearchView search;
|
||||||
private ApplicationAdapter adapter;
|
private ApplicationAdapter applicationAdapter;
|
||||||
private SearchView.OnQueryTextListener searchListener;
|
private SearchView.OnQueryTextListener searchListener;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -43,22 +44,23 @@ public class MagiskHideFragment extends BaseFragment implements Topic.Subscriber
|
|||||||
View view = inflater.inflate(R.layout.fragment_magisk_hide, container, false);
|
View view = inflater.inflate(R.layout.fragment_magisk_hide, container, false);
|
||||||
unbinder = new MagiskHideFragment_ViewBinding(this, view);
|
unbinder = new MagiskHideFragment_ViewBinding(this, view);
|
||||||
|
|
||||||
adapter = new ApplicationAdapter(requireActivity());
|
MultiTypeAdapter adapter = new MultiTypeAdapter();
|
||||||
|
applicationAdapter = new ApplicationAdapter(requireActivity(), adapter);
|
||||||
recyclerView.setAdapter(adapter);
|
recyclerView.setAdapter(adapter);
|
||||||
|
|
||||||
mSwipeRefreshLayout.setRefreshing(true);
|
mSwipeRefreshLayout.setRefreshing(true);
|
||||||
mSwipeRefreshLayout.setOnRefreshListener(adapter::refresh);
|
mSwipeRefreshLayout.setOnRefreshListener(applicationAdapter::refresh);
|
||||||
|
|
||||||
searchListener = new SearchView.OnQueryTextListener() {
|
searchListener = new SearchView.OnQueryTextListener() {
|
||||||
@Override
|
@Override
|
||||||
public boolean onQueryTextSubmit(String query) {
|
public boolean onQueryTextSubmit(String query) {
|
||||||
adapter.filter(query);
|
applicationAdapter.filter(query);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onQueryTextChange(String newText) {
|
public boolean onQueryTextChange(String newText) {
|
||||||
adapter.filter(newText);
|
applicationAdapter.filter(newText);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -82,8 +84,8 @@ public class MagiskHideFragment extends BaseFragment implements Topic.Subscriber
|
|||||||
boolean showSystem = !item.isChecked();
|
boolean showSystem = !item.isChecked();
|
||||||
item.setChecked(showSystem);
|
item.setChecked(showSystem);
|
||||||
Config.set(Config.Key.SHOW_SYSTEM_APP, showSystem);
|
Config.set(Config.Key.SHOW_SYSTEM_APP, showSystem);
|
||||||
adapter.setShowSystem(showSystem);
|
applicationAdapter.setShowSystem(showSystem);
|
||||||
adapter.filter(search.getQuery().toString());
|
applicationAdapter.filter(search.getQuery().toString());
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -96,6 +98,6 @@ public class MagiskHideFragment extends BaseFragment implements Topic.Subscriber
|
|||||||
@Override
|
@Override
|
||||||
public void onPublish(int topic, Object[] result) {
|
public void onPublish(int topic, Object[] result) {
|
||||||
mSwipeRefreshLayout.setRefreshing(false);
|
mSwipeRefreshLayout.setRefreshing(false);
|
||||||
adapter.filter(search.getQuery().toString());
|
applicationAdapter.filter(search.getQuery().toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.cardview.widget.CardView
|
||||||
|
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"
|
||||||
style="?attr/cardStyle"
|
style="?attr/cardStyle"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
@ -33,7 +34,6 @@
|
|||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/app_name"
|
android:id="@+id/app_name"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
@ -42,30 +42,15 @@
|
|||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||||
android:textIsSelectable="false"
|
android:textIsSelectable="false"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/process"
|
app:layout_constraintBottom_toTopOf="@+id/package_name"
|
||||||
app:layout_constraintEnd_toStartOf="@+id/checkbox"
|
app:layout_constraintEnd_toStartOf="@+id/checkbox"
|
||||||
app:layout_constraintStart_toEndOf="@+id/app_icon"
|
app:layout_constraintStart_toEndOf="@+id/app_icon"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/process"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
|
||||||
android:textColor="@android:color/tertiary_text_dark"
|
|
||||||
android:textIsSelectable="false"
|
|
||||||
app:layout_constraintBottom_toTopOf="@+id/package_name"
|
|
||||||
app:layout_constraintEnd_toEndOf="@+id/app_name"
|
|
||||||
app:layout_constraintStart_toStartOf="@+id/app_name"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/app_name" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/package_name"
|
android:id="@+id/package_name"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginBottom="2dp"
|
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
@ -74,22 +59,19 @@
|
|||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="@+id/app_name"
|
app:layout_constraintEnd_toEndOf="@+id/app_name"
|
||||||
app:layout_constraintStart_toStartOf="@+id/app_name"
|
app:layout_constraintStart_toStartOf="@+id/app_name"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/process" />
|
app:layout_constraintTop_toBottomOf="@+id/app_name" />
|
||||||
|
|
||||||
|
<com.buildware.widget.indeterm.IndeterminateCheckBox
|
||||||
<CheckBox
|
|
||||||
android:id="@+id/checkbox"
|
android:id="@+id/checkbox"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="5dp"
|
android:layout_marginStart="5dp"
|
||||||
android:layout_marginEnd="5dp"
|
android:layout_marginEnd="5dp"
|
||||||
android:checked="false"
|
|
||||||
android:focusable="false"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toEndOf="@+id/app_name"
|
app:layout_constraintStart_toEndOf="@+id/app_name"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:srcCompat="@drawable/ic_menu_overflow_material" />
|
/>
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
52
app/src/main/res/layout/list_item_hide_process.xml
Normal file
52
app/src/main/res/layout/list_item_hide_process.xml
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/cardView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:layout_marginStart="@dimen/card_horizontal_margin"
|
||||||
|
android:layout_marginTop="0dp"
|
||||||
|
android:layout_marginEnd="@dimen/card_horizontal_margin"
|
||||||
|
android:layout_marginBottom="0dp"
|
||||||
|
app:cardCornerRadius="@dimen/card_corner_radius"
|
||||||
|
app:cardElevation="@dimen/card_elevation">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/info_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/process"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:textColor="@android:color/tertiary_text_dark"
|
||||||
|
android:textIsSelectable="false"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintVertical_bias="0.506" />
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/checkbox"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="5dp"
|
||||||
|
android:layout_marginEnd="5dp"
|
||||||
|
android:checked="false"
|
||||||
|
android:focusable="false"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user