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