Home | History | Annotate | Download | only in ui
      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