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