Magisk Manager is now a SU client
1. Add request popup 2. Add su request notifications 3. Add su database helpers
This commit is contained in:
parent
cf16fd0104
commit
0bd0eb9e59
@ -8,9 +8,6 @@
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission
|
||||
android:name="android.permission.PACKAGE_USAGE_STATS"
|
||||
tools:ignore="ProtectedPermissions" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
@ -41,6 +38,20 @@
|
||||
<activity
|
||||
android:name=".SettingsActivity"
|
||||
android:theme="@style/AppTheme.Transparent" />
|
||||
<activity
|
||||
android:name=".superuser.RequestActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:launchMode="singleTask"
|
||||
android:taskAffinity="internal.superuser"
|
||||
android:theme="@android:style/Theme.NoDisplay" />
|
||||
<activity
|
||||
android:name=".superuser.SuRequestActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:taskAffinity="internal.superuser"
|
||||
android:theme="@style/SuRequest" />
|
||||
|
||||
<receiver
|
||||
android:name=".superuser.SuReceiver" />
|
||||
|
||||
<provider
|
||||
android:name="android.support.v4.content.FileProvider"
|
||||
|
36
app/src/main/java/com/topjohnwu/magisk/superuser/Policy.java
Normal file
36
app/src/main/java/com/topjohnwu/magisk/superuser/Policy.java
Normal file
@ -0,0 +1,36 @@
|
||||
package com.topjohnwu.magisk.superuser;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.database.Cursor;
|
||||
|
||||
|
||||
public class Policy {
|
||||
public int uid, policy;
|
||||
public long until;
|
||||
public boolean logging, notification;
|
||||
public String packageName, appName;
|
||||
|
||||
public Policy() {}
|
||||
|
||||
public Policy(Cursor c) {
|
||||
uid = c.getInt(c.getColumnIndex("uid"));
|
||||
packageName = c.getString(c.getColumnIndex("package_name"));
|
||||
appName = c.getString(c.getColumnIndex("app_name"));
|
||||
policy = c.getInt(c.getColumnIndex("policy"));
|
||||
until = c.getLong(c.getColumnIndex("until"));
|
||||
logging = c.getInt(c.getColumnIndex("logging")) != 0;
|
||||
notification = c.getInt(c.getColumnIndex("notification")) != 0;
|
||||
}
|
||||
|
||||
public ContentValues getContentValues() {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put("uid", uid);
|
||||
values.put("package_name", packageName);
|
||||
values.put("app_name", appName);
|
||||
values.put("policy",policy);
|
||||
values.put("until", until);
|
||||
values.put("logging", logging ? 1 : 0);
|
||||
values.put("notification", notification ? 1 : 0);
|
||||
return values;
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package com.topjohnwu.magisk.superuser;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
|
||||
public class RequestActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Intent intent = getIntent();
|
||||
if (intent == null) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.setClass(this, SuRequestActivity.class);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
package com.topjohnwu.magisk.superuser;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SuDatabaseHelper extends SQLiteOpenHelper {
|
||||
|
||||
private static final int DATABASE_VER = 1;
|
||||
private static final String TABLE_NAME = "policies";
|
||||
|
||||
public SuDatabaseHelper(Context context) {
|
||||
super(context, "su.db", null, DATABASE_VER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
db.execSQL(
|
||||
"CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " " +
|
||||
"(uid INT, package_name TEXT, app_name TEXT, policy INT, " +
|
||||
"until INT, logging INT, notification INT, " +
|
||||
"PRIMARY KEY(uid))");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
// Currently new database, no upgrading
|
||||
}
|
||||
|
||||
public boolean deletePolicy(int uid) {
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
db.delete(TABLE_NAME, "uid=?", new String[] { String.valueOf(uid) });
|
||||
db.close();
|
||||
return getPolicy(uid) == null;
|
||||
}
|
||||
|
||||
public Policy getPolicy(int uid) {
|
||||
Policy policy = null;
|
||||
SQLiteDatabase db = getReadableDatabase();
|
||||
try (Cursor c = db.query(TABLE_NAME, null, "uid=?", new String[] { String.valueOf(uid) }, null, null, null)) {
|
||||
if (c.moveToNext())
|
||||
policy = new Policy(c);
|
||||
}
|
||||
db.close();
|
||||
return policy;
|
||||
}
|
||||
|
||||
public void addPolicy(Policy policy) {
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
db.replace(TABLE_NAME, null, policy.getContentValues());
|
||||
db.close();
|
||||
}
|
||||
|
||||
public List<Policy> getPolicyList(PackageManager pm) {
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
ArrayList<Policy> 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)) {
|
||||
while (c.moveToNext()) {
|
||||
policy = new Policy(c);
|
||||
// Package is uninstalled
|
||||
if (pm.getPackagesForUid(policy.uid) == null)
|
||||
deletePolicy(policy.uid);
|
||||
else
|
||||
ret.add(policy);
|
||||
}
|
||||
}
|
||||
db.close();
|
||||
return ret;
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package com.topjohnwu.magisk.superuser;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
|
||||
public class SuReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
int fromUid, toUid, pid;
|
||||
String command, action;
|
||||
|
||||
if (intent == null) return;
|
||||
fromUid = intent.getIntExtra("from.uid", -1);
|
||||
if (fromUid < 0) return;
|
||||
action = intent.getStringExtra("action");
|
||||
if (action == null) return;
|
||||
SuDatabaseHelper dbHelper = new SuDatabaseHelper(context);
|
||||
Policy policy = dbHelper.getPolicy(fromUid);
|
||||
if (policy == null) return;
|
||||
if (policy.notification) {
|
||||
String message;
|
||||
switch (action) {
|
||||
case "allow":
|
||||
message = context.getString(R.string.su_allow_toast, policy.appName);
|
||||
break;
|
||||
case "deny":
|
||||
message = context.getString(R.string.su_deny_toast, policy.appName);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
if (policy.logging) {
|
||||
toUid = intent.getIntExtra("to.uid", -1);
|
||||
if (toUid < 0) return;
|
||||
pid = intent.getIntExtra("pid", -1);
|
||||
if (pid < 0) return;
|
||||
command = intent.getStringExtra("command");
|
||||
if (command == null) return;
|
||||
// TODO: Place info into logs
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,163 @@
|
||||
package com.topjohnwu.magisk.superuser;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.LocalSocket;
|
||||
import android.net.LocalSocketAddress;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.view.Window;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.topjohnwu.magisk.R;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class SuRequestActivity extends AppCompatActivity {
|
||||
|
||||
private final static int SU_PROTOCOL_PARAM_MAX = 20;
|
||||
private final static int SU_PROTOCOL_NAME_MAX = 20;
|
||||
private final static int SU_PROTOCOL_VALUE_MAX = 256;
|
||||
|
||||
@BindView(R.id.timeout) Spinner timeout;
|
||||
@BindView(R.id.app_icon) ImageView appIcon;
|
||||
@BindView(R.id.app_name) TextView appNameView;
|
||||
@BindView(R.id.package_name) TextView packageNameView;
|
||||
@BindView(R.id.grant_btn) Button grant_btn;
|
||||
@BindView(R.id.deny_btn) Button deny_btn;
|
||||
|
||||
private String socketPath;
|
||||
private LocalSocket socket;
|
||||
private PackageManager pm;
|
||||
private PackageInfo info;
|
||||
|
||||
private int uid;
|
||||
private String appName, packageName;
|
||||
|
||||
private int[] timeoutList = {0, -1, 10, 20, 30, 60};
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
|
||||
pm = getPackageManager();
|
||||
|
||||
Intent intent = getIntent();
|
||||
socketPath = intent.getStringExtra("socket");
|
||||
|
||||
new SocketManager().execute();
|
||||
}
|
||||
|
||||
void updateUI() throws Throwable {
|
||||
setContentView(R.layout.activity_request);
|
||||
ButterKnife.bind(this);
|
||||
ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this, R.array.timeout, android.R.layout.simple_spinner_item);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
timeout.setAdapter(adapter);
|
||||
appIcon.setImageDrawable(info.applicationInfo.loadIcon(pm));
|
||||
appNameView.setText(appName);
|
||||
packageNameView.setText(packageName);
|
||||
|
||||
grant_btn.setOnClickListener(v -> handleAction(true, timeoutList[timeout.getSelectedItemPosition()]));
|
||||
deny_btn.setOnClickListener(v -> handleAction(false, 0));
|
||||
}
|
||||
|
||||
void handleAction(boolean action, int timeout) {
|
||||
|
||||
try {
|
||||
socket.getOutputStream().write((action ? "socket:ALLOW" : "socket:DENY").getBytes());
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
if (timeout >= 0) {
|
||||
Policy policy = new Policy();
|
||||
policy.uid = uid;
|
||||
policy.packageName = packageName;
|
||||
policy.appName = appName;
|
||||
policy.until = (timeout == 0) ? 0 : System.currentTimeMillis() + timeout * 60 * 1000;
|
||||
policy.policy = action ? 2 : 1;
|
||||
policy.logging = true;
|
||||
policy.notification = true;
|
||||
new SuDatabaseHelper(this).addPolicy(policy);
|
||||
}
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
private class SocketManager extends AsyncTask<Void, Void, Boolean> {
|
||||
|
||||
@Override
|
||||
protected Boolean doInBackground(Void... params) {
|
||||
try{
|
||||
socket = new LocalSocket();
|
||||
socket.connect(new LocalSocketAddress(socketPath, LocalSocketAddress.Namespace.FILESYSTEM));
|
||||
|
||||
DataInputStream is = new DataInputStream(socket.getInputStream());
|
||||
|
||||
ContentValues payload = new ContentValues();
|
||||
|
||||
for (int i = 0; i < SU_PROTOCOL_PARAM_MAX; i++) {
|
||||
|
||||
int nameLen = is.readInt();
|
||||
if (nameLen > SU_PROTOCOL_NAME_MAX)
|
||||
throw new IllegalArgumentException("name length too long: " + nameLen);
|
||||
|
||||
byte[] nameBytes = new byte[nameLen];
|
||||
is.readFully(nameBytes);
|
||||
|
||||
String name = new String(nameBytes);
|
||||
|
||||
int dataLen = is.readInt();
|
||||
if (dataLen > SU_PROTOCOL_VALUE_MAX)
|
||||
throw new IllegalArgumentException(name + " data length too long: " + dataLen);
|
||||
|
||||
byte[] dataBytes = new byte[dataLen];
|
||||
is.readFully(dataBytes);
|
||||
|
||||
String data = new String(dataBytes);
|
||||
|
||||
payload.put(name, data);
|
||||
|
||||
if ("eof".equals(name))
|
||||
break;
|
||||
}
|
||||
|
||||
uid = payload.getAsInteger("uid");
|
||||
|
||||
}catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Boolean result) {
|
||||
try {
|
||||
if (!result) throw new Throwable();
|
||||
String[] pkgs = pm.getPackagesForUid(uid);
|
||||
if (pkgs != null && pkgs.length > 0) {
|
||||
info = pm.getPackageInfo(pkgs[0], 0);
|
||||
packageName = pkgs[0];
|
||||
appName = info.applicationInfo.loadLabel(pm).toString();
|
||||
updateUI();
|
||||
}
|
||||
else
|
||||
throw new Throwable();
|
||||
} catch (Throwable e) {
|
||||
handleAction(false, -1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
9
app/src/main/res/drawable/ic_su_warning.xml
Normal file
9
app/src/main/res/drawable/ic_su_warning.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="18dp"
|
||||
android:height="18dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFd32f2f"
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-2h2v2zM13,13h-2L11,7h2v6z"/>
|
||||
</vector>
|
104
app/src/main/res/layout/activity_request.xml
Normal file
104
app/src/main/res/layout/activity_request.xml
Normal file
@ -0,0 +1,104 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context="com.topjohnwu.playground.superuser.RequestActivity"
|
||||
android:orientation="vertical"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="375dp"
|
||||
android:layout_gravity="center">
|
||||
|
||||
<TextView
|
||||
android:text="@string/su_request_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:id="@+id/request_title"
|
||||
android:gravity="center"
|
||||
android:layout_marginTop="20dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:paddingStart="30dp">
|
||||
|
||||
<ImageView
|
||||
app:srcCompat="@mipmap/ic_launcher"
|
||||
android:id="@+id/app_icon"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_marginStart="5dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_weight="5">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:id="@+id/app_name" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:id="@+id/package_name" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<Spinner
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/timeout"
|
||||
android:layout_margin="15dp"
|
||||
android:layout_gravity="center_horizontal" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/warning"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawableStart="@drawable/ic_su_warning"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
android:text="@string/su_warning"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_margin="5dp"
|
||||
android:drawablePadding="10dp" />
|
||||
|
||||
<LinearLayout
|
||||
style="?android:buttonBarStyle"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="bottom"
|
||||
android:paddingLeft="30dp"
|
||||
android:paddingRight="30dp">
|
||||
|
||||
<Button
|
||||
style="?android:buttonBarButtonStyle"
|
||||
android:text="@string/deny"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/deny_btn"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<Button
|
||||
style="?android:buttonBarButtonStyle"
|
||||
android:text="@string/grant"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/grant_btn"
|
||||
android:layout_weight="1" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
@ -8,4 +8,12 @@
|
||||
<item>@string/theme_default_value</item>
|
||||
<item>@string/theme_dark_value</item>
|
||||
</string-array>
|
||||
<string-array name="timeout">
|
||||
<item>@string/forever</item>
|
||||
<item>@string/once</item>
|
||||
<item>@string/tenmin</item>
|
||||
<item>@string/twentymin</item>
|
||||
<item>@string/thirtymin</item>
|
||||
<item>@string/sixtymin</item>
|
||||
</string-array>
|
||||
</resources>
|
@ -20,4 +20,6 @@
|
||||
<color name="dh_icons">#dedede</color>
|
||||
<color name="dh_divider">#313131</color>
|
||||
|
||||
<color name="su_request_background">#e0e0e0</color>
|
||||
|
||||
</resources>
|
@ -143,5 +143,19 @@
|
||||
<string name="theme_default_value" translatable="false">default</string>
|
||||
<string name="theme_dark_value" translatable="false">dark</string>
|
||||
|
||||
<!--Superuser-->
|
||||
<string name="su_request_title">Superuser Request</string>
|
||||
<string name="deny">Deny</string>
|
||||
<string name="grant">Grant</string>
|
||||
<string name="su_warning">Grants full access to your device.\nDeny if you\'re not sure!</string>
|
||||
<string name="forever">Forever</string>
|
||||
<string name="once">Once</string>
|
||||
<string name="tenmin">10 min</string>
|
||||
<string name="twentymin">20 min</string>
|
||||
<string name="thirtymin">30 min</string>
|
||||
<string name="sixtymin">60 min</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>
|
||||
|
||||
|
||||
</resources>
|
||||
|
@ -121,4 +121,9 @@
|
||||
<item name="android:windowTranslucentNavigation">true</item>
|
||||
</style>
|
||||
|
||||
<style name="SuRequest" parent="Theme.AppCompat.Light.Dialog">
|
||||
<item name="android:windowCloseOnTouchOutside">false</item>
|
||||
<item name="android:colorBackground">@color/su_request_background</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
Loading…
Reference in New Issue
Block a user