SelectPrinterActivity.java revision 5462e88c0272d08773ac6cd16cce435c32d2d9bf
1/* 2 * Copyright (C) 2013 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 17package com.android.printspooler.ui; 18 19import android.app.Activity; 20import android.app.AlertDialog; 21import android.app.Dialog; 22import android.app.DialogFragment; 23import android.app.Fragment; 24import android.app.FragmentTransaction; 25import android.content.ActivityNotFoundException; 26import android.content.ComponentName; 27import android.content.Context; 28import android.content.DialogInterface; 29import android.content.Intent; 30import android.content.IntentSender.SendIntentException; 31import android.content.pm.ActivityInfo; 32import android.content.pm.PackageInfo; 33import android.content.pm.PackageManager; 34import android.content.pm.PackageManager.NameNotFoundException; 35import android.content.pm.ResolveInfo; 36import android.content.pm.ServiceInfo; 37import android.database.ContentObserver; 38import android.database.DataSetObserver; 39import android.graphics.drawable.Drawable; 40import android.net.Uri; 41import android.os.Bundle; 42import android.os.Handler; 43import android.print.PrintManager; 44import android.print.PrinterId; 45import android.print.PrinterInfo; 46import android.printservice.PrintServiceInfo; 47import android.provider.Settings; 48import android.text.TextUtils; 49import android.util.Log; 50import android.view.ContextMenu; 51import android.view.ContextMenu.ContextMenuInfo; 52import android.view.Menu; 53import android.view.MenuItem; 54import android.view.View; 55import android.view.View.OnClickListener; 56import android.view.ViewGroup; 57import android.view.accessibility.AccessibilityManager; 58import android.widget.AdapterView; 59import android.widget.AdapterView.AdapterContextMenuInfo; 60import android.widget.ArrayAdapter; 61import android.widget.BaseAdapter; 62import android.widget.Filter; 63import android.widget.Filterable; 64import android.widget.ImageView; 65import android.widget.ListView; 66import android.widget.SearchView; 67import android.widget.TextView; 68import android.widget.Toast; 69 70import com.android.internal.content.PackageMonitor; 71import com.android.printspooler.R; 72 73import java.util.ArrayList; 74import java.util.List; 75 76/** 77 * This is an activity for selecting a printer. 78 */ 79public final class SelectPrinterActivity extends Activity { 80 81 private static final String LOG_TAG = "SelectPrinterFragment"; 82 83 public static final String INTENT_EXTRA_PRINTER_ID = "INTENT_EXTRA_PRINTER_ID"; 84 85 private static final String FRAGMENT_TAG_ADD_PRINTER_DIALOG = 86 "FRAGMENT_TAG_ADD_PRINTER_DIALOG"; 87 88 private static final String FRAGMENT_ARGUMENT_PRINT_SERVICE_INFOS = 89 "FRAGMENT_ARGUMENT_PRINT_SERVICE_INFOS"; 90 91 private static final String EXTRA_PRINTER_ID = "EXTRA_PRINTER_ID"; 92 93 /** If there are any enabled print services */ 94 private boolean mHasEnabledPrintServices; 95 96 private final ArrayList<PrintServiceInfo> mAddPrinterServices = 97 new ArrayList<>(); 98 99 private PrinterRegistry mPrinterRegistry; 100 101 private ListView mListView; 102 103 private AnnounceFilterResult mAnnounceFilterResult; 104 105 /** Monitor if new print services get enabled or disabled */ 106 private ContentObserver mPrintServicesDisabledObserver; 107 private PackageMonitor mPackageObserver; 108 109 @Override 110 public void onCreate(Bundle savedInstanceState) { 111 super.onCreate(savedInstanceState); 112 getActionBar().setIcon(R.drawable.ic_print); 113 114 setContentView(R.layout.select_printer_activity); 115 116 mPrinterRegistry = new PrinterRegistry(this, null); 117 118 // Hook up the list view. 119 mListView = (ListView) findViewById(android.R.id.list); 120 final DestinationAdapter adapter = new DestinationAdapter(); 121 adapter.registerDataSetObserver(new DataSetObserver() { 122 @Override 123 public void onChanged() { 124 if (!isFinishing() && adapter.getCount() <= 0) { 125 updateEmptyView(adapter); 126 } 127 } 128 129 @Override 130 public void onInvalidated() { 131 if (!isFinishing()) { 132 updateEmptyView(adapter); 133 } 134 } 135 }); 136 mListView.setAdapter(adapter); 137 138 mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { 139 @Override 140 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 141 if (!((DestinationAdapter) mListView.getAdapter()).isActionable(position)) { 142 return; 143 } 144 145 PrinterInfo printer = (PrinterInfo) mListView.getAdapter().getItem(position); 146 onPrinterSelected(printer.getId()); 147 } 148 }); 149 150 registerForContextMenu(mListView); 151 152 // Display a notification about disabled services if there are disabled services 153 String disabledServicesSetting = Settings.Secure.getString(getContentResolver(), 154 Settings.Secure.DISABLED_PRINT_SERVICES); 155 if (!TextUtils.isEmpty(disabledServicesSetting)) { 156 Toast.makeText(this, getString(R.string.print_services_disabled_toast), 157 Toast.LENGTH_LONG).show(); 158 } 159 } 160 161 @Override 162 public boolean onCreateOptionsMenu(Menu menu) { 163 super.onCreateOptionsMenu(menu); 164 165 getMenuInflater().inflate(R.menu.select_printer_activity, menu); 166 167 MenuItem searchItem = menu.findItem(R.id.action_search); 168 SearchView searchView = (SearchView) searchItem.getActionView(); 169 searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { 170 @Override 171 public boolean onQueryTextSubmit(String query) { 172 return true; 173 } 174 175 @Override 176 public boolean onQueryTextChange(String searchString) { 177 ((DestinationAdapter) mListView.getAdapter()).getFilter().filter(searchString); 178 return true; 179 } 180 }); 181 searchView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { 182 @Override 183 public void onViewAttachedToWindow(View view) { 184 if (AccessibilityManager.getInstance(SelectPrinterActivity.this).isEnabled()) { 185 view.announceForAccessibility(getString( 186 R.string.print_search_box_shown_utterance)); 187 } 188 } 189 @Override 190 public void onViewDetachedFromWindow(View view) { 191 if (!isFinishing() && AccessibilityManager.getInstance( 192 SelectPrinterActivity.this).isEnabled()) { 193 view.announceForAccessibility(getString( 194 R.string.print_search_box_hidden_utterance)); 195 } 196 } 197 }); 198 199 return true; 200 } 201 202 @Override 203 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) { 204 if (view == mListView) { 205 final int position = ((AdapterContextMenuInfo) menuInfo).position; 206 PrinterInfo printer = (PrinterInfo) mListView.getAdapter().getItem(position); 207 208 menu.setHeaderTitle(printer.getName()); 209 210 // Add the select menu item if applicable. 211 if (printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE) { 212 MenuItem selectItem = menu.add(Menu.NONE, R.string.print_select_printer, 213 Menu.NONE, R.string.print_select_printer); 214 Intent intent = new Intent(); 215 intent.putExtra(EXTRA_PRINTER_ID, printer.getId()); 216 selectItem.setIntent(intent); 217 } 218 219 // Add the forget menu item if applicable. 220 if (mPrinterRegistry.isFavoritePrinter(printer.getId())) { 221 MenuItem forgetItem = menu.add(Menu.NONE, R.string.print_forget_printer, 222 Menu.NONE, R.string.print_forget_printer); 223 Intent intent = new Intent(); 224 intent.putExtra(EXTRA_PRINTER_ID, printer.getId()); 225 forgetItem.setIntent(intent); 226 } 227 } 228 } 229 230 @Override 231 public boolean onContextItemSelected(MenuItem item) { 232 switch (item.getItemId()) { 233 case R.string.print_select_printer: { 234 PrinterId printerId = item.getIntent().getParcelableExtra(EXTRA_PRINTER_ID); 235 onPrinterSelected(printerId); 236 } return true; 237 238 case R.string.print_forget_printer: { 239 PrinterId printerId = item.getIntent().getParcelableExtra(EXTRA_PRINTER_ID); 240 mPrinterRegistry.forgetFavoritePrinter(printerId); 241 } return true; 242 } 243 return false; 244 } 245 246 /** 247 * Adjust the UI if the enabled print services changed. 248 */ 249 private synchronized void onPrintServicesUpdate() { 250 updateServicesWithAddPrinterActivity(); 251 updateEmptyView((DestinationAdapter)mListView.getAdapter()); 252 invalidateOptionsMenu(); 253 } 254 255 /** 256 * Register listener for changes to the enabled print services. 257 */ 258 private void registerServiceMonitor() { 259 // Listen for services getting disabled 260 mPrintServicesDisabledObserver = new ContentObserver(new Handler()) { 261 @Override 262 public void onChange(boolean selfChange) { 263 onPrintServicesUpdate(); 264 } 265 }; 266 267 // Listen for services getting installed or uninstalled 268 mPackageObserver = new PackageMonitor() { 269 @Override 270 public void onPackageModified(String packageName) { 271 onPrintServicesUpdate(); 272 } 273 274 @Override 275 public void onPackageRemoved(String packageName, int uid) { 276 onPrintServicesUpdate(); 277 } 278 279 @Override 280 public void onPackageAdded(String packageName, int uid) { 281 onPrintServicesUpdate(); 282 } 283 }; 284 285 getContentResolver().registerContentObserver( 286 Settings.Secure.getUriFor(Settings.Secure.DISABLED_PRINT_SERVICES), false, 287 mPrintServicesDisabledObserver); 288 289 mPackageObserver.register(this, getMainLooper(), false); 290 } 291 292 /** 293 * Unregister the listeners for changes to the enabled print services. 294 */ 295 private void unregisterServiceMonitorIfNeeded() { 296 getContentResolver().unregisterContentObserver(mPrintServicesDisabledObserver); 297 mPackageObserver.unregister(); 298 } 299 300 @Override 301 public void onStart() { 302 super.onStart(); 303 registerServiceMonitor(); 304 onPrintServicesUpdate(); 305 } 306 307 @Override 308 public void onPause() { 309 if (mAnnounceFilterResult != null) { 310 mAnnounceFilterResult.remove(); 311 } 312 super.onPause(); 313 } 314 315 @Override 316 public void onStop() { 317 unregisterServiceMonitorIfNeeded(); 318 super.onStop(); 319 } 320 321 @Override 322 public boolean onOptionsItemSelected(MenuItem item) { 323 if (item.getItemId() == R.id.action_add_printer) { 324 showAddPrinterSelectionDialog(); 325 return true; 326 } 327 return super.onOptionsItemSelected(item); 328 } 329 330 private void onPrinterSelected(PrinterId printerId) { 331 Intent intent = new Intent(); 332 intent.putExtra(INTENT_EXTRA_PRINTER_ID, printerId); 333 setResult(RESULT_OK, intent); 334 finish(); 335 } 336 337 private void updateServicesWithAddPrinterActivity() { 338 mHasEnabledPrintServices = true; 339 mAddPrinterServices.clear(); 340 341 // Get all enabled print services. 342 PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE); 343 List<PrintServiceInfo> enabledServices = printManager.getEnabledPrintServices(); 344 345 // No enabled print services - done. 346 if (enabledServices.isEmpty()) { 347 mHasEnabledPrintServices = false; 348 return; 349 } 350 351 // Find the services with valid add printers activities. 352 final int enabledServiceCount = enabledServices.size(); 353 for (int i = 0; i < enabledServiceCount; i++) { 354 PrintServiceInfo enabledService = enabledServices.get(i); 355 356 // No add printers activity declared - next. 357 if (TextUtils.isEmpty(enabledService.getAddPrintersActivityName())) { 358 continue; 359 } 360 361 ServiceInfo serviceInfo = enabledService.getResolveInfo().serviceInfo; 362 ComponentName addPrintersComponentName = new ComponentName( 363 serviceInfo.packageName, enabledService.getAddPrintersActivityName()); 364 Intent addPritnersIntent = new Intent() 365 .setComponent(addPrintersComponentName); 366 367 // The add printers activity is valid - add it. 368 PackageManager pm = getPackageManager(); 369 List<ResolveInfo> resolvedActivities = pm.queryIntentActivities(addPritnersIntent, 0); 370 if (!resolvedActivities.isEmpty()) { 371 // The activity is a component name, therefore it is one or none. 372 ActivityInfo activityInfo = resolvedActivities.get(0).activityInfo; 373 if (activityInfo.exported 374 && (activityInfo.permission == null 375 || pm.checkPermission(activityInfo.permission, getPackageName()) 376 == PackageManager.PERMISSION_GRANTED)) { 377 mAddPrinterServices.add(enabledService); 378 } 379 } 380 } 381 } 382 383 private void showAddPrinterSelectionDialog() { 384 FragmentTransaction transaction = getFragmentManager().beginTransaction(); 385 Fragment oldFragment = getFragmentManager().findFragmentByTag( 386 FRAGMENT_TAG_ADD_PRINTER_DIALOG); 387 if (oldFragment != null) { 388 transaction.remove(oldFragment); 389 } 390 AddPrinterAlertDialogFragment newFragment = new AddPrinterAlertDialogFragment(); 391 Bundle arguments = new Bundle(); 392 arguments.putParcelableArrayList(FRAGMENT_ARGUMENT_PRINT_SERVICE_INFOS, 393 mAddPrinterServices); 394 newFragment.setArguments(arguments); 395 transaction.add(newFragment, FRAGMENT_TAG_ADD_PRINTER_DIALOG); 396 transaction.commit(); 397 } 398 399 public void updateEmptyView(DestinationAdapter adapter) { 400 if (mListView.getEmptyView() == null) { 401 View emptyView = findViewById(R.id.empty_print_state); 402 mListView.setEmptyView(emptyView); 403 } 404 TextView titleView = (TextView) findViewById(R.id.title); 405 View progressBar = findViewById(R.id.progress_bar); 406 if (!mHasEnabledPrintServices) { 407 titleView.setText(R.string.print_no_print_services); 408 progressBar.setVisibility(View.GONE); 409 } else if (adapter.getUnfilteredCount() <= 0) { 410 titleView.setText(R.string.print_searching_for_printers); 411 progressBar.setVisibility(View.VISIBLE); 412 } else { 413 titleView.setText(R.string.print_no_printers); 414 progressBar.setVisibility(View.GONE); 415 } 416 } 417 418 private void announceSearchResultIfNeeded() { 419 if (AccessibilityManager.getInstance(this).isEnabled()) { 420 if (mAnnounceFilterResult == null) { 421 mAnnounceFilterResult = new AnnounceFilterResult(); 422 } 423 mAnnounceFilterResult.post(); 424 } 425 } 426 427 public static class AddPrinterAlertDialogFragment extends DialogFragment { 428 429 private String mAddPrintServiceItem; 430 431 @Override 432 @SuppressWarnings("unchecked") 433 public Dialog onCreateDialog(Bundle savedInstanceState) { 434 AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) 435 .setTitle(R.string.choose_print_service); 436 437 final List<PrintServiceInfo> printServices = (List<PrintServiceInfo>) (List<?>) 438 getArguments().getParcelableArrayList(FRAGMENT_ARGUMENT_PRINT_SERVICE_INFOS); 439 440 final ArrayAdapter<String> adapter = new ArrayAdapter<>( 441 getActivity(), android.R.layout.simple_list_item_1); 442 final int printServiceCount = printServices.size(); 443 for (int i = 0; i < printServiceCount; i++) { 444 PrintServiceInfo printService = printServices.get(i); 445 adapter.add(printService.getResolveInfo().loadLabel( 446 getActivity().getPackageManager()).toString()); 447 } 448 449 final String searchUri = Settings.Secure.getString(getActivity().getContentResolver(), 450 Settings.Secure.PRINT_SERVICE_SEARCH_URI); 451 final Intent viewIntent; 452 if (!TextUtils.isEmpty(searchUri)) { 453 Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(searchUri)); 454 if (getActivity().getPackageManager().resolveActivity(intent, 0) != null) { 455 viewIntent = intent; 456 mAddPrintServiceItem = getString(R.string.add_print_service_label); 457 adapter.add(mAddPrintServiceItem); 458 } else { 459 viewIntent = null; 460 } 461 } else { 462 viewIntent = null; 463 } 464 465 builder.setAdapter(adapter, new DialogInterface.OnClickListener() { 466 @Override 467 public void onClick(DialogInterface dialog, int which) { 468 String item = adapter.getItem(which); 469 if (item.equals(mAddPrintServiceItem)) { 470 try { 471 startActivity(viewIntent); 472 } catch (ActivityNotFoundException anfe) { 473 Log.w(LOG_TAG, "Couldn't start add printer activity", anfe); 474 } 475 } else { 476 PrintServiceInfo printService = printServices.get(which); 477 ComponentName componentName = new ComponentName( 478 printService.getResolveInfo().serviceInfo.packageName, 479 printService.getAddPrintersActivityName()); 480 Intent intent = new Intent(Intent.ACTION_MAIN); 481 intent.setComponent(componentName); 482 try { 483 startActivity(intent); 484 } catch (ActivityNotFoundException anfe) { 485 Log.w(LOG_TAG, "Couldn't start add printer activity", anfe); 486 } 487 } 488 } 489 }); 490 491 return builder.create(); 492 } 493 } 494 495 private final class DestinationAdapter extends BaseAdapter implements Filterable { 496 497 private final Object mLock = new Object(); 498 499 private final List<PrinterInfo> mPrinters = new ArrayList<>(); 500 501 private final List<PrinterInfo> mFilteredPrinters = new ArrayList<>(); 502 503 private CharSequence mLastSearchString; 504 505 public DestinationAdapter() { 506 mPrinterRegistry.setOnPrintersChangeListener(new PrinterRegistry.OnPrintersChangeListener() { 507 @Override 508 public void onPrintersChanged(List<PrinterInfo> printers) { 509 synchronized (mLock) { 510 mPrinters.clear(); 511 mPrinters.addAll(printers); 512 mFilteredPrinters.clear(); 513 mFilteredPrinters.addAll(printers); 514 if (!TextUtils.isEmpty(mLastSearchString)) { 515 getFilter().filter(mLastSearchString); 516 } 517 } 518 notifyDataSetChanged(); 519 } 520 521 @Override 522 public void onPrintersInvalid() { 523 synchronized (mLock) { 524 mPrinters.clear(); 525 mFilteredPrinters.clear(); 526 } 527 notifyDataSetInvalidated(); 528 } 529 }); 530 } 531 532 @Override 533 public Filter getFilter() { 534 return new Filter() { 535 @Override 536 protected FilterResults performFiltering(CharSequence constraint) { 537 synchronized (mLock) { 538 if (TextUtils.isEmpty(constraint)) { 539 return null; 540 } 541 FilterResults results = new FilterResults(); 542 List<PrinterInfo> filteredPrinters = new ArrayList<>(); 543 String constraintLowerCase = constraint.toString().toLowerCase(); 544 final int printerCount = mPrinters.size(); 545 for (int i = 0; i < printerCount; i++) { 546 PrinterInfo printer = mPrinters.get(i); 547 if (printer.getName().toLowerCase().contains(constraintLowerCase)) { 548 filteredPrinters.add(printer); 549 } 550 } 551 results.values = filteredPrinters; 552 results.count = filteredPrinters.size(); 553 return results; 554 } 555 } 556 557 @Override 558 @SuppressWarnings("unchecked") 559 protected void publishResults(CharSequence constraint, FilterResults results) { 560 final boolean resultCountChanged; 561 synchronized (mLock) { 562 final int oldPrinterCount = mFilteredPrinters.size(); 563 mLastSearchString = constraint; 564 mFilteredPrinters.clear(); 565 if (results == null) { 566 mFilteredPrinters.addAll(mPrinters); 567 } else { 568 List<PrinterInfo> printers = (List<PrinterInfo>) results.values; 569 mFilteredPrinters.addAll(printers); 570 } 571 resultCountChanged = (oldPrinterCount != mFilteredPrinters.size()); 572 } 573 if (resultCountChanged) { 574 announceSearchResultIfNeeded(); 575 } 576 notifyDataSetChanged(); 577 } 578 }; 579 } 580 581 public int getUnfilteredCount() { 582 synchronized (mLock) { 583 return mPrinters.size(); 584 } 585 } 586 587 @Override 588 public int getCount() { 589 synchronized (mLock) { 590 return mFilteredPrinters.size(); 591 } 592 } 593 594 @Override 595 public Object getItem(int position) { 596 synchronized (mLock) { 597 return mFilteredPrinters.get(position); 598 } 599 } 600 601 @Override 602 public long getItemId(int position) { 603 return position; 604 } 605 606 @Override 607 public View getDropDownView(int position, View convertView, ViewGroup parent) { 608 return getView(position, convertView, parent); 609 } 610 611 @Override 612 public View getView(int position, View convertView, ViewGroup parent) { 613 if (convertView == null) { 614 convertView = getLayoutInflater().inflate( 615 R.layout.printer_list_item, parent, false); 616 } 617 618 convertView.setEnabled(isActionable(position)); 619 620 final PrinterInfo printer = (PrinterInfo) getItem(position); 621 622 CharSequence title = printer.getName(); 623 Drawable icon = printer.loadIcon(SelectPrinterActivity.this); 624 625 CharSequence printServiceLabel; 626 try { 627 PackageInfo packageInfo = getPackageManager().getPackageInfo( 628 printer.getId().getServiceName().getPackageName(), 0); 629 630 printServiceLabel = packageInfo.applicationInfo.loadLabel(getPackageManager()); 631 } catch (NameNotFoundException e) { 632 printServiceLabel = null; 633 } 634 635 CharSequence description = printer.getDescription(); 636 637 CharSequence subtitle; 638 if (TextUtils.isEmpty(printServiceLabel)) { 639 subtitle = description; 640 } else if (TextUtils.isEmpty(description)) { 641 subtitle = printServiceLabel; 642 } else { 643 subtitle = getString(R.string.printer_extended_description_template, 644 printServiceLabel, description); 645 } 646 647 TextView titleView = (TextView) convertView.findViewById(R.id.title); 648 titleView.setText(title); 649 650 TextView subtitleView = (TextView) convertView.findViewById(R.id.subtitle); 651 if (!TextUtils.isEmpty(subtitle)) { 652 subtitleView.setText(subtitle); 653 subtitleView.setVisibility(View.VISIBLE); 654 } else { 655 subtitleView.setText(null); 656 subtitleView.setVisibility(View.GONE); 657 } 658 659 ImageView moreInfoView = (ImageView) convertView.findViewById(R.id.more_info); 660 if (printer.getInfoIntent() != null) { 661 moreInfoView.setVisibility(View.VISIBLE); 662 moreInfoView.setOnClickListener(new OnClickListener() { 663 @Override 664 public void onClick(View v) { 665 try { 666 startIntentSender(printer.getInfoIntent().getIntentSender(), null, 0, 0, 0); 667 } catch (SendIntentException e) { 668 Log.e(LOG_TAG, "Could not execute pending info intent: %s", e); 669 } 670 } 671 }); 672 } 673 674 ImageView iconView = (ImageView) convertView.findViewById(R.id.icon); 675 if (icon != null) { 676 iconView.setImageDrawable(icon); 677 iconView.setVisibility(View.VISIBLE); 678 } else { 679 iconView.setVisibility(View.GONE); 680 } 681 682 return convertView; 683 } 684 685 public boolean isActionable(int position) { 686 PrinterInfo printer = (PrinterInfo) getItem(position); 687 return printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE; 688 } 689 } 690 691 private final class AnnounceFilterResult implements Runnable { 692 private static final int SEARCH_RESULT_ANNOUNCEMENT_DELAY = 1000; // 1 sec 693 694 public void post() { 695 remove(); 696 mListView.postDelayed(this, SEARCH_RESULT_ANNOUNCEMENT_DELAY); 697 } 698 699 public void remove() { 700 mListView.removeCallbacks(this); 701 } 702 703 @Override 704 public void run() { 705 final int count = mListView.getAdapter().getCount(); 706 final String text; 707 if (count <= 0) { 708 text = getString(R.string.print_no_printers); 709 } else { 710 text = getResources().getQuantityString( 711 R.plurals.print_search_result_count_utterance, count, count); 712 } 713 mListView.announceForAccessibility(text); 714 } 715 } 716} 717