Add Superuser management UI

This commit is contained in:
topjohnwu 2017-01-26 01:13:23 +08:00
parent 3681177be4
commit 67c9e2ead6
16 changed files with 515 additions and 16 deletions

View File

@ -59,7 +59,7 @@ dependencies {
compile 'com.madgag.spongycastle:prov:1.54.0.0' compile 'com.madgag.spongycastle:prov:1.54.0.0'
compile 'com.madgag.spongycastle:pkix:1.54.0.0' compile 'com.madgag.spongycastle:pkix:1.54.0.0'
compile 'com.madgag.spongycastle:pg:1.54.0.0' compile 'com.madgag.spongycastle:pg:1.54.0.0'
compile 'com.google.android.gms:play-services-safetynet:10.0.1' compile 'com.google.android.gms:play-services-safetynet:9.0.1'
compile 'com.android.support:support-v4:25.1.0' compile 'com.android.support:support-v4:25.1.0'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0' annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0'
} }

View File

@ -165,6 +165,9 @@ public class MainActivity extends AppCompatActivity
case R.id.install: case R.id.install:
displayFragment(new InstallFragment(), "install", now); displayFragment(new InstallFragment(), "install", now);
break; break;
case R.id.superuser:
displayFragment(new SuperuserFragment(), "superuser", now);
break;
case R.id.modules: case R.id.modules:
displayFragment(new ModulesFragment(), "modules", now); displayFragment(new ModulesFragment(), "modules", now);
break; break;

View File

@ -1,6 +1,5 @@
package com.topjohnwu.magisk; package com.topjohnwu.magisk;
import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.preference.CheckBoxPreference; import android.preference.CheckBoxPreference;

View File

@ -0,0 +1,67 @@
package com.topjohnwu.magisk;
import android.app.Fragment;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.annotation.Nullable;
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.PolicyAdapter;
import com.topjohnwu.magisk.superuser.Policy;
import com.topjohnwu.magisk.superuser.SuDatabaseHelper;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.Unbinder;
public class SuperuserFragment extends Fragment {
private Unbinder unbinder;
@BindView(R.id.recyclerView) RecyclerView recyclerView;
@BindView(R.id.empty_rv) TextView emptyTv;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_superuser, container, false);
unbinder = ButterKnife.bind(this, view);
PackageManager pm = getActivity().getPackageManager();
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);
recyclerView.setVisibility(View.GONE);
} else {
recyclerView.setAdapter(adapter);
emptyTv.setVisibility(View.GONE);
recyclerView.setVisibility(View.VISIBLE);
}
return view;
}
@Override
public void onStart() {
super.onStart();
getActivity().setTitle(getString(R.string.superuser));
}
@Override
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
}

View File

