Java tutorial
/* * Copyright (C) 2013 Koushik Dutta (@koush) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.koushikdutta.superuser; import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.support.v4.app.Fragment; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.MenuItem.OnMenuItemClickListener; import com.koushikdutta.superuser.util.Settings; import com.koushikdutta.superuser.util.StreamUtility; import com.koushikdutta.superuser.util.SuHelper; import com.koushikdutta.widgets.BetterListActivity; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; public class MainActivity extends BetterListActivity { public MainActivity() { super(PolicyFragment.class); } @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater mi = new MenuInflater(this); mi.inflate(R.menu.app, menu); MenuItem about = menu.findItem(R.id.about); about.setOnMenuItemClickListener(new OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { getSupportFragmentManager().beginTransaction().addToBackStack(getString(R.string.about)) .replace(getListContainerId(), new AboutFragment(), "content").commit(); return true; } }); MenuItem settings = menu.findItem(R.id.settings); settings.setOnMenuItemClickListener(new OnMenuItemClickListener() { @Override public boolean onMenuItemClick(final MenuItem item) { getSupportFragmentManager().beginTransaction().addToBackStack(getString(R.string.settings)) .replace(getListContainerId(), new SettingsFragment(), "content").commit(); return true; } }); return super.onCreateOptionsMenu(menu); } private String getArch() { String prop = System.getProperty("os.arch"); if (prop.contains("x86") || prop.contains("i686") || prop.contains("i386")) { return "x86"; } else if (prop.contains("mips")) { return "mips"; } else { return "armeabi"; } } File extractSu() throws IOException, InterruptedException { ZipFile zf = new ZipFile(getPackageCodePath()); ZipEntry su = zf.getEntry("assets/" + getArch() + "/su"); InputStream zin = zf.getInputStream(su); File ret = getFileStreamPath("su"); FileOutputStream fout = new FileOutputStream(ret); StreamUtility.copyStream(zin, fout); zin.close(); zf.close(); fout.close(); return ret; } void doRecoveryInstall() { final ProgressDialog dlg = new ProgressDialog(this); dlg.setTitle(R.string.installing); dlg.setMessage(getString(R.string.installing_superuser)); dlg.setIndeterminate(true); dlg.show(); new Thread() { void doEntry(ZipOutputStream zout, String entryName, String dest) throws IOException { ZipFile zf = new ZipFile(getPackageCodePath()); ZipEntry ze = zf.getEntry(entryName); zout.putNextEntry(new ZipEntry(dest)); InputStream in; StreamUtility.copyStream(in = zf.getInputStream(ze), zout); zout.closeEntry(); in.close(); zf.close(); } public void run() { try { File zip = getFileStreamPath("superuser.zip"); ZipOutputStream zout = new ZipOutputStream(new FileOutputStream(zip)); doEntry(zout, "assets/update-binary", "META-INF/com/google/android/update-binary"); doEntry(zout, "assets/install-recovery.sh", "install-recovery.sh"); zout.close(); ZipFile zf = new ZipFile(getPackageCodePath()); ZipEntry ze = zf.getEntry("assets/" + getArch() + "/reboot"); InputStream in; FileOutputStream reboot; StreamUtility.copyStream(in = zf.getInputStream(ze), reboot = openFileOutput("reboot", MODE_PRIVATE)); reboot.close(); in.close(); final File su = extractSu(); String command = String.format("cat %s > /cache/superuser.zip\n", zip.getAbsolutePath()) + String.format("cat %s > /cache/su\n", su.getAbsolutePath()) + String.format("cat %s > /cache/Superuser.apk\n", getPackageCodePath()) + "mkdir /cache/recovery\n" + "echo '--update_package=CACHE:superuser.zip' > /cache/recovery/command\n" + "chmod 644 /cache/superuser.zip\n" + "chmod 644 /cache/recovery/command\n" + "sync\n" + String.format("chmod 755 %s\n", getFileStreamPath("reboot").getAbsolutePath()) + "reboot recovery\n"; Process p = Runtime.getRuntime().exec("su"); p.getOutputStream().write(command.getBytes()); p.getOutputStream().close(); File rebootScript = getFileStreamPath("reboot.sh"); StreamUtility.writeFile(rebootScript, "reboot recovery ; " + getFileStreamPath("reboot").getAbsolutePath() + " recovery ;"); p.waitFor(); Runtime.getRuntime().exec(new String[] { "su", "-c", ". " + rebootScript.getAbsolutePath() }); if (p.waitFor() != 0) throw new Exception("non zero result"); } catch (Exception ex) { ex.printStackTrace(); dlg.dismiss(); runOnUiThread(new Runnable() { @Override public void run() { AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); builder.setPositiveButton(android.R.string.ok, null); builder.setTitle(R.string.install); builder.setMessage(R.string.install_error); builder.create().show(); } }); } } }.start(); } void doSystemInstall() { final ProgressDialog dlg = new ProgressDialog(this); dlg.setTitle(R.string.installing); dlg.setMessage(getString(R.string.installing_superuser)); dlg.setIndeterminate(true); dlg.show(); new Thread() { public void run() { boolean _error = false; try { final File su = extractSu(); final String command = "mount -orw,remount /system\n" + "rm /system/xbin/su\n" + "rm /system/bin/su\n" + "rm /system/app/Supersu.*\n" + "rm /system/app/superuser.*\n" + "rm /system/app/supersu.*\n" + "rm /system/app/SuperUser.*\n" + "rm /system/app/SuperSU.*\n" + String.format("cat %s > /system/xbin/su\n", su.getAbsolutePath()) + "chmod 6755 /system/xbin/su\n" + "ln -s /system/xbin/su /system/bin/su\n" + "mount -oro,remount /system\n" + "sync\n"; Process p = Runtime.getRuntime().exec("su"); p.getOutputStream().write(command.getBytes()); p.getOutputStream().close(); if (p.waitFor() != 0) throw new Exception("non zero result"); SuHelper.checkSu(MainActivity.this); } catch (Exception ex) { _error = true; Log.e("Superuser", "error upgrading", ex); } dlg.dismiss(); final boolean error = _error; runOnUiThread(new Runnable() { @Override public void run() { AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); builder.setPositiveButton(android.R.string.ok, null); builder.setTitle(R.string.install); if (error) { builder.setMessage(R.string.install_error); } else { builder.setMessage(R.string.install_success); } builder.create().show(); } }); }; }.start(); } void doInstall() { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.install); builder.setMessage(R.string.install_superuser_info); if (Build.VERSION.SDK_INT < 18) { builder.setPositiveButton(R.string.install, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { doSystemInstall(); } }); } builder.setNegativeButton(android.R.string.cancel, null); builder.setNeutralButton(R.string.recovery_install, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { doRecoveryInstall(); } }); builder.create().show(); } private void saveWhatsNew() { Settings.setString(this, "whats_new", WHATS_NEW); } // this is intentionally not localized as it will change constantly. private static final String WHATS_NEW = "Added support for Android 4.3."; protected void doWhatsNew() { if (WHATS_NEW.equals(Settings.getString(this, "whats_new"))) return; saveWhatsNew(); AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.whats_new); builder.setIcon(R.drawable.ic_launcher); builder.setMessage(WHATS_NEW); builder.setPositiveButton(R.string.rate, new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Intent i = new Intent(); i.setData(Uri.parse("market://details?id=com.koushikdutta.superuser")); startActivity(i); } }); builder.setNegativeButton(android.R.string.cancel, null); builder.create().show(); } @Override protected void onCreate(Bundle savedInstanceState) { Settings.applyDarkThemeSetting(this, R.style.SuperuserDarkActivity); super.onCreate(savedInstanceState); if (Settings.getBoolean(this, "first_run", true)) { saveWhatsNew(); Settings.setBoolean(this, "first_run", false); } final ProgressDialog dlg = new ProgressDialog(this); dlg.setTitle(R.string.superuser); dlg.setMessage(getString(R.string.checking_superuser)); dlg.setIndeterminate(true); dlg.show(); new Thread() { public void run() { boolean _error = false; try { SuHelper.checkSu(MainActivity.this); } catch (Exception e) { e.printStackTrace(); _error = true; } final boolean error = _error; dlg.dismiss(); runOnUiThread(new Runnable() { @Override public void run() { if (error) { doInstall(); } else { doWhatsNew(); } } }); }; }.start(); } }