diff --git a/app/build.gradle b/app/build.gradle index acd58459f..2f67c5c96 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -59,7 +59,7 @@ dependencies { compile 'com.madgag.spongycastle:prov:1.54.0.0' compile 'com.madgag.spongycastle:pkix: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' annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0' } diff --git a/app/src/main/java/com/topjohnwu/magisk/MainActivity.java b/app/src/main/java/com/topjohnwu/magisk/MainActivity.java index 1e1bc4fa8..30efebc0d 100644 --- a/app/src/main/java/com/topjohnwu/magisk/MainActivity.java +++ b/app/src/main/java/com/topjohnwu/magisk/MainActivity.java @@ -165,6 +165,9 @@ public class MainActivity extends AppCompatActivity case R.id.install: displayFragment(new InstallFragment(), "install", now); break; + case R.id.superuser: + displayFragment(new SuperuserFragment(), "superuser", now); + break; case R.id.modules: displayFragment(new ModulesFragment(), "modules", now); break; diff --git a/app/src/main/java/com/topjohnwu/magisk/SettingsActivity.java b/app/src/main/java/com/topjohnwu/magisk/SettingsActivity.java index e1412b9ed..bdd6976c4 100644 --- a/app/src/main/java/com/topjohnwu/magisk/SettingsActivity.java +++ b/app/src/main/java/com/topjohnwu/magisk/SettingsActivity.java @@ -1,6 +1,5 @@ package com.topjohnwu.magisk; -import android.content.Context; import android.content.SharedPreferences; import android.os.Bundle; import android.preference.CheckBoxPreference; diff --git a/app/src/main/java/com/topjohnwu/magisk/SuperuserFragment.java b/app/src/main/java/com/topjohnwu/magisk/SuperuserFragment.java new file mode 100644 index 000000000..710217808 --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/SuperuserFragment.java @@ -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 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(); + } + +} diff --git a/app/src/main/java/com/topjohnwu/magisk/adapters/PolicyAdapter.java b/app/src/main/java/com/topjohnwu/magisk/adapters/PolicyAdapter.java new file mode 100644 index 000000000..296c108c2 --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/adapters/PolicyAdapter.java @@ -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 { + + private List policyList; + private SuDatabaseHelper dbHelper; + private PackageManager pm; + private HashSet expandList = new HashSet<>(); + + public PolicyAdapter(List 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; + } + + } +} diff --git a/app/src/main/java/com/topjohnwu/magisk/superuser/Policy.java b/app/src/main/java/com/topjohnwu/magisk/superuser/Policy.java index bce29e8e8..3bf39a8d6 100644 --- a/app/src/main/java/com/topjohnwu/magisk/superuser/Policy.java +++ b/app/src/main/java/com/topjohnwu/magisk/superuser/Policy.java @@ -7,6 +7,11 @@ import android.database.Cursor; 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 long until; public boolean logging, notification; diff --git a/app/src/main/java/com/topjohnwu/magisk/superuser/SuDatabaseHelper.java b/app/src/main/java/com/topjohnwu/magisk/superuser/SuDatabaseHelper.java index d00183d68..ab77643af 100644 --- a/app/src/main/java/com/topjohnwu/magisk/superuser/SuDatabaseHelper.java +++ b/app/src/main/java/com/topjohnwu/magisk/superuser/SuDatabaseHelper.java @@ -57,19 +57,20 @@ public class SuDatabaseHelper extends SQLiteOpenHelper { } public List getPolicyList(PackageManager pm) { + List ret = new ArrayList<>(); SQLiteDatabase db = getWritableDatabase(); - ArrayList ret = new ArrayList<>(); Policy policy; // Clear outdated policies 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()) { policy = new Policy(c); // Package is uninstalled - if (pm.getPackagesForUid(policy.uid) == null) + if (pm.getPackagesForUid(policy.uid) == null) { deletePolicy(policy.uid); - else + } else { ret.add(policy); + } } } db.close(); diff --git a/app/src/main/java/com/topjohnwu/magisk/superuser/SuRequestActivity.java b/app/src/main/java/com/topjohnwu/magisk/superuser/SuRequestActivity.java index 9eba08db9..a70f75090 100644 --- a/app/src/main/java/com/topjohnwu/magisk/superuser/SuRequestActivity.java +++ b/app/src/main/java/com/topjohnwu/magisk/superuser/SuRequestActivity.java @@ -1,7 +1,6 @@ package com.topjohnwu.magisk.superuser; import android.content.ContentValues; -import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; @@ -11,10 +10,7 @@ import android.os.AsyncTask; import android.os.Bundle; import android.os.CountDownTimer; import android.support.v7.app.AppCompatActivity; -import android.view.MotionEvent; -import android.view.View; import android.view.Window; -import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.ImageView; @@ -117,7 +113,7 @@ public class SuRequestActivity extends AppCompatActivity { policy.packageName = packageName; policy.appName = appName; 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.notification = true; new SuDatabaseHelper(this).addPolicy(policy); diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/Async.java b/app/src/main/java/com/topjohnwu/magisk/utils/Async.java index 0da57cf23..ba1a5f634 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/Async.java +++ b/app/src/main/java/com/topjohnwu/magisk/utils/Async.java @@ -8,7 +8,6 @@ import android.database.Cursor; import android.net.Uri; import android.os.AsyncTask; import android.provider.OpenableColumns; -import android.util.Log; import android.widget.Toast; import com.topjohnwu.magisk.Global; diff --git a/app/src/main/res/drawable/ic_more.xml b/app/src/main/res/drawable/ic_more.xml new file mode 100644 index 000000000..da83afdb1 --- /dev/null +++ b/app/src/main/res/drawable/ic_more.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_notifications.xml b/app/src/main/res/drawable/ic_notifications.xml new file mode 100644 index 000000000..be9f8368d --- /dev/null +++ b/app/src/main/res/drawable/ic_notifications.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_superuser.xml b/app/src/main/res/drawable/ic_superuser.xml new file mode 100644 index 000000000..c7a3bc010 --- /dev/null +++ b/app/src/main/res/drawable/ic_superuser.xml @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_superuser.xml b/app/src/main/res/layout/fragment_superuser.xml new file mode 100644 index 000000000..55a4dd234 --- /dev/null +++ b/app/src/main/res/layout/fragment_superuser.xml @@ -0,0 +1,28 @@ + + + + + + + diff --git a/app/src/main/res/layout/list_item_policy.xml b/app/src/main/res/layout/list_item_policy.xml new file mode 100644 index 000000000..bb6b723d3 --- /dev/null +++ b/app/src/main/res/layout/list_item_policy.xml @@ -0,0 +1,163 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/menu/drawer.xml b/app/src/main/res/menu/drawer.xml index d9da58120..c67365d6f 100644 --- a/app/src/main/res/menu/drawer.xml +++ b/app/src/main/res/menu/drawer.xml @@ -16,6 +16,18 @@ android:title="@string/install" android:visible="false"/> + + + + + + + + + android:id="@+id/third_group"> Magisk Hide Modules Downloads + Superuser Log Settings Status @@ -156,9 +157,7 @@ 60 min %1$s is granted Superuser permissions %1$s is denied Superuser permissions - - - Hello blank fragment + No apps found