Home | History | Annotate | Download | only in incallui
      1 /*
      2  * Copyright (C) 2014 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 
     17 package com.android.incallui;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.app.Activity;
     22 import android.app.Fragment;
     23 import android.app.FragmentManager;
     24 import android.graphics.Outline;
     25 import android.graphics.Point;
     26 import android.os.Bundle;
     27 import android.view.Display;
     28 import android.view.LayoutInflater;
     29 import android.view.View;
     30 import android.view.ViewAnimationUtils;
     31 import android.view.ViewGroup;
     32 import android.view.ViewOutlineProvider;
     33 import android.view.ViewTreeObserver;
     34 import android.view.ViewTreeObserver.OnPreDrawListener;
     35 
     36 import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette;
     37 import com.android.dialer.R;
     38 
     39 public class CircularRevealFragment extends Fragment {
     40     static final String TAG = "CircularRevealFragment";
     41 
     42     private Point mTouchPoint;
     43     private OnCircularRevealCompleteListener mListener;
     44     private boolean mAnimationStarted;
     45 
     46     interface OnCircularRevealCompleteListener {
     47         public void onCircularRevealComplete(FragmentManager fm);
     48     }
     49 
     50     public static void startCircularReveal(FragmentManager fm, Point touchPoint,
     51             OnCircularRevealCompleteListener listener) {
     52         if (fm.findFragmentByTag(TAG) == null) {
     53             fm.beginTransaction().add(R.id.main,
     54                     new CircularRevealFragment(touchPoint, listener), TAG)
     55                             .commitAllowingStateLoss();
     56         } else {
     57             Log.w(TAG, "An instance of CircularRevealFragment already exists");
     58         }
     59     }
     60 
     61     public static void endCircularReveal(FragmentManager fm) {
     62         final Fragment fragment = fm.findFragmentByTag(TAG);
     63         if (fragment != null) {
     64             fm.beginTransaction().remove(fragment).commitAllowingStateLoss();
     65         }
     66     }
     67 
     68     /**
     69      * Empty constructor used only by the {@link FragmentManager}.
     70      */
     71     public CircularRevealFragment() {}
     72 
     73     public CircularRevealFragment(Point touchPoint, OnCircularRevealCompleteListener listener) {
     74         mTouchPoint = touchPoint;
     75         mListener = listener;
     76     }
     77 
     78     @Override
     79     public void onResume() {
     80         super.onResume();
     81         if (!mAnimationStarted) {
     82             // Only run the animation once for each instance of the fragment
     83             startOutgoingAnimation(InCallPresenter.getInstance().getThemeColors());
     84         }
     85         mAnimationStarted = true;
     86     }
     87 
     88     @Override
     89     public View onCreateView(LayoutInflater inflater, ViewGroup container,
     90             Bundle savedInstanceState) {
     91         return inflater.inflate(R.layout.outgoing_call_animation, container, false);
     92     }
     93 
     94     public void startOutgoingAnimation(MaterialPalette palette) {
     95         final Activity activity = getActivity();
     96         if (activity == null) {
     97             Log.w(this, "Asked to do outgoing call animation when not attached");
     98             return;
     99         }
    100 
    101         final View view  = activity.getWindow().getDecorView();
    102 
    103         // The circle starts from an initial size of 0 so clip it such that it is invisible.
    104         // Otherwise the first frame is drawn with a fully opaque screen which causes jank. When
    105         // the animation later starts, this clip will be clobbered by the circular reveal clip.
    106         // See ViewAnimationUtils.createCircularReveal.
    107         view.setOutlineProvider(new ViewOutlineProvider() {
    108             @Override
    109             public void getOutline(View view, Outline outline) {
    110                 // Using (0, 0, 0, 0) will not work since the outline will simply be treated as
    111                 // an empty outline.
    112                 outline.setOval(-1, -1, 0, 0);
    113             }
    114         });
    115         view.setClipToOutline(true);
    116 
    117         if (palette != null) {
    118             view.findViewById(R.id.outgoing_call_animation_circle).setBackgroundColor(
    119                     palette.mPrimaryColor);
    120             activity.getWindow().setStatusBarColor(palette.mSecondaryColor);
    121         }
    122 
    123         view.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
    124             @Override
    125             public boolean onPreDraw() {
    126                 final ViewTreeObserver vto = view.getViewTreeObserver();
    127                 if (vto.isAlive()) {
    128                     vto.removeOnPreDrawListener(this);
    129                 }
    130                 final Animator animator = getRevealAnimator(mTouchPoint);
    131                 if (animator != null) {
    132                     animator.addListener(new AnimatorListenerAdapter() {
    133                         @Override
    134                         public void onAnimationEnd(Animator animation) {
    135                             view.setClipToOutline(false);
    136                             if (mListener != null) {
    137                                 mListener.onCircularRevealComplete(getFragmentManager());
    138                             }
    139                         }
    140                     });
    141                     animator.start();
    142                 }
    143                 return false;
    144             }
    145         });
    146     }
    147 
    148     private Animator getRevealAnimator(Point touchPoint) {
    149         final Activity activity = getActivity();
    150         if (activity == null) {
    151             return null;
    152         }
    153         final View view  = activity.getWindow().getDecorView();
    154         final Display display = activity.getWindowManager().getDefaultDisplay();
    155         final Point size = new Point();
    156         display.getSize(size);
    157 
    158         int startX = size.x / 2;
    159         int startY = size.y / 2;
    160         if (touchPoint != null) {
    161             startX = touchPoint.x;
    162             startY = touchPoint.y;
    163         }
    164 
    165         final Animator valueAnimator = ViewAnimationUtils.createCircularReveal(view,
    166                 startX, startY, 0, Math.max(size.x, size.y));
    167         valueAnimator.setDuration(getResources().getInteger(R.integer.reveal_animation_duration));
    168         return valueAnimator;
    169     }
    170 }
    171