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