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
17package com.android.printspooler.ui;
18
19import android.annotation.IntRange;
20import android.annotation.NonNull;
21import android.annotation.Nullable;
22import android.app.ListActivity;
23import android.app.LoaderManager;
24import android.content.ActivityNotFoundException;
25import android.content.ComponentName;
26import android.content.Context;
27import android.content.Intent;
28import android.content.Loader;
29import android.content.pm.ResolveInfo;
30import android.database.DataSetObserver;
31import android.net.Uri;
32import android.os.Bundle;
33import android.print.PrintManager;
34import android.printservice.recommendation.RecommendationInfo;
35import android.print.PrintServiceRecommendationsLoader;
36import android.print.PrintServicesLoader;
37import android.printservice.PrintServiceInfo;
38import android.provider.Settings;
39import android.text.TextUtils;
40import android.util.ArraySet;
41import android.util.Log;
42import android.util.Pair;
43import android.view.View;
44import android.view.ViewGroup;
45import android.widget.Adapter;
46import android.widget.AdapterView;
47import android.widget.BaseAdapter;
48import android.widget.ImageView;
49import android.widget.TextView;
50import com.android.printspooler.R;
51
52import java.text.Collator;
53import java.util.ArrayList;
54import java.util.Collections;
55import java.util.Comparator;
56import 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 */
70public 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            final int numServices = services.size();
741            for (int i = 0; i < numServices; i++) {
742                mInstalledServices.add(services.get(i).getComponentName().getPackageName());
743            }
744
745            filterRecommendations();
746        }
747
748        /**
749         * Update the recommended print services.
750         *
751         * @param recommendations The new set of recommendations
752         */
753        public void updateRecommendations(List<RecommendationInfo> recommendations) {
754            if (recommendations != null) {
755                final Collator collator = Collator.getInstance();
756
757                // Sort recommendations (early conditions are more important)
758                // - higher number of discovered printers first
759                // - single vendor services first
760                // - alphabetically
761                Collections.sort(recommendations,
762                        new Comparator<RecommendationInfo>() {
763                            @Override public int compare(RecommendationInfo o1,
764                                    RecommendationInfo o2) {
765                                if (o1.getNumDiscoveredPrinters() !=
766                                        o2.getNumDiscoveredPrinters()) {
767                                    return o2.getNumDiscoveredPrinters() -
768                                            o1.getNumDiscoveredPrinters();
769                                } else if (o1.recommendsMultiVendorService()
770                                        != o2.recommendsMultiVendorService()) {
771                                    if (o1.recommendsMultiVendorService()) {
772                                        return 1;
773                                    } else {
774                                        return -1;
775                                    }
776                                } else {
777                                    return collator.compare(o1.getName().toString(),
778                                            o2.getName().toString());
779                                }
780                            }
781                        });
782            }
783
784            mRecommendations = recommendations;
785
786            filterRecommendations();
787        }
788    }
789}
790