2 * Copyright (C) 2013 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.settings.print;
19 import android.app.Activity;
20 import android.app.AlertDialog;
21 import android.app.Dialog;
22 import android.app.LoaderManager;
23 import android.content.ComponentName;
24 import android.content.ContentResolver;
25 import android.content.Context;
26 import android.content.DialogInterface;
27 import android.content.Intent;
28 import android.content.Loader;
29 import android.content.pm.PackageInfo;
30 import android.content.pm.PackageManager.NameNotFoundException;
31 import android.content.pm.ResolveInfo;
32 import android.database.ContentObserver;
33 import android.database.DataSetObserver;
34 import android.graphics.Color;
35 import android.graphics.drawable.ColorDrawable;
36 import android.graphics.drawable.Drawable;
37 import android.net.Uri;
38 import android.os.Bundle;
39 import android.os.Handler;
40 import android.print.PrintManager;
41 import android.print.PrinterDiscoverySession;
42 import android.print.PrinterDiscoverySession.OnPrintersChangeListener;
43 import android.print.PrinterId;
44 import android.print.PrinterInfo;
45 import android.text.TextUtils;
46 import android.util.Log;
47 import android.view.Menu;
48 import android.view.MenuInflater;
49 import android.view.MenuItem;
50 import android.view.View;
51 import android.view.ViewGroup;
52 import android.view.accessibility.AccessibilityManager;
53 import android.widget.BaseAdapter;
54 import android.widget.Filter;
55 import android.widget.Filterable;
56 import android.widget.ImageView;
57 import android.widget.ListView;
58 import android.widget.SearchView;
59 import android.widget.Switch;
60 import android.widget.TextView;
62 import com.android.settings.R;
63 import com.android.settings.SettingsActivity;
64 import com.android.settings.SettingsPreferenceFragment;
66 import java.util.ArrayList;
67 import java.util.LinkedHashMap;
68 import java.util.List;
71 import com.android.settings.widget.SwitchBar;
72 import com.android.settings.widget.ToggleSwitch;
75 * Fragment with print service settings.
77 public class PrintServiceSettingsFragment extends SettingsPreferenceFragment
78 implements DialogInterface.OnClickListener, SwitchBar.OnSwitchChangeListener {
80 private static final int LOADER_ID_PRINTERS_LOADER = 1;
82 private static final int DIALOG_ID_ENABLE_WARNING = 1;
84 private final SettingsContentObserver mSettingsContentObserver =
85 new SettingsContentObserver(new Handler()) {
87 public void onChange(boolean selfChange, Uri uri) {
88 updateUiForServiceState();
92 private final DataSetObserver mDataObserver = new DataSetObserver() {
94 public void onChanged() {
95 invalidateOptionsMenuIfNeeded();
100 public void onInvalidated() {
101 invalidateOptionsMenuIfNeeded();
104 private void invalidateOptionsMenuIfNeeded() {
105 final int unfilteredItemCount = mPrintersAdapter.getUnfilteredCount();
106 if ((mLastUnfilteredItemCount <= 0 && unfilteredItemCount > 0)
107 || mLastUnfilteredItemCount > 0 && unfilteredItemCount <= 0) {
108 getActivity().invalidateOptionsMenu();
110 mLastUnfilteredItemCount = unfilteredItemCount;
114 private SwitchBar mSwitchBar;
115 private ToggleSwitch mToggleSwitch;
117 private String mPreferenceKey;
119 private CharSequence mSettingsTitle;
120 private Intent mSettingsIntent;
122 private CharSequence mAddPrintersTitle;
123 private Intent mAddPrintersIntent;
125 private CharSequence mEnableWarningTitle;
126 private CharSequence mEnableWarningMessage;
128 private ComponentName mComponentName;
130 private PrintersAdapter mPrintersAdapter;
132 // TODO: Showing sub-sub fragment does not handle the activity title
133 // so we do it but this is wrong. Do a real fix when there is time.
134 private CharSequence mOldActivityTitle;
136 private int mLastUnfilteredItemCount;
138 private boolean mServiceEnabled;
141 public void onResume() {
143 mSettingsContentObserver.register(getContentResolver());
145 updateUiForServiceState();
149 public void onPause() {
150 mSettingsContentObserver.unregister(getContentResolver());
155 public void onViewCreated(View view, Bundle savedInstanceState) {
156 super.onViewCreated(view, savedInstanceState);
158 updateUiForArguments();
162 public void onDestroyView() {
163 if (mOldActivityTitle != null) {
164 getActivity().getActionBar().setTitle(mOldActivityTitle);
166 super.onDestroyView();
167 mSwitchBar.removeOnSwitchChangeListener(this);
171 private void onPreferenceToggled(String preferenceKey, boolean enabled) {
172 ComponentName service = ComponentName.unflattenFromString(preferenceKey);
173 List<ComponentName> services = PrintSettingsUtils.readEnabledPrintServices(getActivity());
175 services.add(service);
177 services.remove(service);
179 PrintSettingsUtils.writeEnabledPrintServices(getActivity(), services);
183 public Dialog onCreateDialog(int dialogId) {
184 CharSequence title = null;
185 CharSequence message = null;
187 case DIALOG_ID_ENABLE_WARNING:
188 title = mEnableWarningTitle;
189 message = mEnableWarningMessage;
192 throw new IllegalArgumentException();
194 return new AlertDialog.Builder(getActivity())
198 .setPositiveButton(android.R.string.ok, this)
199 .setNegativeButton(android.R.string.cancel, this)
204 public void onClick(DialogInterface dialog, int which) {
205 final boolean checked;
207 case DialogInterface.BUTTON_POSITIVE:
209 mSwitchBar.setCheckedInternal(checked);
210 getArguments().putBoolean(PrintSettingsFragment.EXTRA_CHECKED, checked);
211 onPreferenceToggled(mPreferenceKey, checked);
213 case DialogInterface.BUTTON_NEGATIVE:
215 mSwitchBar.setCheckedInternal(checked);
216 getArguments().putBoolean(PrintSettingsFragment.EXTRA_CHECKED, checked);
217 onPreferenceToggled(mPreferenceKey, checked);
220 throw new IllegalArgumentException();
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);
233 if (emptyView == null) {
234 emptyView = getActivity().getLayoutInflater().inflate(
235 R.layout.empty_print_state, contentRoot, false);
236 ImageView iconView = (ImageView) emptyView.findViewById(R.id.icon);
237 iconView.setContentDescription(getString(R.string.print_service_disabled));
238 TextView textView = (TextView) emptyView.findViewById(R.id.message);
239 textView.setText(R.string.print_service_disabled);
240 contentRoot.addView(emptyView);
241 listView.setEmptyView(emptyView);
243 } else if (mPrintersAdapter.getUnfilteredCount() <= 0) {
244 if (emptyView != null
245 && emptyView.getId() != R.id.empty_printers_list_service_enabled) {
246 contentRoot.removeView(emptyView);
249 if (emptyView == null) {
250 emptyView = getActivity().getLayoutInflater().inflate(
251 R.layout.empty_printers_list_service_enabled, contentRoot, false);
252 contentRoot.addView(emptyView);
253 listView.setEmptyView(emptyView);
255 } else if (mPrintersAdapter.getCount() <= 0) {
256 if (emptyView != null && emptyView.getId() != R.id.empty_print_state) {
257 contentRoot.removeView(emptyView);
260 if (emptyView == null) {
261 emptyView = getActivity().getLayoutInflater().inflate(
262 R.layout.empty_print_state, contentRoot, false);
263 ImageView iconView = (ImageView) emptyView.findViewById(R.id.icon);
264 iconView.setContentDescription(getString(R.string.print_no_printers_found));
265 TextView textView = (TextView) emptyView.findViewById(R.id.message);
266 textView.setText(R.string.print_no_printers_found);
267 contentRoot.addView(emptyView);
268 listView.setEmptyView(emptyView);
273 private void updateUiForServiceState() {
274 List<ComponentName> services = PrintSettingsUtils.readEnabledPrintServices(getActivity());
275 mServiceEnabled = services.contains(mComponentName);
276 if (mServiceEnabled) {
277 mSwitchBar.setCheckedInternal(true);
278 mPrintersAdapter.enable();
280 mSwitchBar.setCheckedInternal(false);
281 mPrintersAdapter.disable();
283 getActivity().invalidateOptionsMenu();
286 private void initComponents() {
287 mPrintersAdapter = new PrintersAdapter();
288 mPrintersAdapter.registerDataSetObserver(mDataObserver);
290 final SettingsActivity activity = (SettingsActivity) getActivity();
292 mSwitchBar = activity.getSwitchBar();
293 mSwitchBar.addOnSwitchChangeListener(this);
296 mToggleSwitch = mSwitchBar.getSwitch();
297 mToggleSwitch.setOnBeforeCheckedChangeListener(new ToggleSwitch.OnBeforeCheckedChangeListener() {
299 public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked) {
301 if (!TextUtils.isEmpty(mEnableWarningMessage)) {
302 mSwitchBar.setCheckedInternal(false);
303 getArguments().putBoolean(PrintSettingsFragment.EXTRA_CHECKED, false);
304 showDialog(DIALOG_ID_ENABLE_WARNING);
307 onPreferenceToggled(mPreferenceKey, true);
309 onPreferenceToggled(mPreferenceKey, false);
315 getListView().setSelector(new ColorDrawable(Color.TRANSPARENT));
316 getListView().setAdapter(mPrintersAdapter);
321 public void onSwitchChanged(Switch switchView, boolean isChecked) {
325 private void updateUiForArguments() {
326 Bundle arguments = getArguments();
329 mPreferenceKey = arguments.getString(PrintSettingsFragment.EXTRA_PREFERENCE_KEY);
332 final boolean enabled = arguments.getBoolean(PrintSettingsFragment.EXTRA_CHECKED);
333 mSwitchBar.setCheckedInternal(enabled);
335 // Settings title and intent.
336 String settingsTitle = arguments.getString(PrintSettingsFragment.EXTRA_SETTINGS_TITLE);
337 String settingsComponentName = arguments.getString(
338 PrintSettingsFragment.EXTRA_SETTINGS_COMPONENT_NAME);
339 if (!TextUtils.isEmpty(settingsTitle) && !TextUtils.isEmpty(settingsComponentName)) {
340 Intent settingsIntent = new Intent(Intent.ACTION_MAIN).setComponent(
341 ComponentName.unflattenFromString(settingsComponentName.toString()));
342 List<ResolveInfo> resolvedActivities = getPackageManager().queryIntentActivities(
344 if (!resolvedActivities.isEmpty()) {
345 // The activity is a component name, therefore it is one or none.
346 if (resolvedActivities.get(0).activityInfo.exported) {
347 mSettingsTitle = settingsTitle;
348 mSettingsIntent = settingsIntent;
353 // Add printers title and intent.
354 String addPrintersTitle = arguments.getString(
355 PrintSettingsFragment.EXTRA_ADD_PRINTERS_TITLE);
356 String addPrintersComponentName =
357 arguments.getString(PrintSettingsFragment.EXTRA_ADD_PRINTERS_COMPONENT_NAME);
358 if (!TextUtils.isEmpty(addPrintersTitle)
359 && !TextUtils.isEmpty(addPrintersComponentName)) {
360 Intent addPritnersIntent = new Intent(Intent.ACTION_MAIN).setComponent(
361 ComponentName.unflattenFromString(addPrintersComponentName.toString()));
362 List<ResolveInfo> resolvedActivities = getPackageManager().queryIntentActivities(
363 addPritnersIntent, 0);
364 if (!resolvedActivities.isEmpty()) {
365 // The activity is a component name, therefore it is one or none.
366 if (resolvedActivities.get(0).activityInfo.exported) {
367 mAddPrintersTitle = addPrintersTitle;
368 mAddPrintersIntent = addPritnersIntent;
373 // Enable warning title.
374 mEnableWarningTitle = arguments.getCharSequence(
375 PrintSettingsFragment.EXTRA_ENABLE_WARNING_TITLE);
377 // Enable warning message.
378 mEnableWarningMessage = arguments.getCharSequence(
379 PrintSettingsFragment.EXTRA_ENABLE_WARNING_MESSAGE);
382 mComponentName = ComponentName.unflattenFromString(arguments
383 .getString(PrintSettingsFragment.EXTRA_SERVICE_COMPONENT_NAME));
385 setHasOptionsMenu(true);
389 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
390 super.onCreateOptionsMenu(menu, inflater);
391 inflater.inflate(R.menu.print_service_settings, menu);
393 MenuItem addPrinters = menu.findItem(R.id.print_menu_item_add_printer);
394 if (mServiceEnabled && !TextUtils.isEmpty(mAddPrintersTitle)
395 && mAddPrintersIntent != null) {
396 addPrinters.setIntent(mAddPrintersIntent);
398 menu.removeItem(R.id.print_menu_item_add_printer);
401 MenuItem settings = menu.findItem(R.id.print_menu_item_settings);
402 if (mServiceEnabled && !TextUtils.isEmpty(mSettingsTitle)
403 && mSettingsIntent != null) {
404 settings.setIntent(mSettingsIntent);
406 menu.removeItem(R.id.print_menu_item_settings);
409 MenuItem searchItem = menu.findItem(R.id.print_menu_item_search);
410 if (mServiceEnabled && mPrintersAdapter.getUnfilteredCount() > 0) {
411 SearchView searchView = (SearchView) searchItem.getActionView();
412 searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
414 public boolean onQueryTextSubmit(String query) {
419 public boolean onQueryTextChange(String searchString) {
420 ((Filterable) getListView().getAdapter()).getFilter().filter(searchString);
424 searchView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
426 public void onViewAttachedToWindow(View view) {
427 if (AccessibilityManager.getInstance(getActivity()).isEnabled()) {
428 view.announceForAccessibility(getString(
429 R.string.print_search_box_shown_utterance));
433 public void onViewDetachedFromWindow(View view) {
434 Activity activity = getActivity();
435 if (activity != null && !activity.isFinishing()
436 && AccessibilityManager.getInstance(activity).isEnabled()) {
437 view.announceForAccessibility(getString(
438 R.string.print_search_box_hidden_utterance));
443 menu.removeItem(R.id.print_menu_item_search);
447 private static abstract class SettingsContentObserver extends ContentObserver {
449 public SettingsContentObserver(Handler handler) {
453 public void register(ContentResolver contentResolver) {
454 contentResolver.registerContentObserver(android.provider.Settings.Secure.getUriFor(
455 android.provider.Settings.Secure.ENABLED_PRINT_SERVICES), false, this);
458 public void unregister(ContentResolver contentResolver) {
459 contentResolver.unregisterContentObserver(this);
463 public abstract void onChange(boolean selfChange, Uri uri);
466 private final class PrintersAdapter extends BaseAdapter
467 implements LoaderManager.LoaderCallbacks<List<PrinterInfo>>, Filterable {
468 private final Object mLock = new Object();
470 private final List<PrinterInfo> mPrinters = new ArrayList<PrinterInfo>();
472 private final List<PrinterInfo> mFilteredPrinters = new ArrayList<PrinterInfo>();
474 private CharSequence mLastSearchString;
476 public void enable() {
477 getLoaderManager().initLoader(LOADER_ID_PRINTERS_LOADER, null, this);
480 public void disable() {
481 getLoaderManager().destroyLoader(LOADER_ID_PRINTERS_LOADER);
485 public int getUnfilteredCount() {
486 return mPrinters.size();
490 public Filter getFilter() {
491 return new Filter() {
493 protected FilterResults performFiltering(CharSequence constraint) {
494 synchronized (mLock) {
495 if (TextUtils.isEmpty(constraint)) {
498 FilterResults results = new FilterResults();
499 List<PrinterInfo> filteredPrinters = new ArrayList<PrinterInfo>();
500 String constraintLowerCase = constraint.toString().toLowerCase();
501 final int printerCount = mPrinters.size();
502 for (int i = 0; i < printerCount; i++) {
503 PrinterInfo printer = mPrinters.get(i);
504 if (printer.getName().toLowerCase().contains(constraintLowerCase)) {
505 filteredPrinters.add(printer);
508 results.values = filteredPrinters;
509 results.count = filteredPrinters.size();
515 @SuppressWarnings("unchecked")
516 protected void publishResults(CharSequence constraint, FilterResults results) {
517 synchronized (mLock) {
518 mLastSearchString = constraint;
519 mFilteredPrinters.clear();
520 if (results == null) {
521 mFilteredPrinters.addAll(mPrinters);
523 List<PrinterInfo> printers = (List<PrinterInfo>) results.values;
524 mFilteredPrinters.addAll(printers);
527 notifyDataSetChanged();
533 public int getCount() {
534 synchronized (mLock) {
535 return mFilteredPrinters.size();
540 public Object getItem(int position) {
541 synchronized (mLock) {
542 return mFilteredPrinters.get(position);
547 public long getItemId(int position) {
552 public View getView(int position, View convertView, ViewGroup parent) {
553 if (convertView == null) {
554 convertView = getActivity().getLayoutInflater().inflate(
555 R.layout.printer_dropdown_item, parent, false);
558 PrinterInfo printer = (PrinterInfo) getItem(position);
559 CharSequence title = printer.getName();
560 CharSequence subtitle = null;
561 Drawable icon = null;
563 PackageInfo packageInfo = getPackageManager().getPackageInfo(
564 printer.getId().getServiceName().getPackageName(), 0);
565 subtitle = packageInfo.applicationInfo.loadLabel(getPackageManager());
566 icon = packageInfo.applicationInfo.loadIcon(getPackageManager());
567 } catch (NameNotFoundException nnfe) {
571 TextView titleView = (TextView) convertView.findViewById(R.id.title);
572 titleView.setText(title);
574 TextView subtitleView = (TextView) convertView.findViewById(R.id.subtitle);
575 if (!TextUtils.isEmpty(subtitle)) {
576 subtitleView.setText(subtitle);
577 subtitleView.setVisibility(View.VISIBLE);
579 subtitleView.setText(null);
580 subtitleView.setVisibility(View.GONE);
583 ImageView iconView = (ImageView) convertView.findViewById(R.id.icon);
585 iconView.setImageDrawable(icon);
586 iconView.setVisibility(View.VISIBLE);
588 iconView.setVisibility(View.GONE);
595 public boolean isEnabled(int position) {
600 public Loader<List<PrinterInfo>> onCreateLoader(int id, Bundle args) {
601 if (id == LOADER_ID_PRINTERS_LOADER) {
602 return new PrintersLoader(getActivity());
608 public void onLoadFinished(Loader<List<PrinterInfo>> loader,
609 List<PrinterInfo> printers) {
610 synchronized (mLock) {
612 final int printerCount = printers.size();
613 for (int i = 0; i < printerCount; i++) {
614 PrinterInfo printer = printers.get(i);
615 if (printer.getId().getServiceName().equals(mComponentName)) {
616 mPrinters.add(printer);
619 mFilteredPrinters.clear();
620 mFilteredPrinters.addAll(mPrinters);
621 if (!TextUtils.isEmpty(mLastSearchString)) {
622 getFilter().filter(mLastSearchString);
625 notifyDataSetChanged();
629 public void onLoaderReset(Loader<List<PrinterInfo>> loader) {
630 synchronized (mLock) {
632 mFilteredPrinters.clear();
633 mLastSearchString = null;
635 notifyDataSetInvalidated();
639 private static class PrintersLoader extends Loader<List<PrinterInfo>> {
641 private static final String LOG_TAG = "PrintersLoader";
643 private static final boolean DEBUG = false;
645 private final Map<PrinterId, PrinterInfo> mPrinters =
646 new LinkedHashMap<PrinterId, PrinterInfo>();
648 private PrinterDiscoverySession mDiscoverySession;
650 public PrintersLoader(Context context) {
655 public void deliverResult(List<PrinterInfo> printers) {
657 super.deliverResult(printers);
662 protected void onStartLoading() {
664 Log.i(LOG_TAG, "onStartLoading()");
666 // The contract is that if we already have a valid,
667 // result the we have to deliver it immediately.
668 if (!mPrinters.isEmpty()) {
669 deliverResult(new ArrayList<PrinterInfo>(mPrinters.values()));
671 // We want to start discovery at this point.
676 protected void onStopLoading() {
678 Log.i(LOG_TAG, "onStopLoading()");
684 protected void onForceLoad() {
686 Log.i(LOG_TAG, "onForceLoad()");
692 protected boolean onCancelLoad() {
694 Log.i(LOG_TAG, "onCancelLoad()");
696 return cancelInternal();
700 protected void onReset() {
702 Log.i(LOG_TAG, "onReset()");
706 if (mDiscoverySession != null) {
707 mDiscoverySession.destroy();
708 mDiscoverySession = null;
713 protected void onAbandon() {
715 Log.i(LOG_TAG, "onAbandon()");
720 private boolean cancelInternal() {
721 if (mDiscoverySession != null
722 && mDiscoverySession.isPrinterDiscoveryStarted()) {
723 mDiscoverySession.stopPrinterDiscovery();
729 private void loadInternal() {
730 if (mDiscoverySession == null) {
731 PrintManager printManager = (PrintManager) getContext()
732 .getSystemService(Context.PRINT_SERVICE);
733 mDiscoverySession = printManager.createPrinterDiscoverySession();
734 mDiscoverySession.setOnPrintersChangeListener(new OnPrintersChangeListener() {
736 public void onPrintersChanged() {
737 deliverResult(new ArrayList<PrinterInfo>(
738 mDiscoverySession.getPrinters()));
742 mDiscoverySession.startPrinterDiscovery(null);