@ -0,0 +1,200 @@
package com.topjohnwu.magisk.adapters;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.pm.PackageManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.Switch;
import android.widget.TextView;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.superuser.Policy;
import com.topjohnwu.magisk.superuser.SuDatabaseHelper;
import java.util.HashSet;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
public class PolicyAdapter extends RecyclerView.Adapter<PolicyAdapter.ViewHolder> {
private List<Policy> policyList;
private SuDatabaseHelper dbHelper;
private PackageManager pm;
private HashSet<Policy> expandList = new HashSet<>();
public PolicyAdapter(List<Policy> list, SuDatabaseHelper db, PackageManager pm) {
policyList = list;
dbHelper = db;
this.pm = pm;
}
@Override
public ViewHolder onCreateViewHolder(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(ViewHolder holder, int position) {
try {
Policy policy = policyList.get(position);
holder.setExpanded(expandList.contains(policy));
holder.itemView.setOnClickListener(view -> {
if (holder.mExpanded) {
holder.collapse();
expandList.remove(policy);
} else {
holder.expand();
expandList.add(policy);
}
});
holder.appName.setText(policy.appName);
holder.packageName.setText(policy.packageName);
holder.appIcon.setImageDrawable(pm.getPackageInfo(policy.packageName, 0).applicationInfo.loadIcon(pm));
holder.masterSwitch.setOnCheckedChangeListener((v, isChecked) -> {
if (isChecked && policy.policy == Policy.DENY) {
policy.policy = Policy.ALLOW;
dbHelper.addPolicy(policy);
} else if (!isChecked && policy.policy == Policy.ALLOW) {
policy.policy = Policy.DENY;
dbHelper.addPolicy(policy);
}
});
holder.notificationSwitch.setOnCheckedChangeListener((v, isChecked) -> {
if (isChecked && !policy.notification) {
policy.notification = true;
dbHelper.addPolicy(policy);
} else if (!isChecked && policy.notification) {
policy.notification = false;
dbHelper.addPolicy(policy);
}
});
holder.loggingSwitch.setOnCheckedChangeListener((v, isChecked) -> {
if (isChecked && !policy.logging) {
policy.logging = true;
dbHelper.addPolicy(policy);
} else if (!isChecked && policy.logging) {
policy.logging = false;
dbHelper.addPolicy(policy);
}
});
holder.masterSwitch.setChecked(policy.policy == Policy.ALLOW);
holder.notificationSwitch.setChecked(policy.notification);
holder.loggingSwitch.setChecked(policy.logging);
} catch (PackageManager.NameNotFoundException e) {
policyList.remove(position);
notifyItemRemoved(position);
notifyItemRangeChanged(position, policyList.size());
onBindViewHolder(holder, position);
}
}
@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.expand_layout) LinearLayout expandLayout;
@BindView(R.id.app_icon) ImageView appIcon;
@BindView(R.id.master_switch) Switch masterSwitch;
@BindView(R.id.notification_switch) Switch notificationSwitch;
@BindView(R.id.logging_switch) Switch loggingSwitch;
@BindView(R.id.more_info) ImageView moreInfo;
private ValueAnimator mAnimator;
private boolean mExpanded = false;
private static int expandHeight = 0;
ViewHolder(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

@ -7,6 +7,11 @@ import android.database.Cursor;
public class Policy { public class Policy {
public static final int INTERACTIVE = 0;
public static final int DENY = 1;
public static final int ALLOW = 2;
public int uid, policy; public int uid, policy;
public long until; public long until;
public boolean logging, notification; public boolean logging, notification;

View File

@ -57,19 +57,20 @@ public class SuDatabaseHelper extends SQLiteOpenHelper {
} }
public List<Policy> getPolicyList(PackageManager pm) { public List<Policy> getPolicyList(PackageManager pm) {
List<Policy> ret = new ArrayList<>();
SQLiteDatabase db = getWritableDatabase(); SQLiteDatabase db = getWritableDatabase();
ArrayList<Policy> ret = new ArrayList<>();
Policy policy; Policy policy;
// Clear outdated policies // Clear outdated policies
db.delete(TABLE_NAME, "until > 0 and until < ?", new String[] { String.valueOf(System.currentTimeMillis()) }); db.delete(TABLE_NAME, "until > 0 and until < ?", new String[] { String.valueOf(System.currentTimeMillis()) });
try (Cursor c = db.query(TABLE_NAME, null, null, null, null, null, null)) { try (Cursor c = db.query(TABLE_NAME, null, null, null, null, null, "app_name")) {
while (c.moveToNext()) { while (c.moveToNext()) {
policy = new Policy(c); policy = new Policy(c);
// Package is uninstalled // Package is uninstalled
if (pm.getPackagesForUid(policy.uid) == null) if (pm.getPackagesForUid(policy.uid) == null) {
deletePolicy(policy.uid); deletePolicy(policy.uid);
else } else {
ret.add(policy); ret.add(policy);
}
} }
} }
db.close(); db.close();

View File

@ -1,7 +1,6 @@
package com.topjohnwu.magisk.superuser; package com.topjohnwu.magisk.superuser;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
@ -11,10 +10,7 @@ import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.os.CountDownTimer; import android.os.CountDownTimer;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window; import android.view.Window;
import android.widget.AdapterView;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.Button; import android.widget.Button;
import android.widget.ImageView; import android.widget.ImageView;
@ -117,7 +113,7 @@ public class SuRequestActivity extends AppCompatActivity {
policy.packageName = packageName; policy.packageName = packageName;
policy.appName = appName; policy.appName = appName;
policy.until = (timeout == 0) ? 0 : (System.currentTimeMillis() / 1000 + timeout * 60); policy.until = (timeout == 0) ? 0 : (System.currentTimeMillis() / 1000 + timeout * 60);
policy.policy = action ? 2 : 1; policy.policy = action ? Policy.ALLOW : Policy.DENY;
policy.logging = true; policy.logging = true;
policy.notification = true; policy.notification = true;
new SuDatabaseHelper(this).addPolicy(policy); new SuDatabaseHelper(this).addPolicy(policy);

View File

@ -8,7 +8,6 @@ import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.provider.OpenableColumns; import android.provider.OpenableColumns;
import android.util.Log;
import android.widget.Toast; import android.widget.Toast;
import com.topjohnwu.magisk.Global; import com.topjohnwu.magisk.Global;

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M6,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M7.58,4.08L6.15,2.65C3.75,4.48 2.17,7.3 2.03,10.5h2c0.15,-2.65 1.51,-4.97 3.55,-6.42zM19.97,10.5h2c-0.15,-3.2 -1.73,-6.02 -4.12,-7.85l-1.42,1.43c2.02,1.45 3.39,3.77 3.54,6.42zM18,11c0,-3.07 -1.64,-5.64 -4.5,-6.32L13.5,4c0,-0.83 -0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5v0.68C7.63,5.36 6,7.92 6,11v5l-2,2v1h16v-1l-2,-2v-5zM12,22c0.14,0 0.27,-0.01 0.4,-0.04 0.65,-0.14 1.18,-0.58 1.44,-1.18 0.1,-0.24 0.15,-0.5 0.15,-0.78h-4c0.01,1.1 0.9,2 2.01,2z"/>
</vector>

View File

@ -0,0 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="#000" android:pathData="M5.41,21L6.12,17H2.12L2.47,15H6.47L7.53,9H3.53L3.88,7H7.88L8.59,3H10.59L9.88,7H15.88L16.59,3H18.59L17.88,7H21.88L21.53,9H17.53L16.47,15H20.47L20.12,17H16.12L15.41,21H13.41L14.12,17H8.12L7.41,21H5.41M9.53,9L8.47,15H14.47L15.53,9H9.53Z" />
</vector>

View File

@ -0,0 +1,28 @@
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_marginTop="?attr/actionBarSize"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/empty_rv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:fontFamily="sans-serif-light"
android:gravity="center"
android:text="@string/no_apps_found"
android:textSize="20sp"
android:textStyle="italic"
android:visibility="gone" />
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:dividerHeight="@dimen/card_divider_space"
app:layoutManager="android.support.v7.widget.LinearLayoutManager" />
</LinearLayout>

View File

@ -0,0 +1,163 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
style="?attr/cardStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginBottom="@dimen/card_vertical_margin"
android:layout_marginEnd="@dimen/card_horizontal_margin"
android:layout_marginStart="@dimen/card_horizontal_margin"
android:layout_marginTop="@dimen/card_vertical_margin"
android:minHeight="?android:attr/listPreferredItemHeight"
card_view:cardCornerRadius="@dimen/card_corner_radius"
card_view:cardElevation="@dimen/card_elevation">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/info_layout"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:paddingTop="5dp"
android:paddingBottom="5dp">
<ImageView
android:id="@+id/app_icon"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_gravity="center_vertical"
android:gravity="end" />
<LinearLayout
android:orientation="vertical"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:layout_marginStart="5dp"
android:layout_marginEnd="5dp">
<TextView
android:id="@+id/app_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:ellipsize="end"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="?android:attr/textColorPrimary"
android:textIsSelectable="false"/>
<TextView
android:id="@+id/package_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:ellipsize="end"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="@android:color/tertiary_text_dark"
android:textIsSelectable="false" />
</LinearLayout>
<Switch
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/master_switch"
android:layout_weight="0"
android:checked="false"
android:layout_gravity="center_vertical"
android:gravity="center_vertical" />
</LinearLayout>
<LinearLayout
android:id="@+id/expand_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:orientation="horizontal"
android:paddingStart="10dp"
android:paddingEnd="10dp">
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:layout_gravity="center">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_notifications"
android:layout_gravity="center_vertical"
android:tint="@color/icon_grey"
android:layout_marginEnd="10dp" />
<Switch
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/notification_switch"
android:checked="false"
android:layout_gravity="center_vertical"
android:gravity="center_vertical" />
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:layout_gravity="center">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_bug_report"
android:layout_gravity="center_vertical"
android:tint="@color/icon_grey"
android:layout_marginEnd="10dp" />
<Switch
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/logging_switch"
android:checked="false"
android:layout_gravity="center_vertical"
android:gravity="center_vertical" />
</LinearLayout>
<ImageView
android:id="@+id/more_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="?android:attr/selectableItemBackground"
android:src="@drawable/ic_more"
android:tint="@color/icon_grey"
android:layout_gravity="center" />
</LinearLayout>
</LinearLayout>
</android.support.v7.widget.CardView>

View File

@ -16,6 +16,18 @@
android:title="@string/install" android:title="@string/install"
android:visible="false"/> android:visible="false"/>
<item
android:id="@+id/superuser"
android:icon="@drawable/ic_superuser"
android:title="@string/superuser"
android:visible="true"/>
</group>
<group
android:checkableBehavior="single"
android:id="@+id/second_group">
<item <item
android:id="@+id/modules" android:id="@+id/modules"
android:icon="@drawable/ic_extension" android:icon="@drawable/ic_extension"
@ -30,15 +42,17 @@
android:id="@+id/magiskhide" android:id="@+id/magiskhide"
android:icon="@drawable/ic_autoroot" android:icon="@drawable/ic_autoroot"
android:title="@string/magiskhide"/> android:title="@string/magiskhide"/>
<item <item
android:id="@+id/log" android:id="@+id/log"
android:icon="@drawable/ic_bug_report" android:icon="@drawable/ic_bug_report"
android:title="@string/log"/> android:title="@string/log"/>
</group> </group>
<group <group
android:checkableBehavior="none" android:checkableBehavior="none"
android:id="@+id/second_group"> android:id="@+id/third_group">
<item <item
android:id="@+id/settings" android:id="@+id/settings"

View File

@ -9,6 +9,7 @@
<string name="magiskhide">Magisk Hide</string> <string name="magiskhide">Magisk Hide</string>
<string name="modules">Modules</string> <string name="modules">Modules</string>
<string name="downloads">Downloads</string> <string name="downloads">Downloads</string>
<string name="superuser">Superuser</string>
<string name="log">Log</string> <string name="log">Log</string>
<string name="settings">Settings</string> <string name="settings">Settings</string>
<string name="status">Status</string> <string name="status">Status</string>
@ -156,9 +157,7 @@
<string name="sixtymin">60 min</string> <string name="sixtymin">60 min</string>
<string name="su_allow_toast">%1$s is granted Superuser permissions</string> <string name="su_allow_toast">%1$s is granted Superuser permissions</string>
<string name="su_deny_toast">%1$s is denied Superuser permissions</string> <string name="su_deny_toast">%1$s is denied Superuser permissions</string>
<string name="no_apps_found">No apps found</string>
<!-- TODO: Remove or change this placeholder text -->
<string name="hello_blank_fragment">Hello blank fragment</string>
</resources> </resources>