package com.oasisfeng.debug; import android.app.ActivityManager; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.os.Build; import android.os.Debug; import android.util.Log; import com.oasisfeng.pattern.PseudoContentProvider; /** * Special content provider for easy debugger attaching at the starting stage of specific process. * * Declare this content provider in your AndroidManifest.xml, with "android:process" attribute set to the desired process. * * Created by Oasis on 2017/3/20. */ public class DebuggerWaiter extends PseudoContentProvider { private static final long WAITING_TIMEOUT = 3_000; public static void waitIfNeeded(final Context context) { if (shouldWait(context)) new Waiter().doWait(); } @Override public boolean onCreate() { waitIfNeeded(context()); return false; } private static boolean shouldWait(final Context context) { // Skip in non-debuggable build. (BuildConfig.DEBUG may not be true since Deagle is a library dependency which is probably built as release) if ((context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0) return false; // Skip if USB is not connected except on emulator. TODO: Support ADB over TCP. if (! Build.FINGERPRINT.contains("generic")) { final Intent usb_state = context.registerReceiver(null, new IntentFilter("android.hardware.usb.action.USB_STATE")); if (usb_state == null || ! usb_state.getBooleanExtra("connected", false)) return false; } // Skip if running in main process. (IDE could attach debugger to main process upon launch) final ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); final int pid = android.os.Process.myPid(); for (final ActivityManager.RunningAppProcessInfo process : am.getRunningAppProcesses()) if (process.pid == pid) { if (process.pkgList[0].equals(process.processName)) return false; break; } return true; } static final class Waiter { void doWait() { Log.d(TAG, "Start waiting for debugger..."); mWaitingThread.start(); try { synchronized (mSignal) { mSignal.wait(WAITING_TIMEOUT); } } catch (final InterruptedException ignored) {} if (Debug.isDebuggerConnected()) Log.d(TAG, "Debugger connected."); else Log.d(TAG, "Resume without debugger."); } private final Thread mWaitingThread = new Thread() { @Override public void run() { Debug.waitForDebugger(); synchronized (mSignal) { mSignal.notifyAll(); } }}; private final Object mSignal = new Object(); } private static final String TAG = "Debug.Wait" ; }