1 /* 2 * Copyright (C) 2015 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.systemui.assist; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.AnimatorSet; 22 import android.animation.ValueAnimator; 23 import android.content.Context; 24 import android.graphics.Canvas; 25 import android.graphics.Color; 26 import android.graphics.Paint; 27 import android.graphics.PixelFormat; 28 import android.graphics.PorterDuff; 29 import android.graphics.PorterDuffXfermode; 30 import android.os.Handler; 31 import android.view.View; 32 import android.view.WindowManager; 33 import android.view.accessibility.AccessibilityEvent; 34 import android.view.animation.AnimationUtils; 35 36 import com.android.systemui.Interpolators; 37 import com.android.systemui.R; 38 39 /** 40 * Visually discloses that contextual data was provided to an assistant. 41 */ 42 public class AssistDisclosure { 43 private final Context mContext; 44 private final WindowManager mWm; 45 private final Handler mHandler; 46 47 private AssistDisclosureView mView; 48 private boolean mViewAdded; 49 AssistDisclosure(Context context, Handler handler)50 public AssistDisclosure(Context context, Handler handler) { 51 mContext = context; 52 mHandler = handler; 53 mWm = mContext.getSystemService(WindowManager.class); 54 } 55 postShow()56 public void postShow() { 57 mHandler.removeCallbacks(mShowRunnable); 58 mHandler.post(mShowRunnable); 59 } 60 show()61 private void show() { 62 if (mView == null) { 63 mView = new AssistDisclosureView(mContext); 64 } 65 if (!mViewAdded) { 66 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 67 WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY, 68 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 69 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED 70 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 71 | WindowManager.LayoutParams.FLAG_FULLSCREEN 72 | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED, 73 PixelFormat.TRANSLUCENT); 74 lp.setTitle("AssistDisclosure"); 75 76 mWm.addView(mView, lp); 77 mViewAdded = true; 78 } 79 } 80 hide()81 private void hide() { 82 if (mViewAdded) { 83 mWm.removeView(mView); 84 mViewAdded = false; 85 } 86 } 87 88 private Runnable mShowRunnable = new Runnable() { 89 @Override 90 public void run() { 91 show(); 92 } 93 }; 94 95 private class AssistDisclosureView extends View 96 implements ValueAnimator.AnimatorUpdateListener { 97 98 public static final int TRACING_ANIMATION_DURATION = 600; 99 public static final int ALPHA_IN_ANIMATION_DURATION = 450; 100 public static final int ALPHA_OUT_ANIMATION_DURATION = 400; 101 102 private float mThickness; 103 private float mShadowThickness; 104 private final Paint mPaint = new Paint(); 105 private final Paint mShadowPaint = new Paint(); 106 107 private final ValueAnimator mTracingAnimator; 108 private final ValueAnimator mAlphaOutAnimator; 109 private final ValueAnimator mAlphaInAnimator; 110 private final AnimatorSet mAnimator; 111 112 private float mTracingProgress = 0; 113 private int mAlpha = 0; 114 AssistDisclosureView(Context context)115 public AssistDisclosureView(Context context) { 116 super(context); 117 118 mTracingAnimator = ValueAnimator.ofFloat(0, 1).setDuration(TRACING_ANIMATION_DURATION); 119 mTracingAnimator.addUpdateListener(this); 120 mTracingAnimator.setInterpolator(AnimationUtils.loadInterpolator(mContext, 121 R.interpolator.assist_disclosure_trace)); 122 mAlphaInAnimator = ValueAnimator.ofInt(0, 255).setDuration(ALPHA_IN_ANIMATION_DURATION); 123 mAlphaInAnimator.addUpdateListener(this); 124 mAlphaInAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); 125 mAlphaOutAnimator = ValueAnimator.ofInt(255, 0).setDuration( 126 ALPHA_OUT_ANIMATION_DURATION); 127 mAlphaOutAnimator.addUpdateListener(this); 128 mAlphaOutAnimator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN); 129 mAnimator = new AnimatorSet(); 130 mAnimator.play(mAlphaInAnimator).with(mTracingAnimator); 131 mAnimator.play(mAlphaInAnimator).before(mAlphaOutAnimator); 132 mAnimator.addListener(new AnimatorListenerAdapter() { 133 boolean mCancelled; 134 135 @Override 136 public void onAnimationStart(Animator animation) { 137 mCancelled = false; 138 } 139 140 @Override 141 public void onAnimationCancel(Animator animation) { 142 mCancelled = true; 143 } 144 145 @Override 146 public void onAnimationEnd(Animator animation) { 147 if (!mCancelled) { 148 hide(); 149 } 150 } 151 }); 152 153 PorterDuffXfermode srcMode = new PorterDuffXfermode(PorterDuff.Mode.SRC); 154 mPaint.setColor(Color.WHITE); 155 mPaint.setXfermode(srcMode); 156 mShadowPaint.setColor(Color.DKGRAY); 157 mShadowPaint.setXfermode(srcMode); 158 159 mThickness = getResources().getDimension(R.dimen.assist_disclosure_thickness); 160 mShadowThickness = getResources().getDimension( 161 R.dimen.assist_disclosure_shadow_thickness); 162 } 163 164 @Override onAttachedToWindow()165 protected void onAttachedToWindow() { 166 super.onAttachedToWindow(); 167 168 startAnimation(); 169 sendAccessibilityEvent(AccessibilityEvent.TYPE_ASSIST_READING_CONTEXT); 170 } 171 172 @Override onDetachedFromWindow()173 protected void onDetachedFromWindow() { 174 super.onDetachedFromWindow(); 175 176 mAnimator.cancel(); 177 178 mTracingProgress = 0; 179 mAlpha = 0; 180 } 181 startAnimation()182 private void startAnimation() { 183 mAnimator.cancel(); 184 mAnimator.start(); 185 } 186 187 @Override onDraw(Canvas canvas)188 protected void onDraw(Canvas canvas) { 189 mPaint.setAlpha(mAlpha); 190 mShadowPaint.setAlpha(mAlpha / 4); 191 192 drawGeometry(canvas, mShadowPaint, mShadowThickness); 193 drawGeometry(canvas, mPaint, 0); 194 } 195 drawGeometry(Canvas canvas, Paint paint, float padding)196 private void drawGeometry(Canvas canvas, Paint paint, float padding) { 197 final int width = getWidth(); 198 final int height = getHeight(); 199 float thickness = mThickness; 200 final float pixelProgress = mTracingProgress * (width + height - 2 * thickness); 201 202 float bottomProgress = Math.min(pixelProgress, width / 2f); 203 if (bottomProgress > 0) { 204 drawBeam(canvas, 205 width / 2f - bottomProgress, 206 height - thickness, 207 width / 2f + bottomProgress, 208 height, paint, padding); 209 } 210 211 float sideProgress = Math.min(pixelProgress - bottomProgress, height - thickness); 212 if (sideProgress > 0) { 213 drawBeam(canvas, 214 0, 215 (height - thickness) - sideProgress, 216 thickness, 217 height - thickness, paint, padding); 218 drawBeam(canvas, 219 width - thickness, 220 (height - thickness) - sideProgress, 221 width, 222 height - thickness, paint, padding); 223 } 224 225 float topProgress = Math.min(pixelProgress - bottomProgress - sideProgress, 226 width / 2 - thickness); 227 if (sideProgress > 0 && topProgress > 0) { 228 drawBeam(canvas, 229 thickness, 230 0, 231 thickness + topProgress, 232 thickness, paint, padding); 233 drawBeam(canvas, 234 (width - thickness) - topProgress, 235 0, 236 width - thickness, 237 thickness, paint, padding); 238 } 239 } 240 drawBeam(Canvas canvas, float left, float top, float right, float bottom, Paint paint, float padding)241 private void drawBeam(Canvas canvas, float left, float top, float right, float bottom, 242 Paint paint, float padding) { 243 canvas.drawRect(left - padding, 244 top - padding, 245 right + padding, 246 bottom + padding, 247 paint); 248 } 249 250 @Override onAnimationUpdate(ValueAnimator animation)251 public void onAnimationUpdate(ValueAnimator animation) { 252 if (animation == mAlphaOutAnimator) { 253 mAlpha = (int) mAlphaOutAnimator.getAnimatedValue(); 254 } else if (animation == mAlphaInAnimator) { 255 mAlpha = (int) mAlphaInAnimator.getAnimatedValue(); 256 } else if (animation == mTracingAnimator) { 257 mTracingProgress = (float) mTracingAnimator.getAnimatedValue(); 258 } 259 invalidate(); 260 } 261 } 262 } 263