blob: b480ac986b9558d0f111a80d9f7fb1bb303a48fc [file] [log] [blame]
/*
* 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.accessibility.AccessibilityEvent;
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_HIDE_TARGET_DURATION = 175;
private static final int HIGHLIGHT_TARGET_DURATION = 150;
private static final float HIGHLIGHT_TARGET_SCALE = 1.3f;
private static final float UNHIGHLIGHT_TARGET_ALPHA = 0.38f;
private final Context context;
private final WindowManager windowManager;
private final int gradientHeight;
private final int textOffsetSize;
private 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);
textOffsetSize =
context.getResources().getDimensionPixelSize(R.dimen.bubble_bottom_action_text_offset);
}
/** 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
bottomActionViewTop = context.getResources().getDisplayMetrics().heightPixels - gradientHeight;
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_HIDE_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(SHOW_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;
// Set target alpha back to 1
if (!dismissHighlighted && endCallHighlighted && !shouldHighlightEndCall) {
dismissView.animate().alpha(1f).setDuration(HIGHLIGHT_TARGET_DURATION).start();
}
if (!endCallHighlighted && dismissHighlighted && !shouldHighlightDismiss) {
endCallView.animate().alpha(1f).setDuration(HIGHLIGHT_TARGET_DURATION).start();
}
// Scale unhighlight target back to 1x
if (!shouldHighlightDismiss && dismissHighlighted) {
// A11y
dismissView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
// Unhighlight dismiss
dismissView.animate().scaleX(1f).scaleY(1f).setDuration(HIGHLIGHT_TARGET_DURATION).start();
dismissHighlighted = false;
} else if (!shouldHighlightEndCall && endCallHighlighted) {
// A11y
endCallView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
// Unhighlight end call
endCallView.animate().scaleX(1f).scaleY(1f).setDuration(HIGHLIGHT_TARGET_DURATION).start();
endCallHighlighted = false;
}
// Scale highlight target larger
if (shouldHighlightDismiss && !dismissHighlighted) {
// A11y
dismissView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
// Highlight dismiss
dismissView.setPivotY(dismissView.getHeight() / 2 + textOffsetSize);
dismissView
.animate()
.scaleX(HIGHLIGHT_TARGET_SCALE)
.scaleY(HIGHLIGHT_TARGET_SCALE)
.setDuration(HIGHLIGHT_TARGET_DURATION)
.start();
// Fade the other target
endCallView
.animate()
.alpha(UNHIGHLIGHT_TARGET_ALPHA)
.setDuration(HIGHLIGHT_TARGET_DURATION)
.start();
dismissHighlighted = true;
} else if (shouldHighlightEndCall && !endCallHighlighted) {
// A11y
endCallView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
// Highlight end call
endCallView.setPivotY(dismissView.getHeight() / 2 + textOffsetSize);
endCallView
.animate()
.scaleX(HIGHLIGHT_TARGET_SCALE)
.scaleY(HIGHLIGHT_TARGET_SCALE)
.setDuration(HIGHLIGHT_TARGET_DURATION)
.start();
// Fade the other target
dismissView
.animate()
.alpha(UNHIGHLIGHT_TARGET_ALPHA)
.setDuration(HIGHLIGHT_TARGET_DURATION)
.start();
endCallHighlighted = true;
}
}
public boolean isDismissHighlighted() {
return dismissHighlighted;
}
public boolean isEndCallHighlighted() {
return endCallHighlighted;
}
}