/* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.gestures; import android.animation.TimeAnimator; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.Looper; import android.os.Message; import com.android.internal.annotations.VisibleForTesting; import com.android.settings.R; /** A drawable to animate the inset back gesture in both edges of the screen */ public class BackGestureIndicatorDrawable extends Drawable { private static final String TAG = "BackGestureIndicatorDrawable"; private static final int MSG_SET_INDICATOR_WIDTH = 1; private static final int MSG_HIDE_INDICATOR = 3; private static final long ANIMATION_DURATION_MS = 200L; private static final long HIDE_DELAY_MS = 700L; private static final int ALPHA_MAX = 64; private Context mContext; private Paint mPaint = new Paint(); private boolean mReversed; private float mFinalWidth; private float mCurrentWidth; private float mWidthChangePerMs; private TimeAnimator mTimeAnimator = new TimeAnimator(); private final Handler mHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { switch(msg.what) { case MSG_SET_INDICATOR_WIDTH: mTimeAnimator.end(); mFinalWidth = msg.arg1; mWidthChangePerMs = Math.abs(mCurrentWidth - mFinalWidth) / ANIMATION_DURATION_MS; mTimeAnimator.start(); break; case MSG_HIDE_INDICATOR: mCurrentWidth = mFinalWidth; removeMessages(MSG_SET_INDICATOR_WIDTH); sendMessageDelayed(obtainMessage(MSG_SET_INDICATOR_WIDTH, 0, 0), HIDE_DELAY_MS); invalidateSelf(); break; default: break; } } }; /** * Creates an indicator drawable that responds to back gesture inset size change * @param reversed If false, indicator will expand right. If true, indicator will expand left */ public BackGestureIndicatorDrawable(Context context, boolean reversed) { mContext = context; mReversed = reversed; // Restart the timer whenever a change is detected, so we can shrink/fade the indicators mTimeAnimator.setTimeListener((TimeAnimator animation, long totalTime, long deltaTime) -> { updateCurrentWidth(totalTime, deltaTime); invalidateSelf(); }); } private void updateCurrentWidth(long totalTime, long deltaTime) { synchronized (mTimeAnimator) { float step = deltaTime * mWidthChangePerMs; if (totalTime >= ANIMATION_DURATION_MS || step >= Math.abs(mFinalWidth - mCurrentWidth)) { mCurrentWidth = mFinalWidth; mTimeAnimator.end(); } else { float direction = mCurrentWidth < mFinalWidth ? 1 : -1; mCurrentWidth += direction * step; } } } @Override public void draw(@NonNull Canvas canvas) { mPaint.setAntiAlias(true); mPaint.setColor(mContext.getResources().getColor(R.color.back_gesture_indicator)); mPaint.setAlpha(ALPHA_MAX); final int top = 0; final int bottom = canvas.getHeight(); final int width = (int) mCurrentWidth; Rect rect = new Rect(0, top, width, bottom); if (mReversed) { rect.offset(canvas.getWidth() - width, 0); } canvas.drawRect(rect, mPaint); } @Override public void setAlpha(@IntRange(from = 0, to = 255) int alpha) { } @Override public void setColorFilter(@Nullable ColorFilter colorFilter) { } @Override public int getOpacity() { return 0; } /** * Sets the visible width of the indicator in pixels. */ public void setWidth(int width) { if (width == 0) { mHandler.sendEmptyMessage(MSG_HIDE_INDICATOR); } else { mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_INDICATOR_WIDTH, width, 0)); } } @VisibleForTesting public int getWidth() { return (int) mFinalWidth; } }