Merge "Add tutorial dialog for gesture navigation" into qt-dev
am: 102d43b5c1

Change-Id: I12f498a313f5920940d49553f0c11c5619a7a4eb
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index f7465fe..ef68318 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1501,6 +1501,11 @@
         </activity>
 
         <activity
+            android:name=".SettingsTutorialDialogWrapperActivity"
+            android:theme="@style/Theme.AlertDialog"
+            android:exported="false"/>
+
+        <activity
             android:name="Settings$TextToSpeechSettingsActivity"
             android:label="@string/tts_settings">
             <intent-filter android:priority="1">
diff --git a/res/drawable/ic_accessibility_new.xml b/res/drawable/ic_accessibility_new.xml
new file mode 100644
index 0000000..6bfc6c6
--- /dev/null
+++ b/res/drawable/ic_accessibility_new.xml
@@ -0,0 +1,25 @@
+<!--
+  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
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:fillColor="#757575"
+      android:pathData="M20.5,6c-2.61,0.7 -5.67,1 -8.5,1s-5.89,-0.3 -8.5,-1L3,8c1.86,0.5 4,0.83 6,1v13h2v-6h2v6h2V9c2,-0.17 4.14,-0.5 6,-1l-0.5,-2zM12,6c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2z"/>
+</vector>
diff --git a/res/drawable/illustration_accessibility_button.xml b/res/drawable/illustration_accessibility_button.xml
new file mode 100644
index 0000000..264f3d3
--- /dev/null
+++ b/res/drawable/illustration_accessibility_button.xml
@@ -0,0 +1,30 @@
+<!--
+  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
+  -->
+
+<vector android:height="260dp" android:viewportHeight="260"
+    android:viewportWidth="260" android:width="260dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="#FFFFFF" android:pathData="M55,23.81V197c0,3.31 2.69,6 6,6h138c3.31,0 6,-2.69 6,-6V23.81C183.81,8.81 157.93,0 130,0S76.19,8.81 55,23.81z"/>
+    <path android:fillColor="#F1F3F4" android:pathData="M61,209h138c6.62,0 12,-5.38 12,-12V28.32c-1.96,-1.56 -3.95,-3.07 -6,-4.52V197c0,3.31 -2.69,6 -6,6H61c-3.31,0 -6,-2.69 -6,-6V23.81c-2.05,1.45 -4.04,2.96 -6,4.52V197C49,203.62 54.38,209 61,209z"/>
+    <path android:fillColor="#DADCE0" android:pathData="M61,212h138c8.27,0 15,-6.73 15,-15V30.79c-0.5,-0.42 -1,-0.85 -1.5,-1.26V197c0,7.44 -6.06,13.5 -13.5,13.5H61c-7.44,0 -13.5,-6.06 -13.5,-13.5V29.53c-0.5,0.41 -1,0.84 -1.5,1.26V197C46,205.27 52.73,212 61,212z"/>
+    <path android:fillColor="#DADCE0" android:pathData="M211,197c0,6.62 -5.38,12 -12,12H61c-6.62,0 -12,-5.38 -12,-12V28.32c-0.5,0.4 -1,0.8 -1.5,1.21V197c0,7.44 6.06,13.5 13.5,13.5h138c7.44,0 13.5,-6.06 13.5,-13.5V29.53c-0.5,-0.41 -1,-0.81 -1.5,-1.21V197z"/>
+    <path android:fillColor="#202124" android:pathData="M199,203H61c-3.31,0 -6,-2.69 -6,-6v-21.24h150V197C205,200.31 202.31,203 199,203z"/>
+    <path android:fillAlpha="0.8" android:fillColor="#FFFFFF"
+        android:pathData="M81.89,190.03l6.5,3.75c0.5,0.29 1.12,-0.07 1.12,-0.64v-7.51c0,-0.57 -0.62,-0.93 -1.12,-0.64l-6.5,3.75C81.4,189.02 81.4,189.74 81.89,190.03z" android:strokeAlpha="0.8"/>
+    <path android:fillAlpha="0.8" android:fillColor="#FFFFFF"
+        android:pathData="M130,189.38m-4.2,0a4.2,4.2 0,1 1,8.4 0a4.2,4.2 0,1 1,-8.4 0" android:strokeAlpha="0.8"/>
+    <path android:fillColor="#FFFFFF" android:pathData="M179.78,184.38c-2.18,0.58 -4.73,0.83 -7.08,0.83c-2.36,0 -4.91,-0.25 -7.08,-0.83l-0.42,1.67c1.55,0.42 3.33,0.69 5,0.83v10.83h1.67v-5h1.67v5h1.67v-10.83c1.67,-0.14 3.45,-0.42 5,-0.83L179.78,184.38zM172.69,184.38c0.92,0 1.67,-0.75 1.67,-1.67c0,-0.92 -0.75,-1.67 -1.67,-1.67c-0.92,0 -1.67,0.75 -1.67,1.67C171.03,183.63 171.78,184.38 172.69,184.38z"/>
+    <path android:fillColor="#E8EAED" android:pathData="M130,2c34.19,0 66.33,13.31 90.51,37.49S258,95.81 258,130s-13.31,66.33 -37.49,90.51S164.19,258 130,258s-66.33,-13.31 -90.51,-37.49S2,164.19 2,130s13.31,-66.33 37.49,-90.51S95.81,2 130,2M130,0C58.2,0 0,58.2 0,130s58.2,130 130,130s130,-58.2 130,-130S201.8,0 130,0L130,0z"/>
+</vector>
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 0000000..fb4b6cc
--- /dev/null
+++ b/res/layout/tutorial_dialog_launch_by_gesture_navigation_settings.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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
+  -->
+
+<ScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:textDirection="locale"
+    android:scrollbarStyle="outsideOverlay">
+
+    <LinearLayout
+        android:theme="@style/Theme.AlertDialog"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:paddingTop="24dp">
+
+        <TextureView
+            android:id="@+id/gesture_tutorial_video"
+            android:layout_width="200dp"
+            android:layout_height="200dp"
+            android:layout_gravity="center_horizontal"/>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            android:gravity="center_horizontal"
+            android:paddingTop="32dp"
+            android:paddingLeft="24dp"
+            android:paddingRight="24dp">
+
+            <TextView
+                android:id="@+id/gesture_tutorial_title"
+                android:text="@string/accessibility_tutorial_dialog_title_gesture_settings"
+                style="@style/AccessibilityDialogTitle" />
+
+            <TextView
+                android:id="@+id/gesture_tutorial_message"
+                style="@style/AccessibilityDialogDescription" />
+
+        </LinearLayout>
+
+    </LinearLayout>
+
+</ScrollView>
\ No newline at end of file
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 0000000..caa91a8
--- /dev/null
+++ b/res/layout/tutorial_dialog_launch_service_by_accessibility_button.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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
+  -->
+
+<ScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:textDirection="locale"
+    android:scrollbarStyle="outsideOverlay">
+
+    <LinearLayout
+        android:theme="@style/Theme.AlertDialog"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:paddingTop="24dp">
+
+        <ImageView
+            android:id="@+id/button_tutorial_image"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:src="@drawable/illustration_accessibility_button"
+            android:scaleType="fitCenter"/>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            android:gravity="center_horizontal"
+            android:paddingTop="32dp"
+            android:paddingLeft="24dp"
+            android:paddingRight="24dp">
+
+            <TextView
+                android:id="@+id/button_tutorial_title"
+                android:text="@string/accessibility_tutorial_dialog_title_button"
+                style="@style/AccessibilityDialogTitle" />
+
+            <TextView
+                android:id="@+id/button_tutorial_message"
+                style="@style/AccessibilityDialogDescription" />
+
+        </LinearLayout>
+
+    </LinearLayout>
+
+</ScrollView>
\ No newline at end of file
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 0000000..454029b
--- /dev/null
+++ b/res/layout/tutorial_dialog_launch_service_by_gesture_navigation.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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
+  -->
+
+<ScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:textDirection="locale"
+    android:scrollbarStyle="outsideOverlay">
+
+    <LinearLayout
+        android:theme="@style/Theme.AlertDialog"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        android:paddingTop="24dp">
+
+        <TextureView
+            android:id="@+id/gesture_tutorial_video"
+            android:layout_width="200dp"
+            android:layout_height="200dp"
+            android:layout_gravity="center_horizontal"/>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            android:gravity="center_horizontal"
+            android:paddingTop="32dp"
+            android:paddingLeft="24dp"
+            android:paddingRight="24dp">
+
+            <TextView
+                android:id="@+id/gesture_tutorial_title"
+                android:text="@string/accessibility_tutorial_dialog_title_gesture"
+                style="@style/AccessibilityDialogTitle" />
+
+            <TextView
+                android:id="@+id/gesture_tutorial_message"
+                style="@style/AccessibilityDialogDescription" />
+
+        </LinearLayout>
+
+    </LinearLayout>
+
+</ScrollView>
\ No newline at end of file
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 0000000..d48371b
--- /dev/null
+++ b/res/raw/illustration_accessibility_gesture_three_finger.mp4
Binary files 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 0000000..7607abc
--- /dev/null
+++ b/res/raw/illustration_accessibility_gesture_two_finger.mp4
Binary files differ
diff --git a/src/com/android/settings/SettingsTutorialDialogWrapperActivity.java b/src/com/android/settings/SettingsTutorialDialogWrapperActivity.java
new file mode 100644
index 0000000..ce44689
--- /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());
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/accessibility/AccessibilityGestureNavigationTutorial.java b/src/com/android/settings/accessibility/AccessibilityGestureNavigationTutorial.java
new file mode 100644
index 0000000..8c0bdab
--- /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();
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java b/src/com/android/settings/accessibility/ToggleAccessibilityServicePreferenceFragment.java
index b28d8b5..ec8df88 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 @@
 
     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 @@
 
     private final SettingsContentObserver mSettingsContentObserver =
             new SettingsContentObserver(new Handler()) {
-            @Override
+                @Override
                 public void onChange(boolean selfChange, Uri uri) {
                     updateSwitchBarToggleSwitch();
                 }
@@ -144,6 +149,16 @@
                         .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();
             }
