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