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.ActivityManager;
20 import android.app.LoaderManager.LoaderCallbacks;
21 import android.content.AsyncTaskLoader;
22 import android.content.ComponentName;
23 import android.content.ContentResolver;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.Loader;
27 import android.content.pm.PackageManager;
28 import android.content.pm.ResolveInfo;
29 import android.database.ContentObserver;
30 import android.net.Uri;
31 import android.os.Bundle;
32 import android.os.Handler;
33 import android.os.Message;
34 import android.os.UserHandle;
35 import android.os.UserManager;
36 import android.os.Process;
37 import android.preference.Preference;
38 import android.preference.PreferenceCategory;
39 import android.preference.PreferenceScreen;
40 import android.print.PrintJob;
41 import android.print.PrintJobId;
42 import android.print.PrintJobInfo;
43 import android.print.PrintManager;
44 import android.print.PrintManager.PrintJobStateChangeListener;
45 import android.printservice.PrintServiceInfo;
46 import android.provider.SearchIndexableResource;
47 import android.provider.Settings;
48 import android.text.TextUtils;
49 import android.text.format.DateUtils;
50 import android.util.Log;
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.widget.AdapterView;
57 import android.widget.TextView;
59 import com.android.internal.content.PackageMonitor;
60 import com.android.settings.UserSpinnerAdapter;
61 import com.android.settings.UserSpinnerAdapter.UserDetails;
62 import com.android.settings.DialogCreatable;
63 import com.android.settings.R;
64 import com.android.settings.SettingsPreferenceFragment;
65 import com.android.settings.Utils;
66 import com.android.settings.search.BaseSearchIndexProvider;
67 import com.android.settings.search.Indexable;
68 import com.android.settings.search.SearchIndexableRaw;
70 import java.text.DateFormat;
71 import java.util.ArrayList;
72 import java.util.List;
74 import android.widget.AdapterView.OnItemSelectedListener;
75 import android.widget.Spinner;
78 * Fragment with the top level print settings.
80 public class PrintSettingsFragment extends SettingsPreferenceFragment
81 implements DialogCreatable, Indexable, OnItemSelectedListener {
83 private static final int LOADER_ID_PRINT_JOBS_LOADER = 1;
85 private static final String PRINT_JOBS_CATEGORY = "print_jobs_category";
86 private static final String PRINT_SERVICES_CATEGORY = "print_services_category";
88 // Extras passed to sub-fragments.
89 static final String EXTRA_PREFERENCE_KEY = "EXTRA_PREFERENCE_KEY";
90 static final String EXTRA_CHECKED = "EXTRA_CHECKED";
91 static final String EXTRA_TITLE = "EXTRA_TITLE";
92 static final String EXTRA_ENABLE_WARNING_TITLE = "EXTRA_ENABLE_WARNING_TITLE";
93 static final String EXTRA_ENABLE_WARNING_MESSAGE = "EXTRA_ENABLE_WARNING_MESSAGE";
94 static final String EXTRA_SETTINGS_TITLE = "EXTRA_SETTINGS_TITLE";
95 static final String EXTRA_SETTINGS_COMPONENT_NAME = "EXTRA_SETTINGS_COMPONENT_NAME";
96 static final String EXTRA_ADD_PRINTERS_TITLE = "EXTRA_ADD_PRINTERS_TITLE";
97 static final String EXTRA_ADD_PRINTERS_COMPONENT_NAME = "EXTRA_ADD_PRINTERS_COMPONENT_NAME";
98 static final String EXTRA_SERVICE_COMPONENT_NAME = "EXTRA_SERVICE_COMPONENT_NAME";
100 static final String EXTRA_PRINT_JOB_ID = "EXTRA_PRINT_JOB_ID";
102 private static final String EXTRA_PRINT_SERVICE_COMPONENT_NAME =
103 "EXTRA_PRINT_SERVICE_COMPONENT_NAME";
105 private final PackageMonitor mSettingsPackageMonitor = new SettingsPackageMonitor();
107 private final Handler mHandler = new Handler() {
109 public void dispatchMessage(Message msg) {
110 updateServicesPreferences();
114 private final SettingsContentObserver mSettingsContentObserver =
115 new SettingsContentObserver(mHandler) {
117 public void onChange(boolean selfChange, Uri uri) {
118 updateServicesPreferences();
122 private PreferenceCategory mActivePrintJobsCategory;
123 private PreferenceCategory mPrintServicesCategory;
125 private PrintJobsController mPrintJobsController;
126 private UserSpinnerAdapter mProfileSpinnerAdapter;
127 private Spinner mSpinner;
130 public void onCreate(Bundle icicle) {
131 super.onCreate(icicle);
132 addPreferencesFromResource(R.xml.print_settings);
134 mActivePrintJobsCategory = (PreferenceCategory) findPreference(
135 PRINT_JOBS_CATEGORY);
136 mPrintServicesCategory = (PreferenceCategory) findPreference(
137 PRINT_SERVICES_CATEGORY);
138 getPreferenceScreen().removePreference(mActivePrintJobsCategory);
140 mPrintJobsController = new PrintJobsController();
141 getActivity().getLoaderManager().initLoader(LOADER_ID_PRINT_JOBS_LOADER,
142 null, mPrintJobsController);
146 public void onResume() {
148 mSettingsPackageMonitor.register(getActivity(), getActivity().getMainLooper(), false);
149 mSettingsContentObserver.register(getContentResolver());
150 updateServicesPreferences();
151 setHasOptionsMenu(true);
152 startSubSettingsIfNeeded();
156 public void onPause() {
157 mSettingsPackageMonitor.unregister();
158 mSettingsContentObserver.unregister(getContentResolver());
163 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
164 super.onCreateOptionsMenu(menu, inflater);
165 String searchUri = Settings.Secure.getString(getContentResolver(),
166 Settings.Secure.PRINT_SERVICE_SEARCH_URI);
167 if (!TextUtils.isEmpty(searchUri)) {
168 MenuItem menuItem = menu.add(R.string.print_menu_item_add_service);
169 menuItem.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_NEVER);
170 menuItem.setIntent(new Intent(Intent.ACTION_VIEW, Uri.parse(searchUri)));
175 public void onViewCreated(View view, Bundle savedInstanceState) {
176 super.onViewCreated(view, savedInstanceState);
177 ViewGroup contentRoot = (ViewGroup) getListView().getParent();
178 View emptyView = getActivity().getLayoutInflater().inflate(
179 R.layout.empty_print_state, contentRoot, false);
180 TextView textView = (TextView) emptyView.findViewById(R.id.message);
181 textView.setText(R.string.print_no_services_installed);
182 contentRoot.addView(emptyView);
183 getListView().setEmptyView(emptyView);
185 final UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
186 mProfileSpinnerAdapter = Utils.createUserSpinnerAdapter(um, getActivity());
187 if (mProfileSpinnerAdapter != null) {
188 mSpinner = (Spinner) getActivity().getLayoutInflater().inflate(
189 R.layout.spinner_view, null);
190 mSpinner.setAdapter(mProfileSpinnerAdapter);
191 mSpinner.setOnItemSelectedListener(this);
192 setPinnedHeaderView(mSpinner);
196 private void updateServicesPreferences() {
197 if (getPreferenceScreen().findPreference(PRINT_SERVICES_CATEGORY) == null) {
198 getPreferenceScreen().addPreference(mPrintServicesCategory);
200 // Since services category is auto generated we have to do a pass
201 // to generate it since services can come and go.
202 mPrintServicesCategory.removeAll();
205 List<ComponentName> enabledServices = PrintSettingsUtils
206 .readEnabledPrintServices(getActivity());
208 List<ResolveInfo> installedServices = getActivity().getPackageManager()
209 .queryIntentServices(
210 new Intent(android.printservice.PrintService.SERVICE_INTERFACE),
211 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
213 final int installedServiceCount = installedServices.size();
214 for (int i = 0; i < installedServiceCount; i++) {
215 ResolveInfo installedService = installedServices.get(i);
217 PreferenceScreen preference = getPreferenceManager().createPreferenceScreen(
220 String title = installedService.loadLabel(getPackageManager()).toString();
221 preference.setTitle(title);
223 ComponentName componentName = new ComponentName(
224 installedService.serviceInfo.packageName,
225 installedService.serviceInfo.name);
226 preference.setKey(componentName.flattenToString());
228 preference.setOrder(i);
229 preference.setFragment(PrintServiceSettingsFragment.class.getName());
230 preference.setPersistent(false);
232 final boolean serviceEnabled = enabledServices.contains(componentName);
233 if (serviceEnabled) {
234 preference.setSummary(getString(R.string.print_feature_state_on));
236 preference.setSummary(getString(R.string.print_feature_state_off));
239 Bundle extras = preference.getExtras();
240 extras.putString(EXTRA_PREFERENCE_KEY, preference.getKey());
241 extras.putBoolean(EXTRA_CHECKED, serviceEnabled);
242 extras.putString(EXTRA_TITLE, title);
244 PrintServiceInfo printServiceInfo = PrintServiceInfo.create(
245 installedService, getActivity());
247 CharSequence applicationLabel = installedService.loadLabel(getPackageManager());
249 extras.putString(EXTRA_ENABLE_WARNING_TITLE, getString(
250 R.string.print_service_security_warning_title, applicationLabel));
251 extras.putString(EXTRA_ENABLE_WARNING_MESSAGE, getString(
252 R.string.print_service_security_warning_summary, applicationLabel));
254 String settingsClassName = printServiceInfo.getSettingsActivityName();
255 if (!TextUtils.isEmpty(settingsClassName)) {
256 extras.putString(EXTRA_SETTINGS_TITLE,
257 getString(R.string.print_menu_item_settings));
258 extras.putString(EXTRA_SETTINGS_COMPONENT_NAME,
259 new ComponentName(installedService.serviceInfo.packageName,
260 settingsClassName).flattenToString());
263 String addPrinterClassName = printServiceInfo.getAddPrintersActivityName();
264 if (!TextUtils.isEmpty(addPrinterClassName)) {
265 extras.putString(EXTRA_ADD_PRINTERS_TITLE,
266 getString(R.string.print_menu_item_add_printers));
267 extras.putString(EXTRA_ADD_PRINTERS_COMPONENT_NAME,
268 new ComponentName(installedService.serviceInfo.packageName,
269 addPrinterClassName).flattenToString());
272 extras.putString(EXTRA_SERVICE_COMPONENT_NAME, componentName.flattenToString());
274 mPrintServicesCategory.addPreference(preference);
277 if (mPrintServicesCategory.getPreferenceCount() == 0) {
278 getPreferenceScreen().removePreference(mPrintServicesCategory);
282 private void startSubSettingsIfNeeded() {
283 if (getArguments() == null) {
286 String componentName = getArguments().getString(EXTRA_PRINT_SERVICE_COMPONENT_NAME);
287 if (componentName != null) {
288 getArguments().remove(EXTRA_PRINT_SERVICE_COMPONENT_NAME);
289 Preference prereference = findPreference(componentName);
290 if (prereference != null) {
291 prereference.performClick(getPreferenceScreen());
297 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
298 UserHandle selectedUser = mProfileSpinnerAdapter.getUserHandle(position);
299 if (selectedUser.getIdentifier() != UserHandle.myUserId()) {
300 Intent intent = new Intent(Settings.ACTION_PRINT_SETTINGS);
301 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
302 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
303 getActivity().startActivityAsUser(intent, selectedUser);
304 // Go back to default selection, which is the first one
305 mSpinner.setSelection(0);
310 public void onNothingSelected(AdapterView<?> parent) {
314 private class SettingsPackageMonitor extends PackageMonitor {
316 public void onPackageAdded(String packageName, int uid) {
317 mHandler.obtainMessage().sendToTarget();
321 public void onPackageAppeared(String packageName, int reason) {
322 mHandler.obtainMessage().sendToTarget();
326 public void onPackageDisappeared(String packageName, int reason) {
327 mHandler.obtainMessage().sendToTarget();
331 public void onPackageRemoved(String packageName, int uid) {
332 mHandler.obtainMessage().sendToTarget();
336 private static abstract class SettingsContentObserver extends ContentObserver {
338 public SettingsContentObserver(Handler handler) {
342 public void register(ContentResolver contentResolver) {
343 contentResolver.registerContentObserver(Settings.Secure.getUriFor(
344 Settings.Secure.ENABLED_PRINT_SERVICES), false, this);
347 public void unregister(ContentResolver contentResolver) {
348 contentResolver.unregisterContentObserver(this);
352 public abstract void onChange(boolean selfChange, Uri uri);
355 private final class PrintJobsController implements LoaderCallbacks<List<PrintJobInfo>> {
358 public Loader<List<PrintJobInfo>> onCreateLoader(int id, Bundle args) {
359 if (id == LOADER_ID_PRINT_JOBS_LOADER) {
360 return new PrintJobsLoader(getActivity());
366 public void onLoadFinished(Loader<List<PrintJobInfo>> loader,
367 List<PrintJobInfo> printJobs) {
368 if (printJobs == null || printJobs.isEmpty()) {
369 getPreferenceScreen().removePreference(mActivePrintJobsCategory);
371 if (getPreferenceScreen().findPreference(PRINT_JOBS_CATEGORY) == null) {
372 getPreferenceScreen().addPreference(mActivePrintJobsCategory);
375 mActivePrintJobsCategory.removeAll();
377 final int printJobCount = printJobs.size();
378 for (int i = 0; i < printJobCount; i++) {
379 PrintJobInfo printJob = printJobs.get(i);
381 PreferenceScreen preference = getPreferenceManager()
382 .createPreferenceScreen(getActivity());
384 preference.setPersistent(false);
385 preference.setFragment(PrintJobSettingsFragment.class.getName());
386 preference.setKey(printJob.getId().flattenToString());
388 switch (printJob.getState()) {
389 case PrintJobInfo.STATE_QUEUED:
390 case PrintJobInfo.STATE_STARTED: {
391 if (!printJob.isCancelling()) {
392 preference.setTitle(getString(
393 R.string.print_printing_state_title_template,
394 printJob.getLabel()));
396 preference.setTitle(getString(
397 R.string.print_cancelling_state_title_template,
398 printJob.getLabel()));
402 case PrintJobInfo.STATE_FAILED: {
403 preference.setTitle(getString(
404 R.string.print_failed_state_title_template,
405 printJob.getLabel()));
408 case PrintJobInfo.STATE_BLOCKED: {
409 if (!printJob.isCancelling()) {
410 preference.setTitle(getString(
411 R.string.print_blocked_state_title_template,
412 printJob.getLabel()));
414 preference.setTitle(getString(
415 R.string.print_cancelling_state_title_template,
416 printJob.getLabel()));
421 preference.setSummary(getString(R.string.print_job_summary,
422 printJob.getPrinterName(), DateUtils.formatSameDayTime(
423 printJob.getCreationTime(), printJob.getCreationTime(),
424 DateFormat.SHORT, DateFormat.SHORT)));
426 switch (printJob.getState()) {
427 case PrintJobInfo.STATE_QUEUED:
428 case PrintJobInfo.STATE_STARTED: {
429 preference.setIcon(R.drawable.ic_print);
432 case PrintJobInfo.STATE_FAILED:
433 case PrintJobInfo.STATE_BLOCKED: {
434 preference.setIcon(R.drawable.ic_print_error);
438 Bundle extras = preference.getExtras();
439 extras.putString(EXTRA_PRINT_JOB_ID, printJob.getId().flattenToString());
441 mActivePrintJobsCategory.addPreference(preference);
447 public void onLoaderReset(Loader<List<PrintJobInfo>> loader) {
448 getPreferenceScreen().removePreference(mActivePrintJobsCategory);
452 private static final class PrintJobsLoader extends AsyncTaskLoader<List<PrintJobInfo>> {
454 private static final String LOG_TAG = "PrintJobsLoader";
456 private static final boolean DEBUG = false;
458 private List<PrintJobInfo> mPrintJobs = new ArrayList<PrintJobInfo>();
460 private final PrintManager mPrintManager;
462 private PrintJobStateChangeListener mPrintJobStateChangeListener;
464 public PrintJobsLoader(Context context) {
466 mPrintManager = ((PrintManager) context.getSystemService(
467 Context.PRINT_SERVICE)).getGlobalPrintManagerForUser(
468 ActivityManager.getCurrentUser());
472 public void deliverResult(List<PrintJobInfo> printJobs) {
474 super.deliverResult(printJobs);
479 protected void onStartLoading() {
481 Log.i(LOG_TAG, "onStartLoading()");
483 // If we already have a result, deliver it immediately.
484 if (!mPrintJobs.isEmpty()) {
485 deliverResult(new ArrayList<PrintJobInfo>(mPrintJobs));
487 // Start watching for changes.
488 if (mPrintJobStateChangeListener == null) {
489 mPrintJobStateChangeListener = new PrintJobStateChangeListener() {
491 public void onPrintJobStateChanged(PrintJobId printJobId) {
495 mPrintManager.addPrintJobStateChangeListener(
496 mPrintJobStateChangeListener);
498 // If the data changed or we have no data - load it now.
499 if (mPrintJobs.isEmpty()) {
505 protected void onStopLoading() {
507 Log.i(LOG_TAG, "onStopLoading()");
509 // Cancel the load in progress if possible.
514 protected void onReset() {
516 Log.i(LOG_TAG, "onReset()");
520 // Clear the cached result.
522 // Stop watching for changes.
523 if (mPrintJobStateChangeListener != null) {
524 mPrintManager.removePrintJobStateChangeListener(
525 mPrintJobStateChangeListener);
526 mPrintJobStateChangeListener = null;
531 public List<PrintJobInfo> loadInBackground() {
532 List<PrintJobInfo> printJobInfos = null;
533 List<PrintJob> printJobs = mPrintManager.getPrintJobs();
534 final int printJobCount = printJobs.size();
535 for (int i = 0; i < printJobCount; i++) {
536 PrintJobInfo printJob = printJobs.get(i).getInfo();
537 if (shouldShowToUser(printJob)) {
538 if (printJobInfos == null) {
539 printJobInfos = new ArrayList<PrintJobInfo>();
541 printJobInfos.add(printJob);
544 return printJobInfos;
547 private static boolean shouldShowToUser(PrintJobInfo printJob) {
548 switch (printJob.getState()) {
549 case PrintJobInfo.STATE_QUEUED:
550 case PrintJobInfo.STATE_STARTED:
551 case PrintJobInfo.STATE_BLOCKED:
552 case PrintJobInfo.STATE_FAILED: {
560 public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
561 new BaseSearchIndexProvider() {
563 public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
564 List<SearchIndexableRaw> indexables = new ArrayList<SearchIndexableRaw>();
566 PackageManager packageManager = context.getPackageManager();
567 PrintManager printManager = (PrintManager) context.getSystemService(
568 Context.PRINT_SERVICE);
570 String screenTitle = context.getResources().getString(R.string.print_settings);
571 SearchIndexableRaw data = new SearchIndexableRaw(context);
572 data.title = screenTitle;
573 data.screenTitle = screenTitle;
574 indexables.add(data);
576 // Indexing all services, regardless if enabled.
577 List<PrintServiceInfo> services = printManager.getInstalledPrintServices();
578 final int serviceCount = services.size();
579 for (int i = 0; i < serviceCount; i++) {
580 PrintServiceInfo service = services.get(i);
582 ComponentName componentName = new ComponentName(
583 service.getResolveInfo().serviceInfo.packageName,
584 service.getResolveInfo().serviceInfo.name);
586 data = new SearchIndexableRaw(context);
587 data.key = componentName.flattenToString();
588 data.title = service.getResolveInfo().loadLabel(packageManager).toString();
589 data.summaryOn = context.getString(R.string.print_feature_state_on);
590 data.summaryOff = context.getString(R.string.print_feature_state_off);
591 data.screenTitle = screenTitle;
592 indexables.add(data);
599 public List<SearchIndexableResource> getXmlResourcesToIndex(Context context,
601 List<SearchIndexableResource> indexables = new ArrayList<SearchIndexableResource>();
602 SearchIndexableResource indexable = new SearchIndexableResource(context);
603 indexable.xmlResId = R.xml.print_settings;
604 indexables.add(indexable);