Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/com/koushikdutta/droid2/bootstrap/BootReceiver.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.koushikdutta.droid2.bootstrap;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

public class BootReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
Intent i = new Intent(context, BootService.class);
context.startService(i); }

}
67 changes: 67 additions & 0 deletions src/com/koushikdutta/droid2/bootstrap/BootService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.koushikdutta.droid2.bootstrap;

import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.util.Log;

public class BootService extends Service {

private static final String TAG = "D2B/BootService";

@Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);

// wait to make sure the system is sane.
mHandler.postDelayed(new Runnable()
{
@Override
public void run() {

String filesDir = getFilesDir().getAbsolutePath();
String busybox = filesDir + "/busybox";
String adbd = filesDir + "/adbd";

StringBuilder command = new StringBuilder();

// prevent recovery from booting here
Log.d(TAG, "Removing recovery_mode trigger");
command.append("rm /data/.recovery_mode ; ");

ROMBootstrapSettings settings = new ROMBootstrapSettings();

if(settings.restartAdb()) {
// restart adbd as root
Log.d(TAG, "Restarting ADB as Root");
command.append(busybox + " mount -orw,remount / ; ");
command.append("mv /sbin/adbd /sbin/adbd.old ; ");
command.append(busybox + " cp " + adbd + " /sbin/adbd ; ");
command.append(busybox + " mount -oro,remount / ; ");
command.append(busybox + " kill $(ps | " + busybox + " grep adbd) ;");
}

try {
Helper.runSuCommand(BootService.this, command.toString());
}
catch (Exception e) {
e.printStackTrace();
}
finally
{
stopSelf();
}
}
},
30000);
}

Handler mHandler = new Handler();

@Override
public IBinder onBind(Intent arg0) {
return null;
}

}
185 changes: 185 additions & 0 deletions src/com/koushikdutta/droid2/bootstrap/Bootstrap.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package com.koushikdutta.droid2.bootstrap;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.Vector;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class Bootstrap extends Activity {

private static final String TAG = "D2B/Bootstrap";

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

unzipAssets();

Button flash = (Button)findViewById(R.id.flash);
flash.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
String filesDir = getFilesDir().getAbsolutePath();
String busybox = filesDir + "/busybox";
String hijack = filesDir + "/hijack";
String logwrapper = filesDir + "/logwrapper.bin";
String updatebinary = filesDir + "/update-binary";
String recoveryzip = filesDir + "/update.zip";
String adbd = filesDir + "/adbd";

StringBuilder command = new StringBuilder();

ROMBootstrapSettings settings = new ROMBootstrapSettings();

if(settings.installHijack()) {
Log.d(TAG, "Installing hijack");
command.append(busybox + " mount -oremount,rw /system ; ");
command.append(busybox + " cp " + logwrapper + " /system/bin/logwrapper.bin ; ");
command.append(busybox + " cp " + hijack + " /system/bin/hijack ; ");
command.append("cd /system/bin ; rm logwrapper ; ln -s hijack logwrapper ; ");
command.append(busybox + " mount -oremount,ro /system ; ");
}

if(settings.installRecovery()) {
Log.d(TAG, "Installing recovery");
command.append(busybox + " mkdir -p /preinstall/recovery ; ");
command.append(busybox + " cp " + updatebinary + " /preinstall/recovery/update-binary ; ");
command.append(busybox + " cp " + recoveryzip + " /preinstall/recovery/recovery.zip ; ");
command.append(busybox + " cp " + hijack + " /preinstall/recovery/hijack ; ");
command.append(busybox + " cp " + logwrapper + " /preinstall/recovery/logwrapper ; ");
}

if(settings.restartAdb()) {
// restart adbd as root
Log.d(TAG, "Restarting ADB as Root");
command.append(busybox + " mount -orw,remount / ; ");
command.append("mv /sbin/adbd /sbin/adbd.old ; ");
command.append(busybox + " cp " + adbd + " /sbin/adbd ; ");
command.append(busybox + " mount -oro,remount / ; ");
command.append(busybox + " kill $(ps | " + busybox + " grep adbd) ;");
}

// prevent recovery from booting here
Log.d(TAG, "Removing recovery_mode trigger");
command.append("rm /data/.recovery_mode ; ");

AlertDialog.Builder builder = new Builder(Bootstrap.this);
builder.setPositiveButton(android.R.string.ok, null);
try {
Helper.runSuCommand(Bootstrap.this, command.toString());
builder.setMessage("Success!");
}
catch (Exception e) {
builder.setTitle("Failure");
builder.setMessage(e.getMessage());
e.printStackTrace();
}
builder.create().show();
}
});

Button reboot = (Button)findViewById(R.id.reboot);
reboot.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
StringBuilder command = new StringBuilder();
command.append("echo 1 > /data/.recovery_mode ; ");
command.append("sync ; reboot ; ");
try {
Helper.runSuCommand(Bootstrap.this, command.toString());
}
catch (Exception e) {
AlertDialog.Builder builder = new Builder(Bootstrap.this);
builder.setPositiveButton(android.R.string.ok, null);
builder.setTitle("Failure");
builder.setMessage(e.getMessage());
e.printStackTrace();
}
}
});
}

final static String LOGTAG = "Droid2Bootstrap";
final static String ZIP_FILTER = "assets";