@@ -197,32 +212,54 @@
 
     @Override
     public void onClick(View view) {
-        switch (view.getId()) {
-            case R.id.permission_enable_allow_button:
-                if (isFullDiskEncrypted()) {
-                    String title = createConfirmCredentialReasonMessage();
-                    Intent intent = ConfirmDeviceCredentialActivity.createIntent(title, null);
-                    startActivityForResult(intent,
-                            ACTIVITY_REQUEST_CONFIRM_CREDENTIAL_FOR_WEAKER_ENCRYPTION);
-                } else {
-                    handleConfirmServiceEnabled(true);
-                }
-                break;
-            case R.id.permission_enable_deny_button:
-                handleConfirmServiceEnabled(false);
-                break;
-            case R.id.permission_disable_stop_button:
-                handleConfirmServiceEnabled(false);
-                break;
-            case R.id.permission_disable_cancel_button:
+        if (view.getId() == R.id.permission_enable_allow_button) {
+            if (isFullDiskEncrypted()) {
+                String title = createConfirmCredentialReasonMessage();
+                Intent intent = ConfirmDeviceCredentialActivity.createIntent(title, null);
+                startActivityForResult(intent,
+                        ACTIVITY_REQUEST_CONFIRM_CREDENTIAL_FOR_WEAKER_ENCRYPTION);
+            } else {
                 handleConfirmServiceEnabled(true);
-                break;
-            default:
-                throw new IllegalArgumentException();
+                if (isServiceSupportAccessibilityButton()) {
+                    showDialog(DIALOG_ID_LAUNCH_ACCESSIBILITY_TUTORIAL);
+                }
+            }
+        } else if (view.getId() == R.id.permission_enable_deny_button) {
+            handleConfirmServiceEnabled(false);
+        } else if (view.getId() == R.id.permission_disable_stop_button) {
+            handleConfirmServiceEnabled(false);
+        } else if (view.getId() == R.id.permission_disable_cancel_button) {
+            handleConfirmServiceEnabled(true);
+        } else {
+            throw new IllegalArgumentException();
         }
         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<AccessibilityServiceInfo> 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);
@@ -234,11 +271,13 @@
         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()));
@@ -248,7 +287,7 @@
     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 5ed5169..ec3ebcc 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.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 @@
 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;
@@ -162,18 +171,43 @@
     }
 
     @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);
     }
 
     @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 @@
         }
     }
 
+    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 0000000..8f94b76
--- /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 40e8831..28b76b2 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.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 @@
 
         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 @@
                 .show();
     }
 
+    private boolean isAnyServiceSupportAccessibilityButton() {
+        final AccessibilityManager ams = (AccessibilityManager) getContext().getSystemService(
+                Context.ACCESSIBILITY_SERVICE);
+        final List<AccessibilityServiceInfo> 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;