diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5eade796a..95388009d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -8,9 +8,6 @@ - + + + + getPolicyList(PackageManager pm) { + 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)) { + 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; + } +} diff --git a/app/src/main/java/com/topjohnwu/magisk/superuser/SuReceiver.java b/app/src/main/java/com/topjohnwu/magisk/superuser/SuReceiver.java new file mode 100644 index 000000000..a45a560f9 --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/superuser/SuReceiver.java @@ -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 + } + } +} diff --git a/app/src/main/java/com/topjohnwu/magisk/superuser/SuRequestActivity.java b/app/src/main/java/com/topjohnwu/magisk/superuser/SuRequestActivity.java new file mode 100644 index 000000000..ba8809ba1 --- /dev/null +++ b/app/src/main/java/com/topjohnwu/magisk/superuser/SuRequestActivity.java @@ -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 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 { + + @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); + } + } + } +} diff --git a/app/src/main/res/drawable/ic_su_warning.xml b/app/src/main/res/drawable/ic_su_warning.xml new file mode 100644 index 000000000..87e5f4ac4 --- /dev/null +++ b/app/src/main/res/drawable/ic_su_warning.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/activity_request.xml b/app/src/main/res/layout/activity_request.xml new file mode 100644 index 000000000..a7a6b8a84 --- /dev/null +++ b/app/src/main/res/layout/activity_request.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + +