diff --git a/.github/workflows/manual-build.yml b/.github/workflows/manual-build.yml
new file mode 100644
index 0000000..2899fab
--- /dev/null
+++ b/.github/workflows/manual-build.yml
@@ -0,0 +1,59 @@
+---
+name: Manual Android Build
+
+"on":
+ workflow_dispatch:
+ inputs:
+ build_type:
+ description: Build type
+ required: true
+ default: debug
+ type: choice
+ options:
+ - debug
+ - release
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ actions: write
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Set up Java 17
+ uses: actions/setup-java@v4
+ with:
+ distribution: temurin
+ java-version: "17"
+ cache: gradle
+
+ - name: Set up Android SDK
+ uses: android-actions/setup-android@v3
+
+ - name: Install Android SDK packages
+ run: |
+ sdkmanager --install \
+ "platform-tools" \
+ "platforms;android-34" \
+ "build-tools;34.0.0"
+
+ - name: Build app
+ run: |
+ chmod +x ./gradlew
+ if [ "${{ github.event.inputs.build_type }}" = "release" ]; then
+ ./gradlew :liveBootAni2:assembleRelease --stacktrace
+ else
+ ./gradlew :liveBootAni2:assembleDebug --stacktrace
+ fi
+
+ - name: Upload APK artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: liveboot-${{ github.event.inputs.build_type }}-apk
+ path: |
+ liveBootAni2/build/outputs/apk/**/*.apk
+ liveBootAni2/proguard/**/*.txt
+ if-no-files-found: warn
diff --git a/liveBootAni2/src/main/java/eu/chainfire/liveboot/Installer.java b/liveBootAni2/src/main/java/eu/chainfire/liveboot/Installer.java
index 78b611c..6ce0235 100644
--- a/liveBootAni2/src/main/java/eu/chainfire/liveboot/Installer.java
+++ b/liveBootAni2/src/main/java/eu/chainfire/liveboot/Installer.java
@@ -1,19 +1,3 @@
-/* Copyright (C) 2011-2024 Jorrit "Chainfire" Jongma
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
package eu.chainfire.liveboot;
import android.annotation.SuppressLint;
@@ -21,14 +5,18 @@
import android.app.ProgressDialog;
import android.content.Context;
import android.graphics.Point;
+import android.hardware.display.DisplayManager;
import android.os.AsyncTask;
import android.os.Build;
import android.os.StatFs;
+import android.view.Display;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import eu.chainfire.librootjava.AppProcess;
import eu.chainfire.librootjava.Logger;
@@ -41,8 +29,11 @@
public class Installer {
public enum Mode { SU_D, INIT_D, SU_SU_D, SBIN_SU_D, MAGISK_CORE, MAGISK_ADB, KERNELSU }
-
- private static final int LAST_SCRIPT_UPDATE = 188;
+
+ private static final int LAST_SCRIPT_UPDATE = 195;
+ private static final String BOOT_SCRIPT_MARKER = "# liveboot-boot-script-v2";
+ private static final int BOOT_SCRIPT_FAST_WAIT_ATTEMPTS = 50;
+ private static final int BOOT_SCRIPT_MAX_WAIT_SECONDS = 180;
private static final String[] SYSTEM_SCRIPTS_SU_D = new String[] { "/system/su.d/0000liveboot" };
private static final String[] SYSTEM_SCRIPTS_INIT_D = new String[] { "/system/etc/init.d/0000liveboot" };
private static final String[] SYSTEM_SCRIPTS_SU_SU_D = new String[] { "/su/su.d/0000liveboot" };
@@ -64,17 +55,21 @@ public static String[] getScript(Mode mode) {
return null;
}
+ private static boolean usesDelayedBootScript(Mode mode) {
+ return (mode == Mode.MAGISK_CORE) || (mode == Mode.MAGISK_ADB) || (mode == Mode.KERNELSU);
+ }
+
public static boolean systemFree(long wanted, int filecount) {
try {
StatFs fs = new StatFs("/system");
long blocks = (wanted / fs.getBlockSizeLong()) + (filecount * 3);
-
+
return (
(fs.getAvailableBlocksLong() >= blocks) ||
(fs.getFreeBlocksLong() >= blocks)
);
- } catch (Exception e) {
+ } catch (Exception e) {
}
return true;
}
@@ -85,7 +80,7 @@ public static Context directBootContext(Context context) {
}
return context;
}
-
+
private static int getVersion(Context context) {
try {
return context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionCode;
@@ -94,64 +89,110 @@ private static int getVersion(Context context) {
return 0;
}
}
-
+
public static boolean installNeededVersion(Settings settings) {
int lastVersion = settings.LAST_UPDATE.get();
if ((lastVersion == 0) || (lastVersion < LAST_SCRIPT_UPDATE)) {
return true;
- }
+ }
return false;
}
-
+
public static boolean installNeededData(Context context) {
context = directBootContext(context);
String filesDir = context.getFilesDir().getAbsolutePath();
return !(new File(String.format(Locale.ENGLISH, "%s/liveboot", filesDir))).exists();
}
-
+
public static boolean installNeededScript(Context context, Mode mode) {
context = directBootContext(context);
String filesDir = context.getFilesDir().getAbsolutePath();
boolean haveAll = true;
for (String file : getScript(mode)) {
- boolean have = false;
+ boolean haveLaunchCommand = false;
+ boolean haveCurrentVersion = !usesDelayedBootScript(mode);
List ls = Shell.SU.run(String.format(Locale.ENGLISH, "cat %s", file));
if (ls != null) {
for (String line : ls) {
if (line.contains(String.format(Locale.ENGLISH, "%s/liveboot", filesDir))) {
- have = true;
+ haveLaunchCommand = true;
+ }
+ if (line.contains(BOOT_SCRIPT_MARKER)) {
+ haveCurrentVersion = true;
}
}
}
- haveAll = haveAll && have;
+ haveAll = haveAll && haveLaunchCommand && haveCurrentVersion;
}
return !haveAll;
}
-
+
@SuppressLint("SdCardPath")
public static boolean installNeeded(Context context, Mode mode) {
Settings settings = Settings.getInstance(context);
return installNeededVersion(settings) || installNeededData(context) || installNeededScript(context, mode);
}
- public static synchronized Point getScreenDimensions() {
+ private static long getArea(int width, int height) {
+ return (long) width * (long) height;
+ }
+
+ private static boolean isBetterDimensions(int width, int height, Point currentBest) {
+ if (width <= 0 || height <= 0) return false;
+ long candidateArea = getArea(width, height);
+ long currentArea = getArea(currentBest.x, currentBest.y);
+ return (candidateArea > currentArea) ||
+ ((candidateArea == currentArea) && ((width > currentBest.x) || ((width == currentBest.x) && (height > currentBest.y))));
+ }
+
+ private static Point getScreenDimensionsFromDisplayManager(Context context) {
+ Point ret = new Point(0, 0);
+ try {
+ DisplayManager displayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
+ if (displayManager != null) {
+ Display defaultDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
+ if (defaultDisplay != null) {
+ Point size = new Point();
+ defaultDisplay.getRealSize(size);
+ if (size.x > 0 && size.y > 0) {
+ ret.set(size.x, size.y);
+ return ret;
+ }
+ }
+
+ Display[] displays = displayManager.getDisplays();
+ if (displays != null) {
+ for (Display display : displays) {
+ if (display == null) continue;
+ Point size = new Point();
+ display.getRealSize(size);
+ if (isBetterDimensions(size.x, size.y, ret)) {
+ ret.set(size.x, size.y);
+ }
+ }
+ }
+ }
+ } catch(Exception e) {
+ Logger.ex(e);
+ }
+ return ret;
+ }
+
+ private static Point getScreenDimensionsFromDumpsys() {
Point ret = new Point(0, 0);
+ Pattern realPattern = Pattern.compile("\\breal\\s+(\\d+)\\s*x\\s*(\\d+)\\b", Pattern.CASE_INSENSITIVE);
try {
List output = Shell.SU.run("dumpsys display | grep -i real | grep -vi overridedisplay");
if (output != null) {
for (String line : output) {
- String[] parts = line.split(",");
- for (int i = 0; i < parts.length; i++) {
- if (parts[i].contains("real")) {
- String[] sub = parts[i].split(" ");
- for (int j = 0; j < sub.length; j++) {
- if (sub[j].equals("real")) {
- ret.x = Integer.valueOf(sub[j + 1], 10);
- ret.y = Integer.valueOf(sub[j + 3], 10);
- }
- }
+ Matcher matcher = realPattern.matcher(line);
+ while (matcher.find()) {
+ int width = Integer.parseInt(matcher.group(1), 10);
+ int height = Integer.parseInt(matcher.group(2), 10);
+ if (isBetterDimensions(width, height, ret)) {
+ ret.set(width, height);
}
}
}
@@ -161,7 +202,18 @@ public static synchronized Point getScreenDimensions() {
}
return ret;
}
-
+
+ public static synchronized Point getScreenDimensions(Context context) {
+ Point ret = new Point(0, 0);
+ if (context != null) {
+ ret = getScreenDimensionsFromDisplayManager(directBootContext(context));
+ }
+ if (ret.x <= 0 || ret.y <= 0) {
+ ret = getScreenDimensionsFromDumpsys();
+ }
+ return ret;
+ }
+
public static synchronized List getLaunchScript(Context context, boolean boot) {
Settings settings = Settings.getInstance(context);
@@ -187,9 +239,10 @@ public static synchronized List getLaunchScript(Context context, boolean
if (!settings.LOGCAT_COLORS.get()) params.add("logcatnocolors");
params.add("dmesg=" + ((settings.DMESG.get() && (boot || !haveLogcat)) ? Settings.DMESG_ALL : Settings.DMESG_NONE));
params.add("lines=" + settings.LINES.get());
+ params.add("suicidedelay=" + settings.SUICIDE_DELAY_MS.get());
if (settings.WORD_WRAP.get()) params.add("wordwrap");
if (settings.SAVE_LOGS.get() && boot) params.add("save");
- Point dms = getScreenDimensions();
+ Point dms = getScreenDimensions(context);
params.add("fallbackwidth=" + dms.x);
params.add("fallbackheight=" + dms.y);
String relocate = AppProcess.shouldAppProcessBeRelocated() ? "/dev" : null;
@@ -211,9 +264,8 @@ private static boolean testShell(String shell) {
}
return false;
}
-
+
private static String getShell() {
- // works around some boot-time issues on older API levels
String shell = "/system/bin/sh";
if (testShell("/su/bin/sush")) {
shell = "/su/bin/sush";
@@ -222,10 +274,8 @@ private static String getShell() {
}
return shell;
}
-
+
public static void installData(Context context) {
- // much added weirdness here to work around some issues with running during the boot process,
- // on various different API levels and root versions.
context = directBootContext(context);
String filesDir = context.getFilesDir().getAbsolutePath();
@@ -239,9 +289,9 @@ public static void installData(Context context) {
commands.add(String.format(Locale.ENGLISH, Toolbox.command("chown") + " 0.0 %s/app_process", filesDir));
commands.add(String.format(Locale.ENGLISH, Toolbox.command("chmod") + " 0700 %s/app_process", filesDir));
commands.add(String.format(Locale.ENGLISH, Toolbox.command("chcon") + " u:object_r:app_data_file:s0 %s/app_process", filesDir));
-
+
String secontext = null;
- if (Build.VERSION.SDK_INT == 19) { // 4.4 only, not 4.3, not 5.0 - some Note4 madness
+ if (Build.VERSION.SDK_INT == 19) {
String id = Toolbox.command("id");
List ret = Shell.run("su --context u:r:recovery:s0", new String[] { id, "sh -c \"" + id + "\"" }, null, false);
if (ret != null) {
@@ -260,33 +310,58 @@ public static void installData(Context context) {
String shell = getShell();
for (String target : new String[] { "liveboot", "test" }) {
- commands.add(String.format(Locale.ENGLISH, "echo '#!%s' > %s/%s", shell, filesDir, target));
-// commands.add(String.format(Locale.ENGLISH, "echo '" + Toolbox.command("cp") + " %s /dev/.app_process_liveboot' >> %s/%s", app_process, filesDir, target));
-// commands.add(String.format(Locale.ENGLISH, "echo '" + Toolbox.command("chown") + " 0.0 /dev/.app_process_liveboot' >> %s/%s", filesDir, target));
-// commands.add(String.format(Locale.ENGLISH, "echo '" + Toolbox.command("chmod") + " 0700 /dev/.app_process_liveboot' >> %s/%s", filesDir, target));
+ commands.add(String.format(Locale.ENGLISH, "echo '#!%s' > %s/%s", shell, filesDir, target));
if (secontext != null) {
- commands.add(String.format(Locale.ENGLISH, "echo 'echo \"%s\" > /proc/self/attr/current' >> %s/%s", secontext, filesDir, target));
+ commands.add(String.format(Locale.ENGLISH, "echo 'echo \"%s\" > /proc/self/attr/current' >> %s/%s", secontext, filesDir, target));
}
for (String line : getLaunchScript(context, target.equals("liveboot"))) {
commands.add(String.format(Locale.ENGLISH, "echo '%s' >> %s/%s", line, filesDir, target));
commands.add(String.format(Locale.ENGLISH, "%s 0700 %s/%s", Toolbox.command("chmod"), filesDir, target));
}
-// commands.add(String.format(Locale.ENGLISH, "echo '" + Toolbox.command("rm") + " /dev/.app_process_liveboot' >> %s/%s", filesDir, target));
-// commands.add(String.format(Locale.ENGLISH, Toolbox.command("chmod") + " 0755 %s/%s", filesDir, target));
}
-
+
Shell.SU.run(commands);
}
-
+
+ private static void addDelayedBootScriptInstallCommands(List commands, String shell, String filesDir, String script, String[] scripts) {
+ commands.add(String.format(Locale.ENGLISH, "echo '#!%s' > %s", shell, script));
+ commands.add(String.format(Locale.ENGLISH, "echo '%s' >> %s", BOOT_SCRIPT_MARKER, script));
+ commands.add(String.format(Locale.ENGLISH, "echo '{' >> %s", script));
+ commands.add(String.format(Locale.ENGLISH, "echo ' liveboot=\"%s/liveboot\"' >> %s", filesDir, script));
+ commands.add(String.format(Locale.ENGLISH, "echo ' attempts=0' >> %s", script));
+ commands.add(String.format(Locale.ENGLISH, "echo ' while [ ! -x \"$liveboot\" ] && [ \"$attempts\" -lt %d ]; do' >> %s", BOOT_SCRIPT_FAST_WAIT_ATTEMPTS, script));
+ commands.add(String.format(Locale.ENGLISH, "echo ' sleep 0.1' >> %s", script));
+ commands.add(String.format(Locale.ENGLISH, "echo ' attempts=$((attempts + 1))' >> %s", script));
+ commands.add(String.format(Locale.ENGLISH, "echo ' done' >> %s", script));
+ commands.add(String.format(Locale.ENGLISH, "echo ' waited=0' >> %s", script));
+ commands.add(String.format(Locale.ENGLISH, "echo ' while [ ! -x \"$liveboot\" ]; do' >> %s", script));
+ commands.add(String.format(Locale.ENGLISH, "echo ' if [ \"$(/system/bin/getprop sys.boot_completed)\" = \"1\" ]; then' >> %s", script));
+ for (String staleScript : scripts) {
+ commands.add(String.format(Locale.ENGLISH, "echo ' %s -f %s' >> %s", Toolbox.command("rm"), staleScript, script));
+ }
+ commands.add(String.format(Locale.ENGLISH, "echo ' exit 0' >> %s", script));
+ commands.add(String.format(Locale.ENGLISH, "echo ' fi' >> %s", script));
+ commands.add(String.format(Locale.ENGLISH, "echo ' if [ \"$waited\" -ge %d ]; then' >> %s", BOOT_SCRIPT_MAX_WAIT_SECONDS, script));
+ commands.add(String.format(Locale.ENGLISH, "echo ' exit 0' >> %s", script));
+ commands.add(String.format(Locale.ENGLISH, "echo ' fi' >> %s", script));
+ commands.add(String.format(Locale.ENGLISH, "echo ' sleep 1' >> %s", script));
+ commands.add(String.format(Locale.ENGLISH, "echo ' waited=$((waited + 1))' >> %s", script));
+ commands.add(String.format(Locale.ENGLISH, "echo ' done' >> %s", script));
+ commands.add(String.format(Locale.ENGLISH, "echo ' %s \"$liveboot\"' >> %s", shell, script));
+ commands.add(String.format(Locale.ENGLISH, "echo '} &' >> %s", script));
+ commands.add(String.format(Locale.ENGLISH, Toolbox.command("chown") + " 0.0 %s", script));
+ commands.add(String.format(Locale.ENGLISH, Toolbox.command("chmod") + " 0700 %s", script));
+ }
+
public static void install(Context context, Mode mode) {
Settings settings = Settings.getInstance(context);
-
+
context = directBootContext(context);
String filesDir = context.getFilesDir().getAbsolutePath();
String shell = getShell();
-
+
installData(context);
List commands = new ArrayList();
if ((mode == Mode.SU_D) || (mode == Mode.INIT_D)) {
@@ -337,37 +412,16 @@ public static void install(Context context, Mode mode) {
commands.add(String.format(Locale.ENGLISH, Toolbox.command("chmod") + " 0700 %s", SYSTEM_SCRIPT_SBIN_SU_D));
}
} else if ((mode == Mode.MAGISK_CORE) || (mode == Mode.MAGISK_ADB)) {
- for (String script : (mode == Mode.MAGISK_CORE) ? SYSTEM_SCRIPTS_MAGISK_CORE : SYSTEM_SCRIPTS_MAGISK_ADB) {
- commands.add(String.format(Locale.ENGLISH, "echo '#!%s' > %s", shell, script));
- commands.add(String.format(Locale.ENGLISH, "echo '{' >> %s", script));
- commands.add(String.format(Locale.ENGLISH, "echo ' while (true); do' >> %s", script));
- commands.add(String.format(Locale.ENGLISH, "echo ' if [ -d \"%s\" ]; then' >> %s", filesDir, script));
- commands.add(String.format(Locale.ENGLISH, "echo ' break;' >> %s", script));
- commands.add(String.format(Locale.ENGLISH, "echo ' fi' >> %s", script));
- commands.add(String.format(Locale.ENGLISH, "echo ' sleep 0.1' >> %s", script));
- commands.add(String.format(Locale.ENGLISH, "echo ' done' >> %s", script));
- commands.add(String.format(Locale.ENGLISH, "echo ' %s %s/liveboot' >> %s", shell, filesDir, script));
- commands.add(String.format(Locale.ENGLISH, "echo '} &' >> %s", script));
- commands.add(String.format(Locale.ENGLISH, Toolbox.command("chown") + " 0.0 %s", script));
- commands.add(String.format(Locale.ENGLISH, Toolbox.command("chmod") + " 0700 %s", script));
+ String[] scripts = (mode == Mode.MAGISK_CORE) ? SYSTEM_SCRIPTS_MAGISK_CORE : SYSTEM_SCRIPTS_MAGISK_ADB;
+ for (String script : scripts) {
+ addDelayedBootScriptInstallCommands(commands, shell, filesDir, script, scripts);
}
} else if (mode == Mode.KERNELSU) {
commands.add(Toolbox.command("mkdir") + " /data/adb/post-fs-data.d");
commands.add(Toolbox.command("chown") + " 0.0 /data/adb/post-fs-data.d");
commands.add(Toolbox.command("chmod") + " 0755 /data/adb/post-fs-data.d");
for (String script : SYSTEM_SCRIPTS_KERNELSU) {
- commands.add(String.format(Locale.ENGLISH, "echo '#!%s' > %s", shell, script));
- commands.add(String.format(Locale.ENGLISH, "echo '{' >> %s", script));
- commands.add(String.format(Locale.ENGLISH, "echo ' while (true); do' >> %s", script));
- commands.add(String.format(Locale.ENGLISH, "echo ' if [ -d \"%s\" ]; then' >> %s", filesDir, script));
- commands.add(String.format(Locale.ENGLISH, "echo ' break;' >> %s", script));
- commands.add(String.format(Locale.ENGLISH, "echo ' fi' >> %s", script));
- commands.add(String.format(Locale.ENGLISH, "echo ' sleep 0.1' >> %s", script));
- commands.add(String.format(Locale.ENGLISH, "echo ' done' >> %s", script));
- commands.add(String.format(Locale.ENGLISH, "echo ' %s %s/liveboot' >> %s", shell, filesDir, script));
- commands.add(String.format(Locale.ENGLISH, "echo '} &' >> %s", script));
- commands.add(String.format(Locale.ENGLISH, Toolbox.command("chown") + " 0.0 %s", script));
- commands.add(String.format(Locale.ENGLISH, Toolbox.command("chmod") + " 0700 %s", script));
+ addDelayedBootScriptInstallCommands(commands, shell, filesDir, script, SYSTEM_SCRIPTS_KERNELSU);
}
}
if ((mode == Mode.SU_D) || (mode == Mode.INIT_D)) {
@@ -429,7 +483,7 @@ public static void uninstall(Context context) {
public static void installAsync(Activity activity, Mode mode, Runnable onDone) {
(new Async(activity, Async.ACTION_INSTALL, mode, onDone)).execute();
}
-
+
public static void uninstallAsync(Activity activity, Runnable onDone) {
(new Async(activity, Async.ACTION_UNINSTALL, null, onDone)).execute();
}
@@ -437,13 +491,13 @@ public static void uninstallAsync(Activity activity, Runnable onDone) {
private static class Async extends AsyncTask {
public static final int ACTION_INSTALL = 1;
public static final int ACTION_UNINSTALL = 2;
-
+
private final Context context;
private final int action;
private final Mode mode;
private final Runnable onDone;
private ProgressDialog dialog;
-
+
public Async(Context context, int action, Mode mode, Runnable onDone) {
this.context = context;
this.action = action;
@@ -476,16 +530,16 @@ protected Void doInBackground(Void... params) {
uninstall(context);
}
return null;
- }
-
+ }
+
@Override
protected void onPostExecute(Void result) {
try {
dialog.dismiss();
if (onDone != null) onDone.run();
- } catch (Exception e) {
+ } catch (Exception e) {
Logger.ex(e);
}
}
- }
+ }
}
diff --git a/liveBootAni2/src/main/java/eu/chainfire/liveboot/Settings.java b/liveBootAni2/src/main/java/eu/chainfire/liveboot/Settings.java
index 4ea233c..81abeb6 100644
--- a/liveBootAni2/src/main/java/eu/chainfire/liveboot/Settings.java
+++ b/liveBootAni2/src/main/java/eu/chainfire/liveboot/Settings.java
@@ -209,6 +209,7 @@ public SharedPreferences getPrefs() {
public BooleanSetting DMESG = new BooleanSetting(this, "dmesg", true);
public StringSetting LINES = new StringSetting(this, "lines", "80");
+ public StringSetting SUICIDE_DELAY_MS = new StringSetting(this, "suicide_delay_ms", "0");
public BooleanSetting WORD_WRAP = new BooleanSetting(this, "word_wrap", true);
public BooleanSetting SAVE_LOGS = new BooleanSetting(this, "save_logs", false);
diff --git a/liveBootAni2/src/main/java/eu/chainfire/liveboot/SettingsFragment.java b/liveBootAni2/src/main/java/eu/chainfire/liveboot/SettingsFragment.java
index fa4a380..9d689ea 100644
--- a/liveBootAni2/src/main/java/eu/chainfire/liveboot/SettingsFragment.java
+++ b/liveBootAni2/src/main/java/eu/chainfire/liveboot/SettingsFragment.java
@@ -31,6 +31,7 @@
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
+import android.preference.EditTextPreference;
import android.preference.ListPreference;
import android.preference.MultiSelectListPreference;
import android.preference.Preference;
@@ -39,6 +40,7 @@
import android.preference.PreferenceCategory;
import android.preference.PreferenceFragment;
import android.preference.PreferenceScreen;
+import android.text.InputType;
import eu.chainfire.librootjava.Logger;
import eu.chainfire.libsuperuser.Shell;
@@ -51,6 +53,8 @@
import java.util.Set;
public class SettingsFragment extends PreferenceFragment implements OnSharedPreferenceChangeListener {
+ private static final int MAX_SUICIDE_DELAY_MS = 60000;
+
private String APP_TITLE = "";
private SharedPreferences prefs = null;
private Settings settings = null;
@@ -59,6 +63,7 @@ public class SettingsFragment extends PreferenceFragment implements OnSharedPref
private MultiSelectListPreference prefLogcatBuffers = null;
private ListPreference prefLogcatFormat = null;
private ListPreference prefLines = null;
+ private EditTextPreference prefSuicideDelay = null;
private InAppPurchases iap = null;
private volatile boolean pro = false;
@@ -397,6 +402,21 @@ private void disableIfNoRoot(Activity activity, Preference preference, boolean h
}
}
+ private String normalizeSuicideDelayValue(Object value) {
+ String normalized = value == null ? settings.SUICIDE_DELAY_MS.defaultValue : String.valueOf(value).trim();
+ if (normalized.length() == 0) {
+ normalized = settings.SUICIDE_DELAY_MS.defaultValue;
+ }
+ try {
+ long parsed = Long.parseLong(normalized, 10);
+ if (parsed < 0) parsed = 0;
+ if (parsed > MAX_SUICIDE_DELAY_MS) parsed = MAX_SUICIDE_DELAY_MS;
+ return String.valueOf(parsed);
+ } catch (Exception e) {
+ return settings.SUICIDE_DELAY_MS.defaultValue;
+ }
+ }
+
private PreferenceScreen createPreferenceHierarchy(boolean haveRoot) {
final Activity activity = getActivity();
if (activity == null) return null;
@@ -574,6 +594,21 @@ public boolean onPreferenceChange(Preference preference, Object newValue) {
"160"
};
prefLines = Pref.List(activity, catOptions, R.string.settings_lines_title, 0, R.string.settings_lines_title, settings.LINES.name, settings.LINES.defaultValue, lines, lines, true);
+
+ prefSuicideDelay = Pref.Edit(activity, catOptions, R.string.settings_suicide_delay_title, 0, R.string.settings_suicide_delay_title, settings.SUICIDE_DELAY_MS.name, settings.SUICIDE_DELAY_MS.defaultValue, InputType.TYPE_CLASS_NUMBER);
+ prefSuicideDelay.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newValue) {
+ EditTextPreference editPreference = (EditTextPreference) preference;
+ String normalized = normalizeSuicideDelayValue(newValue);
+ editPreference.setText(normalized);
+ if (!normalized.equals(String.valueOf(newValue).trim())) {
+ settings.SUICIDE_DELAY_MS.set(normalized);
+ return false;
+ }
+ return true;
+ }
+ });
Pref.Check(activity, catOptions, R.string.settings_wordwrap_title, R.string.settings_wordwrap_description, settings.WORD_WRAP.name, settings.WORD_WRAP.defaultValue);
@@ -774,7 +809,21 @@ private void updatePrefs(String key) {
));
}
}
-
+
+ if ((key == null) || key.equals(settings.SUICIDE_DELAY_MS.name)) {
+ String normalized = normalizeSuicideDelayValue(settings.SUICIDE_DELAY_MS.get());
+ if (!normalized.equals(settings.SUICIDE_DELAY_MS.get())) {
+ settings.SUICIDE_DELAY_MS.set(normalized);
+ }
+ if (prefSuicideDelay != null) {
+ prefSuicideDelay.setText(normalized);
+ prefSuicideDelay.setSummary(String.format(Locale.ENGLISH, "%s\n[ %s ms ]",
+ getString(R.string.settings_suicide_delay_description),
+ normalized
+ ));
+ }
+ }
+
if (key != null) {
if (activity != null) {
(new Thread(new Runnable() {
diff --git a/liveBootAni2/src/main/java/eu/chainfire/liveboot/shell/Runner.java b/liveBootAni2/src/main/java/eu/chainfire/liveboot/shell/Runner.java
index b37cabb..2d838be 100644
--- a/liveBootAni2/src/main/java/eu/chainfire/liveboot/shell/Runner.java
+++ b/liveBootAni2/src/main/java/eu/chainfire/liveboot/shell/Runner.java
@@ -25,7 +25,13 @@
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
+import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
import eu.chainfire.librootjava.Logger;
@@ -48,6 +54,8 @@ public class
OnLineListener,
SurfaceHost.IGLRenderCallback
{
+ private static volatile Runner sCurrentRunner = null;
+
public static void main(String[] args) {
Logger.setLogTag("LiveBootSurface");
Logger.setDebugLogging(BuildConfig.DEBUG);
@@ -62,6 +70,9 @@ public static void main(String[] args) {
@Override
public void uncaughtException(Thread thread, Throwable throwable) {
Logger.dp("EXCEPTION", "%s", throwable.getClass().getName());
+ if (sCurrentRunner != null) {
+ sCurrentRunner.diagException("EXCEPTION", throwable);
+ }
if (oldHandler != null) {
oldHandler.uncaughtException(thread, throwable);
} else {
@@ -70,19 +81,29 @@ public void uncaughtException(Thread thread, Throwable throwable) {
}
});
- new Runner().run(args);
+ Runner runner = new Runner();
+ sCurrentRunner = runner;
+ runner.run(args);
}
public static final String LIVEBOOT_ABORT_FILE = "/dev/.liveboot_exit";
private static final int TEST_TIME = 5000;
private static final int LEAD_TIME = 200;
- private static final int FOLLOW_TIME_SCRIPT = 60000;
+ private static final int FOLLOW_TIME_SCRIPT = 60000;
+ private static final int FALLBACK_BOOT_SIGNAL_WAIT = 10000;
+ private static final int FAILSAFE_MAX_RUNTIME = 240000;
+ private static final int BOOT_STATE_LOG_INTERVAL = 2000;
+ private static final int MAX_SUICIDE_DELAY_MS = 60000;
+ private static final String[] BOOT_ANIMATION_PROCESS_HINTS = new String[] { "bootanim", "bootanimation" };
+ private static final String[] BOOT_ANIMATION_SERVICE_HINTS = new String[] { "bootanim", "bootanimation" };
+ private static final String[] DIAG_LOG_NAMES = new String[] { "/cache/liveboot.diag.log", "/data/local/tmp/liveboot.diag.log" };
private boolean mTest = false;
private int mWidth = 0;
private int mHeight = 0;
private int mLines = 80;
+ private int mSuicideDelayMs = 0;
private boolean mWordWrap = false;
private boolean mTransparent = false;
private boolean mDark = false;
@@ -91,6 +112,12 @@ public void uncaughtException(Thread thread, Throwable throwable) {
private boolean mLogSave = false;
private OutputStream mLogStream = null;
private ReentrantLock mLogLock = new ReentrantLock(true);
+ private String mDiagLogName = null;
+ private OutputStream mDiagStream = null;
+ private ReentrantLock mDiagLock = new ReentrantLock(true);
+ private PrintStream mDiagPrintStream = null;
+ private PrintStream mOldErr = null;
+ private PrintStream mOldOut = null;
private static final String SCRIPT_NAME_SYSTEM = "/system/su.d/0000liveboot.script";
private static final String SCRIPT_NAME_SU = "/su/su.d/0000liveboot.script";
private static final String SCRIPT_NAME_SBIN = "/sbin/supersu/su.d/0000liveboot.script";
@@ -110,39 +137,263 @@ public void uncaughtException(Thread thread, Throwable throwable) {
private long mFirstLine = 0;
private int mLinesPassed = 0;
+ private int mDroppedLines = 0;
+ private boolean mLoggedFirstLogcatLine = false;
+ private boolean mLoggedFirstDmesgLine = false;
+ private boolean mLoggedFirstScriptLine = false;
+ private long mLastBootStateLog = -BOOT_STATE_LOG_INTERVAL;
+ private String mLastBootStateSummary = null;
private volatile long mComplete = 0;
-
- private boolean isBootAnimationRunning() {
- List ps = Shell.SH.run(new String[] {
- "/system/bin/" + Toolbox.command("ps") + " | /system/bin/" + Toolbox.command("grep") + " bootanim"+ " | /system/bin/" + Toolbox.command("grep") + " -v grep",
- "/system/bin/" + Toolbox.command("ps") + " -A | /system/bin/" + Toolbox.command("grep") + " bootanim" + " | /system/bin/" + Toolbox.command("grep") + " -v grep"
- });
- if (ps != null) {
- for (String line : ps) {
- if (line.contains("bootanim")) {
- return true;
+
+ private void openDiagLog() {
+ for (String fileName : DIAG_LOG_NAMES) {
+ try {
+ mDiagStream = new FileOutputStream(fileName, false);
+ mDiagLogName = fileName;
+ break;
+ } catch (Exception e) {
+ }
+ }
+ }
+
+ private void redirectStandardStreams() {
+ if (mDiagLogName == null) return;
+ try {
+ mDiagPrintStream = new PrintStream(new FileOutputStream(mDiagLogName, true), true);
+ mOldErr = System.err;
+ mOldOut = System.out;
+ System.setErr(mDiagPrintStream);
+ System.setOut(mDiagPrintStream);
+ } catch (Exception e) {
+ }
+ }
+
+ private void restoreStandardStreams() {
+ try {
+ if (mOldErr != null) {
+ System.setErr(mOldErr);
+ }
+ if (mOldOut != null) {
+ System.setOut(mOldOut);
+ }
+ } catch (Exception e) {
+ }
+ try {
+ if (mDiagPrintStream != null) {
+ mDiagPrintStream.close();
+ }
+ } catch (Exception e) {
+ }
+ mDiagPrintStream = null;
+ mOldErr = null;
+ mOldOut = null;
+ }
+
+ private void closeDiagLog() {
+ restoreStandardStreams();
+ mDiagLock.lock();
+ try {
+ if (mDiagStream != null) {
+ try {
+ mDiagStream.close();
+ } catch (Exception e) {
}
+ mDiagStream = null;
}
+ } finally {
+ mDiagLock.unlock();
}
- return SystemProperties.get("init.svc.bootanim", "stopped").equals("running");
}
- private void killBootAnimation() {
- List ps = Shell.SH.run(new String[] {
- "/system/bin/" + Toolbox.command("ps") + " | /system/bin/" + Toolbox.command("grep") + " bootanim"+ " | /system/bin/" + Toolbox.command("grep") + " -v grep",
- "/system/bin/" + Toolbox.command("ps") + " -A | /system/bin/" + Toolbox.command("grep") + " bootanim" + " | /system/bin/" + Toolbox.command("grep") + " -v grep"
+ private void diag(String tag, String format, Object... params) {
+ OutputStream diagStream = mDiagStream;
+ if (diagStream == null) return;
+
+ String message;
+ try {
+ message = (params == null || params.length == 0) ? format : String.format(Locale.ENGLISH, format, params);
+ } catch (Exception e) {
+ message = format;
+ }
+ String line = String.format(Locale.ENGLISH, "[%8d][%s] %s\n", SystemClock.elapsedRealtime(), tag, message);
+
+ mDiagLock.lock();
+ try {
+ if (mDiagStream != null) {
+ mDiagStream.write(line.getBytes());
+ mDiagStream.flush();
+ }
+ } catch (Exception e) {
+ } finally {
+ mDiagLock.unlock();
+ }
+ }
+
+ private void diagException(String tag, Throwable throwable) {
+ if (throwable == null) return;
+ diag(tag, "%s: %s", throwable.getClass().getName(), String.valueOf(throwable.getMessage()));
+ }
+
+ private String shorten(String text) {
+ if (text == null) return "";
+ text = text.replace('\n', ' ').replace('\r', ' ');
+ if (text.length() > 160) {
+ return text.substring(0, 160);
+ }
+ return text;
+ }
+
+ private String getSenderName(Object sender) {
+ if (sender == mLogcat) return "logcat";
+ if (sender == mDmesg) return "dmesg";
+ if (sender == mScript) return "script";
+ if (sender == null) return "null";
+ return sender.getClass().getSimpleName();
+ }
+
+ private void maybeLogFirstLine(Object sender, String text) {
+ if ((sender == mLogcat) && !mLoggedFirstLogcatLine) {
+ mLoggedFirstLogcatLine = true;
+ diag("LINES", "first logcat line: %s", shorten(text));
+ } else if ((sender == mDmesg) && !mLoggedFirstDmesgLine) {
+ mLoggedFirstDmesgLine = true;
+ diag("LINES", "first dmesg line: %s", shorten(text));
+ } else if ((sender == mScript) && !mLoggedFirstScriptLine) {
+ mLoggedFirstScriptLine = true;
+ diag("LINES", "first script line: %s", shorten(text));
+ }
+ }
+
+ private String getInterestingProperties() {
+ return String.format(
+ Locale.ENGLISH,
+ "init.svc.bootanim=%s init.svc.bootanimation=%s service.bootanim.exit=%s service.bootanim.completed=%s sys.boot_completed=%s dev.bootcomplete=%s persist.sys.multi_display_type=%s",
+ SystemProperties.get("init.svc.bootanim", "stopped"),
+ SystemProperties.get("init.svc.bootanimation", "stopped"),
+ SystemProperties.get("service.bootanim.exit", "0"),
+ SystemProperties.get("service.bootanim.completed", "0"),
+ SystemProperties.get("sys.boot_completed", "0"),
+ SystemProperties.get("dev.bootcomplete", "0"),
+ SystemProperties.get("persist.sys.multi_display_type", "")
+ );
+ }
+
+ private void maybeLogBootState(long elapsed, boolean bootAnimationSeen, boolean bootAnimationGone, boolean bootAnimationRunning, List pids, long complete, String completeSignal) {
+ String summary = String.format(
+ Locale.ENGLISH,
+ "elapsed=%d running=%s seen=%s gone=%s complete=%s signal=%s pids=%s props={%s}",
+ elapsed,
+ String.valueOf(bootAnimationRunning),
+ String.valueOf(bootAnimationSeen),
+ String.valueOf(bootAnimationGone),
+ String.valueOf(complete > 0),
+ (completeSignal == null ? "-" : completeSignal),
+ pids.toString(),
+ getInterestingProperties()
+ );
+ if (!summary.equals(mLastBootStateSummary) || (elapsed - mLastBootStateLog >= BOOT_STATE_LOG_INTERVAL)) {
+ diag("BOOT", summary);
+ mLastBootStateSummary = summary;
+ mLastBootStateLog = elapsed;
+ }
+ }
+
+ private boolean isPropertyOne(String key) {
+ return SystemProperties.get(key, "0").equals("1");
+ }
+
+ private String getBootCompletionSignal(boolean bootAnimationSeen, boolean bootAnimationRunning, long elapsed) {
+ if (isPropertyOne("service.bootanim.exit")) {
+ return "service.bootanim.exit";
+ }
+ if (isPropertyOne("service.bootanim.completed")) {
+ return "service.bootanim.completed";
+ }
+
+ // Some ROMs no longer expose service.bootanim.* during boot.
+ if (
+ (elapsed >= FALLBACK_BOOT_SIGNAL_WAIT) &&
+ (bootAnimationSeen || !bootAnimationRunning)
+ ) {
+ if (isPropertyOne("sys.boot_completed")) {
+ return "sys.boot_completed";
+ }
+ if (isPropertyOne("dev.bootcomplete")) {
+ return "dev.bootcomplete";
+ }
+ }
+ return null;
+ }
+
+ private List getProcessList() {
+ return Shell.SH.run(new String[] {
+ "/system/bin/" + Toolbox.command("ps"),
+ "/system/bin/" + Toolbox.command("ps") + " -A"
});
+ }
+
+ private Integer getPidFromPsLine(String line) {
+ if (line == null) return null;
+ String[] parts = line.trim().split(" +");
+ for (String part : parts) {
+ try {
+ int pid = Integer.valueOf(part, 10);
+ if (pid > 1) {
+ return pid;
+ }
+ } catch (Exception e) {
+ }
+ }
+ return null;
+ }
+
+ private boolean isBootAnimationProcessLine(String line) {
+ if (line == null) return false;
+ String lower = line.toLowerCase(Locale.ENGLISH);
+ for (String hint : BOOT_ANIMATION_PROCESS_HINTS) {
+ if (lower.contains(hint)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private List getBootAnimationPids() {
+ List ret = new ArrayList();
+ Set seen = new HashSet();
+ List ps = getProcessList();
if (ps != null) {
for (String line : ps) {
- String[] parts = line.split(" +");
- if (parts.length >= 2) {
- Shell.run("sh", new String[] { "/system/bin/" + Toolbox.command("kill") + " -9 " + parts[1] }, null, false);
+ if (isBootAnimationProcessLine(line)) {
+ Integer pid = getPidFromPsLine(line);
+ if ((pid != null) && !seen.contains(pid)) {
+ seen.add(pid);
+ ret.add(pid);
+ }
}
}
- }
+ }
+ return ret;
}
-
+
+ private boolean isBootAnimationServiceRunning() {
+ for (String service : BOOT_ANIMATION_SERVICE_HINTS) {
+ if (SystemProperties.get("init.svc." + service, "stopped").equals("running")) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isBootAnimationRunning() {
+ return isBootAnimationRunning(getBootAnimationPids());
+ }
+
+ private boolean isBootAnimationRunning(List pids) {
+ return !pids.isEmpty() || isBootAnimationServiceRunning();
+ }
+
private void infanticide() { // children
String pid = String.valueOf(android.os.Process.myPid());
List ps = Shell.SH.run(new String[] {
@@ -169,12 +420,14 @@ private void suicide() { // self
protected void onSize(int width, int height) {
mWidth = width;
mHeight = height;
+ diag("SURFACE", "size=%dx%d", width, height);
}
@Override
protected void onResize(int width, int height) {
mWidth = width;
mHeight = height;
+ diag("SURFACE", "resize=%dx%d", width, height);
mTextManager.resize(-1, -1, width, height, -1, mHeight / mLines);
mHelper.resize(width, height);
}
@@ -184,6 +437,11 @@ protected void onInit(String[] args) {
RootDaemon.daemonize(BuildConfig.APPLICATION_ID, 0, false, null);
Toolbox.init();
+ openDiagLog();
+ redirectStandardStreams();
+ diag("INIT", "pid=%d args=%s", android.os.Process.myPid(), Arrays.toString(args));
+ diag("INIT", "properties={%s}", getInterestingProperties());
+ diag("INIT", "diag log=%s stderr_redirect=%s", String.valueOf(mDiagLogName), String.valueOf(mDiagPrintStream != null));
// parse options
String logcatLevelOpts = null;
@@ -224,6 +482,9 @@ protected void onInit(String[] args) {
} else if (key.equals("lines")) {
mLines = Integer.valueOf(value, 10);
Logger.dp("OPTS", "mLines==%s", mLines);
+ } else if (key.equals("suicidedelay")) {
+ mSuicideDelayMs = Math.max(0, Math.min(MAX_SUICIDE_DELAY_MS, Integer.valueOf(value, 10)));
+ Logger.dp("OPTS", "mSuicideDelayMs==%d", mSuicideDelayMs);
} else if (key.equals("logcatlevels")) {
logcatLevelOpts = value;
Logger.dp("OPTS", "logcatLevelOpts==%s", logcatLevelOpts);
@@ -240,6 +501,7 @@ protected void onInit(String[] args) {
}
} catch (Exception e) {
Logger.ex(e);
+ diagException("INIT", e);
}
}
@@ -254,11 +516,31 @@ protected void onInit(String[] args) {
mHandlerThread = new HandlerThread("LiveBoot HandlerThread");
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
+ diag(
+ "INIT",
+ "runScript=%s test=%s transparent=%s dark=%s wordwrap=%s save=%s lines=%d suicideDelayMs=%d fallback=%dx%d logcatLevels=%s logcatBuffers=%s logcatFormat=%s dmesg=%s",
+ (mRunScript == null ? "" : mRunScript),
+ String.valueOf(mTest),
+ String.valueOf(mTransparent),
+ String.valueOf(mDark),
+ String.valueOf(mWordWrap),
+ String.valueOf(mLogSave),
+ mLines,
+ mSuicideDelayMs,
+ fallbackWidth,
+ fallbackHeight,
+ String.valueOf(logcatLevelOpts),
+ String.valueOf(logcatBufferOpts),
+ String.valueOf(logcatFormatOpt),
+ String.valueOf(dmesgOpts)
+ );
if (mLogSave) {
try {
mLogStream = new FileOutputStream(LOG_NAME, false);
+ diag("INIT", "log stream opened at %s", LOG_NAME);
} catch (Exception e) {
+ diagException("INIT", e);
}
}
@@ -266,19 +548,26 @@ protected void onInit(String[] args) {
if (mRunScript == null) {
mLogcat = new Logcat(this, mLines * 4, logcatLevelOpts, logcatBufferOpts, logcatFormatOpt, mHandler);
mDmesg = new Dmesg(this, mLines * 4, dmesgOpts, mHandler);
+ diag("INIT", "started direct log sources");
+ } else {
+ diag("INIT", "waiting for script mode source");
}
}
@Override
protected void onDone() {
+ diag("DONE", "onDone start");
mHandlerThread.quit();
if (mLogcat != null) mLogcat.destroy();
if (mDmesg != null) mDmesg.destroy();
if (mScript != null) mScript.destroy();
+ diag("DONE", "onDone complete");
+ closeDiagLog();
}
@Override
protected void onInitRender() {
+ diag("SURFACE", "initRender width=%d height=%d runScript=%s", mWidth, mHeight, (mRunScript == null ? "" : mRunScript));
mTextureManager = new GLTextureManager();
mHelper = new GLHelper(mWidth, mHeight, GLHelper.getDefaultVMatrix());
mTextManager = new GLTextManager(mTextureManager, mHelper, mWidth, mHeight, mHeight / mLines);
@@ -291,6 +580,7 @@ protected void onInitRender() {
if (mLogcat != null) mLogcat.setReady();
} else {
mScript = new Script(this, mRunScript);
+ diag("SURFACE", "script renderer started for %s", mRunScript);
}
}
@@ -316,6 +606,7 @@ public void onGLRenderFrame() {
@Override
protected void onDoneRender() {
+ diag("SURFACE", "doneRender");
mTextManager.destroy();
mTextManager = null;
mTextureManager.destroy();
@@ -330,6 +621,7 @@ public void onLine(Object sender, String text, int color) {
mHandler.post(new Runnable() {
@Override
public void run() {
+ maybeLogFirstLine(s, t);
if (mTextManager != null) {
long wait = 0L;
if (mComplete == 0) {
@@ -352,6 +644,11 @@ public void run() {
} else {
mTextManager.add("", Color.WHITE, mWordWrap);
}
+ } else {
+ mDroppedLines++;
+ if ((mDroppedLines <= 5) || ((mDroppedLines % 100) == 0)) {
+ diag("LINES", "drop count=%d sender=%s text=%s", mDroppedLines, getSenderName(s), shorten(t));
+ }
}
}
});
@@ -377,60 +674,60 @@ public void onLog(Object sender, String text) {
@Override
protected void onMainLoop() {
if (mTest) {
+ diag("BOOT", "test mode sleep=%d", TEST_TIME);
try {
Thread.sleep(TEST_TIME);
} catch (Exception e) {
+ diagException("BOOT", e);
}
} else {
long start = SystemClock.elapsedRealtime();
boolean bootAnimationSeen = false;
boolean bootAnimationGone = false;
- boolean bootAnimationKilled = false;
long complete = 0;
+ diag("BOOT", "main loop start");
while (true) {
- // do not check sys.boot_completed or dev.bootcomplete, as these are already set to 1 before entering decryption password
long now = SystemClock.elapsedRealtime();
- if ((complete == 0) && SystemProperties.get("service.bootanim.exit", "0").equals("1")) {
- // sign from Android that we should quit
- Logger.d("service.bootanim.exit");
- complete = now;
- }
- if ((complete == 0) && SystemProperties.get("service.bootanim.completed", "0").equals("1")) {
- // sign from Android that we should quit
- Logger.d("service.bootanim.completed");
+ long elapsed = now - start;
+ List bootAnimationPids = getBootAnimationPids();
+ boolean bootAnimationRunning = isBootAnimationRunning(bootAnimationPids);
+ String completeSignal = getBootCompletionSignal(bootAnimationSeen, bootAnimationRunning, elapsed);
+ maybeLogBootState(elapsed, bootAnimationSeen, bootAnimationGone, bootAnimationRunning, bootAnimationPids, complete, completeSignal);
+ if ((complete == 0) && (completeSignal != null)) {
+ Logger.d(completeSignal);
+ diag("BOOT", "complete signal=%s", completeSignal);
complete = now;
}
- if ((complete == 0) && !bootAnimationSeen && (now - start > 1500)) {
+ if ((complete == 0) && !bootAnimationSeen && (elapsed > 1500)) {
// register if we ever saw the bootanimation
- if (isBootAnimationRunning()) {
+ if (bootAnimationRunning) {
Logger.d("bootAnimationSeen");
+ diag("BOOT", "bootAnimationSeen pids=%s", bootAnimationPids.toString());
bootAnimationSeen = true;
}
}
- if (bootAnimationSeen && !bootAnimationGone && (now - start > 2500)) {
+ if (bootAnimationSeen && !bootAnimationGone && (elapsed > 2500)) {
// if we saw the bootanimation before and its gone now, note that
- if (!isBootAnimationRunning()) {
+ if (!bootAnimationRunning) {
Logger.d("bootAnimationGone");
- bootAnimationGone = true;
- }
- }
- if ((complete > 0) && bootAnimationSeen && !bootAnimationGone && !bootAnimationKilled) {
- // if we have an exit sign from Android and bootanimation is still running, kill it
- Logger.d("bootAnimationkill");
- killBootAnimation();
- bootAnimationKilled = true;
- if (!isBootAnimationRunning()) {
- Logger.d("bootAnimationkill/Gone");
+ diag("BOOT", "bootAnimationGone");
bootAnimationGone = true;
}
}
if ((complete == 0) && (new File(LIVEBOOT_ABORT_FILE)).exists()) {
Logger.d("bootCompleteAbortFromAPK");
+ diag("BOOT", "abort file detected: %s", LIVEBOOT_ABORT_FILE);
complete = now;
bootAnimationSeen = true;
- bootAnimationKilled = true;
bootAnimationGone = true;
}
+ if ((complete == 0) && (elapsed > FAILSAFE_MAX_RUNTIME)) {
+ Logger.d("bootAnimationTimeout");
+ diag("BOOT", "timeout after %d ms", elapsed);
+ complete = now;
+ bootAnimationSeen = true;
+ bootAnimationGone = true;
+ }
if (
// Android has signaled to quit, and we haven't seen bootanimation
@@ -443,10 +740,20 @@ protected void onMainLoop() {
(bootAnimationSeen && (complete > 0) && (now - complete > 2500))
) {
Logger.dp("EXIT", "exit sequence");
+ diag("BOOT", "exit sequence entered");
if ((mRunScript != null) && !mTest) {
try {
Thread.sleep(FOLLOW_TIME_SCRIPT);
} catch (Exception e) {
+ diagException("BOOT", e);
+ }
+ }
+ if (mSuicideDelayMs > 0) {
+ diag("BOOT", "suicide delay sleep=%d", mSuicideDelayMs);
+ try {
+ Thread.sleep(mSuicideDelayMs);
+ } catch (Exception e) {
+ diagException("BOOT", e);
}
}
break;
@@ -454,9 +761,11 @@ protected void onMainLoop() {
try {
Thread.sleep(64);
} catch (Exception e) {
+ diagException("BOOT", e);
}
}
Logger.d("Runtime: %dms", SystemClock.elapsedRealtime() - start);
+ diag("BOOT", "runtime=%d", SystemClock.elapsedRealtime() - start);
}
mComplete = SystemClock.elapsedRealtime();
mLinesPassed = 0;
@@ -466,6 +775,7 @@ protected void onMainLoop() {
try {
Thread.sleep(LEAD_TIME);
} catch (Exception e) {
+ diagException("BOOT", e);
}
if (mLogSave) {
mLogLock.lock();
@@ -479,7 +789,7 @@ protected void onMainLoop() {
mLogLock.unlock();
}
}
- killBootAnimation();
+ diag("DONE", "final cleanup start");
infanticide();
suicide();
}
diff --git a/liveBootAni2/src/main/res/values/strings.xml b/liveBootAni2/src/main/res/values/strings.xml
index e692b29..6cd3413 100644
--- a/liveBootAni2/src/main/res/values/strings.xml
+++ b/liveBootAni2/src/main/res/values/strings.xml
@@ -60,6 +60,8 @@
Use a darker background than the default setting
Lines
Number of lines to fit on screen
+ Exit delay
+ Wait this many milliseconds before LiveBoot exits after boot is considered complete. Useful to cover black gaps after the stock boot animation disappears. 0 disables.
Word wrap
If a line doesn\'t fit on screen, break it up into multiple lines