/* * 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.View.GONE; import static android.view.View.VISIBLE; import static com.android.settings.accessibility.AccessibilityUtil.UserShortcutType; import android.app.settings.SettingsEnums; import android.content.Context; import android.content.DialogInterface; import android.graphics.drawable.Drawable; import android.text.Spannable; import android.text.SpannableString; import android.text.SpannableStringBuilder; import android.text.style.ImageSpan; import android.util.ArrayMap; import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.widget.Button; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextSwitcher; import android.widget.TextView; import androidx.annotation.AnimRes; import androidx.annotation.DrawableRes; import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RawRes; import androidx.annotation.VisibleForTesting; import androidx.appcompat.app.AlertDialog; import androidx.core.util.Preconditions; import androidx.core.widget.TextViewCompat; import androidx.viewpager.widget.PagerAdapter; import androidx.viewpager.widget.ViewPager; import com.android.server.accessibility.Flags; import com.android.settings.R; import com.android.settings.core.SubSettingLauncher; import com.android.settingslib.utils.StringUtil; import com.android.settingslib.widget.LottieColorUtils; import com.airbnb.lottie.LottieAnimationView; import com.airbnb.lottie.LottieDrawable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * Utility class for creating the dialog that shows tutorials on how to use the selected * accessibility shortcut types */ public final class AccessibilityShortcutsTutorial { private static final String TAG = "AccessibilityGestureNavigationTutorial"; /** IntDef enum for dialog type. */ @Retention(RetentionPolicy.SOURCE) @IntDef({ DialogType.LAUNCH_SERVICE_BY_ACCESSIBILITY_BUTTON, DialogType.LAUNCH_SERVICE_BY_ACCESSIBILITY_GESTURE, DialogType.GESTURE_NAVIGATION_SETTINGS, }) private @interface DialogType { int LAUNCH_SERVICE_BY_ACCESSIBILITY_BUTTON = 0; int LAUNCH_SERVICE_BY_ACCESSIBILITY_GESTURE = 1; int GESTURE_NAVIGATION_SETTINGS = 2; } private AccessibilityShortcutsTutorial() {} private static final DialogInterface.OnClickListener ON_CLICK_LISTENER = (DialogInterface dialog, int which) -> dialog.dismiss(); /** * Displays a dialog that guides users to use accessibility features with accessibility * gestures under system gesture navigation mode. */ public static AlertDialog showGestureNavigationTutorialDialog(Context context, DialogInterface.OnDismissListener onDismissListener) { final AlertDialog alertDialog = new AlertDialog.Builder(context) .setView(createTutorialDialogContentView(context, DialogType.GESTURE_NAVIGATION_SETTINGS)) .setPositiveButton(R.string.accessibility_tutorial_dialog_button, ON_CLICK_LISTENER) .setOnDismissListener(onDismissListener) .create(); alertDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); alertDialog.setCanceledOnTouchOutside(false); alertDialog.show(); return alertDialog; } static AlertDialog showAccessibilityGestureTutorialDialog(Context context) { return createDialog(context, DialogType.LAUNCH_SERVICE_BY_ACCESSIBILITY_GESTURE); } static AlertDialog createAccessibilityTutorialDialog( @NonNull Context context, int shortcutTypes, @NonNull CharSequence featureName) { return createAccessibilityTutorialDialog( context, shortcutTypes, ON_CLICK_LISTENER, featureName); } static AlertDialog createAccessibilityTutorialDialog( @NonNull Context context, int shortcutTypes, @Nullable DialogInterface.OnClickListener actionButtonListener, @NonNull CharSequence featureName) { final int category = SettingsEnums.SWITCH_SHORTCUT_DIALOG_ACCESSIBILITY_BUTTON_SETTINGS; final DialogInterface.OnClickListener linkButtonListener = (dialog, which) -> new SubSettingLauncher(context) .setDestination(AccessibilityButtonFragment.class.getName()) .setSourceMetricsCategory(category) .launch(); final AlertDialog alertDialog = new AlertDialog.Builder(context) .setPositiveButton(R.string.accessibility_tutorial_dialog_button, actionButtonListener) .setNegativeButton(R.string.accessibility_tutorial_dialog_link_button, linkButtonListener) .create(); final List tutorialPages = createShortcutTutorialPages( context, shortcutTypes, featureName, /* isInSetupWizard= */ false); Preconditions.checkArgument(!tutorialPages.isEmpty(), /* errorMessage= */ "Unexpected tutorial pages size"); final TutorialPageChangeListener.OnPageSelectedCallback callback = index -> updateTutorialNegativeButtonTextAndVisibility( alertDialog, tutorialPages, index); alertDialog.setView(createShortcutNavigationContentView(context, tutorialPages, callback)); // Showing first page won't invoke onPageSelectedCallback. Need to check the first tutorial // page type manually to set correct visibility of the link button. alertDialog.setOnShowListener( dialog -> updateTutorialNegativeButtonTextAndVisibility( alertDialog, tutorialPages, /* selectedPageIndex= */ 0)); return alertDialog; } private static void updateTutorialNegativeButtonTextAndVisibility( AlertDialog dialog, List pages, int selectedPageIndex) { final Button button = dialog.getButton(DialogInterface.BUTTON_NEGATIVE); final int pageType = pages.get(selectedPageIndex).getType(); final int buttonVisibility = pageType == UserShortcutType.SOFTWARE ? VISIBLE : GONE; button.setVisibility(buttonVisibility); if (buttonVisibility == VISIBLE) { final int textResId = AccessibilityUtil.isFloatingMenuEnabled(dialog.getContext()) ? R.string.accessibility_tutorial_dialog_link_button : R.string.accessibility_tutorial_dialog_configure_software_shortcut_type; button.setText(textResId); } } static AlertDialog createAccessibilityTutorialDialogForSetupWizard(Context context, int shortcutTypes, CharSequence featureName) { return createAccessibilityTutorialDialogForSetupWizard(context, shortcutTypes, ON_CLICK_LISTENER, featureName); } static AlertDialog createAccessibilityTutorialDialogForSetupWizard( @NonNull Context context, int shortcutTypes, @Nullable DialogInterface.OnClickListener actionButtonListener, @NonNull CharSequence featureName) { final AlertDialog alertDialog = new AlertDialog.Builder(context) .setPositiveButton(R.string.accessibility_tutorial_dialog_button, actionButtonListener) .create(); final List tutorialPages = createShortcutTutorialPages( context, shortcutTypes, featureName, /* inSetupWizard= */ true); Preconditions.checkArgument(!tutorialPages.isEmpty(), /* errorMessage= */ "Unexpected tutorial pages size"); alertDialog.setView(createShortcutNavigationContentView(context, tutorialPages, null)); return alertDialog; } /** * Gets 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_ACCESSIBILITY_GESTURE: content = inflater.inflate( R.layout.tutorial_dialog_launch_service_by_gesture_navigation, null); setupGestureNavigationTextWithImage(context, content); break; case DialogType.GESTURE_NAVIGATION_SETTINGS: content = inflater.inflate( R.layout.tutorial_dialog_launch_by_gesture_navigation_settings, null); setupGestureNavigationTextWithImage(context, content); break; } return content; } private static void setupGestureNavigationTextWithImage(Context context, View view) { final boolean isTouchExploreEnabled = AccessibilityUtil.isTouchExploreEnabled(context); final ImageView imageView = view.findViewById(R.id.image); final int gestureSettingsImageResId = isTouchExploreEnabled ? R.drawable.accessibility_shortcut_type_gesture_preview_touch_explore_on : R.drawable.accessibility_shortcut_type_gesture_preview; imageView.setImageResource(gestureSettingsImageResId); final TextView textView = view.findViewById(R.id.gesture_tutorial_message); textView.setText(isTouchExploreEnabled ? R.string.accessibility_tutorial_dialog_message_gesture_settings_talkback : R.string.accessibility_tutorial_dialog_message_gesture_settings); } private static AlertDialog createDialog(Context context, int dialogType) { final AlertDialog alertDialog = new AlertDialog.Builder(context) .setView(createTutorialDialogContentView(context, dialogType)) .setPositiveButton(R.string.accessibility_tutorial_dialog_button, ON_CLICK_LISTENER) .create(); alertDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); alertDialog.setCanceledOnTouchOutside(false); alertDialog.show(); return alertDialog; } private static class TutorialPagerAdapter extends PagerAdapter { private final List mTutorialPages; private TutorialPagerAdapter(List tutorialPages) { this.mTutorialPages = tutorialPages; } @NonNull @Override public Object instantiateItem(@NonNull ViewGroup container, int position) { final View itemView = mTutorialPages.get(position).getIllustrationView(); container.addView(itemView); return itemView; } @Override public int getCount() { return mTutorialPages.size(); } @Override public boolean isViewFromObject(@NonNull View view, @NonNull Object o) { return view == o; } @Override public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) { final View itemView = mTutorialPages.get(position).getIllustrationView(); container.removeView(itemView); } } private static ImageView createImageView(Context context, int imageRes) { final ImageView imageView = new ImageView(context); imageView.setImageResource(imageRes); imageView.setAdjustViewBounds(true); return imageView; } private static View createIllustrationView(Context context, @DrawableRes int imageRes) { final View illustrationFrame = inflateAndInitIllustrationFrame(context); final LottieAnimationView lottieView = illustrationFrame.findViewById(R.id.image); lottieView.setImageResource(imageRes); return illustrationFrame; } private static View createIllustrationViewWithImageRawResource(Context context, @RawRes int imageRawRes) { final View illustrationFrame = inflateAndInitIllustrationFrame(context); final LottieAnimationView lottieView = illustrationFrame.findViewById(R.id.image); lottieView.setFailureListener( result -> Log.w(TAG, "Invalid image raw resource id: " + imageRawRes, result)); lottieView.setAnimation(imageRawRes); lottieView.setRepeatCount(LottieDrawable.INFINITE); LottieColorUtils.applyDynamicColors(context, lottieView); lottieView.playAnimation(); return illustrationFrame; } private static View inflateAndInitIllustrationFrame(Context context) { final LayoutInflater inflater = context.getSystemService(LayoutInflater.class); return inflater.inflate(R.layout.accessibility_lottie_animation_view, /* root= */ null); } private static View createShortcutNavigationContentView(Context context, List tutorialPages, TutorialPageChangeListener.OnPageSelectedCallback onPageSelectedCallback) { final LayoutInflater inflater = context.getSystemService(LayoutInflater.class); final View contentView = inflater.inflate( R.layout.accessibility_shortcut_tutorial_dialog, /* root= */ null); final LinearLayout indicatorContainer = contentView.findViewById(R.id.indicator_container); indicatorContainer.setVisibility(tutorialPages.size() > 1 ? VISIBLE : GONE); for (TutorialPage page : tutorialPages) { indicatorContainer.addView(page.getIndicatorIcon()); } tutorialPages.get(/* firstIndex */ 0).getIndicatorIcon().setEnabled(true); final TextSwitcher title = contentView.findViewById(R.id.title); title.setFactory(() -> makeTitleView(context)); title.setText(tutorialPages.get(/* firstIndex */ 0).getTitle()); final TextSwitcher instruction = contentView.findViewById(R.id.instruction); instruction.setFactory(() -> makeInstructionView(context)); instruction.setText(tutorialPages.get(/* firstIndex */ 0).getInstruction()); final ViewPager viewPager = contentView.findViewById(R.id.view_pager); viewPager.setAdapter(new TutorialPagerAdapter(tutorialPages)); viewPager.setContentDescription(context.getString(R.string.accessibility_tutorial_pager, /* firstPage */ 1, tutorialPages.size())); viewPager.setImportantForAccessibility(tutorialPages.size() > 1 ? View.IMPORTANT_FOR_ACCESSIBILITY_YES : View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); TutorialPageChangeListener listener = new TutorialPageChangeListener(context, viewPager, title, instruction, tutorialPages); listener.setOnPageSelectedCallback(onPageSelectedCallback); return contentView; } private static View makeTitleView(Context context) { final TextView textView = new TextView(context); // Sets the text color, size, style, hint color, and highlight color from the specified // TextAppearance resource. TextViewCompat.setTextAppearance(textView, R.style.AccessibilityDialogTitle); textView.setGravity(Gravity.CENTER); return textView; } private static View makeInstructionView(Context context) { final TextView textView = new TextView(context); TextViewCompat.setTextAppearance(textView, R.style.AccessibilityDialogDescription); return textView; } private static TutorialPage createSoftwareTutorialPage(@NonNull Context context) { final int type = UserShortcutType.SOFTWARE; final CharSequence title = getSoftwareTitle(context); final View image = createSoftwareImage(context); final CharSequence instruction = getSoftwareInstruction(context); final ImageView indicatorIcon = createImageView(context, R.drawable.ic_accessibility_page_indicator); indicatorIcon.setEnabled(false); return new TutorialPage(type, title, image, indicatorIcon, instruction); } private static TutorialPage createHardwareTutorialPage(@NonNull Context context) { final int type = UserShortcutType.HARDWARE; final CharSequence title = context.getText(R.string.accessibility_tutorial_dialog_title_volume); final View image = createIllustrationView(context, R.drawable.accessibility_shortcut_type_volume_keys); final ImageView indicatorIcon = createImageView(context, R.drawable.ic_accessibility_page_indicator); final CharSequence instruction = context.getText(R.string.accessibility_tutorial_dialog_message_volume); indicatorIcon.setEnabled(false); return new TutorialPage(type, title, image, indicatorIcon, instruction); } private static TutorialPage createTripleTapTutorialPage(@NonNull Context context) { final int type = UserShortcutType.TRIPLETAP; final CharSequence title = context.getText(R.string.accessibility_tutorial_dialog_title_triple); final View image = createIllustrationViewWithImageRawResource(context, R.raw.accessibility_shortcut_type_tripletap); final CharSequence instruction = context.getString( R.string.accessibility_tutorial_dialog_tripletap_instruction, 3); final ImageView indicatorIcon = createImageView(context, R.drawable.ic_accessibility_page_indicator); indicatorIcon.setEnabled(false); return new TutorialPage(type, title, image, indicatorIcon, instruction); } private static TutorialPage createTwoFingerTripleTapTutorialPage(@NonNull Context context) { final int type = UserShortcutType.TWOFINGER_DOUBLETAP; final int numFingers = 2; final CharSequence title = context.getString( R.string.accessibility_tutorial_dialog_title_two_finger_double, numFingers); final View image = createIllustrationViewWithImageRawResource(context, R.raw.accessibility_shortcut_type_2finger_doubletap); final CharSequence instruction = context.getString( R.string.accessibility_tutorial_dialog_twofinger_doubletap_instruction, numFingers); final ImageView indicatorIcon = createImageView(context, R.drawable.ic_accessibility_page_indicator); indicatorIcon.setEnabled(false); return new TutorialPage(type, title, image, indicatorIcon, instruction); } private static TutorialPage createQuickSettingsTutorialPage( @NonNull Context context, @NonNull CharSequence featureName, boolean inSetupWizard) { final int type = UserShortcutType.QUICK_SETTINGS; final CharSequence title = context.getText(R.string.accessibility_tutorial_dialog_title_quick_setting); final View image = createIllustrationView(context, R.drawable.accessibility_shortcut_type_quick_settings); // Remove the unneeded background, since the main image already includes a background image.findViewById(R.id.image_background).setVisibility(GONE); final int numFingers = AccessibilityUtil.isTouchExploreEnabled(context) ? 2 : 1; Map arguments = new ArrayMap<>(); arguments.put("count", numFingers); arguments.put("featureName", featureName); final CharSequence instruction = StringUtil.getIcuPluralsString(context, arguments, R.string.accessibility_tutorial_dialog_message_quick_setting); final SpannableStringBuilder tutorialText = new SpannableStringBuilder(); if (inSetupWizard) { tutorialText.append(context.getText( R.string.accessibility_tutorial_dialog_shortcut_unavailable_in_suw)) .append("\n\n"); } tutorialText.append(instruction); final ImageView indicatorIcon = createImageView(context, R.drawable.ic_accessibility_page_indicator); indicatorIcon.setEnabled(false); return new TutorialPage(type, title, image, indicatorIcon, tutorialText); } /** * Create the tutorial pages for selected shortcut types in the same order as shown in the * edit shortcut screen. */ @VisibleForTesting static List createShortcutTutorialPages( @NonNull Context context, int shortcutTypes, @NonNull CharSequence featureName, boolean inSetupWizard) { // LINT.IfChange(shortcut_type_ui_order) final List tutorialPages = new ArrayList<>(); if (android.view.accessibility.Flags.a11yQsShortcut()) { if ((shortcutTypes & UserShortcutType.QUICK_SETTINGS) == UserShortcutType.QUICK_SETTINGS) { tutorialPages.add( createQuickSettingsTutorialPage(context, featureName, inSetupWizard)); } } if ((shortcutTypes & UserShortcutType.SOFTWARE) == UserShortcutType.SOFTWARE) { tutorialPages.add(createSoftwareTutorialPage(context)); } if ((shortcutTypes & UserShortcutType.HARDWARE) == UserShortcutType.HARDWARE) { tutorialPages.add(createHardwareTutorialPage(context)); } if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) { if ((shortcutTypes & UserShortcutType.TWOFINGER_DOUBLETAP) == UserShortcutType.TWOFINGER_DOUBLETAP) { tutorialPages.add(createTwoFingerTripleTapTutorialPage(context)); } } if ((shortcutTypes & UserShortcutType.TRIPLETAP) == UserShortcutType.TRIPLETAP) { tutorialPages.add(createTripleTapTutorialPage(context)); } // LINT.ThenChange(/res/xml/accessibility_edit_shortcuts.xml:shortcut_type_ui_order) return tutorialPages; } private static View createSoftwareImage(Context context) { int resId; if (AccessibilityUtil.isFloatingMenuEnabled(context)) { return createIllustrationViewWithImageRawResource( context, R.raw.accessibility_shortcut_type_fab); } else if (AccessibilityUtil.isGestureNavigateEnabled(context)) { resId = AccessibilityUtil.isTouchExploreEnabled(context) ? R.drawable.accessibility_shortcut_type_gesture_touch_explore_on : R.drawable.accessibility_shortcut_type_gesture; } else { resId = R.drawable.accessibility_shortcut_type_navbar; } return createIllustrationView(context, resId); } private static CharSequence getSoftwareTitle(Context context) { int resId; if (AccessibilityUtil.isFloatingMenuEnabled(context)) { resId = R.string.accessibility_tutorial_dialog_title_button; } else if (AccessibilityUtil.isGestureNavigateEnabled(context)) { resId = R.string.accessibility_tutorial_dialog_title_gesture; } else { resId = R.string.accessibility_tutorial_dialog_title_button; } return context.getText(resId); } private static CharSequence getSoftwareInstruction(Context context) { final SpannableStringBuilder sb = new SpannableStringBuilder(); if (AccessibilityUtil.isFloatingMenuEnabled(context)) { final int resId = R.string.accessibility_tutorial_dialog_message_floating_button; sb.append(context.getText(resId)); } else if (AccessibilityUtil.isGestureNavigateEnabled(context)) { final int numFingers = AccessibilityUtil.isTouchExploreEnabled(context) ? 3 : 2; sb.append(StringUtil.getIcuPluralsString( context, numFingers, R.string.accessibility_tutorial_dialog_gesture_shortcut_instruction)); } else { final int resId = R.string.accessibility_tutorial_dialog_message_button; sb.append(getSoftwareInstructionWithIcon(context, context.getText(resId))); } return sb; } private static CharSequence getSoftwareInstructionWithIcon(Context context, CharSequence text) { final String message = text.toString(); final SpannableString spannableInstruction = SpannableString.valueOf(message); final int indexIconStart = message.indexOf("%s"); final int indexIconEnd = indexIconStart + 2; final ImageView iconView = new ImageView(context); iconView.setImageDrawable(context.getDrawable(R.drawable.ic_accessibility_new)); final Drawable icon = iconView.getDrawable().mutate(); final ImageSpan imageSpan = new ImageSpan(icon); imageSpan.setContentDescription(""); icon.setBounds(/* left= */ 0, /* top= */ 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight()); spannableInstruction.setSpan(imageSpan, indexIconStart, indexIconEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); return spannableInstruction; } private static class TutorialPage { private final int mType; private final CharSequence mTitle; private final View mIllustrationView; private final ImageView mIndicatorIcon; private final CharSequence mInstruction; TutorialPage(int type, CharSequence title, View illustrationView, ImageView indicatorIcon, CharSequence instruction) { this.mType = type; this.mTitle = title; this.mIllustrationView = illustrationView; this.mIndicatorIcon = indicatorIcon; this.mInstruction = instruction; setupIllustrationChildViewsGravity(); } public int getType() { return mType; } public CharSequence getTitle() { return mTitle; } public View getIllustrationView() { return mIllustrationView; } public ImageView getIndicatorIcon() { return mIndicatorIcon; } public CharSequence getInstruction() { return mInstruction; } private void setupIllustrationChildViewsGravity() { final View backgroundView = mIllustrationView.findViewById(R.id.image_background); initViewGravity(backgroundView); final View lottieView = mIllustrationView.findViewById(R.id.image); initViewGravity(lottieView); } private void initViewGravity(@NonNull View view) { final FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT); layoutParams.gravity = Gravity.CENTER; view.setLayoutParams(layoutParams); } } private static class TutorialPageChangeListener implements ViewPager.OnPageChangeListener { private int mLastTutorialPagePosition = 0; private final Context mContext; private final TextSwitcher mTitle; private final TextSwitcher mInstruction; private final List mTutorialPages; private final ViewPager mViewPager; private OnPageSelectedCallback mOnPageSelectedCallback; TutorialPageChangeListener(Context context, ViewPager viewPager, ViewGroup title, ViewGroup instruction, List tutorialPages) { this.mContext = context; this.mViewPager = viewPager; this.mTitle = (TextSwitcher) title; this.mInstruction = (TextSwitcher) instruction; this.mTutorialPages = tutorialPages; this.mOnPageSelectedCallback = null; this.mViewPager.addOnPageChangeListener(this); } public void setOnPageSelectedCallback( OnPageSelectedCallback callback) { this.mOnPageSelectedCallback = callback; } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { // Do nothing. } @Override public void onPageSelected(int position) { final boolean isPreviousPosition = mLastTutorialPagePosition > position; @AnimRes final int inAnimationResId = isPreviousPosition ? android.R.anim.slide_in_left : com.android.internal.R.anim.slide_in_right; @AnimRes final int outAnimationResId = isPreviousPosition ? android.R.anim.slide_out_right : com.android.internal.R.anim.slide_out_left; mTitle.setInAnimation(mContext, inAnimationResId); mTitle.setOutAnimation(mContext, outAnimationResId); mTitle.setText(mTutorialPages.get(position).getTitle()); mInstruction.setInAnimation(mContext, inAnimationResId); mInstruction.setOutAnimation(mContext, outAnimationResId); mInstruction.setText(mTutorialPages.get(position).getInstruction()); for (TutorialPage page : mTutorialPages) { page.getIndicatorIcon().setEnabled(false); } mTutorialPages.get(position).getIndicatorIcon().setEnabled(true); mLastTutorialPagePosition = position; final int currentPageNumber = position + 1; mViewPager.setContentDescription( mContext.getString(R.string.accessibility_tutorial_pager, currentPageNumber, mTutorialPages.size())); if (mOnPageSelectedCallback != null) { mOnPageSelectedCallback.onPageSelected(position); } } @Override public void onPageScrollStateChanged(int state) { // Do nothing. } /** The interface that provides a callback method after tutorial page is selected. */ private interface OnPageSelectedCallback { /** The callback method after tutorial page is selected. */ void onPageSelected(int index); } } }