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 17 package com.android.printspooler.ui; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.app.Activity; 22 import android.app.ActivityOptions; 23 import android.app.LoaderManager; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.IntentSender.SendIntentException; 28 import android.content.Loader; 29 import android.content.pm.ApplicationInfo; 30 import android.content.pm.PackageManager; 31 import android.database.DataSetObserver; 32 import android.graphics.drawable.Drawable; 33 import android.os.Build; 34 import android.os.Bundle; 35 import android.print.PrintManager; 36 import android.print.PrintServicesLoader; 37 import android.print.PrinterId; 38 import android.print.PrinterInfo; 39 import android.printservice.PrintService; 40 import android.printservice.PrintServiceInfo; 41 import android.provider.Settings; 42 import android.text.TextUtils; 43 import android.util.ArrayMap; 44 import android.util.Log; 45 import android.util.TypedValue; 46 import android.view.ContextMenu; 47 import android.view.ContextMenu.ContextMenuInfo; 48 import android.view.Menu; 49 import android.view.MenuItem; 50 import android.view.View; 51 import android.view.View.OnClickListener; 52 import android.view.ViewGroup; 53 import android.view.accessibility.AccessibilityManager; 54 import android.widget.AdapterView; 55 import android.widget.AdapterView.AdapterContextMenuInfo; 56 import android.widget.BaseAdapter; 57 import android.widget.Filter; 58 import android.widget.Filterable; 59 import android.widget.ImageView; 60 import android.widget.LinearLayout; 61 import android.widget.ListView; 62 import android.widget.SearchView; 63 import android.widget.TextView; 64 import android.widget.Toast; 65 66 import com.android.internal.logging.MetricsLogger; 67 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 68 import com.android.printspooler.R; 69 70 import java.util.ArrayList; 71 import java.util.List; 72 73 /** 74 * This is an activity for selecting a printer. 75 */ 76 public final class SelectPrinterActivity extends Activity implements 77 LoaderManager.LoaderCallbacks<List<PrintServiceInfo>> { 78 79 private static final String LOG_TAG = "SelectPrinterFragment"; 80 81 private static final int LOADER_ID_PRINT_REGISTRY = 1; 82 private static final int LOADER_ID_PRINT_REGISTRY_INT = 2; 83 private static final int LOADER_ID_ENABLED_PRINT_SERVICES = 3; 84 85 private static final int INFO_INTENT_REQUEST_CODE = 1; 86 87 public static final String INTENT_EXTRA_PRINTER = "INTENT_EXTRA_PRINTER"; 88 89 private static final String EXTRA_PRINTER = "EXTRA_PRINTER"; 90 private static final String EXTRA_PRINTER_ID = "EXTRA_PRINTER_ID"; 91 92 private static final String KEY_NOT_FIRST_CREATE = "KEY_NOT_FIRST_CREATE"; 93 private static final String KEY_DID_SEARCH = "DID_SEARCH"; 94 private static final String KEY_PRINTER_FOR_INFO_INTENT = "KEY_PRINTER_FOR_INFO_INTENT"; 95 96 // Constants for MetricsLogger.count and MetricsLogger.histo 97 private static final String PRINTERS_LISTED_COUNT = "printers_listed"; 98 private static final String PRINTERS_ICON_COUNT = "printers_icon"; 99 private static final String PRINTERS_INFO_COUNT = "printers_info"; 100 101 /** The currently enabled print services by their ComponentName */ 102 private ArrayMap<ComponentName, PrintServiceInfo> mEnabledPrintServices; 103 104 private PrinterRegistry mPrinterRegistry; 105 106 private ListView mListView; 107 108 private AnnounceFilterResult mAnnounceFilterResult; 109 110 private boolean mDidSearch; 111 112 /** 113 * Printer we are currently in the info intent for. This is only non-null while this activity 114 * started an info intent that has not yet returned 115 */ 116 private @Nullable PrinterInfo mPrinterForInfoIntent; 117 startAddPrinterActivity()118 private void startAddPrinterActivity() { 119 MetricsLogger.action(this, MetricsEvent.ACTION_PRINT_SERVICE_ADD); 120 startActivity(new Intent(this, AddPrinterActivity.class)); 121 } 122 123 @Override onCreate(Bundle savedInstanceState)124 public void onCreate(Bundle savedInstanceState) { 125 super.onCreate(savedInstanceState); 126 getActionBar().setIcon(com.android.internal.R.drawable.ic_print); 127 128 setContentView(R.layout.select_printer_activity); 129 130 getActionBar().setDisplayHomeAsUpEnabled(true); 131 132 mEnabledPrintServices = new ArrayMap<>(); 133 134 mPrinterRegistry = new PrinterRegistry(this, null, LOADER_ID_PRINT_REGISTRY, 135 LOADER_ID_PRINT_REGISTRY_INT); 136 137 // Hook up the list view. 138 mListView = findViewById(android.R.id.list); 139 final DestinationAdapter adapter = new DestinationAdapter(); 140 adapter.registerDataSetObserver(new DataSetObserver() { 141 @Override 142 public void onChanged() { 143 if (!isFinishing() && adapter.getCount() <= 0) { 144 updateEmptyView(adapter); 145 } 146 } 147 148 @Override 149 public void onInvalidated() { 150 if (!isFinishing()) { 151 updateEmptyView(adapter); 152 } 153 } 154 }); 155 mListView.setAdapter(adapter); 156 157 mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { 158 @Override 159 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 160 if (!((DestinationAdapter) mListView.getAdapter()).isActionable(position)) { 161 return; 162 } 163 164 PrinterInfo printer = (PrinterInfo) mListView.getAdapter().getItem(position); 165 166 if (printer == null) { 167 startAddPrinterActivity(); 168 } else { 169 onPrinterSelected(printer); 170 } 171 } 172 }); 173 174 findViewById(R.id.button).setOnClickListener(new OnClickListener() { 175 @Override public void onClick(View v) { 176 startAddPrinterActivity(); 177 } 178 }); 179 180 registerForContextMenu(mListView); 181 182 getLoaderManager().initLoader(LOADER_ID_ENABLED_PRINT_SERVICES, null, this); 183 184 // On first creation: 185 // 186 // If no services are installed, instantly open add printer dialog. 187 // If some are disabled and some are enabled show a toast to notify the user 188 if (savedInstanceState == null || !savedInstanceState.getBoolean(KEY_NOT_FIRST_CREATE)) { 189 List<PrintServiceInfo> allServices = 190 ((PrintManager) getSystemService(Context.PRINT_SERVICE)) 191 .getPrintServices(PrintManager.ALL_SERVICES); 192 boolean hasEnabledServices = false; 193 boolean hasDisabledServices = false; 194 195 if (allServices != null) { 196 final int numServices = allServices.size(); 197 for (int i = 0; i < numServices; i++) { 198 if (allServices.get(i).isEnabled()) { 199 hasEnabledServices = true; 200 } else { 201 hasDisabledServices = true; 202 } 203 } 204 } 205 206 if (!hasEnabledServices) { 207 startAddPrinterActivity(); 208 } else if (hasDisabledServices) { 209 String disabledServicesSetting = Settings.Secure.getString(getContentResolver(), 210 Settings.Secure.DISABLED_PRINT_SERVICES); 211 if (!TextUtils.isEmpty(disabledServicesSetting)) { 212 Toast.makeText(this, getString(R.string.print_services_disabled_toast), 213 Toast.LENGTH_LONG).show(); 214 } 215 } 216 } 217 218 if (savedInstanceState != null) { 219 mDidSearch = savedInstanceState.getBoolean(KEY_DID_SEARCH); 220 mPrinterForInfoIntent = savedInstanceState.getParcelable(KEY_PRINTER_FOR_INFO_INTENT); 221 } 222 } 223 224 @Override onSaveInstanceState(Bundle outState)225 protected void onSaveInstanceState(Bundle outState) { 226 super.onSaveInstanceState(outState); 227 outState.putBoolean(KEY_NOT_FIRST_CREATE, true); 228 outState.putBoolean(KEY_DID_SEARCH, mDidSearch); 229 outState.putParcelable(KEY_PRINTER_FOR_INFO_INTENT, mPrinterForInfoIntent); 230 } 231 232 @Override onCreateOptionsMenu(Menu menu)233 public boolean onCreateOptionsMenu(Menu menu) { 234 super.onCreateOptionsMenu(menu); 235 236 getMenuInflater().inflate(R.menu.select_printer_activity, menu); 237 238 MenuItem searchItem = menu.findItem(R.id.action_search); 239 SearchView searchView = (SearchView) searchItem.getActionView(); 240 searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { 241 @Override 242 public boolean onQueryTextSubmit(String query) { 243 return true; 244 } 245 246 @Override 247 public boolean onQueryTextChange(String searchString) { 248 ((DestinationAdapter) mListView.getAdapter()).getFilter().filter(searchString); 249 return true; 250 } 251 }); 252 searchView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { 253 @Override 254 public void onViewAttachedToWindow(View view) { 255 if (AccessibilityManager.getInstance(SelectPrinterActivity.this).isEnabled()) { 256 view.announceForAccessibility(getString( 257 R.string.print_search_box_shown_utterance)); 258 } 259 } 260 @Override 261 public void onViewDetachedFromWindow(View view) { 262 if (!isFinishing() && AccessibilityManager.getInstance( 263 SelectPrinterActivity.this).isEnabled()) { 264 view.announceForAccessibility(getString( 265 R.string.print_search_box_hidden_utterance)); 266 } 267 } 268 }); 269 270 return true; 271 } 272 273 @Override onOptionsItemSelected(MenuItem item)274 public boolean onOptionsItemSelected(MenuItem item) { 275 if (item.getItemId() == android.R.id.home) { 276 finish(); 277 return true; 278 } else { 279 return super.onOptionsItemSelected(item); 280 } 281 } 282 283 @Override onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo)284 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) { 285 if (view == mListView) { 286 final int position = ((AdapterContextMenuInfo) menuInfo).position; 287 PrinterInfo printer = (PrinterInfo) mListView.getAdapter().getItem(position); 288 289 // Printer is null if this is a context menu for the "add printer" entry 290 if (printer == null) { 291 return; 292 } 293 294 menu.setHeaderTitle(printer.getName()); 295 296 // Add the select menu item if applicable. 297 if (printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE) { 298 MenuItem selectItem = menu.add(Menu.NONE, R.string.print_select_printer, 299 Menu.NONE, R.string.print_select_printer); 300 Intent intent = new Intent(); 301 intent.putExtra(EXTRA_PRINTER, printer); 302 selectItem.setIntent(intent); 303 } 304 305 // Add the forget menu item if applicable. 306 if (mPrinterRegistry.isFavoritePrinter(printer.getId())) { 307 MenuItem forgetItem = menu.add(Menu.NONE, R.string.print_forget_printer, 308 Menu.NONE, R.string.print_forget_printer); 309 Intent intent = new Intent(); 310 intent.putExtra(EXTRA_PRINTER_ID, printer.getId()); 311 forgetItem.setIntent(intent); 312 } 313 } 314 } 315 316 @Override onContextItemSelected(MenuItem item)317 public boolean onContextItemSelected(MenuItem item) { 318 final int itemId = item.getItemId(); 319 if (itemId == R.string.print_select_printer) { 320 PrinterInfo printer = item.getIntent().getParcelableExtra(EXTRA_PRINTER); 321 onPrinterSelected(printer); 322 return true; 323 } else if (itemId == R.string.print_forget_printer) { 324 PrinterId printerId = item.getIntent().getParcelableExtra(EXTRA_PRINTER_ID); 325 mPrinterRegistry.forgetFavoritePrinter(printerId); 326 return true; 327 } 328 return false; 329 } 330 331 /** 332 * Adjust the UI if the enabled print services changed. 333 */ onPrintServicesUpdate()334 private synchronized void onPrintServicesUpdate() { 335 updateEmptyView((DestinationAdapter)mListView.getAdapter()); 336 invalidateOptionsMenu(); 337 } 338 339 @Override onStart()340 public void onStart() { 341 super.onStart(); 342 onPrintServicesUpdate(); 343 } 344 345 @Override onPause()346 public void onPause() { 347 if (mAnnounceFilterResult != null) { 348 mAnnounceFilterResult.remove(); 349 } 350 super.onPause(); 351 } 352 353 @Override onStop()354 public void onStop() { 355 super.onStop(); 356 } 357 358 @Override onDestroy()359 protected void onDestroy() { 360 if (isFinishing()) { 361 DestinationAdapter adapter = (DestinationAdapter) mListView.getAdapter(); 362 List<PrinterInfo> printers = adapter.getPrinters(); 363 int numPrinters = adapter.getPrinters().size(); 364 365 MetricsLogger.action(this, MetricsEvent.PRINT_ALL_PRINTERS, numPrinters); 366 MetricsLogger.count(this, PRINTERS_LISTED_COUNT, numPrinters); 367 368 int numInfoPrinters = 0; 369 int numIconPrinters = 0; 370 for (int i = 0; i < numPrinters; i++) { 371 PrinterInfo printer = printers.get(i); 372 373 if (printer.getInfoIntent() != null) { 374 numInfoPrinters++; 375 } 376 377 if (printer.getHasCustomPrinterIcon()) { 378 numIconPrinters++; 379 } 380 } 381 382 MetricsLogger.count(this, PRINTERS_INFO_COUNT, numInfoPrinters); 383 MetricsLogger.count(this, PRINTERS_ICON_COUNT, numIconPrinters); 384 } 385 386 super.onDestroy(); 387 } 388 389 @Override onActivityResult(int requestCode, int resultCode, Intent data)390 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 391 switch (requestCode) { 392 case INFO_INTENT_REQUEST_CODE: 393 if (resultCode == RESULT_OK && 394 data != null && 395 data.getBooleanExtra(PrintService.EXTRA_SELECT_PRINTER, false) && 396 mPrinterForInfoIntent != null && 397 mPrinterForInfoIntent.getStatus() != PrinterInfo.STATUS_UNAVAILABLE) { 398 onPrinterSelected(mPrinterForInfoIntent); 399 } 400 mPrinterForInfoIntent = null; 401 break; 402 default: 403 // not reached 404 } 405 } 406 onPrinterSelected(PrinterInfo printer)407 private void onPrinterSelected(PrinterInfo printer) { 408 Intent intent = new Intent(); 409 intent.putExtra(INTENT_EXTRA_PRINTER, printer); 410 setResult(RESULT_OK, intent); 411 finish(); 412 } 413 updateEmptyView(DestinationAdapter adapter)414 public void updateEmptyView(DestinationAdapter adapter) { 415 if (mListView.getEmptyView() == null) { 416 View emptyView = findViewById(R.id.empty_print_state); 417 mListView.setEmptyView(emptyView); 418 } 419 TextView titleView = findViewById(R.id.title); 420 View progressBar = findViewById(R.id.progress_bar); 421 if (mEnabledPrintServices.size() == 0) { 422 titleView.setText(R.string.print_no_print_services); 423 progressBar.setVisibility(View.GONE); 424 } else if (adapter.getUnfilteredCount() <= 0) { 425 titleView.setText(R.string.print_searching_for_printers); 426 progressBar.setVisibility(View.VISIBLE); 427 } else { 428 titleView.setText(R.string.print_no_printers); 429 progressBar.setVisibility(View.GONE); 430 } 431 } 432 announceSearchResultIfNeeded()433 private void announceSearchResultIfNeeded() { 434 if (AccessibilityManager.getInstance(this).isEnabled()) { 435 if (mAnnounceFilterResult == null) { 436 mAnnounceFilterResult = new AnnounceFilterResult(); 437 } 438 mAnnounceFilterResult.post(); 439 } 440 } 441 442 @Override onCreateLoader(int id, Bundle args)443 public Loader<List<PrintServiceInfo>> onCreateLoader(int id, Bundle args) { 444 return new PrintServicesLoader((PrintManager) getSystemService(Context.PRINT_SERVICE), this, 445 PrintManager.ENABLED_SERVICES); 446 } 447 448 @Override onLoadFinished(Loader<List<PrintServiceInfo>> loader, List<PrintServiceInfo> services)449 public void onLoadFinished(Loader<List<PrintServiceInfo>> loader, 450 List<PrintServiceInfo> services) { 451 mEnabledPrintServices.clear(); 452 453 if (services != null && !services.isEmpty()) { 454 final int numServices = services.size(); 455 for (int i = 0; i < numServices; i++) { 456 PrintServiceInfo service = services.get(i); 457 458 mEnabledPrintServices.put(service.getComponentName(), service); 459 } 460 } 461 462 onPrintServicesUpdate(); 463 } 464 465 @Override onLoaderReset(Loader<List<PrintServiceInfo>> loader)466 public void onLoaderReset(Loader<List<PrintServiceInfo>> loader) { 467 if (!isFinishing()) { 468 onLoadFinished(loader, null); 469 } 470 } 471 472 /** 473 * Return the target SDK of the package that defined the printer. 474 * 475 * @param printer The printer 476 * 477 * @return The target SDK that defined a printer. 478 */ getTargetSDKOfPrintersService(@onNull PrinterInfo printer)479 private int getTargetSDKOfPrintersService(@NonNull PrinterInfo printer) { 480 ApplicationInfo serviceAppInfo; 481 try { 482 serviceAppInfo = getPackageManager().getApplicationInfo( 483 printer.getId().getServiceName().getPackageName(), 0); 484 } catch (PackageManager.NameNotFoundException e) { 485 Log.e(LOG_TAG, "Could not find package that defined the printer", e); 486 return Build.VERSION_CODES.KITKAT; 487 } 488 489 return serviceAppInfo.targetSdkVersion; 490 } 491 492 private final class DestinationAdapter extends BaseAdapter implements Filterable { 493 494 private final Object mLock = new Object(); 495 496 private final List<PrinterInfo> mPrinters = new ArrayList<>(); 497 498 private final List<PrinterInfo> mFilteredPrinters = new ArrayList<>(); 499 500 private CharSequence mLastSearchString; 501 502 /** 503 * Get the currently known printers. 504 * 505 * @return The currently known printers 506 */ getPrinters()507 @NonNull List<PrinterInfo> getPrinters() { 508 return mPrinters; 509 } 510 DestinationAdapter()511 public DestinationAdapter() { 512 mPrinterRegistry.setOnPrintersChangeListener(new PrinterRegistry.OnPrintersChangeListener() { 513 @Override 514 public void onPrintersChanged(List<PrinterInfo> printers) { 515 synchronized (mLock) { 516 mPrinters.clear(); 517 mPrinters.addAll(printers); 518 mFilteredPrinters.clear(); 519 mFilteredPrinters.addAll(printers); 520 if (!TextUtils.isEmpty(mLastSearchString)) { 521 getFilter().filter(mLastSearchString); 522 } 523 } 524 notifyDataSetChanged(); 525 } 526 527 @Override 528 public void onPrintersInvalid() { 529 synchronized (mLock) { 530 mPrinters.clear(); 531 mFilteredPrinters.clear(); 532 } 533 notifyDataSetInvalidated(); 534 } 535 }); 536 } 537 538 @Override getFilter()539 public Filter getFilter() { 540 return new Filter() { 541 @Override 542 protected FilterResults performFiltering(CharSequence constraint) { 543 synchronized (mLock) { 544 if (TextUtils.isEmpty(constraint)) { 545 return null; 546 } 547 FilterResults results = new FilterResults(); 548 List<PrinterInfo> filteredPrinters = new ArrayList<>(); 549 String constraintLowerCase = constraint.toString().toLowerCase(); 550 final int printerCount = mPrinters.size(); 551 for (int i = 0; i < printerCount; i++) { 552 PrinterInfo printer = mPrinters.get(i); 553 String description = printer.getDescription(); 554 if (printer.getName().toLowerCase().contains(constraintLowerCase) 555 || description != null && description.toLowerCase() 556 .contains(constraintLowerCase)) { 557 filteredPrinters.add(printer); 558 } 559 } 560 results.values = filteredPrinters; 561 results.count = filteredPrinters.size(); 562 return results; 563 } 564 } 565 566 @Override 567 @SuppressWarnings("unchecked") 568 protected void publishResults(CharSequence constraint, FilterResults results) { 569 final boolean resultCountChanged; 570 synchronized (mLock) { 571 final int oldPrinterCount = mFilteredPrinters.size(); 572 mLastSearchString = constraint; 573 mFilteredPrinters.clear(); 574 if (results == null) { 575 mFilteredPrinters.addAll(mPrinters); 576 } else { 577 List<PrinterInfo> printers = (List<PrinterInfo>) results.values; 578 mFilteredPrinters.addAll(printers); 579 } 580 resultCountChanged = (oldPrinterCount != mFilteredPrinters.size()); 581 } 582 if (resultCountChanged) { 583 announceSearchResultIfNeeded(); 584 } 585 586 if (!mDidSearch) { 587 MetricsLogger.action(SelectPrinterActivity.this, 588 MetricsEvent.ACTION_PRINTER_SEARCH); 589 mDidSearch = true; 590 } 591 notifyDataSetChanged(); 592 } 593 }; 594 } 595 596 public int getUnfilteredCount() { 597 synchronized (mLock) { 598 return mPrinters.size(); 599 } 600 } 601 602 @Override 603 public int getCount() { 604 synchronized (mLock) { 605 if (mFilteredPrinters.isEmpty()) { 606 return 0; 607 } else { 608 // Add "add printer" item to the end of the list. If the list is empty there is 609 // a link on the empty view 610 return mFilteredPrinters.size() + 1; 611 } 612 } 613 } 614 615 @Override 616 public int getViewTypeCount() { 617 return 2; 618 } 619 620 @Override 621 public int getItemViewType(int position) { 622 // Use separate view types for the "add printer" item an the items referring to printers 623 if (getItem(position) == null) { 624 return 0; 625 } else { 626 return 1; 627 } 628 } 629 630 @Override 631 public Object getItem(int position) { 632 synchronized (mLock) { 633 if (position < mFilteredPrinters.size()) { 634 return mFilteredPrinters.get(position); 635 } else { 636 // Return null to mark this as the "add printer item" 637 return null; 638 } 639 } 640 } 641 642 @Override 643 public long getItemId(int position) { 644 return position; 645 } 646 647 @Override 648 public View getDropDownView(int position, View convertView, ViewGroup parent) { 649 return getView(position, convertView, parent); 650 } 651 652 @Override 653 public View getView(int position, View convertView, ViewGroup parent) { 654 final PrinterInfo printer = (PrinterInfo) getItem(position); 655 656 // Handle "add printer item" 657 if (printer == null) { 658 if (convertView == null) { 659 convertView = getLayoutInflater().inflate(R.layout.add_printer_list_item, 660 parent, false); 661 } 662 663 return convertView; 664 } 665 666 if (convertView == null) { 667 convertView = getLayoutInflater().inflate( 668 R.layout.printer_list_item, parent, false); 669 } 670 671 convertView.setEnabled(isActionable(position)); 672 673 674 CharSequence title = printer.getName(); 675 Drawable icon = printer.loadIcon(SelectPrinterActivity.this); 676 677 PrintServiceInfo service = mEnabledPrintServices.get(printer.getId().getServiceName()); 678 679 CharSequence printServiceLabel = null; 680 if (service != null) { 681 printServiceLabel = service.getResolveInfo().loadLabel(getPackageManager()) 682 .toString(); 683 } 684 685 CharSequence description = printer.getDescription(); 686 687 CharSequence subtitle; 688 if (TextUtils.isEmpty(printServiceLabel)) { 689 subtitle = description; 690 } else if (TextUtils.isEmpty(description)) { 691 subtitle = printServiceLabel; 692 } else { 693 subtitle = getString(R.string.printer_extended_description_template, 694 printServiceLabel, description); 695 } 696 697 TextView titleView = (TextView) convertView.findViewById(R.id.title); 698 titleView.setText(title); 699 700 TextView subtitleView = (TextView) convertView.findViewById(R.id.subtitle); 701 if (!TextUtils.isEmpty(subtitle)) { 702 subtitleView.setText(subtitle); 703 subtitleView.setVisibility(View.VISIBLE); 704 } else { 705 subtitleView.setText(null); 706 subtitleView.setVisibility(View.GONE); 707 } 708 709 LinearLayout moreInfoView = (LinearLayout) convertView.findViewById(R.id.more_info); 710 if (printer.getInfoIntent() != null) { 711 moreInfoView.setVisibility(View.VISIBLE); 712 moreInfoView.setOnClickListener(v -> { 713 Intent fillInIntent = new Intent(); 714 fillInIntent.putExtra(PrintService.EXTRA_CAN_SELECT_PRINTER, true); 715 716 try { 717 mPrinterForInfoIntent = printer; 718 Bundle options = ActivityOptions.makeBasic() 719 .setPendingIntentBackgroundActivityStartMode( 720 ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) 721 .toBundle(); 722 startIntentSenderForResult(printer.getInfoIntent().getIntentSender(), 723 INFO_INTENT_REQUEST_CODE, fillInIntent, 0, 0, 0, 724 options); 725 } catch (SendIntentException e) { 726 mPrinterForInfoIntent = null; 727 Log.e(LOG_TAG, "Could not execute pending info intent: %s", e); 728 } 729 }); 730 } else { 731 moreInfoView.setVisibility(View.GONE); 732 } 733 734 ImageView iconView = (ImageView) convertView.findViewById(R.id.icon); 735 if (icon != null) { 736 iconView.setVisibility(View.VISIBLE); 737 if (!isActionable(position)) { 738 icon.mutate(); 739 740 TypedValue value = new TypedValue(); 741 getTheme().resolveAttribute(android.R.attr.disabledAlpha, value, true); 742 icon.setAlpha((int)(value.getFloat() * 255)); 743 } 744 iconView.setImageDrawable(icon); 745 } else { 746 iconView.setVisibility(View.GONE); 747 } 748 749 return convertView; 750 } 751 752 public boolean isActionable(int position) { 753 PrinterInfo printer = (PrinterInfo) getItem(position); 754 755 if (printer == null) { 756 return true; 757 } else { 758 return printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE; 759 } 760 } 761 } 762 763 private final class AnnounceFilterResult implements Runnable { 764 private static final int SEARCH_RESULT_ANNOUNCEMENT_DELAY = 1000; // 1 sec 765 766 public void post() { 767 remove(); 768 mListView.postDelayed(this, SEARCH_RESULT_ANNOUNCEMENT_DELAY); 769 } 770 771 public void remove() { 772 mListView.removeCallbacks(this); 773 } 774 775 @Override 776 public void run() { 777 final int count = mListView.getAdapter().getCount(); 778 final String text; 779 if (count <= 0) { 780 text = getString(R.string.print_no_printers); 781 } else { 782 text = getResources().getQuantityString( 783 R.plurals.print_search_result_count_utterance, count, count); 784 } 785 mListView.announceForAccessibility(text); 786 } 787 } 788 } 789