Home | History | Annotate | Download | only in assist
      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 
     49     public AssistDisclosure(Context context, Handler handler) {
     50         mContext = context;
     51         mHandler = handler;
     52         mWm = mContext.getSystemService(WindowManager.class);
     53     }
     54 
     55     public void postShow() {
     56         mHandler.removeCallbacks(mShowRunnable);
     57         mHandler.post(mShowRunnable);
     58     }
     59 
     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 
     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 
    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
    158         protected void onAttachedToWindow() {
    159             super.onAttachedToWindow();
    160 
    161             startAnimation();
    162             sendAccessibilityEvent(AccessibilityEvent.TYPE_ASSIST_READING_CONTEXT);
    163         }
    164 
    165         @Override
    166         protected void onDetachedFromWindow() {
    167             super.onDetachedFromWindow();
    168 
    169             mAnimator.cancel();
    170             mAlpha = 0;
    171         }
    172 
    173         private void startAnimation() {
    174             mAnimator.cancel();
    175             mAnimator.start();
    176         }
    177 
    178         @Override
    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 
    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 
    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
    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