yueg | f473e1d | 2018-01-02 16:23:14 -0800 | [diff] [blame] | 1 | /* |
| 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 | */ |
| 16 | package com.android.newbubble; |
| 17 | |
| 18 | import android.content.Context; |
| 19 | import android.graphics.PixelFormat; |
| 20 | import android.support.v4.os.BuildCompat; |
| 21 | import android.view.Gravity; |
| 22 | import android.view.LayoutInflater; |
| 23 | import android.view.View; |
| 24 | import android.view.WindowManager; |
| 25 | import android.view.WindowManager.LayoutParams; |
| 26 | import android.view.animation.LinearInterpolator; |
| 27 | |
| 28 | /** Controller for showing and hiding bubble bottom action view. */ |
| 29 | final 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; |
yueg | 6518fdb | 2018-01-09 17:30:36 -0800 | [diff] [blame] | 35 | private static final int SHOW_HIDE_TARGET_DURATION = 175; |
| 36 | private static final int HIGHLIGHT_TARGET_DURATION = 150; |
yueg | 35f0cc1 | 2018-01-18 15:56:25 -0800 | [diff] [blame] | 37 | private static final float HIGHLIGHT_TARGET_SCALE = 1.3f; |
yueg | 6518fdb | 2018-01-09 17:30:36 -0800 | [diff] [blame] | 38 | private static final float UNHIGHLIGHT_TARGET_ALPHA = 0.38f; |
yueg | f473e1d | 2018-01-02 16:23:14 -0800 | [diff] [blame] | 39 | |
| 40 | private final Context context; |
| 41 | private final WindowManager windowManager; |
| 42 | private final int gradientHeight; |
yueg | 6518fdb | 2018-01-09 17:30:36 -0800 | [diff] [blame] | 43 | private final int textOffsetSize; |
yueg | b52ea74 | 2018-01-23 13:04:18 -0800 | [diff] [blame] | 44 | private int bottomActionViewTop; |
yueg | f473e1d | 2018-01-02 16:23:14 -0800 | [diff] [blame] | 45 | |
| 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); |
yueg | 6518fdb | 2018-01-09 17:30:36 -0800 | [diff] [blame] | 58 | textOffsetSize = |
| 59 | context.getResources().getDimensionPixelSize(R.dimen.bubble_bottom_action_text_offset); |
yueg | f473e1d | 2018-01-02 16:23:14 -0800 | [diff] [blame] | 60 | } |
| 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 |
yueg | b52ea74 | 2018-01-23 13:04:18 -0800 | [diff] [blame] | 78 | bottomActionViewTop = context.getResources().getDisplayMetrics().heightPixels - gradientHeight; |
yueg | f473e1d | 2018-01-02 16:23:14 -0800 | [diff] [blame] | 79 | 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) |
yueg | 6518fdb | 2018-01-09 17:30:36 -0800 | [diff] [blame] | 111 | .setDuration(SHOW_HIDE_TARGET_DURATION) |
yueg | f473e1d | 2018-01-02 16:23:14 -0800 | [diff] [blame] | 112 | .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()) |
yueg | 6518fdb | 2018-01-09 17:30:36 -0800 | [diff] [blame] | 124 | .setDuration(SHOW_HIDE_TARGET_DURATION) |
yueg | f473e1d | 2018-01-02 16:23:14 -0800 | [diff] [blame] | 125 | .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 | |
yueg | 6518fdb | 2018-01-09 17:30:36 -0800 | [diff] [blame] | 150 | // 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 |
yueg | f473e1d | 2018-01-02 16:23:14 -0800 | [diff] [blame] | 159 | if (!shouldHighlightDismiss && dismissHighlighted) { |
| 160 | // Unhighlight dismiss |
yueg | 6518fdb | 2018-01-09 17:30:36 -0800 | [diff] [blame] | 161 | dismissView.animate().scaleX(1f).scaleY(1f).setDuration(HIGHLIGHT_TARGET_DURATION).start(); |
yueg | f473e1d | 2018-01-02 16:23:14 -0800 | [diff] [blame] | 162 | dismissHighlighted = false; |
| 163 | } else if (!shouldHighlightEndCall && endCallHighlighted) { |
| 164 | // Unhighlight end call |
yueg | 6518fdb | 2018-01-09 17:30:36 -0800 | [diff] [blame] | 165 | endCallView.animate().scaleX(1f).scaleY(1f).setDuration(HIGHLIGHT_TARGET_DURATION).start(); |
yueg | f473e1d | 2018-01-02 16:23:14 -0800 | [diff] [blame] | 166 | endCallHighlighted = false; |
| 167 | } |
| 168 | |
yueg | 6518fdb | 2018-01-09 17:30:36 -0800 | [diff] [blame] | 169 | // Scale highlight target larger |
yueg | f473e1d | 2018-01-02 16:23:14 -0800 | [diff] [blame] | 170 | if (shouldHighlightDismiss && !dismissHighlighted) { |
| 171 | // Highlight dismiss |
yueg | 6518fdb | 2018-01-09 17:30:36 -0800 | [diff] [blame] | 172 | dismissView.setPivotY(dismissView.getHeight() / 2 + textOffsetSize); |
yueg | f473e1d | 2018-01-02 16:23:14 -0800 | [diff] [blame] | 173 | dismissView |
| 174 | .animate() |
| 175 | .scaleX(HIGHLIGHT_TARGET_SCALE) |
| 176 | .scaleY(HIGHLIGHT_TARGET_SCALE) |
yueg | 6518fdb | 2018-01-09 17:30:36 -0800 | [diff] [blame] | 177 | .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) |
yueg | f473e1d | 2018-01-02 16:23:14 -0800 | [diff] [blame] | 184 | .start(); |
| 185 | dismissHighlighted = true; |
| 186 | } else if (shouldHighlightEndCall && !endCallHighlighted) { |
| 187 | // Highlight end call |
yueg | 6518fdb | 2018-01-09 17:30:36 -0800 | [diff] [blame] | 188 | endCallView.setPivotY(dismissView.getHeight() / 2 + textOffsetSize); |
yueg | f473e1d | 2018-01-02 16:23:14 -0800 | [diff] [blame] | 189 | endCallView |
| 190 | .animate() |
| 191 | .scaleX(HIGHLIGHT_TARGET_SCALE) |
| 192 | .scaleY(HIGHLIGHT_TARGET_SCALE) |
yueg | 6518fdb | 2018-01-09 17:30:36 -0800 | [diff] [blame] | 193 | .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) |
yueg | f473e1d | 2018-01-02 16:23:14 -0800 | [diff] [blame] | 200 | .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 | } |