Add Superuser logging UI

This commit is contained in:
topjohnwu
2017-01-28 01:10:50 +08:00
parent ca9334b2df
commit cbb32f82eb
23 changed files with 594 additions and 71 deletions

View File

@ -3,7 +3,6 @@ package com.topjohnwu.magisk;
import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import com.topjohnwu.magisk.module.Module;
import com.topjohnwu.magisk.module.Repo;

View File

@ -2,17 +2,14 @@ package com.topjohnwu.magisk;
import android.app.Fragment;
import android.app.FragmentManager;
import android.os.Bundle;
import android.support.design.widget.TabLayout;
import android.support.v13.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
import java.util.List;
import com.topjohnwu.magisk.adapters.TabFragmentAdapter;
import butterknife.BindView;
import butterknife.ButterKnife;
@ -25,7 +22,6 @@ public class LogFragment extends Fragment {
@BindView(R.id.container) ViewPager viewPager;
@BindView(R.id.tab) TabLayout tab;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
@ -33,7 +29,7 @@ public class LogFragment extends Fragment {
View v = inflater.inflate(R.layout.fragment_log, container, false);
unbinder = ButterKnife.bind(this, v);
ViewPagerAdapter adapter = new ViewPagerAdapter(getChildFragmentManager());
TabFragmentAdapter adapter = new TabFragmentAdapter(getChildFragmentManager());
adapter.addTab(new MagiskLogFragment(), getString(R.string.magisk));
@ -54,38 +50,4 @@ public class LogFragment extends Fragment {
unbinder.unbind();
}
private class ViewPagerAdapter extends FragmentPagerAdapter {
List<Fragment> fragmentList;
List<String> titleList;
public ViewPagerAdapter(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);
}
}
}

View File

