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