diff options
Diffstat (limited to 'java/com/android/newbubble')
6 files changed, 321 insertions, 5 deletions
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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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 + --> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <gradient + android:angle="90" + android:endColor="@android:color/transparent" + android:startColor="#AA000000"/> +</shape> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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 + --> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="@dimen/bubble_bottom_action_view_height" + android:orientation="horizontal" + android:background="@drawable/bottom_action_scrim"> + + <LinearLayout + android:id="@+id/bottom_action_dismiss_layout" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:gravity="center_horizontal|center_vertical"> + <ImageView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/bubble_button_icon_padding" + android:src="@drawable/quantum_ic_clear_vd_theme_24" + android:tint="@color/bubble_button_color_white"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAllCaps="true" + android:textColor="@color/bubble_button_color_white" + android:text="Hide"/> + </LinearLayout> + + <LinearLayout + android:id="@+id/bottom_action_end_call_layout" + android:layout_width="0dp" + android:layout_height="match_parent" + android:layout_weight="1" + android:gravity="center_horizontal|center_vertical"> + <ImageView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/bubble_button_icon_padding" + android:src="@drawable/quantum_ic_call_end_vd_theme_24" + android:tint="@color/bubble_button_color_white"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAllCaps="true" + android:textColor="@color/bubble_button_color_white" + android:text="End call"/> + </LinearLayout> + +</LinearLayout>
\ 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 @@ <dimen name="bubble_expanded_separator_height">8dp</dimen> <dimen name="bubble_small_icon_size">24dp</dimen> <dimen name="bubble_small_icon_padding">4dp</dimen> + + <dimen name="bubble_bottom_action_view_height">180dp</dimen> </resources> |