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
17package com.android.systemui.assist;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.AnimatorSet;
22import android.animation.ValueAnimator;
23import android.content.Context;
24import android.graphics.Canvas;
25import android.graphics.Color;
26import android.graphics.Paint;
27import android.graphics.PixelFormat;
28import android.graphics.PorterDuff;
29import android.graphics.PorterDuffXfermode;
30import android.os.Handler;
31import android.view.View;
32import android.view.WindowManager;
33import android.view.accessibility.AccessibilityEvent;
34import android.view.animation.AnimationUtils;
35
36import com.android.systemui.Interpolators;
37import com.android.systemui.R;
38
39/**
40 * Visually discloses that contextual data was provided to an assistant.
41 */
42public 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
50    public AssistDisclosure(Context context, Handler handler) {
51        mContext = context;
52        mHandler = handler;
53        mWm = mContext.getSystemService(WindowManager.class);
54    }
55
56    public void postShow() {
57        mHandler.removeCallbacks(mShowRunnable);
58        mHandler.post(mShowRunnable);
59    }
60
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
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
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
160        protected void onAttachedToWindow() {
161            super.onAttachedToWindow();
162
163            startAnimation();
164            sendAccessibilityEvent(AccessibilityEvent.TYPE_ASSIST_READING_CONTEXT);
165        }
166
167        @Override
168        protected void onDetachedFromWindow() {
169            super.onDetachedFromWindow();
170
171            mAnimator.cancel();
172            mAlpha = 0;
173        }
174
175        private void startAnimation() {
176            mAnimator.cancel();
177            mAnimator.start();
178        }
179
180        @Override
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
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
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
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