diff --git a/res/layout/back_gesture_indicator_container.xml b/res/layout/back_gesture_indicator_container.xml new file mode 100644 index 00000000000..17b91265665 --- /dev/null +++ b/res/layout/back_gesture_indicator_container.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + diff --git a/res/values/colors.xml b/res/values/colors.xml index a1cb8fcadff..3fd77e8837d 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -146,4 +146,6 @@ @*android:color/background_device_default_light #ffdadce0 + + #4182ef \ No newline at end of file diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 41c059c3346..2e71893af5d 100755 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -403,6 +403,8 @@ 320dp + 60dp + 16sp diff --git a/src/com/android/settings/gestures/BackGestureIndicatorDrawable.java b/src/com/android/settings/gestures/BackGestureIndicatorDrawable.java new file mode 100644 index 00000000000..2d09e6b029f --- /dev/null +++ b/src/com/android/settings/gestures/BackGestureIndicatorDrawable.java @@ -0,0 +1,161 @@ +/* + * 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; + } +} diff --git a/src/com/android/settings/gestures/BackGestureIndicatorView.java b/src/com/android/settings/gestures/BackGestureIndicatorView.java new file mode 100644 index 00000000000..2bb84358b56 --- /dev/null +++ b/src/com/android/settings/gestures/BackGestureIndicatorView.java @@ -0,0 +1,100 @@ +/* + * 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.content.Context; +import android.content.res.TypedArray; +import android.graphics.PixelFormat; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.ImageView; +import android.widget.LinearLayout; + +import com.android.settings.R; + +/** + * A linear layout containing the left and right location indicators. + */ +public class BackGestureIndicatorView extends LinearLayout { + private ViewGroup mLayout; + private ImageView mLeftIndicator; + private ImageView mRightIndicator; + private BackGestureIndicatorDrawable mLeftDrawable; + private BackGestureIndicatorDrawable mRightDrawable; + + public BackGestureIndicatorView(Context context) { + super(context); + + LayoutInflater factory = LayoutInflater.from(context); + mLayout = (ViewGroup) factory.inflate(R.layout.back_gesture_indicator_container, + this, false); + + if (mLayout == null) { + return; + } + + addView(mLayout); + + mLeftDrawable = new BackGestureIndicatorDrawable(context, false); + mRightDrawable = new BackGestureIndicatorDrawable(context, true); + + mLeftIndicator = mLayout.findViewById(R.id.indicator_left); + mRightIndicator = mLayout.findViewById(R.id.indicator_right); + + mLeftIndicator.setImageDrawable(mLeftDrawable); + mRightIndicator.setImageDrawable(mRightDrawable); + + TypedArray a = context.obtainStyledAttributes(new int[] { + android.R.attr.windowLightNavigationBar, + android.R.attr.windowLightStatusBar}); + if (a.getBoolean(0, false)) { + setSystemUiVisibility( + getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR); + } + if (a.getBoolean(1, false)) { + setSystemUiVisibility( + getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); + } + a.recycle(); + } + + public void setIndicatorWidth(int width, boolean leftIndicator) { + BackGestureIndicatorDrawable indicator = leftIndicator ? mLeftDrawable : mRightDrawable; + indicator.setWidth(width); + } + + public WindowManager.LayoutParams getLayoutParams( + WindowManager.LayoutParams parentWindowAttributes) { + int copiedFlags = (parentWindowAttributes.flags + & WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.TYPE_APPLICATION, + WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED + | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | copiedFlags, + PixelFormat.TRANSLUCENT); + + lp.setTitle("BackGestureIndicatorView"); + lp.token = getContext().getActivityToken(); + return lp; + } +} diff --git a/tests/robotests/src/com/android/settings/gestures/BackGestureIndicatorViewTest.java b/tests/robotests/src/com/android/settings/gestures/BackGestureIndicatorViewTest.java new file mode 100644 index 00000000000..8d43aaa277d --- /dev/null +++ b/tests/robotests/src/com/android/settings/gestures/BackGestureIndicatorViewTest.java @@ -0,0 +1,64 @@ +/* + * 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 static org.junit.Assert.assertEquals; + +import android.content.Context; +import android.widget.ImageView; + +import com.android.settings.R; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class BackGestureIndicatorViewTest { + + private Context mContext; + + private BackGestureIndicatorDrawable mLeftDrawable; + private BackGestureIndicatorDrawable mRightDrawable; + + private BackGestureIndicatorView mView; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mContext = RuntimeEnvironment.application; + mView = new BackGestureIndicatorView(mContext); + + mLeftDrawable = (BackGestureIndicatorDrawable) ((ImageView) mView.findViewById( + R.id.indicator_left)).getDrawable(); + mRightDrawable = (BackGestureIndicatorDrawable) ((ImageView) mView.findViewById( + R.id.indicator_right)).getDrawable(); + } + + @Test + public void testSetIndicatoreWidth() { + mView.setIndicatorWidth(25, true); + mView.setIndicatorWidth(52, false); + + assertEquals(25, mLeftDrawable.getWidth()); + assertEquals(52, mRightDrawable.getWidth()); + } +}