OSDN Git Service

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