1 /* 2 * Copyright (C) 2016 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.IntRange; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.app.ListActivity; 23 import android.app.LoaderManager; 24 import android.content.ActivityNotFoundException; 25 import android.content.ComponentName; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.Loader; 29 import android.content.pm.ResolveInfo; 30 import android.database.DataSetObserver; 31 import android.net.Uri; 32 import android.os.Bundle; 33 import android.print.PrintManager; 34 import android.printservice.recommendation.RecommendationInfo; 35 import android.print.PrintServiceRecommendationsLoader; 36 import android.print.PrintServicesLoader; 37 import android.printservice.PrintServiceInfo; 38 import android.provider.Settings; 39 import android.text.TextUtils; 40 import android.util.ArraySet; 41 import android.util.Log; 42 import android.util.Pair; 43 import android.view.View; 44 import android.view.ViewGroup; 45 import android.widget.Adapter; 46 import android.widget.AdapterView; 47 import android.widget.BaseAdapter; 48 import android.widget.ImageView; 49 import android.widget.TextView; 50 import com.android.printspooler.R; 51 52 import java.text.Collator; 53 import java.util.ArrayList; 54 import java.util.Collections; 55 import java.util.Comparator; 56 import java.util.List; 57 58 /** 59 * This is an activity for adding a printer or. It consists of a list fed from three adapters: 60 * <ul> 61 * <li>{@link #mEnabledServicesAdapter} for all enabled services. If a service has an {@link 62 * PrintServiceInfo#getAddPrintersActivityName() add printer activity} this is started 63 * when the item is clicked.</li> 64 * <li>{@link #mDisabledServicesAdapter} for all disabled services. Once clicked the settings page 65 * for this service is opened.</li> 66 * <li>{@link #mRecommendedServicesAdapter} for a link to all services. If this item is clicked 67 * the market app is opened to show all print services.</li> 68 * </ul> 69 */ 70 public class AddPrinterActivity extends ListActivity implements AdapterView.OnItemClickListener { 71 private static final String LOG_TAG = "AddPrinterActivity"; 72 73 /** Ids for the loaders */ 74 private static final int LOADER_ID_ENABLED_SERVICES = 1; 75 private static final int LOADER_ID_DISABLED_SERVICES = 2; 76 private static final int LOADER_ID_RECOMMENDED_SERVICES = 3; 77 private static final int LOADER_ID_ALL_SERVICES = 4; 78 79 /** 80 * The enabled services list. This is filled from the {@link #LOADER_ID_ENABLED_SERVICES} 81 * loader in {@link PrintServiceInfoLoaderCallbacks#onLoadFinished}. 82 */ 83 private EnabledServicesAdapter mEnabledServicesAdapter; 84 85 /** 86 * The disabled services list. This is filled from the {@link #LOADER_ID_DISABLED_SERVICES} 87 * loader in {@link PrintServiceInfoLoaderCallbacks#onLoadFinished}. 88 */ 89 private DisabledServicesAdapter mDisabledServicesAdapter; 90 91 /** 92 * The recommended services list. This is filled from the 93 * {@link #LOADER_ID_RECOMMENDED_SERVICES} loader in 94 * {@link PrintServicePrintServiceRecommendationLoaderCallbacks#onLoadFinished}. 95 */ 96 private RecommendedServicesAdapter mRecommendedServicesAdapter; 97 98 @Override 99 protected void onCreate(@Nullable Bundle savedInstanceState) { 100 super.onCreate(savedInstanceState); 101 102 setContentView(R.layout.add_printer_activity); 103 104 mEnabledServicesAdapter = new EnabledServicesAdapter(); 105 mDisabledServicesAdapter = new DisabledServicesAdapter(); 106 mRecommendedServicesAdapter = new RecommendedServicesAdapter(); 107 108 ArrayList<ActionAdapter> adapterList = new ArrayList<>(3); 109 adapterList.add(mEnabledServicesAdapter); 110 adapterList.add(mRecommendedServicesAdapter); 111 adapterList.add(mDisabledServicesAdapter); 112 113 setListAdapter(new CombinedAdapter(adapterList)); 114 115 getListView().setOnItemClickListener(this); 116 117 PrintServiceInfoLoaderCallbacks printServiceLoaderCallbacks = 118 new PrintServiceInfoLoaderCallbacks(); 119 120 getLoaderManager().initLoader(LOADER_ID_ENABLED_SERVICES, null, printServiceLoaderCallbacks); 121 getLoaderManager().initLoader(LOADER_ID_DISABLED_SERVICES, null, printServiceLoaderCallbacks); 122 getLoaderManager().initLoader(LOADER_ID_RECOMMENDED_SERVICES, null, 123 new PrintServicePrintServiceRecommendationLoaderCallbacks()); 124 getLoaderManager().initLoader(LOADER_ID_ALL_SERVICES, null, printServiceLoaderCallbacks); 125 } 126 127 /** 128 * Callbacks for the loaders operating on list of {@link PrintServiceInfo print service infos}. 129 */ 130 private class PrintServiceInfoLoaderCallbacks implements 131 LoaderManager.LoaderCallbacks<List<PrintServiceInfo>> { 132 @Override 133 public Loader<List<PrintServiceInfo>> onCreateLoader(int id, Bundle args) { 134 switch (id) { 135 case LOADER_ID_ENABLED_SERVICES: 136 return new PrintServicesLoader( 137 (PrintManager) getSystemService(Context.PRINT_SERVICE), 138 AddPrinterActivity.this, PrintManager.ENABLED_SERVICES); 139 case LOADER_ID_DISABLED_SERVICES: 140 return new PrintServicesLoader( 141 (PrintManager) getSystemService(Context.PRINT_SERVICE), 142 AddPrinterActivity.this, PrintManager.DISABLED_SERVICES); 143 case LOADER_ID_ALL_SERVICES: 144 return new PrintServicesLoader( 145 (PrintManager) getSystemService(Context.PRINT_SERVICE), 146 AddPrinterActivity.this, PrintManager.ALL_SERVICES); 147 default: 148 // not reached 149 return null; 150 } 151 } 152 153 154 @Override 155 public void onLoadFinished(Loader<List<PrintServiceInfo>> loader, 156 List<PrintServiceInfo> data) { 157 switch (loader.getId()) { 158 case LOADER_ID_ENABLED_SERVICES: 159 mEnabledServicesAdapter.updateData(data); 160 break; 161 case LOADER_ID_DISABLED_SERVICES: 162 mDisabledServicesAdapter.updateData(data); 163 break; 164 case LOADER_ID_ALL_SERVICES: 165 mRecommendedServicesAdapter.updateInstalledServices(data); 166 default: 167 // not reached 168 } 169 } 170 171 @Override 172 public void onLoaderReset(Loader<List<PrintServiceInfo>> loader) { 173 if (!isFinishing()) { 174 switch (loader.getId()) { 175 case LOADER_ID_ENABLED_SERVICES: 176 mEnabledServicesAdapter.updateData(null); 177 break; 178 case LOADER_ID_DISABLED_SERVICES: 179 mDisabledServicesAdapter.updateData(null); 180 break; 181 case LOADER_ID_ALL_SERVICES: 182 mRecommendedServicesAdapter.updateInstalledServices(null); 183 break; 184 default: 185 // not reached 186 } 187 } 188 } 189 } 190 191 /** 192 * Callbacks for the loaders operating on list of {@link RecommendationInfo print service 193 * recommendations}. 194 */ 195 private class PrintServicePrintServiceRecommendationLoaderCallbacks implements 196 LoaderManager.LoaderCallbacks<List<RecommendationInfo>> { 197 @Override 198 public Loader<List<RecommendationInfo>> onCreateLoader(int id, Bundle args) { 199 return new PrintServiceRecommendationsLoader( 200 (PrintManager) getSystemService(Context.PRINT_SERVICE), 201 AddPrinterActivity.this); 202 } 203 204 205 @Override 206 public void onLoadFinished(Loader<List<RecommendationInfo>> loader, 207 List<RecommendationInfo> data) { 208 mRecommendedServicesAdapter.updateRecommendations(data); 209 } 210 211 @Override 212 public void onLoaderReset(Loader<List<RecommendationInfo>> loader) { 213 if (!isFinishing()) { 214 mRecommendedServicesAdapter.updateRecommendations(null); 215 } 216 } 217 } 218 219 @Override 220 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 221 ((ActionAdapter) getListAdapter()).performAction(position); 222 } 223 224 /** 225 * Marks an adapter that can can perform an action for a position in it's list. 226 */ 227 private abstract class ActionAdapter extends BaseAdapter { 228 /** 229 * Perform the action for a position in the list. 230 * 231 * @param position The position of the item 232 */ 233 abstract void performAction(@IntRange(from = 0) int position); 234 235 @Override 236 public boolean areAllItemsEnabled() { 237 return false; 238 } 239 } 240 241 /** 242 * An adapter presenting multiple sub adapters as a single combined adapter. 243 */ 244 private class CombinedAdapter extends ActionAdapter { 245 /** The adapters to combine */ 246 private final @NonNull ArrayList<ActionAdapter> mAdapters; 247 248 /** 249 * Create a combined adapter. 250 * 251 * @param adapters the list of adapters to combine 252 */ 253 CombinedAdapter(@NonNull ArrayList<ActionAdapter> adapters) { 254 mAdapters = adapters; 255 256 final int numAdapters = mAdapters.size(); 257 for (int i = 0; i < numAdapters; i++) { 258 mAdapters.get(i).registerDataSetObserver(new DataSetObserver() { 259 @Override 260 public void onChanged() { 261 notifyDataSetChanged(); 262 } 263 264 @Override 265 public void onInvalidated() { 266 notifyDataSetChanged(); 267 } 268 }); 269 } 270 } 271 272 @Override 273 public int getCount() { 274 int totalCount = 0; 275 276 final int numAdapters = mAdapters.size(); 277 for (int i = 0; i < numAdapters; i++) { 278 totalCount += mAdapters.get(i).getCount(); 279 } 280 281 return totalCount; 282 } 283 284 /** 285 * Find the sub adapter and the position in the sub-adapter the position in the combined 286 * adapter refers to. 287 * 288 * @param position The position in the combined adapter 289 * 290 * @return The pair of adapter and position in sub adapter 291 */ 292 private @NonNull Pair<ActionAdapter, Integer> getSubAdapter(int position) { 293 final int numAdapters = mAdapters.size(); 294 for (int i = 0; i < numAdapters; i++) { 295 ActionAdapter adapter = mAdapters.get(i); 296 297 if (position < adapter.getCount()) { 298 return new Pair<>(adapter, position); 299 } else { 300 position -= adapter.getCount(); 301 } 302 } 303 304 throw new IllegalArgumentException("Invalid position"); 305 } 306 307 @Override 308 public int getItemViewType(int position) { 309 int numLowerViewTypes = 0; 310 311 final int numAdapters = mAdapters.size(); 312 for (int i = 0; i < numAdapters; i++) { 313 Adapter adapter = mAdapters.get(i); 314 315 if (position < adapter.getCount()) { 316 return numLowerViewTypes + adapter.getItemViewType(position); 317 } else { 318 numLowerViewTypes += adapter.getViewTypeCount(); 319 position -= adapter.getCount(); 320 } 321 } 322 323 throw new IllegalArgumentException("Invalid position"); 324 } 325 326 @Override 327 public int getViewTypeCount() { 328 int totalViewCount = 0; 329 330 final int numAdapters = mAdapters.size(); 331 for (int i = 0; i < numAdapters; i++) { 332 totalViewCount += mAdapters.get(i).getViewTypeCount(); 333 } 334 335 return totalViewCount; 336 } 337 338 @Override 339 public View getView(int position, View convertView, ViewGroup parent) { 340 Pair<ActionAdapter, Integer> realPosition = getSubAdapter(position); 341 342 return realPosition.first.getView(realPosition.second, convertView, parent); 343 } 344 345 @Override 346 public Object getItem(int position) { 347 Pair<ActionAdapter, Integer> realPosition = getSubAdapter(position); 348 349 return realPosition.first.getItem(realPosition.second); 350 } 351 352 @Override 353 public long getItemId(int position) { 354 return position; 355 } 356 357 @Override 358 public boolean isEnabled(int position) { 359 Pair<ActionAdapter, Integer> realPosition = getSubAdapter(position); 360 361 return realPosition.first.isEnabled(realPosition.second); 362 } 363 364 @Override 365 public void performAction(@IntRange(from = 0) int position) { 366 Pair<ActionAdapter, Integer> realPosition = getSubAdapter(position); 367 368 realPosition.first.performAction(realPosition.second); 369 } 370 } 371 372 /** 373 * Superclass for all adapters that just display a list of {@link PrintServiceInfo}. 374 */ 375 private abstract class PrintServiceInfoAdapter extends ActionAdapter { 376 /** 377 * Raw data of the list. 378 * 379 * @see #updateData(List) 380 */ 381 private @NonNull List<PrintServiceInfo> mServices; 382 383 /** 384 * Create a new adapter. 385 */ 386 PrintServiceInfoAdapter() { 387 mServices = Collections.emptyList(); 388 } 389 390 /** 391 * Update the data. 392 * 393 * @param services The new raw data. 394 */ 395 void updateData(@Nullable List<PrintServiceInfo> services) { 396 if (services == null || services.isEmpty()) { 397 mServices = Collections.emptyList(); 398 } else { 399 mServices = services; 400 } 401 402 notifyDataSetChanged(); 403 } 404 405 @Override 406 public int getViewTypeCount() { 407 return 2; 408 } 409 410 @Override 411 public int getItemViewType(int position) { 412 if (position == 0) { 413 return 0; 414 } else { 415 return 1; 416 } 417 } 418 419 @Override 420 public int getCount() { 421 if (mServices.isEmpty()) { 422 return 0; 423 } else { 424 return mServices.size() + 1; 425 } 426 } 427 428 @Override 429 public Object getItem(int position) { 430 if (position == 0) { 431 return null; 432 } else { 433 return mServices.get(position - 1); 434 } 435 } 436 437 @Override 438 public boolean isEnabled(int position) { 439 return position != 0; 440 } 441 442 @Override 443 public long getItemId(int position) { 444 return position; 445 } 446 } 447 448 /** 449 * Adapter for the enabled services. 450 */ 451 private class EnabledServicesAdapter extends PrintServiceInfoAdapter { 452 @Override 453 public void performAction(@IntRange(from = 0) int position) { 454 Intent intent = getAddPrinterIntent((PrintServiceInfo) getItem(position)); 455 if (intent != null) { 456 try { 457 startActivity(intent); 458 } catch (ActivityNotFoundException|SecurityException e) { 459 Log.e(LOG_TAG, "Cannot start add printers activity", e); 460 } 461 } 462 } 463 464 /** 465 * Get the intent used to launch the add printers activity. 466 * 467 * @param service The service the printer should be added for 468 * 469 * @return The intent to launch the activity or null if the activity could not be launched. 470 */ 471 private Intent getAddPrinterIntent(@NonNull PrintServiceInfo service) { 472 String addPrinterActivityName = service.getAddPrintersActivityName(); 473 474 if (!TextUtils.isEmpty(addPrinterActivityName)) { 475 Intent intent = new Intent(Intent.ACTION_MAIN); 476 intent.setComponent(new ComponentName(service.getComponentName().getPackageName(), 477 addPrinterActivityName)); 478 479 List<ResolveInfo> resolvedActivities = getPackageManager().queryIntentActivities( 480 intent, 0); 481 if (!resolvedActivities.isEmpty()) { 482 // The activity is a component name, therefore it is one or none. 483 if (resolvedActivities.get(0).activityInfo.exported) { 484 return intent; 485 } 486 } 487 } 488 489 return null; 490 } 491 492 @Override 493 public View getView(int position, View convertView, ViewGroup parent) { 494 if (position == 0) { 495 if (convertView == null) { 496 convertView = getLayoutInflater().inflate(R.layout.add_printer_list_header, 497 parent, false); 498 } 499 500 ((TextView) convertView.findViewById(R.id.text)) 501 .setText(R.string.enabled_services_title); 502 503 return convertView; 504 } 505 506 if (convertView == null) { 507 convertView = getLayoutInflater().inflate(R.layout.enabled_print_services_list_item, 508 parent, false); 509 } 510 511 PrintServiceInfo service = (PrintServiceInfo) getItem(position); 512 513 TextView title = (TextView) convertView.findViewById(R.id.title); 514 ImageView icon = (ImageView) convertView.findViewById(R.id.icon); 515 TextView subtitle = (TextView) convertView.findViewById(R.id.subtitle); 516 517 title.setText(service.getResolveInfo().loadLabel(getPackageManager())); 518 icon.setImageDrawable(service.getResolveInfo().loadIcon(getPackageManager())); 519 520 if (getAddPrinterIntent(service) == null) { 521 subtitle.setText(getString(R.string.cannot_add_printer)); 522 } else { 523 subtitle.setText(getString(R.string.select_to_add_printers)); 524 } 525 526 return convertView; 527 } 528 } 529 530 /** 531 * Adapter for the disabled services. 532 */ 533 private class DisabledServicesAdapter extends PrintServiceInfoAdapter { 534 @Override 535 public void performAction(@IntRange(from = 0) int position) { 536 ((PrintManager) getSystemService(Context.PRINT_SERVICE)).setPrintServiceEnabled( 537 ((PrintServiceInfo) getItem(position)).getComponentName(), true); 538 } 539 540 @Override 541 public View getView(int position, View convertView, ViewGroup parent) { 542 if (position == 0) { 543 if (convertView == null) { 544 convertView = getLayoutInflater().inflate(R.layout.add_printer_list_header, 545 parent, false); 546 } 547 548 ((TextView) convertView.findViewById(R.id.text)) 549 .setText(R.string.disabled_services_title); 550 551 return convertView; 552 } 553 554 if (convertView == null) { 555 convertView = getLayoutInflater().inflate( 556 R.layout.disabled_print_services_list_item, parent, false); 557 } 558 559 PrintServiceInfo service = (PrintServiceInfo) getItem(position); 560 561 TextView title = (TextView) convertView.findViewById(R.id.title); 562 ImageView icon = (ImageView) convertView.findViewById(R.id.icon); 563 564 title.setText(service.getResolveInfo().loadLabel(getPackageManager())); 565 icon.setImageDrawable(service.getResolveInfo().loadIcon(getPackageManager())); 566 567 return convertView; 568 } 569 } 570 571 /** 572 * Adapter for the recommended services. 573 */ 574 private class RecommendedServicesAdapter extends ActionAdapter { 575 /** Package names of all installed print services */ 576 private @NonNull final ArraySet<String> mInstalledServices; 577 578 /** All print service recommendations */ 579 private @Nullable List<RecommendationInfo> mRecommendations; 580 581 /** 582 * Sorted print service recommendations for services that are not installed 583 * 584 * @see #filterRecommendations 585 */ 586 private @Nullable List<RecommendationInfo> mFilteredRecommendations; 587 588 /** 589 * Create a new adapter. 590 */ 591 private RecommendedServicesAdapter() { 592 mInstalledServices = new ArraySet<>(); 593 } 594 595 @Override 596 public int getCount() { 597 if (mFilteredRecommendations == null) { 598 return 2; 599 } else { 600 return mFilteredRecommendations.size() + 2; 601 } 602 } 603 604 @Override 605 public int getViewTypeCount() { 606 return 3; 607 } 608 609 /** 610 * @return The position the all services link is at. 611 */ 612 private int getAllServicesPos() { 613 return getCount() - 1; 614 } 615 616 @Override 617 public int getItemViewType(int position) { 618 if (position == 0) { 619 return 0; 620 } else if (getAllServicesPos() == position) { 621 return 1; 622 } else { 623 return 2; 624 } 625 } 626 627 @Override 628 public Object getItem(int position) { 629 if (position == 0 || position == getAllServicesPos()) { 630 return null; 631 } else { 632 return mFilteredRecommendations.get(position - 1); 633 } 634 } 635 636 @Override 637 public long getItemId(int position) { 638 return position; 639 } 640 641 @Override 642 public View getView(int position, View convertView, ViewGroup parent) { 643 if (position == 0) { 644 if (convertView == null) { 645 convertView = getLayoutInflater().inflate(R.layout.add_printer_list_header, 646 parent, false); 647 } 648 649 ((TextView) convertView.findViewById(R.id.text)) 650 .setText(R.string.recommended_services_title); 651 652 return convertView; 653 } else if (position == getAllServicesPos()) { 654 if (convertView == null) { 655 convertView = getLayoutInflater().inflate(R.layout.all_print_services_list_item, 656 parent, false); 657 } 658 } else { 659 RecommendationInfo recommendation = (RecommendationInfo) getItem(position); 660 661 if (convertView == null) { 662 convertView = getLayoutInflater().inflate( 663 R.layout.print_service_recommendations_list_item, parent, false); 664 } 665 666 ((TextView) convertView.findViewById(R.id.title)).setText(recommendation.getName()); 667 668 ((TextView) convertView.findViewById(R.id.subtitle)).setText(getResources() 669 .getQuantityString(R.plurals.print_services_recommendation_subtitle, 670 recommendation.getNumDiscoveredPrinters(), 671 recommendation.getNumDiscoveredPrinters())); 672 673 return convertView; 674 } 675 676 return convertView; 677 } 678 679 @Override 680 public boolean isEnabled(int position) { 681 return position != 0; 682 } 683 684 @Override 685 public void performAction(@IntRange(from = 0) int position) { 686 if (position == getAllServicesPos()) { 687 String searchUri = Settings.Secure 688 .getString(getContentResolver(), Settings.Secure.PRINT_SERVICE_SEARCH_URI); 689 690 if (searchUri != null) { 691 try { 692 startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(searchUri))); 693 } catch (ActivityNotFoundException e) { 694 Log.e(LOG_TAG, "Cannot start market", e); 695 } 696 } 697 } else { 698 RecommendationInfo recommendation = (RecommendationInfo) getItem(position); 699 700 try { 701 startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(getString( 702 R.string.uri_package_details, recommendation.getPackageName())))); 703 } catch (ActivityNotFoundException e) { 704 Log.e(LOG_TAG, "Cannot start market", e); 705 } 706 } 707 } 708 709 /** 710 * Filter recommended services. 711 */ 712 private void filterRecommendations() { 713 if (mRecommendations == null) { 714 mFilteredRecommendations = null; 715 } else { 716 mFilteredRecommendations = new ArrayList<>(); 717 718 // Filter out recommendations for already installed services 719 final int numRecommendations = mRecommendations.size(); 720 for (int i = 0; i < numRecommendations; i++) { 721 RecommendationInfo recommendation = mRecommendations.get(i); 722 723 if (!mInstalledServices.contains(recommendation.getPackageName())) { 724 mFilteredRecommendations.add(recommendation); 725 } 726 } 727 } 728 729 notifyDataSetChanged(); 730 } 731 732 /** 733 * Update the installed print services. 734 * 735 * @param services The new set of services 736 */ 737 public void updateInstalledServices(List<PrintServiceInfo> services) { 738 mInstalledServices.clear(); 739 740 if (services != null) { 741 final int numServices = services.size(); 742 for (int i = 0; i < numServices; i++) { 743 mInstalledServices.add(services.get(i).getComponentName().getPackageName()); 744 } 745 } 746 747 filterRecommendations(); 748 } 749 750 /** 751 * Update the recommended print services. 752 * 753 * @param recommendations The new set of recommendations 754 */ 755 public void updateRecommendations(List<RecommendationInfo> recommendations) { 756 if (recommendations != null) { 757 final Collator collator = Collator.getInstance(); 758 759 // Sort recommendations (early conditions are more important) 760 // - higher number of discovered printers first 761 // - single vendor services first 762 // - alphabetically 763 Collections.sort(recommendations, 764 new Comparator<RecommendationInfo>() { 765 @Override public int compare(RecommendationInfo o1, 766 RecommendationInfo o2) { 767 if (o1.getNumDiscoveredPrinters() != 768 o2.getNumDiscoveredPrinters()) { 769 return o2.getNumDiscoveredPrinters() - 770 o1.getNumDiscoveredPrinters(); 771 } else if (o1.recommendsMultiVendorService() 772 != o2.recommendsMultiVendorService()) { 773 if (o1.recommendsMultiVendorService()) { 774 return 1; 775 } else { 776 return -1; 777 } 778 } else { 779 return collator.compare(o1.getName().toString(), 780 o2.getName().toString()); 781 } 782 } 783 }); 784 } 785 786 mRecommendations = recommendations; 787 788 filterRecommendations(); 789 } 790 } 791 } 792