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_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