Rewrite SuLogAdapter

This commit is contained in:
topjohnwu 2017-07-24 00:34:34 +08:00
parent 76e7c5623d
commit 356065d1ee
6 changed files with 144 additions and 206 deletions

View File

@ -53,7 +53,6 @@ dependencies {
implementation 'com.android.support:design:26.0.0-beta2' implementation 'com.android.support:design:26.0.0-beta2'
implementation 'com.android.support:support-v4:26.0.0-beta2' implementation 'com.android.support:support-v4:26.0.0-beta2'
implementation 'com.jakewharton:butterknife:8.7.0' implementation 'com.jakewharton:butterknife:8.7.0'
implementation 'com.thoughtbot:expandablerecyclerview:1.4'
implementation 'us.feras.mdv:markdownview:1.1.0' implementation 'us.feras.mdv:markdownview:1.1.0'
implementation 'org.bouncycastle:bcprov-jdk15on:1.57' implementation 'org.bouncycastle:bcprov-jdk15on:1.57'
implementation 'org.bouncycastle:bcpkix-jdk15on:1.57' implementation 'org.bouncycastle:bcpkix-jdk15on:1.57'

View File

@ -13,9 +13,6 @@ import android.widget.TextView;
import com.topjohnwu.magisk.adapters.SuLogAdapter; import com.topjohnwu.magisk.adapters.SuLogAdapter;
import com.topjohnwu.magisk.components.Fragment; import com.topjohnwu.magisk.components.Fragment;
import com.topjohnwu.magisk.superuser.SuLogEntry;
import java.util.List;
import butterknife.BindView; import butterknife.BindView;
import butterknife.ButterKnife; import butterknife.ButterKnife;
@ -28,6 +25,7 @@ public class SuLogFragment extends Fragment {
private Unbinder unbinder; private Unbinder unbinder;
private MagiskManager magiskManager; private MagiskManager magiskManager;
private SuLogAdapter adapter;
@Override @Override
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
@ -48,6 +46,8 @@ public class SuLogFragment extends Fragment {
View v = 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); unbinder = ButterKnife.bind(this, v);
magiskManager = getApplication(); magiskManager = getApplication();
adapter = new SuLogAdapter(magiskManager.suDB);
recyclerView.setAdapter(adapter);
updateList(); updateList();
@ -55,13 +55,12 @@ public class SuLogFragment extends Fragment {
} }
private void updateList() { private void updateList() {
List<SuLogEntry> logs = magiskManager.suDB.getLogList(); adapter.notifyDBChanged();
if (logs.size() == 0) { if (adapter.getSectionCount() == 0) {
emptyRv.setVisibility(View.VISIBLE); emptyRv.setVisibility(View.VISIBLE);
recyclerView.setVisibility(View.GONE); recyclerView.setVisibility(View.GONE);
} else { } else {
recyclerView.setAdapter(new SuLogAdapter(logs).getAdapter());
emptyRv.setVisibility(View.GONE); emptyRv.setVisibility(View.GONE);
recyclerView.setVisibility(View.VISIBLE); recyclerView.setVisibility(View.VISIBLE);
} }

View File

@ -48,6 +48,21 @@ public abstract class SectionedAdapter<S extends RecyclerView.ViewHolder, C exte
return 0; 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) { private PositionInfo getPositionInfo(int position) {
int section = 0; int section = 0;
while (true) { while (true) {

View File

@ -1,6 +1,6 @@
package com.topjohnwu.magisk.adapters; package com.topjohnwu.magisk.adapters;
import android.content.Context; import android.database.Cursor;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@ -10,147 +10,125 @@ import android.view.animation.RotateAnimation;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; 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.R;
import com.topjohnwu.magisk.components.ExpandableViewHolder; import com.topjohnwu.magisk.components.ExpandableViewHolder;
import com.topjohnwu.magisk.database.SuDatabaseHelper;
import com.topjohnwu.magisk.superuser.SuLogEntry; import com.topjohnwu.magisk.superuser.SuLogEntry;
import java.util.ArrayList; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import butterknife.BindView; import butterknife.BindView;
import butterknife.ButterKnife; import butterknife.ButterKnife;
public class SuLogAdapter { public class SuLogAdapter extends SectionedAdapter<SuLogAdapter.SectionHolder, SuLogAdapter.LogViewHolder> {
private ExpandableAdapter adapter; private List<List<Integer>> logEntryList;
private Set<SuLogEntry> expandList = new HashSet<>(); private Set<Integer> itemExpanded, sectionExpanded;
private SuDatabaseHelper suDB;
private Cursor suLogCursor = null;
public SuLogAdapter(List<SuLogEntry> list) { public SuLogAdapter(SuDatabaseHelper db) {
suDB = db;
// Separate the logs with date logEntryList = Collections.emptyList();
Map<String, List<SuLogEntry>> logEntryMap = new LinkedHashMap<>(); sectionExpanded = new HashSet<>();
List<SuLogEntry> group; itemExpanded = new HashSet<>();
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
List<LogGroup> logEntryGroups = new ArrayList<>();
for (Map.Entry<String, List<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 @Override
public LogGroupViewHolder onCreateGroupViewHolder(ViewGroup parent, int viewType) { public int getSectionCount() {
return logEntryList.size();
}
@Override
public int getItemCount(int section) {
return sectionExpanded.contains(section) ? logEntryList.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); View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_sulog_group, parent, false);
return new LogGroupViewHolder(v); return new SectionHolder(v);
} }
@Override @Override
public LogViewHolder onCreateChildViewHolder(ViewGroup parent, int viewType) { public LogViewHolder onCreateItemViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_sulog, parent, false); View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_sulog, parent, false);
return new LogViewHolder(v); return new LogViewHolder(v);
} }
@Override @Override
public void onBindChildViewHolder(LogViewHolder holder, int flatPosition, ExpandableGroup group, int childIndex) { public void onBindSectionViewHolder(SectionHolder holder, int section) {
Context context = holder.itemView.getContext(); suLogCursor.moveToPosition(logEntryList.get(section).get(0));
SuLogEntry logEntry = (SuLogEntry) group.getItems().get(childIndex); SuLogEntry entry = new SuLogEntry(suLogCursor);
holder.setExpanded(expandList.contains(logEntry)); holder.arrow.setRotation(sectionExpanded.contains(section) ? 180 : 0);
holder.itemView.setOnClickListener(view -> { holder.itemView.setOnClickListener(v -> {
if (holder.getExpanded()) { RotateAnimation rotate;
holder.collapse(); if (sectionExpanded.contains(section)) {
expandList.remove(logEntry); 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), logEntryList.get(section).size());
} else { } else {
holder.expand(); rotate = new RotateAnimation(0, 180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
expandList.add(logEntry); sectionExpanded.add(section);
notifyItemRangeChanged(getItemPosition(section, 0), logEntryList.get(section).size());
} }
rotate.setDuration(300);
rotate.setFillAfter(true);
holder.arrow.setAnimation(rotate);
}); });
holder.appName.setText(logEntry.appName); holder.date.setText(entry.getDateString());
holder.action.setText(context.getString(logEntry.action ? R.string.grant : 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 @Override
public void onBindGroupViewHolder(LogGroupViewHolder holder, int flatPosition, ExpandableGroup group) { public void onBindItemViewHolder(LogViewHolder holder, int section, int position) {
holder.date.setText(group.getTitle()); int sqlPosition = logEntryList.get(section).get(position);
if (isGroupExpanded(flatPosition)) { suLogCursor.moveToPosition(sqlPosition);
SuLogEntry entry = new SuLogEntry(suLogCursor);
holder.setExpanded(itemExpanded.contains(sqlPosition));
holder.itemView.setOnClickListener(view -> {
if (holder.mExpanded) {
holder.collapse();
itemExpanded.remove(sqlPosition);
} else {
holder.expand(); holder.expand();
itemExpanded.add(sqlPosition);
} }
} });
holder.appName.setText(entry.appName);
holder.action.setText(entry.action ? R.string.grant : R.string.deny);
holder.command.setText(entry.command);
holder.fromPid.setText(String.valueOf(entry.fromPid));
holder.toUid.setText(String.valueOf(entry.toUid));
holder.time.setText(entry.getTimeString());
} }
private class LogGroup extends ExpandableGroup<SuLogEntry> { public void notifyDBChanged() {
LogGroup(String title, List<SuLogEntry> items) { if (suLogCursor != null)
super(title, items); suLogCursor.close();
} suLogCursor = suDB.getLogCursor();
logEntryList = suDB.getLogStructure();
itemExpanded.clear();
sectionExpanded.clear();
sectionExpanded.add(0);
notifyDataSetChanged();
} }
static class LogGroupViewHolder extends GroupViewHolder { static class SectionHolder extends RecyclerView.ViewHolder {
@BindView(R.id.date) TextView date; @BindView(R.id.date) TextView date;
@BindView(R.id.arrow) ImageView arrow; @BindView(R.id.arrow) ImageView arrow;
public LogGroupViewHolder(View itemView) { SectionHolder(View itemView) {
super(itemView); super(itemView);
ButterKnife.bind(this, 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 static class LogViewHolder extends ExpandableViewHolder {
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);
}
}
// Wrapper class
static class LogViewHolder extends ChildViewHolder {
private InternalViewHolder expandableViewHolder;
@BindView(R.id.app_name) TextView appName; @BindView(R.id.app_name) TextView appName;
@BindView(R.id.action) TextView action; @BindView(R.id.action) TextView action;
@ -162,13 +140,6 @@ public class SuLogAdapter {
LogViewHolder(View itemView) { LogViewHolder(View itemView) {
super(itemView); super(itemView);
ButterKnife.bind(this, itemView); ButterKnife.bind(this, itemView);
expandableViewHolder = new InternalViewHolder(itemView);
}
private class InternalViewHolder extends ExpandableViewHolder {
InternalViewHolder(View itemView) {
super(itemView);
} }
@Override @Override
@ -176,22 +147,4 @@ public class SuLogAdapter {
expandLayout = itemView.findViewById(R.id.expand_layout); expandLayout = itemView.findViewById(R.id.expand_layout);
} }
} }
private boolean getExpanded() {
return expandableViewHolder.mExpanded;
}
private void setExpanded(boolean expanded) {
expandableViewHolder.setExpanded(expanded);
}
private void expand() {
expandableViewHolder.expand();
}
private void collapse() {
expandableViewHolder.collapse();
}
}
} }

View File

@ -4,8 +4,10 @@ import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.database.Cursor; import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
import android.text.TextUtils;
import com.topjohnwu.magisk.MagiskManager; import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.superuser.Policy; import com.topjohnwu.magisk.superuser.Policy;
@ -13,8 +15,10 @@ import com.topjohnwu.magisk.superuser.SuLogEntry;
import com.topjohnwu.magisk.utils.Utils; import com.topjohnwu.magisk.utils.Utils;
import java.io.File; import java.io.File;
import java.text.DateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.List; import java.util.List;
public class SuDatabaseHelper extends SQLiteOpenHelper { public class SuDatabaseHelper extends SQLiteOpenHelper {
@ -179,31 +183,42 @@ public class SuDatabaseHelper extends SQLiteOpenHelper {
} }
} }
private List<SuLogEntry> getLogList(SQLiteDatabase db, String selection) { public List<List<Integer>> getLogStructure() {
try (Cursor c = db.query(LOG_TABLE, null, selection, null, null, null, "time DESC")) { try (Cursor c = mDb.query(LOG_TABLE, new String[] { "time" }, null, null, null, null, "time DESC")) {
List<SuLogEntry> ret = new ArrayList<>(c.getCount()); List<List<Integer>> ret = new ArrayList<>();
List<Integer> list = null;
String dateString = null, newString;
while (c.moveToNext()) { while (c.moveToNext()) {
ret.add(new SuLogEntry(c)); Date date = new Date(c.getLong(c.getColumnIndex("time")) * 1000);
newString = DateFormat.getDateInstance(DateFormat.MEDIUM, MagiskManager.locale).format(date);
if (!TextUtils.equals(dateString, newString)) {
dateString = newString;
list = new ArrayList<>();
ret.add(list);
}
list.add(c.getPosition());
} }
return ret; return ret;
} }
} }
public Cursor getLogCursor() {
return getLogCursor(mDb);
}
public Cursor getLogCursor(SQLiteDatabase db) {
return db.query(LOG_TABLE, null, null, null, null, null, "time DESC");
}
private void migrateLegacyLogList(File oldDB, SQLiteDatabase newDB) { private void migrateLegacyLogList(File oldDB, SQLiteDatabase newDB) {
SQLiteDatabase oldDb = SQLiteDatabase.openDatabase(oldDB.getPath(), null, SQLiteDatabase.OPEN_READWRITE); try (SQLiteDatabase oldDb = SQLiteDatabase.openDatabase(oldDB.getPath(), null, SQLiteDatabase.OPEN_READWRITE);
List<SuLogEntry> logs = getLogList(oldDb, null); Cursor c = getLogCursor(oldDb)) {
for (SuLogEntry log : logs) { while (c.moveToNext()) {
newDB.insert(LOG_TABLE, null, log.getContentValues()); ContentValues values = new ContentValues();
DatabaseUtils.cursorRowToContentValues(c, values);
newDB.insert(LOG_TABLE, null, values);
} }
oldDb.close();
} }
public List<SuLogEntry> getLogList() {
return getLogList(null);
}
public List<SuLogEntry> getLogList(String selection) {
return getLogList(mDb, selection);
} }
public void addLog(SuLogEntry log) { public void addLog(SuLogEntry log) {

View File

@ -2,8 +2,6 @@ package com.topjohnwu.magisk.superuser;
import android.content.ContentValues; import android.content.ContentValues;
import android.database.Cursor; import android.database.Cursor;
import android.os.Parcel;
import android.os.Parcelable;
import com.topjohnwu.magisk.MagiskManager; import com.topjohnwu.magisk.MagiskManager;
@ -11,7 +9,7 @@ import java.text.DateFormat;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
public class SuLogEntry implements Parcelable { public class SuLogEntry {
public int fromUid, toUid, fromPid; public int fromUid, toUid, fromPid;
public String packageName, appName, command; public String packageName, appName, command;
@ -55,45 +53,4 @@ public class SuLogEntry implements Parcelable {
public String getTimeString() { public String getTimeString() {
return new SimpleDateFormat("h:mm a", MagiskManager.locale).format(date); return new SimpleDateFormat("h:mm a", MagiskManager.locale).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());
}
} }