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.Interpolators; 36 import com.android.systemui.R; 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 74 mWm.addView(mView, lp); 75 mViewAdded = true; 76 } 77 } 78 hide()79 private void hide() { 80 if (mViewAdded) { 81 mWm.removeView(mView); 82 mViewAdded = false; 83 } 84 } 85 86 private Runnable mShowRunnable = new Runnable() { 87 @Override 88 public void run() { 89 show(); 90 } 91 }; 92 93 private class AssistDisclosureView extends View 94 implements ValueAnimator.AnimatorUpdateListener { 95 96 static final int FULL_ALPHA = 222; // 87% 97 static final int ALPHA_IN_ANIMATION_DURATION = 400; 98 static final int ALPHA_OUT_ANIMATION_DURATION = 300; 99 100 101 private float mThickness; 102 private float mShadowThickness; 103 private final Paint mPaint = new Paint(); 104 private final Paint mShadowPaint = new Paint(); 105 106 private final ValueAnimator mAlphaOutAnimator; 107 private final ValueAnimator mAlphaInAnimator; 108 private final AnimatorSet mAnimator; 109 110 private int mAlpha = 0; 111 AssistDisclosureView(Context context)112 public AssistDisclosureView(Context context) { 113 super(context); 114 115 mAlphaInAnimator = ValueAnimator.ofInt(0, FULL_ALPHA) 116 .setDuration(ALPHA_IN_ANIMATION_DURATION); 117 mAlphaInAnimator.addUpdateListener(this); 118 mAlphaInAnimator.setInterpolator(Interpolators.CUSTOM_40_40); 119 mAlphaOutAnimator = ValueAnimator.ofInt(FULL_ALPHA, 0).setDuration( 120 ALPHA_OUT_ANIMATION_DURATION); 121 mAlphaOutAnimator.addUpdateListener(this); 122 mAlphaOutAnimator.setInterpolator(Interpolators.CUSTOM_40_40); 123 mAnimator = new AnimatorSet(); 124 mAnimator.play(mAlphaInAnimator).before(mAlphaOutAnimator); 125 mAnimator.addListener(new AnimatorListenerAdapter() { 126 boolean mCancelled; 127 128 @Override 129 public void onAnimationStart(Animator animation) { 130 mCancelled = false; 131 } 132 133 @Override 134 public void onAnimationCancel(Animator animation) { 135 mCancelled = true; 136 } 137 138 @Override 139 public void onAnimationEnd(Animator animation) { 140 if (!mCancelled) { 141 hide(); 142 } 143 } 144 }); 145 146 PorterDuffXfermode srcMode = new PorterDuffXfermode(PorterDuff.Mode.SRC); 147 mPaint.setColor(Color.WHITE); 148 mPaint.setXfermode(srcMode); 149 mShadowPaint.setColor(Color.DKGRAY); 150 mShadowPaint.setXfermode(srcMode); 151 152 mThickness = getResources().getDimension(R.dimen.assist_disclosure_thickness); 153 mShadowThickness = getResources().getDimension( 154 R.dimen.assist_disclosure_shadow_thickness); 155 } 156 157 @Override onAttachedToWindow()158 protected void onAttachedToWindow() { 159 super.onAttachedToWindow(); 160 161 startAnimation(); 162 sendAccessibilityEvent(AccessibilityEvent.TYPE_ASSIST_READING_CONTEXT); 163 } 164 165 @Override onDetachedFromWindow()166 protected void onDetachedFromWindow() { 167 super.onDetachedFromWindow(); 168 169 mAnimator.cancel(); 170 mAlpha = 0; 171 } 172 startAnimation()173 private void startAnimation() { 174 mAnimator.cancel(); 175 mAnimator.start(); 176 } 177 178 @Override onDraw(Canvas canvas)179 protected void onDraw(Canvas canvas) { 180 mPaint.setAlpha(mAlpha); 181 mShadowPaint.setAlpha(mAlpha / 4); 182 183 drawGeometry(canvas, mShadowPaint, mShadowThickness); 184 drawGeometry(canvas, mPaint, 0); 185 } 186 drawGeometry(Canvas canvas, Paint paint, float padding)187 private void drawGeometry(Canvas canvas, Paint paint, float padding) { 188 final int width = getWidth(); 189 final int height = getHeight(); 190 float thickness = mThickness; 191 192 // bottom 193 drawBeam(canvas, 194 0, 195 height - thickness, 196 width, 197 height, paint, padding); 198 199 // sides 200 drawBeam(canvas, 201 0, 202 0, 203 thickness, 204 height - thickness, paint, padding); 205 drawBeam(canvas, 206 width - thickness, 207 0, 208 width, 209 height - thickness, paint, padding); 210 211 // top 212 drawBeam(canvas, 213 thickness, 214 0, 215 width - thickness, 216 thickness, paint, padding); 217 } 218 drawBeam(Canvas canvas, float left, float top, float right, float bottom, Paint paint, float padding)219 private void drawBeam(Canvas canvas, float left, float top, float right, float bottom, 220 Paint paint, float padding) { 221 canvas.drawRect(left - padding, 222 top - padding, 223 right + padding, 224 bottom + padding, 225 paint); 226 } 227 228 @Override onAnimationUpdate(ValueAnimator animation)229 public void onAnimationUpdate(ValueAnimator animation) { 230 if (animation == mAlphaOutAnimator) { 231 mAlpha = (int) mAlphaOutAnimator.getAnimatedValue(); 232 } else if (animation == mAlphaInAnimator) { 233 mAlpha = (int) mAlphaInAnimator.getAnimatedValue(); 234 } 235 invalidate(); 236 } 237 } 238 } 239