void unzipAssets() {
String apkPath = getPackageCodePath();
String mAppRoot = getFilesDir().toString();
try {
File zipFile = new File(apkPath);
long zipLastModified = zipFile.lastModified();
ZipFile zip = new ZipFile(apkPath);
Vector<ZipEntry> files = getAssets(zip);
int zipFilterLength = ZIP_FILTER.length();

Enumeration<?> entries = files.elements();
while (entries.hasMoreElements()) {
ZipEntry entry = (ZipEntry) entries.nextElement();
String path = entry.getName().substring(zipFilterLength);
File outputFile = new File(mAppRoot, path);
outputFile.getParentFile().mkdirs();

if (outputFile.exists() && entry.getSize() == outputFile.length() && zipLastModified < outputFile.lastModified())
continue;
FileOutputStream fos = new FileOutputStream(outputFile);
copyStreams(zip.getInputStream(entry), fos);
Runtime.getRuntime().exec("chmod 755 " + outputFile.getAbsolutePath());
}
} catch (IOException e) {
Log.e(LOGTAG, "Error: " + e.getMessage());
}
}

static final int BUFSIZE = 5192;

void copyStreams(InputStream is, FileOutputStream fos) {
BufferedOutputStream os = null;
try {
byte data[] = new byte[BUFSIZE];
int count;
os = new BufferedOutputStream(fos, BUFSIZE);
while ((count = is.read(data, 0, BUFSIZE)) != -1) {
os.write(data, 0, count);
}
os.flush();
} catch (IOException e) {
Log.e(LOGTAG, "Exception while copying: " + e);
} finally {
try {
if (os != null) {
os.close();
}
} catch (IOException e2) {
Log.e(LOGTAG, "Exception while closing the stream: " + e2);
}
}
}

public Vector<ZipEntry> getAssets(ZipFile zip) {
Vector<ZipEntry> list = new Vector<ZipEntry>();
Enumeration<?> entries = zip.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = (ZipEntry) entries.nextElement();
if (entry.getName().startsWith(ZIP_FILTER)) {
list.add(entry);
}
}
return list;
}
}
36 changes: 36 additions & 0 deletions src/com/koushikdutta/droid2/bootstrap/Helper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.koushikdutta.droid2.bootstrap;

import java.io.DataOutputStream;
import java.io.IOException;

import android.content.Context;

public class Helper {
static final String LOGTAG = "Droid2Bootstrap";

public final static String SCRIPT_NAME = "surunner.sh";

public static Process runSuCommandAsync(Context context, String command) throws IOException
{
DataOutputStream fout = new DataOutputStream(context.openFileOutput(SCRIPT_NAME, 0));
fout.writeBytes(command);
fout.close();

String[] args = new String[] { "su", "-c", ". " + context.getFilesDir().getAbsolutePath() + "/" + SCRIPT_NAME };
Process proc = Runtime.getRuntime().exec(args);
return proc;
}

public static int runSuCommand(Context context, String command) throws IOException, InterruptedException
{
return runSuCommandAsync(context, command).waitFor();
}

public static int runSuCommandNoScriptWrapper(Context context, String command) throws IOException, InterruptedException
{
String[] args = new String[] { "su", "-c", command };
Process proc = Runtime.getRuntime().exec(args);
return proc.waitFor();
}

}
67 changes: 67 additions & 0 deletions src/com/koushikdutta/droid2/bootstrap/ROMBootstrapSettings.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.koushikdutta.droid2.bootstrap;

import java.io.File;
import java.io.FileInputStream;

import android.util.Log;

import org.json.JSONObject;

public class ROMBootstrapSettings {
private static final String TAG = "D2B/ROMBootstrapSettings";

private static final File SETTINGS_FILE = new File("/system/etc/Droid2Bootstrap.cfg");
private static final String RESTART_ADB_KEY = "restart_adb";
private static final String INSTALL_HIJACK_KEY = "install_hijack";
private static final String INSTALL_RECOVERY_KEY = "install_recovery";

private boolean mRestartAdb = true;
private boolean mInstallHijack = true;
private boolean mInstallRecovery = true;

public ROMBootstrapSettings() {
if(SETTINGS_FILE.exists()) {
Log.d(TAG, "Found settings file, parsing");

FileInputStream f = null;
String data = "";

try {
// read file into a string
byte[] buffer = new byte[(int) SETTINGS_FILE.length()];
f = new FileInputStream(SETTINGS_FILE);
f.read(buffer);
data = new String(buffer);
} catch(Exception e) {
Log.e(TAG, "Error reading settings file", e);
} finally {
// ensure the stream is closed
if(f != null) try { f.close(); } catch(Exception ignored) { }
}

try {
// parse as JSON
JSONObject json = new JSONObject(data);

if(json.has(RESTART_ADB_KEY)) {
mRestartAdb = json.optBoolean(RESTART_ADB_KEY, true);
Log.d(TAG, "Setting [RestartAdb] to [" + (mRestartAdb ? "true" : "false") + "]");
}
if(json.has(INSTALL_HIJACK_KEY)) {
mInstallHijack = json.optBoolean(INSTALL_HIJACK_KEY, true);
Log.d(TAG, "Setting [InstallHijack] to [" + (mInstallHijack ? "true" : "false") + "]");
}
if(json.has(INSTALL_RECOVERY_KEY)) {
mInstallRecovery = json.optBoolean(INSTALL_RECOVERY_KEY, true);
Log.d(TAG, "Setting [InstallRecovery] to [" + (mInstallRecovery ? "true" : "false") + "]");
}
} catch(Exception e) {
Log.e(TAG, "Error parsing settings file", e);
}
}
}

public boolean restartAdb() { return mRestartAdb; }
public boolean installHijack() { return mInstallHijack; }
public boolean installRecovery() { return mInstallRecovery; }
}