| /* |
| * 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(); |
| } |
| } |