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