OSDN Git Service

fa0c29d073b27515a898b4df4db11a8af00685eb
[android-x86/external-koush-Superuser.git] / Superuser / src / com / koushikdutta / superuser / MultitaskSuRequestActivity.java
1 package com.koushikdutta.superuser;
2
3 import java.io.DataInputStream;
4 import java.io.File;
5 import java.util.HashMap;
6
7 import junit.framework.Assert;
8 import android.annotation.SuppressLint;
9 import android.content.ContentValues;
10 import android.content.Intent;
11 import android.content.pm.PackageInfo;
12 import android.content.pm.PackageManager;
13 import android.content.res.Configuration;
14 import android.net.LocalSocket;
15 import android.net.LocalSocketAddress;
16 import android.net.LocalSocketAddress.Namespace;
17 import android.os.Bundle;
18 import android.os.Handler;
19 import android.support.v4.app.FragmentActivity;
20 import android.support.v4.app.FragmentTransaction;
21 import android.util.Log;
22 import android.view.View;
23 import android.view.View.OnClickListener;
24 import android.view.ViewGroup;
25 import android.widget.ArrayAdapter;
26 import android.widget.Button;
27 import android.widget.ImageView;
28 import android.widget.RadioButton;
29 import android.widget.RadioGroup;
30 import android.widget.Spinner;
31 import android.widget.TextView;
32 import android.widget.Toast;
33
34 import com.koushikdutta.superuser.db.SuDatabaseHelper;
35 import com.koushikdutta.superuser.db.UidPolicy;
36 import com.koushikdutta.superuser.util.Settings;
37
38 @SuppressLint("ValidFragment")
39 public class MultitaskSuRequestActivity extends FragmentActivity {
40     private static final String LOGTAG = "Superuser";
41     int mCallerUid;
42     int mDesiredUid;
43     String mDesiredCmd;
44     int mPid;
45     
46     Spinner mSpinner;
47     
48     Handler mHandler = new Handler();
49     
50     int mTimeLeft = 3;
51     
52     Button mAllow;
53     Button mDeny;
54
55     boolean mHandled;
56     
57     public int getGracePeriod() {
58         return 10;
59     }
60     
61     void handleAction(boolean action, Integer until) {
62         Assert.assertTrue(!mHandled);
63         mHandled = true;
64         try {
65             mSocket.getOutputStream().write((action ? "socket:ALLOW" : "socket:DENY").getBytes());
66         }
67         catch (Exception ex) {
68         }
69         try {
70             if (until == null) {
71                 until = -1;
72                 if (mSpinner.isShown()) {
73                     int pos = mSpinner.getSelectedItemPosition();
74                     int id = mSpinnerIds[pos];
75                     if (id == R.string.remember_for) {
76                         until = (int)(System.currentTimeMillis() / 1000) + getGracePeriod() * 60;
77                     }
78                     else if (id == R.string.remember_forever) {
79                         until = 0;
80                     }
81                 }
82                 else if (mRemember.isShown()) {
83                     if (mRemember.getCheckedRadioButtonId() == R.id.remember_for) {
84                         until = (int)(System.currentTimeMillis() / 1000) + getGracePeriod() * 60;
85                     }
86                     else if (mRemember.getCheckedRadioButtonId() == R.id.remember_forever) {
87                         until = 0;
88                     }
89                 }
90             }
91             // got a policy? let's set it.
92             if (until != -1) {
93                 UidPolicy policy = new UidPolicy();
94                 policy.policy = action ? UidPolicy.ALLOW : UidPolicy.DENY;
95                 policy.uid = mCallerUid;
96                 // for now just approve all commands, since per command approval is stupid
97 //                policy.command = mDesiredCmd;
98                 policy.command = null;
99                 policy.until = until;
100                 policy.desiredUid = mDesiredUid;
101                 SuDatabaseHelper.setPolicy(this, policy);
102             }
103             // TODO: logging? or should su binary handle that via broadcast?
104             // Probably the latter, so it is consolidated and from the system of record.
105         }
106         catch (Exception ex) {
107         }
108         finish();
109     }
110     
111     @Override
112     protected void onDestroy() {
113         super.onDestroy();
114         if (!mHandled)
115             handleAction(false, -1);
116         try {
117             if (mSocket != null)
118                 mSocket.close();
119         }
120         catch (Exception ex) {
121         }
122         new File(mSocketPath).delete();
123     }
124
125     public static final String PERMISSION = "android.permission.ACCESS_SUPERUSER";
126     
127     boolean mRequestReady;
128     void requestReady() {
129         findViewById(R.id.incoming).setVisibility(View.GONE);
130         findViewById(R.id.ready).setVisibility(View.VISIBLE);
131         
132         final View packageInfo = findViewById(R.id.packageinfo);
133         final PackageManager pm = getPackageManager();
134         String[] pkgs = pm.getPackagesForUid(mCallerUid);
135         TextView unknown = (TextView)findViewById(R.id.unknown);
136         unknown.setText(getString(R.string.unknown_uid, mCallerUid));
137
138         final View appInfo = findViewById(R.id.app_info);
139         appInfo.setOnClickListener(new OnClickListener() {
140             @Override
141             public void onClick(View v) {
142                 if (packageInfo.getVisibility() == View.GONE) {
143                     appInfo.setVisibility(View.GONE);
144                     packageInfo.setVisibility(View.VISIBLE);
145                 }
146             }
147         });
148         
149         packageInfo.setOnClickListener(new OnClickListener() {
150             @Override
151             public void onClick(View v) {
152                 if (appInfo.getVisibility() == View.GONE) {
153                     appInfo.setVisibility(View.VISIBLE);
154                     packageInfo.setVisibility(View.GONE);
155                 }
156             }
157         });
158         
159         ((TextView)findViewById(R.id.uid_header)).setText(Integer.toString(mDesiredUid));
160         ((TextView)findViewById(R.id.command_header)).setText(mDesiredCmd);
161
162         boolean superuserDeclared = false;
163         boolean granted = false;
164         if (pkgs != null && pkgs.length > 0) {
165             for (String pkg: pkgs) {
166                 try {
167                     PackageInfo pi = pm.getPackageInfo(pkg, PackageManager.GET_PERMISSIONS);
168                     ((TextView)findViewById(R.id.request)).setText(getString(R.string.application_request, pi.applicationInfo.loadLabel(pm)));
169                     ImageView icon = (ImageView)packageInfo.findViewById(R.id.image);
170                     icon.setImageDrawable(pi.applicationInfo.loadIcon(pm));
171                     ((TextView)packageInfo.findViewById(R.id.title)).setText(pi.applicationInfo.loadLabel(pm));
172                     
173                     ((TextView)findViewById(R.id.app_header)).setText(pi.applicationInfo.loadLabel(pm));
174                     ((TextView)findViewById(R.id.package_header)).setText(pi.packageName);
175                     
176                     if (pi.requestedPermissions != null) {
177                         for (String perm: pi.requestedPermissions) {
178                             if (PERMISSION.equals(perm)) {
179                                 superuserDeclared = true;
180                                 break;
181                             }
182                         }
183                     }
184                     
185                     granted |= checkPermission(PERMISSION, mPid, mCallerUid) == PackageManager.PERMISSION_GRANTED;
186                     
187                     // could display them all, but screw it...
188                     // maybe a better ux for this later
189                     break;
190                 }
191                 catch (Exception ex) {
192                 }
193             }
194             findViewById(R.id.unknown).setVisibility(View.GONE);
195         }
196         
197         if (!superuserDeclared) {
198             findViewById(R.id.developer_warning).setVisibility(View.VISIBLE);
199         }
200         
201         // handle automatic responses
202         // these will be considered permanent user policies
203         // even though they are automatic.
204         // this is so future su requests dont invoke ui
205
206         // handle declared permission
207         if (Settings.getRequirePermission(MultitaskSuRequestActivity.this) && !superuserDeclared) {
208             Log.i(LOGTAG, "Automatically denying due to missing permission");
209             mHandler.post(new Runnable() {
210                 @Override
211                 public void run() {
212                     if (!mHandled)
213                         handleAction(false, 0);
214                 }
215             });
216             return;
217         }
218
219         // automatic response
220         switch (Settings.getAutomaticResponse(MultitaskSuRequestActivity.this)) {
221         case Settings.AUTOMATIC_RESPONSE_ALLOW:
222 //            // automatic response and pin can not be used together
223 //            if (Settings.isPinProtected(MultitaskSuRequestActivity.this))
224 //                break;
225             // check if the permission must be granted 
226             if (Settings.getRequirePermission(MultitaskSuRequestActivity.this) && !granted)
227                 break;
228             Log.i(LOGTAG, "Automatically allowing due to user preference");
229             mHandler.post(new Runnable() {
230                 @Override
231                 public void run() {
232                     if (!mHandled)
233                         handleAction(true, 0);
234                 }
235             });
236             return;
237         case Settings.AUTOMATIC_RESPONSE_DENY:
238             Log.i(LOGTAG, "Automatically denying due to user preference");
239             mHandler.post(new Runnable() {
240                 @Override
241                 public void run() {
242                     if (!mHandled)
243                         handleAction(false, 0);
244                 }
245             });
246             return;
247         }
248         
249         new Runnable() {
250             public void run() {
251                 mAllow.setText(getString(R.string.allow) + " (" + mTimeLeft + ")");
252                 if (mTimeLeft-- <= 0) {
253                     mAllow.setText(getString(R.string.allow));
254                     if (!mHandled)
255                         mAllow.setEnabled(true);
256                     return;
257                 }
258                 mHandler.postDelayed(this, 1000);
259             };
260         }.run();
261     }
262
263     private final static int SU_PROTOCOL_PARAM_MAX = 20;
264     private final static int SU_PROTOCOL_NAME_MAX = 20;
265     private final static int SU_PROTOCOL_VALUE_MAX_DEFAULT = 256;
266     private final static HashMap<String, Integer> SU_PROTOCOL_VALUE_MAX = new HashMap<String, Integer>() {
267         {
268             put("command", 2048);
269         }
270     };
271     
272     private static int getValueMax(String name) {
273         Integer max = SU_PROTOCOL_VALUE_MAX.get(name);
274         if (max == null)
275             return SU_PROTOCOL_VALUE_MAX_DEFAULT;
276         return max;
277     }
278     
279     void manageSocket() {
280         new Thread() {
281             @Override
282             public void run() {
283                 try {
284                     mSocket = new LocalSocket();
285                     mSocket.connect(new LocalSocketAddress(mSocketPath, Namespace.FILESYSTEM));
286
287                     DataInputStream is = new DataInputStream(mSocket.getInputStream());
288                     
289                     ContentValues payload = new ContentValues();
290
291
292                     for (int i = 0; i < SU_PROTOCOL_PARAM_MAX; i++) {
293                         int nameLen = is.readInt();
294                         if (nameLen > SU_PROTOCOL_NAME_MAX)
295                             throw new IllegalArgumentException("name length too long: " + nameLen);
296                         byte[] nameBytes = new byte[nameLen];
297                         is.readFully(nameBytes);
298                         String name = new String(nameBytes);
299                         int dataLen = is.readInt();
300                         if (dataLen > getValueMax(name))
301                             throw new IllegalArgumentException(name + " data length too long: " + dataLen);
302                         byte[] dataBytes = new byte[dataLen];
303                         is.readFully(dataBytes);
304                         String data = new String(dataBytes);
305                         payload.put(name, data);
306 //                        Log.i(LOGTAG, name);
307 //                        Log.i(LOGTAG, data);
308                         if ("eof".equals(name))
309                             break;
310                     }
311                     
312                     int protocolVersion = payload.getAsInteger("version");
313                     mCallerUid = payload.getAsInteger("from.uid");
314                     mDesiredUid = payload.getAsByte("to.uid");
315                     mDesiredCmd = payload.getAsString("command");
316                     String calledBin = payload.getAsString("from.bin");
317                     mPid = payload.getAsInteger("pid");
318                     runOnUiThread(new Runnable() {
319                         @Override
320                         public void run() {
321                             mRequestReady = true;
322                             requestReady();
323                         }
324                     });                    
325                     
326                 }
327                 catch (Exception ex) {
328                     Log.i(LOGTAG, ex.getMessage(), ex);
329                     try {
330                         mSocket.close();
331                     }
332                     catch (Exception e) {
333                     }
334                     runOnUiThread(new Runnable() {
335                         @Override
336                         public void run() {
337                             finish();
338                         }
339                     });
340                 }
341             }
342         }.start();
343     }
344     
345
346     RadioGroup mRemember;
347     
348     LocalSocket mSocket;
349     @Override
350     protected void onCreate(Bundle savedInstanceState) {
351         super.onCreate(savedInstanceState);
352
353         Intent intent = getIntent();
354         if (intent == null) {
355             finish();
356             return;
357         }
358
359         mSocketPath = intent.getStringExtra("socket");
360         if (mSocketPath == null) {
361             finish();
362             return;
363         }
364
365         setContentView();
366
367         manageSocket();
368         
369         
370         // watch for the socket disappearing. that means su died.
371         new Runnable() {
372             public void run() {
373                 if (isFinishing())
374                     return;
375                 if (!new File(mSocketPath).exists()) {
376                     finish();
377                     return;
378                 }
379                 
380                 mHandler.postDelayed(this, 1000);
381             };
382         }.run();
383         
384         mHandler.postDelayed(new Runnable() {
385             @Override
386             public void run() {
387                 if (isFinishing())
388                     return;
389                 if (!mHandled)
390                     handleAction(false, -1);
391             }
392         }, Settings.getRequestTimeout(this) * 1000);
393     }
394     
395     @Override
396     public void onConfigurationChanged(Configuration newConfig) {
397         super.onConfigurationChanged(newConfig);
398         
399         setContentView();
400     }
401     
402     final int[] mSpinnerIds = new int[] {
403             R.string.this_time_only,
404             R.string.remember_for,
405             R.string.remember_forever
406     };
407     
408     void approve() {
409         mAllow.setEnabled(false);
410         mDeny.setEnabled(false);
411         handleAction(true, null);
412     }
413
414     void deny() {
415         mAllow.setEnabled(false);
416         mDeny.setEnabled(false);
417         handleAction(false, null);
418     }
419
420     String mSocketPath;
421     ArrayAdapter<String> mSpinnerAdapter;
422     void setContentView() {
423         setContentView(R.layout.request);
424
425         mSpinner = (Spinner)findViewById(R.id.remember_choices);
426         mSpinner.setAdapter(mSpinnerAdapter = new ArrayAdapter<String>(this, R.layout.request_spinner_choice, R.id.request_spinner_choice));
427         for (int id: mSpinnerIds) {
428             mSpinnerAdapter.add(getString(id, getGracePeriod()));
429         }
430         
431         mRemember = (RadioGroup)findViewById(R.id.remember);
432         RadioButton rememberFor = (RadioButton)findViewById(R.id.remember_for);
433         rememberFor.setText(getString(R.string.remember_for, getGracePeriod()));
434
435         mAllow = (Button)findViewById(R.id.allow);
436         mDeny = (Button)findViewById(R.id.deny);
437         
438         mAllow.setOnClickListener(new OnClickListener() {
439             @Override
440             public void onClick(View v) {
441                 if (!Settings.isPinProtected(MultitaskSuRequestActivity.this)) {
442                     approve();
443                     return;
444                 }
445                 
446                 ViewGroup ready = (ViewGroup)findViewById(R.id.root);
447                 ready.removeAllViews();
448                 
449                 PinViewHelper pin = new PinViewHelper(getLayoutInflater(), ready, null) {
450                     @Override
451                     public void onEnter(String password) {
452                         super.onEnter(password);
453                         if (Settings.checkPin(MultitaskSuRequestActivity.this, password)) {
454                             approve();
455                         }
456                         else {
457                             Toast.makeText(MultitaskSuRequestActivity.this, getString(R.string.incorrect_pin), Toast.LENGTH_SHORT).show();
458                         }
459                     }
460                     @Override
461                     public void onCancel() {
462                         super.onCancel();
463                         deny();
464                     }
465                 };
466                 
467                 ready.addView(pin.getView());
468             }
469         });
470         mDeny.setOnClickListener(new OnClickListener() {
471             @Override
472             public void onClick(View v) {
473                 deny();
474             }
475         });
476         
477         if (mRequestReady)
478             requestReady();
479     }
480 }