From 3f2279789971fea1b95d188764ee3eec1b2895fd Mon Sep 17 00:00:00 2001 From: Mehdi Alizadeh Date: Fri, 13 Dec 2019 15:24:53 -0800 Subject: [PATCH 1/2] Adds BackGestureIndicatorView to animate left and right insets Bug: 146004827 Test: make RunSettingsRoboTests ROBOTEST_FILTER=BackGestureIndicatorViewTest Change-Id: Id8c01bf047138a1721237f368f269c6def964515 --- .../back_gesture_indicator_container.xml | 41 +++++ res/values/colors.xml | 2 + res/values/dimens.xml | 2 + .../BackGestureIndicatorDrawable.java | 161 ++++++++++++++++++ .../gestures/BackGestureIndicatorView.java | 100 +++++++++++ .../BackGestureIndicatorViewTest.java | 64 +++++++ 6 files changed, 370 insertions(+) create mode 100644 res/layout/back_gesture_indicator_container.xml create mode 100644 src/com/android/settings/gestures/BackGestureIndicatorDrawable.java create mode 100644 src/com/android/settings/gestures/BackGestureIndicatorView.java create mode 100644 tests/robotests/src/com/android/settings/gestures/BackGestureIndicatorViewTest.java 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()); + } +} From 035559615b08cfdc2911a4255aa6e99ee1b863ae Mon Sep 17 00:00:00 2001 From: Mehdi Alizadeh Date: Fri, 13 Dec 2019 15:31:53 -0800 Subject: [PATCH 2/2] Adds a new activity for Gesture Navigation settings Bug: 146004827 Test: WIP Change-Id: Id2732a94e7e1469575aa8204c727379a829bccf8 --- AndroidManifest.xml | 18 +++ res/values/strings.xml | 3 + res/xml/gesture_navigation_settings.xml | 51 ++++++ src/com/android/settings/Settings.java | 1 + .../core/gateway/SettingsGateway.java | 4 +- .../GestureNavigationSettingsFragment.java | 148 ++++++++++++++++++ 6 files changed, 224 insertions(+), 1 deletion(-) create mode 100644 res/xml/gesture_navigation_settings.xml create mode 100644 src/com/android/settings/gestures/GestureNavigationSettingsFragment.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 6ee6a876fe0..a4803a984f1 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -3244,6 +3244,24 @@ android:value="com.android.settings.gestures.GlobalActionsPanelSettings" /> + + + + + + + + + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 206c704d483..38ce0c769c8 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -10237,6 +10237,9 @@ Gesture Settings + + gesture navigation, back sensitivity, back gesture + Double-tap to check phone diff --git a/res/xml/gesture_navigation_settings.xml b/res/xml/gesture_navigation_settings.xml new file mode 100644 index 00000000000..2751f883329 --- /dev/null +++ b/res/xml/gesture_navigation_settings.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java index 46992ef68d9..50caf32f1f8 100644 --- a/src/com/android/settings/Settings.java +++ b/src/com/android/settings/Settings.java @@ -131,6 +131,7 @@ public class Settings extends SettingsActivity { public static class PhotosStorageActivity extends SettingsActivity { /* empty */ } + public static class GestureNavigationSettingsActivity extends SettingsActivity { /* empty */ } public static class ApnSettingsActivity extends SettingsActivity { /* empty */ } public static class WifiCallingSettingsActivity extends SettingsActivity { /* empty */ } diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java index 01b15983f45..1e97bdb6ffd 100644 --- a/src/com/android/settings/core/gateway/SettingsGateway.java +++ b/src/com/android/settings/core/gateway/SettingsGateway.java @@ -87,6 +87,7 @@ import com.android.settings.gestures.AssistGestureSettings; import com.android.settings.gestures.DoubleTapPowerSettings; import com.android.settings.gestures.DoubleTapScreenSettings; import com.android.settings.gestures.DoubleTwistGestureSettings; +import com.android.settings.gestures.GestureNavigationSettingsFragment; import com.android.settings.gestures.GlobalActionsPanelSettings; import com.android.settings.gestures.PickupGestureSettings; import com.android.settings.gestures.SwipeToNotificationSettings; @@ -289,7 +290,8 @@ public class SettingsGateway { MobileNetworkListFragment.class.getName(), GlobalActionsPanelSettings.class.getName(), DarkModeSettingsFragment.class.getName(), - BugReportHandlerPicker.class.getName() + BugReportHandlerPicker.class.getName(), + GestureNavigationSettingsFragment.class.getName() }; public static final String[] SETTINGS_FOR_RESTRICTED = { diff --git a/src/com/android/settings/gestures/GestureNavigationSettingsFragment.java b/src/com/android/settings/gestures/GestureNavigationSettingsFragment.java new file mode 100644 index 00000000000..c209c81a6ff --- /dev/null +++ b/src/com/android/settings/gestures/GestureNavigationSettingsFragment.java @@ -0,0 +1,148 @@ +/* + * 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.app.settings.SettingsEnums; +import android.content.Context; +import android.os.Bundle; +import android.provider.Settings; +import android.view.WindowManager; + +import com.android.settings.R; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settingslib.search.SearchIndexable; + +/** + * A fragment to include all the settings related to Gesture Navigation mode. + */ +@SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC) +public class GestureNavigationSettingsFragment extends DashboardFragment { + + public static final String TAG = "GestureNavigationSettingsFragment"; + + public static final String GESTURE_NAVIGATION_SETTINGS = + "com.android.settings.GESTURE_NAVIGATION_SETTINGS"; + + private static final String LEFT_EDGE_SEEKBAR_KEY = "gesture_left_back_sensitivity"; + private static final String RIGHT_EDGE_SEEKBAR_KEY = "gesture_right_back_sensitivity"; + + private WindowManager mWindowManager; + private BackGestureIndicatorView mIndicatorView; + + private static final float[] BACK_GESTURE_INSET_SCALES = {0.75f, 1.0f, 1.33f, 1.66f}; + + private float mDefaultBackGestureInset; + + public GestureNavigationSettingsFragment() { + super(); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mIndicatorView = new BackGestureIndicatorView(getActivity()); + mWindowManager = (WindowManager) getActivity().getSystemService(Context.WINDOW_SERVICE); + + mDefaultBackGestureInset = getActivity().getResources().getDimensionPixelSize( + com.android.internal.R.dimen.config_backGestureInset); + } + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + super.onCreatePreferences(savedInstanceState, rootKey); + + initSeekBarPreference(LEFT_EDGE_SEEKBAR_KEY); + initSeekBarPreference(RIGHT_EDGE_SEEKBAR_KEY); + } + + @Override + public void onResume() { + super.onResume(); + + mWindowManager.addView(mIndicatorView, mIndicatorView.getLayoutParams( + getActivity().getWindow().getAttributes())); + } + + @Override + public void onPause() { + super.onPause(); + + mWindowManager.removeView(mIndicatorView); + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.gesture_navigation_settings; + } + + @Override + public int getHelpResource() { + // TODO(b/146001201): Replace with gesture navigation help page when ready. + return R.string.help_uri_default; + } + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + public int getMetricsCategory() { + return SettingsEnums.SETTINGS_GESTURE_NAV_BACK_SENSITIVITY_DLG; + } + + private void initSeekBarPreference(final String key) { + final GestureNavigationSeekBarPreference pref = getPreferenceScreen().findPreference(key); + pref.setContinuousUpdates(true); + + final String settingsKey = key == LEFT_EDGE_SEEKBAR_KEY + ? Settings.Secure.BACK_GESTURE_INSET_SCALE_LEFT + : Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT; + final float initScale = Settings.Secure.getFloat( + getContext().getContentResolver(), settingsKey, 1.0f); + + // Find the closest value to initScale + float minDistance = Float.MAX_VALUE; + int minDistanceIndex = -1; + for (int i = 0; i < BACK_GESTURE_INSET_SCALES.length; i++) { + float d = Math.abs(BACK_GESTURE_INSET_SCALES[i] - initScale); + if (d < minDistance) { + minDistance = d; + minDistanceIndex = i; + } + } + pref.setProgress(minDistanceIndex); + + pref.setOnPreferenceChangeListener((p, v) -> { + final int width = (int) (mDefaultBackGestureInset * BACK_GESTURE_INSET_SCALES[(int) v]); + mIndicatorView.setIndicatorWidth(width, key == LEFT_EDGE_SEEKBAR_KEY); + return true; + }); + + pref.setOnPreferenceChangeStopListener((p, v) -> { + mIndicatorView.setIndicatorWidth(0, key == LEFT_EDGE_SEEKBAR_KEY); + final float scale = BACK_GESTURE_INSET_SCALES[(int) v]; + Settings.Secure.putFloat(getContext().getContentResolver(), settingsKey, scale); + return true; + }); + } + + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider(R.xml.gesture_navigation_settings); +}