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