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.LoaderManager.LoaderCallbacks;
21 import android.content.ActivityNotFoundException;
22 import android.content.AsyncTaskLoader;
23 import android.content.ComponentName;
24 import android.content.ContentResolver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.Loader;
28 import android.content.pm.PackageManager;
29 import android.content.pm.ResolveInfo;
30 import android.database.ContentObserver;
31 import android.graphics.drawable.Drawable;
32 import android.net.Uri;
33 import android.os.Bundle;
34 import android.os.Handler;
35 import android.os.Message;
36 import android.os.UserHandle;
37 import android.os.UserManager;
38 import android.print.PrintJob;
39 import android.print.PrintJobId;
40 import android.print.PrintJobInfo;
41 import android.print.PrintManager;
42 import android.print.PrintManager.PrintJobStateChangeListener;
43 import android.printservice.PrintServiceInfo;
44 import android.provider.SearchIndexableResource;
45 import android.provider.Settings;
46 import android.support.v7.preference.Preference;
47 import android.support.v7.preference.PreferenceCategory;
48 import android.support.v7.preference.PreferenceScreen;
49 import android.text.TextUtils;
50 import android.text.format.DateUtils;
51 import android.util.Log;
52 import android.view.View;
53 import android.view.View.OnClickListener;
54 import android.view.ViewGroup;
55 import android.widget.AdapterView;
56 import android.widget.AdapterView.OnItemSelectedListener;
57 import android.widget.Button;
58 import android.widget.Spinner;
59 import android.widget.TextView;
61 import com.android.internal.content.PackageMonitor;
62 import com.android.internal.logging.MetricsProto.MetricsEvent;
63 import com.android.settings.DialogCreatable;
64 import com.android.settings.R;
65 import com.android.settings.SettingsPreferenceFragment;
66 import com.android.settings.dashboard.SummaryLoader;
67 import com.android.settings.search.BaseSearchIndexProvider;
68 import com.android.settings.search.Indexable;
69 import com.android.settings.search.SearchIndexableRaw;
70 import com.android.settingslib.drawer.UserAdapter;
72 import java.text.DateFormat;
73 import java.util.ArrayList;
74 import java.util.List;
77 * Fragment with the top level print settings.
79 public class PrintSettingsFragment extends SettingsPreferenceFragment
80 implements DialogCreatable, Indexable, OnItemSelectedListener, OnClickListener {
81 public static final String TAG = "PrintSettingsFragment";
82 private static final int LOADER_ID_PRINT_JOBS_LOADER = 1;
84 private static final String PRINT_JOBS_CATEGORY = "print_jobs_category";
85 private static final String PRINT_SERVICES_CATEGORY = "print_services_category";
87 // Extras passed to sub-fragments.
88 static final String EXTRA_PREFERENCE_KEY = "EXTRA_PREFERENCE_KEY";
89 static final String EXTRA_CHECKED = "EXTRA_CHECKED";
90 static final String EXTRA_TITLE = "EXTRA_TITLE";
91 static final String EXTRA_SETTINGS_TITLE = "EXTRA_SETTINGS_TITLE";
92 static final String EXTRA_SETTINGS_COMPONENT_NAME = "EXTRA_SETTINGS_COMPONENT_NAME";
93 static final String EXTRA_ADD_PRINTERS_TITLE = "EXTRA_ADD_PRINTERS_TITLE";
94 static final String EXTRA_ADD_PRINTERS_COMPONENT_NAME = "EXTRA_ADD_PRINTERS_COMPONENT_NAME";
95 static final String EXTRA_SERVICE_COMPONENT_NAME = "EXTRA_SERVICE_COMPONENT_NAME";
97 static final String EXTRA_PRINT_JOB_ID = "EXTRA_PRINT_JOB_ID";
99 private static final String EXTRA_PRINT_SERVICE_COMPONENT_NAME =
100 "EXTRA_PRINT_SERVICE_COMPONENT_NAME";
102 private static final int ORDER_LAST = 1000;
104 private final PackageMonitor mSettingsPackageMonitor = new SettingsPackageMonitor();
106 private final Handler mHandler = new Handler() {
108 public void dispatchMessage(Message msg) {
109 updateServicesPreferences();
113 private final SettingsContentObserver mSettingsContentObserver =
114 new SettingsContentObserver(mHandler) {
116 public void onChange(boolean selfChange, Uri uri) {
117 updateServicesPreferences();
121 private PreferenceCategory mActivePrintJobsCategory;
122 private PreferenceCategory mPrintServicesCategory;
124 private PrintJobsController mPrintJobsController;
125 private UserAdapter mProfileSpinnerAdapter;
126 private Spinner mSpinner;
127 private Button mAddNewServiceButton;
130 protected int getMetricsCategory() {
131 return MetricsEvent.PRINT_SETTINGS;
135 protected int getHelpResource() {
136 return R.string.help_uri_printing;
140 public void onCreate(Bundle icicle) {
141 super.onCreate(icicle);
142 addPreferencesFromResource(R.xml.print_settings);
144 mActivePrintJobsCategory = (PreferenceCategory) findPreference(
145 PRINT_JOBS_CATEGORY);
146 mPrintServicesCategory = (PreferenceCategory) findPreference(
147 PRINT_SERVICES_CATEGORY);
148 getPreferenceScreen().removePreference(mActivePrintJobsCategory);
150 mPrintJobsController = new PrintJobsController();
151 getActivity().getLoaderManager().initLoader(LOADER_ID_PRINT_JOBS_LOADER,
152 null, mPrintJobsController);
156 public void onStart() {
158 mSettingsPackageMonitor.register(getActivity(), getActivity().getMainLooper(), false);
159 mSettingsContentObserver.register(getContentResolver());
160 updateServicesPreferences();
161 setHasOptionsMenu(true);
162 startSubSettingsIfNeeded();
166 public void onStop() {
167 mSettingsPackageMonitor.unregister();
168 mSettingsContentObserver.unregister(getContentResolver());
173 public void onViewCreated(View view, Bundle savedInstanceState) {
174 super.onViewCreated(view, savedInstanceState);
175 ViewGroup contentRoot = (ViewGroup) getListView().getParent();
176 View emptyView = getActivity().getLayoutInflater().inflate(
177 R.layout.empty_print_state, contentRoot, false);
178 TextView textView = (TextView) emptyView.findViewById(R.id.message);
179 textView.setText(R.string.print_no_services_installed);
181 final Intent addNewServiceIntent = createAddNewServiceIntentOrNull();
182 if (addNewServiceIntent != null) {
183 mAddNewServiceButton = (Button) emptyView.findViewById(R.id.add_new_service);
184 mAddNewServiceButton.setOnClickListener(this);
185 // The empty is used elsewhere too so it's hidden by default.
186 mAddNewServiceButton.setVisibility(View.VISIBLE);
189 contentRoot.addView(emptyView);
190 setEmptyView(emptyView);
192 final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
193 mProfileSpinnerAdapter = UserAdapter.createUserSpinnerAdapter(um, getActivity());
194 if (mProfileSpinnerAdapter != null) {
195 mSpinner = (Spinner) setPinnedHeaderView(R.layout.spinner_view);
196 mSpinner.setAdapter(mProfileSpinnerAdapter);
197 mSpinner.setOnItemSelectedListener(this);
201 private void updateServicesPreferences() {
202 if (getPreferenceScreen().findPreference(PRINT_SERVICES_CATEGORY) == null) {
203 getPreferenceScreen().addPreference(mPrintServicesCategory);
205 // Since services category is auto generated we have to do a pass
206 // to generate it since services can come and go.
207 mPrintServicesCategory.removeAll();
210 List<ComponentName> disabledServices = PrintSettingsUtils
211 .readDisabledPrintServices(getActivity());
213 final PackageManager pm = getActivity().getPackageManager();
215 List<ResolveInfo> installedServices = pm
216 .queryIntentServices(
217 new Intent(android.printservice.PrintService.SERVICE_INTERFACE),
218 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
220 final int installedServiceCount = installedServices.size();
221 for (int i = 0; i < installedServiceCount; i++) {
222 ResolveInfo installedService = installedServices.get(i);
224 PreferenceScreen preference = getPreferenceManager().createPreferenceScreen(
227 String title = installedService.loadLabel(getPackageManager()).toString();
228 preference.setTitle(title);
230 ComponentName componentName = new ComponentName(
231 installedService.serviceInfo.packageName,
232 installedService.serviceInfo.name);
233 preference.setKey(componentName.flattenToString());
235 preference.setOrder(i);
236 preference.setFragment(PrintServiceSettingsFragment.class.getName());
237 preference.setPersistent(false);
239 final boolean serviceEnabled = !disabledServices.contains(componentName);
240 if (serviceEnabled) {
241 preference.setSummary(getString(R.string.print_feature_state_on));
243 preference.setSummary(getString(R.string.print_feature_state_off));
246 final Drawable drawable = installedService.loadIcon(pm);
247 if (drawable != null) {
248 preference.setIcon(drawable);
251 Bundle extras = preference.getExtras();
252 extras.putString(EXTRA_PREFERENCE_KEY, preference.getKey());
253 extras.putBoolean(EXTRA_CHECKED, serviceEnabled);
254 extras.putString(EXTRA_TITLE, title);
256 PrintServiceInfo printServiceInfo = PrintServiceInfo.create(
257 installedService, getActivity());
259 String settingsClassName = printServiceInfo.getSettingsActivityName();
260 if (!TextUtils.isEmpty(settingsClassName)) {
261 extras.putString(EXTRA_SETTINGS_TITLE,
262 getString(R.string.print_menu_item_settings));
263 extras.putString(EXTRA_SETTINGS_COMPONENT_NAME,
264 new ComponentName(installedService.serviceInfo.packageName,
265 settingsClassName).flattenToString());
268 String addPrinterClassName = printServiceInfo.getAddPrintersActivityName();
269 if (!TextUtils.isEmpty(addPrinterClassName)) {
270 extras.putString(EXTRA_ADD_PRINTERS_TITLE,
271 getString(R.string.print_menu_item_add_printers));
272 extras.putString(EXTRA_ADD_PRINTERS_COMPONENT_NAME,
273 new ComponentName(installedService.serviceInfo.packageName,
274 addPrinterClassName).flattenToString());
277 extras.putString(EXTRA_SERVICE_COMPONENT_NAME, componentName.flattenToString());
279 mPrintServicesCategory.addPreference(preference);
282 if (mPrintServicesCategory.getPreferenceCount() == 0) {
283 getPreferenceScreen().removePreference(mPrintServicesCategory);
285 final Preference addNewServicePreference = newAddServicePreferenceOrNull();
286 if (addNewServicePreference != null) {
287 mPrintServicesCategory.addPreference(addNewServicePreference);
292 private Preference newAddServicePreferenceOrNull() {
293 final Intent addNewServiceIntent = createAddNewServiceIntentOrNull();
294 if (addNewServiceIntent == null) {
297 Preference preference = new Preference(getPrefContext());
298 preference.setTitle(R.string.print_menu_item_add_service);
299 preference.setIcon(R.drawable.ic_menu_add);
300 preference.setOrder(ORDER_LAST);
301 preference.setIntent(addNewServiceIntent);
302 preference.setPersistent(false);
306 private Intent createAddNewServiceIntentOrNull() {
307 final String searchUri = Settings.Secure.getString(getContentResolver(),
308 Settings.Secure.PRINT_SERVICE_SEARCH_URI);
309 if (TextUtils.isEmpty(searchUri)) {
312 return new Intent(Intent.ACTION_VIEW, Uri.parse(searchUri));
315 private void startSubSettingsIfNeeded() {
316 if (getArguments() == null) {
319 String componentName = getArguments().getString(EXTRA_PRINT_SERVICE_COMPONENT_NAME);
320 if (componentName != null) {
321 getArguments().remove(EXTRA_PRINT_SERVICE_COMPONENT_NAME);
322 Preference prereference = findPreference(componentName);
323 if (prereference != null) {
324 prereference.performClick();
330 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
331 UserHandle selectedUser = mProfileSpinnerAdapter.getUserHandle(position);
332 if (selectedUser.getIdentifier() != UserHandle.myUserId()) {
333 Intent intent = new Intent(Settings.ACTION_PRINT_SETTINGS);
334 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
335 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
336 getActivity().startActivityAsUser(intent, selectedUser);
337 // Go back to default selection, which is the first one
338 mSpinner.setSelection(0);
343 public void onNothingSelected(AdapterView<?> parent) {
348 public void onClick(View v) {
349 if (mAddNewServiceButton == v) {
350 final Intent addNewServiceIntent = createAddNewServiceIntentOrNull();
351 if (addNewServiceIntent != null) { // check again just in case.
353 startActivity(addNewServiceIntent);
354 } catch (ActivityNotFoundException e) {
355 Log.w(TAG, "Unable to start activity", e);
361 private class SettingsPackageMonitor extends PackageMonitor {
363 public void onPackageAdded(String packageName, int uid) {
364 mHandler.obtainMessage().sendToTarget();
368 public void onPackageAppeared(String packageName, int reason) {
369 mHandler.obtainMessage().sendToTarget();
373 public void onPackageDisappeared(String packageName, int reason) {
374 mHandler.obtainMessage().sendToTarget();
378 public void onPackageRemoved(String packageName, int uid) {
379 mHandler.obtainMessage().sendToTarget();
383 private static abstract class SettingsContentObserver extends ContentObserver {
385 public SettingsContentObserver(Handler handler) {
389 public void register(ContentResolver contentResolver) {
390 contentResolver.registerContentObserver(Settings.Secure.getUriFor(
391 Settings.Secure.DISABLED_PRINT_SERVICES), false, this);
394 public void unregister(ContentResolver contentResolver) {
395 contentResolver.unregisterContentObserver(this);
399 public abstract void onChange(boolean selfChange, Uri uri);
402 private final class PrintJobsController implements LoaderCallbacks<List<PrintJobInfo>> {
405 public Loader<List<PrintJobInfo>> onCreateLoader(int id, Bundle args) {
406 if (id == LOADER_ID_PRINT_JOBS_LOADER) {
407 return new PrintJobsLoader(getActivity());
413 public void onLoadFinished(Loader<List<PrintJobInfo>> loader,
414 List<PrintJobInfo> printJobs) {
415 if (printJobs == null || printJobs.isEmpty()) {
416 getPreferenceScreen().removePreference(mActivePrintJobsCategory);
418 if (getPreferenceScreen().findPreference(PRINT_JOBS_CATEGORY) == null) {
419 getPreferenceScreen().addPreference(mActivePrintJobsCategory);
422 mActivePrintJobsCategory.removeAll();
424 final int printJobCount = printJobs.size();
425 for (int i = 0; i < printJobCount; i++) {
426 PrintJobInfo printJob = printJobs.get(i);
428 PreferenceScreen preference = getPreferenceManager()
429 .createPreferenceScreen(getActivity());
431 preference.setPersistent(false);
432 preference.setFragment(PrintJobSettingsFragment.class.getName());
433 preference.setKey(printJob.getId().flattenToString());
435 switch (printJob.getState()) {
436 case PrintJobInfo.STATE_QUEUED:
437 case PrintJobInfo.STATE_STARTED: {
438 if (!printJob.isCancelling()) {
439 preference.setTitle(getString(
440 R.string.print_printing_state_title_template,
441 printJob.getLabel()));
443 preference.setTitle(getString(
444 R.string.print_cancelling_state_title_template,
445 printJob.getLabel()));
449 case PrintJobInfo.STATE_FAILED: {
450 preference.setTitle(getString(
451 R.string.print_failed_state_title_template,
452 printJob.getLabel()));
455 case PrintJobInfo.STATE_BLOCKED: {
456 if (!printJob.isCancelling()) {
457 preference.setTitle(getString(
458 R.string.print_blocked_state_title_template,
459 printJob.getLabel()));
461 preference.setTitle(getString(
462 R.string.print_cancelling_state_title_template,
463 printJob.getLabel()));
468 preference.setSummary(getString(R.string.print_job_summary,
469 printJob.getPrinterName(), DateUtils.formatSameDayTime(
470 printJob.getCreationTime(), printJob.getCreationTime(),
471 DateFormat.SHORT, DateFormat.SHORT)));
473 switch (printJob.getState()) {
474 case PrintJobInfo.STATE_QUEUED:
475 case PrintJobInfo.STATE_STARTED: {
476 preference.setIcon(R.drawable.ic_print);
479 case PrintJobInfo.STATE_FAILED:
480 case PrintJobInfo.STATE_BLOCKED: {
481 preference.setIcon(R.drawable.ic_print_error);
485 Bundle extras = preference.getExtras();
486 extras.putString(EXTRA_PRINT_JOB_ID, printJob.getId().flattenToString());
488 mActivePrintJobsCategory.addPreference(preference);
494 public void onLoaderReset(Loader<List<PrintJobInfo>> loader) {
495 getPreferenceScreen().removePreference(mActivePrintJobsCategory);
499 private static final class PrintJobsLoader extends AsyncTaskLoader<List<PrintJobInfo>> {
501 private static final String LOG_TAG = "PrintJobsLoader";
503 private static final boolean DEBUG = false;
505 private List<PrintJobInfo> mPrintJobs = new ArrayList<PrintJobInfo>();
507 private final PrintManager mPrintManager;
509 private PrintJobStateChangeListener mPrintJobStateChangeListener;
511 public PrintJobsLoader(Context context) {
513 mPrintManager = ((PrintManager) context.getSystemService(
514 Context.PRINT_SERVICE)).getGlobalPrintManagerForUser(
515 context.getUserId());
519 public void deliverResult(List<PrintJobInfo> printJobs) {
521 super.deliverResult(printJobs);
526 protected void onStartLoading() {
528 Log.i(LOG_TAG, "onStartLoading()");
530 // If we already have a result, deliver it immediately.
531 if (!mPrintJobs.isEmpty()) {
532 deliverResult(new ArrayList<PrintJobInfo>(mPrintJobs));
534 // Start watching for changes.
535 if (mPrintJobStateChangeListener == null) {
536 mPrintJobStateChangeListener = new PrintJobStateChangeListener() {
538 public void onPrintJobStateChanged(PrintJobId printJobId) {
542 mPrintManager.addPrintJobStateChangeListener(
543 mPrintJobStateChangeListener);
545 // If the data changed or we have no data - load it now.
546 if (mPrintJobs.isEmpty()) {
552 protected void onStopLoading() {
554 Log.i(LOG_TAG, "onStopLoading()");
556 // Cancel the load in progress if possible.
561 protected void onReset() {
563 Log.i(LOG_TAG, "onReset()");
567 // Clear the cached result.
569 // Stop watching for changes.
570 if (mPrintJobStateChangeListener != null) {
571 mPrintManager.removePrintJobStateChangeListener(
572 mPrintJobStateChangeListener);
573 mPrintJobStateChangeListener = null;
578 public List<PrintJobInfo> loadInBackground() {
579 List<PrintJobInfo> printJobInfos = null;
580 List<PrintJob> printJobs = mPrintManager.getPrintJobs();
581 final int printJobCount = printJobs.size();
582 for (int i = 0; i < printJobCount; i++) {
583 PrintJobInfo printJob = printJobs.get(i).getInfo();
584 if (shouldShowToUser(printJob)) {
585 if (printJobInfos == null) {
586 printJobInfos = new ArrayList<PrintJobInfo>();
588 printJobInfos.add(printJob);
591 return printJobInfos;
596 * Should the print job the shown to the user in the settings app.
598 * @param printJob The print job in question.
599 * @return true iff the print job should be shown.
601 private static boolean shouldShowToUser(PrintJobInfo printJob) {
602 switch (printJob.getState()) {
603 case PrintJobInfo.STATE_QUEUED:
604 case PrintJobInfo.STATE_STARTED:
605 case PrintJobInfo.STATE_BLOCKED:
606 case PrintJobInfo.STATE_FAILED: {
614 * Provider for the print settings summary
616 private static class PrintSummaryProvider
617 implements SummaryLoader.SummaryProvider, PrintJobStateChangeListener {
618 private final Context mContext;
619 private final PrintManager mPrintManager;
620 private final SummaryLoader mSummaryLoader;
623 * Create a new {@link PrintSummaryProvider}.
625 * @param context The context this provider is for
626 * @param summaryLoader The summary load using this provider
628 public PrintSummaryProvider(Context context, SummaryLoader summaryLoader) {
630 mSummaryLoader = summaryLoader;
631 mPrintManager = ((PrintManager) context.getSystemService(Context.PRINT_SERVICE))
632 .getGlobalPrintManagerForUser(context.getUserId());
636 public void setListening(boolean isListening) {
638 mPrintManager.addPrintJobStateChangeListener(this);
639 onPrintJobStateChanged(null);
641 mPrintManager.removePrintJobStateChangeListener(this);
646 public void onPrintJobStateChanged(PrintJobId printJobId) {
647 List<PrintJob> printJobs = mPrintManager.getPrintJobs();
649 int numActivePrintJobs = 0;
650 final int numPrintJobs = printJobs.size();
651 for (int i = 0; i < numPrintJobs; i++) {
652 if (shouldShowToUser(printJobs.get(i).getInfo())) {
653 numActivePrintJobs++;
657 mSummaryLoader.setSummary(this, mContext.getResources().getQuantityString(
658 R.plurals.print_settings_title, numActivePrintJobs, numActivePrintJobs));
663 * A factory for {@link PrintSummaryProvider providers} the settings app can use to read the
666 public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY
667 = new SummaryLoader.SummaryProviderFactory() {
669 public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity,
670 SummaryLoader summaryLoader) {
671 return new PrintSummaryProvider(activity, summaryLoader);
675 public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
676 new BaseSearchIndexProvider() {
678 public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
679 List<SearchIndexableRaw> indexables = new ArrayList<SearchIndexableRaw>();
681 PackageManager packageManager = context.getPackageManager();
682 PrintManager printManager = (PrintManager) context.getSystemService(
683 Context.PRINT_SERVICE);
685 String screenTitle = context.getResources().getString(R.string.print_settings);
686 SearchIndexableRaw data = new SearchIndexableRaw(context);
687 data.title = screenTitle;
688 data.screenTitle = screenTitle;
689 indexables.add(data);
691 // Indexing all services, regardless if enabled.
692 List<PrintServiceInfo> services = printManager.getInstalledPrintServices();
693 final int serviceCount = services.size();
694 for (int i = 0; i < serviceCount; i++) {
695 PrintServiceInfo service = services.get(i);
697 ComponentName componentName = new ComponentName(
698 service.getResolveInfo().serviceInfo.packageName,
699 service.getResolveInfo().serviceInfo.name);
701 data = new SearchIndexableRaw(context);
702 data.key = componentName.flattenToString();
703 data.title = service.getResolveInfo().loadLabel(packageManager).toString();
704 data.summaryOn = context.getString(R.string.print_feature_state_on);
705 data.summaryOff = context.getString(R.string.print_feature_state_off);
706 data.screenTitle = screenTitle;
707 indexables.add(data);
714 public List<SearchIndexableResource> getXmlResourcesToIndex(Context context,
716 List<SearchIndexableResource> indexables = new ArrayList<SearchIndexableResource>();
717 SearchIndexableResource indexable = new SearchIndexableResource(context);
718 indexable.xmlResId = R.xml.print_settings;
719 indexables.add(indexable);