Implement touchable region insets for the volume dialog.

This allows touches outside of clickable volume UI elements to go to the app below, rather than being ignored.

Fixes: 147433928
Test: touch area below/around volume dialog in portrait+landscape
Change-Id: Iff58f182c1603fc81540df9ac966428f3bb00d49
This commit is contained in:
Josh Tsuji
2020-08-26 11:38:19 -04:00
parent af7f8f199e
commit 335a4ae438

View File

@@ -49,6 +49,7 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.Region;
import android.graphics.drawable.ColorDrawable;
import android.media.AudioManager;
import android.media.AudioSystem;
@@ -71,6 +72,7 @@ import android.view.View.AccessibilityDelegate;
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
import android.view.ViewStub;
import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
@@ -109,7 +111,8 @@ import java.util.List;
* Methods ending in "H" must be called on the (ui) handler.
*/
public class VolumeDialogImpl implements VolumeDialog,
ConfigurationController.ConfigurationListener {
ConfigurationController.ConfigurationListener,
ViewTreeObserver.OnComputeInternalInsetsListener {
private static final String TAG = Util.logTag(VolumeDialogImpl.class);
private static final long USER_ATTEMPT_GRACE_PERIOD = 1000;
@@ -126,6 +129,7 @@ public class VolumeDialogImpl implements VolumeDialog,
private final H mHandler = new H();
private final VolumeDialogController mController;
private final DeviceProvisionedController mDeviceProvisionedController;
private final Region mTouchableRegion = new Region();
private Window mWindow;
private CustomDialog mDialog;
@@ -204,6 +208,33 @@ public class VolumeDialogImpl implements VolumeDialog,
Dependency.get(ConfigurationController.class).removeCallback(this);
}
@Override
public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo internalInsetsInfo) {
// Set touchable region insets on the root dialog view. This tells WindowManager that
// touches outside of this region should not be delivered to the volume window, and instead
// go to the window below. This is the only way to do this - returning false in
// onDispatchTouchEvent results in the event being ignored entirely, rather than passed to
// the next window.
internalInsetsInfo.setTouchableInsets(
ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
mTouchableRegion.setEmpty();
// Set the touchable region to the union of all child view bounds. We don't use touches on
// the volume dialog container itself, so this is fine.
for (int i = 0; i < mDialogView.getChildCount(); i++) {
final View view = mDialogView.getChildAt(i);
mTouchableRegion.op(
view.getLeft(),
view.getTop(),
view.getRight(),
view.getBottom(),
Region.Op.UNION);
}
internalInsetsInfo.touchableRegion.set(mTouchableRegion);
}
private void initDialog() {
mDialog = new CustomDialog(mContext);
@@ -235,6 +266,7 @@ public class VolumeDialogImpl implements VolumeDialog,
mDialogView.setAlpha(0);
mDialog.setCanceledOnTouchOutside(true);
mDialog.setOnShowListener(dialog -> {
mDialogView.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
if (!isLandscape()) mDialogView.setTranslationX(mDialogView.getWidth() / 2.0f);
mDialogView.setAlpha(0);
mDialogView.animate()
@@ -253,6 +285,11 @@ public class VolumeDialogImpl implements VolumeDialog,
.start();
});
mDialog.setOnDismissListener(dialogInterface ->
mDialogView
.getViewTreeObserver()
.removeOnComputeInternalInsetsListener(VolumeDialogImpl.this));
mDialogView.setOnHoverListener((v, event) -> {
int action = event.getActionMasked();
mHovering = (action == MotionEvent.ACTION_HOVER_ENTER)
@@ -1369,6 +1406,11 @@ public class VolumeDialogImpl implements VolumeDialog,
super(context, R.style.volume_dialog_theme);
}
/**
* NOTE: This will only be called for touches within the touchable region of the volume
* dialog, as returned by {@link #onComputeInternalInsets}. Other touches, even if they are
* within the bounds of the volume dialog, will fall through to the window below.
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
rescheduleTimeoutH();
@@ -1387,6 +1429,12 @@ public class VolumeDialogImpl implements VolumeDialog,
mHandler.sendEmptyMessage(H.RECHECK_ALL);
}
/**
* NOTE: This will be called with ACTION_OUTSIDE MotionEvents for touches that occur outside
* of the touchable region of the volume dialog (as returned by
* {@link #onComputeInternalInsets}) even if those touches occurred within the bounds of the
* volume dialog.
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mShowing) {