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
getMetricsCategory()130     protected int getMetricsCategory() {
131         return MetricsEvent.PRINT_SERVICE_SETTINGS;
132     }
133 
134     @Override
onCreate(Bundle icicle)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
onStart()147     public void onStart() {
148         super.onStart();
149         updateEmptyView();
150         updateUiForServiceState();
151     }
152 
153     @Override
onPause()154     public void onPause() {
155         if (mSearchView != null) {
156             mSearchView.setOnQueryTextListener(null);
157         }
158         super.onPause();
159     }
160 
161     @Override
onStop()162     public void onStop() {
163         super.onStop();
164     }
165 
166     @Override
onViewCreated(View view, Bundle savedInstanceState)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
onDestroyView()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 
onPreferenceToggled(String preferenceKey, boolean enabled)184     private void onPreferenceToggled(String preferenceKey, boolean enabled) {
185         ((PrintManager)getContext().getSystemService(Context.PRINT_SERVICE))
186                 .setPrintServiceEnabled(mComponentName, enabled);
187     }
188 
getBackupListView()189     private ListView getBackupListView() {
190         return (ListView) getView().findViewById(R.id.backup_list);
191     }
192 
updateEmptyView()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 
updateUiForServiceState()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 
initComponents()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
onSwitchChanged(Switch switchView, boolean isChecked)292     public void onSwitchChanged(Switch switchView, boolean isChecked) {
293         updateEmptyView();
294     }
295 
updateUiForArguments()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
onCreateLoader(int id, Bundle args)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
onLoadFinished(Loader<List<PrintServiceInfo>> loader, List<PrintServiceInfo> services)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
onLoaderReset(Loader<List<PrintServiceInfo>> loader)385     public void onLoaderReset(Loader<List<PrintServiceInfo>> loader) {
386         updateUiForServiceState();
387     }
388 
389     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)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 
enable()456         public void enable() {
457             getLoaderManager().initLoader(LOADER_ID_PRINTERS_LOADER, null, this);
458         }
459 
disable()460         public void disable() {
461             getLoaderManager().destroyLoader(LOADER_ID_PRINTERS_LOADER);
462             mPrinters.clear();
463         }
464 
getUnfilteredCount()465         public int getUnfilteredCount() {
466             return mPrinters.size();
467         }
468 
469         @Override
getFilter()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