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