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.Activity;
     20 import android.app.LoaderManager;
     21 import android.content.ComponentName;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.content.IntentSender.SendIntentException;
     25 import android.content.Loader;
     26 import android.content.pm.ResolveInfo;
     27 import android.database.DataSetObserver;
     28 import android.graphics.Color;
     29 import android.graphics.drawable.ColorDrawable;
     30 import android.graphics.drawable.Drawable;
     31 import android.os.Bundle;
     32 import android.print.PrintManager;
     33 import android.print.PrintServicesLoader;
     34 import android.print.PrinterDiscoverySession;
     35 import android.print.PrinterDiscoverySession.OnPrintersChangeListener;
     36 import android.print.PrinterId;
     37 import android.print.PrinterInfo;
     38 import android.printservice.PrintServiceInfo;
     39 import android.text.TextUtils;
     40 import android.util.Log;
     41 import android.util.TypedValue;
     42 import android.view.Menu;
     43 import android.view.MenuInflater;
     44 import android.view.MenuItem;
     45 import android.view.View;
     46 import android.view.ViewGroup;
     47 import android.view.View.OnClickListener;
     48 import android.view.accessibility.AccessibilityManager;
     49 import android.widget.AdapterView;
     50 import android.widget.BaseAdapter;
     51 import android.widget.Filter;
     52 import android.widget.Filterable;
     53 import android.widget.ImageView;
     54 import android.widget.LinearLayout;
     55 import android.widget.ListView;
     56 import android.widget.SearchView;
     57 import android.widget.Switch;
     58 import android.widget.TextView;
     59 
     60 import com.android.internal.logging.MetricsProto.MetricsEvent;
     61 import com.android.settings.R;
     62 import com.android.settings.SettingsActivity;
     63 import com.android.settings.SettingsPreferenceFragment;
     64 import com.android.settings.widget.SwitchBar;
     65 import com.android.settings.widget.ToggleSwitch;
     66 
     67 import java.util.ArrayList;
     68 import java.util.LinkedHashMap;
     69 import java.util.List;
     70 import java.util.Map;
     71 
     72 /**
     73  * Fragment with print service settings.
     74  */
     75 public class PrintServiceSettingsFragment extends SettingsPreferenceFragment
     76         implements SwitchBar.OnSwitchChangeListener,
     77         LoaderManager.LoaderCallbacks<List<PrintServiceInfo>> {
     78 
     79     private static final String LOG_TAG = "PrintServiceSettingsFragment";
     80 
     81     private static final int LOADER_ID_PRINTERS_LOADER = 1;
     82     private static final int LOADER_ID_PRINT_SERVICE_LOADER = 2;
     83 
     84     private final DataSetObserver mDataObserver = new DataSetObserver() {
     85         @Override
     86         public void onChanged() {
     87             invalidateOptionsMenuIfNeeded();
     88             updateEmptyView();
     89         }
     90 
     91         @Override
     92         public void onInvalidated() {
     93             invalidateOptionsMenuIfNeeded();
     94         }
     95 
     96         private void invalidateOptionsMenuIfNeeded() {
     97             final int unfilteredItemCount = mPrintersAdapter.getUnfilteredCount();
     98             if ((mLastUnfilteredItemCount <= 0 && unfilteredItemCount > 0)
     99                     || mLastUnfilteredItemCount > 0 && unfilteredItemCount <= 0) {
    100                 getActivity().invalidateOptionsMenu();
    101             }
    102             mLastUnfilteredItemCount = unfilteredItemCount;
    103         }
    104     };
    105 
    106     private SwitchBar mSwitchBar;
    107     private ToggleSwitch mToggleSwitch;
    108 
    109     private String mPreferenceKey;
    110 
    111     private Intent mSettingsIntent;
    112 
    113     private Intent mAddPrintersIntent;
    114 
    115     private ComponentName mComponentName;
    116 
    117     private PrintersAdapter mPrintersAdapter;
    118 
    119     // TODO: Showing sub-sub fragment does not handle the activity title
    120     // so we do it but this is wrong. Do a real fix when there is time.
    121     private CharSequence mOldActivityTitle;
    122 
    123     private int mLastUnfilteredItemCount;
    124 
    125     private boolean mServiceEnabled;
    126 
    127     private SearchView mSearchView;
    128 
    129     @Override
    130     protected int getMetricsCategory() {
    131         return MetricsEvent.PRINT_SERVICE_SETTINGS;
    132     }
    133 
    134     @Override
    135     public void onCreate(Bundle icicle) {
    136         super.onCreate(icicle);
    137 
    138         mServiceEnabled = getArguments().getBoolean(PrintSettingsFragment.EXTRA_CHECKED);
    139 
    140         String title = getArguments().getString(PrintSettingsFragment.EXTRA_TITLE);
    141         if (!TextUtils.isEmpty(title)) {
    142             getActivity().setTitle(title);
    143         }
    144     }
    145 
    146     @Override
    147     public void onStart() {
    148         super.onStart();
    149         updateEmptyView();
    150         updateUiForServiceState();
    151     }
    152 
    153     @Override
    154     public void onPause() {
    155         if (mSearchView != null) {
    156             mSearchView.setOnQueryTextListener(null);
    157         }
    158         super.onPause();
    159     }
    160 
    161     @Override
    162     public void onStop() {
    163         super.onStop();
    164     }
    165 
    166     @Override
    167     public void onViewCreated(View view, Bundle savedInstanceState) {
    168         super.onViewCreated(view, savedInstanceState);
    169         initComponents();
    170         updateUiForArguments();
    171         getBackupListView().setVisibility(View.VISIBLE);
    172     }
    173 
    174     @Override
    175     public void onDestroyView() {
    176         if (mOldActivityTitle != null) {
    177             getActivity().getActionBar().setTitle(mOldActivityTitle);
    178         }
    179         super.onDestroyView();
    180         mSwitchBar.removeOnSwitchChangeListener(this);
    181         mSwitchBar.hide();
    182     }
    183 
    184     private void onPreferenceToggled(String preferenceKey, boolean enabled) {
    185         ((PrintManager)getContext().getSystemService(Context.PRINT_SERVICE))
    186                 .setPrintServiceEnabled(mComponentName, enabled);
    187     }
    188 
    189     private ListView getBackupListView() {
    190         return (ListView) getView().findViewById(R.id.backup_list);
    191     }
    192 
    193     private void updateEmptyView() {
    194         ViewGroup contentRoot = (ViewGroup) getListView().getParent();
    195         View emptyView = getBackupListView().getEmptyView();
    196         if (!mToggleSwitch.isChecked()) {
    197             if (emptyView != null && emptyView.getId() != R.id.empty_print_state) {
    198                 contentRoot.removeView(emptyView);
    199                 emptyView = null;
    200             }
    201             if (emptyView == null) {
    202                 emptyView = getActivity().getLayoutInflater().inflate(
    203                         R.layout.empty_print_state, contentRoot, false);
    204                 ImageView iconView = (ImageView) emptyView.findViewById(R.id.icon);
    205                 iconView.setContentDescription(getString(R.string.print_service_disabled));
    206                 TextView textView = (TextView) emptyView.findViewById(R.id.message);
    207                 textView.setText(R.string.print_service_disabled);
    208                 contentRoot.addView(emptyView);
    209                 getBackupListView().setEmptyView(emptyView);
    210             }
    211         } else if (mPrintersAdapter.getUnfilteredCount() <= 0) {
    212             if (emptyView != null
    213                     && emptyView.getId() != R.id.empty_printers_list_service_enabled) {
    214                 contentRoot.removeView(emptyView);
    215                 emptyView = null;
    216             }
    217             if (emptyView == null) {
    218                 emptyView = getActivity().getLayoutInflater().inflate(
    219                         R.layout.empty_printers_list_service_enabled, contentRoot, false);
    220                 contentRoot.addView(emptyView);
    221                 getBackupListView().setEmptyView(emptyView);
    222             }
    223         } else if (mPrintersAdapter.getCount() <= 0) {
    224             if (emptyView != null && emptyView.getId() != R.id.empty_print_state) {
    225                 contentRoot.removeView(emptyView);
    226                 emptyView = null;
    227             }
    228             if (emptyView == null) {
    229                 emptyView = getActivity().getLayoutInflater().inflate(
    230                         R.layout.empty_print_state, contentRoot, false);
    231                 ImageView iconView = (ImageView) emptyView.findViewById(R.id.icon);
    232                 iconView.setContentDescription(getString(R.string.print_no_printers_found));
    233                 TextView textView = (TextView) emptyView.findViewById(R.id.message);
    234                 textView.setText(R.string.print_no_printers_found);
    235                 contentRoot.addView(emptyView);
    236                 getBackupListView().setEmptyView(emptyView);
    237             }
    238         }
    239     }
    240 
    241     private void updateUiForServiceState() {
    242         if (mServiceEnabled) {
    243             mSwitchBar.setCheckedInternal(true);
    244             mPrintersAdapter.enable();
    245         } else {
    246             mSwitchBar.setCheckedInternal(false);
    247             mPrintersAdapter.disable();
    248         }
    249         getActivity().invalidateOptionsMenu();
    250     }
    251 
    252     private void initComponents() {
    253         mPrintersAdapter = new PrintersAdapter();
    254         mPrintersAdapter.registerDataSetObserver(mDataObserver);
    255 
    256         final SettingsActivity activity = (SettingsActivity) getActivity();
    257 
    258         mSwitchBar = activity.getSwitchBar();
    259         mSwitchBar.addOnSwitchChangeListener(this);
    260         mSwitchBar.show();
    261 
    262         mToggleSwitch = mSwitchBar.getSwitch();
    263         mToggleSwitch.setOnBeforeCheckedChangeListener(new ToggleSwitch.OnBeforeCheckedChangeListener() {
    264             @Override
    265             public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked) {
    266                 onPreferenceToggled(mPreferenceKey, checked);
    267                 return false;
    268             }
    269         });
    270 
    271         getBackupListView().setSelector(new ColorDrawable(Color.TRANSPARENT));
    272         getBackupListView().setAdapter(mPrintersAdapter);
    273         getBackupListView().setOnItemClickListener(new AdapterView.OnItemClickListener() {
    274             @Override
    275             public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    276                 PrinterInfo printer = (PrinterInfo) mPrintersAdapter.getItem(position);
    277 
    278                 if (printer.getInfoIntent() != null) {
    279                     try {
    280                         getActivity().startIntentSender(printer.getInfoIntent().getIntentSender(),
    281                                 null, 0, 0, 0);
    282                     } catch (SendIntentException e) {
    283                         Log.e(LOG_TAG, "Could not execute info intent: %s", e);
    284                     }
    285                 }
    286             }
    287         });
    288     }
    289 
    290 
    291     @Override
    292     public void onSwitchChanged(Switch switchView, boolean isChecked) {
    293         updateEmptyView();
    294     }
    295 
    296     private void updateUiForArguments() {
    297         Bundle arguments = getArguments();
    298 
    299         // Component name.
    300         mComponentName = ComponentName.unflattenFromString(arguments
    301                 .getString(PrintSettingsFragment.EXTRA_SERVICE_COMPONENT_NAME));
    302 
    303         // Key.
    304         mPreferenceKey = mComponentName.flattenToString();
    305 
    306         // Enabled.
    307         final boolean enabled = arguments.getBoolean(PrintSettingsFragment.EXTRA_CHECKED);
    308         mSwitchBar.setCheckedInternal(enabled);
    309 
    310         getLoaderManager().initLoader(LOADER_ID_PRINT_SERVICE_LOADER, null, this);
    311         setHasOptionsMenu(true);
    312     }
    313 
    314     @Override
    315     public Loader<List<PrintServiceInfo>> onCreateLoader(int id, Bundle args) {
    316         return new PrintServicesLoader(
    317                 (PrintManager) getContext().getSystemService(Context.PRINT_SERVICE), getContext(),
    318                 PrintManager.ALL_SERVICES);
    319     }
    320 
    321     @Override
    322     public void onLoadFinished(Loader<List<PrintServiceInfo>> loader,
    323             List<PrintServiceInfo> services) {
    324         PrintServiceInfo service = null;
    325 
    326         if (services != null) {
    327             final int numServices = services.size();
    328             for (int i = 0; i < numServices; i++) {
    329                 if (services.get(i).getComponentName().equals(mComponentName)) {
    330                     service = services.get(i);
    331                     break;
    332                 }
    333             }
    334         }
    335 
    336         if (service == null) {
    337             // The print service was uninstalled
    338             finishFragment();
    339         }
    340 
    341         mServiceEnabled = service.isEnabled();
    342 
    343         if (service.getSettingsActivityName() != null) {
    344             Intent settingsIntent = new Intent(Intent.ACTION_MAIN);
    345 
    346             settingsIntent.setComponent(
    347                     new ComponentName(service.getComponentName().getPackageName(),
    348                             service.getSettingsActivityName()));
    349 
    350             List<ResolveInfo> resolvedActivities = getPackageManager().queryIntentActivities(
    351                     settingsIntent, 0);
    352             if (!resolvedActivities.isEmpty()) {
    353                 // The activity is a component name, therefore it is one or none.
    354                 if (resolvedActivities.get(0).activityInfo.exported) {
    355                     mSettingsIntent = settingsIntent;
    356                 }
    357             }
    358         } else {
    359             mSettingsIntent = null;
    360         }
    361 
    362         if (service.getAddPrintersActivityName() != null) {
    363             Intent addPrintersIntent = new Intent(Intent.ACTION_MAIN);
    364 
    365             addPrintersIntent.setComponent(
    366                     new ComponentName(service.getComponentName().getPackageName(),
    367                             service.getAddPrintersActivityName()));
    368 
    369             List<ResolveInfo> resolvedActivities = getPackageManager().queryIntentActivities(
    370                     addPrintersIntent, 0);
    371             if (!resolvedActivities.isEmpty()) {
    372                 // The activity is a component name, therefore it is one or none.
    373                 if (resolvedActivities.get(0).activityInfo.exported) {
    374                     mAddPrintersIntent = addPrintersIntent;
    375                 }
    376             }
    377         } else {
    378             mAddPrintersIntent = null;
    379         }
    380 
    381         updateUiForServiceState();
    382     }
    383 
    384     @Override
    385     public void onLoaderReset(Loader<List<PrintServiceInfo>> loader) {
    386         updateUiForServiceState();
    387     }
    388 
    389     @Override
    390     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    391         super.onCreateOptionsMenu(menu, inflater);
    392         inflater.inflate(R.menu.print_service_settings, menu);
    393 
    394         MenuItem addPrinters = menu.findItem(R.id.print_menu_item_add_printer);
    395         if (mServiceEnabled && mAddPrintersIntent != null) {
    396             addPrinters.setIntent(mAddPrintersIntent);
    397         } else {
    398             menu.removeItem(R.id.print_menu_item_add_printer);
    399         }
    400 
    401         MenuItem settings = menu.findItem(R.id.print_menu_item_settings);
    402         if (mServiceEnabled && mSettingsIntent != null) {
    403             settings.setIntent(mSettingsIntent);
    404         } else {
    405             menu.removeItem(R.id.print_menu_item_settings);
    406         }
    407 
    408         MenuItem searchItem = menu.findItem(R.id.print_menu_item_search);
    409         if (mServiceEnabled && mPrintersAdapter.getUnfilteredCount() > 0) {
    410             mSearchView = (SearchView) searchItem.getActionView();
    411             mSearchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
    412                 @Override
    413                 public boolean onQueryTextSubmit(String query) {
    414                     return true;
    415                 }
    416 
    417                 @Override
    418                 public boolean onQueryTextChange(String searchString) {
    419                     mPrintersAdapter.getFilter().filter(searchString);
    420                     return true;
    421                 }
    422             });
    423             mSearchView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
    424                 @Override
    425                 public void onViewAttachedToWindow(View view) {
    426                     if (AccessibilityManager.getInstance(getActivity()).isEnabled()) {
    427                         view.announceForAccessibility(getString(
    428                                 R.string.print_search_box_shown_utterance));
    429                     }
    430                 }
    431                 @Override
    432                 public void onViewDetachedFromWindow(View view) {
    433                     Activity activity = getActivity();
    434                     if (activity != null && !activity.isFinishing()
    435                             && AccessibilityManager.getInstance(activity).isEnabled()) {
    436                         view.announceForAccessibility(getString(
    437                                 R.string.print_search_box_hidden_utterance));
    438                     }
    439                 }
    440             });
    441         } else {
    442             menu.removeItem(R.id.print_menu_item_search);
    443         }
    444     }
    445 
    446     private final class PrintersAdapter extends BaseAdapter
    447             implements LoaderManager.LoaderCallbacks<List<PrinterInfo>>, Filterable {
    448         private final Object mLock = new Object();
    449 
    450         private final List<PrinterInfo> mPrinters = new ArrayList<PrinterInfo>();
    451 
    452         private final List<PrinterInfo> mFilteredPrinters = new ArrayList<PrinterInfo>();
    453 
    454         private CharSequence mLastSearchString;
    455 
    456         public void enable() {
    457             getLoaderManager().initLoader(LOADER_ID_PRINTERS_LOADER, null, this);
    458         }
    459 
    460         public void disable() {
    461             getLoaderManager().destroyLoader(LOADER_ID_PRINTERS_LOADER);
    462             mPrinters.clear();
    463         }
    464 
    465         public int getUnfilteredCount() {
    466             return mPrinters.size();
    467         }
    468 
    469         @Override
    470         public Filter getFilter() {
    471             return new Filter() {
    472                 @Override
    473                 protected FilterResults performFiltering(CharSequence constraint) {
    474                     synchronized (mLock) {
    475                         if (TextUtils.isEmpty(constraint)) {
    476                             return null;
    477                         }
    478                         FilterResults results = new FilterResults();
    479                         List<PrinterInfo> filteredPrinters = new ArrayList<PrinterInfo>();
    480                         String constraintLowerCase = constraint.toString().toLowerCase();
    481                         final int printerCount = mPrinters.size();
    482                         for (int i = 0; i < printerCount; i++) {
    483                             PrinterInfo printer = mPrinters.get(i);
    484                             String name = printer.getName();
    485                             if (name != null && name.toLowerCase().contains(constraintLowerCase)) {
    486                                 filteredPrinters.add(printer);
    487                             }
    488                         }
    489                         results.values = filteredPrinters;
    490                         results.count = filteredPrinters.size();
    491                         return results;
    492                     }
    493                 }
    494 
    495                 @Override
    496                 @SuppressWarnings("unchecked")
    497                 protected void publishResults(CharSequence constraint, FilterResults results) {
    498                     synchronized (mLock) {
    499                         mLastSearchString = constraint;
    500                         mFilteredPrinters.clear();
    501                         if (results == null) {
    502                             mFilteredPrinters.addAll(mPrinters);
    503                         } else {
    504                             List<PrinterInfo> printers = (List<PrinterInfo>) results.values;
    505                             mFilteredPrinters.addAll(printers);
    506                         }
    507                     }
    508                     notifyDataSetChanged();
    509                 }
    510             };
    511         }
    512 
    513         @Override
    514         public int getCount() {
    515             synchronized (mLock) {
    516                 return mFilteredPrinters.size();
    517             }
    518         }
    519 
    520         @Override
    521         public Object getItem(int position) {
    522             synchronized (mLock) {
    523                 return mFilteredPrinters.get(position);
    524             }
    525         }
    526 
    527         @Override
    528         public long getItemId(int position) {
    529             return position;
    530         }
    531 
    532         /**
    533          * Checks if a printer can be used for printing
    534          *
    535          * @param position The position of the printer in the list
    536          * @return true iff the printer can be used for printing.
    537          */
    538         public boolean isActionable(int position) {
    539             PrinterInfo printer = (PrinterInfo) getItem(position);
    540             return printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE;
    541         }
    542 
    543         @Override
    544         public View getView(int position, View convertView, ViewGroup parent) {
    545             if (convertView == null) {
    546                 convertView = getActivity().getLayoutInflater().inflate(
    547                         R.layout.printer_dropdown_item, parent, false);
    548             }
    549 
    550             convertView.setEnabled(isActionable(position));
    551 
    552             final PrinterInfo printer = (PrinterInfo) getItem(position);
    553             CharSequence title = printer.getName();
    554             CharSequence subtitle = printer.getDescription();
    555             Drawable icon = printer.loadIcon(getActivity());
    556 
    557             TextView titleView = (TextView) convertView.findViewById(R.id.title);
    558             titleView.setText(title);
    559 
    560             TextView subtitleView = (TextView) convertView.findViewById(R.id.subtitle);
    561             if (!TextUtils.isEmpty(subtitle)) {
    562                 subtitleView.setText(subtitle);
    563                 subtitleView.setVisibility(View.VISIBLE);
    564             } else {
    565                 subtitleView.setText(null);
    566                 subtitleView.setVisibility(View.GONE);
    567             }
    568 
    569             LinearLayout moreInfoView = (LinearLayout) convertView.findViewById(R.id.more_info);
    570             if (printer.getInfoIntent() != null) {
    571                 moreInfoView.setVisibility(View.VISIBLE);
    572                 moreInfoView.setOnClickListener(new OnClickListener() {
    573                     @Override
    574                     public void onClick(View v) {
    575                         try {
    576                             getActivity().startIntentSender(
    577                                     printer.getInfoIntent().getIntentSender(), null, 0, 0, 0);
    578                         } catch (SendIntentException e) {
    579                             Log.e(LOG_TAG, "Could not execute pending info intent: %s", e);
    580                         }
    581                     }
    582                 });
    583             } else {
    584                 moreInfoView.setVisibility(View.GONE);
    585             }
    586 
    587             ImageView iconView = (ImageView) convertView.findViewById(R.id.icon);
    588             if (icon != null) {
    589                 iconView.setVisibility(View.VISIBLE);
    590                 if (!isActionable(position)) {
    591                     icon.mutate();
    592 
    593                     TypedValue value = new TypedValue();
    594                     getActivity().getTheme().resolveAttribute(android.R.attr.disabledAlpha, value,
    595                             true);
    596                     icon.setAlpha((int)(value.getFloat() * 255));
    597                 }
    598                 iconView.setImageDrawable(icon);
    599             } else {
    600                 iconView.setVisibility(View.GONE);
    601             }
    602 
    603             return convertView;
    604         }
    605 
    606         @Override
    607         public Loader<List<PrinterInfo>> onCreateLoader(int id, Bundle args) {
    608             if (id == LOADER_ID_PRINTERS_LOADER) {
    609                 return new PrintersLoader(getContext());
    610             }
    611             return null;
    612         }
    613 
    614         @Override
    615         public void onLoadFinished(Loader<List<PrinterInfo>> loader,
    616                 List<PrinterInfo> printers) {
    617             synchronized (mLock) {
    618                 mPrinters.clear();
    619                 final int printerCount = printers.size();
    620                 for (int i = 0; i < printerCount; i++) {
    621                     PrinterInfo printer = printers.get(i);
    622                     if (printer.getId().getServiceName().equals(mComponentName)) {
    623                         mPrinters.add(printer);
    624                     }
    625                 }
    626                 mFilteredPrinters.clear();
    627                 mFilteredPrinters.addAll(mPrinters);
    628                 if (!TextUtils.isEmpty(mLastSearchString)) {
    629                     getFilter().filter(mLastSearchString);
    630                 }
    631             }
    632             notifyDataSetChanged();
    633         }
    634 
    635         @Override
    636         public void onLoaderReset(Loader<List<PrinterInfo>> loader) {
    637             synchronized (mLock) {
    638                 mPrinters.clear();
    639                 mFilteredPrinters.clear();
    640                 mLastSearchString = null;
    641             }
    642             notifyDataSetInvalidated();
    643         }
    644     }
    645 
    646     private static class PrintersLoader extends Loader<List<PrinterInfo>> {
    647 
    648         private static final String LOG_TAG = "PrintersLoader";
    649 
    650         private static final boolean DEBUG = false;
    651 
    652         private final Map<PrinterId, PrinterInfo> mPrinters =
    653                 new LinkedHashMap<PrinterId, PrinterInfo>();
    654 
    655         private PrinterDiscoverySession mDiscoverySession;
    656 
    657         public PrintersLoader(Context context) {
    658             super(context);
    659         }
    660 
    661         @Override
    662         public void deliverResult(List<PrinterInfo> printers) {
    663             if (isStarted()) {
    664                 super.deliverResult(printers);
    665             }
    666         }
    667 
    668         @Override
    669         protected void onStartLoading() {
    670             if (DEBUG) {
    671                 Log.i(LOG_TAG, "onStartLoading()");
    672             }
    673             // The contract is that if we already have a valid,
    674             // result the we have to deliver it immediately.
    675             if (!mPrinters.isEmpty()) {
    676                 deliverResult(new ArrayList<PrinterInfo>(mPrinters.values()));
    677             }
    678             // We want to start discovery at this point.
    679             onForceLoad();
    680         }
    681 
    682         @Override
    683         protected void onStopLoading() {
    684             if (DEBUG) {
    685                 Log.i(LOG_TAG, "onStopLoading()");
    686             }
    687             onCancelLoad();
    688         }
    689 
    690         @Override
    691         protected void onForceLoad() {
    692             if (DEBUG) {
    693                 Log.i(LOG_TAG, "onForceLoad()");
    694             }
    695             loadInternal();
    696         }
    697 
    698         @Override
    699         protected boolean onCancelLoad() {
    700             if (DEBUG) {
    701                 Log.i(LOG_TAG, "onCancelLoad()");
    702             }
    703             return cancelInternal();
    704         }
    705 
    706         @Override
    707         protected void onReset() {
    708             if (DEBUG) {
    709                 Log.i(LOG_TAG, "onReset()");
    710             }
    711             onStopLoading();
    712             mPrinters.clear();
    713             if (mDiscoverySession != null) {
    714                 mDiscoverySession.destroy();
    715                 mDiscoverySession = null;
    716             }
    717         }
    718 
    719         @Override
    720         protected void onAbandon() {
    721             if (DEBUG) {
    722                 Log.i(LOG_TAG, "onAbandon()");
    723             }
    724             onStopLoading();
    725         }
    726 
    727         private boolean cancelInternal() {
    728             if (mDiscoverySession != null
    729                     && mDiscoverySession.isPrinterDiscoveryStarted()) {
    730                 mDiscoverySession.stopPrinterDiscovery();
    731                 return true;
    732             }
    733             return false;
    734         }
    735 
    736         private void loadInternal() {
    737             if (mDiscoverySession == null) {
    738                 PrintManager printManager = (PrintManager) getContext()
    739                         .getSystemService(Context.PRINT_SERVICE);
    740                 mDiscoverySession = printManager.createPrinterDiscoverySession();
    741                 mDiscoverySession.setOnPrintersChangeListener(new OnPrintersChangeListener() {
    742                     @Override
    743                     public void onPrintersChanged() {
    744                         deliverResult(new ArrayList<PrinterInfo>(
    745                                 mDiscoverySession.getPrinters()));
    746                     }
    747                 });
    748             }
    749             mDiscoverySession.startPrinterDiscovery(null);
    750         }
    751     }
    752 }
    753