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