1 /* 2 * Copyright (C) 2019 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.settings.gestures; 18 19 import android.animation.TimeAnimator; 20 import android.annotation.IntRange; 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.content.Context; 24 import android.graphics.Canvas; 25 import android.graphics.ColorFilter; 26 import android.graphics.Paint; 27 import android.graphics.Rect; 28 import android.graphics.drawable.Drawable; 29 import android.os.Handler; 30 import android.os.Looper; 31 import android.os.Message; 32 33 import com.android.internal.annotations.VisibleForTesting; 34 import com.android.settings.R; 35 36 /** A drawable to animate the inset back gesture in both edges of the screen */ 37 public class BackGestureIndicatorDrawable extends Drawable { 38 39 private static final String TAG = "BackGestureIndicatorDrawable"; 40 41 private static final int MSG_SET_INDICATOR_WIDTH = 1; 42 private static final int MSG_HIDE_INDICATOR = 3; 43 44 private static final long ANIMATION_DURATION_MS = 200L; 45 private static final long HIDE_DELAY_MS = 700L; 46 47 private static final int ALPHA_MAX = 64; 48 49 private Context mContext; 50 51 private Paint mPaint = new Paint(); 52 private boolean mReversed; 53 54 private float mFinalWidth; 55 private float mCurrentWidth; 56 private float mWidthChangePerMs; 57 58 private TimeAnimator mTimeAnimator = new TimeAnimator(); 59 60 private final Handler mHandler = new Handler(Looper.getMainLooper()) { 61 @Override 62 public void handleMessage(Message msg) { 63 switch(msg.what) { 64 case MSG_SET_INDICATOR_WIDTH: 65 mTimeAnimator.end(); 66 mFinalWidth = msg.arg1; 67 mWidthChangePerMs = Math.abs(mCurrentWidth - mFinalWidth) 68 / ANIMATION_DURATION_MS; 69 mTimeAnimator.start(); 70 break; 71 case MSG_HIDE_INDICATOR: 72 mCurrentWidth = mFinalWidth; 73 removeMessages(MSG_SET_INDICATOR_WIDTH); 74 sendMessageDelayed(obtainMessage(MSG_SET_INDICATOR_WIDTH, 0, 0), HIDE_DELAY_MS); 75 invalidateSelf(); 76 break; 77 default: 78 break; 79 } 80 } 81 }; 82 83 /** 84 * Creates an indicator drawable that responds to back gesture inset size change 85 * @param reversed If false, indicator will expand right. If true, indicator will expand left 86 */ BackGestureIndicatorDrawable(Context context, boolean reversed)87 public BackGestureIndicatorDrawable(Context context, boolean reversed) { 88 mContext = context; 89 mReversed = reversed; 90 91 // Restart the timer whenever a change is detected, so we can shrink/fade the indicators 92 mTimeAnimator.setTimeListener((TimeAnimator animation, long totalTime, long deltaTime) -> { 93 updateCurrentWidth(totalTime, deltaTime); 94 invalidateSelf(); 95 }); 96 } 97 updateCurrentWidth(long totalTime, long deltaTime)98 private void updateCurrentWidth(long totalTime, long deltaTime) { 99 synchronized (mTimeAnimator) { 100 float step = deltaTime * mWidthChangePerMs; 101 if (totalTime >= ANIMATION_DURATION_MS 102 || step >= Math.abs(mFinalWidth - mCurrentWidth)) { 103 mCurrentWidth = mFinalWidth; 104 mTimeAnimator.end(); 105 } else { 106 float direction = mCurrentWidth < mFinalWidth ? 1 : -1; 107 mCurrentWidth += direction * step; 108 } 109 } 110 } 111 112 @Override 113 public void draw(@NonNull Canvas canvas) { 114 115 mPaint.setAntiAlias(true); 116 mPaint.setColor(mContext.getResources().getColor(R.color.back_gesture_indicator)); 117 mPaint.setAlpha(ALPHA_MAX); 118 119 final int top = 0; 120 final int bottom = canvas.getHeight(); 121 final int width = (int) mCurrentWidth; 122 123 Rect rect = new Rect(0, top, width, bottom); 124 if (mReversed) { 125 rect.offset(canvas.getWidth() - width, 0); 126 } 127 128 canvas.drawRect(rect, mPaint); 129 } 130 131 @Override 132 public void setAlpha(@IntRange(from = 0, to = 255) int alpha) { 133 134 } 135 136 @Override 137 public void setColorFilter(@Nullable ColorFilter colorFilter) { 138 139 } 140 141 @Override 142 public int getOpacity() { 143 return 0; 144 } 145 146 /** 147 * Sets the visible width of the indicator in pixels. 148 */ 149 public void setWidth(int width) { 150 if (width == 0) { 151 mHandler.sendEmptyMessage(MSG_HIDE_INDICATOR); 152 } else { 153 mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_INDICATOR_WIDTH, width, 0)); 154 } 155 } 156 157 @VisibleForTesting 158 public int getWidth() { 159 return (int) mFinalWidth; 160 } 161 } 162