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.content.BroadcastReceiver; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.IntentFilter; 26 import android.graphics.Outline; 27 import android.graphics.Point; 28 import android.os.Bundle; 29 import android.support.v4.content.LocalBroadcastManager; 30 import android.view.Display; 31 import android.view.View; 32 import android.view.ViewAnimationUtils; 33 import android.view.ViewOutlineProvider; 34 import android.view.ViewTreeObserver; 35 import android.view.ViewTreeObserver.OnPreDrawListener; 36 37 import com.android.contacts.common.interactions.TouchPointManager; 38 import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette; 39 40 /** 41 * Lightweight activity used to display a circular reveal while InCallActivity is starting up. 42 * A BroadcastReceiver is used to listen to broadcasts from a LocalBroadcastManager to finish 43 * the activity at suitable times. 44 */ 45 public class CircularRevealActivity extends Activity { 46 private static final int REVEAL_DURATION = 333; 47 public static final String EXTRA_THEME_COLORS = "extra_theme_colors"; 48 public static final String ACTION_CLEAR_DISPLAY = "action_clear_display"; 49 50 final BroadcastReceiver mClearDisplayReceiver = new BroadcastReceiver( ) { 51 @Override 52 public void onReceive(Context context, Intent intent) { 53 clearDisplay(); 54 } 55 }; 56 57 @Override 58 protected void onCreate(Bundle savedInstanceState) { 59 super.onCreate(savedInstanceState); 60 overridePendingTransition(0, 0); 61 setContentView(R.layout.outgoing_call_animation); 62 final Point touchPoint = getIntent().getParcelableExtra(TouchPointManager.TOUCH_POINT); 63 final MaterialPalette palette = getIntent().getParcelableExtra(EXTRA_THEME_COLORS); 64 setupDecorView(touchPoint, palette); 65 } 66 67 @Override 68 protected void onStart() { 69 super.onStart(); 70 if (!InCallPresenter.getInstance().isServiceBound()) { 71 clearDisplay(); 72 } 73 final IntentFilter filter = new IntentFilter(); 74 filter.addAction(ACTION_CLEAR_DISPLAY); 75 LocalBroadcastManager.getInstance(this).registerReceiver(mClearDisplayReceiver, filter); 76 } 77 78 @Override 79 protected void onStop() { 80 LocalBroadcastManager.getInstance(this).unregisterReceiver(mClearDisplayReceiver); 81 super.onStop(); 82 } 83 84 private void setupDecorView(final Point touchPoint, MaterialPalette palette) { 85 final View view = getWindow().getDecorView(); 86 87 // The circle starts from an initial size of 0 so clip it such that it is invisible. When 88 // the animation later starts, this clip will be clobbered by the circular reveal clip. 89 // See ViewAnimationUtils.createCircularReveal. 90 view.setOutlineProvider(new ViewOutlineProvider() { 91 @Override 92 public void getOutline(View view, Outline outline) { 93 // Using (0, 0, 0, 0) will not work since the outline will simply be treated as 94 // an empty outline. 95 outline.setOval(-1, -1, 0, 0); 96 } 97 }); 98 view.setClipToOutline(true); 99 100 if (palette != null) { 101 view.findViewById(R.id.outgoing_call_animation_circle).setBackgroundColor( 102 palette.mPrimaryColor); 103 getWindow().setStatusBarColor(palette.mSecondaryColor); 104 } 105 106 view.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() { 107 @Override 108 public boolean onPreDraw() { 109 final ViewTreeObserver vto = view.getViewTreeObserver(); 110 if (vto.isAlive()) { 111 vto.removeOnPreDrawListener(this); 112 } 113 final Animator animator = getRevealAnimator(touchPoint); 114 // Since this animator is a RenderNodeAnimator (native animator), add an arbitary 115 // start delay to force the onAnimationStart callback to happen later on the UI 116 // thread. Otherwise it would happen right away inside animator.start() 117 animator.setStartDelay(5); 118 animator.addListener(new AnimatorListenerAdapter() { 119 @Override 120 public void onAnimationStart(Animator animation) { 121 InCallPresenter.getInstance().onCircularRevealStarted( 122 CircularRevealActivity.this); 123 } 124 125 @Override 126 public void onAnimationEnd(Animator animation) { 127 view.setClipToOutline(false); 128 super.onAnimationEnd(animation); 129 } 130 }); 131 animator.start(); 132 return false; 133 } 134 }); 135 } 136 137 private void clearDisplay() { 138 getWindow().getDecorView().setVisibility(View.INVISIBLE); 139 finish(); 140 } 141 142 @Override 143 public void onBackPressed() { 144 return; 145 } 146 147 public static void sendClearDisplayBroadcast(Context context) { 148 LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent(ACTION_CLEAR_DISPLAY)); 149 } 150 151 private Animator getRevealAnimator(Point touchPoint) { 152 final View view = getWindow().getDecorView(); 153 final Display display = getWindowManager().getDefaultDisplay(); 154 final Point size = new Point(); 155 display.getSize(size); 156 157 int startX = size.x / 2; 158 int startY = size.y / 2; 159 if (touchPoint != null) { 160 startX = touchPoint.x; 161 startY = touchPoint.y; 162 } 163 164 final Animator valueAnimator = ViewAnimationUtils.createCircularReveal(view, 165 startX, startY, 0, Math.max(size.x, size.y)); 166 valueAnimator.setDuration(REVEAL_DURATION); 167 return valueAnimator; 168 } 169 } 170