@ -41,6 +41,7 @@ public class MainActivity extends AppCompatActivity
@BindView(R.id.nav_view) public NavigationView navigationView;
private int mSelectedId = R.id.status;
private float toolbarElevation;
@Override
protected void onCreate(final Bundle savedInstanceState) {
@ -74,6 +75,8 @@ public class MainActivity extends AppCompatActivity
}
};
toolbarElevation = toolbar.getElevation();
drawer.addDrawerListener(toggle);
toggle.syncState();
@ -158,6 +161,7 @@ public class MainActivity extends AppCompatActivity
}
public void navigate(int itemId, boolean now) {
toolbar.setElevation(toolbarElevation);
switch (itemId) {
case R.id.status:
displayFragment(new StatusFragment(), "status", now);
@ -179,6 +183,7 @@ public class MainActivity extends AppCompatActivity
break;
case R.id.log:
displayFragment(new LogFragment(), "log", now);
toolbar.setElevation(0);
break;
case R.id.settings:
startActivity(new Intent(this, SettingsActivity.class));

View File

@ -35,7 +35,7 @@ public class ModulesFragment extends Fragment implements CallbackHandler.EventLi
private Unbinder unbinder;
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
@BindView(R.id.recyclerView) RecyclerView recyclerView;
@BindView(R.id.empty_rv) TextView emptyTv;
@BindView(R.id.empty_rv) TextView emptyRv;
@BindView(R.id.fab) FloatingActionButton fabio;
private List<Module> listModules = new ArrayList<>();
@ -114,10 +114,10 @@ public class ModulesFragment extends Fragment implements CallbackHandler.EventLi
private void updateUI() {
ModuleHelper.getModuleList(listModules);
if (listModules.size() == 0) {
emptyTv.setVisibility(View.VISIBLE);
emptyRv.setVisibility(View.VISIBLE);
recyclerView.setVisibility(View.GONE);
} else {
emptyTv.setVisibility(View.GONE);
emptyRv.setVisibility(View.GONE);
recyclerView.setVisibility(View.VISIBLE);
recyclerView.setAdapter(new ModulesAdapter(listModules));
}

View File

@ -33,7 +33,7 @@ public class ReposFragment extends Fragment implements CallbackHandler.EventList
private Unbinder unbinder;
@BindView(R.id.recyclerView) RecyclerView recyclerView;
@BindView(R.id.empty_rv) TextView emptyTv;
@BindView(R.id.empty_rv) TextView emptyRv;
@BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout mSwipeRefreshLayout;
private List<Repo> mUpdateRepos = new ArrayList<>();
@ -138,7 +138,7 @@ public class ReposFragment extends Fragment implements CallbackHandler.EventList
private void updateUI() {
if (fUpdateRepos.size() + fInstalledRepos.size() + fOthersRepos.size() == 0) {
emptyTv.setVisibility(View.VISIBLE);
emptyRv.setVisibility(View.VISIBLE);
recyclerView.setVisibility(View.GONE);
} else {
List<SimpleSectionedRecyclerViewAdapter.Section> sections = new ArrayList<>();
@ -153,7 +153,7 @@ public class ReposFragment extends Fragment implements CallbackHandler.EventList
}
SimpleSectionedRecyclerViewAdapter.Section[] array = sections.toArray(new SimpleSectionedRecyclerViewAdapter.Section[sections.size()]);
mSectionedAdapter.setSections(array);
emptyTv.setVisibility(View.GONE);
emptyRv.setVisibility(View.GONE);
recyclerView.setVisibility(View.VISIBLE);
}
mSwipeRefreshLayout.setRefreshing(false);

View File

@ -3,7 +3,6 @@ package com.topjohnwu.magisk;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;

View File

@ -21,8 +21,6 @@ import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Utils;
import java.util.List;
import butterknife.BindColor;
import butterknife.BindView;
import butterknife.ButterKnife;

View File

@ -3,18 +3,54 @@ package com.topjohnwu.magisk;
import android.app.Fragment;
import android.os.Bundle;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.topjohnwu.magisk.adapters.SuLogAdapter;
import com.topjohnwu.magisk.superuser.SuLogDatabaseHelper;
import com.topjohnwu.magisk.superuser.SuLogEntry;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.Unbinder;
public class SuLogFragment extends Fragment {
@BindView(R.id.empty_rv) TextView emptyRv;
@BindView(R.id.recyclerView) RecyclerView recyclerView;
private Unbinder unbinder;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_su_log, container, false);
View v = inflater.inflate(R.layout.fragment_su_log, container, false);
unbinder = ButterKnife.bind(this, v);
SuLogDatabaseHelper dbHelper = new SuLogDatabaseHelper(getActivity());
List<SuLogEntry> logs = dbHelper.getLogList();
if (logs.size() == 0) {
emptyRv.setVisibility(View.VISIBLE);
recyclerView.setVisibility(View.GONE);
} else {
recyclerView.setAdapter(new SuLogAdapter(logs).getAdapter());
emptyRv.setVisibility(View.GONE);
recyclerView.setVisibility(View.VISIBLE);
}
return v;
}
@Override
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
}

View File

@ -25,7 +25,7 @@ public class SuperuserFragment extends Fragment {
private Unbinder unbinder;
@BindView(R.id.recyclerView) RecyclerView recyclerView;
@BindView(R.id.empty_rv) TextView emptyTv;
@BindView(R.id.empty_rv) TextView emptyRv;
@Nullable
@Override
@ -38,14 +38,12 @@ public class SuperuserFragment extends Fragment {
SuDatabaseHelper dbHelper = new SuDatabaseHelper(getActivity());
List<Policy> policyList = dbHelper.getPolicyList(pm);
PolicyAdapter adapter = new PolicyAdapter(policyList, dbHelper, pm);
if (policyList.size() == 0) {
emptyTv.setVisibility(View.VISIBLE);
emptyRv.setVisibility(View.VISIBLE);
recyclerView.setVisibility(View.GONE);
} else {
recyclerView.setAdapter(adapter);
emptyTv.setVisibility(View.GONE);
recyclerView.setAdapter(new PolicyAdapter(policyList, dbHelper, pm));
emptyRv.setVisibility(View.GONE);
recyclerView.setVisibility(View.VISIBLE);
}

View File

@ -111,6 +111,9 @@ public class PolicyAdapter extends RecyclerView.Adapter<PolicyAdapter.ViewHolder
holder.notificationSwitch.setChecked(policy.notification);
holder.loggingSwitch.setChecked(policy.logging);
// Hide for now
holder.moreInfo.setVisibility(View.GONE);
} catch (PackageManager.NameNotFoundException e) {
policyList.remove(position);
dbHelper.deletePolicy(policy.uid);

View File

@ -0,0 +1,238 @@
package com.topjohnwu.magisk.adapters;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.thoughtbot.expandablerecyclerview.ExpandableRecyclerViewAdapter;
import com.thoughtbot.expandablerecyclerview.models.ExpandableGroup;
import com.thoughtbot.expandablerecyclerview.viewholders.ChildViewHolder;
import com.thoughtbot.expandablerecyclerview.viewholders.GroupViewHolder;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.superuser.SuLogEntry;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
public class SuLogAdapter {
private ExpandableAdapter adapter;
private HashSet<SuLogEntry> expandList = new HashSet<>();
public SuLogAdapter(List<SuLogEntry> list) {
// Separate the logs with date
LinkedHashMap<String, ArrayList<SuLogEntry>> logEntryMap = new LinkedHashMap<>();
ArrayList<SuLogEntry> group;
for (SuLogEntry log : list) {
String date = log.getDateString();
group = logEntryMap.get(date);
if (group == null) {
group = new ArrayList<>();
logEntryMap.put(date, group);
}
group.add(log);
}
// Then format them into expandable groups
ArrayList<LogGroup> logEntryGroups = new ArrayList<>();
for (HashMap.Entry<String, ArrayList<SuLogEntry>> entry : logEntryMap.entrySet()) {
logEntryGroups.add(new LogGroup(entry.getKey(), entry.getValue()));
}
adapter = new ExpandableAdapter(logEntryGroups);
}
public RecyclerView.Adapter getAdapter() {
return adapter;
}
private class ExpandableAdapter
extends ExpandableRecyclerViewAdapter<LogGroupViewHolder, LogViewHolder> {
ExpandableAdapter(List<? extends ExpandableGroup> groups) {
super(groups);
expandableList.expandedGroupIndexes[0] = true;
}
@Override
public LogGroupViewHolder onCreateGroupViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_sulog_group, parent, false);
return new LogGroupViewHolder(v);
}
@Override
public LogViewHolder onCreateChildViewHolder(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 onBindChildViewHolder(LogViewHolder holder, int flatPosition, ExpandableGroup group, int childIndex) {
Context context = holder.itemView.getContext();
SuLogEntry logEntry = (SuLogEntry) group.getItems().get(childIndex);
holder.setExpanded(expandList.contains(logEntry));
holder.itemView.setOnClickListener(view -> {
if (holder.mExpanded) {
holder.collapse();
expandList.remove(logEntry);
} else {
holder.expand();
expandList.add(logEntry);
}
});
holder.appName.setText(logEntry.appName);
holder.action.setText(logEntry.action ? context.getString(R.string.grant) : context.getString(R.string.deny, ""));
holder.command.setText(logEntry.command);
holder.fromPid.setText(String.valueOf(logEntry.fromPid));
holder.toUid.setText(String.valueOf(logEntry.toUid));
holder.time.setText(logEntry.getTimeString());
}
@Override
public void onBindGroupViewHolder(LogGroupViewHolder holder, int flatPosition, ExpandableGroup group) {
holder.date.setText(group.getTitle());
}
}
private class LogGroup extends ExpandableGroup<SuLogEntry> {
LogGroup(String title, List<SuLogEntry> items) {
super(title, items);
}
}
static class LogGroupViewHolder extends GroupViewHolder {
@BindView(R.id.date) TextView date;
@BindView(R.id.arrow) ImageView arrow;
public LogGroupViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
@Override
public void expand() {
RotateAnimation rotate =
new RotateAnimation(360, 180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
rotate.setDuration(300);
rotate.setFillAfter(true);
arrow.setAnimation(rotate);
}
@Override
public void collapse() {
RotateAnimation rotate =
new RotateAnimation(180, 360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
rotate.setDuration(300);
rotate.setFillAfter(true);
arrow.setAnimation(rotate);
}
}
static class LogViewHolder extends ChildViewHolder {
@BindView(R.id.app_name) TextView appName;
@BindView(R.id.action) TextView action;
@BindView(R.id.time) TextView time;
@BindView(R.id.fromPid) TextView fromPid;
@BindView(R.id.toUid) TextView toUid;
@BindView(R.id.command) TextView command;
@BindView(R.id.expand_layout) LinearLayout expandLayout;
private ValueAnimator mAnimator;
private boolean mExpanded = false;
private static int expandHeight = 0;
public LogViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
expandLayout.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
if (expandHeight == 0) {
final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
final int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
expandLayout.measure(widthSpec, heightSpec);
expandHeight = expandLayout.getMeasuredHeight();
}
expandLayout.getViewTreeObserver().removeOnPreDrawListener(this);
expandLayout.setVisibility(View.GONE);
mAnimator = slideAnimator(0, expandHeight);
return true;
}
});
}
private void setExpanded(boolean expanded) {
mExpanded = expanded;
ViewGroup.LayoutParams layoutParams = expandLayout.getLayoutParams();
layoutParams.height = expanded ? expandHeight : 0;
expandLayout.setLayoutParams(layoutParams);
expandLayout.setVisibility(expanded ? View.VISIBLE : View.GONE);
}
private void expand() {
expandLayout.setVisibility(View.VISIBLE);
mAnimator.start();
mExpanded = true;
}
private void collapse() {
if (!mExpanded) return;
int finalHeight = expandLayout.getHeight();
ValueAnimator mAnimator = slideAnimator(finalHeight, 0);
mAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationEnd(Animator animator) {
expandLayout.setVisibility(View.GONE);
}
@Override
public void onAnimationStart(Animator animator) {}
@Override
public void onAnimationCancel(Animator animator) {}
@Override
public void onAnimationRepeat(Animator animator) {}
});
mAnimator.start();
mExpanded = false;
}
private ValueAnimator slideAnimator(int start, int end) {
ValueAnimator animator = ValueAnimator.ofInt(start, end);
animator.addUpdateListener(valueAnimator -> {
int value = (Integer) valueAnimator.getAnimatedValue();
ViewGroup.LayoutParams layoutParams = expandLayout.getLayoutParams();
layoutParams.height = value;
expandLayout.setLayoutParams(layoutParams);
});
return animator;
}
}
}

