1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.settings.accessibility; 18 19 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL; 20 21 import android.content.Context; 22 import android.content.DialogInterface; 23 import android.content.res.TypedArray; 24 import android.graphics.drawable.Drawable; 25 import android.text.Spannable; 26 import android.text.SpannableString; 27 import android.text.style.ImageSpan; 28 import android.view.LayoutInflater; 29 import android.view.TextureView; 30 import android.view.View; 31 import android.view.Window; 32 import android.view.accessibility.AccessibilityManager; 33 import android.widget.TextView; 34 35 import androidx.annotation.ColorInt; 36 import androidx.annotation.IntDef; 37 import androidx.appcompat.app.AlertDialog; 38 import androidx.core.content.ContextCompat; 39 40 import com.android.settings.R; 41 42 import java.lang.annotation.Retention; 43 import java.lang.annotation.RetentionPolicy; 44 45 /** 46 * Utility class for creating the dialog that guides users for gesture navigation for 47 * accessibility services. 48 */ 49 public class AccessibilityGestureNavigationTutorial { 50 51 /** IntDef enum for dialog type. */ 52 @Retention(RetentionPolicy.SOURCE) 53 @IntDef({ 54 DialogType.LAUNCH_SERVICE_BY_ACCESSIBILITY_BUTTON, 55 DialogType.LAUNCH_SERVICE_BY_GESTURE_NAVIGATION, 56 DialogType.GESTURE_NAVIGATION_SETTINGS, 57 }) 58 59 private @interface DialogType { 60 int LAUNCH_SERVICE_BY_ACCESSIBILITY_BUTTON = 0; 61 int LAUNCH_SERVICE_BY_GESTURE_NAVIGATION = 1; 62 int GESTURE_NAVIGATION_SETTINGS = 2; 63 } 64 65 private static final DialogInterface.OnClickListener mOnClickListener = 66 (DialogInterface dialog, int which) -> dialog.dismiss(); 67 showGestureNavigationSettingsTutorialDialog(Context context, DialogInterface.OnDismissListener dismissListener)68 public static void showGestureNavigationSettingsTutorialDialog(Context context, 69 DialogInterface.OnDismissListener dismissListener) { 70 final AlertDialog alertDialog = new AlertDialog.Builder(context) 71 .setView(createTutorialDialogContentView(context, 72 DialogType.GESTURE_NAVIGATION_SETTINGS)) 73 .setNegativeButton(R.string.accessibility_tutorial_dialog_button, mOnClickListener) 74 .setOnDismissListener(dismissListener) 75 .create(); 76 77 alertDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); 78 alertDialog.setCanceledOnTouchOutside(false); 79 alertDialog.show(); 80 } 81 showAccessibilityButtonTutorialDialog(Context context)82 static AlertDialog showAccessibilityButtonTutorialDialog(Context context) { 83 final AlertDialog alertDialog = createDialog(context, 84 DialogType.LAUNCH_SERVICE_BY_ACCESSIBILITY_BUTTON); 85 86 if (!isGestureNavigateEnabled(context)) { 87 updateMessageWithIcon(context, alertDialog); 88 } 89 90 return alertDialog; 91 } 92 showGestureNavigationTutorialDialog(Context context)93 static AlertDialog showGestureNavigationTutorialDialog(Context context) { 94 return createDialog(context, DialogType.LAUNCH_SERVICE_BY_GESTURE_NAVIGATION); 95 } 96 97 /** 98 * Get a content View for a dialog to confirm that they want to enable a service. 99 * 100 * @param context A valid context 101 * @param dialogType The type of tutorial dialog 102 * @return A content view suitable for viewing 103 */ createTutorialDialogContentView(Context context, int dialogType)104 private static View createTutorialDialogContentView(Context context, int dialogType) { 105 final LayoutInflater inflater = (LayoutInflater) context.getSystemService( 106 Context.LAYOUT_INFLATER_SERVICE); 107 108 View content = null; 109 110 switch (dialogType) { 111 case DialogType.LAUNCH_SERVICE_BY_ACCESSIBILITY_BUTTON: 112 content = inflater.inflate( 113 R.layout.tutorial_dialog_launch_service_by_accessibility_button, null); 114 break; 115 case DialogType.LAUNCH_SERVICE_BY_GESTURE_NAVIGATION: 116 content = inflater.inflate( 117 R.layout.tutorial_dialog_launch_service_by_gesture_navigation, null); 118 final TextureView gestureTutorialVideo = content.findViewById( 119 R.id.gesture_tutorial_video); 120 final TextView gestureTutorialMessage = content.findViewById( 121 R.id.gesture_tutorial_message); 122 VideoPlayer.create(context, isTouchExploreOn(context) 123 ? R.raw.illustration_accessibility_gesture_three_finger 124 : R.raw.illustration_accessibility_gesture_two_finger, 125 gestureTutorialVideo); 126 gestureTutorialMessage.setText(isTouchExploreOn(context) 127 ? R.string.accessibility_tutorial_dialog_message_gesture_with_talkback 128 : R.string.accessibility_tutorial_dialog_message_gesture_without_talkback); 129 break; 130 case DialogType.GESTURE_NAVIGATION_SETTINGS: 131 content = inflater.inflate( 132 R.layout.tutorial_dialog_launch_by_gesture_navigation_settings, null); 133 final TextureView gestureSettingsTutorialVideo = content.findViewById( 134 R.id.gesture_tutorial_video); 135 final TextView gestureSettingsTutorialMessage = content.findViewById( 136 R.id.gesture_tutorial_message); 137 VideoPlayer.create(context, isTouchExploreOn(context) 138 ? R.raw.illustration_accessibility_gesture_three_finger 139 : R.raw.illustration_accessibility_gesture_two_finger, 140 gestureSettingsTutorialVideo); 141 gestureSettingsTutorialMessage.setText(isTouchExploreOn(context) 142 ? 143 R.string.accessibility_tutorial_dialog_message_gesture_settings_with_talkback 144 : R.string.accessibility_tutorial_dialog_message_gesture_settings_without_talkback); 145 break; 146 } 147 148 return content; 149 } 150 createDialog(Context context, int dialogType)151 private static AlertDialog createDialog(Context context, int dialogType) { 152 final AlertDialog alertDialog = new AlertDialog.Builder(context) 153 .setView(createTutorialDialogContentView(context, dialogType)) 154 .setNegativeButton(R.string.accessibility_tutorial_dialog_button, mOnClickListener) 155 .create(); 156 157 alertDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); 158 alertDialog.setCanceledOnTouchOutside(false); 159 alertDialog.show(); 160 161 return alertDialog; 162 } 163 updateMessageWithIcon(Context context, AlertDialog alertDialog)164 private static void updateMessageWithIcon(Context context, AlertDialog alertDialog) { 165 final TextView gestureTutorialMessage = alertDialog.findViewById( 166 R.id.button_tutorial_message); 167 168 // Get the textView line height to update [icon] size. Must be called after show() 169 final int lineHeight = gestureTutorialMessage.getLineHeight(); 170 gestureTutorialMessage.setText(getMessageStringWithIcon(context, lineHeight)); 171 } 172 getMessageStringWithIcon(Context context, int lineHeight)173 private static SpannableString getMessageStringWithIcon(Context context, int lineHeight) { 174 final String messageString = context 175 .getString(R.string.accessibility_tutorial_dialog_message_button); 176 final SpannableString spannableMessage = SpannableString.valueOf(messageString); 177 178 // Icon 179 final int indexIconStart = messageString.indexOf("%s"); 180 final int indexIconEnd = indexIconStart + 2; 181 final Drawable icon = context.getDrawable(R.drawable.ic_accessibility_new); 182 icon.setTint(getThemeAttrColor(context, android.R.attr.textColorPrimary)); 183 icon.setBounds(0, 0, lineHeight, lineHeight); 184 spannableMessage.setSpan( 185 new ImageSpan(icon), indexIconStart, indexIconEnd, 186 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 187 188 return spannableMessage; 189 } 190 191 /** Returns the color associated with the specified attribute in the context's theme. */ 192 @ColorInt getThemeAttrColor(final Context context, final int attributeColor)193 private static int getThemeAttrColor(final Context context, final int attributeColor) { 194 final int colorResId = getAttrResourceId(context, attributeColor); 195 return ContextCompat.getColor(context, colorResId); 196 } 197 198 /** Returns the identifier of the resolved resource assigned to the given attribute. */ getAttrResourceId(final Context context, final int attributeColor)199 private static int getAttrResourceId(final Context context, final int attributeColor) { 200 final int[] attrs = {attributeColor}; 201 final TypedArray typedArray = context.obtainStyledAttributes(attrs); 202 final int colorResId = typedArray.getResourceId(0, 0); 203 typedArray.recycle(); 204 return colorResId; 205 } 206 isGestureNavigateEnabled(Context context)207 private static boolean isGestureNavigateEnabled(Context context) { 208 return context.getResources().getInteger( 209 com.android.internal.R.integer.config_navBarInteractionMode) 210 == NAV_BAR_MODE_GESTURAL; 211 } 212 isTouchExploreOn(Context context)213 private static boolean isTouchExploreOn(Context context) { 214 return ((AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE)) 215 .isTouchExplorationEnabled(); 216 } 217 }