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