OSDN Git Service

work in progress...
authorKoushik Dutta <koushd@gmail.com>
Mon, 18 Feb 2013 06:18:45 +0000 (22:18 -0800)
committerKoushik Dutta <koushd@gmail.com>
Mon, 18 Feb 2013 06:18:45 +0000 (22:18 -0800)
22 files changed:
Superuser/AndroidManifest.xml
Superuser/gen/com/koushikdutta/superuser/R.java
Superuser/jni/su/activity.c
Superuser/jni/su/db.c
Superuser/jni/su/su.c
Superuser/jni/su/su.h
Superuser/res/layout-land/app_layout.xml [new file with mode: 0644]
Superuser/res/layout/activity_main.xml [deleted file]
Superuser/res/layout/app_info.xml [new file with mode: 0644]
Superuser/res/layout/app_layout.xml [new file with mode: 0644]
Superuser/res/layout/request.xml [new file with mode: 0644]
Superuser/res/layout/request_buttons.xml [new file with mode: 0644]
Superuser/res/values-v11/styles.xml [deleted file]
Superuser/res/values-v14/styles.xml
Superuser/res/values/strings.xml
Superuser/res/values/styles.xml
Superuser/src/com/koushikdutta/superuser/MainActivity.java
Superuser/src/com/koushikdutta/superuser/MultitaskSuRequestActivity.java
Superuser/src/com/koushikdutta/superuser/RequestActivity.java [new file with mode: 0644]
Superuser/src/com/koushikdutta/superuser/SuApplication.java [new file with mode: 0644]
Superuser/src/com/koushikdutta/superuser/SuDatabaseHelper.java [new file with mode: 0644]
Superuser/src/com/koushikdutta/superuser/SuReceiver.java

index 7648ccc..872d083 100644 (file)
         android:protectionLevel="signatureOrSystem" />
 
     <application
+        android:name=".SuApplication"
         android:allowBackup="true"
         android:icon="@drawable/ic_launcher"
         android:label="@string/app_name"
         android:theme="@style/AppTheme" >
         <activity
-            android:launchMode="singleTask"
-            android:name="com.koushikdutta.superuser.MainActivity"
-            android:label="@string/app_name"
-            android:permission="com.koushikdutta.superuser.REQUEST" >
+            android:configChanges="keyboardHidden|orientation|screenSize"
+            android:name=".MainActivity"
+            android:label="@string/app_name" >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
 
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
-
-        <activity android:name="com.koushikdutta.superuser.MultitaskSuRequestActivity" android:theme="@style/AppTheme"  android:label="@string/request" />
+        <activity
+            android:configChanges="keyboardHidden|orientation|screenSize"
+            android:name=".RequestActivity"
+            android:label="@string/app_name"
+            android:launchMode="singleTask"
+            android:permission="com.koushikdutta.superuser.REQUEST" />
+        <activity
+            android:configChanges="keyboardHidden|orientation|screenSize"
+            android:name=".MultitaskSuRequestActivity"
+            android:label="@string/request"
+            android:theme="@style/AppTheme" />
 
         <receiver
             android:name=".SuReceiver"
index a408ef0..a62806b 100644 (file)
@@ -10,6 +10,9 @@ package com.koushikdutta.superuser;
 public final class R {
     public static final class attr {
     }
+    public static final class color {
+        public static final int background_dark=0x7f070000;
+    }
     public static final class dimen {
         /**  Default screen margins, per the Android Design guidelines. 
 
@@ -24,46 +27,52 @@ public final class R {
         public static final int ic_launcher=0x7f020000;
     }
     public static final class id {
-        public static final int allow=0x7f07000c;
-        public static final int allow_temporarily=0x7f07000a;
-        public static final int always_allow=0x7f070009;
-        public static final int app_header=0x7f070003;
-        public static final int command_header=0x7f070006;
-        public static final int deny=0x7f07000b;
-        public static final int image=0x7f07000d;
-        public static final int incoming=0x7f070000;
-        public static final int list=0x7f070007;
-        public static final int package_header=0x7f070004;
-        public static final int ready=0x7f070001;
-        public static final int request=0x7f070002;
-        public static final int summary=0x7f07000f;
-        public static final int title=0x7f07000e;
-        public static final int uid_header=0x7f070005;
-        public static final int unknown=0x7f070008;
+        public static final int allow=0x7f080012;
+        public static final int app_header=0x7f080000;
+        public static final int command_header=0x7f080003;
+        public static final int deny=0x7f080011;
+        public static final int image=0x7f080005;
+        public static final int incoming=0x7f080009;
+        public static final int list=0x7f080004;
+        public static final int package_header=0x7f080001;
+        public static final int ready=0x7f08000a;
+        public static final int remember=0x7f08000d;
+        public static final int remember_for=0x7f08000f;
+        public static final int remember_forever=0x7f080010;
+        public static final int request=0x7f08000b;
+        public static final int root=0x7f080008;
+        public static final int summary=0x7f080007;
+        public static final int this_time_only=0x7f08000e;
+        public static final int title=0x7f080006;
+        public static final int uid_header=0x7f080002;
+        public static final int unknown=0x7f08000c;
     }
     public static final class layout {
-        public static final int activity_main=0x7f030000;
-        public static final int packageinfo=0x7f030001;
+        public static final int app_info=0x7f030000;
+        public static final int app_layout=0x7f030001;
+        public static final int packageinfo=0x7f030002;
+        public static final int request=0x7f030003;
+        public static final int request_buttons=0x7f030004;
     }
     public static final class string {
         public static final int allow=0x7f050001;
-        public static final int allow_temporarily=0x7f050008;
-        public static final int always_allow=0x7f050009;
-        public static final int app_header=0x7f05000b;
+        public static final int app_header=0x7f050009;
         public static final int app_name=0x7f050000;
         public static final int application_request=0x7f050005;
-        public static final int command_header=0x7f05000d;
+        public static final int command_header=0x7f05000b;
         public static final int deny=0x7f050002;
         public static final int info=0x7f050006;
-        public static final int package_header=0x7f05000a;
+        public static final int package_header=0x7f050008;
+        public static final int remember_for=0x7f05000d;
+        public static final int remember_forever=0x7f05000e;
         public static final int request=0x7f050007;
         public static final int status_incoming=0x7f050003;
-        public static final int uid_header=0x7f05000c;
+        public static final int this_time_only=0x7f05000c;
+        public static final int uid_header=0x7f05000a;
         public static final int unknown_uid=0x7f050004;
     }
     public static final class style {
         /**  API 14 theme customizations can go here. 
- API 14 theme customizations can go here. 
          */
         public static final int AppBaseDarkTheme=0x7f060002;
         /** 
@@ -76,11 +85,6 @@ public final class R {
             backward-compatibility can go here.
         
 
-        Base application theme for API 11+. This theme completely replaces
-        AppBaseTheme from res/values/styles.xml on API 11+ devices.
-    
- API 11 theme customizations can go here. 
-
         Base application theme for API 14+. This theme completely replaces
         AppBaseTheme from BOTH res/values/styles.xml and
         res/values-v11/styles.xml on API 14+ devices.
@@ -96,7 +100,6 @@ public final class R {
  All customizations that are NOT specific to a particular API-level can go here. 
          */
         public static final int AppTheme=0x7f060001;
-        public static final int Dialog=0x7f060005;
         public static final int FlatButton=0x7f060004;
     }
 }
