package com.noshufou.android.su; import com.noshufou.android.su.util.Util; import org.apache.http.util.ByteArrayBuffer; import org.json.JSONException; import org.json.JSONObject; import android.app.AlertDialog; import android.app.NotificationManager; import android.content.Context; import android.content.DialogInterface; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import android.os.AsyncTask; import android.os.Build.VERSION; import android.os.Bundle; import android.support.v4.app.ListFragment; import android.text.Spannable; import android.text.style.ForegroundColorSpan; import android.util.Log; 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.ProgressBar; import android.widget.TextView; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import java.util.regex.Matcher; import java.util.regex.Pattern; public class UpdaterFragment extends ListFragment implements OnClickListener { private static final String TAG = "Su.UpdaterFragment"; public static final int NOTIFICATION_ID = 42; private String MANIFEST_URL; private int CONSOLE_GREY; private int CONSOLE_RED; private int CONSOLE_GREEN; private enum Step { DOWNLOAD_MANIFEST, DOWNLOAD_BUSYBOX; } private class Manifest { public String version; public int versionCode; public String binaryUrl; public String binaryMd5; public String busyboxUrl; public String busyboxMd5; public boolean populate(JSONObject manifest) { try { version = manifest.getString("version"); versionCode = manifest.getInt("version-code"); binaryUrl = manifest.getString("binary"); binaryMd5 = manifest.getString("binary-md5sum"); busyboxUrl = manifest.getString("busybox"); busyboxMd5 = manifest.getString("busybox-md5sum"); } catch (JSONException e) { return false; } // verify that all values have been properly initialized if (version == null || versionCode == 0 || binaryUrl == null || binaryMd5 == null || busyboxUrl == null || busyboxMd5 == null) { return false; } return true; } } private Manifest mManifest; private String mBusyboxPath = ""; private Step mCurrentStep = Step.DOWNLOAD_MANIFEST; private ProgressBar mTitleProgress; private ProgressBar mProgressBar; private TextView mStatusText; private Button mActionButton; private UpdateTask mUpdateTask; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { MANIFEST_URL = getString(Integer.parseInt(VERSION.SDK) < 5? R.string.updater_manifest_legacy:R.string.updater_manifest); CONSOLE_GREY = getActivity().getResources().getColor(R.color.console_grey); CONSOLE_RED = getActivity().getResources().getColor(R.color.console_red); CONSOLE_GREEN = getActivity().getResources().getColor(R.color.console_green); View view = inflater.inflate(R.layout.fragment_updater, container, false); ((TextView)view.findViewById(R.id.title_text)).setText(R.string.updater_title); mTitleProgress = (ProgressBar) view.findViewById(R.id.title_refresh_progress); mProgressBar = (ProgressBar) view.findViewById(R.id.progress_bar); mStatusText = (TextView) view.findViewById(R.id.status); mActionButton = (Button) view.findViewById(R.id.action_button); mActionButton.setOnClickListener(this); return view; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); setListAdapter(new ConsoleAdapter(getActivity())); mProgressBar.setInterpolator(getActivity(), android.R.anim.accelerate_decelerate_interpolator); mUpdateTask = new UpdateTask(); mUpdateTask.execute(); } @Override public void onStop() { super.onStop(); if (mUpdateTask != null) { mUpdateTask.cancel(false); } } @Override public void onClick(View view) { mUpdateTask = new UpdateTask(); mUpdateTask.execute(); } @Override public ConsoleAdapter getListAdapter() { return (ConsoleAdapter) super.getListAdapter(); } private class UpdateTask extends AsyncTask<Void, Object, Integer> { public static final int STATUS_AWAITING_ACTION = 1; public static final int STATUS_FINISHED_SUCCESSFUL = 2; public static final int STATUS_FINISHED_FAIL = 3; public static final int STATUS_FINISHED_NO_NEED = 4; public static final int STATUS_CONN_FAILED = 5; public static final int STATUS_FINISHED_FAIL_REMOUNT = R.string.updater_failed_remount; public static final int STATUS_FINISHED_FAIL_SBIN = R.string.updater_bad_install_location_explained; public static final int STATUS_CANCELLED = 8; @Override protected void onPreExecute() { mTitleProgress.setVisibility(View.VISIBLE); mStatusText.setText(R.string.updater_working); mActionButton.setText(R.string.updater_working); mActionButton.setEnabled(false); } @Override protected Integer doInBackground(Void... params) { int progressTotal = 0; int progressStep = 0; switch (mCurrentStep) { case DOWNLOAD_MANIFEST: progressTotal = 4; if (isCancelled()) { return STATUS_CANCELLED; } progressStep++; publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_step_download_manifest); if (downloadFile(MANIFEST_URL, "manifest")) { publishProgress(progressTotal, progressStep, progressStep, R.string.updater_ok, CONSOLE_GREEN); } else { publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_fail, CONSOLE_RED); return STATUS_CONN_FAILED; } // Parse manifest // TODO: Actually parse the manifest here, as of now it's being // done at download time. if (isCancelled()) { return STATUS_CANCELLED; } progressStep++; publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_step_parse_manifest); if (mManifest == null) { publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_fail, CONSOLE_RED); return STATUS_FINISHED_FAIL; } publishProgress(progressTotal, progressStep, progressStep, R.string.updater_ok, CONSOLE_GREEN); // Display the latest version if (isCancelled()) { return STATUS_CANCELLED; } progressStep++; publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_step_latest_version); if (mManifest.version != null) { publishProgress(progressTotal, progressStep, progressStep, mManifest.version, CONSOLE_GREEN); } else { publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_fail, CONSOLE_RED); } // Check the currently installed version if (isCancelled()) { return STATUS_CANCELLED; } progressStep++; publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_step_check_installed_version); int installedVersionCode = Util.getSuVersionCode(); String installedVersion = Util.getSuVersion(); if (installedVersion == null) { installedVersion = "legacy"; } if (installedVersionCode < mManifest.versionCode) { publishProgress(progressTotal, progressStep, progressStep, installedVersion, CONSOLE_RED); mCurrentStep = Step.DOWNLOAD_BUSYBOX; return STATUS_AWAITING_ACTION; } else { publishProgress(progressTotal, progressStep, progressStep, installedVersion, CONSOLE_GREEN); mCurrentStep = Step.DOWNLOAD_BUSYBOX; return STATUS_FINISHED_NO_NEED; } case DOWNLOAD_BUSYBOX: // Fix the db if necessary. A bug in the 2.3x binary could cause // the su binary to fail if there is no prefs table, which would // happen if the user never opened the app. Fringe case, but // needs to be addressed. boolean fixDb = (Util.getSuVersionCode() == 0); if (fixDb) { progressTotal = 14; if (isCancelled()) { return STATUS_CANCELLED; } progressStep = 1; publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_step_fix_db); SQLiteDatabase db = null; try { db = getActivity().openOrCreateDatabase( "permissions.sqlite", Context.MODE_PRIVATE, null); // We just need to make sure all the tables exist and have the proper // columns. We'll delete this DB at the end of the update. db.execSQL("CREATE TABLE IF NOT EXISTS apps (_id INTEGER, uid INTEGER, " + "package TEXT, name TEXT, exec_uid INTEGER, exec_cmd TEXT, " + " allow INTEGER, PRIMARY KEY (_id), UNIQUE (uid,exec_uid,exec_cmd))"); db.execSQL("CREATE TABLE IF NOT EXISTS logs (_id INTEGER, app_id INTEGER, " + "date INTEGER, type INTEGER, PRIMARY KEY (_id))"); db.execSQL("CREATE TABLE IF NOT EXISTS prefs (_id INTEGER, key TEXT, " + "value TEXT, PRIMARY KEY (_id))"); } catch (SQLException e) { Log.e(TAG, "Failed to fix database", e); publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_fail, CONSOLE_RED); return STATUS_FINISHED_FAIL; } finally { // Make sure we close the DB here, or the su call will also fail. if (db != null) { db.close(); } } publishProgress(progressTotal, progressStep, progressStep, R.string.updater_ok, CONSOLE_GREEN); } else { progressTotal = 13; progressStep = 0; } // Check for busybox if (isCancelled()) { return STATUS_CANCELLED; } publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_step_find_busybox); if (findBusybox()) { publishProgress(progressTotal, progressStep, progressStep, R.string.updater_ok, CONSOLE_GREEN); } else { publishProgress(progressTotal, progressStep, progressStep, R.string.updater_not_found, CONSOLE_RED); progressTotal += 2; // Download custom tiny busybox if (isCancelled()) { return STATUS_CANCELLED; } progressStep++; publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_step_download_busybox); if (downloadFile(mManifest.busyboxUrl, "busybox")) { try { Process process = new ProcessBuilder() .command("chmod", "755", mBusyboxPath) .redirectErrorStream(true).start(); Log.d(TAG, "chmod 755 " + mBusyboxPath); process.waitFor(); process.destroy(); } catch (IOException e) { Log.e(TAG, "Failed to set busybox to executable", e); publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_fail, CONSOLE_RED); return STATUS_FINISHED_FAIL; } catch (InterruptedException e) { Log.w(TAG, "Process interrupted", e); } publishProgress(progressTotal, progressStep, progressStep, R.string.updater_ok, CONSOLE_GREEN); } else { Log.e(TAG, "Failed to download busybox"); publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_fail); return STATUS_FINISHED_FAIL; } // Verify md5sum of busybox if (isCancelled()) { return STATUS_CANCELLED; } progressStep++; publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_step_check_md5sum); if (verifyFile(mBusyboxPath, mManifest.busyboxMd5)) { publishProgress(progressTotal, progressStep, progressStep, R.string.updater_ok, CONSOLE_GREEN); } else { publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_fail, CONSOLE_RED); return STATUS_FINISHED_FAIL; } } // Check where the current su binary is installed if (isCancelled()) { return STATUS_CANCELLED; } progressStep++; publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_step_check_installed_path); String installedSu = whichSu(); Log.d(TAG, "su installed to " + installedSu); if (installedSu == null) { publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_fail, CONSOLE_RED); publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_find_su_failed); return STATUS_FINISHED_FAIL; } else if (installedSu.equals("/sbin/su")) { publishProgress(progressTotal, progressStep - 1, progressStep, installedSu, CONSOLE_RED); publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_bad_install_location); return STATUS_FINISHED_FAIL_SBIN; } publishProgress(progressTotal, progressStep, progressStep, installedSu, CONSOLE_GREEN); // Download new su binary if (isCancelled()) { return STATUS_CANCELLED; } progressStep++; publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_step_download_su); String suPath; if (downloadFile(mManifest.binaryUrl, "su")) { suPath = getActivity().getFileStreamPath("su").toString(); publishProgress(progressTotal, progressStep, progressStep, R.string.updater_ok, CONSOLE_GREEN); } else { publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_fail, CONSOLE_RED); return STATUS_FINISHED_FAIL; } // Verify md5sum of su if (isCancelled()) { return STATUS_CANCELLED; } progressStep++; publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_step_check_md5sum); if (verifyFile(suPath, mManifest.binaryMd5)) { publishProgress(progressTotal, progressStep, progressStep, R.string.updater_ok, CONSOLE_GREEN); } else { publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_fail, CONSOLE_RED); return STATUS_FINISHED_FAIL; } Process process = null; try { // Just use one try/catch for all the root commands // Get root access progressStep++; publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_step_get_root); process = Runtime.getRuntime().exec("su"); DataOutputStream os = new DataOutputStream(process.getOutputStream()); BufferedReader is = new BufferedReader(new InputStreamReader( new DataInputStream(process.getInputStream())), 64); String inLine = executeCommand(os, is, 1000, mBusyboxPath, "touch /data/sutest &&", mBusyboxPath, "echo YEAH"); if (inLine == null || !inLine.equals("YEAH")) { publishProgress(progressTotal, progressStep -1, progressStep, R.string.updater_fail, CONSOLE_RED); return STATUS_FINISHED_FAIL; } publishProgress(progressTotal, progressStep, progressStep, R.string.updater_ok, CONSOLE_GREEN); // Remount system partition progressStep++; publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_step_remount_rw); executeCommand(os, null, mBusyboxPath + " mount -o remount,rw /system"); inLine = executeCommand(os, is, mBusyboxPath + " touch /system/su && " + mBusyboxPath + " echo YEAH"); if (inLine == null || !inLine.equals("YEAH")) { publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_fail, CONSOLE_RED); return STATUS_FINISHED_FAIL_REMOUNT; } publishProgress(progressTotal, progressStep, progressStep, R.string.updater_ok, CONSOLE_GREEN); // Copy su to /system. Put it in here first so it's on the system // partition then use an atomic move to make sure we don't get // corrupted progressStep++; publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_step_cp); inLine = executeCommand(os, is, mBusyboxPath, "cp", suPath, "/system &&", mBusyboxPath, "echo YEAH"); if (inLine == null || !inLine.equals("YEAH")) { publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_fail, CONSOLE_RED); return STATUS_FINISHED_FAIL; } publishProgress(progressTotal, progressStep, progressStep, R.string.updater_ok, CONSOLE_GREEN); // Check su md5sum again. Do it often to make sure everything is // going good. progressStep++; publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_step_check_md5sum); inLine = executeCommand(os, is, mBusyboxPath, "md5sum /system/su"); if (inLine == null || !inLine.split(" ")[0].equals(mManifest.binaryMd5)) { publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_fail, CONSOLE_RED); return STATUS_FINISHED_FAIL; } publishProgress(progressTotal, progressStep, progressStep, R.string.updater_ok, CONSOLE_GREEN); // Change su file mode progressStep++; publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_step_chmod); inLine = executeCommand(os, is, mBusyboxPath, "chmod 06755 /system/su", "&&", mBusyboxPath, "echo YEAH"); if (inLine == null || !inLine.equals("YEAH")) { publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_fail, CONSOLE_RED); } publishProgress(progressTotal, progressStep, progressStep, R.string.updater_ok, CONSOLE_GREEN); // Move /system/su to wherer it belongs progressStep++; publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_step_mv); inLine = executeCommand(os, is, mBusyboxPath, "mv /system/su", installedSu, "&&", mBusyboxPath, "echo YEAH"); if (inLine == null || !inLine.equals("YEAH")) { publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_fail, CONSOLE_RED); return STATUS_FINISHED_FAIL; } publishProgress(progressTotal, progressStep, progressStep, R.string.updater_ok, CONSOLE_GREEN); // Check su md5sum again. Last time, I promise progressStep++; publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_step_check_md5sum); // Can't use the verifyFile method here since we need to be root inLine = executeCommand(os, is, mBusyboxPath, "md5sum", installedSu); if (inLine == null || !inLine.split(" ")[0].equals(mManifest.binaryMd5)) { publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_fail, CONSOLE_RED); return STATUS_FINISHED_FAIL; } publishProgress(progressTotal, progressStep, progressStep, R.string.updater_ok, CONSOLE_GREEN); // Remount system partition progressStep++; publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_step_remount_ro); executeCommand(os, null, mBusyboxPath, "mount -o remount,ro /system"); inLine = executeCommand(os, is, mBusyboxPath, "touch /system/su ||", mBusyboxPath, "echo YEAH"); if (inLine == null || !inLine.equals("YEAH")) { publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_fail, CONSOLE_RED); publishProgress(progressTotal, progressStep, progressStep, R.string.updater_remount_ro_failed); } else { publishProgress(progressTotal, progressStep, progressStep, R.string.updater_ok, CONSOLE_GREEN); } os.writeBytes("exit\n"); } catch (IOException e) { Log.e(TAG, "Failed to execute root commands", e); } finally { process.destroy(); } // Verify the proper version is installed. progressStep++; publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_step_check_installed_version); installedVersionCode = Util.getSuVersionCode(); installedVersion = Util.getSuVersion(); if (installedVersion == null) { publishProgress(progressTotal, progressStep, progressStep, R.string.updater_fail, CONSOLE_RED); return STATUS_FINISHED_FAIL; } if (installedVersionCode == mManifest.versionCode) { publishProgress(progressTotal, progressStep, progressStep, installedVersion, CONSOLE_GREEN); } else { publishProgress(progressTotal, progressStep, progressStep, installedVersion, CONSOLE_RED); mCurrentStep = Step.DOWNLOAD_BUSYBOX; return STATUS_FINISHED_FAIL; } // Clean up progressStep++; publishProgress(progressTotal, progressStep - 1, progressStep, R.string.updater_step_clean_up); getActivity().deleteFile("busybox"); getActivity().deleteFile("su"); if (fixDb) { getActivity().deleteDatabase("permissions.sqlite"); } publishProgress(progressTotal, progressStep, progressStep, R.string.updater_ok, CONSOLE_GREEN); return STATUS_FINISHED_SUCCESSFUL; } return -1; } @Override protected void onProgressUpdate(Object... values) { if (isCancelled()) { return; } getListAdapter().notifyDataSetChanged(); mProgressBar.setMax((Integer)values[0]); mProgressBar.setProgress((Integer)values[1]); mProgressBar.setSecondaryProgress((Integer)values[2]); if (values.length == 4) { addConsoleEntry((Integer)values[3]); } else if (values.length == 5) { if (values[3] instanceof String) { addStatusToEntry((String)values[3], (Integer)values[4]); } else if (values[3] instanceof Integer){ addStatusToEntry((Integer)values[3], (Integer)values[4]); } else { addStatusToEntry(R.string.updater_fail, CONSOLE_RED); } } } @Override protected void onPostExecute(Integer result) { mTitleProgress.setVisibility(View.GONE); mActionButton.setEnabled(true); switch (result) { case STATUS_AWAITING_ACTION: mActionButton.setText(R.string.updater_update); mStatusText.setText(R.string.updater_new_su_found); break; case STATUS_FINISHED_NO_NEED: mActionButton.setText(R.string.updater_update_anyway); mStatusText.setText(R.string.updater_current_installed); break; case STATUS_FINISHED_SUCCESSFUL: mActionButton.setText(R.string.updater_cool); mStatusText.setText(R.string.updater_su_updated); NotificationManager nm = (NotificationManager) getActivity() .getSystemService(Context.NOTIFICATION_SERVICE); nm.cancel(1); break; case STATUS_FINISHED_FAIL: mActionButton.setText(R.string.updater_try_again); mStatusText.setText(R.string.updater_update_failed); break; case STATUS_CONN_FAILED: mActionButton.setText(R.string.updater_try_again); mStatusText.setText(R.string.updater_no_conn); break; case STATUS_FINISHED_FAIL_REMOUNT: case STATUS_FINISHED_FAIL_SBIN: AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setMessage(result) .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { getActivity().finish(); } }) .create().show(); } } @Override protected void onCancelled() { Log.d(TAG, "UpdaterTask cancelled"); } private boolean downloadFile(String urlStr, String localName) { BufferedInputStream bis = null; try { URL url = new URL(urlStr); URLConnection urlCon = url.openConnection(); bis = new BufferedInputStream(urlCon.getInputStream()); ByteArrayBuffer baf = new ByteArrayBuffer(50); int current = 0; while (((current = bis.read()) != -1)) { baf.append((byte) current); if (isCancelled()) { return false; } } bis.close(); if (isCancelled()) { return false; } else if (localName.equals("manifest")) { try { mManifest = new Manifest(); return mManifest.populate(new JSONObject(new String(baf.toByteArray()))); } catch (JSONException e) { Log.e(TAG, "Malformed manifest file", e); return false; } } else { FileOutputStream outFileStream = getActivity().openFileOutput(localName, 0); outFileStream.write(baf.toByteArray()); outFileStream.close(); if (localName.equals("busybox")) { mBusyboxPath = getActivity().getFilesDir().getAbsolutePath().concat("/busybox"); } } } catch (MalformedURLException e) { Log.e(TAG, "Bad URL: " + urlStr, e); return false; } catch (IOException e) { Log.e(TAG, "Problem downloading file: " + localName, e); return false; } return true; } } private void addConsoleEntry(int res) { ConsoleEntry entry = new ConsoleEntry(res); getListAdapter().add(entry); } private void addStatusToEntry(int res, int color) { addStatusToEntry(getActivity().getString(res), color); } private void addStatusToEntry(String status, int color) { ConsoleEntry entry = getListAdapter().getItem(getListAdapter().getCount() - 1); entry.status = status; entry.statusColor = color; } private boolean verifyFile(String path, String md5sum) { if (mBusyboxPath == null) { Log.e(TAG, "Busybox not present"); return false; } Process process = null; try { process = Runtime.getRuntime().exec( new String[] { mBusyboxPath, "md5sum", path}); BufferedReader is = new BufferedReader(new InputStreamReader( new DataInputStream(process.getInputStream())), 64); BufferedReader es = new BufferedReader(new InputStreamReader( new DataInputStream(process.getErrorStream())), 64); for (int i = 0; i < 200; i++) { if (is.ready()) break; try { Thread.sleep(5); } catch (InterruptedException e) { Log.w(TAG, "Sleep timer got interrupted..."); } } String inLine = null; if (es.ready()) { inLine = es.readLine(); Log.d(TAG, inLine); } if (is.ready()) { inLine = is.readLine(); } else { Log.e(TAG, "Could not check md5sum"); return false; } process.destroy(); if (!inLine.split(" ")[0].equals(md5sum)) { Log.e(TAG, "Checksum mismatch"); return false; } } catch (IOException e) { Log.e(TAG, "Checking of md5sum failed", e); return false; } return true; } private String whichSu() { for (String s : System.getenv("PATH").split(":")) { File su = new File(s + "/su"); if (su.exists() && su.isFile()) { try { if (su.getAbsolutePath().equals(su.getCanonicalPath())) { return su.getAbsolutePath(); } } catch (IOException e) { // If we get an exception here, it's probably not the right file, // Log it and move on Log.w(TAG, "IOException while finding canonical path of " + su.getAbsolutePath(), e); } } } return null; } private String executeCommand(DataOutputStream os, BufferedReader is, String... commands) throws IOException { return executeCommand(os, is, 200, commands); } private String executeCommand(DataOutputStream os, BufferedReader is, int timeout, String... commands) throws IOException { if (commands.length == 0) return null; StringBuilder command = new StringBuilder(); for (String s : commands) { command.append(s).append(" "); } command.append("\n"); Log.d(TAG, command.toString()); os.writeBytes(command.toString()); if (is != null) { for (int i = 0; i < timeout; i++) { if (is.ready()) break; try { Thread.sleep(5); } catch (InterruptedException e) { Log.w(TAG, "Sleep timer interrupted", e); } } if (is.ready()) { return is.readLine(); } else { return null; } } else { return null; } } private boolean findBusybox() { String path = System.getenv("PATH"); for (String s : path.split(":")) { File file = new File(s, "busybox"); if (file.exists()) { mBusyboxPath = file.getAbsolutePath(); return true; } } return false; } private class ConsoleAdapter extends ArrayAdapter<ConsoleEntry> { ConsoleAdapter(Context context) { super(context, R.layout.console_item); } @Override public View getView(int position, View convertView, ViewGroup parent) { TextView view = (TextView) super.getView(position, convertView, parent); ConsoleEntry entry = getItem(position); Spannable str = (Spannable) view.getText(); str.setSpan(new ForegroundColorSpan(entry.statusColor), entry.entry.length(), entry.toString().length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); return view; } } private class ConsoleEntry { public String entry; public String status = ""; public int statusColor = CONSOLE_GREY; public ConsoleEntry(int res) { entry = getActivity().getString(res); } @Override public String toString() { return entry + status; } } }