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 com.android.settings.accessibility.ItemInfoArrayAdapter.ItemInfo; 20 21 import android.app.Dialog; 22 import android.app.settings.SettingsEnums; 23 import android.content.Context; 24 import android.content.DialogInterface; 25 import android.content.res.TypedArray; 26 import android.graphics.drawable.Drawable; 27 import android.icu.text.MessageFormat; 28 import android.text.Spannable; 29 import android.text.SpannableString; 30 import android.text.SpannableStringBuilder; 31 import android.text.TextUtils; 32 import android.text.method.LinkMovementMethod; 33 import android.text.style.ImageSpan; 34 import android.util.Log; 35 import android.view.LayoutInflater; 36 import android.view.View; 37 import android.widget.AbsListView; 38 import android.widget.AdapterView; 39 import android.widget.CheckBox; 40 import android.widget.ImageView; 41 import android.widget.LinearLayout; 42 import android.widget.ListView; 43 import android.widget.ScrollView; 44 import android.widget.TextView; 45 46 import androidx.annotation.ColorInt; 47 import androidx.annotation.DrawableRes; 48 import androidx.annotation.IntDef; 49 import androidx.annotation.NonNull; 50 import androidx.annotation.Nullable; 51 import androidx.annotation.RawRes; 52 import androidx.appcompat.app.AlertDialog; 53 import androidx.core.content.ContextCompat; 54 55 import com.android.server.accessibility.Flags; 56 import com.android.settings.R; 57 import com.android.settings.core.SubSettingLauncher; 58 import com.android.settings.utils.AnnotationSpan; 59 import com.android.settingslib.widget.LottieColorUtils; 60 61 import com.airbnb.lottie.LottieAnimationView; 62 import com.airbnb.lottie.LottieDrawable; 63 64 import java.lang.annotation.Retention; 65 import java.lang.annotation.RetentionPolicy; 66 import java.util.List; 67 68 69 /** 70 * Utility class for creating the edit dialog. 71 */ 72 public class AccessibilityDialogUtils { 73 private static final String TAG = "AccessibilityDialogUtils"; 74 75 /** Denotes the dialog emuns for show dialog. */ 76 @Retention(RetentionPolicy.SOURCE) 77 public @interface DialogEnums { 78 79 /** OPEN: Settings > Accessibility > Any toggle service > Shortcut > Settings. */ 80 int EDIT_SHORTCUT = 1; 81 82 /** OPEN: Settings > Accessibility > Magnification > Shortcut > Settings. */ 83 int MAGNIFICATION_EDIT_SHORTCUT = 1001; 84 85 /** 86 * OPEN: Settings > Accessibility > Downloaded toggle service > Toggle use service to 87 * enable service. 88 */ 89 int ENABLE_WARNING_FROM_TOGGLE = 1002; 90 91 /** OPEN: Settings > Accessibility > Downloaded toggle service > Shortcut checkbox. */ 92 int ENABLE_WARNING_FROM_SHORTCUT = 1003; 93 94 /** 95 * OPEN: Settings > Accessibility > Downloaded toggle service > Shortcut checkbox 96 * toggle. 97 */ 98 int ENABLE_WARNING_FROM_SHORTCUT_TOGGLE = 1004; 99 100 /** 101 * OPEN: Settings > Accessibility > Downloaded toggle service > Toggle use service to 102 * disable service. 103 */ 104 int DISABLE_WARNING_FROM_TOGGLE = 1005; 105 106 /** 107 * OPEN: Settings > Accessibility > Magnification > Toggle user service in button 108 * navigation. 109 */ 110 int ACCESSIBILITY_BUTTON_TUTORIAL = 1006; 111 112 /** 113 * OPEN: Settings > Accessibility > Magnification > Toggle user service in gesture 114 * navigation. 115 */ 116 int GESTURE_NAVIGATION_TUTORIAL = 1007; 117 118 /** 119 * OPEN: Settings > Accessibility > Downloaded toggle service > Toggle user service > Show 120 * launch tutorial. 121 */ 122 int LAUNCH_ACCESSIBILITY_TUTORIAL = 1008; 123 124 /** 125 * OPEN: Settings > Accessibility > Display size and text > Click 'Reset settings' button. 126 */ 127 int DIALOG_RESET_SETTINGS = 1009; 128 } 129 130 /** 131 * IntDef enum for dialog type that indicates different dialog for user to choose the shortcut 132 * type. 133 */ 134 @Retention(RetentionPolicy.SOURCE) 135 @IntDef({ 136 DialogType.EDIT_SHORTCUT_GENERIC, 137 DialogType.EDIT_SHORTCUT_GENERIC_SUW, 138 DialogType.EDIT_SHORTCUT_MAGNIFICATION, 139 DialogType.EDIT_SHORTCUT_MAGNIFICATION_SUW, 140 }) 141 142 public @interface DialogType { 143 int EDIT_SHORTCUT_GENERIC = 0; 144 int EDIT_SHORTCUT_GENERIC_SUW = 1; 145 int EDIT_SHORTCUT_MAGNIFICATION = 2; 146 int EDIT_SHORTCUT_MAGNIFICATION_SUW = 3; 147 } 148 149 /** 150 * Method to show the edit shortcut dialog. 151 * 152 * @param context A valid context 153 * @param dialogType The type of edit shortcut dialog 154 * @param dialogTitle The title of edit shortcut dialog 155 * @param listener The listener to determine the action of edit shortcut dialog 156 * @return A edit shortcut dialog for showing 157 */ showEditShortcutDialog(Context context, int dialogType, CharSequence dialogTitle, DialogInterface.OnClickListener listener)158 public static AlertDialog showEditShortcutDialog(Context context, int dialogType, 159 CharSequence dialogTitle, DialogInterface.OnClickListener listener) { 160 final AlertDialog alertDialog = createDialog(context, dialogType, dialogTitle, listener); 161 alertDialog.show(); 162 setScrollIndicators(alertDialog); 163 return alertDialog; 164 } 165 166 /** 167 * Updates the shortcut content in edit shortcut dialog. 168 * 169 * @param context A valid context 170 * @param editShortcutDialog Need to be a type of edit shortcut dialog 171 * @return True if the update is successful 172 */ updateShortcutInDialog(Context context, Dialog editShortcutDialog)173 public static boolean updateShortcutInDialog(Context context, 174 Dialog editShortcutDialog) { 175 final View container = editShortcutDialog.findViewById(R.id.container_layout); 176 if (container != null) { 177 initSoftwareShortcut(context, container); 178 initHardwareShortcut(context, container); 179 return true; 180 } 181 return false; 182 } 183 createDialog(Context context, int dialogType, CharSequence dialogTitle, DialogInterface.OnClickListener listener)184 private static AlertDialog createDialog(Context context, int dialogType, 185 CharSequence dialogTitle, DialogInterface.OnClickListener listener) { 186 187 final AlertDialog alertDialog = new AlertDialog.Builder(context) 188 .setView(createEditDialogContentView(context, dialogType)) 189 .setTitle(dialogTitle) 190 .setPositiveButton(R.string.save, listener) 191 .setNegativeButton(R.string.cancel, 192 (DialogInterface dialog, int which) -> dialog.dismiss()) 193 .create(); 194 195 return alertDialog; 196 } 197 198 /** 199 * Sets the scroll indicators for dialog view. The indicators appears while content view is 200 * out of vision for vertical scrolling. 201 */ setScrollIndicators(AlertDialog dialog)202 private static void setScrollIndicators(AlertDialog dialog) { 203 final ScrollView scrollView = dialog.findViewById(R.id.container_layout); 204 setScrollIndicators(scrollView); 205 } 206 207 /** 208 * Sets the scroll indicators for dialog view. The indicators appear while content view is 209 * out of vision for vertical scrolling. 210 * 211 * @param view The view contains customized dialog content. Usually it is {@link ScrollView} or 212 * {@link AbsListView} 213 */ setScrollIndicators(@onNull View view)214 private static void setScrollIndicators(@NonNull View view) { 215 view.setScrollIndicators( 216 View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM, 217 View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM); 218 } 219 220 /** 221 * Get a content View for the edit shortcut dialog. 222 * 223 * @param context A valid context 224 * @param dialogType The type of edit shortcut dialog 225 * @return A content view suitable for viewing 226 */ createEditDialogContentView(Context context, int dialogType)227 private static View createEditDialogContentView(Context context, int dialogType) { 228 final LayoutInflater inflater = (LayoutInflater) context.getSystemService( 229 Context.LAYOUT_INFLATER_SERVICE); 230 231 View contentView = null; 232 233 switch (dialogType) { 234 case DialogType.EDIT_SHORTCUT_GENERIC: 235 contentView = inflater.inflate( 236 R.layout.accessibility_edit_shortcut, null); 237 initSoftwareShortcut(context, contentView); 238 initHardwareShortcut(context, contentView); 239 break; 240 case DialogType.EDIT_SHORTCUT_GENERIC_SUW: 241 contentView = inflater.inflate( 242 R.layout.accessibility_edit_shortcut, null); 243 initSoftwareShortcutForSUW(context, contentView); 244 initHardwareShortcut(context, contentView); 245 break; 246 case DialogType.EDIT_SHORTCUT_MAGNIFICATION: 247 contentView = inflater.inflate( 248 R.layout.accessibility_edit_shortcut_magnification, null); 249 initSoftwareShortcut(context, contentView); 250 initHardwareShortcut(context, contentView); 251 if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) { 252 initTwoFingerDoubleTapMagnificationShortcut(context, contentView); 253 } 254 initMagnifyShortcut(context, contentView); 255 initAdvancedWidget(contentView); 256 break; 257 case DialogType.EDIT_SHORTCUT_MAGNIFICATION_SUW: 258 contentView = inflater.inflate( 259 R.layout.accessibility_edit_shortcut_magnification, null); 260 initSoftwareShortcutForSUW(context, contentView); 261 initHardwareShortcut(context, contentView); 262 if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) { 263 initTwoFingerDoubleTapMagnificationShortcut(context, contentView); 264 } 265 initMagnifyShortcut(context, contentView); 266 initAdvancedWidget(contentView); 267 break; 268 default: 269 throw new IllegalArgumentException(); 270 } 271 272 return contentView; 273 } 274 setupShortcutWidget(View view, CharSequence titleText, CharSequence summaryText, @DrawableRes int imageResId)275 private static void setupShortcutWidget(View view, CharSequence titleText, 276 CharSequence summaryText, @DrawableRes int imageResId) { 277 setupShortcutWidgetWithTitleAndSummary(view, titleText, summaryText); 278 setupShortcutWidgetWithImageResource(view, imageResId); 279 } 280 setupShortcutWidgetWithImageRawResource(Context context, View view, CharSequence titleText, CharSequence summaryText, @RawRes int imageRawResId)281 private static void setupShortcutWidgetWithImageRawResource(Context context, 282 View view, CharSequence titleText, 283 CharSequence summaryText, @RawRes int imageRawResId) { 284 setupShortcutWidgetWithTitleAndSummary(view, titleText, summaryText); 285 setupShortcutWidgetWithImageRawResource(context, view, imageRawResId); 286 } 287 setupShortcutWidgetWithTitleAndSummary(View view, CharSequence titleText, CharSequence summaryText)288 private static void setupShortcutWidgetWithTitleAndSummary(View view, CharSequence titleText, 289 CharSequence summaryText) { 290 final CheckBox checkBox = view.findViewById(R.id.checkbox); 291 checkBox.setText(titleText); 292 293 final TextView summary = view.findViewById(R.id.summary); 294 if (TextUtils.isEmpty(summaryText)) { 295 summary.setVisibility(View.GONE); 296 } else { 297 summary.setText(summaryText); 298 summary.setMovementMethod(LinkMovementMethod.getInstance()); 299 summary.setFocusable(false); 300 } 301 } 302 setupShortcutWidgetWithImageResource(View view, @DrawableRes int imageResId)303 private static void setupShortcutWidgetWithImageResource(View view, 304 @DrawableRes int imageResId) { 305 final ImageView imageView = view.findViewById(R.id.image); 306 imageView.setImageResource(imageResId); 307 } 308 setupShortcutWidgetWithImageRawResource(Context context, View view, @RawRes int imageRawResId)309 private static void setupShortcutWidgetWithImageRawResource(Context context, View view, 310 @RawRes int imageRawResId) { 311 final LottieAnimationView lottieView = view.findViewById(R.id.image); 312 lottieView.setFailureListener( 313 result -> Log.w(TAG, "Invalid image raw resource id: " + imageRawResId, 314 result)); 315 lottieView.setAnimation(imageRawResId); 316 lottieView.setRepeatCount(LottieDrawable.INFINITE); 317 LottieColorUtils.applyDynamicColors(context, lottieView); 318 lottieView.playAnimation(); 319 } 320 initSoftwareShortcutForSUW(Context context, View view)321 private static void initSoftwareShortcutForSUW(Context context, View view) { 322 final View dialogView = view.findViewById(R.id.software_shortcut); 323 final CharSequence title = context.getText( 324 R.string.accessibility_shortcut_edit_dialog_title_software); 325 final TextView summary = dialogView.findViewById(R.id.summary); 326 final int lineHeight = summary.getLineHeight(); 327 328 setupShortcutWidget(dialogView, title, 329 retrieveSoftwareShortcutSummaryForSUW(context, lineHeight), 330 retrieveSoftwareShortcutImageResId(context)); 331 } 332 initSoftwareShortcut(Context context, View view)333 private static void initSoftwareShortcut(Context context, View view) { 334 final View dialogView = view.findViewById(R.id.software_shortcut); 335 final TextView summary = dialogView.findViewById(R.id.summary); 336 final int lineHeight = summary.getLineHeight(); 337 338 setupShortcutWidget(dialogView, 339 retrieveTitle(context), 340 retrieveSoftwareShortcutSummary(context, lineHeight), 341 retrieveSoftwareShortcutImageResId(context)); 342 } 343 initHardwareShortcut(Context context, View view)344 private static void initHardwareShortcut(Context context, View view) { 345 final View dialogView = view.findViewById(R.id.hardware_shortcut); 346 final CharSequence title = context.getText( 347 R.string.accessibility_shortcut_edit_dialog_title_hardware); 348 final CharSequence summary = context.getText( 349 R.string.accessibility_shortcut_edit_dialog_summary_hardware); 350 setupShortcutWidget(dialogView, title, summary, 351 R.drawable.a11y_shortcut_type_hardware); 352 } 353 initMagnifyShortcut(Context context, View view)354 private static void initMagnifyShortcut(Context context, View view) { 355 final View dialogView = view.findViewById(R.id.triple_tap_shortcut); 356 final CharSequence title = context.getText( 357 R.string.accessibility_shortcut_edit_dialog_title_triple_tap); 358 String summary = context.getString( 359 R.string.accessibility_shortcut_edit_dialog_summary_triple_tap); 360 // Format the number '3' in the summary. 361 final Object[] arguments = {3}; 362 summary = MessageFormat.format(summary, arguments); 363 364 setupShortcutWidgetWithImageRawResource(context, dialogView, title, summary, 365 R.raw.a11y_shortcut_type_triple_tap); 366 } 367 initTwoFingerDoubleTapMagnificationShortcut(Context context, View view)368 private static void initTwoFingerDoubleTapMagnificationShortcut(Context context, View view) { 369 // TODO(b/306153204): Update shortcut string and image when UX provides them 370 final View dialogView = view.findViewById(R.id.two_finger_triple_tap_shortcut); 371 final CharSequence title = context.getText( 372 R.string.accessibility_shortcut_edit_dialog_title_two_finger_double_tap); 373 String summary = context.getString( 374 R.string.accessibility_shortcut_edit_dialog_summary_two_finger_double_tap); 375 // Format the number '2' in the summary. 376 final Object[] arguments = {2}; 377 summary = MessageFormat.format(summary, arguments); 378 379 setupShortcutWidgetWithImageRawResource(context, dialogView, title, summary, 380 R.raw.a11y_shortcut_type_triple_tap); 381 382 dialogView.setVisibility(View.VISIBLE); 383 } 384 initAdvancedWidget(View view)385 private static void initAdvancedWidget(View view) { 386 final LinearLayout advanced = view.findViewById(R.id.advanced_shortcut); 387 final View tripleTap = view.findViewById(R.id.triple_tap_shortcut); 388 advanced.setOnClickListener((View v) -> { 389 advanced.setVisibility(View.GONE); 390 tripleTap.setVisibility(View.VISIBLE); 391 }); 392 } 393 retrieveSoftwareShortcutSummaryForSUW(Context context, int lineHeight)394 private static CharSequence retrieveSoftwareShortcutSummaryForSUW(Context context, 395 int lineHeight) { 396 final SpannableStringBuilder sb = new SpannableStringBuilder(); 397 if (!AccessibilityUtil.isFloatingMenuEnabled(context)) { 398 sb.append(getSummaryStringWithIcon(context, lineHeight)); 399 } 400 return sb; 401 } 402 retrieveTitle(Context context)403 private static CharSequence retrieveTitle(Context context) { 404 int resId; 405 if (AccessibilityUtil.isFloatingMenuEnabled(context)) { 406 resId = R.string.accessibility_shortcut_edit_dialog_title_software; 407 } else if (AccessibilityUtil.isGestureNavigateEnabled(context)) { 408 resId = R.string.accessibility_shortcut_edit_dialog_title_software_by_gesture; 409 } else { 410 resId = R.string.accessibility_shortcut_edit_dialog_title_software; 411 } 412 return context.getText(resId); 413 } 414 retrieveSoftwareShortcutSummary(Context context, int lineHeight)415 private static CharSequence retrieveSoftwareShortcutSummary(Context context, int lineHeight) { 416 final SpannableStringBuilder sb = new SpannableStringBuilder(); 417 if (AccessibilityUtil.isFloatingMenuEnabled(context)) { 418 sb.append(getCustomizeAccessibilityButtonLink(context)); 419 } else if (AccessibilityUtil.isGestureNavigateEnabled(context)) { 420 final int resId = AccessibilityUtil.isTouchExploreEnabled(context) 421 ? R.string.accessibility_shortcut_edit_dialog_summary_software_gesture_talkback 422 : R.string.accessibility_shortcut_edit_dialog_summary_software_gesture; 423 sb.append(context.getText(resId)); 424 sb.append("\n\n"); 425 sb.append(getCustomizeAccessibilityButtonLink(context)); 426 } else { 427 sb.append(getSummaryStringWithIcon(context, lineHeight)); 428 sb.append("\n\n"); 429 sb.append(getCustomizeAccessibilityButtonLink(context)); 430 } 431 return sb; 432 } 433 retrieveSoftwareShortcutImageResId(Context context)434 private static int retrieveSoftwareShortcutImageResId(Context context) { 435 int resId; 436 if (AccessibilityUtil.isFloatingMenuEnabled(context)) { 437 resId = R.drawable.a11y_shortcut_type_software_floating; 438 } else if (AccessibilityUtil.isGestureNavigateEnabled(context)) { 439 resId = AccessibilityUtil.isTouchExploreEnabled(context) 440 ? R.drawable.a11y_shortcut_type_software_gesture_talkback 441 : R.drawable.a11y_shortcut_type_software_gesture; 442 } else { 443 resId = R.drawable.a11y_shortcut_type_software; 444 } 445 return resId; 446 } 447 getCustomizeAccessibilityButtonLink(Context context)448 private static CharSequence getCustomizeAccessibilityButtonLink(Context context) { 449 final View.OnClickListener linkListener = v -> new SubSettingLauncher(context) 450 .setDestination(AccessibilityButtonFragment.class.getName()) 451 .setSourceMetricsCategory( 452 SettingsEnums.SWITCH_SHORTCUT_DIALOG_ACCESSIBILITY_BUTTON_SETTINGS) 453 .launch(); 454 final AnnotationSpan.LinkInfo linkInfo = new AnnotationSpan.LinkInfo( 455 AnnotationSpan.LinkInfo.DEFAULT_ANNOTATION, linkListener); 456 return AnnotationSpan.linkify(context.getText( 457 R.string.accessibility_shortcut_edit_dialog_summary_software_floating), linkInfo); 458 } 459 getSummaryStringWithIcon(Context context, int lineHeight)460 private static SpannableString getSummaryStringWithIcon(Context context, int lineHeight) { 461 final String summary = context 462 .getString(R.string.accessibility_shortcut_edit_dialog_summary_software); 463 final SpannableString spannableMessage = SpannableString.valueOf(summary); 464 465 // Icon 466 final int indexIconStart = summary.indexOf("%s"); 467 final int indexIconEnd = indexIconStart + 2; 468 final Drawable icon = context.getDrawable(R.drawable.ic_accessibility_new); 469 final ImageSpan imageSpan = new ImageSpan(icon); 470 imageSpan.setContentDescription(""); 471 icon.setBounds(0, 0, lineHeight, lineHeight); 472 spannableMessage.setSpan( 473 imageSpan, indexIconStart, indexIconEnd, 474 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 475 return spannableMessage; 476 } 477 478 /** 479 * Returns the color associated with the specified attribute in the context's theme. 480 */ 481 @ColorInt getThemeAttrColor(final Context context, final int attributeColor)482 private static int getThemeAttrColor(final Context context, final int attributeColor) { 483 final int colorResId = getAttrResourceId(context, attributeColor); 484 return ContextCompat.getColor(context, colorResId); 485 } 486 487 /** 488 * Returns the identifier of the resolved resource assigned to the given attribute. 489 */ getAttrResourceId(final Context context, final int attributeColor)490 private static int getAttrResourceId(final Context context, final int attributeColor) { 491 final int[] attrs = {attributeColor}; 492 final TypedArray typedArray = context.obtainStyledAttributes(attrs); 493 final int colorResId = typedArray.getResourceId(0, 0); 494 typedArray.recycle(); 495 return colorResId; 496 } 497 498 /** 499 * Creates a dialog with the given view. 500 * 501 * @param context A valid context 502 * @param dialogTitle The title of the dialog 503 * @param customView The customized view 504 * @param positiveButtonText The text of the positive button 505 * @param positiveListener This listener will be invoked when the positive button in the dialog 506 * is clicked 507 * @param negativeButtonText The text of the negative button 508 * @param negativeListener This listener will be invoked when the negative button in the dialog 509 * is clicked 510 * @return the {@link Dialog} with the given view 511 */ createCustomDialog(Context context, CharSequence dialogTitle, View customView, CharSequence positiveButtonText, DialogInterface.OnClickListener positiveListener, CharSequence negativeButtonText, DialogInterface.OnClickListener negativeListener)512 public static Dialog createCustomDialog(Context context, CharSequence dialogTitle, 513 View customView, CharSequence positiveButtonText, 514 DialogInterface.OnClickListener positiveListener, CharSequence negativeButtonText, 515 DialogInterface.OnClickListener negativeListener) { 516 final AlertDialog alertDialog = new AlertDialog.Builder(context) 517 .setView(customView) 518 .setTitle(dialogTitle) 519 .setCancelable(true) 520 .setPositiveButton(positiveButtonText, positiveListener) 521 .setNegativeButton(negativeButtonText, negativeListener) 522 .create(); 523 if (customView instanceof ScrollView || customView instanceof AbsListView) { 524 setScrollIndicators(customView); 525 } 526 return alertDialog; 527 } 528 529 /** 530 * Creates a single choice {@link ListView} with given {@link ItemInfo} list. 531 * 532 * @param context A context. 533 * @param itemInfoList A {@link ItemInfo} list. 534 * @param itemListener The listener will be invoked when the item is clicked. 535 */ 536 @NonNull createSingleChoiceListView(@onNull Context context, @NonNull List<? extends ItemInfo> itemInfoList, @Nullable AdapterView.OnItemClickListener itemListener)537 public static ListView createSingleChoiceListView(@NonNull Context context, 538 @NonNull List<? extends ItemInfo> itemInfoList, 539 @Nullable AdapterView.OnItemClickListener itemListener) { 540 final ListView list = new ListView(context); 541 // Set an id to save its state. 542 list.setId(android.R.id.list); 543 list.setDivider(/* divider= */ null); 544 list.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 545 final ItemInfoArrayAdapter 546 adapter = new ItemInfoArrayAdapter(context, itemInfoList); 547 list.setAdapter(adapter); 548 list.setOnItemClickListener(itemListener); 549 return list; 550 } 551 } 552