blob: b480ac986b9558d0f111a80d9f7fb1bb303a48fc [file] [log] [blame]
yuegf473e1d2018-01-02 16:23:14 -08001/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.newbubble;
17
18import android.content.Context;
19import android.graphics.PixelFormat;
20import android.support.v4.os.BuildCompat;
21import android.view.Gravity;
22import android.view.LayoutInflater;
23import android.view.View;
24import android.view.WindowManager;
25import android.view.WindowManager.LayoutParams;
yueg2a422f72018-01-24 15:20:10 -080026import android.view.accessibility.AccessibilityEvent;
yuegf473e1d2018-01-02 16:23:14 -080027import android.view.animation.LinearInterpolator;
28
29/** Controller for showing and hiding bubble bottom action view. */
30final class BottomActionViewController {
31
32 // This delay controls how long to wait before we show the target when the user first moves
33 // the bubble, to prevent the bottom action view from animating if the user just wants to fling
34 // the bubble.
35 private static final int SHOW_TARGET_DELAY = 100;
yueg6518fdb2018-01-09 17:30:36 -080036 private static final int SHOW_HIDE_TARGET_DURATION = 175;
37 private static final int HIGHLIGHT_TARGET_DURATION = 150;
yueg35f0cc12018-01-18 15:56:25 -080038 private static final float HIGHLIGHT_TARGET_SCALE = 1.3f;
yueg6518fdb2018-01-09 17:30:36 -080039 private static final float UNHIGHLIGHT_TARGET_ALPHA = 0.38f;
yuegf473e1d2018-01-02 16:23:14 -080040
41 private final Context context;
42 private final WindowManager windowManager;
43 private final int gradientHeight;
yueg6518fdb2018-01-09 17:30:36 -080044 private final int textOffsetSize;
yuegb52ea742018-01-23 13:04:18 -080045 private int bottomActionViewTop;
yuegf473e1d2018-01-02 16:23:14 -080046
47 private View bottomActionView;
48 private View dismissView;
49 private View endCallView;
50
51 private boolean dismissHighlighted;
52 private boolean endCallHighlighted;
53
54 public BottomActionViewController(Context context) {
55 this.context = context;
56 windowManager = context.getSystemService(WindowManager.class);
57 gradientHeight =
58 context.getResources().getDimensionPixelSize(R.dimen.bubble_bottom_action_view_height);
yueg6518fdb2018-01-09 17:30:36 -080059 textOffsetSize =
60 context.getResources().getDimensionPixelSize(R.dimen.bubble_bottom_action_text_offset);
yuegf473e1d2018-01-02 16:23:14 -080061 }
62
63 /** Creates and show the bottom action view. */
64 public void createAndShowBottomActionView() {
65 if (bottomActionView != null) {
66 return;
67 }
68
69 // Create a new view for the dismiss target
70 bottomActionView = LayoutInflater.from(context).inflate(R.layout.bottom_action_base, null);
71 bottomActionView.setAlpha(0);
72
73 // Sub views
74 dismissView = bottomActionView.findViewById(R.id.bottom_action_dismiss_layout);
75 endCallView = bottomActionView.findViewById(R.id.bottom_action_end_call_layout);
76
77 // Add the target to the window
78 // TODO(yueg): use TYPE_NAVIGATION_BAR_PANEL to draw over navigation bar
yuegb52ea742018-01-23 13:04:18 -080079 bottomActionViewTop = context.getResources().getDisplayMetrics().heightPixels - gradientHeight;
yuegf473e1d2018-01-02 16:23:14 -080080 LayoutParams layoutParams =
81 new LayoutParams(
82 LayoutParams.MATCH_PARENT,
83 gradientHeight,
84 0,
85 bottomActionViewTop,
86 BuildCompat.isAtLeastO()
87 ? LayoutParams.TYPE_APPLICATION_OVERLAY
88 : LayoutParams.TYPE_SYSTEM_OVERLAY,
89 LayoutParams.FLAG_LAYOUT_IN_SCREEN
90 | LayoutParams.FLAG_NOT_TOUCHABLE
91 | LayoutParams.FLAG_NOT_FOCUSABLE,
92 PixelFormat.TRANSLUCENT);
93 layoutParams.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
94 windowManager.addView(bottomActionView, layoutParams);
95 bottomActionView.setSystemUiVisibility(
96 View.SYSTEM_UI_FLAG_LAYOUT_STABLE
97 | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
98 | View.SYSTEM_UI_FLAG_FULLSCREEN);
99 bottomActionView
100 .getRootView()
101 .setSystemUiVisibility(
102 View.SYSTEM_UI_FLAG_LAYOUT_STABLE
103 | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
104 | View.SYSTEM_UI_FLAG_FULLSCREEN);
105
106 // Shows the botton action view
107 bottomActionView
108 .animate()
109 .alpha(1f)
110 .setInterpolator(new LinearInterpolator())
111 .setStartDelay(SHOW_TARGET_DELAY)
yueg6518fdb2018-01-09 17:30:36 -0800112 .setDuration(SHOW_HIDE_TARGET_DURATION)
yuegf473e1d2018-01-02 16:23:14 -0800113 .start();
114 }
115
116 /** Hides and destroys the bottom action view. */
117 public void destroyBottomActionView() {
118 if (bottomActionView == null) {
119 return;
120 }
121 bottomActionView
122 .animate()
123 .alpha(0f)
124 .setInterpolator(new LinearInterpolator())
yueg6518fdb2018-01-09 17:30:36 -0800125 .setDuration(SHOW_HIDE_TARGET_DURATION)
yuegf473e1d2018-01-02 16:23:14 -0800126 .withEndAction(
127 () -> {
128 // Use removeViewImmediate instead of removeView to avoid view flashing before removed
129 windowManager.removeViewImmediate(bottomActionView);
130 bottomActionView = null;
131 })
132 .start();
133 }
134
135 /**
136 * Change highlight state of dismiss view and end call view according to current touch point.
137 * Highlight the view with touch point moving into its boundary. Unhighlight the view with touch
138 * point moving out of its boundary.
139 *
140 * @param x x position of current touch point
141 * @param y y position of current touch point
142 */
143 public void highlightIfHover(float x, float y) {
144 if (bottomActionView == null) {
145 return;
146 }
147 final int middle = context.getResources().getDisplayMetrics().widthPixels / 2;
148 boolean shouldHighlightDismiss = y > bottomActionViewTop && x < middle;
149 boolean shouldHighlightEndCall = y > bottomActionViewTop && x >= middle;
150
yueg6518fdb2018-01-09 17:30:36 -0800151 // Set target alpha back to 1
152 if (!dismissHighlighted && endCallHighlighted && !shouldHighlightEndCall) {
153 dismissView.animate().alpha(1f).setDuration(HIGHLIGHT_TARGET_DURATION).start();
154 }
155 if (!endCallHighlighted && dismissHighlighted && !shouldHighlightDismiss) {
156 endCallView.animate().alpha(1f).setDuration(HIGHLIGHT_TARGET_DURATION).start();
157 }
158
159 // Scale unhighlight target back to 1x
yuegf473e1d2018-01-02 16:23:14 -0800160 if (!shouldHighlightDismiss && dismissHighlighted) {
yueg2a422f72018-01-24 15:20:10 -0800161 // A11y
162 dismissView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
yuegf473e1d2018-01-02 16:23:14 -0800163 // Unhighlight dismiss
yueg6518fdb2018-01-09 17:30:36 -0800164 dismissView.animate().scaleX(1f).scaleY(1f).setDuration(HIGHLIGHT_TARGET_DURATION).start();
yuegf473e1d2018-01-02 16:23:14 -0800165 dismissHighlighted = false;
166 } else if (!shouldHighlightEndCall && endCallHighlighted) {
yueg2a422f72018-01-24 15:20:10 -0800167 // A11y
168 endCallView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
yuegf473e1d2018-01-02 16:23:14 -0800169 // Unhighlight end call
yueg6518fdb2018-01-09 17:30:36 -0800170 endCallView.animate().scaleX(1f).scaleY(1f).setDuration(HIGHLIGHT_TARGET_DURATION).start();
yuegf473e1d2018-01-02 16:23:14 -0800171 endCallHighlighted = false;
172 }
173
yueg6518fdb2018-01-09 17:30:36 -0800174 // Scale highlight target larger
yuegf473e1d2018-01-02 16:23:14 -0800175 if (shouldHighlightDismiss && !dismissHighlighted) {
yueg2a422f72018-01-24 15:20:10 -0800176 // A11y
177 dismissView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
yuegf473e1d2018-01-02 16:23:14 -0800178 // Highlight dismiss
yueg6518fdb2018-01-09 17:30:36 -0800179 dismissView.setPivotY(dismissView.getHeight() / 2 + textOffsetSize);
yuegf473e1d2018-01-02 16:23:14 -0800180 dismissView
181 .animate()
182 .scaleX(HIGHLIGHT_TARGET_SCALE)
183 .scaleY(HIGHLIGHT_TARGET_SCALE)
yueg6518fdb2018-01-09 17:30:36 -0800184 .setDuration(HIGHLIGHT_TARGET_DURATION)
185 .start();
186 // Fade the other target
187 endCallView
188 .animate()
189 .alpha(UNHIGHLIGHT_TARGET_ALPHA)
190 .setDuration(HIGHLIGHT_TARGET_DURATION)
yuegf473e1d2018-01-02 16:23:14 -0800191 .start();
192 dismissHighlighted = true;
193 } else if (shouldHighlightEndCall && !endCallHighlighted) {
yueg2a422f72018-01-24 15:20:10 -0800194 // A11y
195 endCallView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
yuegf473e1d2018-01-02 16:23:14 -0800196 // Highlight end call
yueg6518fdb2018-01-09 17:30:36 -0800197 endCallView.setPivotY(dismissView.getHeight() / 2 + textOffsetSize);
yuegf473e1d2018-01-02 16:23:14 -0800198 endCallView
199 .animate()
200 .scaleX(HIGHLIGHT_TARGET_SCALE)
201 .scaleY(HIGHLIGHT_TARGET_SCALE)
yueg6518fdb2018-01-09 17:30:36 -0800202 .setDuration(HIGHLIGHT_TARGET_DURATION)
203 .start();
204 // Fade the other target
205 dismissView
206 .animate()
207 .alpha(UNHIGHLIGHT_TARGET_ALPHA)
208 .setDuration(HIGHLIGHT_TARGET_DURATION)
yuegf473e1d2018-01-02 16:23:14 -0800209 .start();
210 endCallHighlighted = true;
211 }
212 }
213
214 public boolean isDismissHighlighted() {
215 return dismissHighlighted;
216 }
217
218 public boolean isEndCallHighlighted() {
219 return endCallHighlighted;
220 }
221}