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:
topjohnwu
2017-01-24 14:19:28 +08:00
parent cf16fd0104
commit 0bd0eb9e59
12 changed files with 505 additions and 3 deletions

View 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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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
}
}
}

View File

@ -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);
}
}
}
}