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