index 59d1e69..250fdbc 100644 (file)
@@ -71,13 +71,11 @@ static int silent_run(char* command) {
     return -1;
 }
 
-int send_result(struct su_context *ctx, allow_t allow) {
+int send_result(struct su_context *ctx, policy_t allow) {
     return 0;
 }
 
 int send_request(struct su_context *ctx) {
-    pid_t pid;
-
     // if su is operating in MULTIUSER_MODEL_OWNER,
     // and the user requestor is not the owner,
     // the owner needs to be notified of the request.
@@ -101,37 +99,29 @@ int send_request(struct su_context *ctx) {
         return -1;
     }
 
-    pid = fork();
-    if (!pid) {
-        if (needs_owner_login_prompt) {
-            // in multiuser mode, the owner gets the su prompt
-            pid = fork();
-            if (!pid) {
-                char notify_command[ARG_MAX];
-
-                // start the activity that confirms the request
-                snprintf(notify_command, sizeof(notify_command),
-                    "exec /system/bin/am " ACTION_NOTIFY " --ei caller_uid %d --user %d",
-                    ctx->from.uid, ctx->user.android_user_id);
 
-                return silent_run(notify_command);
-            }
-        }
-        
-        char request_command[ARG_MAX];
+    int ret;
+    if (needs_owner_login_prompt) {
+        // in multiuser mode, the owner gets the su prompt
+        char notify_command[ARG_MAX];
 
         // start the activity that confirms the request
-        snprintf(request_command, sizeof(request_command),
-            "exec /system/bin/am " ACTION_REQUEST " --es socket '%s' %s",
-            ctx->sock_path, user);
+        snprintf(notify_command, sizeof(notify_command),
+            "exec /system/bin/am " ACTION_NOTIFY " --ei caller_uid %d --user %d",
+            ctx->from.uid, ctx->user.android_user_id);
 
-        return silent_run(request_command);
-    }
-    
-    /* Parent */
-    if (pid < 0) {
-        PLOGE("fork");
-        return -1;
+        int ret = silent_run(notify_command);
+        if (ret) {
+            return ret;
+        }
     }
-    return 0;
+
+    char request_command[ARG_MAX];
+
+    // start the activity that confirms the request
+    snprintf(request_command, sizeof(request_command),
+        "exec /system/bin/am " ACTION_REQUEST " --es socket '%s' %s",
+        ctx->sock_path, user);
+
+    return silent_run(request_command);
 }
index cbccd6e..e1ec6a3 100644 (file)
 #include <stdio.h>
 #include <limits.h>
 #include <sqlite3.h>
+#include <time.h>
 
 #include "su.h"
 
-static int database_callback(void *NotUsed, int argc, char **argv, char **azColName){
+struct callback_data_t {
+    struct su_context *ctx;
+    policy_t policy;
+};
+
+static int database_callback(void *v, int argc, char **argv, char **azColName){
+    struct callback_data_t *data = (struct callback_data_t *)v;
+    int command_match = 0;
+    policy_t policy = DENY;
     int i;
-    for(i=0; i<argc; i++){
-        printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
+    time_t until = 0;
+    for(i = 0; i < argc; i++) {
+        if (strcmp(azColName[i], "policy") == 0) {
+            if (argv[i] == NULL) {
+                policy = DENY;
+            }
+            if (strcmp(argv[i], "allow") == 0) {
+                policy = ALLOW;
+            }
+            else if (strcmp(argv[i], "interactive") == 0) {
+                policy = INTERACTIVE;
+            }
+            else {
+                policy = DENY;
+            }
+        }
+        else if (strcmp(azColName[i], "command") == 0) {
+            // null command means to match all commands (whitelist all from uid)
+            command_match = argv[i] == NULL || strcmp(argv[i], get_command(&(data->ctx->to))) == 0;
+        }
+        else if (strcmp(azColName[i], "until") == 0) {
+            if (argv[i] != NULL) {
+                until = atoi(argv[i]);
+            }
+        }
+    }
+
+    // check for command match
+    if (command_match) {
+        // also make sure this policy has not expired
+        if (until == 0 || until < time(NULL)) {
+            if (policy == DENY) {
+                data->policy = DENY;
+                return -1;
+            }
+
+            data->policy = ALLOW;
+            // even though we allow, continue, so we can see if there's another policy
+            // that denies...
+        }
     }
-    printf("\n");
+    
     return 0;
 }
 
-int database_check(struct su_context *ctx) {
+policy_t database_check(struct su_context *ctx) {
     sqlite3 *db = NULL;
     
     char query[512];
-    snprintf(query, sizeof(query), "select allow_type, until, command from access where uid=%d", ctx->from.uid);
-    int ret = sqlite3_open(query, &db);
+    snprintf(query, sizeof(query), "select policy, until, command from uid_policy where uid=%d", ctx->from.uid);
+    int ret = sqlite3_open_v2(ctx->user.database_path, &db, SQLITE_OPEN_READONLY, NULL);
     if (ret) {
-        LOGE("sqlite3 open failure");
-        return ret;
+        LOGE("sqlite3 open failure: %d", ret);
+        sqlite3_close(db);
+        return DENY;
     }
-
+    
+    int result;
+    char *err = NULL;
+    struct callback_data_t data;
+    data.ctx = ctx;
+    data.policy = INTERACTIVE;
+    ret = sqlite3_exec(db, query, database_callback, &data, &err);
     sqlite3_close(db);
+    if (err != NULL) {
+        LOGE("sqlite3_exec: %s", err);
+        return DENY;
+    }
 
-    return -1;
+    return data.policy;
 }
index b2273bd..44ba39b 100644 (file)
@@ -49,39 +49,44 @@ int get_shell_uid() {
 void exec_log(char *priority, char* logline) {
   int pid;
   if ((pid = fork()) == 0) {
-    execl("/system/bin/log", "/system/bin/log", "-p", priority, "-t", LOG_TAG, logline);
-    exit(0);
+      int zero = open("/dev/zero", O_RDONLY | O_CLOEXEC);
+      dup2(zero, 0);
+      int null = open("/dev/null", O_WRONLY | O_CLOEXEC);
+      dup2(null, 1);
+      dup2(null, 2);
+      execl("/system/bin/log", "/system/bin/log", "-p", priority, "-t", LOG_TAG, logline);
+      _exit(0);
   }
 }
 
 void exec_loge(const char* fmt, ...) {
-  va_list args;
+    va_list args;
 
-  char logline[PATH_MAX];
-  va_start(args, fmt);
-  vsnprintf(logline, PATH_MAX, fmt, args);
-  va_end(args);
-  exec_log("e", logline);
+    char logline[PATH_MAX];
+    va_start(args, fmt);
+    vsnprintf(logline, PATH_MAX, fmt, args);
+    va_end(args);
+    exec_log("e", logline);
 }
 
 void exec_logw(const char* fmt, ...) {
-  va_list args;
+    va_list args;
 
-  char logline[PATH_MAX];
-  va_start(args, fmt);
-  vsnprintf(logline, PATH_MAX, fmt, args);
-  va_end(args);
-  exec_log("w", logline);
+    char logline[PATH_MAX];
+    va_start(args, fmt);
+    vsnprintf(logline, PATH_MAX, fmt, args);
+    va_end(args);
+    exec_log("w", logline);
 }
 
 void exec_logd(const char* fmt, ...) {
-  va_list args;
+    va_list args;
 
-  char logline[PATH_MAX];
-  va_start(args, fmt);
-  vsnprintf(logline, PATH_MAX, fmt, args);
-  va_end(args);
-  exec_log("d", logline);
+    char logline[PATH_MAX];
+    va_start(args, fmt);
+    vsnprintf(logline, PATH_MAX, fmt, args);
+    va_end(args);
+    exec_log("d", logline);
 }
 
 static int from_init(struct su_initiator *from) {
@@ -153,6 +158,7 @@ static void read_options(struct su_context *ctx) {
     FILE *fp;
     if ((fp = fopen(REQUESTOR_MULTIUSER_MODE, "r"))) {
         fgets(mode, sizeof(mode), fp);
+        LOGD("multiuser mode: %s", mode);
         if (strcmp(mode, "user\n") == 0) {
             ctx->user.multiuser_mode = MULTIUSER_MODE_USER;
         } else if (strcmp(mode, "owner\n") == 0) {
@@ -289,6 +295,7 @@ static int socket_accept(int serv_fd) {
     tv.tv_usec = 0;
     FD_ZERO(&fds);
     FD_SET(serv_fd, &fds);
+    LOGD("select");
     do {
         rc = select(serv_fd + 1, &fds, NULL, NULL, &tv);
     } while (rc < 0 && errno == EINTR);
@@ -348,6 +355,7 @@ do {                            \
 static int socket_receive_result(int fd, char *result, ssize_t result_len) {
     ssize_t len;
     
+    LOGD("waiting for result");
     len = read(fd, result, result_len-1);
     if (len < 0) {
         PLOGE("read(result)");
@@ -570,7 +578,7 @@ int main(int argc, char *argv[]) {
     struct stat st;
     int c, socket_serv_fd, fd;
     char buf[64], *result;
-    allow_t dballow;
+    policy_t dballow;
     struct option long_opts[] = {
         { "command",            required_argument,    NULL, 'c' },
         { "help",            no_argument,        NULL, 'h' },
@@ -648,11 +656,15 @@ int main(int argc, char *argv[]) {
     read_options(&ctx);
     user_init(&ctx);
     
-    if (ctx.user.multiuser_mode == MULTIUSER_MODE_OWNER_ONLY && ctx.user.android_user_id != 0)
+    if (ctx.user.multiuser_mode == MULTIUSER_MODE_OWNER_ONLY && ctx.user.android_user_id != 0) {
+        LOGD("multiuser mode: owner only");
         deny(&ctx);
+    }
 
-    if (access_disabled(&ctx.from))
+    if (access_disabled(&ctx.from)) {
+        LOGD("access_disabled");
         deny(&ctx);
+    }
 
     ctx.umask = umask(027);
 
@@ -660,7 +672,7 @@ int main(int argc, char *argv[]) {
         allow(&ctx);
 
     if (stat(ctx.user.database_path, &st) < 0) {
-        PLOGE("stat");
+        PLOGE("stat %s", ctx.user.database_path);
         deny(&ctx);
     }
 
@@ -671,7 +683,8 @@ int main(int argc, char *argv[]) {
         deny(&ctx);
     }
 
-    mkdir(REQUESTOR_CACHE_PATH, 0770);
+    int ret = mkdir(REQUESTOR_CACHE_PATH, 0770);
+    LOGD("mkdir: %d", ret);
     if (chown(REQUESTOR_CACHE_PATH, st.st_uid, st.st_gid)) {
         PLOGE("chown (%s, %ld, %ld)", REQUESTOR_CACHE_PATH, st.st_uid, st.st_gid);
         deny(&ctx);
@@ -692,10 +705,15 @@ int main(int argc, char *argv[]) {
 
     dballow = database_check(&ctx);
     switch (dballow) {
-        case INTERACTIVE: break;
-        case ALLOW: allow(&ctx);    /* never returns */
+        case INTERACTIVE:
+            break;
+        case ALLOW:
+            LOGD("db allowed");
+            allow(&ctx);    /* never returns */
         case DENY:
-        default: deny(&ctx);        /* never returns too */
+        default:
+            LOGD("db denied");
+            deny(&ctx);        /* never returns too */
     }
     
     socket_serv_fd = socket_create_temp(ctx.sock_path, sizeof(ctx.sock_path));
index 960c74a..3075ab7 100644 (file)
@@ -50,7 +50,7 @@
 #define REQUESTOR_MULTIUSER_MODE REQUESTOR_FILES_PATH "/multiuser_mode"
 
 /* intent actions */
-#define ACTION_REQUEST "start -n " REQUESTOR "/.MainActivity"
+#define ACTION_REQUEST "start -n " REQUESTOR "/.RequestActivity"
 #define ACTION_NOTIFY "start -n " REQUESTOR "/.NotifyActivity"
 #define ACTION_RESULT "broadcast -n " REQUESTOR "/.SuReceiver"
 
@@ -114,15 +114,15 @@ typedef enum {
 } multiuser_mode_t;
 
 typedef enum {
-    INTERACTIVE = -1,
-    DENY = 0,
-    ALLOW = 1,
-} allow_t;
+    INTERACTIVE = 0,
+    DENY = 1,
+    ALLOW = 2,
+} policy_t;
 
-extern allow_t database_check(struct su_context *ctx);
+extern policy_t database_check(struct su_context *ctx);
 extern void set_identity(unsigned int uid);
 extern int send_request(struct su_context *ctx);
-extern int send_result(struct su_context *ctx, allow_t allow);
+extern int send_result(struct su_context *ctx, policy_t policy);
 extern void sigchld_handler(int sig);
 
 static inline char *get_command(const struct su_request *to)
diff --git a/Superuser/res/layout-land/app_layout.xml b/Superuser/res/layout-land/app_layout.xml
new file mode 100644 (file)
index 0000000..602c335
--- /dev/null
@@ -0,0 +1,32 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content" 
+    android:layout_gravity="center"
+    android:gravity="center"
+    >
+
+    <ListView
+        android:id="@+id/list"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:layout_weight="1"
+        android:divider="@null"
+        android:dividerHeight="0dp"
+        android:gravity="center"
+        android:paddingBottom="20dp"
+        android:paddingTop="20dp" />
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:orientation="vertical" >
+
+        <include
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            layout="@layout/app_info" />
+    </LinearLayout>
+
+
+</LinearLayout>
\ No newline at end of file
diff --git a/Superuser/res/layout/activity_main.xml b/Superuser/res/layout/activity_main.xml
deleted file mode 100644 (file)
index a68bb7e..0000000
+++ /dev/null
@@ -1,234 +0,0 @@
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical"
-    tools:context=".MainActivity" >
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="0dp"
-        android:layout_weight="1"
-        android:orientation="vertical"
-        android:paddingBottom="@dimen/activity_vertical_margin"
-        android:paddingLeft="@dimen/activity_horizontal_margin"
-        android:paddingRight="@dimen/activity_horizontal_margin"
-        android:paddingTop="@dimen/activity_vertical_margin" >
-
-        <TextView
-            android:id="@+id/incoming"
-            style="@android:style/TextAppearance.Medium"
-            android:layout_width="wrap_content"
-            android:layout_height="fill_parent"
-            android:layout_gravity="center"
-            android:gravity="center"
-            android:text="@string/status_incoming" />
-
-        <LinearLayout
-            android:id="@+id/ready"
-            android:layout_width="fill_parent"
-            android:layout_height="fill_parent"
-            android:orientation="vertical"
-            android:visibility="gone" >
-
-            <TextView
-                android:id="@+id/request"
-                style="@android:style/TextAppearance.Large"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_gravity="center"
-                android:gravity="center"
-                android:textStyle="bold" />
-
-            <TextView
-                style="@android:style/TextAppearance.Small"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_gravity="center"
-                android:gravity="center"
-                android:paddingBottom="10dp"
-                android:paddingTop="10dp"
-                android:text="@string/info" />
-
-            <LinearLayout
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:orientation="horizontal" >
-
-                <TextView
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_weight="3"
-                    android:gravity="right"
-                    android:paddingRight="10dp"
-                    android:text="@string/app_header"
-                    android:textStyle="bold" />
-
-                <TextView
-                    android:id="@+id/app_header"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_weight="2"
-                    android:singleLine="true" />
-            </LinearLayout>
-
-            <LinearLayout
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:orientation="horizontal" >
-
-                <TextView
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_weight="3"
-                    android:gravity="right"
-                    android:paddingRight="10dp"
-                    android:text="@string/package_header"
-                    android:textStyle="bold" />
-
-                <TextView
-                    android:id="@+id/package_header"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_weight="2"
-                    android:singleLine="true" />
-            </LinearLayout>
-
-            <LinearLayout
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:orientation="horizontal" >
-
-                <TextView
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_weight="3"
-                    android:gravity="right"
-                    android:paddingRight="10dp"
-                    android:text="@string/uid_header"
-                    android:textStyle="bold" />
-
-                <TextView
-                    android:id="@+id/uid_header"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_weight="2"
-                    android:singleLine="true" />
-            </LinearLayout>
-
-            <LinearLayout
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:orientation="horizontal" >
-
-                <TextView
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_weight="3"
-                    android:gravity="right"
-                    android:paddingRight="10dp"
-                    android:text="@string/command_header"
-                    android:textStyle="bold" />
-
-                <TextView
-                    android:id="@+id/command_header"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_weight="2"
-                    android:ellipsize="middle"
-                    android:singleLine="true" />
-            </LinearLayout>
-
-            <ListView
-                android:id="@+id/list"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_gravity="center"
-                android:divider="@null"
-                android:dividerHeight="0dp"
-                android:gravity="center"
-                android:paddingBottom="20dp"
-                android:paddingTop="20dp" />
-
-            <TextView
-                android:id="@+id/unknown"
-                style="@android:style/TextAppearance.Medium"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_gravity="center"
-                android:gravity="center" />
-
-            <LinearLayout
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_gravity="center"
-                android:orientation="vertical" >
-
-                <CheckBox
-                    android:id="@+id/always_allow"
-                    style="@android:style/TextAppearance.Medium"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:paddingBottom="10dp"
-                    android:text="@string/always_allow" />
-
-                <CheckBox
-                    android:id="@+id/allow_temporarily"
-                    style="@android:style/TextAppearance.Medium"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:paddingBottom="10dp"
-                    android:text="@string/allow_temporarily" />
-            </LinearLayout>
-        </LinearLayout>
-    </LinearLayout>
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="vertical" >
-
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="1dp"
-            android:background="@android:color/darker_gray"
-            android:orientation="vertical" >
-        </LinearLayout>
-    </LinearLayout>
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content" >
-
-        <Button
-            android:id="@+id/deny"
-            style="@style/FlatButton"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_weight="1"
-            android:text="@string/deny" />
-
-        <LinearLayout
-            android:layout_width="wrap_content"
-            android:layout_height="match_parent"
-            android:paddingBottom="10dp"
-            android:paddingTop="10dp" >
-
-            <LinearLayout
-                android:layout_width="1dp"
-                android:layout_height="match_parent"
-                android:background="@android:color/darker_gray" >
-            </LinearLayout>
-        </LinearLayout>
-
-        <Button
-            android:id="@+id/allow"
-            style="@style/FlatButton"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_weight="1"
-            android:enabled="false"
-            android:text="@string/allow" />
-    </LinearLayout>
-
-</LinearLayout>
\ No newline at end of file
diff --git a/Superuser/res/layout/app_info.xml b/Superuser/res/layout/app_info.xml
new file mode 100644 (file)
index 0000000..1a08c91
--- /dev/null
@@ -0,0 +1,92 @@
+<merge xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal" >
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="3"
+            android:gravity="right"
+            android:paddingRight="10dp"
+            android:text="@string/app_header"
+            android:textStyle="bold" />
+
+        <TextView
+            android:id="@+id/app_header"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="2"
+            android:singleLine="true" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal" >
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="3"
+            android:gravity="right"
+            android:paddingRight="10dp"
+            android:text="@string/package_header"
+            android:textStyle="bold" />
+
+        <TextView
+            android:id="@+id/package_header"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="2"
+            android:singleLine="true" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal" >
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="3"
+            android:gravity="right"
+            android:paddingRight="10dp"
+            android:text="@string/uid_header"
+            android:textStyle="bold" />
+
+        <TextView
+            android:id="@+id/uid_header"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="2"
+            android:singleLine="true" />
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal" >
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="3"
+            android:gravity="right"
+            android:paddingRight="10dp"
+            android:text="@string/command_header"
+            android:textStyle="bold" />
+
+        <TextView
+            android:id="@+id/command_header"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="2"
+            android:ellipsize="middle"
+            android:singleLine="true" />
+    </LinearLayout>
+
+</merge>
\ No newline at end of file
diff --git a/Superuser/res/layout/app_layout.xml b/Superuser/res/layout/app_layout.xml
new file mode 100644 (file)
index 0000000..3c650f8
--- /dev/null
@@ -0,0 +1,19 @@
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <include
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        layout="@layout/app_info" />
+
+    <ListView
+        android:id="@+id/list"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:divider="@null"
+        android:dividerHeight="0dp"
+        android:gravity="center"
+        android:paddingBottom="20dp"
+        android:paddingTop="20dp" />
+
+</merge>
\ No newline at end of file
diff --git a/Superuser/res/layout/request.xml b/Superuser/res/layout/request.xml
new file mode 100644 (file)
index 0000000..eac4bdb
--- /dev/null
@@ -0,0 +1,108 @@
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    android:id="@+id/root"
+    tools:context=".RequestActivity" >
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        android:orientation="vertical"
+        android:paddingBottom="@dimen/activity_vertical_margin"
+        android:paddingLeft="@dimen/activity_horizontal_margin"
+        android:paddingRight="@dimen/activity_horizontal_margin"
+        android:paddingTop="@dimen/activity_vertical_margin" >
+
+        <TextView
+            android:id="@+id/incoming"
+            style="@android:style/TextAppearance.Medium"
+            android:layout_width="wrap_content"
+            android:layout_height="fill_parent"
+            android:layout_gravity="center"
+            android:gravity="center"
+            android:text="@string/status_incoming" />
+
+        <LinearLayout
+            android:id="@+id/ready"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:orientation="vertical"
+            android:visibility="gone" >
+
+            <TextView
+                android:id="@+id/request"
+                style="@android:style/TextAppearance.Large"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:gravity="center"
+                android:textStyle="bold" />
+
+            <TextView
+                style="@android:style/TextAppearance.Small"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:gravity="center"
+                android:paddingBottom="10dp"
+                android:paddingTop="10dp"
+                android:text="@string/info" />
+
+            <include
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                layout="@layout/app_layout" />
+
+            <TextView
+                android:id="@+id/unknown"
+                style="@android:style/TextAppearance.Medium"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:gravity="center" />
+
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:orientation="vertical" >
+
+                <RadioGroup
+                    android:id="@+id/remember"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content" >
+
+                    <RadioButton
+                        android:checked="true"
+                        android:id="@+id/this_time_only"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:text="@string/this_time_only" />
+
+                    <RadioButton
+                        android:id="@+id/remember_for"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:text="@string/remember_for" />
+
+                    <RadioButton
+                        android:id="@+id/remember_forever"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:text="@string/remember_forever" />
+                </RadioGroup>
+            </LinearLayout>
+        </LinearLayout>
+    </LinearLayout>
+
+<!--     <include
+        android:background="@color/background_dark"
+        style="@style/AppBaseDarkTheme"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        layout="@layout/request_buttons" /> -->
+
+</LinearLayout>
\ No newline at end of file
diff --git a/Superuser/res/layout/request_buttons.xml b/Superuser/res/layout/request_buttons.xml
new file mode 100644 (file)
index 0000000..c4aa847
--- /dev/null
@@ -0,0 +1,52 @@
+<merge xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical" >
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="1dp"
+            android:background="@android:color/darker_gray"
+            android:orientation="vertical" >
+        </LinearLayout>
+    </LinearLayout>
+
+    <LinearLayout
+        android:background="@color/background_dark"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" >
+
+        <Button
+            android:id="@+id/deny"
+            style="@style/FlatButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:text="@string/deny" />
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:paddingBottom="10dp"
+            android:paddingTop="10dp" >
+
+            <LinearLayout
+                android:layout_width="1dp"
+                android:layout_height="match_parent"
+                android:background="@android:color/darker_gray" >
+            </LinearLayout>
+        </LinearLayout>
+
+        <Button
+            android:id="@+id/allow"
+            style="@style/FlatButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:enabled="false"
+            android:text="@string/allow" />
+    </LinearLayout>
+
+</merge>
\ No newline at end of file
diff --git a/Superuser/res/values-v11/styles.xml b/Superuser/res/values-v11/styles.xml
deleted file mode 100644 (file)
index fff1478..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-<resources>
-
-    <!--
-        Base application theme for API 11+. This theme completely replaces
-        AppBaseTheme from res/values/styles.xml on API 11+ devices.
-    -->
-    <style name="AppBaseTheme" parent="android:Theme.Holo.Light">
-        <!-- API 11 theme customizations can go here. -->
-    </style>
-
-    <style name="AppBaseDarkTheme" parent="@android:style/Theme.Holo">
-        <!-- API 14 theme customizations can go here. -->
-    </style>
-        
-    <style name="FlatButton">
-        <item name="android:background">?android:attr/selectableItemBackground</item>
-    </style>
-    
-</resources>
\ No newline at end of file
index 94b5881..4b08f2e 100644 (file)
@@ -5,7 +5,7 @@
         AppBaseTheme from BOTH res/values/styles.xml and
         res/values-v11/styles.xml on API 14+ devices.
     -->
-    <style name="AppBaseTheme" parent="android:Theme.Holo.Light.DarkActionBar">
+    <style name="AppBaseTheme" parent="@android:style/Theme.Holo.Light.DarkActionBar">
         <!-- API 14 theme customizations can go here. -->
     </style>
 
index 9a4b158..db1407a 100644 (file)
@@ -6,13 +6,15 @@
     <string name="status_incoming">Incoming Superuser Request...</string>
     <string name="unknown_uid">Unknown UID: %s</string>
     <string name="application_request">%s is requesting Superuser access.</string>
-    <string name="info">Warning: If you did not initiate this action or do not understand this request, you should Deny the request.</string>
+    <string name="info">Warning: If you do not understand this, you should Deny the request.</string>
     <string name="request">Superuser Request</string>
-    <string name="allow_temporarily">Temporarily allow future requests</string>
-    <string name="always_allow">Allow all future requests</string>
     
     <string name="package_header">Package:</string>
     <string name="app_header">App:</string>
     <string name="uid_header">Requested UID:</string>
     <string name="command_header">Command:</string>
+
+    <string name="this_time_only">This time only</string>
+    <string name="remember_for">Remember choice for %s minutes</string>
+    <string name="remember_forever">Remember choice forever</string>
 </resources>
\ No newline at end of file
index cf8024d..8917d31 100644 (file)
@@ -4,7 +4,7 @@
         Base application theme, dependent on API level. This theme is replaced
         by AppBaseTheme from res/values-vXX/styles.xml on newer devices.
     -->
-    <style name="AppBaseTheme" parent="android:Theme.Light">
+    <style name="AppBaseTheme" parent="@android:style/Theme.Light">
         <!--
             Theme customizations available in newer API levels can go in
             res/values-vXX/styles.xml, while customizations related to
@@ -29,8 +29,7 @@
     <style name="FlatButton">
         
     </style>
-    <style name="Dialog" parent="@android:style/Theme.Dialog">
-        
-    </style>
+    
+    <color name="background_dark">#1F1F1F</color>
     
 </resources>
\ No newline at end of file
index bcf5523..07182b3 100644 (file)
@@ -1,246 +1,7 @@
 package com.koushikdutta.superuser;
 
-import java.io.DataInputStream;
-import java.io.IOException;
-
 import android.app.Activity;
-import android.content.Intent;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.net.LocalSocket;
-import android.net.LocalSocketAddress;
-import android.net.LocalSocketAddress.Namespace;
-import android.os.Bundle;
-import android.os.Handler;
-import android.util.Log;
-import android.view.ContextThemeWrapper;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.ListView;
-import android.widget.TextView;
 
 public class MainActivity extends Activity {
-    private static final String LOGTAG = "Superuser";
-    int mCallerUid;
-    int mDesiredUid;
-    String mDesiredCmd;
-    
-    ArrayAdapter<PackageInfo> mAdapter;
-    
-    Handler mHandler = new Handler();
-    
-    int mTimeLeft = 3;
-    
-    Boolean mAction;
-    Button mAllow;
-    Button mDeny;
-
-    void handleAction() {
-        try {
-            mSocket.getOutputStream().write((mAction ? "socket:ALLOW" : "socket:DENY").getBytes());
-        }
-        catch (Exception ex) {
-        }
-        finish();
-    }
-    
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-        try {
-            if (mSocket != null)
-                mSocket.close();
-        }
-        catch (Exception ex) {
-        }
-    }
-    
-    void requestReady() {
-        findViewById(R.id.incoming).setVisibility(View.GONE);
-        findViewById(R.id.ready).setVisibility(View.VISIBLE);
-        
-        ListView list = (ListView)findViewById(R.id.list);
-        list.setEnabled(false);
-        list.setEmptyView(findViewById(R.id.unknown));
-        final PackageManager pm = getPackageManager();
-        String[] pkgs = pm.getPackagesForUid(mCallerUid);
-        TextView unknown = (TextView)findViewById(R.id.unknown);
-        unknown.setText(getString(R.string.unknown_uid, mCallerUid));
-        
-        ((TextView)findViewById(R.id.uid_header)).setText(Integer.toString(mDesiredUid));
-        ((TextView)findViewById(R.id.command_header)).setText(mDesiredCmd);
-
-        mAdapter = new ArrayAdapter<PackageInfo>(this, R.layout.packageinfo, R.id.title) {
-            @Override
-            public View getView(int position, View convertView, ViewGroup parent) {
-                convertView = super.getView(position, convertView, parent);
-                
-                PackageInfo pi = getItem(position);
-                ImageView icon = (ImageView)convertView.findViewById(R.id.image);
-                icon.setImageDrawable(pi.applicationInfo.loadIcon(pm));
-                ((TextView)convertView.findViewById(R.id.title)).setText(pi.applicationInfo.loadLabel(pm));
-                
-                return convertView;
-            }
-        };
-        
-        list.setAdapter(mAdapter);
-        for (String pkg: pkgs) {
-            try {
-                PackageInfo pi = pm.getPackageInfo(pkg, 0);
-                ((TextView)findViewById(R.id.request)).setText(getString(R.string.application_request, pi.applicationInfo.loadLabel(pm)));
-                mAdapter.add(pi);
-                ((TextView)findViewById(R.id.app_header)).setText(pi.applicationInfo.loadLabel(pm));
-                ((TextView)findViewById(R.id.package_header)).setText(pi.packageName);
-            }
-            catch (Exception ex) {
-            }
-        }
-        
-        new Runnable() {
-            public void run() {
-                mAllow.setText(getString(R.string.allow) + " (" + mTimeLeft + ")");
-                if (mTimeLeft-- <= 0) {
-                    mAllow.setText(getString(R.string.allow));
-                    if (mAction == null)
-                        mAllow.setEnabled(true);
-                    return;
-                }
-                mHandler.postDelayed(this, 1000);
-            };
-        }.run();
-    }
-
-    void manageSocket(final String socket) {
-        new Thread() {
-            @Override
-            public void run() {
-                try {
-                    mSocket = new LocalSocket();
-                    mSocket.connect(new LocalSocketAddress(socket, Namespace.FILESYSTEM));
-
-                    DataInputStream is = new DataInputStream(mSocket.getInputStream());
-
-                    int protocolVersion = is.readInt();
-                    Log.d(LOGTAG, "INT32:PROTO VERSION = " + protocolVersion);
-
-                    int exeSizeMax = is.readInt();
-                    Log.d(LOGTAG, "UINT32:FIELD7MAX = " + exeSizeMax);
-                    int cmdSizeMax = is.readInt();
-                    Log.d(LOGTAG, "UINT32:FIELD9MAX = " + cmdSizeMax);
-                    mCallerUid = is.readInt();
-                    Log.d(LOGTAG, "UINT32:CALLER = " + mCallerUid);
-                    mDesiredUid = is.readInt();
-                    Log.d(LOGTAG, "UINT32:TO = " + mDesiredUid);
-
-                    int exeSize = is.readInt();
-                    Log.d(LOGTAG, "UINT32:EXESIZE = " + exeSize);
-                    if (exeSize > exeSizeMax) {
-                        throw new IOException("Incomming string bigger than allowed");
-                    }
-                    byte[] buf = new byte[exeSize];
-                    is.read(buf);
-                    String callerBin = new String(buf, 0, exeSize - 1);
-                    Log.d(LOGTAG, "STRING:EXE = " + callerBin);
-
-                    int cmdSize = is.readInt();
-                    Log.d(LOGTAG, "UINT32:CMDSIZE = " + cmdSize);
-                    if (cmdSize > cmdSizeMax) {
-                        throw new IOException("Incomming string bigger than allowed");
-                    }
-                    buf = new byte[cmdSize];
-                    is.read(buf);
-                    mDesiredCmd = new String(buf, 0, cmdSize - 1);
-                    Log.d(LOGTAG, "STRING:CMD = " + mDesiredCmd);
-                    runOnUiThread(new Runnable() {
-                        @Override
-                        public void run() {
-                            requestReady();
-                        }
-                    });
-                }
-                catch (Exception ex) {
-                    Log.i(LOGTAG, ex.getMessage(), ex);
-                    try {
-                        mSocket.close();
-                    }
-                    catch (Exception e) {
-                    }
-                    runOnUiThread(new Runnable() {
-                        @Override
-                        public void run() {
-                            finish();
-                        }
-                    });
-                }
-            }
-        }.start();
-    }
-    
-    LocalSocket mSocket;
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity_main);
-
-        Intent intent = getIntent();
-        if (intent == null) {
-            finish();
-            return;
-        }
-
-        String socket = intent.getStringExtra("socket");
-        if (socket == null) {
-            finish();
-            return;
-        }
-     
-        if (getClass() == MainActivity.class) {
-            // TODO: is this the right way to do this?
-            // maybe queue requests to maintain the order?
-            // fragile apps may have race conditions that occur
-            // if the su requests are handled LIFO.
-            
-            // MainActivity is actually just a passthrough to a new task
-            // stack.
-            // Pretty much every superuser implementation i've seen craps out if there
-            // is more than 1 su request at a time. Each subsequent su request
-            // will get "lost" because there can only be one instance of the activity
-            // at a time. Really annoying. This fixes that.
-            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            intent.setClass(this, MultitaskSuRequestActivity.class);
-            startActivity(intent);
-            finish();
-            return;
-        }
 
-        mAllow = (Button)findViewById(R.id.allow);
-        mDeny = (Button)findViewById(R.id.deny);
-        
-        mAllow.setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                mAction = true;
-                mAllow.setEnabled(false);
-                mDeny.setEnabled(false);
-                handleAction();
-            }
-        });
-        mDeny.setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                mAction = false;
-                mAllow.setEnabled(false);
-                mDeny.setEnabled(false);
-                handleAction();
-            }
-        });
-        manageSocket(socket);
-    }
 }
index 8446105..7c0fd2e 100644 (file)
@@ -2,7 +2,7 @@ package com.koushikdutta.superuser;
 
 import android.os.Bundle;
 
-public class MultitaskSuRequestActivity extends MainActivity {
+public class MultitaskSuRequestActivity extends RequestActivity {
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
diff --git a/Superuser/src/com/koushikdutta/superuser/RequestActivity.java b/Superuser/src/com/koushikdutta/superuser/RequestActivity.java
new file mode 100644 (file)
index 0000000..e831631
--- /dev/null
@@ -0,0 +1,298 @@
+package com.koushikdutta.superuser;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+
+import junit.framework.Assert;
+import android.app.Activity;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.net.LocalSocket;
+import android.net.LocalSocketAddress;
+import android.net.LocalSocketAddress.Namespace;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+public class RequestActivity extends Activity {
+    private static final String LOGTAG = "Superuser";
+    int mCallerUid;
+    int mDesiredUid;
+    String mDesiredCmd;
+    
+    Spinner mSpinner;
+    ArrayAdapter<PackageInfo> mAdapter;
+    
+    Handler mHandler = new Handler();
+    
+    int mTimeLeft = 3;
+    
+    Button mAllow;
+    Button mDeny;
+
+    boolean mHandled;
+    void handleAction(boolean action) {
+        Assert.assertTrue(!mHandled);
+        mHandled = true;
+        try {
+            mSocket.getOutputStream().write((action ? "socket:ALLOW" : "socket:DENY").getBytes());
+        }
+        catch (Exception ex) {
+        }
+        try {
+            SuDatabaseHelper.setPolicy(this, mCallerUid, mDesiredCmd, action ? SuDatabaseHelper.POLICY_ALLOW : SuDatabaseHelper.POLICY_DENY, 0);
+        }
+        catch (Exception ex) {
+        }
+        finish();
+    }
+    
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        try {
+            if (mSocket != null)
+                mSocket.close();
+        }
+        catch (Exception ex) {
+        }
+    }
+
+    boolean mRequestReady;
+    void requestReady() {
+        findViewById(R.id.incoming).setVisibility(View.GONE);
+        findViewById(R.id.ready).setVisibility(View.VISIBLE);
+        
+        ListView list = (ListView)findViewById(R.id.list);
+        list.setEnabled(false);
+        list.setEmptyView(findViewById(R.id.unknown));
+        final PackageManager pm = getPackageManager();
+        String[] pkgs = pm.getPackagesForUid(mCallerUid);
+        TextView unknown = (TextView)findViewById(R.id.unknown);
+        unknown.setText(getString(R.string.unknown_uid, mCallerUid));
+        
+        ((TextView)findViewById(R.id.uid_header)).setText(Integer.toString(mDesiredUid));
+        ((TextView)findViewById(R.id.command_header)).setText(mDesiredCmd);
+
+        mAdapter = new ArrayAdapter<PackageInfo>(this, R.layout.packageinfo, R.id.title) {
+            @Override
+            public View getView(int position, View convertView, ViewGroup parent) {
+                convertView = super.getView(position, convertView, parent);
+                
+                PackageInfo pi = getItem(position);
+                ImageView icon = (ImageView)convertView.findViewById(R.id.image);
+                icon.setImageDrawable(pi.applicationInfo.loadIcon(pm));
+                ((TextView)convertView.findViewById(R.id.title)).setText(pi.applicationInfo.loadLabel(pm));
+                
+                return convertView;
+            }
+        };
+        
+        list.setAdapter(mAdapter);
+        if (pkgs != null) {
+            for (String pkg: pkgs) {
+                try {
+                    PackageInfo pi = pm.getPackageInfo(pkg, 0);
+                    ((TextView)findViewById(R.id.request)).setText(getString(R.string.application_request, pi.applicationInfo.loadLabel(pm)));
+                    mAdapter.add(pi);
+                    ((TextView)findViewById(R.id.app_header)).setText(pi.applicationInfo.loadLabel(pm));
+                    ((TextView)findViewById(R.id.package_header)).setText(pi.packageName);
+                    
+                    // could display them all, but screw it...
+                    // maybe a better ux for this later
+                    break;
+                }
+                catch (Exception ex) {
+                }
+            }
+        }
+        
+        new Runnable() {
+            public void run() {
+                mAllow.setText(getString(R.string.allow) + " (" + mTimeLeft + ")");
+                if (mTimeLeft-- <= 0) {
+                    mAllow.setText(getString(R.string.allow));
+                    if (!mHandled)
+                        mAllow.setEnabled(true);
+                    return;
+                }
+                mHandler.postDelayed(this, 1000);
+            };
+        }.run();
+    }
+
+    void manageSocket(final String socket) {
+        new Thread() {
+            @Override
+            public void run() {
+                try {
+                    mSocket = new LocalSocket();
+                    mSocket.connect(new LocalSocketAddress(socket, Namespace.FILESYSTEM));
+                    Log.i(LOGTAG, "connected to socket.");
+
+                    DataInputStream is = new DataInputStream(mSocket.getInputStream());
+
+                    int protocolVersion = is.readInt();
+                    Log.d(LOGTAG, "INT32:PROTO VERSION = " + protocolVersion);
+
+                    int exeSizeMax = is.readInt();
+                    Log.d(LOGTAG, "UINT32:FIELD7MAX = " + exeSizeMax);
+                    int cmdSizeMax = is.readInt();
+                    Log.d(LOGTAG, "UINT32:FIELD9MAX = " + cmdSizeMax);
+                    mCallerUid = is.readInt();
+                    Log.d(LOGTAG, "UINT32:CALLER = " + mCallerUid);
+                    mDesiredUid = is.readInt();
+                    Log.d(LOGTAG, "UINT32:TO = " + mDesiredUid);
+
+                    int exeSize = is.readInt();
+                    Log.d(LOGTAG, "UINT32:EXESIZE = " + exeSize);
+                    if (exeSize > exeSizeMax) {
+                        throw new IOException("Incomming string bigger than allowed");
+                    }
+                    byte[] buf = new byte[exeSize];
+                    is.read(buf);
+                    String callerBin = new String(buf, 0, exeSize - 1);
+                    Log.d(LOGTAG, "STRING:EXE = " + callerBin);
+
+                    int cmdSize = is.readInt();
+                    Log.d(LOGTAG, "UINT32:CMDSIZE = " + cmdSize);
+                    if (cmdSize > cmdSizeMax) {
+                        throw new IOException("Incomming string bigger than allowed");
+                    }
+                    buf = new byte[cmdSize];
+                    is.read(buf);
+                    mDesiredCmd = new String(buf, 0, cmdSize - 1);
+                    Log.d(LOGTAG, "STRING:CMD = " + mDesiredCmd);
+                    runOnUiThread(new Runnable() {
+                        @Override
+                        public void run() {
+                            mRequestReady = true;
+                            requestReady();
+                        }
+                    });
+                    
+                    // now, even though the request is ready, keep reading.
+                    // if the su process dies for whatever, the socket will close.
+                    // in that case, an exception will throw and this activity will finish.
+                    is.read();
+                }
+                catch (Exception ex) {
+                    Log.i(LOGTAG, ex.getMessage(), ex);
+                    try {
+                        mSocket.close();
+                    }
+                    catch (Exception e) {
+                    }
+                    runOnUiThread(new Runnable() {
+                        @Override
+                        public void run() {
+                            finish();
+                        }
+                    });
+                }
+            }
+        }.start();
+    }
+    
+
+    RadioGroup mRemember;
+    
+    LocalSocket mSocket;
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Intent intent = getIntent();
+        if (intent == null) {
+            finish();
+            return;
+        }
+
+        String socket = intent.getStringExtra("socket");
+        if (socket == null) {
+            finish();
+            return;
+        }
+     
+        if (getClass() == RequestActivity.class) {
+            // TODO: is this the right way to do this?
+            // maybe queue requests to maintain the order?
+            // fragile apps may have race conditions that occur
+            // if the su requests are handled LIFO.
+
+            // MainActivity is actually just a passthrough to a new task
+            // stack.
+            // Pretty much every superuser implementation i've seen craps out if there
+            // is more than 1 su request at a time. Each subsequent su request
+            // will get "lost" because there can only be one instance of the activity
+            // at a time. Really annoying. This fixes that.
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            intent.setClass(this, MultitaskSuRequestActivity.class);
+            startActivity(intent);
+            finish();
+            return;
+        }
+        
+        setContentView();
+
+        manageSocket(socket);
+    }
+    
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        
+        setContentView();
+    }
+    
+    void setContentView() {
+        setContentView(R.layout.request);
+        
+        ContextThemeWrapper wrapper =  new ContextThemeWrapper(this, R.style.AppDarkTheme);
+        LayoutInflater darkInflater = (LayoutInflater)wrapper.getSystemService(LAYOUT_INFLATER_SERVICE);
+        ViewGroup root = (ViewGroup)findViewById(R.id.root);
+        darkInflater.inflate(R.layout.request_buttons, root);
+
+        mRemember = (RadioGroup)findViewById(R.id.remember);
+        RadioButton rememberFor = (RadioButton)findViewById(R.id.remember_for);
+        rememberFor.setText(getString(R.string.remember_for, 10));
+
+        mAllow = (Button)findViewById(R.id.allow);
+        mDeny = (Button)findViewById(R.id.deny);
+        
+        mAllow.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mAllow.setEnabled(false);
+                mDeny.setEnabled(false);
+                handleAction(true);
+            }
+        });
+        mDeny.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mAllow.setEnabled(false);
+                mDeny.setEnabled(false);
+                handleAction(false);
+            }
+        });
+        
+        if (mRequestReady)
+            requestReady();
+    }
+}
diff --git a/Superuser/src/com/koushikdutta/superuser/SuApplication.java b/Superuser/src/com/koushikdutta/superuser/SuApplication.java
new file mode 100644 (file)
index 0000000..9b850a3
--- /dev/null
@@ -0,0 +1,12 @@
+package com.koushikdutta.superuser;
+
+import android.app.Application;
+
+public class SuApplication extends Application {
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        
+        new SuDatabaseHelper(this).getWritableDatabase().close();
+    }
+}
diff --git a/Superuser/src/com/koushikdutta/superuser/SuDatabaseHelper.java b/Superuser/src/com/koushikdutta/superuser/SuDatabaseHelper.java
new file mode 100644 (file)
index 0000000..8f31003
--- /dev/null
@@ -0,0 +1,41 @@
+package com.koushikdutta.superuser;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+public class SuDatabaseHelper extends SQLiteOpenHelper {
+    public SuDatabaseHelper(Context context) {
+        super(context, "su.sqlite", null, 1);
+    }
+
+    @Override
+    public void onCreate(SQLiteDatabase db) {
+        onUpgrade(db, 0, 1);
+    }
+
+    @Override
+    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+        if (oldVersion == 0) {
+            db.execSQL("create table if not exists uid_policy (policy text, until integer, command text, uid integer, primary key(uid, command))");
+            oldVersion = 1;
+        }
+    }
+    
+    public static final String POLICY_ALLOW = "allow";
+    public static final String POLICY_DENY = "deny";
+    public static final String POLICY_INTERACTOVE = "interactive";
+    
+    
+    public static void setPolicy(Context context, int uid, String command, String policy, int until) {
+        SQLiteDatabase db = new SuDatabaseHelper(context).getWritableDatabase();
+        
+        ContentValues values = new ContentValues();
+        values.put("uid", uid);
+        values.put("command", command);
+        values.put("policy", policy);
+        values.put("until", until);
+        db.replace("uid_policy", null, values);
+    }
+}
index 5b0083a..17af981 100644 (file)
@@ -8,7 +8,7 @@ public class SuReceiver extends BroadcastReceiver {
     @Override
     public void onReceive(Context context, Intent intent) {
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        intent.setClass(context, MainActivity.class);
+        intent.setClass(context, RequestActivity.class);
         context.startActivity(intent);
     }
 }