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