package ru.ivanarh.jndcrash; import android.app.Service; import android.content.Intent; import android.content.SharedPreferences; import android.os.IBinder; import android.support.annotation.CallSuper; import android.util.Log; /** * Service for out-of-process crash handling daemon. Should be run from a separate process. */ public class NDCrashService extends Service implements NDCrash.OnCrashCallback { /** * Log tag. */ private static final String TAG = "JNDCRASH"; /** * Indicates if a daemon was started. */ private static boolean mDaemonStarted = false; /** * A name for shared preferences. */ private static final String PREFS_NAME = "NDCrashService"; /** * Key for report file in arguments. */ public static final String EXTRA_REPORT_FILE = "report_file"; /** * Key for unwinder in arguments. Ordinal value is saved as integer. */ public static final String EXTRA_UNWINDER = "unwinder"; @Override public int onStartCommand(Intent intent, int flags, int startId) { final SharedPreferences preferences = getSharedPreferences(PREFS_NAME, MODE_PRIVATE); NDCrashUnwinder unwinder = null; String reportPath = null; if (intent != null) { unwinder = NDCrashUnwinder.values()[intent.getIntExtra(EXTRA_UNWINDER, NDCrashUnwinder.libunwind.ordinal())]; reportPath = intent.getStringExtra(EXTRA_REPORT_FILE); // Using the same keys as extras. final SharedPreferences.Editor editor = preferences.edit(); if (unwinder != null) { editor.putInt(EXTRA_UNWINDER, unwinder.ordinal()); } else { editor.remove(EXTRA_UNWINDER); } if (reportPath != null) { editor.putString(EXTRA_REPORT_FILE, reportPath); } else { editor.remove(EXTRA_REPORT_FILE); } editor.apply(); } else { unwinder = NDCrashUnwinder.values()[preferences.getInt(EXTRA_UNWINDER, NDCrashUnwinder.libunwind.ordinal())]; reportPath = preferences.getString(EXTRA_REPORT_FILE, null); } if (!mDaemonStarted) { if (unwinder != null) { mDaemonStarted = true; final NDCrashError initResult = NDCrash.startOutOfProcessDaemon(this, reportPath, unwinder, this); if (initResult != NDCrashError.ok) { Log.e(TAG, "Couldn't start NDCrash out-of-process daemon with unwinder: " + unwinder + ", error: " + initResult); } else { Log.i(TAG, "Out-of-process unwinding daemon is started with unwinder: " + unwinder + " report path: " + (reportPath != null ? reportPath : "null")); onDaemonStart(unwinder, reportPath, initResult); } } else { Log.e(TAG, "Couldn't start NDCrash out-of-process daemon: unwinder is unknown."); } } else { Log.i(TAG, "NDCrash out-of-process daemon is already started."); } // START_REDELIVER_INTENT may seem better but found by experimental way that when we return // this value a service is restarted significantly slower (with a longer delay) after its // process is killed. So a workaround is used: Saving initialization parameters to shared // preferences and reading them when intent is null. return Service.START_STICKY; } @Override @CallSuper public void onDestroy() { if (mDaemonStarted) { mDaemonStarted = false; final boolean stoppedSuccessfully = NDCrash.stopOutOfProcessDaemon(); Log.i(TAG, "Out-of-process daemon " + (stoppedSuccessfully ? "is successfully stopped." : "failed to stop.")); } super.onDestroy(); } @Override public IBinder onBind(Intent intent) { // Service doesn't support to be bound. return null; } @Override public void onCrash(String reportPath) { } /** * Called on daemon start attempt, both on success and failed. * * @param unwinder Unwinder that is used. * @param reportPath Path to crash report file. * @param result Start result. */ protected void onDaemonStart(NDCrashUnwinder unwinder, String reportPath, NDCrashError result) { } }