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