SelectPrinterActivity.java revision 8141bdfa56f13c3946bed12ba7801e492ec25c11
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.app.Activity;
20import android.app.AlertDialog;
21import android.app.Dialog;
22import android.app.DialogFragment;
23import android.app.Fragment;
24import android.app.FragmentTransaction;
25import android.content.ActivityNotFoundException;
26import android.content.ComponentName;
27import android.content.Context;
28import android.content.DialogInterface;
29import android.content.Intent;
30import android.content.IntentSender.SendIntentException;
31import android.content.pm.ActivityInfo;
32import android.content.pm.PackageInfo;
33import android.content.pm.PackageManager;
34import android.content.pm.PackageManager.NameNotFoundException;
35import android.content.pm.ResolveInfo;
36import android.content.pm.ServiceInfo;
37import android.database.ContentObserver;
38import android.database.DataSetObserver;
39import android.graphics.drawable.Drawable;
40import android.net.Uri;
41import android.os.Bundle;
42import android.os.Handler;
43import android.print.PrintManager;
44import android.print.PrinterId;
45import android.print.PrinterInfo;
46import android.printservice.PrintServiceInfo;
47import android.provider.Settings;
48import android.text.TextUtils;
49import android.util.Log;
50import android.view.ContextMenu;
51import android.view.ContextMenu.ContextMenuInfo;
52import android.view.Menu;
53import android.view.MenuItem;
54import android.view.View;
55import android.view.View.OnClickListener;
56import android.view.ViewGroup;
57import android.view.accessibility.AccessibilityManager;
58import android.widget.AdapterView;
59import android.widget.AdapterView.AdapterContextMenuInfo;
60import android.widget.ArrayAdapter;
61import android.widget.BaseAdapter;
62import android.widget.Filter;
63import android.widget.Filterable;
64import android.widget.ImageView;
65import android.widget.ListView;
66import android.widget.SearchView;
67import android.widget.TextView;
68
69import com.android.internal.content.PackageMonitor;
70import com.android.printspooler.R;
71
72import java.util.ArrayList;
73import java.util.List;
74
75/**
76 * This is an activity for selecting a printer.
77 */
78public final class SelectPrinterActivity extends Activity {
79
80    private static final String LOG_TAG = "SelectPrinterFragment";
81
82    public static final String INTENT_EXTRA_PRINTER_ID = "INTENT_EXTRA_PRINTER_ID";
83
84    private static final String FRAGMENT_TAG_ADD_PRINTER_DIALOG =
85            "FRAGMENT_TAG_ADD_PRINTER_DIALOG";
86
87    private static final String FRAGMENT_ARGUMENT_PRINT_SERVICE_INFOS =
88            "FRAGMENT_ARGUMENT_PRINT_SERVICE_INFOS";
89
90    private static final String EXTRA_PRINTER_ID = "EXTRA_PRINTER_ID";
91
92    /** If there are any enabled print services */
93    private boolean mHasEnabledPrintServices;
94
95    private final ArrayList<PrintServiceInfo> mAddPrinterServices =
96            new ArrayList<>();
97
98    private PrinterRegistry mPrinterRegistry;
99
100    private ListView mListView;
101
102    private AnnounceFilterResult mAnnounceFilterResult;
103
104    /** Monitor if new print services get enabled or disabled */
105    private ContentObserver mPrintServicesDisabledObserver;
106    private PackageMonitor mPackageObserver;
107
108    @Override
109    public void onCreate(Bundle savedInstanceState) {
110        super.onCreate(savedInstanceState);
111        getActionBar().setIcon(R.drawable.ic_print);
112
113        setContentView(R.layout.select_printer_activity);
114
115        mPrinterRegistry = new PrinterRegistry(this, null);
116
117        // Hook up the list view.
118        mListView = (ListView) findViewById(android.R.id.list);
119        final DestinationAdapter adapter = new DestinationAdapter();
120        adapter.registerDataSetObserver(new DataSetObserver() {
121            @Override
122            public void onChanged() {
123                if (!isFinishing() && adapter.getCount() <= 0) {
124                    updateEmptyView(adapter);
125                }
126            }
127
128            @Override
129            public void onInvalidated() {
130                if (!isFinishing()) {
131                    updateEmptyView(adapter);
132                }
133            }
134        });
135        mListView.setAdapter(adapter);
136
137        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
138            @Override
139            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
140                if (!((DestinationAdapter) mListView.getAdapter()).isActionable(position)) {
141                    return;
142                }
143
144                PrinterInfo printer = (PrinterInfo) mListView.getAdapter().getItem(position);
145                onPrinterSelected(printer.getId());
146            }
147        });
148
149        registerForContextMenu(mListView);
150    }
151
152    @Override
153    public boolean onCreateOptionsMenu(Menu menu) {
154        super.onCreateOptionsMenu(menu);
155
156        getMenuInflater().inflate(R.menu.select_printer_activity, menu);
157
158        MenuItem searchItem = menu.findItem(R.id.action_search);
159        SearchView searchView = (SearchView) searchItem.getActionView();
160        searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
161            @Override
162            public boolean onQueryTextSubmit(String query) {
163                return true;
164            }
165
166            @Override
167            public boolean onQueryTextChange(String searchString) {
168                ((DestinationAdapter) mListView.getAdapter()).getFilter().filter(searchString);
169                return true;
170            }
171        });
172        searchView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
173            @Override
174            public void onViewAttachedToWindow(View view) {
175                if (AccessibilityManager.getInstance(SelectPrinterActivity.this).isEnabled()) {
176                    view.announceForAccessibility(getString(
177                            R.string.print_search_box_shown_utterance));
178                }
179            }
180            @Override
181            public void onViewDetachedFromWindow(View view) {
182                if (!isFinishing() && AccessibilityManager.getInstance(
183                        SelectPrinterActivity.this).isEnabled()) {
184                    view.announceForAccessibility(getString(
185                            R.string.print_search_box_hidden_utterance));
186                }
187            }
188        });
189
190        return true;
191    }
192
193    @Override
194    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) {
195        if (view == mListView) {
196            final int position = ((AdapterContextMenuInfo) menuInfo).position;
197            PrinterInfo printer = (PrinterInfo) mListView.getAdapter().getItem(position);
198
199            menu.setHeaderTitle(printer.getName());
200
201            // Add the select menu item if applicable.
202            if (printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE) {
203                MenuItem selectItem = menu.add(Menu.NONE, R.string.print_select_printer,
204                        Menu.NONE, R.string.print_select_printer);
205                Intent intent = new Intent();
206                intent.putExtra(EXTRA_PRINTER_ID, printer.getId());
207                selectItem.setIntent(intent);
208            }
209
210            // Add the forget menu item if applicable.
211            if (mPrinterRegistry.isFavoritePrinter(printer.getId())) {
212                MenuItem forgetItem = menu.add(Menu.NONE, R.string.print_forget_printer,
213                        Menu.NONE, R.string.print_forget_printer);
214                Intent intent = new Intent();
215                intent.putExtra(EXTRA_PRINTER_ID, printer.getId());
216                forgetItem.setIntent(intent);
217            }
218        }
219    }
220
221    @Override
222    public boolean onContextItemSelected(MenuItem item) {
223        switch (item.getItemId()) {
224            case R.string.print_select_printer: {
225                PrinterId printerId = item.getIntent().getParcelableExtra(EXTRA_PRINTER_ID);
226                onPrinterSelected(printerId);
227            } return true;
228
229            case R.string.print_forget_printer: {
230                PrinterId printerId = item.getIntent().getParcelableExtra(EXTRA_PRINTER_ID);
231                mPrinterRegistry.forgetFavoritePrinter(printerId);
232            } return true;
233        }
234        return false;
235    }
236
237    /**
238     * Adjust the UI if the enabled print services changed.
239     */
240    private synchronized void onPrintServicesUpdate() {
241        updateServicesWithAddPrinterActivity();
242        updateEmptyView((DestinationAdapter)mListView.getAdapter());
243        invalidateOptionsMenu();
244    }
245
246    /**
247     * Register listener for changes to the enabled print services.
248     */
249    private void registerServiceMonitor() {
250        // Listen for services getting disabled
251        mPrintServicesDisabledObserver = new ContentObserver(new Handler()) {
252            @Override
253            public void onChange(boolean selfChange) {
254                onPrintServicesUpdate();
255            }
256        };
257
258        // Listen for services getting installed or uninstalled
259        mPackageObserver = new PackageMonitor() {
260            @Override
261            public void onPackageModified(String packageName) {
262                onPrintServicesUpdate();
263            }
264
265            @Override
266            public void onPackageRemoved(String packageName, int uid) {
267                onPrintServicesUpdate();
268            }
269
270            @Override
271            public void onPackageAdded(String packageName, int uid) {
272                onPrintServicesUpdate();
273            }
274        };
275
276        getContentResolver().registerContentObserver(
277                Settings.Secure.getUriFor(Settings.Secure.DISABLED_PRINT_SERVICES), false,
278                mPrintServicesDisabledObserver);
279
280        mPackageObserver.register(this, getMainLooper(), false);
281    }
282
283    /**
284     * Unregister the listeners for changes to the enabled print services.
285     */
286    private void unregisterServiceMonitorIfNeeded() {
287        getContentResolver().unregisterContentObserver(mPrintServicesDisabledObserver);
288        mPackageObserver.unregister();
289    }
290
291    @Override
292    public void onStart() {
293        super.onStart();
294        registerServiceMonitor();
295        onPrintServicesUpdate();
296    }
297
298    @Override
299    public void onPause() {
300        if (mAnnounceFilterResult != null) {
301            mAnnounceFilterResult.remove();
302        }
303        super.onPause();
304    }
305
306    @Override
307    public void onStop() {
308        unregisterServiceMonitorIfNeeded();
309        super.onStop();
310    }
311
312    @Override
313    public boolean onOptionsItemSelected(MenuItem item) {
314        if (item.getItemId() == R.id.action_add_printer) {
315            showAddPrinterSelectionDialog();
316            return true;
317        }
318        return super.onOptionsItemSelected(item);
319    }
320
321    private void onPrinterSelected(PrinterId printerId) {
322        Intent intent = new Intent();
323        intent.putExtra(INTENT_EXTRA_PRINTER_ID, printerId);
324        setResult(RESULT_OK, intent);
325        finish();
326    }
327
328    private void updateServicesWithAddPrinterActivity() {
329        mHasEnabledPrintServices = true;
330        mAddPrinterServices.clear();
331
332        // Get all enabled print services.
333        PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE);
334        List<PrintServiceInfo> enabledServices = printManager.getEnabledPrintServices();
335
336        // No enabled print services - done.
337        if (enabledServices.isEmpty()) {
338            mHasEnabledPrintServices = false;
339            return;
340        }
341
342        // Find the services with valid add printers activities.
343        final int enabledServiceCount = enabledServices.size();
344        for (int i = 0; i < enabledServiceCount; i++) {
345            PrintServiceInfo enabledService = enabledServices.get(i);
346
347            // No add printers activity declared - next.
348            if (TextUtils.isEmpty(enabledService.getAddPrintersActivityName())) {
349                continue;
350            }
351
352            ServiceInfo serviceInfo = enabledService.getResolveInfo().serviceInfo;
353            ComponentName addPrintersComponentName = new ComponentName(
354                    serviceInfo.packageName, enabledService.getAddPrintersActivityName());
355            Intent addPritnersIntent = new Intent()
356                .setComponent(addPrintersComponentName);
357
358            // The add printers activity is valid - add it.
359            PackageManager pm = getPackageManager();
360            List<ResolveInfo> resolvedActivities = pm.queryIntentActivities(addPritnersIntent, 0);
361            if (!resolvedActivities.isEmpty()) {
362                // The activity is a component name, therefore it is one or none.
363                ActivityInfo activityInfo = resolvedActivities.get(0).activityInfo;
364                if (activityInfo.exported
365                        && (activityInfo.permission == null
366                                || pm.checkPermission(activityInfo.permission, getPackageName())
367                                        == PackageManager.PERMISSION_GRANTED)) {
368                    mAddPrinterServices.add(enabledService);
369                }
370            }
371        }
372    }
373
374    private void showAddPrinterSelectionDialog() {
375        FragmentTransaction transaction = getFragmentManager().beginTransaction();
376        Fragment oldFragment = getFragmentManager().findFragmentByTag(
377                FRAGMENT_TAG_ADD_PRINTER_DIALOG);
378        if (oldFragment != null) {
379            transaction.remove(oldFragment);
380        }
381        AddPrinterAlertDialogFragment newFragment = new AddPrinterAlertDialogFragment();
382        Bundle arguments = new Bundle();
383        arguments.putParcelableArrayList(FRAGMENT_ARGUMENT_PRINT_SERVICE_INFOS,
384                mAddPrinterServices);
385        newFragment.setArguments(arguments);
386        transaction.add(newFragment, FRAGMENT_TAG_ADD_PRINTER_DIALOG);
387        transaction.commit();
388    }
389
390    public void updateEmptyView(DestinationAdapter adapter) {
391        if (mListView.getEmptyView() == null) {
392            View emptyView = findViewById(R.id.empty_print_state);
393            mListView.setEmptyView(emptyView);
394        }
395        TextView titleView = (TextView) findViewById(R.id.title);
396        View progressBar = findViewById(R.id.progress_bar);
397        if (!mHasEnabledPrintServices) {
398            titleView.setText(R.string.print_no_print_services);
399            progressBar.setVisibility(View.GONE);
400        } else if (adapter.getUnfilteredCount() <= 0) {
401            titleView.setText(R.string.print_searching_for_printers);
402            progressBar.setVisibility(View.VISIBLE);
403        } else {
404            titleView.setText(R.string.print_no_printers);
405            progressBar.setVisibility(View.GONE);
406        }
407    }
408
409    private void announceSearchResultIfNeeded() {
410        if (AccessibilityManager.getInstance(this).isEnabled()) {
411            if (mAnnounceFilterResult == null) {
412                mAnnounceFilterResult = new AnnounceFilterResult();
413            }
414            mAnnounceFilterResult.post();
415        }
416    }
417
418    public static class AddPrinterAlertDialogFragment extends DialogFragment {
419
420        private String mAddPrintServiceItem;
421
422        @Override
423        @SuppressWarnings("unchecked")
424        public Dialog onCreateDialog(Bundle savedInstanceState) {
425            AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
426                    .setTitle(R.string.choose_print_service);
427
428            final List<PrintServiceInfo> printServices = (List<PrintServiceInfo>) (List<?>)
429                    getArguments().getParcelableArrayList(FRAGMENT_ARGUMENT_PRINT_SERVICE_INFOS);
430
431            final ArrayAdapter<String> adapter = new ArrayAdapter<>(
432                    getActivity(), android.R.layout.simple_list_item_1);
433            final int printServiceCount = printServices.size();
434            for (int i = 0; i < printServiceCount; i++) {
435                PrintServiceInfo printService = printServices.get(i);
436                adapter.add(printService.getResolveInfo().loadLabel(
437                        getActivity().getPackageManager()).toString());
438            }
439
440            final String searchUri = Settings.Secure.getString(getActivity().getContentResolver(),
441                    Settings.Secure.PRINT_SERVICE_SEARCH_URI);
442            final Intent viewIntent;
443            if (!TextUtils.isEmpty(searchUri)) {
444                Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(searchUri));
445                if (getActivity().getPackageManager().resolveActivity(intent, 0) != null) {
446                    viewIntent = intent;
447                    mAddPrintServiceItem = getString(R.string.add_print_service_label);
448                    adapter.add(mAddPrintServiceItem);
449                } else {
450                    viewIntent = null;
451                }
452            } else {
453                viewIntent = null;
454            }
455
456            builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
457                @Override
458                public void onClick(DialogInterface dialog, int which) {
459                    String item = adapter.getItem(which);
460                    if (item.equals(mAddPrintServiceItem)) {
461                        try {
462                            startActivity(viewIntent);
463                        } catch (ActivityNotFoundException anfe) {
464                            Log.w(LOG_TAG, "Couldn't start add printer activity", anfe);
465                        }
466                    } else {
467                        PrintServiceInfo printService = printServices.get(which);
468                        ComponentName componentName = new ComponentName(
469                                printService.getResolveInfo().serviceInfo.packageName,
470                                printService.getAddPrintersActivityName());
471                        Intent intent = new Intent(Intent.ACTION_MAIN);
472                        intent.setComponent(componentName);
473                        try {
474                            startActivity(intent);
475                        } catch (ActivityNotFoundException anfe) {
476                            Log.w(LOG_TAG, "Couldn't start add printer activity", anfe);
477                        }
478                    }
479                }
480            });
481
482            return builder.create();
483        }
484    }
485
486    private final class DestinationAdapter extends BaseAdapter implements Filterable {
487
488        private final Object mLock = new Object();
489
490        private final List<PrinterInfo> mPrinters = new ArrayList<>();
491
492        private final List<PrinterInfo> mFilteredPrinters = new ArrayList<>();
493
494        private CharSequence mLastSearchString;
495
496        public DestinationAdapter() {
497            mPrinterRegistry.setOnPrintersChangeListener(new PrinterRegistry.OnPrintersChangeListener() {
498                @Override
499                public void onPrintersChanged(List<PrinterInfo> printers) {
500                    synchronized (mLock) {
501                        mPrinters.clear();
502                        mPrinters.addAll(printers);
503                        mFilteredPrinters.clear();
504                        mFilteredPrinters.addAll(printers);
505                        if (!TextUtils.isEmpty(mLastSearchString)) {
506                            getFilter().filter(mLastSearchString);
507                        }
508                    }
509                    notifyDataSetChanged();
510                }
511
512                @Override
513                public void onPrintersInvalid() {
514                    synchronized (mLock) {
515                        mPrinters.clear();
516                        mFilteredPrinters.clear();
517                    }
518                    notifyDataSetInvalidated();
519                }
520            });
521        }
522
523        @Override
524        public Filter getFilter() {
525            return new Filter() {
526                @Override
527                protected FilterResults performFiltering(CharSequence constraint) {
528                    synchronized (mLock) {
529                        if (TextUtils.isEmpty(constraint)) {
530                            return null;
531                        }
532                        FilterResults results = new FilterResults();
533                        List<PrinterInfo> filteredPrinters = new ArrayList<>();
534                        String constraintLowerCase = constraint.toString().toLowerCase();
535                        final int printerCount = mPrinters.size();
536                        for (int i = 0; i < printerCount; i++) {
537                            PrinterInfo printer = mPrinters.get(i);
538                            if (printer.getName().toLowerCase().contains(constraintLowerCase)) {
539                                filteredPrinters.add(printer);
540                            }
541                        }
542                        results.values = filteredPrinters;
543                        results.count = filteredPrinters.size();
544                        return results;
545                    }
546                }
547
548                @Override
549                @SuppressWarnings("unchecked")
550                protected void publishResults(CharSequence constraint, FilterResults results) {
551                    final boolean resultCountChanged;
552                    synchronized (mLock) {
553                        final int oldPrinterCount = mFilteredPrinters.size();
554                        mLastSearchString = constraint;
555                        mFilteredPrinters.clear();
556                        if (results == null) {
557                            mFilteredPrinters.addAll(mPrinters);
558                        } else {
559                            List<PrinterInfo> printers = (List<PrinterInfo>) results.values;
560                            mFilteredPrinters.addAll(printers);
561                        }
562                        resultCountChanged = (oldPrinterCount != mFilteredPrinters.size());
563                    }
564                    if (resultCountChanged) {
565                        announceSearchResultIfNeeded();
566                    }
567                    notifyDataSetChanged();
568                }
569            };
570        }
571
572        public int getUnfilteredCount() {
573            synchronized (mLock) {
574                return mPrinters.size();
575            }
576        }
577
578        @Override
579        public int getCount() {
580            synchronized (mLock) {
581                return mFilteredPrinters.size();
582            }
583        }
584
585        @Override
586        public Object getItem(int position) {
587            synchronized (mLock) {
588                return mFilteredPrinters.get(position);
589            }
590        }
591
592        @Override
593        public long getItemId(int position) {
594            return position;
595        }
596
597        @Override
598        public View getDropDownView(int position, View convertView, ViewGroup parent) {
599            return getView(position, convertView, parent);
600        }
601
602        @Override
603        public View getView(int position, View convertView, ViewGroup parent) {
604            if (convertView == null) {
605                convertView = getLayoutInflater().inflate(
606                        R.layout.printer_list_item, parent, false);
607            }
608
609            convertView.setEnabled(isActionable(position));
610
611            final PrinterInfo printer = (PrinterInfo) getItem(position);
612
613            CharSequence title = printer.getName();
614            Drawable icon = printer.loadIcon(SelectPrinterActivity.this);
615
616            CharSequence printServiceLabel;
617            try {
618                PackageInfo packageInfo = getPackageManager().getPackageInfo(
619                        printer.getId().getServiceName().getPackageName(), 0);
620
621                printServiceLabel = packageInfo.applicationInfo.loadLabel(getPackageManager());
622            } catch (NameNotFoundException e) {
623                printServiceLabel = null;
624            }
625
626            CharSequence description = printer.getDescription();
627
628            CharSequence subtitle;
629            if (printServiceLabel == null) {
630                subtitle = description;
631            } else if (description == null) {
632                subtitle = printServiceLabel;
633            } else {
634                subtitle = getString(R.string.printer_extended_description_template,
635                        printServiceLabel, description);
636            }
637
638            TextView titleView = (TextView) convertView.findViewById(R.id.title);
639            titleView.setText(title);
640
641            TextView subtitleView = (TextView) convertView.findViewById(R.id.subtitle);
642            if (!TextUtils.isEmpty(subtitle)) {
643                subtitleView.setText(subtitle);
644                subtitleView.setVisibility(View.VISIBLE);
645            } else {
646                subtitleView.setText(null);
647                subtitleView.setVisibility(View.GONE);
648            }
649
650            ImageView moreInfoView = (ImageView) convertView.findViewById(R.id.more_info);
651            if (printer.getInfoIntent() != null) {
652                moreInfoView.setVisibility(View.VISIBLE);
653                moreInfoView.setOnClickListener(new OnClickListener() {
654                    @Override
655                    public void onClick(View v) {
656                        try {
657                            startIntentSender(printer.getInfoIntent().getIntentSender(), null, 0, 0, 0);
658                        } catch (SendIntentException e) {
659                            Log.e(LOG_TAG, "Could not execute pending info intent: %s", e);
660                        }
661                    }
662                });
663            }
664
665            ImageView iconView = (ImageView) convertView.findViewById(R.id.icon);
666            if (icon != null) {
667                iconView.setImageDrawable(icon);
668                iconView.setVisibility(View.VISIBLE);
669            } else {
670                iconView.setVisibility(View.GONE);
671            }
672
673            return convertView;
674        }
675
676        public boolean isActionable(int position) {
677            PrinterInfo printer =  (PrinterInfo) getItem(position);
678            return printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE;
679        }
680    }
681
682    private final class AnnounceFilterResult implements Runnable {
683        private static final int SEARCH_RESULT_ANNOUNCEMENT_DELAY = 1000; // 1 sec
684
685        public void post() {
686            remove();
687            mListView.postDelayed(this, SEARCH_RESULT_ANNOUNCEMENT_DELAY);
688        }
689
690        public void remove() {
691            mListView.removeCallbacks(this);
692        }
693
694        @Override
695        public void run() {
696            final int count = mListView.getAdapter().getCount();
697            final String text;
698            if (count <= 0) {
699                text = getString(R.string.print_no_printers);
700            } else {
701                text = getResources().getQuantityString(
702                    R.plurals.print_search_result_count_utterance, count, count);
703            }
704            mListView.announceForAccessibility(text);
705        }
706    }
707}
708