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