Cleaned up unnecessary classes
This commit is contained in:
parent
aceb3ee863
commit
6df7006b36
@ -1,426 +0,0 @@
|
||||
package com.topjohnwu.magisk.model.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 com.buildware.widget.indeterm.IndeterminateCheckBox;
|
||||
import com.topjohnwu.magisk.App;
|
||||
import com.topjohnwu.magisk.Config;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.utils.Event;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.view.ArrowExpandable;
|
||||
import com.topjohnwu.magisk.view.Expandable;
|
||||
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 androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.collection.ArraySet;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,126 +0,0 @@
|
||||
package com.topjohnwu.magisk.model.adapters;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
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 com.google.android.material.snackbar.Snackbar;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.model.entity.Module;
|
||||
import com.topjohnwu.magisk.view.SnackbarMaker;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import butterknife.BindView;
|
||||
|
||||
public class ModulesAdapter extends RecyclerView.Adapter<ModulesAdapter.ViewHolder> {
|
||||
|
||||
private final List<Module> mList;
|
||||
|
||||
public ModulesAdapter(List<Module> list) {
|
||||
mList = list;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_module, parent, false);
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(final ViewHolder holder, int position) {
|
||||
Context context = holder.itemView.getContext();
|
||||
final Module module = mList.get(position);
|
||||
|
||||
String version = module.getVersion();
|
||||
String author = module.getAuthor();
|
||||
String description = module.getDescription();
|
||||
String noInfo = context.getString(R.string.no_info_provided);
|
||||
|
||||
holder.title.setText(module.getName());
|
||||
holder.versionName.setText(TextUtils.isEmpty(version) ? noInfo : version);
|
||||
holder.author.setText(TextUtils.isEmpty(author) ? noInfo : context.getString(R.string.author, author));
|
||||
holder.description.setText(TextUtils.isEmpty(description) ? noInfo : description);
|
||||
|
||||
holder.checkBox.setOnCheckedChangeListener(null);
|
||||
holder.checkBox.setChecked(module.isEnabled());
|
||||
holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> {
|
||||
int snack;
|
||||
if (isChecked) {
|
||||
module.removeDisableFile();
|
||||
snack = R.string.disable_file_removed;
|
||||
} else {
|
||||
module.createDisableFile();
|
||||
snack = R.string.disable_file_created;
|
||||
}
|
||||
SnackbarMaker.make(holder.itemView, snack, Snackbar.LENGTH_SHORT).show();
|
||||
});
|
||||
|
||||
holder.delete.setOnClickListener(v -> {
|
||||
boolean removed = module.willBeRemoved();
|
||||
int snack;
|
||||
if (removed) {
|
||||
module.deleteRemoveFile();
|
||||
snack = R.string.remove_file_deleted;
|
||||
} else {
|
||||
module.createRemoveFile();
|
||||
snack = R.string.remove_file_created;
|
||||
}
|
||||
SnackbarMaker.make(holder.itemView, snack, Snackbar.LENGTH_SHORT).show();
|
||||
updateDeleteButton(holder, module);
|
||||
});
|
||||
|
||||
if (module.isUpdated()) {
|
||||
holder.notice.setVisibility(View.VISIBLE);
|
||||
holder.notice.setText(R.string.update_file_created);
|
||||
holder.delete.setEnabled(false);
|
||||
} else {
|
||||
updateDeleteButton(holder, module);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateDeleteButton(ViewHolder holder, Module module) {
|
||||
holder.notice.setVisibility(module.willBeRemoved() ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (module.willBeRemoved()) {
|
||||
holder.delete.setImageResource(R.drawable.ic_undelete);
|
||||
} else {
|
||||
holder.delete.setImageResource(R.drawable.ic_delete);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mList.size();
|
||||
}
|
||||
|
||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
@BindView(R.id.title) TextView title;
|
||||
@BindView(R.id.version_name) TextView versionName;
|
||||
@BindView(R.id.description) TextView description;
|
||||
@BindView(R.id.notice) TextView notice;
|
||||
@BindView(R.id.checkbox) CheckBox checkBox;
|
||||
@BindView(R.id.author) TextView author;
|
||||
@BindView(R.id.delete) ImageView delete;
|
||||
|
||||
ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
new ModulesAdapter$ViewHolder_ViewBinding(this, itemView);
|
||||
|
||||
if (!Shell.rootAccess()) {
|
||||
checkBox.setEnabled(false);
|
||||
delete.setEnabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,168 +0,0 @@
|
||||
package com.topjohnwu.magisk.model.adapters;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.data.database.MagiskDB;
|
||||
import com.topjohnwu.magisk.model.entity.Policy;
|
||||
import com.topjohnwu.magisk.utils.FingerprintHelper;
|
||||
import com.topjohnwu.magisk.view.ArrowExpandable;
|
||||
import com.topjohnwu.magisk.view.Expandable;
|
||||
import com.topjohnwu.magisk.view.ExpandableViewHolder;
|
||||
import com.topjohnwu.magisk.view.SnackbarMaker;
|
||||
import com.topjohnwu.magisk.view.dialogs.CustomAlertDialog;
|
||||
import com.topjohnwu.magisk.view.dialogs.FingerprintAuthDialog;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.SwitchCompat;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import butterknife.BindView;
|
||||
|
||||
public class PolicyAdapter extends RecyclerView.Adapter<PolicyAdapter.ViewHolder> {
|
||||
|
||||
private final List<Policy> policyList;
|
||||
private final MagiskDB dbHelper;
|
||||
private final PackageManager pm;
|
||||
private final boolean[] expandList;
|
||||
|
||||
public PolicyAdapter(List<Policy> list, MagiskDB db, PackageManager pm) {
|
||||
policyList = list;
|
||||
expandList = new boolean[policyList.size()];
|
||||
dbHelper = db;
|
||||
this.pm = pm;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_policy, parent, false);
|
||||
return new ViewHolder(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
|
||||
Policy policy = policyList.get(position);
|
||||
|
||||
holder.settings.setExpanded(expandList[position]);
|
||||
holder.trigger.setOnClickListener(view -> {
|
||||
if (holder.settings.isExpanded()) {
|
||||
holder.settings.collapse();
|
||||
expandList[position] = false;
|
||||
} else {
|
||||
holder.settings.expand();
|
||||
expandList[position] = true;
|
||||
}
|
||||
});
|
||||
|
||||
holder.appName.setText(policy.appName);
|
||||
holder.packageName.setText(policy.packageName);
|
||||
holder.appIcon.setImageDrawable(policy.info.loadIcon(pm));
|
||||
|
||||
holder.notificationSwitch.setOnCheckedChangeListener(null);
|
||||
holder.loggingSwitch.setOnCheckedChangeListener(null);
|
||||
|
||||
holder.masterSwitch.setChecked(policy.policy == Policy.ALLOW);
|
||||
holder.notificationSwitch.setChecked(policy.notification);
|
||||
holder.loggingSwitch.setChecked(policy.logging);
|
||||
|
||||
holder.masterSwitch.setOnClickListener(v -> {
|
||||
boolean isChecked = holder.masterSwitch.isChecked();
|
||||
Runnable r = () -> {
|
||||
if (isChecked ? policy.policy == Policy.DENY : policy.policy == Policy.ALLOW) {
|
||||
policy.policy = isChecked ? Policy.ALLOW : Policy.DENY;
|
||||
String message = v.getContext().getString(
|
||||
isChecked ? R.string.su_snack_grant : R.string.su_snack_deny, policy.appName);
|
||||
SnackbarMaker.make(holder.itemView, message, Snackbar.LENGTH_SHORT).show();
|
||||
dbHelper.updatePolicy(policy);
|
||||
}
|
||||
};
|
||||
if (FingerprintHelper.useFingerprint()) {
|
||||
holder.masterSwitch.setChecked(!isChecked);
|
||||
new FingerprintAuthDialog((Activity) v.getContext(), () -> {
|
||||
holder.masterSwitch.setChecked(isChecked);
|
||||
r.run();
|
||||
}).show();
|
||||
} else {
|
||||
r.run();
|
||||
}
|
||||
});
|
||||
holder.notificationSwitch.setOnCheckedChangeListener((v, isChecked) -> {
|
||||
if (isChecked != policy.notification) {
|
||||
policy.notification = isChecked;
|
||||
String message = v.getContext().getString(
|
||||
isChecked ? R.string.su_snack_notif_on : R.string.su_snack_notif_off, policy.appName);
|
||||
SnackbarMaker.make(holder.itemView, message, Snackbar.LENGTH_SHORT).show();
|
||||
dbHelper.updatePolicy(policy);
|
||||
}
|
||||
});
|
||||
holder.loggingSwitch.setOnCheckedChangeListener((v, isChecked) -> {
|
||||
if (isChecked != policy.logging) {
|
||||
policy.logging = isChecked;
|
||||
String message = v.getContext().getString(
|
||||
isChecked ? R.string.su_snack_log_on : R.string.su_snack_log_off, policy.appName);
|
||||
SnackbarMaker.make(holder.itemView, message, Snackbar.LENGTH_SHORT).show();
|
||||
dbHelper.updatePolicy(policy);
|
||||
}
|
||||
});
|
||||
holder.delete.setOnClickListener(v -> {
|
||||
DialogInterface.OnClickListener l = (dialog, which) -> {
|
||||
policyList.remove(position);
|
||||
notifyItemRemoved(position);
|
||||
notifyItemRangeChanged(position, policyList.size());
|
||||
SnackbarMaker.make(holder.itemView, v.getContext().getString(R.string.su_snack_revoke, policy.appName),
|
||||
Snackbar.LENGTH_SHORT).show();
|
||||
dbHelper.deletePolicy(policy);
|
||||
};
|
||||
if (FingerprintHelper.useFingerprint()) {
|
||||
new FingerprintAuthDialog((Activity) v.getContext(),
|
||||
() -> l.onClick(null, 0)).show();
|
||||
} else {
|
||||
new CustomAlertDialog(v.getContext())
|
||||
.setTitle(R.string.su_revoke_title)
|
||||
.setMessage(v.getContext().getString(R.string.su_revoke_msg, policy.appName))
|
||||
.setPositiveButton(R.string.yes, l)
|
||||
.setNegativeButton(R.string.no_thanks, null)
|
||||
.setCancelable(true)
|
||||
.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return policyList.size();
|
||||
}
|
||||
|
||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
@BindView(R.id.app_name) TextView appName;
|
||||
@BindView(R.id.package_name) TextView packageName;
|
||||
@BindView(R.id.app_icon) ImageView appIcon;
|
||||
@BindView(R.id.master_switch) SwitchCompat masterSwitch;
|
||||
@BindView(R.id.notification_switch) SwitchCompat notificationSwitch;
|
||||
@BindView(R.id.logging_switch) SwitchCompat loggingSwitch;
|
||||
@BindView(R.id.expand_layout) ViewGroup expandLayout;
|
||||
@BindView(R.id.arrow) ImageView arrow;
|
||||
@BindView(R.id.trigger) View trigger;
|
||||
@BindView(R.id.delete) ImageView delete;
|
||||
@BindView(R.id.more_info) ImageView moreInfo;
|
||||
|
||||
Expandable settings;
|
||||
|
||||
public ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
new PolicyAdapter$ViewHolder_ViewBinding(this, itemView);
|
||||
settings = new ArrowExpandable(new ExpandableViewHolder(expandLayout), arrow);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,246 +0,0 @@
|
||||
package com.topjohnwu.magisk.model.adapters;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.os.Build;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Pair;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.SearchView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.App;
|
||||
import com.topjohnwu.magisk.ClassMap;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.data.database.RepoDatabaseHelper;
|
||||
import com.topjohnwu.magisk.model.download.DownloadModuleService;
|
||||
import com.topjohnwu.magisk.model.entity.Module;
|
||||
import com.topjohnwu.magisk.model.entity.Repo;
|
||||
import com.topjohnwu.magisk.ui.base.IBaseLeanback;
|
||||
import com.topjohnwu.magisk.ui.base.MagiskActivity;
|
||||
import com.topjohnwu.magisk.utils.Event;
|
||||
import com.topjohnwu.magisk.view.MarkDownWindow;
|
||||
import com.topjohnwu.magisk.view.dialogs.CustomAlertDialog;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import butterknife.BindView;
|
||||
import java9.util.stream.StreamSupport;
|
||||
|
||||
public class ReposAdapter
|
||||
extends SectionedAdapter<ReposAdapter.SectionHolder, ReposAdapter.RepoHolder>
|
||||
implements Event.AutoListener, SearchView.OnQueryTextListener {
|
||||
|
||||
private static final int UPDATES = 0;
|
||||
private static final int INSTALLED = 1;
|
||||
private static final int OTHERS = 2;
|
||||
|
||||
private Map<String, Module> moduleMap;
|
||||
private final RepoDatabaseHelper repoDB;
|
||||
private final List<Pair<Integer, List<Repo>>> repoPairs;
|
||||
private List<Repo> fullList;
|
||||
private SearchView mSearch;
|
||||
|
||||
public ReposAdapter() {
|
||||
repoDB = App.self.getRepoDB();
|
||||
moduleMap = Collections.emptyMap();
|
||||
fullList = Collections.emptyList();
|
||||
repoPairs = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSectionCount() {
|
||||
return repoPairs.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount(int section) {
|
||||
return repoPairs.get(section).second.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SectionHolder onCreateSectionViewHolder(ViewGroup parent) {
|
||||
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.section, parent, false);
|
||||
return new SectionHolder(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RepoHolder onCreateItemViewHolder(ViewGroup parent, int viewType) {
|
||||
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_repo, parent, false);
|
||||
return new RepoHolder(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindSectionViewHolder(SectionHolder holder, int section) {
|
||||
switch (repoPairs.get(section).first) {
|
||||
case UPDATES:
|
||||
holder.sectionText.setText(R.string.update_available);
|
||||
break;
|
||||
case INSTALLED:
|
||||
holder.sectionText.setText(R.string.installed);
|
||||
break;
|
||||
case OTHERS:
|
||||
holder.sectionText.setText(R.string.not_installed);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindItemViewHolder(RepoHolder holder, int section, int position) {
|
||||
Repo repo = repoPairs.get(section).second.get(position);
|
||||
Context context = holder.itemView.getContext();
|
||||
|
||||
String name = repo.getName();
|
||||
String version = repo.getVersion();
|
||||
String author = repo.getAuthor();
|
||||
String description = repo.getDescription();
|
||||
String noInfo = context.getString(R.string.no_info_provided);
|
||||
|
||||
holder.title.setText(TextUtils.isEmpty(name) ? noInfo : name);
|
||||
holder.versionName.setText(TextUtils.isEmpty(version) ? noInfo : version);
|
||||
holder.author.setText(TextUtils.isEmpty(author) ? noInfo : context.getString(R.string.author, author));
|
||||
holder.description.setText(TextUtils.isEmpty(description) ? noInfo : description);
|
||||
holder.updateTime.setText(context.getString(R.string.updated_on, repo.getLastUpdateString()));
|
||||
|
||||
holder.infoLayout.setOnClickListener(v ->
|
||||
MarkDownWindow.show(context, null, repo.getDetailUrl()));
|
||||
|
||||
holder.downloadImage.setOnClickListener(v -> {
|
||||
new CustomAlertDialog(context)
|
||||
.setTitle(context.getString(R.string.repo_install_title, repo.getName()))
|
||||
.setMessage(context.getString(R.string.repo_install_msg, repo.getDownloadFilename()))
|
||||
.setCancelable(true)
|
||||
.setPositiveButton(R.string.install, (d, i) ->
|
||||
startDownload((MagiskActivity) context, repo, true))
|
||||
.setNeutralButton(R.string.download, (d, i) ->
|
||||
startDownload((MagiskActivity) context, repo, false))
|
||||
.setNegativeButton(R.string.no_thanks, null)
|
||||
.show();
|
||||
});
|
||||
}
|
||||
|
||||
private <Ctxt extends Context & IBaseLeanback> void startDownload(Ctxt activity, Repo repo, Boolean install) {
|
||||
activity.runWithExternalRW(() -> {
|
||||
Intent intent = new Intent(activity, ClassMap.get(DownloadModuleService.class))
|
||||
.putExtra("repo", repo).putExtra("install", install);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
activity.startForegroundService(intent);
|
||||
} else {
|
||||
activity.startService(intent);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void updateLists() {
|
||||
if (mSearch != null)
|
||||
onQueryTextChange(mSearch.getQuery().toString());
|
||||
else
|
||||
onQueryTextChange("");
|
||||
}
|
||||
|
||||
private static boolean noCaseContain(String a, String b) {
|
||||
return a.toLowerCase().contains(b.toLowerCase());
|
||||
}
|
||||
|
||||
public void setSearchView(SearchView view) {
|
||||
mSearch = view;
|
||||
mSearch.setOnQueryTextListener(this);
|
||||
}
|
||||
|
||||
public void notifyDBChanged(boolean refresh) {
|
||||
try (Cursor c = repoDB.getRepoCursor()) {
|
||||
fullList = new ArrayList<>(c.getCount());
|
||||
while (c.moveToNext())
|
||||
fullList.add(new Repo(c));
|
||||
}
|
||||
if (refresh)
|
||||
updateLists();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(int event) {
|
||||
moduleMap = Event.getResult(event);
|
||||
updateLists();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getListeningEvents() {
|
||||
return new int[] {Event.MODULE_LOAD_DONE};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String s) {
|
||||
List<Repo> updates = new ArrayList<>();
|
||||
List<Repo> installed = new ArrayList<>();
|
||||
List<Repo> others = new ArrayList<>();
|
||||
|
||||
StreamSupport.stream(fullList)
|
||||
.filter(repo -> noCaseContain(repo.getName(), s)
|
||||
|| noCaseContain(repo.getAuthor(), s)
|
||||
|| noCaseContain(repo.getDescription(), s))
|
||||
.forEach(repo -> {
|
||||
Module module = moduleMap.get(repo.getId());
|
||||
if (module != null) {
|
||||
if (repo.getVersionCode() > module.getVersionCode()) {
|
||||
// Updates
|
||||
updates.add(repo);
|
||||
} else {
|
||||
installed.add(repo);
|
||||
}
|
||||
} else {
|
||||
others.add(repo);
|
||||
}
|
||||
});
|
||||
|
||||
repoPairs.clear();
|
||||
if (!updates.isEmpty())
|
||||
repoPairs.add(new Pair<>(UPDATES, updates));
|
||||
if (!installed.isEmpty())
|
||||
repoPairs.add(new Pair<>(INSTALLED, installed));
|
||||
if (!others.isEmpty())
|
||||
repoPairs.add(new Pair<>(OTHERS, others));
|
||||
|
||||
notifyDataSetChanged();
|
||||
return false;
|
||||
}
|
||||
|
||||
static class SectionHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
@BindView(R.id.section_text) TextView sectionText;
|
||||
|
||||
SectionHolder(View itemView) {
|
||||
super(itemView);
|
||||
new ReposAdapter$SectionHolder_ViewBinding(this, itemView);
|
||||
}
|
||||
}
|
||||
|
||||
static class RepoHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
@BindView(R.id.title) TextView title;
|
||||
@BindView(R.id.version_name) TextView versionName;
|
||||
@BindView(R.id.description) TextView description;
|
||||
@BindView(R.id.author) TextView author;
|
||||
@BindView(R.id.info_layout) View infoLayout;
|
||||
@BindView(R.id.download) ImageView downloadImage;
|
||||
@BindView(R.id.update_time) TextView updateTime;
|
||||
|
||||
RepoHolder(View itemView) {
|
||||
super(itemView);
|
||||
new ReposAdapter$RepoHolder_ViewBinding(this, itemView);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
package com.topjohnwu.magisk.model.adapters;
|
||||
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public abstract class SectionedAdapter<S extends RecyclerView.ViewHolder, C extends RecyclerView.ViewHolder>
|
||||
extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
|
||||
private static final int SECTION_TYPE = Integer.MIN_VALUE;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
final public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
if (viewType == SECTION_TYPE)
|
||||
return onCreateSectionViewHolder(parent);
|
||||
return onCreateItemViewHolder(parent, viewType);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
final public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
||||
PositionInfo info = getPositionInfo(position);
|
||||
if (info.position == -1)
|
||||
onBindSectionViewHolder((S) holder, info.section);
|
||||
else
|
||||
onBindItemViewHolder((C) holder, info.section, info.position);
|
||||
}
|
||||
|
||||
@Override
|
||||
final public int getItemCount() {
|
||||
int size, sec;
|
||||
size = sec = getSectionCount();
|
||||
for (int i = 0; i < sec; ++i){
|
||||
size += getItemCount(i);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override
|
||||
final public int getItemViewType(int position) {
|
||||
PositionInfo info = getPositionInfo(position);
|
||||
if (info.position == -1)
|
||||
return SECTION_TYPE;
|
||||
else
|
||||
return getItemViewType(info.section, info.position);
|
||||
}
|
||||
|
||||
public int getItemViewType(int section, int position) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected int getSectionPosition(int section) {
|
||||
return getItemPosition(section, -1);
|
||||
}
|
||||
|
||||
protected int getItemPosition(int section, int position) {
|
||||
int realPosition = 0;
|
||||
// Previous sections
|
||||
for (int i = 0; i < section; ++i) {
|
||||
realPosition += getItemCount(i) + 1;
|
||||
}
|
||||
// Current section
|
||||
realPosition += position + 1;
|
||||
return realPosition;
|
||||
}
|
||||
|
||||
private PositionInfo getPositionInfo(int position) {
|
||||
int section = 0;
|
||||
while (true) {
|
||||
if (position == 0)
|
||||
return new PositionInfo(section, -1);
|
||||
position -= 1;
|
||||
if (position < getItemCount(section))
|
||||
return new PositionInfo(section, position);
|
||||
position -= getItemCount(section++);
|
||||
}
|
||||
}
|
||||
|
||||
private static class PositionInfo {
|
||||
int section;
|
||||
int position;
|
||||
PositionInfo(int section, int position) {
|
||||
this.section = section;
|
||||
this.position = position;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract int getSectionCount();
|
||||
public abstract int getItemCount(int section);
|
||||
public abstract S onCreateSectionViewHolder(ViewGroup parent);
|
||||
public abstract C onCreateItemViewHolder(ViewGroup parent, int viewType);
|
||||
public abstract void onBindSectionViewHolder(S holder, int section);
|
||||
public abstract void onBindItemViewHolder(C holder, int section, int position);
|
||||
}
|
@ -1,143 +0,0 @@
|
||||
package com.topjohnwu.magisk.model.adapters;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.RotateAnimation;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.data.database.MagiskDB;
|
||||
import com.topjohnwu.magisk.model.entity.SuLogEntry;
|
||||
import com.topjohnwu.magisk.view.ExpandableViewHolder;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import butterknife.BindView;
|
||||
|
||||
public class SuLogAdapter extends SectionedAdapter<SuLogAdapter.SectionHolder, SuLogAdapter.LogViewHolder> {
|
||||
|
||||
private List<List<SuLogEntry>> logEntries;
|
||||
private Set<Integer> itemExpanded, sectionExpanded;
|
||||
private MagiskDB suDB;
|
||||
|
||||
public SuLogAdapter(MagiskDB db) {
|
||||
suDB = db;
|
||||
logEntries = Collections.emptyList();
|
||||
sectionExpanded = new HashSet<>();
|
||||
itemExpanded = new HashSet<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSectionCount() {
|
||||
return logEntries.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount(int section) {
|
||||
return sectionExpanded.contains(section) ? logEntries.get(section).size() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SectionHolder onCreateSectionViewHolder(ViewGroup parent) {
|
||||
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_sulog_group, parent, false);
|
||||
return new SectionHolder(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LogViewHolder onCreateItemViewHolder(ViewGroup parent, int viewType) {
|
||||
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_sulog, parent, false);
|
||||
return new LogViewHolder(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindSectionViewHolder(SectionHolder holder, int section) {
|
||||
SuLogEntry entry = logEntries.get(section).get(0);
|
||||
holder.arrow.setRotation(sectionExpanded.contains(section) ? 180 : 0);
|
||||
holder.itemView.setOnClickListener(v -> {
|
||||
RotateAnimation rotate;
|
||||
if (sectionExpanded.contains(section)) {
|
||||
holder.arrow.setRotation(0);
|
||||
rotate = new RotateAnimation(180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
|
||||
sectionExpanded.remove(section);
|
||||
notifyItemRangeRemoved(getItemPosition(section, 0), logEntries.get(section).size());
|
||||
} else {
|
||||
rotate = new RotateAnimation(0, 180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
|
||||
sectionExpanded.add(section);
|
||||
notifyItemRangeInserted(getItemPosition(section, 0), logEntries.get(section).size());
|
||||
}
|
||||
rotate.setDuration(300);
|
||||
rotate.setFillAfter(true);
|
||||
holder.arrow.setAnimation(rotate);
|
||||
});
|
||||
holder.date.setText(entry.getDateString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindItemViewHolder(LogViewHolder holder, int section, int position) {
|
||||
SuLogEntry entry = logEntries.get(section).get(position);
|
||||
int realIdx = getItemPosition(section, position);
|
||||
holder.expandable.setExpanded(itemExpanded.contains(realIdx));
|
||||
holder.itemView.setOnClickListener(view -> {
|
||||
if (holder.expandable.isExpanded()) {
|
||||
holder.expandable.collapse();
|
||||
itemExpanded.remove(realIdx);
|
||||
} else {
|
||||
holder.expandable.expand();
|
||||
itemExpanded.add(realIdx);
|
||||
}
|
||||
});
|
||||
Context context = holder.itemView.getContext();
|
||||
holder.appName.setText(entry.appName);
|
||||
holder.action.setText(entry.action ? R.string.grant : R.string.deny);
|
||||
holder.pid.setText(context.getString(R.string.pid, entry.fromPid));
|
||||
holder.uid.setText(context.getString(R.string.target_uid, entry.toUid));
|
||||
holder.command.setText(context.getString(R.string.command, entry.command));
|
||||
holder.time.setText(entry.getTimeString());
|
||||
}
|
||||
|
||||
public void notifyDBChanged() {
|
||||
logEntries = suDB.getLogs();
|
||||
itemExpanded.clear();
|
||||
sectionExpanded.clear();
|
||||
sectionExpanded.add(0);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
static class SectionHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
@BindView(R.id.date) TextView date;
|
||||
@BindView(R.id.arrow) ImageView arrow;
|
||||
|
||||
SectionHolder(View itemView) {
|
||||
super(itemView);
|
||||
new SuLogAdapter$SectionHolder_ViewBinding(this, itemView);
|
||||
}
|
||||
}
|
||||
|
||||
static class LogViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
@BindView(R.id.app_name) TextView appName;
|
||||
@BindView(R.id.action) TextView action;
|
||||
@BindView(R.id.time) TextView time;
|
||||
@BindView(R.id.pid) TextView pid;
|
||||
@BindView(R.id.uid) TextView uid;
|
||||
@BindView(R.id.cmd) TextView command;
|
||||
@BindView(R.id.expand_layout) ViewGroup expandLayout;
|
||||
|
||||
ExpandableViewHolder expandable;
|
||||
|
||||
LogViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
new SuLogAdapter$LogViewHolder_ViewBinding(this, itemView);
|
||||
expandable = new ExpandableViewHolder(expandLayout);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
package com.topjohnwu.magisk.model.adapters;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentPagerAdapter;
|
||||
|
||||
public class TabFragmentAdapter extends FragmentPagerAdapter {
|
||||
|
||||
private List<Fragment> fragmentList;
|
||||
private List<String> titleList;
|
||||
|
||||
public TabFragmentAdapter(FragmentManager fm) {
|
||||
super(fm);
|
||||
fragmentList = new ArrayList<>();
|
||||
titleList = new ArrayList<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
return fragmentList.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return fragmentList.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getPageTitle(int position) {
|
||||
return titleList.get(position);
|
||||
}
|
||||
|
||||
public void addTab(Fragment fragment, String title) {
|
||||
fragmentList.add(fragment);
|
||||
titleList.add(title);
|
||||
}
|
||||
}
|
@ -1,156 +0,0 @@
|
||||
package com.topjohnwu.magisk.ui.log;
|
||||
|
||||
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 com.google.android.material.snackbar.Snackbar;
|
||||
import com.topjohnwu.magisk.Const;
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.model.adapters.StringListAdapter;
|
||||
import com.topjohnwu.magisk.ui.base.BaseFragment;
|
||||
import com.topjohnwu.magisk.utils.Utils;
|
||||
import com.topjohnwu.magisk.view.SnackbarMaker;
|
||||
import com.topjohnwu.superuser.Shell;
|
||||
import com.topjohnwu.superuser.internal.NOPList;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import butterknife.BindView;
|
||||
|
||||
public class MagiskLogFragment extends BaseFragment {
|
||||
|
||||
@BindView(R.id.recyclerView) RecyclerView rv;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_magisk_log, container, false);
|
||||
unbinder = new MagiskLogFragment_ViewBinding(this, view);
|
||||
setHasOptionsMenu(true);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
getActivity().setTitle(R.string.log);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
readLogs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.menu_log, menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_refresh:
|
||||
readLogs();
|
||||
return true;
|
||||
case R.id.menu_save:
|
||||
runWithExternalRW(this::saveLogs);
|
||||
return true;
|
||||
case R.id.menu_clear:
|
||||
clearLogs();
|
||||
rv.setAdapter(new MagiskLogAdapter(NOPList.getInstance()));
|
||||
return true;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void readLogs() {
|
||||
Shell.su("tail -n 5000 " + Const.MAGISK_LOG).submit(result -> {
|
||||
rv.setAdapter(new MagiskLogAdapter(result.getOut()));
|
||||
});
|
||||
}
|
||||
|
||||
private void saveLogs() {
|
||||
Calendar now = Calendar.getInstance();
|
||||
String filename = Utils.fmt("magisk_log_%04d%02d%02d_%02d%02d%02d.log",
|
||||
now.get(Calendar.YEAR), now.get(Calendar.MONTH) + 1,
|
||||
now.get(Calendar.DAY_OF_MONTH), now.get(Calendar.HOUR_OF_DAY),
|
||||
now.get(Calendar.MINUTE), now.get(Calendar.SECOND));
|
||||
|
||||
File logFile = new File(Const.EXTERNAL_PATH, filename);
|
||||
try {
|
||||
logFile.createNewFile();
|
||||
} catch (IOException e) {
|
||||
return;
|
||||
}
|
||||
Shell.su("cat " + Const.MAGISK_LOG + " > " + logFile)
|
||||
.submit(result ->
|
||||
SnackbarMaker.make(rv, logFile.getPath(), Snackbar.LENGTH_SHORT).show());
|
||||
}
|
||||
|
||||
private void clearLogs() {
|
||||
Shell.su("echo -n > " + Const.MAGISK_LOG).submit();
|
||||
SnackbarMaker.make(rv, R.string.logs_cleared, Snackbar.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
private class MagiskLogAdapter extends StringListAdapter<MagiskLogAdapter.ViewHolder> {
|
||||
|
||||
MagiskLogAdapter(List<String> list) {
|
||||
super(list);
|
||||
if (mList.isEmpty())
|
||||
mList.add(requireContext().getString(R.string.log_is_empty));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int itemLayoutRes() {
|
||||
return R.layout.list_item_console;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ViewHolder createViewHolder(@NonNull View v) {
|
||||
return new ViewHolder(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onUpdateTextWidth(ViewHolder holder) {
|
||||
super.onUpdateTextWidth(holder);
|
||||
// Find the longest string and update accordingly
|
||||
int max = 0;
|
||||
String maxStr = "";
|
||||
for (String s : mList) {
|
||||
int len = s.length();
|
||||
if (len > max) {
|
||||
max = len;
|
||||
maxStr = s;
|
||||
}
|
||||
}
|
||||
holder.txt.setText(maxStr);
|
||||
super.onUpdateTextWidth(holder);
|
||||
}
|
||||
|
||||
public class ViewHolder extends StringListAdapter.ViewHolder {
|
||||
|
||||
public ViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int textViewResId() {
|
||||
return R.id.txt;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
package com.topjohnwu.magisk.ui.log;
|
||||
|
||||
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.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
import com.topjohnwu.magisk.model.adapters.SuLogAdapter;
|
||||
import com.topjohnwu.magisk.ui.base.BaseFragment;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import butterknife.BindView;
|
||||
|
||||
public class SuLogFragment extends BaseFragment {
|
||||
|
||||
@BindView(R.id.empty_rv) TextView emptyRv;
|
||||
@BindView(R.id.recyclerView) RecyclerView recyclerView;
|
||||
|
||||
private SuLogAdapter adapter;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.menu_log, menu);
|
||||
menu.findItem(R.id.menu_save).setVisible(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
// Inflate the layout for this fragment
|
||||
View v = inflater.inflate(R.layout.fragment_su_log, container, false);
|
||||
unbinder = new SuLogFragment_ViewBinding(this, v);
|
||||
adapter = new SuLogAdapter(app.getDB());
|
||||
recyclerView.setAdapter(adapter);
|
||||
|
||||
updateList();
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
private void updateList() {
|
||||
adapter.notifyDBChanged();
|
||||
|
||||
if (adapter.getSectionCount() == 0) {
|
||||
emptyRv.setVisibility(View.VISIBLE);
|
||||
recyclerView.setVisibility(View.GONE);
|
||||
} else {
|
||||
emptyRv.setVisibility(View.GONE);
|
||||
recyclerView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_refresh:
|
||||
updateList();
|
||||
return true;
|
||||
case R.id.menu_clear:
|
||||
app.getDB().clearLogs();
|
||||
updateList();
|
||||
return true;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user