blob: 04e0e5fe7861169c52509c2e74f997ae3a718c16 [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;
26import android.view.animation.LinearInterpolator;
27
28/** Controller for showing and hiding bubble bottom action view. */
29final class BottomActionViewController {
30
31 // This delay controls how long to wait before we show the target when the user first moves
32 // the bubble, to prevent the bottom action view from animating if the user just wants to fling
33 // the bubble.
34 private static final int SHOW_TARGET_DELAY = 100;
yueg6518fdb2018-01-09 17:30:36 -080035 private static final int SHOW_HIDE_TARGET_DURATION = 175;
36 private static final int HIGHLIGHT_TARGET_DURATION = 150;
yueg35f0cc12018-01-18 15:56:25 -080037 private static final float HIGHLIGHT_TARGET_SCALE = 1.3f;
yueg6518fdb2018-01-09 17:30:36 -080038 private static final float UNHIGHLIGHT_TARGET_ALPHA = 0.38f;
yuegf473e1d2018-01-02 16:23:14 -080039
40 private final Context context;
41 private final WindowManager windowManager;
42 private final int gradientHeight;
yueg6518fdb2018-01-09 17:30:36 -080043 private final int textOffsetSize;
yuegb52ea742018-01-23 13:04:18 -080044 private int bottomActionViewTop;
yuegf473e1d2018-01-02 16:23:14 -080045
46 private View bottomActionView;
47 private View dismissView;
48 private View endCallView;
49
50 private boolean dismissHighlighted;
51 private boolean endCallHighlighted;
52
53 public BottomActionViewController(Context context) {
54 this.context = context;
55 windowManager = context.getSystemService(WindowManager.class);
56 gradientHeight =
57 context.getResources().getDimensionPixelSize(R.dimen.bubble_bottom_action_view_height);
yueg6518fdb2018-01-09 17:30:36 -080058 textOffsetSize =
59 context.getResources().getDimensionPixelSize(R.dimen.bubble_bottom_action_text_offset);
yuegf473e1d2018-01-02 16:23:14 -080060 }
61
62 /** Creates and show the bottom action view. */
63 public void createAndShowBottomActionView() {
64 if (bottomActionView != null) {
65 return;
66 }
67
68 // Create a new view for the dismiss target
69 bottomActionView = LayoutInflater.from(context).inflate(R.layout.bottom_action_base, null);
70 bottomActionView.setAlpha(0);
71
72 // Sub views
73 dismissView = bottomActionView.findViewById(R.id.bottom_action_dismiss_layout);
74 endCallView = bottomActionView.findViewById(R.id.bottom_action_end_call_layout);
75
76 // Add the target to the window
77 // TODO(yueg): use TYPE_NAVIGATION_BAR_PANEL to draw over navigation bar
yuegb52ea742018-01-23 13:04:18 -080078 bottomActionViewTop = context.getResources().getDisplayMetrics().heightPixels - gradientHeight;
yuegf473e1d2018-01-02 16:23:14 -080079 LayoutParams layoutParams =
80 new LayoutParams(
81 LayoutParams.MATCH_PARENT,
82 gradientHeight,
83 0,
84 bottomActionViewTop,
85 BuildCompat.isAtLeastO()
86 ? LayoutParams.TYPE_APPLICATION_OVERLAY
87 : LayoutParams.TYPE_SYSTEM_OVERLAY,
88 LayoutParams.FLAG_LAYOUT_IN_SCREEN
89 | LayoutParams.FLAG_NOT_TOUCHABLE
90 | LayoutParams.FLAG_NOT_FOCUSABLE,
91 PixelFormat.TRANSLUCENT);
92 layoutParams.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
93 windowManager.addView(bottomActionView, layoutParams);
94 bottomActionView.setSystemUiVisibility(
95 View.SYSTEM_UI_FLAG_LAYOUT_STABLE
96 | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
97 | View.SYSTEM_UI_FLAG_FULLSCREEN);
98 bottomActionView
99 .getRootView()
100 .setSystemUiVisibility(
101 View.SYSTEM_UI_FLAG_LAYOUT_STABLE
102 | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
103 | View.SYSTEM_UI_FLAG_FULLSCREEN);
104
105 // Shows the botton action view
106 bottomActionView
107 .animate()
108 .alpha(1f)
109 .setInterpolator(new LinearInterpolator())
110 .setStartDelay(SHOW_TARGET_DELAY)
yueg6518fdb2018-01-09 17:30:36 -0800111 .setDuration(SHOW_HIDE_TARGET_DURATION)
yuegf473e1d2018-01-02 16:23:14 -0800112 .start();
113 }
114
115 /** Hides and destroys the bottom action view. */
116 public void destroyBottomActionView() {
117 if (bottomActionView == null) {
118 return;
119 }
120 bottomActionView
121 .animate()
122 .alpha(0f)
123 .setInterpolator(new LinearInterpolator())
yueg6518fdb2018-01-09 17:30:36 -0800124 .setDuration(SHOW_HIDE_TARGET_DURATION)
yuegf473e1d2018-01-02 16:23:14 -0800125 .withEndAction(
126 () -> {
127 // Use removeViewImmediate instead of removeView to avoid view flashing before removed
128 windowManager.removeViewImmediate(bottomActionView);
129 bottomActionView = null;
130 })
131 .start();
132 }
133
134 /**
135 * Change highlight state of dismiss view and end call view according to current touch point.
136 * Highlight the view with touch point moving into its boundary. Unhighlight the view with touch
137 * point moving out of its boundary.
138 *
139 * @param x x position of current touch point
140 * @param y y position of current touch point
141 */
142 public void highlightIfHover(float x, float y) {
143 if (bottomActionView == null) {
144 return;
145 }
146 final int middle = context.getResources().getDisplayMetrics().widthPixels / 2;
147 boolean shouldHighlightDismiss = y > bottomActionViewTop && x < middle;
148 boolean shouldHighlightEndCall = y > bottomActionViewTop && x >= middle;
149
yueg6518fdb2018-01-09 17:30:36 -0800150 // Set target alpha back to 1
151 if (!dismissHighlighted && endCallHighlighted && !shouldHighlightEndCall) {
152 dismissView.animate().alpha(1f).setDuration(HIGHLIGHT_TARGET_DURATION).start();
153 }
154 if (!endCallHighlighted && dismissHighlighted && !shouldHighlightDismiss) {
155 endCallView.animate().alpha(1f).setDuration(HIGHLIGHT_TARGET_DURATION).start();
156 }
157
158 // Scale unhighlight target back to 1x
yuegf473e1d2018-01-02 16:23:14 -0800159 if (!shouldHighlightDismiss && dismissHighlighted) {
160 // Unhighlight dismiss
yueg6518fdb2018-01-09 17:30:36 -0800161 dismissView.animate().scaleX(1f).scaleY(1f).setDuration(HIGHLIGHT_TARGET_DURATION).start();
yuegf473e1d2018-01-02 16:23:14 -0800162 dismissHighlighted = false;
163 } else if (!shouldHighlightEndCall && endCallHighlighted) {
164 // Unhighlight end call
yueg6518fdb2018-01-09 17:30:36 -0800165 endCallView.animate().scaleX(1f).scaleY(1f).setDuration(HIGHLIGHT_TARGET_DURATION).start();
yuegf473e1d2018-01-02 16:23:14 -0800166 endCallHighlighted = false;
167 }
168
yueg6518fdb2018-01-09 17:30:36 -0800169 // Scale highlight target larger
yuegf473e1d2018-01-02 16:23:14 -0800170 if (shouldHighlightDismiss && !dismissHighlighted) {
171 // Highlight dismiss
yueg6518fdb2018-01-09 17:30:36 -0800172 dismissView.setPivotY(dismissView.getHeight() / 2 + textOffsetSize);
yuegf473e1d2018-01-02 16:23:14 -0800173 dismissView
174 .animate()
175 .scaleX(HIGHLIGHT_TARGET_SCALE)
176 .scaleY(HIGHLIGHT_TARGET_SCALE)
yueg6518fdb2018-01-09 17:30:36 -0800177 .setDuration(HIGHLIGHT_TARGET_DURATION)
178 .start();
179 // Fade the other target
180 endCallView
181 .animate()
182 .alpha(UNHIGHLIGHT_TARGET_ALPHA)
183 .setDuration(HIGHLIGHT_TARGET_DURATION)
yuegf473e1d2018-01-02 16:23:14 -0800184 .start();
185 dismissHighlighted = true;
186 } else if (shouldHighlightEndCall && !endCallHighlighted) {
187 // Highlight end call
yueg6518fdb2018-01-09 17:30:36 -0800188 endCallView.setPivotY(dismissView.getHeight() / 2 + textOffsetSize);
yuegf473e1d2018-01-02 16:23:14 -0800189 endCallView
190 .animate()
191 .scaleX(HIGHLIGHT_TARGET_SCALE)
192 .scaleY(HIGHLIGHT_TARGET_SCALE)
yueg6518fdb2018-01-09 17:30:36 -0800193 .setDuration(HIGHLIGHT_TARGET_DURATION)
194 .start();
195 // Fade the other target
196 dismissView
197 .animate()
198 .alpha(UNHIGHLIGHT_TARGET_ALPHA)
199 .setDuration(HIGHLIGHT_TARGET_DURATION)
yuegf473e1d2018-01-02 16:23:14 -0800200 .start();
201 endCallHighlighted = true;
202 }
203 }
204
205 public boolean isDismissHighlighted() {
206 return dismissHighlighted;
207 }
208
209 public boolean isEndCallHighlighted() {
210 return endCallHighlighted;
211 }
212}