diff --git a/leakcanary-android/src/main/java/com/squareup/leakcanary/AndroidHeapDumper.java b/leakcanary-android/src/main/java/com/squareup/leakcanary/AndroidHeapDumper.java index 63e750287a..18207e5d7d 100644 --- a/leakcanary-android/src/main/java/com/squareup/leakcanary/AndroidHeapDumper.java +++ b/leakcanary-android/src/main/java/com/squareup/leakcanary/AndroidHeapDumper.java @@ -18,6 +18,9 @@ import android.app.Notification; import android.app.NotificationManager; import android.content.Context; +import android.content.res.Configuration; +import android.graphics.PixelFormat; +import android.os.Build; import android.os.Debug; import android.os.Handler; import android.os.Looper; @@ -25,11 +28,16 @@ import android.os.SystemClock; import android.view.Gravity; import android.view.LayoutInflater; +import android.view.View; +import android.view.WindowManager; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; import android.widget.Toast; import com.squareup.leakcanary.internal.FutureResult; import com.squareup.leakcanary.internal.LeakCanaryInternals; import java.io.File; +import static android.view.accessibility.AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED; import static java.util.concurrent.TimeUnit.SECONDS; public final class AndroidHeapDumper implements HeapDumper { @@ -89,7 +97,7 @@ private void showToast(final FutureResult waitingForToast) { toast.setDuration(Toast.LENGTH_LONG); LayoutInflater inflater = LayoutInflater.from(context); toast.setView(inflater.inflate(R.layout.leak_canary_heap_dump_toast, null)); - toast.show(); + show(toast); // Waiting for Idle to make sure Toast gets rendered. Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() { @Override public boolean queueIdle() { @@ -104,8 +112,85 @@ private void showToast(final FutureResult waitingForToast) { private void cancelToast(final Toast toast) { mainHandler.post(new Runnable() { @Override public void run() { - toast.cancel(); + hide(toast); } }); } + + private void show(Toast toast) { + View view = toast.getView(); + Context context = toast.getView().getContext(); + + WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + + // We can resolve the Gravity here by using the Locale for getting + // the layout direction + Configuration config = view.getContext().getResources().getConfiguration(); + int gravity = toast.getGravity(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + gravity = Gravity.getAbsoluteGravity(gravity, config.getLayoutDirection()); + } + + WindowManager.LayoutParams params = buildLayoutParams(); + params.gravity = gravity; + if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) { + params.horizontalWeight = 1.0f; + } + if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) { + params.verticalWeight = 1.0f; + } + params.x = toast.getXOffset(); + params.y = toast.getYOffset(); + params.verticalMargin = toast.getVerticalMargin(); + params.horizontalMargin = toast.getHorizontalMargin(); + params.packageName = context.getPackageName(); + try { + windowManager.addView(view, params); + } catch (WindowManager.BadTokenException e) { + CanaryLog.d(e, "Could not show leak toast, the window token has been canceled"); + return; + } + trySendAccessibilityEvent(view); + } + + private void hide(Toast toast) { + View view = toast.getView(); + if (view.getParent() != null) { + Context context = toast.getView().getContext(); + WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + windowManager.removeView(view); + } + } + + private WindowManager.LayoutParams buildLayoutParams() { + WindowManager.LayoutParams params = new WindowManager.LayoutParams(); + params.height = WindowManager.LayoutParams.WRAP_CONTENT; + params.width = WindowManager.LayoutParams.WRAP_CONTENT; + params.format = PixelFormat.TRANSLUCENT; + params.windowAnimations = android.R.style.Animation_Toast; + params.type = WindowManager.LayoutParams.TYPE_TOAST; + params.setTitle("Toast"); + params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; + return params; + } + + private void trySendAccessibilityEvent(View view) { + Context context = view.getContext(); + AccessibilityManager accessibilityManager = + (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE); + if (!accessibilityManager.isEnabled()) { + return; + } + // treat toasts as notifications since they are used to + // announce a transient piece of information to the user + AccessibilityEvent event = AccessibilityEvent.obtain(TYPE_NOTIFICATION_STATE_CHANGED); + event.setClassName(getClass().getName()); + event.setPackageName(context.getPackageName()); + view.dispatchPopulateAccessibilityEvent(event); + accessibilityManager.sendAccessibilityEvent(event); + } + + }