/* * 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_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) { // Unhighlight dismiss dismissView.animate().scaleX(1f).scaleY(1f).setDuration(HIGHLIGHT_TARGET_DURATION).start(); dismissHighlighted = false; } else if (!shouldHighlightEndCall && endCallHighlighted) { // Unhighlight end call endCallView.animate().scaleX(1f).scaleY(1f).setDuration(HIGHLIGHT_TARGET_DURATION).start(); endCallHighlighted = false; } // Scale highlight target larger if (shouldHighlightDismiss && !dismissHighlighted) { // 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) { // 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; } }