1 package com.koushikdutta.superuser;
3 import java.io.DataInputStream;
5 import java.util.HashMap;
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;
34 import com.koushikdutta.superuser.db.SuDatabaseHelper;
35 import com.koushikdutta.superuser.db.UidPolicy;
36 import com.koushikdutta.superuser.util.Settings;
38 @SuppressLint("ValidFragment")
39 public class MultitaskSuRequestActivity extends FragmentActivity {
40 private static final String LOGTAG = "Superuser";
48 Handler mHandler = new Handler();
57 public int getGracePeriod() {
61 void handleAction(boolean action, Integer until) {
62 Assert.assertTrue(!mHandled);
65 mSocket.getOutputStream().write((action ? "socket:ALLOW" : "socket:DENY").getBytes());
67 catch (Exception ex) {
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;
78 else if (id == R.string.remember_forever) {
82 else if (mRemember.isShown()) {
83 if (mRemember.getCheckedRadioButtonId() == R.id.remember_for) {
84 until = (int)(System.currentTimeMillis() / 1000) + getGracePeriod() * 60;
86 else if (mRemember.getCheckedRadioButtonId() == R.id.remember_forever) {
91 // got a policy? let's set it.
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;
100 policy.desiredUid = mDesiredUid;
101 SuDatabaseHelper.setPolicy(this, policy);
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.
106 catch (Exception ex) {
112 protected void onDestroy() {
115 handleAction(false, -1);
120 catch (Exception ex) {
122 new File(mSocketPath).delete();
125 public static final String PERMISSION = "android.permission.ACCESS_SUPERUSER";
127 boolean mRequestReady;
128 void requestReady() {
129 findViewById(R.id.incoming).setVisibility(View.GONE);
130 findViewById(R.id.ready).setVisibility(View.VISIBLE);
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));
138 final View appInfo = findViewById(R.id.app_info);
139 appInfo.setOnClickListener(new OnClickListener() {
141 public void onClick(View v) {
142 if (packageInfo.getVisibility() == View.GONE) {
143 appInfo.setVisibility(View.GONE);
144 packageInfo.setVisibility(View.VISIBLE);
149 packageInfo.setOnClickListener(new OnClickListener() {
151 public void onClick(View v) {
152 if (appInfo.getVisibility() == View.GONE) {
153 appInfo.setVisibility(View.VISIBLE);
154 packageInfo.setVisibility(View.GONE);
159 ((TextView)findViewById(R.id.uid_header)).setText(Integer.toString(mDesiredUid));
160 ((TextView)findViewById(R.id.command_header)).setText(mDesiredCmd);
162 boolean superuserDeclared = false;
163 boolean granted = false;
164 if (pkgs != null && pkgs.length > 0) {
165 for (String pkg: pkgs) {
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));
173 ((TextView)findViewById(R.id.app_header)).setText(pi.applicationInfo.loadLabel(pm));
174 ((TextView)findViewById(R.id.package_header)).setText(pi.packageName);
176 if (pi.requestedPermissions != null) {
177 for (String perm: pi.requestedPermissions) {
178 if (PERMISSION.equals(perm)) {
179 superuserDeclared = true;
185 granted |= checkPermission(PERMISSION, mPid, mCallerUid) == PackageManager.PERMISSION_GRANTED;
187 // could display them all, but screw it...
188 // maybe a better ux for this later
191 catch (Exception ex) {
194 findViewById(R.id.unknown).setVisibility(View.GONE);
197 if (!superuserDeclared) {
198 findViewById(R.id.developer_warning).setVisibility(View.VISIBLE);
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
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() {
213 handleAction(false, 0);
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))
225 // check if the permission must be granted
226 if (Settings.getRequirePermission(MultitaskSuRequestActivity.this) && !granted)
228 Log.i(LOGTAG, "Automatically allowing due to user preference");
229 mHandler.post(new Runnable() {
233 handleAction(true, 0);
237 case Settings.AUTOMATIC_RESPONSE_DENY:
238 Log.i(LOGTAG, "Automatically denying due to user preference");
239 mHandler.post(new Runnable() {
243 handleAction(false, 0);
251 mAllow.setText(getString(R.string.allow) + " (" + mTimeLeft + ")");
252 if (mTimeLeft-- <= 0) {
253 mAllow.setText(getString(R.string.allow));
255 mAllow.setEnabled(true);
258 mHandler.postDelayed(this, 1000);
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>() {
268 put("command", 2048);
272 private static int getValueMax(String name) {
273 Integer max = SU_PROTOCOL_VALUE_MAX.get(name);
275 return SU_PROTOCOL_VALUE_MAX_DEFAULT;
279 void manageSocket() {
284 mSocket = new LocalSocket();
285 mSocket.connect(new LocalSocketAddress(mSocketPath, Namespace.FILESYSTEM));
287 DataInputStream is = new DataInputStream(mSocket.getInputStream());
289 ContentValues payload = new ContentValues();
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))
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() {
321 mRequestReady = true;
327 catch (Exception ex) {
328 Log.i(LOGTAG, ex.getMessage(), ex);
332 catch (Exception e) {
334 runOnUiThread(new Runnable() {
346 RadioGroup mRemember;
350 protected void onCreate(Bundle savedInstanceState) {
351 super.onCreate(savedInstanceState);
353 Intent intent = getIntent();
354 if (intent == null) {
359 mSocketPath = intent.getStringExtra("socket");
360 if (mSocketPath == null) {
370 // watch for the socket disappearing. that means su died.
375 if (!new File(mSocketPath).exists()) {
380 mHandler.postDelayed(this, 1000);
384 mHandler.postDelayed(new Runnable() {
390 handleAction(false, -1);
392 }, Settings.getRequestTimeout(this) * 1000);
396 public void onConfigurationChanged(Configuration newConfig) {
397 super.onConfigurationChanged(newConfig);
402 final int[] mSpinnerIds = new int[] {
403 R.string.this_time_only,
404 R.string.remember_for,
405 R.string.remember_forever
409 mAllow.setEnabled(false);
410 mDeny.setEnabled(false);
411 handleAction(true, null);
415 mAllow.setEnabled(false);
416 mDeny.setEnabled(false);
417 handleAction(false, null);
421 ArrayAdapter<String> mSpinnerAdapter;
422 void setContentView() {
423 setContentView(R.layout.request);
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()));
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()));
435 mAllow = (Button)findViewById(R.id.allow);
436 mDeny = (Button)findViewById(R.id.deny);
438 mAllow.setOnClickListener(new OnClickListener() {
440 public void onClick(View v) {
441 if (!Settings.isPinProtected(MultitaskSuRequestActivity.this)) {
446 ViewGroup ready = (ViewGroup)findViewById(R.id.root);
447 ready.removeAllViews();
449 PinViewHelper pin = new PinViewHelper(getLayoutInflater(), ready, null) {
451 public void onEnter(String password) {
452 super.onEnter(password);
453 if (Settings.checkPin(MultitaskSuRequestActivity.this, password)) {
457 Toast.makeText(MultitaskSuRequestActivity.this, getString(R.string.incorrect_pin), Toast.LENGTH_SHORT).show();
461 public void onCancel() {
467 ready.addView(pin.getView());
470 mDeny.setOnClickListener(new OnClickListener() {
472 public void onClick(View v) {