View File

@ -0,0 +1,40 @@
package com.topjohnwu.magisk.adapters;
import android.app.Fragment;
import android.app.FragmentManager;
import android.support.v13.app.FragmentPagerAdapter;
import java.util.ArrayList;
import java.util.List;
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);
}
}

View File

@ -2,13 +2,20 @@ package com.topjohnwu.magisk.superuser;
import android.content.ContentValues;
import android.database.Cursor;
import android.os.Parcel;
import android.os.Parcelable;
public class SuLogEntry {
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class SuLogEntry implements Parcelable {
public int fromUid, toUid, fromPid;
public String packageName, appName, command;
public boolean action;
public long time;
public Date date;
public SuLogEntry(Policy policy) {
fromUid = policy.uid;
@ -24,7 +31,7 @@ public class SuLogEntry {
appName = c.getString(c.getColumnIndex("app_name"));
command = c.getString(c.getColumnIndex("command"));
action = c.getInt(c.getColumnIndex("action")) != 0;
time = c.getLong(c.getColumnIndex("until"));
date = new Date(c.getLong(c.getColumnIndex("time")) * 1000);
}
public ContentValues getContentValues() {
@ -35,8 +42,57 @@ public class SuLogEntry {
values.put("from_pid", fromPid);
values.put("command", command);
values.put("to_uid", toUid);
values.put("action", action);
values.put("time", time);
values.put("action", action ? 1 : 0);
values.put("time", date.getTime() / 1000);
return values;
}
public String getDateString() {
return DateFormat.getDateInstance(DateFormat.MEDIUM).format(date);
}
public String getTimeString() {
return new SimpleDateFormat("h:mm a", Locale.US).format(date);
}
public static final Creator<SuLogEntry> CREATOR = new Creator<SuLogEntry>() {
@Override
public SuLogEntry createFromParcel(Parcel in) {
return new SuLogEntry(in);
}
@Override
public SuLogEntry[] newArray(int size) {
return new SuLogEntry[size];
}
};
protected SuLogEntry(Parcel in) {
fromUid = in.readInt();
toUid = in.readInt();
fromPid = in.readInt();
packageName = in.readString();
appName = in.readString();
command = in.readString();
action = in.readByte() != 0;
date = new Date(in.readLong());
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(fromUid);
dest.writeInt(toUid);
dest.writeInt(fromPid);
dest.writeString(packageName);
dest.writeString(appName);
dest.writeString(command);
dest.writeByte((byte) (action ? 1 : 0));
dest.writeLong(date.getTime());
}
}

View File

@ -8,6 +8,8 @@ import android.widget.Toast;
import com.topjohnwu.magisk.R;
import java.util.Date;
public class SuReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
@ -32,14 +34,18 @@ public class SuReceiver extends BroadcastReceiver {
return;
}
SuLogEntry log = new SuLogEntry(policy);
if (policy.notification) {
String message;
switch (action) {
case "allow":
message = context.getString(R.string.su_allow_toast, policy.appName);
log.action = true;
break;
case "deny":
message = context.getString(R.string.su_deny_toast, policy.appName);
log.action = false;
break;
default:
return;
@ -53,10 +59,10 @@ public class SuReceiver extends BroadcastReceiver {
if (pid < 0) return;
command = intent.getStringExtra("command");
if (command == null) return;
SuLogEntry log = new SuLogEntry(policy);
log.toUid = toUid;
log.fromPid = pid;
log.command = command;
log.date = new Date();
SuLogDatabaseHelper logDbHelper = new SuLogDatabaseHelper(context);
logDbHelper.addLog(log);
}

View File

@ -16,8 +16,6 @@ import com.topjohnwu.magisk.Global;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.receivers.DownloadReceiver;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.util.List;