From f473e1d0988bb13874a0774db9cbcd66777f9150 Mon Sep 17 00:00:00 2001 From: yueg Date: Tue, 2 Jan 2018 16:23:14 -0800 Subject: Bubble v2 dismiss. Drag and drop bubble to bottom to hide or end call. Flinging to bottom does not trigger the actions. Color/text is not final. Navigation bar is not hiden and the change will be in a following CL. Bug: 67605985 Test: NewBubbleTest PiperOrigin-RevId: 180608133 Change-Id: Iff4cb32226d8fbf0f8e5319f6876a1d74c336b4a --- .../android/dialer/logging/dialer_impression.proto | 6 +- .../newbubble/BottomActionViewController.java | 184 +++++++++++++++++++++ java/com/android/newbubble/NewBubble.java | 43 ++++- java/com/android/newbubble/NewMoveHandler.java | 10 ++ .../newbubble/res/drawable/bottom_action_scrim.xml | 24 +++ .../newbubble/res/layout/bottom_action_base.xml | 63 +++++++ java/com/android/newbubble/res/values/values.xml | 2 + 7 files changed, 326 insertions(+), 6 deletions(-) create mode 100644 java/com/android/newbubble/BottomActionViewController.java create mode 100644 java/com/android/newbubble/res/drawable/bottom_action_scrim.xml create mode 100644 java/com/android/newbubble/res/layout/bottom_action_base.xml (limited to 'java') diff --git a/java/com/android/dialer/logging/dialer_impression.proto b/java/com/android/dialer/logging/dialer_impression.proto index 2d2eebf67..a17d365b1 100644 --- a/java/com/android/dialer/logging/dialer_impression.proto +++ b/java/com/android/dialer/logging/dialer_impression.proto @@ -12,7 +12,7 @@ message DialerImpression { // Event enums to be used for Impression Logging in Dialer. // It's perfectly acceptable for this enum to be large // Values should be from 1000 to 100000. - // Next Tag: 1320 + // Next Tag: 1322 enum Type { UNKNOWN_AOSP_EVENT_TYPE = 1000; @@ -644,5 +644,9 @@ message DialerImpression { BUBBLE_V2_BLUETOOTH = 1318; // User ended call from bubble call action menu BUBBLE_V2_END_CALL = 1319; + // Drag bubble to bottom and dismiss + BUBBLE_V2_BOTTOM_ACTION_DISMISS = 1320; + // Drag bubble to bottom and end call + BUBBLE_V2_BOTTOM_ACTION_END_CALL = 1321; } } diff --git a/java/com/android/newbubble/BottomActionViewController.java b/java/com/android/newbubble/BottomActionViewController.java new file mode 100644 index 000000000..7c7105194 --- /dev/null +++ b/java/com/android/newbubble/BottomActionViewController.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.newbubble; + +import android.content.Context; +import android.graphics.PixelFormat; +import android.support.v4.os.BuildCompat; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.WindowManager; +import android.view.WindowManager.LayoutParams; +import android.view.animation.LinearInterpolator; + +/** Controller for showing and hiding bubble bottom action view. */ +final class BottomActionViewController { + + // This delay controls how long to wait before we show the target when the user first moves + // the bubble, to prevent the bottom action view from animating if the user just wants to fling + // the bubble. + private static final int SHOW_TARGET_DELAY = 100; + private static final int SHOW_TARGET_DURATION = 350; + private static final int HIDE_TARGET_DURATION = 225; + private static final float HIGHLIGHT_TARGET_SCALE = 1.5f; + + private final Context context; + private final WindowManager windowManager; + private final int gradientHeight; + private final int bottomActionViewTop; + + private View bottomActionView; + private View dismissView; + private View endCallView; + + private boolean dismissHighlighted; + private boolean endCallHighlighted; + + public BottomActionViewController(Context context) { + this.context = context; + windowManager = context.getSystemService(WindowManager.class); + gradientHeight = + context.getResources().getDimensionPixelSize(R.dimen.bubble_bottom_action_view_height); + bottomActionViewTop = context.getResources().getDisplayMetrics().heightPixels - gradientHeight; + } + + /** Creates and show the bottom action view. */ + public void createAndShowBottomActionView() { + if (bottomActionView != null) { + return; + } + + // Create a new view for the dismiss target + bottomActionView = LayoutInflater.from(context).inflate(R.layout.bottom_action_base, null); + bottomActionView.setAlpha(0); + + // Sub views + dismissView = bottomActionView.findViewById(R.id.bottom_action_dismiss_layout); + endCallView = bottomActionView.findViewById(R.id.bottom_action_end_call_layout); + + // Add the target to the window + // TODO(yueg): use TYPE_NAVIGATION_BAR_PANEL to draw over navigation bar + LayoutParams layoutParams = + new LayoutParams( + LayoutParams.MATCH_PARENT, + gradientHeight, + 0, + bottomActionViewTop, + BuildCompat.isAtLeastO() + ? LayoutParams.TYPE_APPLICATION_OVERLAY + : LayoutParams.TYPE_SYSTEM_OVERLAY, + LayoutParams.FLAG_LAYOUT_IN_SCREEN + | LayoutParams.FLAG_NOT_TOUCHABLE + | LayoutParams.FLAG_NOT_FOCUSABLE, + PixelFormat.TRANSLUCENT); + layoutParams.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL; + windowManager.addView(bottomActionView, layoutParams); + bottomActionView.setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_FULLSCREEN); + bottomActionView + .getRootView() + .setSystemUiVisibility( + View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_FULLSCREEN); + + // Shows the botton action view + bottomActionView + .animate() + .alpha(1f) + .setInterpolator(new LinearInterpolator()) + .setStartDelay(SHOW_TARGET_DELAY) + .setDuration(SHOW_TARGET_DURATION) + .start(); + } + + /** Hides and destroys the bottom action view. */ + public void destroyBottomActionView() { + if (bottomActionView == null) { + return; + } + bottomActionView + .animate() + .alpha(0f) + .setInterpolator(new LinearInterpolator()) + .setDuration(HIDE_TARGET_DURATION) + .withEndAction( + () -> { + // Use removeViewImmediate instead of removeView to avoid view flashing before removed + windowManager.removeViewImmediate(bottomActionView); + bottomActionView = null; + }) + .start(); + } + + /** + * Change highlight state of dismiss view and end call view according to current touch point. + * Highlight the view with touch point moving into its boundary. Unhighlight the view with touch + * point moving out of its boundary. + * + * @param x x position of current touch point + * @param y y position of current touch point + */ + public void highlightIfHover(float x, float y) { + if (bottomActionView == null) { + return; + } + final int middle = context.getResources().getDisplayMetrics().widthPixels / 2; + boolean shouldHighlightDismiss = y > bottomActionViewTop && x < middle; + boolean shouldHighlightEndCall = y > bottomActionViewTop && x >= middle; + + if (!shouldHighlightDismiss && dismissHighlighted) { + // Unhighlight dismiss + dismissView.animate().scaleX(1f).scaleY(1f).setDuration(HIDE_TARGET_DURATION).start(); + dismissHighlighted = false; + } else if (!shouldHighlightEndCall && endCallHighlighted) { + // Unhighlight end call + endCallView.animate().scaleX(1f).scaleY(1f).setDuration(HIDE_TARGET_DURATION).start(); + endCallHighlighted = false; + } + + if (shouldHighlightDismiss && !dismissHighlighted) { + // Highlight dismiss + dismissView + .animate() + .scaleX(HIGHLIGHT_TARGET_SCALE) + .scaleY(HIGHLIGHT_TARGET_SCALE) + .setDuration(SHOW_TARGET_DURATION) + .start(); + dismissHighlighted = true; + } else if (shouldHighlightEndCall && !endCallHighlighted) { + // Highlight end call + endCallView + .animate() + .scaleX(HIGHLIGHT_TARGET_SCALE) + .scaleY(HIGHLIGHT_TARGET_SCALE) + .setDuration(SHOW_TARGET_DURATION) + .start(); + endCallHighlighted = true; + } + } + + public boolean isDismissHighlighted() { + return dismissHighlighted; + } + + public boolean isEndCallHighlighted() { + return endCallHighlighted; + } +} diff --git a/java/com/android/newbubble/NewBubble.java b/java/com/android/newbubble/NewBubble.java index 469c15d71..f5a036f93 100644 --- a/java/com/android/newbubble/NewBubble.java +++ b/java/com/android/newbubble/NewBubble.java @@ -60,6 +60,7 @@ import android.view.animation.AnticipateInterpolator; import android.view.animation.OvershootInterpolator; import android.widget.ImageView; import android.widget.TextView; +import android.widget.Toast; import android.widget.ViewAnimator; import com.android.dialer.common.LogUtil; import com.android.dialer.logging.DialerImpression; @@ -830,6 +831,12 @@ public class NewBubble { */ void replaceViewHolder() { LogUtil.enterBlock("NewBubble.replaceViewHolder"); + // Don't do it. If windowParams is null, either we haven't initialized it or we set it to null. + // There is no need to recreate bubble. + if (windowParams == null) { + return; + } + ViewHolder oldViewHolder = viewHolder; // Create a new ViewHolder and copy needed info. @@ -873,6 +880,27 @@ public class NewBubble { return viewHolder.getExpandedView().getVisibility(); } + void bottomActionDismiss() { + logBasicOrCallImpression(DialerImpression.Type.BUBBLE_V2_BOTTOM_ACTION_DISMISS); + // Create bubble at default location at next time + hideAndReset(); + windowParams = null; + } + + void bottomActionEndCall() { + logBasicOrCallImpression(DialerImpression.Type.BUBBLE_V2_BOTTOM_ACTION_END_CALL); + // Hide without animation + hideHelper( + () -> { + defaultAfterHidingAnimation(); + DialerCall call = getCall(); + if (call != null) { + call.disconnect(); + Toast.makeText(context, R.string.incall_call_ended, Toast.LENGTH_SHORT).show(); + } + }); + } + private boolean isDrawingFromRight() { return (windowParams.gravity & Gravity.RIGHT) == Gravity.RIGHT; } @@ -896,11 +924,7 @@ public class NewBubble { } private void logBasicOrCallImpression(DialerImpression.Type impressionType) { - // Bubble is shown for outgoing, active or background call - DialerCall call = CallList.getInstance().getOutgoingCall(); - if (call == null) { - call = CallList.getInstance().getActiveOrBackgroundCall(); - } + DialerCall call = getCall(); if (call != null) { Logger.get(context) .logCallImpression(impressionType, call.getUniqueCallId(), call.getTimeAddedMs()); @@ -909,6 +933,15 @@ public class NewBubble { } } + private DialerCall getCall() { + // Bubble is shown for outgoing, active or background call + DialerCall call = CallList.getInstance().getOutgoingCall(); + if (call == null) { + call = CallList.getInstance().getActiveOrBackgroundCall(); + } + return call; + } + private void setPrimaryButtonAccessibilityAction(String description) { viewHolder .getPrimaryButton() diff --git a/java/com/android/newbubble/NewMoveHandler.java b/java/com/android/newbubble/NewMoveHandler.java index 9e6d95553..c00c10729 100644 --- a/java/com/android/newbubble/NewMoveHandler.java +++ b/java/com/android/newbubble/NewMoveHandler.java @@ -51,6 +51,7 @@ class NewMoveHandler implements OnTouchListener { private final int bubbleShadowPaddingHorizontal; private final int bubbleExpandedViewWidth; private final float touchSlopSquared; + private final BottomActionViewController bottomActionViewController; private boolean clickable = true; private boolean isMoving; @@ -156,6 +157,8 @@ class NewMoveHandler implements OnTouchListener { // efficient than needing to take a square root. touchSlopSquared = (float) Math.pow(ViewConfiguration.get(context).getScaledTouchSlop(), 2); + bottomActionViewController = new BottomActionViewController(context); + targetView.setOnTouchListener(this); } @@ -200,7 +203,9 @@ class NewMoveHandler implements OnTouchListener { if (!isMoving) { isMoving = true; bubble.onMoveStart(); + bottomActionViewController.createAndShowBottomActionView(); } + bottomActionViewController.highlightIfHover(eventX, eventY); ensureSprings(); @@ -229,11 +234,16 @@ class NewMoveHandler implements OnTouchListener { moveXAnimation.animateToFinalPosition(target.x); moveYAnimation.animateToFinalPosition(target.y); + } else if (bottomActionViewController.isDismissHighlighted()) { + bubble.bottomActionDismiss(); + } else if (bottomActionViewController.isEndCallHighlighted()) { + bubble.bottomActionEndCall(); } else { snapX(); } isMoving = false; bubble.onMoveFinish(); + bottomActionViewController.destroyBottomActionView(); } else { v.performClick(); if (clickable) { diff --git a/java/com/android/newbubble/res/drawable/bottom_action_scrim.xml b/java/com/android/newbubble/res/drawable/bottom_action_scrim.xml new file mode 100644 index 000000000..bd13382ec --- /dev/null +++ b/java/com/android/newbubble/res/drawable/bottom_action_scrim.xml @@ -0,0 +1,24 @@ + + + + + + diff --git a/java/com/android/newbubble/res/layout/bottom_action_base.xml b/java/com/android/newbubble/res/layout/bottom_action_base.xml new file mode 100644 index 000000000..bf08e1be5 --- /dev/null +++ b/java/com/android/newbubble/res/layout/bottom_action_base.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/com/android/newbubble/res/values/values.xml b/java/com/android/newbubble/res/values/values.xml index 71f813ac6..040a5be1c 100644 --- a/java/com/android/newbubble/res/values/values.xml +++ b/java/com/android/newbubble/res/values/values.xml @@ -41,4 +41,6 @@ 8dp 24dp 4dp + + 180dp -- cgit v1.2.3