Home | History | Annotate | Download | only in print
      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.settings.print;
     18 
     19 import android.app.ActionBar;
     20 import android.app.Activity;
     21 import android.app.AlertDialog;
     22 import android.app.Dialog;
     23 import android.app.LoaderManager;
     24 import android.content.ComponentName;
     25 import android.content.ContentResolver;
     26 import android.content.Context;
     27 import android.content.DialogInterface;
     28 import android.content.Intent;
     29 import android.content.Loader;
     30 import android.content.pm.PackageInfo;
     31 import android.content.pm.PackageManager.NameNotFoundException;
     32 import android.content.pm.ResolveInfo;
     33 import android.database.ContentObserver;
     34 import android.database.DataSetObserver;
     35 import android.graphics.Color;
     36 import android.graphics.drawable.ColorDrawable;
     37 import android.graphics.drawable.Drawable;
     38 import android.net.Uri;
     39 import android.os.Bundle;
     40 import android.os.Handler;
     41 import android.preference.PreferenceActivity;
     42 import android.print.PrintManager;
     43 import android.print.PrinterDiscoverySession;
     44 import android.print.PrinterDiscoverySession.OnPrintersChangeListener;
     45 import android.print.PrinterId;
     46 import android.print.PrinterInfo;
     47 import android.provider.Settings;
     48 import android.text.TextUtils;
     49 import android.util.Log;
     50 import android.view.Gravity;
     51 import android.view.Menu;
     52 import android.view.MenuInflater;
     53 import android.view.MenuItem;
     54 import android.view.View;
     55 import android.view.ViewGroup;
     56 import android.view.accessibility.AccessibilityManager;
     57 import android.widget.BaseAdapter;
     58 import android.widget.CompoundButton;
     59 import android.widget.CompoundButton.OnCheckedChangeListener;
     60 import android.widget.Filter;
     61 import android.widget.Filterable;
     62 import android.widget.ImageView;
     63 import android.widget.ListView;
     64 import android.widget.SearchView;
     65 import android.widget.TextView;
     66 
     67 import com.android.settings.R;
     68 import com.android.settings.SettingsPreferenceFragment;
     69 import com.android.settings.print.PrintSettingsFragment.ToggleSwitch;
     70 import com.android.settings.print.PrintSettingsFragment.ToggleSwitch.OnBeforeCheckedChangeListener;
     71 
     72 import java.util.ArrayList;
     73 import java.util.LinkedHashMap;
     74 import java.util.List;
     75 import java.util.Map;
     76 /**
     77  * Fragment with print service settings.
     78  */
     79 public class PrintServiceSettingsFragment extends SettingsPreferenceFragment
     80         implements DialogInterface.OnClickListener {
     81 
     82     private static final int LOADER_ID_PRINTERS_LOADER = 1;
     83 
     84     private static final int DIALOG_ID_ENABLE_WARNING = 1;
     85 
     86     private final SettingsContentObserver mSettingsContentObserver =
     87             new SettingsContentObserver(new Handler()) {
     88         @Override
     89         public void onChange(boolean selfChange, Uri uri) {
     90             updateUiForServiceState();
     91         }
     92     };
     93 
     94     private final DataSetObserver mDataObserver = new DataSetObserver() {
     95         @Override
     96         public void onChanged() {
     97             invalidateOptionsMenuIfNeeded();
     98             updateEmptyView();
     99         }
    100 
    101         @Override
    102         public void onInvalidated() {
    103             invalidateOptionsMenuIfNeeded();
    104         }
    105 
    106         private void invalidateOptionsMenuIfNeeded() {
    107             final int unfilteredItemCount = mPrintersAdapter.getUnfilteredCount();
    108             if ((mLastUnfilteredItemCount <= 0 && unfilteredItemCount > 0)
    109                     || mLastUnfilteredItemCount > 0 && unfilteredItemCount <= 0) {
    110                 getActivity().invalidateOptionsMenu();
    111             }
    112             mLastUnfilteredItemCount = unfilteredItemCount;
    113         }
    114     };
    115 
    116     private ToggleSwitch mToggleSwitch;
    117 
    118     private String mPreferenceKey;
    119 
    120     private CharSequence mSettingsTitle;
    121     private Intent mSettingsIntent;
    122 
    123     private CharSequence mAddPrintersTitle;
    124     private Intent mAddPrintersIntent;
    125 
    126     private CharSequence mEnableWarningTitle;
    127     private CharSequence mEnableWarningMessage;
    128 
    129     private ComponentName mComponentName;
    130 
    131     private PrintersAdapter mPrintersAdapter;
    132 
    133     private int mLastUnfilteredItemCount;
    134 
    135     private boolean mServiceEnabled;
    136 
    137     private AnnounceFilterResult mAnnounceFilterResult;
    138 
    139     @Override
    140     public void onResume() {
    141         super.onResume();
    142         mSettingsContentObserver.register(getContentResolver());
    143         updateEmptyView();
    144         updateUiForServiceState();
    145     }
    146 
    147     @Override
    148     public void onPause() {
    149         mSettingsContentObserver.unregister(getContentResolver());
    150         if (mAnnounceFilterResult != null) {
    151             mAnnounceFilterResult.remove();
    152         }
    153         super.onPause();
    154     }
    155 
    156     @Override
    157     public void onViewCreated(View view, Bundle savedInstanceState) {
    158         super.onViewCreated(view, savedInstanceState);
    159         initComponents();
    160         updateUiForArguments();
    161     }
    162 
    163     @Override
    164     public void onDestroyView() {
    165         getActivity().getActionBar().setCustomView(null);
    166         mToggleSwitch.setOnBeforeCheckedChangeListener(null);
    167         super.onDestroyView();
    168     }
    169 
    170     private void onPreferenceToggled(String preferenceKey, boolean enabled) {
    171         ComponentName service = ComponentName.unflattenFromString(preferenceKey);
    172         List<ComponentName> services = SettingsUtils.readEnabledPrintServices(getActivity());
    173         if (enabled) {
    174             services.add(service);
    175         } else {
    176             services.remove(service);
    177         }
    178         SettingsUtils.writeEnabledPrintServices(getActivity(), services);
    179     }
    180 
    181     @Override
    182     public Dialog onCreateDialog(int dialogId) {
    183         CharSequence title = null;
    184         CharSequence message = null;
    185         switch (dialogId) {
    186             case DIALOG_ID_ENABLE_WARNING:
    187                 title = mEnableWarningTitle;
    188                 message = mEnableWarningMessage;
    189                 break;
    190             default:
    191                 throw new IllegalArgumentException();
    192         }
    193         return new AlertDialog.Builder(getActivity())
    194                 .setTitle(title)
    195                 .setIconAttribute(android.R.attr.alertDialogIcon)
    196                 .setMessage(message)
    197                 .setCancelable(true)
    198                 .setPositiveButton(android.R.string.ok, this)
    199                 .setNegativeButton(android.R.string.cancel, this)
    200                 .create();
    201     }
    202 
    203     @Override
    204     public void onClick(DialogInterface dialog, int which) {
    205         final boolean checked;
    206         switch (which) {
    207             case DialogInterface.BUTTON_POSITIVE:
    208                 checked = true;
    209                 mToggleSwitch.setCheckedInternal(checked);
    210                 getArguments().putBoolean(PrintSettingsFragment.EXTRA_CHECKED, checked);
    211                 onPreferenceToggled(mPreferenceKey, checked);
    212                 break;
    213             case DialogInterface.BUTTON_NEGATIVE:
    214                 checked = false;
    215                 mToggleSwitch.setCheckedInternal(checked);
    216                 getArguments().putBoolean(PrintSettingsFragment.EXTRA_CHECKED, checked);
    217                 onPreferenceToggled(mPreferenceKey, checked);
    218                 break;
    219             default:
    220                 throw new IllegalArgumentException();
    221         }
    222     }
    223 
    224     private void updateEmptyView() {
    225         ListView listView = getListView();
    226         ViewGroup contentRoot = (ViewGroup) listView.getParent();
    227         View emptyView = listView.getEmptyView();
    228         if (!mToggleSwitch.isChecked()) {
    229             if (emptyView != null && emptyView.getId() != R.id.empty_print_state) {
    230                 contentRoot.removeView(emptyView);
    231                 emptyView = null;
    232             }
    233             if (emptyView == null) {
    234                 emptyView = getActivity().getLayoutInflater().inflate(
    235                         R.layout.empty_print_state, contentRoot, false);
    236                 emptyView.setContentDescription(getString(R.string.print_service_disabled));
    237                 TextView textView = (TextView) emptyView.findViewById(R.id.message);
    238                 textView.setText(R.string.print_service_disabled);
    239                 contentRoot.addView(emptyView);
    240                 listView.setEmptyView(emptyView);
    241             }
    242         } else if (mPrintersAdapter.getUnfilteredCount() <= 0) {
    243             if (emptyView != null
    244                     && emptyView.getId() != R.id.empty_printers_list_service_enabled) {
    245                 contentRoot.removeView(emptyView);
    246                 emptyView = null;
    247             }
    248             if (emptyView == null) {
    249                 emptyView = getActivity().getLayoutInflater().inflate(
    250                         R.layout.empty_printers_list_service_enabled, contentRoot, false);
    251                 contentRoot.addView(emptyView);
    252                 listView.setEmptyView(emptyView);
    253             }
    254         } else if (mPrintersAdapter.getCount() <= 0) {
    255             if (emptyView != null && emptyView.getId() != R.id.empty_print_state) {
    256                 contentRoot.removeView(emptyView);
    257                 emptyView = null;
    258             }
    259             if (emptyView == null) {
    260                 emptyView = getActivity().getLayoutInflater().inflate(
    261                         R.layout.empty_print_state, contentRoot, false);
    262                 emptyView.setContentDescription(getString(R.string.print_no_printers_found));
    263                 TextView textView = (TextView) emptyView.findViewById(R.id.message);
    264                 textView.setText(R.string.print_no_printers_found);
    265                 contentRoot.addView(emptyView);
    266                 listView.setEmptyView(emptyView);
    267             }
    268         }
    269     }
    270 
    271     private void updateUiForServiceState() {
    272         List<ComponentName> services = SettingsUtils.readEnabledPrintServices(getActivity());
    273         mServiceEnabled = services.contains(mComponentName);
    274         if (mServiceEnabled) {
    275             mToggleSwitch.setCheckedInternal(true);
    276             mPrintersAdapter.enable();
    277         } else {
    278             mToggleSwitch.setCheckedInternal(false);
    279             mPrintersAdapter.disable();
    280         }
    281         getActivity().invalidateOptionsMenu();
    282     }
    283 
    284     private void initComponents() {
    285         mPrintersAdapter = new PrintersAdapter();
    286         mPrintersAdapter.registerDataSetObserver(mDataObserver);
    287 
    288         mToggleSwitch = createAndAddActionBarToggleSwitch(getActivity());
    289         mToggleSwitch.setOnBeforeCheckedChangeListener(new OnBeforeCheckedChangeListener() {
    290             @Override
    291             public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked) {
    292                 if (checked) {
    293                     if (!TextUtils.isEmpty(mEnableWarningMessage)) {
    294                         toggleSwitch.setCheckedInternal(false);
    295                         getArguments().putBoolean(PrintSettingsFragment.EXTRA_CHECKED, false);
    296                         showDialog(DIALOG_ID_ENABLE_WARNING);
    297                         return true;
    298                     }
    299                     onPreferenceToggled(mPreferenceKey, true);
    300                 } else {
    301                     onPreferenceToggled(mPreferenceKey, false);
    302                 }
    303                 return false;
    304             }
    305         });
    306         mToggleSwitch.setOnCheckedChangeListener(new OnCheckedChangeListener() {
    307             @Override
    308             public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
    309                 updateEmptyView();
    310             }
    311         });
    312 
    313         getListView().setSelector(new ColorDrawable(Color.TRANSPARENT));
    314         getListView().setAdapter(mPrintersAdapter);
    315     }
    316 
    317     private void updateUiForArguments() {
    318         Bundle arguments = getArguments();
    319 
    320         // Key.
    321         mPreferenceKey = arguments.getString(PrintSettingsFragment.EXTRA_PREFERENCE_KEY);
    322 
    323         // Enabled.
    324         final boolean enabled = arguments.getBoolean(PrintSettingsFragment.EXTRA_CHECKED);
    325         mToggleSwitch.setCheckedInternal(enabled);
    326 
    327         // Title.
    328         PreferenceActivity activity = (PreferenceActivity) getActivity();
    329         if (!activity.onIsMultiPane() || activity.onIsHidingHeaders()) {
    330             // PreferenceActivity allows passing as an extra only title by its
    331             // resource id but we do not have the resource id for the print
    332             // service label. Therefore, we do it ourselves.
    333             String title = arguments.getString(PrintSettingsFragment.EXTRA_TITLE);
    334             getActivity().setTitle(title);
    335         }
    336 
    337         // Settings title and intent.
    338         String settingsTitle = arguments.getString(PrintSettingsFragment.EXTRA_SETTINGS_TITLE);
    339         String settingsComponentName = arguments.getString(
    340                 PrintSettingsFragment.EXTRA_SETTINGS_COMPONENT_NAME);
    341         if (!TextUtils.isEmpty(settingsTitle) && !TextUtils.isEmpty(settingsComponentName)) {
    342             Intent settingsIntent = new Intent(Intent.ACTION_MAIN).setComponent(
    343                     ComponentName.unflattenFromString(settingsComponentName.toString()));
    344             List<ResolveInfo> resolvedActivities = getPackageManager().queryIntentActivities(
    345                     settingsIntent, 0);
    346             if (!resolvedActivities.isEmpty()) {
    347                 // The activity is a component name, therefore it is one or none.
    348                 if (resolvedActivities.get(0).activityInfo.exported) {
    349                     mSettingsTitle = settingsTitle;
    350                     mSettingsIntent = settingsIntent;
    351                 }
    352             }
    353         }
    354 
    355         // Add printers title and intent.
    356         String addPrintersTitle = arguments.getString(
    357                 PrintSettingsFragment.EXTRA_ADD_PRINTERS_TITLE);
    358         String addPrintersComponentName =
    359                 arguments.getString(PrintSettingsFragment.EXTRA_ADD_PRINTERS_COMPONENT_NAME);
    360         if (!TextUtils.isEmpty(addPrintersTitle)
    361                 && !TextUtils.isEmpty(addPrintersComponentName)) {
    362             Intent addPritnersIntent = new Intent(Intent.ACTION_MAIN).setComponent(
    363                     ComponentName.unflattenFromString(addPrintersComponentName.toString()));
    364             List<ResolveInfo> resolvedActivities = getPackageManager().queryIntentActivities(
    365                     addPritnersIntent, 0);
    366             if (!resolvedActivities.isEmpty()) {
    367                 // The activity is a component name, therefore it is one or none.
    368                 if (resolvedActivities.get(0).activityInfo.exported) {
    369                     mAddPrintersTitle = addPrintersTitle;
    370                     mAddPrintersIntent = addPritnersIntent;
    371                 }
    372             }
    373         }
    374 
    375         // Enable warning title.
    376         mEnableWarningTitle = arguments.getCharSequence(
    377                 PrintSettingsFragment.EXTRA_ENABLE_WARNING_TITLE);
    378 
    379         // Enable warning message.
    380         mEnableWarningMessage = arguments.getCharSequence(
    381                 PrintSettingsFragment.EXTRA_ENABLE_WARNING_MESSAGE);
    382 
    383         // Component name.
    384         mComponentName = ComponentName.unflattenFromString(arguments
    385                 .getString(PrintSettingsFragment.EXTRA_SERVICE_COMPONENT_NAME));
    386 
    387         setHasOptionsMenu(true);
    388     }
    389 
    390     @Override
    391     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    392         super.onCreateOptionsMenu(menu, inflater);
    393         inflater.inflate(R.menu.print_service_settings, menu);
    394 
    395         MenuItem addPrinters = menu.findItem(R.id.print_menu_item_add_printer);
    396         if (mServiceEnabled && !TextUtils.isEmpty(mAddPrintersTitle)
    397                 && mAddPrintersIntent != null) {
    398             addPrinters.setIntent(mAddPrintersIntent);
    399         } else {
    400             menu.removeItem(R.id.print_menu_item_add_printer);
    401         }
    402 
    403         MenuItem settings = menu.findItem(R.id.print_menu_item_settings);
    404         if (mServiceEnabled && !TextUtils.isEmpty(mSettingsTitle)
    405                 && mSettingsIntent != null) {
    406             settings.setIntent(mSettingsIntent);
    407         } else {
    408             menu.removeItem(R.id.print_menu_item_settings);
    409         }
    410 
    411         MenuItem searchItem = menu.findItem(R.id.print_menu_item_search);
    412         if (mServiceEnabled && mPrintersAdapter.getUnfilteredCount() > 0) {
    413             SearchView searchView = (SearchView) searchItem.getActionView();
    414             searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
    415                 @Override
    416                 public boolean onQueryTextSubmit(String query) {
    417                     return true;
    418                 }
    419 
    420                 @Override
    421                 public boolean onQueryTextChange(String searchString) {
    422                     ((Filterable) getListView().getAdapter()).getFilter().filter(searchString);
    423                     return true;
    424                 }
    425             });
    426             searchView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
    427                 @Override
    428                 public void onViewAttachedToWindow(View view) {
    429                     if (AccessibilityManager.getInstance(getActivity()).isEnabled()) {
    430                         view.announceForAccessibility(getString(
    431                                 R.string.print_search_box_shown_utterance));
    432                     }
    433                 }
    434                 @Override
    435                 public void onViewDetachedFromWindow(View view) {
    436                     Activity activity = getActivity();
    437                     if (activity != null && !activity.isFinishing()
    438                             && AccessibilityManager.getInstance(activity).isEnabled()) {
    439                         view.announceForAccessibility(getString(
    440                                 R.string.print_search_box_hidden_utterance));
    441                     }
    442                 }
    443             });
    444         } else {
    445             menu.removeItem(R.id.print_menu_item_search);
    446         }
    447     }
    448 
    449     private ToggleSwitch createAndAddActionBarToggleSwitch(Activity activity) {
    450         ToggleSwitch toggleSwitch = new ToggleSwitch(activity);
    451         final int padding = activity.getResources().getDimensionPixelSize(
    452                 R.dimen.action_bar_switch_padding);
    453         toggleSwitch.setPaddingRelative(0, 0, padding, 0);
    454         activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
    455                 ActionBar.DISPLAY_SHOW_CUSTOM);
    456         activity.getActionBar().setCustomView(toggleSwitch,
    457                 new ActionBar.LayoutParams(ActionBar.LayoutParams.WRAP_CONTENT,
    458                         ActionBar.LayoutParams.WRAP_CONTENT,
    459                         Gravity.CENTER_VERTICAL | Gravity.END));
    460         return toggleSwitch;
    461     }
    462 
    463     private static abstract class SettingsContentObserver extends ContentObserver {
    464 
    465         public SettingsContentObserver(Handler handler) {
    466             super(handler);
    467         }
    468 
    469         public void register(ContentResolver contentResolver) {
    470             contentResolver.registerContentObserver(Settings.Secure.getUriFor(
    471                     Settings.Secure.ENABLED_PRINT_SERVICES), false, this);
    472         }
    473 
    474         public void unregister(ContentResolver contentResolver) {
    475             contentResolver.unregisterContentObserver(this);
    476         }
    477 
    478         @Override
    479         public abstract void onChange(boolean selfChange, Uri uri);
    480     }
    481 
    482     private final class AnnounceFilterResult implements Runnable {
    483         private static final int SEARCH_RESULT_ANNOUNCEMENT_DELAY = 1000; // 1 sec
    484 
    485         public void post() {
    486             remove();
    487             getListView().postDelayed(this, SEARCH_RESULT_ANNOUNCEMENT_DELAY);
    488         }
    489 
    490         public void remove() {
    491             getListView().removeCallbacks(this);
    492         }
    493 
    494         @Override
    495         public void run() {
    496             final int count = getListView().getAdapter().getCount();
    497             final String text;
    498             if (count <= 0) {
    499                 text = getString(R.string.print_no_printers_found);
    500             } else {
    501                 text = getActivity().getResources().getQuantityString(
    502                     R.plurals.print_search_result_count_utterance, count, count);
    503             }
    504             getListView().announceForAccessibility(text);
    505         }
    506     }
    507 
    508     private void announceSearchResult() {
    509         if (mAnnounceFilterResult == null) {
    510             mAnnounceFilterResult = new AnnounceFilterResult();
    511         }
    512         mAnnounceFilterResult.post();
    513     }
    514 
    515     private final class PrintersAdapter extends BaseAdapter
    516             implements LoaderManager.LoaderCallbacks<List<PrinterInfo>>, Filterable {
    517         private final Object mLock = new Object();
    518 
    519         private final List<PrinterInfo> mPrinters = new ArrayList<PrinterInfo>();
    520 
    521         private final List<PrinterInfo> mFilteredPrinters = new ArrayList<PrinterInfo>();
    522 
    523         private CharSequence mLastSearchString;
    524 
    525         public void enable() {
    526             getLoaderManager().initLoader(LOADER_ID_PRINTERS_LOADER, null, this);
    527         }
    528 
    529         public void disable() {
    530             getLoaderManager().destroyLoader(LOADER_ID_PRINTERS_LOADER);
    531             mPrinters.clear();
    532         }
    533 
    534         public int getUnfilteredCount() {
    535             return mPrinters.size();
    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<PrinterInfo>();
    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                             if (printer.getName().toLowerCase().contains(constraintLowerCase)) {
    554                                 filteredPrinters.add(printer);
    555                             }
    556                         }
    557                         results.values = filteredPrinters;
    558                         results.count = filteredPrinters.size();
    559                         return results;
    560                     }
    561                 }
    562 
    563                 @Override
    564                 @SuppressWarnings("unchecked")
    565                 protected void publishResults(CharSequence constraint, FilterResults results) {
    566                     final boolean resultCountChanged;
    567                     synchronized (mLock) {
    568                         final int oldPrinterCount = mFilteredPrinters.size();
    569                         mLastSearchString = constraint;
    570                         mFilteredPrinters.clear();
    571                         if (results == null) {
    572                             mFilteredPrinters.addAll(mPrinters);
    573                         } else {
    574                             List<PrinterInfo> printers = (List<PrinterInfo>) results.values;
    575                             mFilteredPrinters.addAll(printers);
    576                         }
    577                         resultCountChanged = (oldPrinterCount != mFilteredPrinters.size());
    578                     }
    579                     if (resultCountChanged) {
    580                         announceSearchResult();
    581                     }
    582                     notifyDataSetChanged();
    583                 }
    584             };
    585         }
    586 
    587         @Override
    588         public int getCount() {
    589             synchronized (mLock) {
    590                 return mFilteredPrinters.size();
    591             }
    592         }
    593 
    594         @Override
    595         public Object getItem(int position) {
    596             synchronized (mLock) {
    597                 return mFilteredPrinters.get(position);
    598             }
    599         }
    600 
    601         @Override
    602         public long getItemId(int position) {
    603             return position;
    604         }
    605 
    606         @Override
    607         public View getView(int position, View convertView, ViewGroup parent) {
    608             if (convertView == null) {
    609                 convertView = getActivity().getLayoutInflater().inflate(
    610                         R.layout.printer_dropdown_item, parent, false);
    611             }
    612 
    613             PrinterInfo printer = (PrinterInfo) getItem(position);
    614             CharSequence title = printer.getName();
    615             CharSequence subtitle = null;
    616             Drawable icon = null;
    617             try {
    618                 PackageInfo packageInfo = getPackageManager().getPackageInfo(
    619                         printer.getId().getServiceName().getPackageName(), 0);
    620                         subtitle = packageInfo.applicationInfo.loadLabel(getPackageManager());
    621                         icon = packageInfo.applicationInfo.loadIcon(getPackageManager());
    622             } catch (NameNotFoundException nnfe) {
    623                 /* ignore */
    624             }
    625 
    626             TextView titleView = (TextView) convertView.findViewById(R.id.title);
    627             titleView.setText(title);
    628 
    629             TextView subtitleView = (TextView) convertView.findViewById(R.id.subtitle);
    630             if (!TextUtils.isEmpty(subtitle)) {
    631                 subtitleView.setText(subtitle);
    632                 subtitleView.setVisibility(View.VISIBLE);
    633             } else {
    634                 subtitleView.setText(null);
    635                 subtitleView.setVisibility(View.GONE);
    636             }
    637 
    638             ImageView iconView = (ImageView) convertView.findViewById(R.id.icon);
    639             if (icon != null) {
    640                 iconView.setImageDrawable(icon);
    641                 iconView.setVisibility(View.VISIBLE);
    642             } else {
    643                 iconView.setVisibility(View.GONE);
    644             }
    645 
    646             return convertView;
    647         }
    648 
    649         @Override
    650         public boolean isEnabled(int position) {
    651             return false;
    652         }
    653 
    654         @Override
    655         public Loader<List<PrinterInfo>> onCreateLoader(int id, Bundle args) {
    656             if (id == LOADER_ID_PRINTERS_LOADER) {
    657                 return new PrintersLoader(getActivity());
    658             }
    659             return null;
    660         }
    661 
    662         @Override
    663         public void onLoadFinished(Loader<List<PrinterInfo>> loader,
    664                 List<PrinterInfo> printers) {
    665             synchronized (mLock) {
    666                 mPrinters.clear();
    667                 final int printerCount = printers.size();
    668                 for (int i = 0; i < printerCount; i++) {
    669                     PrinterInfo printer = printers.get(i);
    670                     if (printer.getId().getServiceName().equals(mComponentName)) {
    671                         mPrinters.add(printer);
    672                     }
    673                 }
    674                 mFilteredPrinters.clear();
    675                 mFilteredPrinters.addAll(mPrinters);
    676                 if (!TextUtils.isEmpty(mLastSearchString)) {
    677                     getFilter().filter(mLastSearchString);
    678                 }
    679             }
    680             notifyDataSetChanged();
    681         }
    682 
    683         @Override
    684         public void onLoaderReset(Loader<List<PrinterInfo>> loader) {
    685             synchronized (mLock) {
    686                 mPrinters.clear();
    687                 mFilteredPrinters.clear();
    688                 mLastSearchString = null;
    689             }
    690             notifyDataSetInvalidated();
    691         }
    692     }
    693 
    694     private static class PrintersLoader extends Loader<List<PrinterInfo>> {
    695 
    696         private static final String LOG_TAG = "PrintersLoader";
    697 
    698         private static final boolean DEBUG = false;
    699 
    700         private final Map<PrinterId, PrinterInfo> mPrinters =
    701                 new LinkedHashMap<PrinterId, PrinterInfo>();
    702 
    703         private PrinterDiscoverySession mDiscoverySession;
    704 
    705         public PrintersLoader(Context context) {
    706             super(context);
    707         }
    708 
    709         @Override
    710         public void deliverResult(List<PrinterInfo> printers) {
    711             if (isStarted()) {
    712                 super.deliverResult(printers);
    713             }
    714         }
    715 
    716         @Override
    717         protected void onStartLoading() {
    718             if (DEBUG) {
    719                 Log.i(LOG_TAG, "onStartLoading()");
    720             }
    721             // The contract is that if we already have a valid,
    722             // result the we have to deliver it immediately.
    723             if (!mPrinters.isEmpty()) {
    724                 deliverResult(new ArrayList<PrinterInfo>(mPrinters.values()));
    725             }
    726             // We want to start discovery at this point.
    727             onForceLoad();
    728         }
    729 
    730         @Override
    731         protected void onStopLoading() {
    732             if (DEBUG) {
    733                 Log.i(LOG_TAG, "onStopLoading()");
    734             }
    735             onCancelLoad();
    736         }
    737 
    738         @Override
    739         protected void onForceLoad() {
    740             if (DEBUG) {
    741                 Log.i(LOG_TAG, "onForceLoad()");
    742             }
    743             loadInternal();
    744         }
    745 
    746         @Override
    747         protected boolean onCancelLoad() {
    748             if (DEBUG) {
    749                 Log.i(LOG_TAG, "onCancelLoad()");
    750             }
    751             return cancelInternal();
    752         }
    753 
    754         @Override
    755         protected void onReset() {
    756             if (DEBUG) {
    757                 Log.i(LOG_TAG, "onReset()");
    758             }
    759             onStopLoading();
    760             mPrinters.clear();
    761             if (mDiscoverySession != null) {
    762                 mDiscoverySession.destroy();
    763                 mDiscoverySession = null;
    764             }
    765         }
    766 
    767         @Override
    768         protected void onAbandon() {
    769             if (DEBUG) {
    770                 Log.i(LOG_TAG, "onAbandon()");
    771             }
    772             onStopLoading();
    773         }
    774 
    775         private boolean cancelInternal() {
    776             if (mDiscoverySession != null
    777                     && mDiscoverySession.isPrinterDiscoveryStarted()) {
    778                 mDiscoverySession.stopPrinterDiscovery();
    779                 return true;
    780             }
    781             return false;
    782         }
    783 
    784         private void loadInternal() {
    785             if (mDiscoverySession == null) {
    786                 PrintManager printManager = (PrintManager) getContext()
    787                         .getSystemService(Context.PRINT_SERVICE);
    788                 mDiscoverySession = printManager.createPrinterDiscoverySession();
    789                 mDiscoverySession.setOnPrintersChangeListener(new OnPrintersChangeListener() {
    790                     @Override
    791                     public void onPrintersChanged() {
    792                         deliverResult(new ArrayList<PrinterInfo>(
    793                                 mDiscoverySession.getPrinters()));
    794                     }
    795                 });
    796             }
    797             mDiscoverySession.startPrinterDisovery(null);
    798         }
    799     }
    800 }
    801 
    802