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.View.GONE; 20 import static android.view.View.VISIBLE; 21 22 import static com.android.settings.accessibility.AccessibilityUtil.UserShortcutType; 23 24 import android.content.Context; 25 import android.content.DialogInterface; 26 import android.content.res.TypedArray; 27 import android.graphics.drawable.Drawable; 28 import android.text.Spannable; 29 import android.text.SpannableString; 30 import android.text.SpannableStringBuilder; 31 import android.text.style.ImageSpan; 32 import android.util.Log; 33 import android.view.Gravity; 34 import android.view.LayoutInflater; 35 import android.view.View; 36 import android.view.ViewGroup; 37 import android.view.Window; 38 import android.widget.FrameLayout; 39 import android.widget.ImageView; 40 import android.widget.LinearLayout; 41 import android.widget.TextSwitcher; 42 import android.widget.TextView; 43 44 import androidx.annotation.AnimRes; 45 import androidx.annotation.ColorInt; 46 import androidx.annotation.DrawableRes; 47 import androidx.annotation.IntDef; 48 import androidx.annotation.NonNull; 49 import androidx.annotation.RawRes; 50 import androidx.annotation.VisibleForTesting; 51 import androidx.appcompat.app.AlertDialog; 52 import androidx.core.content.ContextCompat; 53 import androidx.core.util.Preconditions; 54 import androidx.core.widget.TextViewCompat; 55 import androidx.viewpager.widget.PagerAdapter; 56 import androidx.viewpager.widget.ViewPager; 57 58 import com.android.settings.R; 59 60 import com.airbnb.lottie.LottieAnimationView; 61 import com.airbnb.lottie.LottieDrawable; 62 63 import java.lang.annotation.Retention; 64 import java.lang.annotation.RetentionPolicy; 65 import java.util.ArrayList; 66 import java.util.List; 67 68 /** 69 * Utility class for creating the dialog that guides users for gesture navigation for 70 * accessibility services. 71 */ 72 public final class AccessibilityGestureNavigationTutorial { 73 private static final String TAG = "AccessibilityGestureNavigationTutorial"; 74 75 /** IntDef enum for dialog type. */ 76 @Retention(RetentionPolicy.SOURCE) 77 @IntDef({ 78 DialogType.LAUNCH_SERVICE_BY_ACCESSIBILITY_BUTTON, 79 DialogType.LAUNCH_SERVICE_BY_ACCESSIBILITY_GESTURE, 80 DialogType.GESTURE_NAVIGATION_SETTINGS, 81 }) 82 83 private @interface DialogType { 84 int LAUNCH_SERVICE_BY_ACCESSIBILITY_BUTTON = 0; 85 int LAUNCH_SERVICE_BY_ACCESSIBILITY_GESTURE = 1; 86 int GESTURE_NAVIGATION_SETTINGS = 2; 87 } 88 AccessibilityGestureNavigationTutorial()89 private AccessibilityGestureNavigationTutorial() {} 90 91 private static final DialogInterface.OnClickListener mOnClickListener = 92 (DialogInterface dialog, int which) -> dialog.dismiss(); 93 94 /** 95 * Displays a dialog that guides users to use accessibility features with accessibility 96 * gestures under system gesture navigation mode. 97 */ showGestureNavigationTutorialDialog(Context context, DialogInterface.OnDismissListener onDismissListener)98 public static void showGestureNavigationTutorialDialog(Context context, 99 DialogInterface.OnDismissListener onDismissListener) { 100 final AlertDialog alertDialog = new AlertDialog.Builder(context) 101 .setView(createTutorialDialogContentView(context, 102 DialogType.GESTURE_NAVIGATION_SETTINGS)) 103 .setNegativeButton(R.string.accessibility_tutorial_dialog_button, mOnClickListener) 104 .setOnDismissListener(onDismissListener) 105 .create(); 106 107 alertDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); 108 alertDialog.setCanceledOnTouchOutside(false); 109 alertDialog.show(); 110 } 111 showAccessibilityButtonTutorialDialog(Context context)112 static AlertDialog showAccessibilityButtonTutorialDialog(Context context) { 113 final AlertDialog alertDialog = createDialog(context, 114 DialogType.LAUNCH_SERVICE_BY_ACCESSIBILITY_BUTTON); 115 116 if (!AccessibilityUtil.isGestureNavigateEnabled(context)) { 117 updateMessageWithIcon(context, alertDialog); 118 } 119 120 return alertDialog; 121 } 122 showAccessibilityGestureTutorialDialog(Context context)123 static AlertDialog showAccessibilityGestureTutorialDialog(Context context) { 124 return createDialog(context, DialogType.LAUNCH_SERVICE_BY_ACCESSIBILITY_GESTURE); 125 } 126 createAccessibilityTutorialDialog(Context context, int shortcutTypes)127 static AlertDialog createAccessibilityTutorialDialog(Context context, int shortcutTypes) { 128 return new AlertDialog.Builder(context) 129 .setView(createShortcutNavigationContentView(context, shortcutTypes)) 130 .setNegativeButton(R.string.accessibility_tutorial_dialog_button, mOnClickListener) 131 .create(); 132 } 133 134 /** 135 * Gets a content View for a dialog to confirm that they want to enable a service. 136 * 137 * @param context A valid context 138 * @param dialogType The type of tutorial dialog 139 * @return A content view suitable for viewing 140 */ createTutorialDialogContentView(Context context, int dialogType)141 private static View createTutorialDialogContentView(Context context, int dialogType) { 142 final LayoutInflater inflater = (LayoutInflater) context.getSystemService( 143 Context.LAYOUT_INFLATER_SERVICE); 144 145 View content = null; 146 147 switch (dialogType) { 148 case DialogType.LAUNCH_SERVICE_BY_ACCESSIBILITY_BUTTON: 149 content = inflater.inflate( 150 R.layout.tutorial_dialog_launch_service_by_accessibility_button, null); 151 break; 152 case DialogType.LAUNCH_SERVICE_BY_ACCESSIBILITY_GESTURE: 153 content = inflater.inflate( 154 R.layout.tutorial_dialog_launch_service_by_gesture_navigation, null); 155 setupGestureNavigationTextWithImage(context, content); 156 break; 157 case DialogType.GESTURE_NAVIGATION_SETTINGS: 158 content = inflater.inflate( 159 R.layout.tutorial_dialog_launch_by_gesture_navigation_settings, null); 160 setupGestureNavigationTextWithImage(context, content); 161 break; 162 } 163 164 return content; 165 } 166 setupGestureNavigationTextWithImage(Context context, View view)167 private static void setupGestureNavigationTextWithImage(Context context, View view) { 168 final boolean isTouchExploreEnabled = AccessibilityUtil.isTouchExploreEnabled(context); 169 170 final ImageView imageView = view.findViewById(R.id.image); 171 final int gestureSettingsImageResId = 172 isTouchExploreEnabled ? R.drawable.illustration_accessibility_gesture_three_finger 173 : R.drawable.illustration_accessibility_gesture_two_finger; 174 imageView.setImageResource(gestureSettingsImageResId); 175 176 final TextView textView = view.findViewById(R.id.gesture_tutorial_message); 177 textView.setText(isTouchExploreEnabled 178 ? R.string.accessibility_tutorial_dialog_message_gesture_settings_talkback 179 : R.string.accessibility_tutorial_dialog_message_gesture_settings); 180 } 181 createDialog(Context context, int dialogType)182 private static AlertDialog createDialog(Context context, int dialogType) { 183 final AlertDialog alertDialog = new AlertDialog.Builder(context) 184 .setView(createTutorialDialogContentView(context, dialogType)) 185 .setNegativeButton(R.string.accessibility_tutorial_dialog_button, mOnClickListener) 186 .create(); 187 188 alertDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); 189 alertDialog.setCanceledOnTouchOutside(false); 190 alertDialog.show(); 191 192 return alertDialog; 193 } 194 updateMessageWithIcon(Context context, AlertDialog alertDialog)195 private static void updateMessageWithIcon(Context context, AlertDialog alertDialog) { 196 final TextView gestureTutorialMessage = alertDialog.findViewById( 197 R.id.button_tutorial_message); 198 199 // Get the textView line height to update [icon] size. Must be called after show() 200 final int lineHeight = gestureTutorialMessage.getLineHeight(); 201 gestureTutorialMessage.setText(getMessageStringWithIcon(context, lineHeight)); 202 } 203 getMessageStringWithIcon(Context context, int lineHeight)204 private static SpannableString getMessageStringWithIcon(Context context, int lineHeight) { 205 final String messageString = context 206 .getString(R.string.accessibility_tutorial_dialog_message_button); 207 final SpannableString spannableMessage = SpannableString.valueOf(messageString); 208 209 // Icon 210 final int indexIconStart = messageString.indexOf("%s"); 211 final int indexIconEnd = indexIconStart + 2; 212 final Drawable icon = context.getDrawable(R.drawable.ic_accessibility_new); 213 final ImageSpan imageSpan = new ImageSpan(icon); 214 imageSpan.setContentDescription(""); 215 icon.setBounds(0, 0, lineHeight, lineHeight); 216 spannableMessage.setSpan( 217 imageSpan, indexIconStart, indexIconEnd, 218 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 219 220 return spannableMessage; 221 } 222 223 /** Returns the color associated with the specified attribute in the context's theme. */ 224 @ColorInt getThemeAttrColor(final Context context, final int attributeColor)225 private static int getThemeAttrColor(final Context context, final int attributeColor) { 226 final int colorResId = getAttrResourceId(context, attributeColor); 227 return ContextCompat.getColor(context, colorResId); 228 } 229 230 /** Returns the identifier of the resolved resource assigned to the given attribute. */ getAttrResourceId(final Context context, final int attributeColor)231 private static int getAttrResourceId(final Context context, final int attributeColor) { 232 final int[] attrs = {attributeColor}; 233 final TypedArray typedArray = context.obtainStyledAttributes(attrs); 234 final int colorResId = typedArray.getResourceId(0, 0); 235 typedArray.recycle(); 236 return colorResId; 237 } 238 239 private static class TutorialPagerAdapter extends PagerAdapter { 240 private final List<TutorialPage> mTutorialPages; TutorialPagerAdapter(List<TutorialPage> tutorialPages)241 private TutorialPagerAdapter(List<TutorialPage> tutorialPages) { 242 this.mTutorialPages = tutorialPages; 243 } 244 245 @NonNull 246 @Override instantiateItem(@onNull ViewGroup container, int position)247 public Object instantiateItem(@NonNull ViewGroup container, int position) { 248 final View itemView = mTutorialPages.get(position).getIllustrationView(); 249 container.addView(itemView); 250 return itemView; 251 } 252 253 @Override getCount()254 public int getCount() { 255 return mTutorialPages.size(); 256 } 257 258 @Override isViewFromObject(@onNull View view, @NonNull Object o)259 public boolean isViewFromObject(@NonNull View view, @NonNull Object o) { 260 return view == o; 261 } 262 263 @Override destroyItem(@onNull ViewGroup container, int position, @NonNull Object object)264 public void destroyItem(@NonNull ViewGroup container, int position, 265 @NonNull Object object) { 266 final View itemView = mTutorialPages.get(position).getIllustrationView(); 267 container.removeView(itemView); 268 } 269 } 270 createImageView(Context context, int imageRes)271 private static ImageView createImageView(Context context, int imageRes) { 272 final ImageView imageView = new ImageView(context); 273 imageView.setImageResource(imageRes); 274 imageView.setAdjustViewBounds(true); 275 276 return imageView; 277 } 278 createIllustrationView(Context context, @DrawableRes int imageRes)279 private static View createIllustrationView(Context context, @DrawableRes int imageRes) { 280 final View illustrationFrame = inflateAndInitIllustrationFrame(context); 281 final LottieAnimationView lottieView = illustrationFrame.findViewById(R.id.image); 282 lottieView.setImageResource(imageRes); 283 284 return illustrationFrame; 285 } 286 createIllustrationViewWithImageRawResource(Context context, @RawRes int imageRawRes)287 private static View createIllustrationViewWithImageRawResource(Context context, 288 @RawRes int imageRawRes) { 289 final View illustrationFrame = inflateAndInitIllustrationFrame(context); 290 final LottieAnimationView lottieView = illustrationFrame.findViewById(R.id.image); 291 lottieView.setFailureListener( 292 result -> Log.w(TAG, "Invalid image raw resource id: " + imageRawRes, 293 result)); 294 lottieView.setAnimation(imageRawRes); 295 lottieView.setRepeatCount(LottieDrawable.INFINITE); 296 lottieView.playAnimation(); 297 298 return illustrationFrame; 299 } 300 inflateAndInitIllustrationFrame(Context context)301 private static View inflateAndInitIllustrationFrame(Context context) { 302 final LayoutInflater inflater = context.getSystemService(LayoutInflater.class); 303 304 return inflater.inflate(R.layout.accessibility_lottie_animation_view, /* root= */ null); 305 } 306 createShortcutNavigationContentView(Context context, int shortcutTypes)307 private static View createShortcutNavigationContentView(Context context, int shortcutTypes) { 308 final LayoutInflater inflater = context.getSystemService(LayoutInflater.class); 309 final View contentView = inflater.inflate( 310 R.layout.accessibility_shortcut_tutorial_dialog, /* root= */ null); 311 final List<TutorialPage> tutorialPages = 312 createShortcutTutorialPages(context, shortcutTypes); 313 Preconditions.checkArgument(!tutorialPages.isEmpty(), 314 /* errorMessage= */ "Unexpected tutorial pages size"); 315 316 final LinearLayout indicatorContainer = contentView.findViewById(R.id.indicator_container); 317 indicatorContainer.setVisibility(tutorialPages.size() > 1 ? VISIBLE : GONE); 318 for (TutorialPage page : tutorialPages) { 319 indicatorContainer.addView(page.getIndicatorIcon()); 320 } 321 tutorialPages.get(/* firstIndex */ 0).getIndicatorIcon().setEnabled(true); 322 323 final TextSwitcher title = contentView.findViewById(R.id.title); 324 title.setFactory(() -> makeTitleView(context)); 325 title.setText(tutorialPages.get(/* firstIndex */ 0).getTitle()); 326 327 final TextSwitcher instruction = contentView.findViewById(R.id.instruction); 328 instruction.setFactory(() -> makeInstructionView(context)); 329 instruction.setText(tutorialPages.get(/* firstIndex */ 0).getInstruction()); 330 331 final ViewPager viewPager = contentView.findViewById(R.id.view_pager); 332 viewPager.setAdapter(new TutorialPagerAdapter(tutorialPages)); 333 viewPager.setContentDescription(context.getString(R.string.accessibility_tutorial_pager, 334 /* firstPage */ 1, tutorialPages.size())); 335 viewPager.setImportantForAccessibility(tutorialPages.size() > 1 336 ? View.IMPORTANT_FOR_ACCESSIBILITY_YES 337 : View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); 338 viewPager.addOnPageChangeListener( 339 new TutorialPageChangeListener(context, viewPager, title, instruction, 340 tutorialPages)); 341 342 return contentView; 343 } 344 makeTitleView(Context context)345 private static View makeTitleView(Context context) { 346 final TextView textView = new TextView(context); 347 // Sets the text color, size, style, hint color, and highlight color from the specified 348 // TextAppearance resource. 349 TextViewCompat.setTextAppearance(textView, R.style.AccessibilityDialogTitle); 350 textView.setGravity(Gravity.CENTER); 351 return textView; 352 } 353 makeInstructionView(Context context)354 private static View makeInstructionView(Context context) { 355 final TextView textView = new TextView(context); 356 TextViewCompat.setTextAppearance(textView, R.style.AccessibilityDialogDescription); 357 return textView; 358 } 359 createSoftwareTutorialPage(@onNull Context context)360 private static TutorialPage createSoftwareTutorialPage(@NonNull Context context) { 361 final CharSequence title = getSoftwareTitle(context); 362 final View image = createSoftwareImage(context); 363 final CharSequence instruction = getSoftwareInstruction(context); 364 final ImageView indicatorIcon = 365 createImageView(context, R.drawable.ic_accessibility_page_indicator); 366 indicatorIcon.setEnabled(false); 367 368 return new TutorialPage(title, image, indicatorIcon, instruction); 369 } 370 createHardwareTutorialPage(@onNull Context context)371 private static TutorialPage createHardwareTutorialPage(@NonNull Context context) { 372 final CharSequence title = 373 context.getText(R.string.accessibility_tutorial_dialog_title_volume); 374 final View image = 375 createIllustrationView(context, R.drawable.accessibility_shortcut_type_hardware); 376 final ImageView indicatorIcon = 377 createImageView(context, R.drawable.ic_accessibility_page_indicator); 378 final CharSequence instruction = 379 context.getText(R.string.accessibility_tutorial_dialog_message_volume); 380 indicatorIcon.setEnabled(false); 381 382 return new TutorialPage(title, image, indicatorIcon, instruction); 383 } 384 createTripleTapTutorialPage(@onNull Context context)385 private static TutorialPage createTripleTapTutorialPage(@NonNull Context context) { 386 final CharSequence title = 387 context.getText(R.string.accessibility_tutorial_dialog_title_triple); 388 final View image = 389 createIllustrationViewWithImageRawResource(context, 390 R.raw.accessibility_shortcut_type_triple_tap); 391 final CharSequence instruction = 392 context.getText(R.string.accessibility_tutorial_dialog_message_triple); 393 final ImageView indicatorIcon = 394 createImageView(context, R.drawable.ic_accessibility_page_indicator); 395 indicatorIcon.setEnabled(false); 396 397 return new TutorialPage(title, image, indicatorIcon, instruction); 398 } 399 400 @VisibleForTesting createShortcutTutorialPages(@onNull Context context, int shortcutTypes)401 static List<TutorialPage> createShortcutTutorialPages(@NonNull Context context, 402 int shortcutTypes) { 403 final List<TutorialPage> tutorialPages = new ArrayList<>(); 404 if ((shortcutTypes & UserShortcutType.SOFTWARE) == UserShortcutType.SOFTWARE) { 405 tutorialPages.add(createSoftwareTutorialPage(context)); 406 } 407 408 if ((shortcutTypes & UserShortcutType.HARDWARE) == UserShortcutType.HARDWARE) { 409 tutorialPages.add(createHardwareTutorialPage(context)); 410 } 411 412 if ((shortcutTypes & UserShortcutType.TRIPLETAP) == UserShortcutType.TRIPLETAP) { 413 tutorialPages.add(createTripleTapTutorialPage(context)); 414 } 415 416 return tutorialPages; 417 } 418 createSoftwareImage(Context context)419 private static View createSoftwareImage(Context context) { 420 int resId; 421 if (AccessibilityUtil.isFloatingMenuEnabled(context)) { 422 resId = R.drawable.accessibility_shortcut_type_software_floating; 423 } else if (AccessibilityUtil.isGestureNavigateEnabled(context)) { 424 resId = AccessibilityUtil.isTouchExploreEnabled(context) 425 ? R.drawable.accessibility_shortcut_type_software_gesture_talkback 426 : R.drawable.accessibility_shortcut_type_software_gesture; 427 } else { 428 resId = R.drawable.accessibility_shortcut_type_software; 429 } 430 return createIllustrationView(context, resId); 431 } 432 getSoftwareTitle(Context context)433 private static CharSequence getSoftwareTitle(Context context) { 434 int resId; 435 if (AccessibilityUtil.isFloatingMenuEnabled(context)) { 436 resId = R.string.accessibility_tutorial_dialog_title_button; 437 } else if (AccessibilityUtil.isGestureNavigateEnabled(context)) { 438 resId = R.string.accessibility_tutorial_dialog_title_gesture; 439 } else { 440 resId = R.string.accessibility_tutorial_dialog_title_button; 441 } 442 return context.getText(resId); 443 } 444 getSoftwareInstruction(Context context)445 private static CharSequence getSoftwareInstruction(Context context) { 446 final SpannableStringBuilder sb = new SpannableStringBuilder(); 447 if (AccessibilityUtil.isFloatingMenuEnabled(context)) { 448 final int resId = R.string.accessibility_tutorial_dialog_message_floating_button; 449 sb.append(context.getText(resId)); 450 } else if (AccessibilityUtil.isGestureNavigateEnabled(context)) { 451 final int resId = AccessibilityUtil.isTouchExploreEnabled(context) 452 ? R.string.accessibility_tutorial_dialog_message_gesture_talkback 453 : R.string.accessibility_tutorial_dialog_message_gesture; 454 sb.append(context.getText(resId)); 455 } else { 456 final int resId = R.string.accessibility_tutorial_dialog_message_button; 457 sb.append(getSoftwareInstructionWithIcon(context, context.getText(resId))); 458 } 459 return sb; 460 } 461 getSoftwareInstructionWithIcon(Context context, CharSequence text)462 private static CharSequence getSoftwareInstructionWithIcon(Context context, CharSequence text) { 463 final String message = text.toString(); 464 final SpannableString spannableInstruction = SpannableString.valueOf(message); 465 final int indexIconStart = message.indexOf("%s"); 466 final int indexIconEnd = indexIconStart + 2; 467 final ImageView iconView = new ImageView(context); 468 iconView.setImageDrawable(context.getDrawable(R.drawable.ic_accessibility_new)); 469 final Drawable icon = iconView.getDrawable().mutate(); 470 final ImageSpan imageSpan = new ImageSpan(icon); 471 imageSpan.setContentDescription(""); 472 icon.setBounds(/* left= */ 0, /* top= */ 0, 473 icon.getIntrinsicWidth(), icon.getIntrinsicHeight()); 474 spannableInstruction.setSpan(imageSpan, indexIconStart, indexIconEnd, 475 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 476 477 return spannableInstruction; 478 } 479 480 private static class TutorialPage { 481 private final CharSequence mTitle; 482 private final View mIllustrationView; 483 private final ImageView mIndicatorIcon; 484 private final CharSequence mInstruction; 485 TutorialPage(CharSequence title, View illustrationView, ImageView indicatorIcon, CharSequence instruction)486 TutorialPage(CharSequence title, View illustrationView, ImageView indicatorIcon, 487 CharSequence instruction) { 488 this.mTitle = title; 489 this.mIllustrationView = illustrationView; 490 this.mIndicatorIcon = indicatorIcon; 491 this.mInstruction = instruction; 492 493 setupIllustrationChildViewsGravity(); 494 } 495 getTitle()496 public CharSequence getTitle() { 497 return mTitle; 498 } 499 getIllustrationView()500 public View getIllustrationView() { 501 return mIllustrationView; 502 } 503 getIndicatorIcon()504 public ImageView getIndicatorIcon() { 505 return mIndicatorIcon; 506 } 507 getInstruction()508 public CharSequence getInstruction() { 509 return mInstruction; 510 } 511 setupIllustrationChildViewsGravity()512 private void setupIllustrationChildViewsGravity() { 513 final View backgroundView = mIllustrationView.findViewById(R.id.image_background); 514 initViewGravity(backgroundView); 515 516 final View lottieView = mIllustrationView.findViewById(R.id.image); 517 initViewGravity(lottieView); 518 } 519 initViewGravity(@onNull View view)520 private void initViewGravity(@NonNull View view) { 521 final FrameLayout.LayoutParams layoutParams = 522 new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, 523 FrameLayout.LayoutParams.WRAP_CONTENT); 524 layoutParams.gravity = Gravity.CENTER; 525 526 view.setLayoutParams(layoutParams); 527 } 528 } 529 530 private static class TutorialPageChangeListener implements ViewPager.OnPageChangeListener { 531 private int mLastTutorialPagePosition = 0; 532 private final Context mContext; 533 private final TextSwitcher mTitle; 534 private final TextSwitcher mInstruction; 535 private final List<TutorialPage> mTutorialPages; 536 private final ViewPager mViewPager; 537 TutorialPageChangeListener(Context context, ViewPager viewPager, ViewGroup title, ViewGroup instruction, List<TutorialPage> tutorialPages)538 TutorialPageChangeListener(Context context, ViewPager viewPager, ViewGroup title, 539 ViewGroup instruction, List<TutorialPage> tutorialPages) { 540 this.mContext = context; 541 this.mViewPager = viewPager; 542 this.mTitle = (TextSwitcher) title; 543 this.mInstruction = (TextSwitcher) instruction; 544 this.mTutorialPages = tutorialPages; 545 } 546 547 @Override onPageScrolled(int position, float positionOffset, int positionOffsetPixels)548 public void onPageScrolled(int position, float positionOffset, 549 int positionOffsetPixels) { 550 // Do nothing. 551 } 552 553 @Override onPageSelected(int position)554 public void onPageSelected(int position) { 555 final boolean isPreviousPosition = 556 mLastTutorialPagePosition > position; 557 @AnimRes 558 final int inAnimationResId = isPreviousPosition 559 ? android.R.anim.slide_in_left 560 : com.android.internal.R.anim.slide_in_right; 561 562 @AnimRes 563 final int outAnimationResId = isPreviousPosition 564 ? android.R.anim.slide_out_right 565 : com.android.internal.R.anim.slide_out_left; 566 567 mTitle.setInAnimation(mContext, inAnimationResId); 568 mTitle.setOutAnimation(mContext, outAnimationResId); 569 mTitle.setText(mTutorialPages.get(position).getTitle()); 570 571 mInstruction.setInAnimation(mContext, inAnimationResId); 572 mInstruction.setOutAnimation(mContext, outAnimationResId); 573 mInstruction.setText(mTutorialPages.get(position).getInstruction()); 574 575 for (TutorialPage page : mTutorialPages) { 576 page.getIndicatorIcon().setEnabled(false); 577 } 578 mTutorialPages.get(position).getIndicatorIcon().setEnabled(true); 579 mLastTutorialPagePosition = position; 580 581 final int currentPageNumber = position + 1; 582 mViewPager.setContentDescription( 583 mContext.getString(R.string.accessibility_tutorial_pager, 584 currentPageNumber, mTutorialPages.size())); 585 } 586 587 @Override onPageScrollStateChanged(int state)588 public void onPageScrollStateChanged(int state) { 589 // Do nothing. 590 } 591 } 592 } 593