diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index f7465fec324..ef68318799b 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1500,6 +1500,11 @@
android:value="true" />
+
+
diff --git a/res/drawable/ic_accessibility_new.xml b/res/drawable/ic_accessibility_new.xml
new file mode 100644
index 00000000000..6bfc6c60f92
--- /dev/null
+++ b/res/drawable/ic_accessibility_new.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
diff --git a/res/drawable/illustration_accessibility_button.xml b/res/drawable/illustration_accessibility_button.xml
new file mode 100644
index 00000000000..264f3d361ed
--- /dev/null
+++ b/res/drawable/illustration_accessibility_button.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/tutorial_dialog_launch_by_gesture_navigation_settings.xml b/res/layout/tutorial_dialog_launch_by_gesture_navigation_settings.xml
new file mode 100644
index 00000000000..524f7c9b7e4
--- /dev/null
+++ b/res/layout/tutorial_dialog_launch_by_gesture_navigation_settings.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/tutorial_dialog_launch_service_by_accessibility_button.xml b/res/layout/tutorial_dialog_launch_service_by_accessibility_button.xml
new file mode 100644
index 00000000000..ecbccabd43f
--- /dev/null
+++ b/res/layout/tutorial_dialog_launch_service_by_accessibility_button.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/tutorial_dialog_launch_service_by_gesture_navigation.xml b/res/layout/tutorial_dialog_launch_service_by_gesture_navigation.xml
new file mode 100644
index 00000000000..7c4971fe487
--- /dev/null
+++ b/res/layout/tutorial_dialog_launch_service_by_gesture_navigation.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/raw/illustration_accessibility_gesture_three_finger.mp4 b/res/raw/illustration_accessibility_gesture_three_finger.mp4
new file mode 100644
index 00000000000..d48371b0748
Binary files /dev/null and b/res/raw/illustration_accessibility_gesture_three_finger.mp4 differ
diff --git a/res/raw/illustration_accessibility_gesture_two_finger.mp4 b/res/raw/illustration_accessibility_gesture_two_finger.mp4
new file mode 100644
index 00000000000..7607abc0e66
Binary files /dev/null and b/res/raw/illustration_accessibility_gesture_two_finger.mp4 differ
diff --git a/src/com/android/settings/SettingsTutorialDialogWrapperActivity.java b/src/com/android/settings/SettingsTutorialDialogWrapperActivity.java
new file mode 100644
index 00000000000..50b966fb215
--- /dev/null
+++ b/src/com/android/settings/SettingsTutorialDialogWrapperActivity.java
@@ -0,0 +1,42 @@
+/*
+ * 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;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.android.settings.accessibility.AccessibilityGestureNavigationTutorial;
+import com.android.settings.R;
+
+/**
+ * This activity is to create the tutorial dialog in gesture navigation settings since we couldn't
+ * use the dialog utils because SystemNavigationGestureSettings extends RadioButtonPickerFragment,
+ * not SettingsPreferenceFragment.
+ */
+public class SettingsTutorialDialogWrapperActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ showDialog();
+ }
+
+ private void showDialog() {
+ AccessibilityGestureNavigationTutorial
+ .showGestureNavigationSettingsTutorialDialog(this, dialog -> finish());
+ }
+}
diff --git a/src/com/android/settings/accessibility/AccessibilityGestureNavigationTutorial.java b/src/com/android/settings/accessibility/AccessibilityGestureNavigationTutorial.java
new file mode 100644
index 00000000000..e7b2b829b57
--- /dev/null
+++ b/src/com/android/settings/accessibility/AccessibilityGestureNavigationTutorial.java
@@ -0,0 +1,217 @@
+/*
+ * 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.accessibility;
+
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.style.ImageSpan;
+import android.view.LayoutInflater;
+import android.view.TextureView;
+import android.view.View;
+import android.view.Window;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.TextView;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.IntDef;
+import androidx.appcompat.app.AlertDialog;
+import androidx.core.content.ContextCompat;
+
+import com.android.settings.R;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Utility class for creating the dialog that guides users for gesture navigation for
+ * accessibility services.
+ */
+public class AccessibilityGestureNavigationTutorial {
+
+ /** IntDef enum for dialog type. */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ DialogType.LAUNCH_SERVICE_BY_ACCESSIBILITY_BUTTON,
+ DialogType.LAUNCH_SERVICE_BY_GESTURE_NAVIGATION,
+ DialogType.GESTURE_NAVIGATION_SETTINGS,
+ })
+
+ private @interface DialogType {
+ int LAUNCH_SERVICE_BY_ACCESSIBILITY_BUTTON = 0;
+ int LAUNCH_SERVICE_BY_GESTURE_NAVIGATION = 1;
+ int GESTURE_NAVIGATION_SETTINGS = 2;
+ }
+
+ private static final DialogInterface.OnClickListener mOnClickListener =
+ (DialogInterface dialog, int which) -> dialog.dismiss();
+
+ public static void showGestureNavigationSettingsTutorialDialog(Context context,
+ DialogInterface.OnDismissListener dismissListener) {
+ final AlertDialog alertDialog = new AlertDialog.Builder(context)
+ .setView(createTutorialDialogContentView(context,
+ DialogType.GESTURE_NAVIGATION_SETTINGS))
+ .setNegativeButton(R.string.accessibility_tutorial_dialog_button, mOnClickListener)
+ .setOnDismissListener(dismissListener)
+ .create();
+
+ alertDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+ alertDialog.setCanceledOnTouchOutside(false);
+ alertDialog.show();
+ }
+
+ static AlertDialog showAccessibilityButtonTutorialDialog(Context context) {
+ final AlertDialog alertDialog = createDialog(context,
+ DialogType.LAUNCH_SERVICE_BY_ACCESSIBILITY_BUTTON);
+
+ if (!isGestureNavigateEnabled(context)) {
+ updateMessageWithIcon(context, alertDialog);
+ }
+
+ return alertDialog;
+ }
+
+ static AlertDialog showGestureNavigationTutorialDialog(Context context) {
+ return createDialog(context, DialogType.LAUNCH_SERVICE_BY_GESTURE_NAVIGATION);
+ }
+
+ /**
+ * Get a content View for a dialog to confirm that they want to enable a service.
+ *
+ * @param context A valid context
+ * @param dialogType The type of tutorial dialog
+ * @return A content view suitable for viewing
+ */
+ private static View createTutorialDialogContentView(Context context, int dialogType) {
+ final LayoutInflater inflater = (LayoutInflater) context.getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+
+ View content = null;
+
+ switch (dialogType) {
+ case DialogType.LAUNCH_SERVICE_BY_ACCESSIBILITY_BUTTON:
+ content = inflater.inflate(
+ R.layout.tutorial_dialog_launch_service_by_accessibility_button, null);
+ break;
+ case DialogType.LAUNCH_SERVICE_BY_GESTURE_NAVIGATION:
+ content = inflater.inflate(
+ R.layout.tutorial_dialog_launch_service_by_gesture_navigation, null);
+ final TextureView gestureTutorialVideo = content.findViewById(
+ R.id.gesture_tutorial_video);
+ final TextView gestureTutorialMessage = content.findViewById(
+ R.id.gesture_tutorial_message);
+ VideoPlayer.create(context, isTouchExploreOn(context)
+ ? R.raw.illustration_accessibility_gesture_three_finger
+ : R.raw.illustration_accessibility_gesture_two_finger,
+ gestureTutorialVideo);
+ gestureTutorialMessage.setText(isTouchExploreOn(context)
+ ? R.string.accessibility_tutorial_dialog_message_gesture_with_talkback
+ : R.string.accessibility_tutorial_dialog_message_gesture_without_talkback);
+ break;
+ case DialogType.GESTURE_NAVIGATION_SETTINGS:
+ content = inflater.inflate(
+ R.layout.tutorial_dialog_launch_by_gesture_navigation_settings, null);
+ final TextureView gestureSettingsTutorialVideo = content.findViewById(
+ R.id.gesture_tutorial_video);
+ final TextView gestureSettingsTutorialMessage = content.findViewById(
+ R.id.gesture_tutorial_message);
+ VideoPlayer.create(context, isTouchExploreOn(context)
+ ? R.raw.illustration_accessibility_gesture_three_finger
+ : R.raw.illustration_accessibility_gesture_two_finger,
+ gestureSettingsTutorialVideo);
+ gestureSettingsTutorialMessage.setText(isTouchExploreOn(context)
+ ?
+ R.string.accessibility_tutorial_dialog_message_gesture_settings_with_talkback
+ : R.string.accessibility_tutorial_dialog_message_gesture_settings_without_talkback);
+ break;
+ }
+
+ return content;
+ }
+
+ private static AlertDialog createDialog(Context context, int dialogType) {
+ final AlertDialog alertDialog = new AlertDialog.Builder(context)
+ .setView(createTutorialDialogContentView(context, dialogType))
+ .setNegativeButton(R.string.accessibility_tutorial_dialog_button, mOnClickListener)
+ .create();
+
+ alertDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
+ alertDialog.setCanceledOnTouchOutside(false);
+ alertDialog.show();
+
+ return alertDialog;
+ }
+
+ private static void updateMessageWithIcon(Context context, AlertDialog alertDialog) {
+ final TextView gestureTutorialMessage = alertDialog.findViewById(
+ R.id.button_tutorial_message);
+
+ // Get the textView line height to update [icon] size. Must be called after show()
+ final int lineHeight = gestureTutorialMessage.getLineHeight();
+ gestureTutorialMessage.setText(getMessageStringWithIcon(context, lineHeight));
+ }
+
+ private static SpannableString getMessageStringWithIcon(Context context, int lineHeight) {
+ final String messageString = context
+ .getString(R.string.accessibility_tutorial_dialog_message_button);
+ final SpannableString spannableMessage = SpannableString.valueOf(messageString);
+
+ // Icon
+ final int indexIconStart = messageString.indexOf("%s");
+ final int indexIconEnd = indexIconStart + 2;
+ final Drawable icon = context.getDrawable(R.drawable.ic_accessibility_new);
+ icon.setTint(getThemeAttrColor(context, android.R.attr.textColorPrimary));
+ icon.setBounds(0, 0, lineHeight, lineHeight);
+ spannableMessage.setSpan(
+ new ImageSpan(icon), indexIconStart, indexIconEnd,
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+ return spannableMessage;
+ }
+
+ /** Returns the color associated with the specified attribute in the context's theme. */
+ @ColorInt
+ private static int getThemeAttrColor(final Context context, final int attributeColor) {
+ final int colorResId = getAttrResourceId(context, attributeColor);
+ return ContextCompat.getColor(context, colorResId);
+ }
+
+ /** Returns the identifier of the resolved resource assigned to the given attribute. */
+ private static int getAttrResourceId(final Context context, final int attributeColor) {
+ final int[] attrs = {attributeColor};
+ final TypedArray typedArray = context.obtainStyledAttributes(attrs);
+ final int colorResId = typedArray.getResourceId(0, 0);
+ typedArray.recycle();
+ return colorResId;
+ }
+
+ private static boolean isGestureNavigateEnabled(Context context) {
+ return context.getResources().getInteger(
+ com.android.internal.R.integer.config_navBarInteractionMode)
+ == NAV_BAR_MODE_GESTURAL;
+ }
+
+ private static boolean isTouchExploreOn(Context context) {
+ return ((AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE))
+ .isTouchExplorationEnabled();
+ }
+}
diff --git a/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java b/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java
index d1adf3c6023..ec8df88d977 100644
--- a/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java
+++ b/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java
@@ -16,14 +16,18 @@
package com.android.settings.accessibility;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
+
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Activity;
import android.app.Dialog;
import android.app.admin.DevicePolicyManager;
import android.app.settings.SettingsEnums;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -50,6 +54,7 @@ public class ToggleAccessibilityServicePreferenceFragment
private static final int DIALOG_ID_ENABLE_WARNING = 1;
private static final int DIALOG_ID_DISABLE_WARNING = 2;
+ private static final int DIALOG_ID_LAUNCH_ACCESSIBILITY_TUTORIAL = 3;
public static final int ACTIVITY_REQUEST_CONFIRM_CREDENTIAL_FOR_WEAKER_ENCRYPTION = 1;
@@ -57,7 +62,7 @@ public class ToggleAccessibilityServicePreferenceFragment
private final SettingsContentObserver mSettingsContentObserver =
new SettingsContentObserver(new Handler()) {
- @Override
+ @Override
public void onChange(boolean selfChange, Uri uri) {
updateSwitchBarToggleSwitch();
}
@@ -144,6 +149,16 @@ public class ToggleAccessibilityServicePreferenceFragment
.createDisableDialog(getActivity(), info, this);
break;
}
+ case DIALOG_ID_LAUNCH_ACCESSIBILITY_TUTORIAL: {
+ if (isGestureNavigateEnabled()) {
+ mDialog = AccessibilityGestureNavigationTutorial
+ .showGestureNavigationTutorialDialog(getActivity());
+ } else {
+ mDialog = AccessibilityGestureNavigationTutorial
+ .showAccessibilityButtonTutorialDialog(getActivity());
+ }
+ break;
+ }
default: {
throw new IllegalArgumentException();
}
@@ -205,6 +220,9 @@ public class ToggleAccessibilityServicePreferenceFragment
ACTIVITY_REQUEST_CONFIRM_CREDENTIAL_FOR_WEAKER_ENCRYPTION);
} else {
handleConfirmServiceEnabled(true);
+ if (isServiceSupportAccessibilityButton()) {
+ showDialog(DIALOG_ID_LAUNCH_ACCESSIBILITY_TUTORIAL);
+ }
}
} else if (view.getId() == R.id.permission_enable_deny_button) {
handleConfirmServiceEnabled(false);
@@ -218,6 +236,30 @@ public class ToggleAccessibilityServicePreferenceFragment
mDialog.dismiss();
}
+ private boolean isGestureNavigateEnabled() {
+ return getContext().getResources().getInteger(
+ com.android.internal.R.integer.config_navBarInteractionMode)
+ == NAV_BAR_MODE_GESTURAL;
+ }
+
+ private boolean isServiceSupportAccessibilityButton() {
+ final AccessibilityManager ams = (AccessibilityManager) getContext().getSystemService(
+ Context.ACCESSIBILITY_SERVICE);
+ final List services = ams.getInstalledAccessibilityServiceList();
+
+ for (AccessibilityServiceInfo info : services) {
+ if ((info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0) {
+ ServiceInfo serviceInfo = info.getResolveInfo().serviceInfo;
+ if (serviceInfo != null && TextUtils.equals(serviceInfo.name,
+ getAccessibilityServiceInfo().getResolveInfo().serviceInfo.name)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
private void handleConfirmServiceEnabled(boolean confirmed) {
mSwitchBar.setCheckedInternal(confirmed);
getArguments().putBoolean(AccessibilitySettings.EXTRA_CHECKED, confirmed);
@@ -229,11 +271,13 @@ public class ToggleAccessibilityServicePreferenceFragment
switch (mLockPatternUtils.getKeyguardStoredPasswordQuality(UserHandle.myUserId())) {
case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING: {
resId = R.string.enable_service_pattern_reason;
- } break;
+ }
+ break;
case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX: {
resId = R.string.enable_service_pin_reason;
- } break;
+ }
+ break;
}
return getString(resId, getAccessibilityServiceInfo().getResolveInfo()
.loadLabel(getPackageManager()));
@@ -243,7 +287,7 @@ public class ToggleAccessibilityServicePreferenceFragment
protected void onInstallSwitchBarToggleSwitch() {
super.onInstallSwitchBarToggleSwitch();
mToggleSwitch.setOnBeforeCheckedChangeListener(new OnBeforeCheckedChangeListener() {
- @Override
+ @Override
public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked) {
if (checked) {
mSwitchBar.setCheckedInternal(false);
diff --git a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java
index 5ed5169f1b7..ec3ebcc1a68 100644
--- a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java
+++ b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java
@@ -16,6 +16,9 @@
package com.android.settings.accessibility;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
+
+import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.content.ContentResolver;
import android.content.Context;
@@ -26,6 +29,8 @@ import android.media.MediaPlayer;
import android.media.MediaPlayer.OnPreparedListener;
import android.net.Uri;
import android.os.Bundle;
+import android.provider.Settings;
+import android.text.TextUtils;
import android.view.Display;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.WindowManager;
@@ -44,6 +49,10 @@ import com.android.settings.widget.SwitchBar;
public class ToggleScreenMagnificationPreferenceFragment extends
ToggleFeaturePreferenceFragment implements SwitchBar.OnSwitchChangeListener {
+ private static final int DIALOG_ID_GESTURE_NAVIGATION_TUTORIAL = 1;
+
+ private Dialog mDialog;
+
protected class VideoPreference extends Preference {
private ImageView mVideoBackgroundView;
private OnGlobalLayoutListener mLayoutListener;
@@ -161,12 +170,32 @@ public class ToggleScreenMagnificationPreferenceFragment extends
updateConfigurationWarningIfNeeded();
}
+ @Override
+ public Dialog onCreateDialog(int dialogId) {
+ if (dialogId == DIALOG_ID_GESTURE_NAVIGATION_TUTORIAL) {
+ if (isGestureNavigateEnabled()) {
+ mDialog = AccessibilityGestureNavigationTutorial
+ .showGestureNavigationTutorialDialog(getActivity());
+ } else {
+ mDialog = AccessibilityGestureNavigationTutorial
+ .showAccessibilityButtonTutorialDialog(getActivity());
+ }
+ }
+
+ return mDialog;
+ }
+
@Override
public int getMetricsCategory() {
// TODO: Distinguish between magnification modes
return SettingsEnums.ACCESSIBILITY_TOGGLE_SCREEN_MAGNIFICATION;
}
+ @Override
+ public int getDialogMetricsCategory(int dialogId) {
+ return SettingsEnums.ACCESSIBILITY_TOGGLE_SCREEN_MAGNIFICATION;
+ }
+
@Override
public void onSwitchChanged(Switch switchView, boolean isChecked) {
onPreferenceToggled(mPreferenceKey, isChecked);
@@ -174,6 +203,11 @@ public class ToggleScreenMagnificationPreferenceFragment extends
@Override
protected void onPreferenceToggled(String preferenceKey, boolean enabled) {
+ if (enabled && TextUtils.equals(
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
+ preferenceKey)) {
+ showDialog(DIALOG_ID_GESTURE_NAVIGATION_TUTORIAL);
+ }
MagnificationPreferenceFragment.setChecked(getContentResolver(), preferenceKey, enabled);
updateConfigurationWarningIfNeeded();
}
@@ -224,6 +258,12 @@ public class ToggleScreenMagnificationPreferenceFragment extends
}
}
+ private boolean isGestureNavigateEnabled() {
+ return getContext().getResources().getInteger(
+ com.android.internal.R.integer.config_navBarInteractionMode)
+ == NAV_BAR_MODE_GESTURAL;
+ }
+
private void updateConfigurationWarningIfNeeded() {
final CharSequence warningMessage =
MagnificationPreferenceFragment.getConfigurationWarningStringForSecureSettingsKey(
diff --git a/src/com/android/settings/accessibility/VideoPlayer.java b/src/com/android/settings/accessibility/VideoPlayer.java
new file mode 100644
index 00000000000..8f94b768f67
--- /dev/null
+++ b/src/com/android/settings/accessibility/VideoPlayer.java
@@ -0,0 +1,149 @@
+/*
+ * 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.accessibility;
+
+import android.content.Context;
+import android.graphics.SurfaceTexture;
+import android.media.MediaPlayer;
+import android.view.Surface;
+import android.view.TextureView;
+import android.view.TextureView.SurfaceTextureListener;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.RawRes;
+
+/**
+ * Plays the video by {@link MediaPlayer} on {@link TextureView}, calls {@link #create(Context, int,
+ * TextureView)} to setup the listener for TextureView and start to play the video. Once this player
+ * is no longer used, call {@link #release()} so that MediaPlayer object can be released.
+ */
+public class VideoPlayer implements SurfaceTextureListener {
+ private final Context context;
+ private final Object mediaPlayerLock = new Object();
+ // Media player object can't be used after it has been released, so it will be set to null. But
+ // VideoPlayer is asynchronized, media player object might be paused or resumed again before
+ // released media player is set to null. Therefore, lock mediaPlayer and mediaPlayerState by
+ // mediaPlayerLock keep their states consistent.
+ @GuardedBy("mediaPlayerLock")
+ private MediaPlayer mediaPlayer;
+ @GuardedBy("mediaPlayerLock")
+ private State mediaPlayerState = State.NONE;
+ @RawRes
+ private final int videoRes;
+ private Surface animationSurface;
+
+
+ /**
+ * Creates a {@link MediaPlayer} for a given resource id and starts playback when the surface
+ * for
+ * a given {@link TextureView} is ready.
+ */
+ public static VideoPlayer create(Context context, @RawRes int videoRes,
+ TextureView textureView) {
+ return new VideoPlayer(context, videoRes, textureView);
+ }
+
+ private VideoPlayer(Context context, @RawRes int videoRes, TextureView textureView) {
+ this.context = context;
+ this.videoRes = videoRes;
+ textureView.setSurfaceTextureListener(this);
+ }
+
+ public void pause() {
+ synchronized (mediaPlayerLock) {
+ if (mediaPlayerState == State.STARTED) {
+ mediaPlayerState = State.PAUSED;
+ mediaPlayer.pause();
+ }
+ }
+ }
+
+ public void resume() {
+ synchronized (mediaPlayerLock) {
+ if (mediaPlayerState == State.PAUSED) {
+ mediaPlayer.start();
+ mediaPlayerState = State.STARTED;
+ }
+ }
+ }
+
+ /** Release media player when it's no longer needed. */
+ public void release() {
+ synchronized (mediaPlayerLock) {
+ if (mediaPlayerState != State.NONE && mediaPlayerState != State.END) {
+ mediaPlayerState = State.END;
+ mediaPlayer.release();
+ mediaPlayer = null;
+ }
+ }
+ if (animationSurface != null) {
+ animationSurface.release();
+ animationSurface = null;
+ }
+ }
+
+ @Override
+ public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
+ animationSurface = new Surface(surface);
+ synchronized (mediaPlayerLock) {
+ mediaPlayer = MediaPlayer.create(context, videoRes);
+ mediaPlayerState = State.PREPARED;
+ mediaPlayer.setSurface(animationSurface);
+ mediaPlayer.setLooping(true);
+ mediaPlayer.start();
+ mediaPlayerState = State.STARTED;
+ }
+ }
+
+ @Override
+ public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+ }
+
+ @Override
+ public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+ release();
+ return false;
+ }
+
+ @Override
+ public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+ }
+
+ /**
+ * The state of MediaPlayer object. Refer to
+ * https://developer.android.com/reference/android/media/MediaPlayer#StateDiagram.
+ */
+ public enum State {
+ /** MediaPlayer objects has not be created. */
+ NONE,
+ /** MediaPlayer objects is created by create() method. */
+ PREPARED,
+ /** MediaPlayer is started. It can be paused by pause() method. */
+ STARTED,
+ /** MediaPlayer object is paused. Calling start() to resume it. */
+ PAUSED,
+ /**
+ * MediaPlayer object is stopped and cannot be started until calling prepare() or
+ * prepareAsync()
+ * methods.
+ */
+ STOPPED,
+ /** MediaPlayer object is released. It cannot be used again. */
+ END
+ }
+}
+
diff --git a/src/com/android/settings/gestures/SystemNavigationGestureSettings.java b/src/com/android/settings/gestures/SystemNavigationGestureSettings.java
index 40e8831e5c2..28b76b2b011 100644
--- a/src/com/android/settings/gestures/SystemNavigationGestureSettings.java
+++ b/src/com/android/settings/gestures/SystemNavigationGestureSettings.java
@@ -25,18 +25,24 @@ import static com.android.settings.widget.RadioButtonPreferenceWithExtraWidget.E
import static com.android.settings.widget.RadioButtonPreferenceWithExtraWidget.EXTRA_WIDGET_VISIBILITY_INFO;
import android.app.AlertDialog;
+import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.settings.SettingsEnums;
import android.content.Context;
+import android.content.Intent;
import android.content.SharedPreferences;
import android.content.om.IOverlayManager;
import android.graphics.drawable.Drawable;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.provider.SearchIndexableResource;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.view.accessibility.AccessibilityManager;
import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceScreen;
+import com.android.settings.SettingsTutorialDialogWrapperActivity;
import com.android.settings.R;
import com.android.settings.dashboard.suggestions.SuggestionFeatureProvider;
import com.android.settings.overlay.FeatureFactory;
@@ -165,6 +171,12 @@ public class SystemNavigationGestureSettings extends RadioButtonPickerFragment {
setCurrentSystemNavigationMode(mOverlayManager, key);
setIllustrationVideo(mVideoPreference, key);
+ if (TextUtils.equals(KEY_SYSTEM_NAV_GESTURAL, key) && (
+ isAnyServiceSupportAccessibilityButton() || isNavBarMagnificationEnabled())) {
+ Intent intent = new Intent(getActivity(), SettingsTutorialDialogWrapperActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(intent);
+ }
return true;
}
@@ -256,6 +268,26 @@ public class SystemNavigationGestureSettings extends RadioButtonPickerFragment {
.show();
}
+ private boolean isAnyServiceSupportAccessibilityButton() {
+ final AccessibilityManager ams = (AccessibilityManager) getContext().getSystemService(
+ Context.ACCESSIBILITY_SERVICE);
+ final List services = ams.getEnabledAccessibilityServiceList(
+ AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
+
+ for (AccessibilityServiceInfo info : services) {
+ if ((info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private boolean isNavBarMagnificationEnabled() {
+ return Settings.Secure.getInt(getContext().getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, 0) == 1;
+ }
+
static class NavModeCandidateInfo extends CandidateInfo {
private final CharSequence mLabel;
private final CharSequence mSummary;