You've already forked Magisk
mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-09-06 06:36:58 +00:00
It seems that even adding this to the list doesn't 100% works on all devices out there, and some even reported crashes on several Google services. Disable it for now and do further investigations in the future.
428 lines
15 KiB
Java
428 lines
15 KiB
Java
package com.topjohnwu.magisk.adapters;
|
|
|
|
import android.content.Context;
|
|
import android.content.pm.ApplicationInfo;
|
|
import android.content.pm.ComponentInfo;
|
|
import android.content.pm.PackageInfo;
|
|
import android.content.pm.PackageManager;
|
|
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.Nullable;
|
|
import androidx.annotation.WorkerThread;
|
|
import androidx.collection.ArraySet;
|
|
import androidx.recyclerview.widget.RecyclerView;
|
|
|
|
import com.buildware.widget.indeterm.IndeterminateCheckBox;
|
|
import com.topjohnwu.magisk.App;
|
|
import com.topjohnwu.magisk.Config;
|
|
import com.topjohnwu.magisk.R;
|
|
import com.topjohnwu.magisk.uicomponents.ArrowExpandable;
|
|
import com.topjohnwu.magisk.uicomponents.Expandable;
|
|
import com.topjohnwu.magisk.utils.Event;
|
|
import com.topjohnwu.magisk.utils.Utils;
|
|
import com.topjohnwu.superuser.Shell;
|
|
import com.topjohnwu.superuser.internal.UiThreadHandler;
|
|
|
|
import java.util.Collections;
|
|
import java.util.Comparator;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
|
|
import butterknife.BindView;
|
|
import java9.util.Comparators;
|
|
import java9.util.Lists;
|
|
import java9.util.Objects;
|
|
import java9.util.Sets;
|
|
import java9.util.stream.Collectors;
|
|
import java9.util.stream.Stream;
|
|
import java9.util.stream.StreamSupport;
|
|
|
|
public class ApplicationAdapter extends SectionedAdapter
|
|
<ApplicationAdapter.AppViewHolder, ApplicationAdapter.ProcessViewHolder> {
|
|
|
|
private static final String SAFETYNET_PROCESS = "com.google.android.gms.unstable";
|
|
private static final String GMS_PACKAGE = "com.google.android.gms";
|
|
private static boolean old_hide = false;
|
|
|
|
/* A list of apps that should not be shown as hide-able */
|
|
private static final List<String> HIDE_BLACKLIST = Lists.of(
|
|
App.self.getPackageName(),
|
|
"android",
|
|
"com.android.chrome",
|
|
"com.chrome.beta",
|
|
"com.chrome.dev",
|
|
"com.chrome.canary",
|
|
"com.android.webview",
|
|
"com.google.android.webview"
|
|
);
|
|
private static final List<String> DEFAULT_HIDELIST = Lists.of(
|
|
SAFETYNET_PROCESS
|
|
);
|
|
|
|
private static int BOTTOM_MARGIN = -1;
|
|
|
|
private List<HideAppInfo> fullList, showList;
|
|
private List<HideTarget> hideList;
|
|
private PackageManager pm;
|
|
private boolean showSystem;
|
|
|
|
public ApplicationAdapter(Context context) {
|
|
fullList = showList = Collections.emptyList();
|
|
hideList = Collections.emptyList();
|
|
pm = context.getPackageManager();
|
|
showSystem = Config.get(Config.Key.SHOW_SYSTEM_APP);
|
|
AsyncTask.SERIAL_EXECUTOR.execute(this::loadApps);
|
|
}
|
|
|
|
private static ViewGroup.MarginLayoutParams getMargins(RecyclerView.ViewHolder vh) {
|
|
return (ViewGroup.MarginLayoutParams) vh.itemView.getLayoutParams();
|
|
}
|
|
|
|
@Override
|
|
public int getSectionCount() {
|
|
return showList.size();
|
|
}
|
|
|
|
@Override
|
|
public int getItemCount(int section) {
|
|
HideAppInfo app = showList.get(section);
|
|
return app.expanded ? app.processList.size() : 0;
|
|
}
|
|
|
|
@Override
|
|
public AppViewHolder onCreateSectionViewHolder(ViewGroup parent) {
|
|
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_hide_app, parent, false);
|
|
AppViewHolder vh = new AppViewHolder(v);
|
|
if (BOTTOM_MARGIN < 0)
|
|
BOTTOM_MARGIN = getMargins(vh).bottomMargin;
|
|
return vh;
|
|
}
|
|
|
|
@Override
|
|
public ProcessViewHolder onCreateItemViewHolder(ViewGroup parent, int viewType) {
|
|
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_hide_process, parent, false);
|
|
return new ProcessViewHolder(v);
|
|
}
|
|
|
|
@Override
|
|
public void onBindSectionViewHolder(AppViewHolder holder, int section) {
|
|
HideAppInfo app = showList.get(section);
|
|
holder.app_name.setText(app.name);
|
|
holder.app_icon.setImageDrawable(app.info.loadIcon(pm));
|
|
holder.package_name.setText(app.info.packageName);
|
|
holder.checkBox.setOnStateChangedListener(null);
|
|
holder.checkBox.setState(app.getState());
|
|
holder.ex.setExpanded(app.expanded);
|
|
|
|
int index = getItemPosition(section, 0);
|
|
holder.checkBox.setOnStateChangedListener((IndeterminateCheckBox box, @Nullable Boolean status) -> {
|
|
if (status != null) {
|
|
setHide(status, app);
|
|
if (app.expanded)
|
|
notifyItemRangeChanged(index, app.processList.size());
|
|
}
|
|
});
|
|
|
|
if (app.processList.size() > 1) {
|
|
holder.arrow.setVisibility(View.VISIBLE);
|
|
|
|
holder.trigger.setOnClickListener((v) -> {
|
|
if (app.expanded) {
|
|
app.expanded = false;
|
|
notifyItemRangeRemoved(index, app.processList.size());
|
|
holder.ex.collapse();
|
|
} else {
|
|
app.expanded = true;
|
|
notifyItemRangeInserted(index, app.processList.size());
|
|
holder.ex.expand();
|
|
}
|
|
});
|
|
} else {
|
|
holder.arrow.setVisibility(View.GONE);
|
|
holder.trigger.setOnClickListener(null);
|
|
}
|
|
|
|
}
|
|
|
|
@Override
|
|
public void onBindItemViewHolder(ProcessViewHolder holder, int section, int position) {
|
|
HideAppInfo app = showList.get(section);
|
|
HideProcessInfo target = app.processList.get(position);
|
|
holder.process.setText(target.name);
|
|
holder.checkbox.setOnCheckedChangeListener(null);
|
|
holder.checkbox.setChecked(target.hidden);
|
|
holder.checkbox.setOnCheckedChangeListener((v, checked) -> {
|
|
setHide(checked, app, target);
|
|
notifyItemChanged(getSectionPosition(section));
|
|
});
|
|
getMargins(holder).bottomMargin =
|
|
position == app.processList.size() - 1 ? BOTTOM_MARGIN : 0;
|
|
}
|
|
|
|
public void filter(String constraint) {
|
|
AsyncTask.SERIAL_EXECUTOR.execute(() -> {
|
|
Stream<HideAppInfo> s = StreamSupport.stream(fullList)
|
|
.filter(this::systemFilter)
|
|
.filter(t -> nameFilter(t, constraint));
|
|
UiThreadHandler.run(() -> {
|
|
showList = s.collect(Collectors.toList());
|
|
notifyDataSetChanged();
|
|
});
|
|
});
|
|
}
|
|
|
|
public void setShowSystem(boolean b) {
|
|
showSystem = b;
|
|
}
|
|
|
|
public void refresh() {
|
|
AsyncTask.SERIAL_EXECUTOR.execute(this::loadApps);
|
|
}
|
|
|
|
private void setHide(boolean add, HideAppInfo app) {
|
|
if (add) {
|
|
StreamSupport.stream(app.processList).forEach(p -> setHide(true, app, p));
|
|
} else {
|
|
if (StreamSupport.stream(app.processList)
|
|
.anyMatch(p -> p.name.equals(SAFETYNET_PROCESS))) {
|
|
StreamSupport.stream(app.processList).forEach(p -> setHide(false, app, p));
|
|
} else {
|
|
// Quick removal
|
|
Shell.su("magiskhide --rm " + app.info.packageName).submit();
|
|
StreamSupport.stream(app.processList).forEach(p -> p.hidden = false);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void setHide(boolean add, HideAppInfo app, HideProcessInfo process) {
|
|
// Don't remove SafetyNet
|
|
if (!add && DEFAULT_HIDELIST.contains(process.name))
|
|
return;
|
|
Shell.su(Utils.fmt("magiskhide --%s %s %s", add ? "add" : "rm",
|
|
app.info.packageName, process.name)).submit();
|
|
process.hidden = add;
|
|
}
|
|
|
|
private void addProcesses(Set<String> set, ComponentInfo[] infos) {
|
|
if (infos != null)
|
|
for (ComponentInfo info : infos)
|
|
set.add(info.processName);
|
|
}
|
|
|
|
private PackageInfo getPackageInfo(String pkg) {
|
|
// Try super hard to get as much info as possible
|
|
try {
|
|
return pm.getPackageInfo(pkg,
|
|
PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES |
|
|
PackageManager.GET_RECEIVERS | PackageManager.GET_PROVIDERS);
|
|
} catch (Exception e1) {
|
|
try {
|
|
PackageInfo info = pm.getPackageInfo(pkg, PackageManager.GET_ACTIVITIES);
|
|
info.services = pm.getPackageInfo(pkg, PackageManager.GET_SERVICES).services;
|
|
info.receivers = pm.getPackageInfo(pkg, PackageManager.GET_RECEIVERS).receivers;
|
|
info.providers = pm.getPackageInfo(pkg, PackageManager.GET_PROVIDERS).providers;
|
|
return info;
|
|
} catch (Exception e2) {
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
@WorkerThread
|
|
private void loadApps() {
|
|
hideList = StreamSupport.stream(Shell.su("magiskhide --ls").exec().getOut())
|
|
.map(HideTarget::new)
|
|
.collect(Collectors.toList());
|
|
|
|
fullList = StreamSupport.stream(pm.getInstalledApplications(0))
|
|
.filter(info -> !HIDE_BLACKLIST.contains(info.packageName) && info.enabled)
|
|
.map(info -> {
|
|
if (old_hide) {
|
|
return new HideAppInfo(info, Sets.of(info.packageName));
|
|
} else {
|
|
Set<String> set = new ArraySet<>();
|
|
PackageInfo pkg = getPackageInfo(info.packageName);
|
|
if (pkg != null) {
|
|
addProcesses(set, pkg.activities);
|
|
addProcesses(set, pkg.services);
|
|
addProcesses(set, pkg.receivers);
|
|
addProcesses(set, pkg.providers);
|
|
}
|
|
if (set.isEmpty())
|
|
return null;
|
|
return new HideAppInfo(info, set);
|
|
}
|
|
}).filter(Objects::nonNull).sorted()
|
|
.collect(Collectors.toList());
|
|
|
|
Event.trigger(false, Event.MAGISK_HIDE_DONE);
|
|
}
|
|
|
|
// True if not system app or user already hidden it
|
|
private boolean systemFilter(HideAppInfo target) {
|
|
return showSystem || target.haveHidden() ||
|
|
(target.info.flags & ApplicationInfo.FLAG_SYSTEM) == 0;
|
|
}
|
|
|
|
private boolean contains(String s, String filter) {
|
|
return s.toLowerCase().contains(filter);
|
|
}
|
|
|
|
private boolean nameFilter(HideAppInfo target, String filter) {
|
|
if (filter == null || filter.isEmpty())
|
|
return true;
|
|
filter = filter.toLowerCase();
|
|
if (contains(target.name, filter))
|
|
return true;
|
|
for (HideProcessInfo p : target.processList) {
|
|
if (contains(p.name, filter))
|
|
return true;
|
|
}
|
|
return contains(target.info.packageName, filter);
|
|
}
|
|
|
|
class HideAppInfo implements Comparable<HideAppInfo> {
|
|
String name;
|
|
ApplicationInfo info;
|
|
List<HideProcessInfo> processList;
|
|
boolean expanded;
|
|
IndeterminateCheckBox.OnStateChangedListener listener;
|
|
|
|
HideAppInfo(ApplicationInfo appInfo, Set<String> set) {
|
|
info = appInfo;
|
|
name = Utils.getAppLabel(info, pm);
|
|
expanded = false;
|
|
processList = StreamSupport.stream(set)
|
|
.map(process -> new HideProcessInfo(info.packageName, process))
|
|
.sorted().collect(Collectors.toList());
|
|
listener = (IndeterminateCheckBox box, @Nullable Boolean status) -> {
|
|
if (status != null) {
|
|
for (HideProcessInfo p : processList) {
|
|
String cmd = Utils.fmt("magiskhide --%s %s %s",
|
|
status ? "add" : "rm", info.packageName, p.name);
|
|
Shell.su(cmd).submit();
|
|
p.hidden = status;
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
@Override
|
|
public int compareTo(HideAppInfo o) {
|
|
Comparator<HideAppInfo> c;
|
|
c = Comparators.comparing(HideAppInfo::haveHidden);
|
|
c = Comparators.reversed(c);
|
|
c = Comparators.thenComparing(c, t -> t.name, String::compareToIgnoreCase);
|
|
c = Comparators.thenComparing(c, t -> t.info.packageName);
|
|
return c.compare(this, o);
|
|
}
|
|
|
|
Boolean getState() {
|
|
boolean all = true;
|
|
boolean hidden = false;
|
|
for (HideProcessInfo p : processList) {
|
|
if (!p.hidden)
|
|
all = false;
|
|
else
|
|
hidden = true;
|
|
}
|
|
if (all)
|
|
return true;
|
|
return hidden ? null : false;
|
|
}
|
|
|
|
boolean haveHidden() {
|
|
Boolean c = getState();
|
|
return c == null ? true : c;
|
|
}
|
|
}
|
|
|
|
class HideProcessInfo implements Comparable<HideProcessInfo> {
|
|
String name;
|
|
boolean hidden;
|
|
|
|
HideProcessInfo(String pkg, String process) {
|
|
this.name = process;
|
|
for (HideTarget t : hideList) {
|
|
if (t.pkg.equals(pkg) && t.process.equals(process)) {
|
|
hidden = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int compareTo(HideProcessInfo o) {
|
|
Comparator<HideProcessInfo> c;
|
|
c = Comparators.comparing((HideProcessInfo t) -> t.hidden);
|
|
c = Comparators.reversed(c);
|
|
c = Comparators.thenComparing(c, t -> t.name);
|
|
return c.compare(this, o);
|
|
}
|
|
}
|
|
|
|
class HideTarget {
|
|
String pkg;
|
|
String process;
|
|
|
|
HideTarget(String line) {
|
|
String[] split = line.split("\\|", 2);
|
|
pkg = split[0];
|
|
if (split.length == 2) {
|
|
process = split[1];
|
|
} else {
|
|
// Backwards compatibility
|
|
old_hide = true;
|
|
process = pkg;
|
|
}
|
|
}
|
|
}
|
|
|
|
class AppViewHolder 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;
|
|
@BindView(R.id.trigger) View trigger;
|
|
@BindView(R.id.arrow) ImageView arrow;
|
|
|
|
Expandable ex;
|
|
|
|
AppViewHolder(@NonNull View itemView) {
|
|
super(itemView);
|
|
new ApplicationAdapter$AppViewHolder_ViewBinding(this, itemView);
|
|
ex = new ArrowExpandable(new Expandable() {
|
|
@Override
|
|
protected void onExpand() {
|
|
getMargins(AppViewHolder.this).bottomMargin = 0;
|
|
}
|
|
|
|
@Override
|
|
protected void onCollapse() {
|
|
getMargins(AppViewHolder.this).bottomMargin = BOTTOM_MARGIN;
|
|
}
|
|
}, arrow);
|
|
}
|
|
}
|
|
|
|
class ProcessViewHolder extends RecyclerView.ViewHolder {
|
|
|
|
@BindView(R.id.process) TextView process;
|
|
@BindView(R.id.checkbox) CheckBox checkbox;
|
|
|
|
ProcessViewHolder(@NonNull View itemView) {
|
|
super(itemView);
|
|
new ApplicationAdapter$ProcessViewHolder_ViewBinding(this, itemView);
|
|
}
|
|
}
|
|
|
|
}
|