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