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 {
onCircularRevealComplete(FragmentManager fm)47         public void onCircularRevealComplete(FragmentManager fm);
48     }
49 
startCircularReveal(FragmentManager fm, Point touchPoint, OnCircularRevealCompleteListener listener)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 
endCircularReveal(FragmentManager fm)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      */
CircularRevealFragment()71     public CircularRevealFragment() {}
72 
CircularRevealFragment(Point touchPoint, OnCircularRevealCompleteListener listener)73     public CircularRevealFragment(Point touchPoint, OnCircularRevealCompleteListener listener) {
74         mTouchPoint = touchPoint;
75         mListener = listener;
76     }
77 
78     @Override
onResume()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
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)89     public View onCreateView(LayoutInflater inflater, ViewGroup container,
90             Bundle savedInstanceState) {
91         return inflater.inflate(R.layout.outgoing_call_animation, container, false);
92     }
93 
startOutgoingAnimation(MaterialPalette palette)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 
getRevealAnimator(Point touchPoint)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