/* * Copyright (C) 2014 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.incallui; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.app.Activity; import android.app.Fragment; import android.app.FragmentManager; import android.graphics.Outline; import android.graphics.Point; import android.os.Bundle; import android.view.Display; import android.view.LayoutInflater; import android.view.View; import android.view.ViewAnimationUtils; import android.view.ViewGroup; import android.view.ViewOutlineProvider; import android.view.ViewTreeObserver; import android.view.ViewTreeObserver.OnPreDrawListener; import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette; public class CircularRevealFragment extends Fragment { static final String TAG = "CircularRevealFragment"; private Point mTouchPoint; private OnCircularRevealCompleteListener mListener; private boolean mAnimationStarted; interface OnCircularRevealCompleteListener { public void onCircularRevealComplete(FragmentManager fm); } public static void startCircularReveal(FragmentManager fm, Point touchPoint, OnCircularRevealCompleteListener listener) { if (fm.findFragmentByTag(TAG) == null) { fm.beginTransaction().add(R.id.main, new CircularRevealFragment(touchPoint, listener), TAG) .commitAllowingStateLoss(); } else { Log.w(TAG, "An instance of CircularRevealFragment already exists"); } } public static void endCircularReveal(FragmentManager fm) { final Fragment fragment = fm.findFragmentByTag(TAG); if (fragment != null) { fm.beginTransaction().remove(fragment).commitAllowingStateLoss(); } } /** * Empty constructor used only by the {@link FragmentManager}. */ public CircularRevealFragment() {} public CircularRevealFragment(Point touchPoint, OnCircularRevealCompleteListener listener) { mTouchPoint = touchPoint; mListener = listener; } @Override public void onResume() { super.onResume(); if (!mAnimationStarted) { // Only run the animation once for each instance of the fragment startOutgoingAnimation(InCallPresenter.getInstance().getThemeColors()); } mAnimationStarted = true; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.outgoing_call_animation, container, false); } public void startOutgoingAnimation(MaterialPalette palette) { final Activity activity = getActivity(); if (activity == null) { Log.w(this, "Asked to do outgoing call animation when not attached"); return; } final View view = activity.getWindow().getDecorView(); // The circle starts from an initial size of 0 so clip it such that it is invisible. // Otherwise the first frame is drawn with a fully opaque screen which causes jank. When // the animation later starts, this clip will be clobbered by the circular reveal clip. // See ViewAnimationUtils.createCircularReveal. view.setOutlineProvider(new ViewOutlineProvider() { @Override public void getOutline(View view, Outline outline) { // Using (0, 0, 0, 0) will not work since the outline will simply be treated as // an empty outline. outline.setOval(-1, -1, 0, 0); } }); view.setClipToOutline(true); if (palette != null) { view.findViewById(R.id.outgoing_call_animation_circle).setBackgroundColor( palette.mPrimaryColor); activity.getWindow().setStatusBarColor(palette.mSecondaryColor); } view.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() { @Override public boolean onPreDraw() { final ViewTreeObserver vto = view.getViewTreeObserver(); if (vto.isAlive()) { vto.removeOnPreDrawListener(this); } final Animator animator = getRevealAnimator(mTouchPoint); if (animator != null) { animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { view.setClipToOutline(false); if (mListener != null) { mListener.onCircularRevealComplete(getFragmentManager()); } } }); animator.start(); } return false; } }); } private Animator getRevealAnimator(Point touchPoint) { final Activity activity = getActivity(); if (activity == null) { return null; } final View view = activity.getWindow().getDecorView(); final Display display = activity.getWindowManager().getDefaultDisplay(); final Point size = new Point(); display.getSize(size); int startX = size.x / 2; int startY = size.y / 2; if (touchPoint != null) { startX = touchPoint.x; startY = touchPoint.y; } final Animator valueAnimator = ViewAnimationUtils.createCircularReveal(view, startX, startY, 0, Math.max(size.x, size.y)); valueAnimator.setDuration(getResources().getInteger(R.integer.reveal_animation_duration)); return valueAnimator; } }