PrintSettingsFragment.java revision 63609c58a0ae857cc6ec62d28d73e2a0fb8241e9
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
17package com.android.settings.print;
18
19import android.app.ActivityManager;
20import android.app.LoaderManager.LoaderCallbacks;
21import android.content.AsyncTaskLoader;
22import android.content.ComponentName;
23import android.content.ContentResolver;
24import android.content.Context;
25import android.content.Intent;
26import android.content.Loader;
27import android.content.pm.PackageManager;
28import android.content.pm.ResolveInfo;
29import android.database.ContentObserver;
30import android.net.Uri;
31import android.os.Bundle;
32import android.os.Handler;
33import android.os.Message;
34import android.preference.Preference;
35import android.preference.PreferenceCategory;
36import android.preference.PreferenceScreen;
37import android.print.PrintJob;
38import android.print.PrintJobId;
39import android.print.PrintJobInfo;
40import android.print.PrintManager;
41import android.print.PrintManager.PrintJobStateChangeListener;
42import android.printservice.PrintServiceInfo;
43import android.provider.SearchIndexableResource;
44import android.provider.Settings;
45import android.text.TextUtils;
46import android.text.format.DateUtils;
47import android.util.Log;
48import android.view.Menu;
49import android.view.MenuInflater;
50import android.view.MenuItem;
51import android.view.View;
52import android.view.ViewGroup;
53import android.widget.Switch;
54import android.widget.TextView;
55
56import com.android.internal.content.PackageMonitor;
57import com.android.settings.DialogCreatable;
58import com.android.settings.R;
59import com.android.settings.SettingsPreferenceFragment;
60import com.android.settings.search.BaseSearchIndexProvider;
61import com.android.settings.search.Indexable;
62import com.android.settings.search.SearchIndexableRaw;
63
64import java.text.DateFormat;
65import java.util.ArrayList;
66import java.util.List;
67
68/**
69 * Fragment with the top level print settings.
70 */
71public class PrintSettingsFragment extends SettingsPreferenceFragment
72        implements DialogCreatable, Indexable {
73
74    private static final int LOADER_ID_PRINT_JOBS_LOADER = 1;
75
76    private static final String PRINT_JOBS_CATEGORY = "print_jobs_category";
77    private static final String PRINT_SERVICES_CATEGORY = "print_services_category";
78
79    // Extras passed to sub-fragments.
80    static final String EXTRA_PREFERENCE_KEY = "EXTRA_PREFERENCE_KEY";
81    static final String EXTRA_CHECKED = "EXTRA_CHECKED";
82    static final String EXTRA_TITLE = "EXTRA_TITLE";
83    static final String EXTRA_ENABLE_WARNING_TITLE = "EXTRA_ENABLE_WARNING_TITLE";
84    static final String EXTRA_ENABLE_WARNING_MESSAGE = "EXTRA_ENABLE_WARNING_MESSAGE";
85    static final String EXTRA_SETTINGS_TITLE = "EXTRA_SETTINGS_TITLE";
86    static final String EXTRA_SETTINGS_COMPONENT_NAME = "EXTRA_SETTINGS_COMPONENT_NAME";
87    static final String EXTRA_ADD_PRINTERS_TITLE = "EXTRA_ADD_PRINTERS_TITLE";
88    static final String EXTRA_ADD_PRINTERS_COMPONENT_NAME = "EXTRA_ADD_PRINTERS_COMPONENT_NAME";
89    static final String EXTRA_SERVICE_COMPONENT_NAME = "EXTRA_SERVICE_COMPONENT_NAME";
90
91    static final String EXTRA_PRINT_JOB_ID = "EXTRA_PRINT_JOB_ID";
92
93    private static final String EXTRA_PRINT_SERVICE_COMPONENT_NAME =
94            "EXTRA_PRINT_SERVICE_COMPONENT_NAME";
95
96    private final PackageMonitor mSettingsPackageMonitor = new SettingsPackageMonitor();
97
98    private final Handler mHandler = new Handler() {
99        @Override
100        public void dispatchMessage(Message msg) {
101            updateServicesPreferences();
102        }
103    };
104
105    private final SettingsContentObserver mSettingsContentObserver =
106            new SettingsContentObserver(mHandler) {
107        @Override
108        public void onChange(boolean selfChange, Uri uri) {
109            updateServicesPreferences();
110        }
111    };
112
113    private PreferenceCategory mActivePrintJobsCategory;
114    private PreferenceCategory mPrintServicesCategory;
115
116    private PrintJobsController mPrintJobsController;
117
118    @Override
119    public void onCreate(Bundle icicle) {
120        super.onCreate(icicle);
121        addPreferencesFromResource(R.xml.print_settings);
122
123        mActivePrintJobsCategory = (PreferenceCategory) findPreference(
124                PRINT_JOBS_CATEGORY);
125        mPrintServicesCategory = (PreferenceCategory) findPreference(
126                PRINT_SERVICES_CATEGORY);
127        getPreferenceScreen().removePreference(mActivePrintJobsCategory);
128
129        mPrintJobsController = new PrintJobsController();
130        getActivity().getLoaderManager().initLoader(LOADER_ID_PRINT_JOBS_LOADER,
131                null, mPrintJobsController);
132    }
133
134    @Override
135    public void onResume() {
136        super.onResume();
137        mSettingsPackageMonitor.register(getActivity(), getActivity().getMainLooper(), false);
138        mSettingsContentObserver.register(getContentResolver());
139        updateServicesPreferences();
140        setHasOptionsMenu(true);
141        startSubSettingsIfNeeded();
142    }
143
144    @Override
145    public void onPause() {
146        mSettingsPackageMonitor.unregister();
147        mSettingsContentObserver.unregister(getContentResolver());
148        super.onPause();
149    }
150
151    @Override
152    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
153        super.onCreateOptionsMenu(menu, inflater);
154        String searchUri = Settings.Secure.getString(getContentResolver(),
155                Settings.Secure.PRINT_SERVICE_SEARCH_URI);
156        if (!TextUtils.isEmpty(searchUri)) {
157            MenuItem menuItem = menu.add(R.string.print_menu_item_add_service);
158            menuItem.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_IF_ROOM);
159            menuItem.setIntent(new Intent(Intent.ACTION_VIEW, Uri.parse(searchUri)));
160        }
161    }
162
163    @Override
164    public void onViewCreated(View view, Bundle savedInstanceState) {
165        super.onViewCreated(view, savedInstanceState);
166        ViewGroup contentRoot = (ViewGroup) getListView().getParent();
167        View emptyView = getActivity().getLayoutInflater().inflate(
168                R.layout.empty_print_state, contentRoot, false);
169        TextView textView = (TextView) emptyView.findViewById(R.id.message);
170        textView.setText(R.string.print_no_services_installed);
171        contentRoot.addView(emptyView);
172        getListView().setEmptyView(emptyView);
173    }
174
175    private void updateServicesPreferences() {
176        if (getPreferenceScreen().findPreference(PRINT_SERVICES_CATEGORY) == null) {
177            getPreferenceScreen().addPreference(mPrintServicesCategory);
178        } else {
179            // Since services category is auto generated we have to do a pass
180            // to generate it since services can come and go.
181            mPrintServicesCategory.removeAll();
182        }
183
184        List<ComponentName> enabledServices = PrintSettingsUtils
185                .readEnabledPrintServices(getActivity());
186
187        List<ResolveInfo> installedServices = getActivity().getPackageManager()
188                .queryIntentServices(
189                        new Intent(android.printservice.PrintService.SERVICE_INTERFACE),
190                        PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
191
192        final int installedServiceCount = installedServices.size();
193        for (int i = 0; i < installedServiceCount; i++) {
194            ResolveInfo installedService = installedServices.get(i);
195
196            PreferenceScreen preference = getPreferenceManager().createPreferenceScreen(
197                    getActivity());
198
199            String title = installedService.loadLabel(getPackageManager()).toString();
200            preference.setTitle(title);
201
202            ComponentName componentName = new ComponentName(
203                    installedService.serviceInfo.packageName,
204                    installedService.serviceInfo.name);
205            preference.setKey(componentName.flattenToString());
206
207            preference.setOrder(i);
208            preference.setFragment(PrintServiceSettingsFragment.class.getName());
209            preference.setPersistent(false);
210
211            final boolean serviceEnabled = enabledServices.contains(componentName);
212            if (serviceEnabled) {
213                preference.setSummary(getString(R.string.print_feature_state_on));
214            } else {
215                preference.setSummary(getString(R.string.print_feature_state_off));
216            }
217
218            Bundle extras = preference.getExtras();
219            extras.putString(EXTRA_PREFERENCE_KEY, preference.getKey());
220            extras.putBoolean(EXTRA_CHECKED, serviceEnabled);
221            extras.putString(EXTRA_TITLE, title);
222
223            PrintServiceInfo printServiceInfo = PrintServiceInfo.create(
224                    installedService, getActivity());
225
226            CharSequence applicationLabel = installedService.loadLabel(getPackageManager());
227
228            extras.putString(EXTRA_ENABLE_WARNING_TITLE, getString(
229                    R.string.print_service_security_warning_title, applicationLabel));
230            extras.putString(EXTRA_ENABLE_WARNING_MESSAGE, getString(
231                    R.string.print_service_security_warning_summary, applicationLabel));
232
233            String settingsClassName = printServiceInfo.getSettingsActivityName();
234            if (!TextUtils.isEmpty(settingsClassName)) {
235                extras.putString(EXTRA_SETTINGS_TITLE,
236                        getString(R.string.print_menu_item_settings));
237                extras.putString(EXTRA_SETTINGS_COMPONENT_NAME,
238                        new ComponentName(installedService.serviceInfo.packageName,
239                                settingsClassName).flattenToString());
240            }
241
242            String addPrinterClassName = printServiceInfo.getAddPrintersActivityName();
243            if (!TextUtils.isEmpty(addPrinterClassName)) {
244                extras.putString(EXTRA_ADD_PRINTERS_TITLE,
245                        getString(R.string.print_menu_item_add_printers));
246                extras.putString(EXTRA_ADD_PRINTERS_COMPONENT_NAME,
247                        new ComponentName(installedService.serviceInfo.packageName,
248                                addPrinterClassName).flattenToString());
249            }
250
251            extras.putString(EXTRA_SERVICE_COMPONENT_NAME, componentName.flattenToString());
252
253            mPrintServicesCategory.addPreference(preference);
254        }
255
256        if (mPrintServicesCategory.getPreferenceCount() == 0) {
257            getPreferenceScreen().removePreference(mPrintServicesCategory);
258        }
259    }
260
261    private void startSubSettingsIfNeeded() {
262        if (getArguments() == null) {
263            return;
264        }
265        String componentName = getArguments().getString(EXTRA_PRINT_SERVICE_COMPONENT_NAME);
266        if (componentName != null) {
267            getArguments().remove(EXTRA_PRINT_SERVICE_COMPONENT_NAME);
268            Preference prereference = findPreference(componentName);
269            if (prereference != null) {
270                prereference.performClick(getPreferenceScreen());
271            }
272        }
273    }
274
275    private class SettingsPackageMonitor extends PackageMonitor {
276        @Override
277        public void onPackageAdded(String packageName, int uid) {
278            mHandler.obtainMessage().sendToTarget();
279        }
280
281        @Override
282        public void onPackageAppeared(String packageName, int reason) {
283            mHandler.obtainMessage().sendToTarget();
284        }
285
286        @Override
287        public void onPackageDisappeared(String packageName, int reason) {
288            mHandler.obtainMessage().sendToTarget();
289        }
290
291        @Override
292        public void onPackageRemoved(String packageName, int uid) {
293            mHandler.obtainMessage().sendToTarget();
294        }
295    }
296
297    public static class ToggleSwitch extends Switch {
298
299        private OnBeforeCheckedChangeListener mOnBeforeListener;
300
301        public static interface OnBeforeCheckedChangeListener {
302            public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked);
303        }
304
305        public ToggleSwitch(Context context) {
306            super(context);
307        }
308
309        public void setOnBeforeCheckedChangeListener(OnBeforeCheckedChangeListener listener) {
310            mOnBeforeListener = listener;
311        }
312
313        @Override
314        public void setChecked(boolean checked) {
315            if (mOnBeforeListener != null
316                    && mOnBeforeListener.onBeforeCheckedChanged(this, checked)) {
317                return;
318            }
319            super.setChecked(checked);
320        }
321
322        public void setCheckedInternal(boolean checked) {
323            super.setChecked(checked);
324        }
325    }
326
327    private static abstract class SettingsContentObserver extends ContentObserver {
328
329        public SettingsContentObserver(Handler handler) {
330            super(handler);
331        }
332
333        public void register(ContentResolver contentResolver) {
334            contentResolver.registerContentObserver(Settings.Secure.getUriFor(
335                    Settings.Secure.ENABLED_PRINT_SERVICES), false, this);
336        }
337
338        public void unregister(ContentResolver contentResolver) {
339            contentResolver.unregisterContentObserver(this);
340        }
341
342        @Override
343        public abstract void onChange(boolean selfChange, Uri uri);
344    }
345
346    private final class PrintJobsController implements LoaderCallbacks<List<PrintJobInfo>> {
347
348        @Override
349        public Loader<List<PrintJobInfo>> onCreateLoader(int id, Bundle args) {
350            if (id == LOADER_ID_PRINT_JOBS_LOADER) {
351                return new PrintJobsLoader(getActivity());
352            }
353            return null;
354        }
355
356        @Override
357        public void onLoadFinished(Loader<List<PrintJobInfo>> loader,
358                List<PrintJobInfo> printJobs) {
359            if (printJobs == null || printJobs.isEmpty()) {
360                getPreferenceScreen().removePreference(mActivePrintJobsCategory);
361            } else {
362                if (getPreferenceScreen().findPreference(PRINT_JOBS_CATEGORY) == null) {
363                    getPreferenceScreen().addPreference(mActivePrintJobsCategory);
364                }
365
366                mActivePrintJobsCategory.removeAll();
367
368                final int printJobCount = printJobs.size();
369                for (int i = 0; i < printJobCount; i++) {
370                    PrintJobInfo printJob = printJobs.get(i);
371
372                    PreferenceScreen preference = getPreferenceManager()
373                            .createPreferenceScreen(getActivity());
374
375                    preference.setPersistent(false);
376                    preference.setFragment(PrintJobSettingsFragment.class.getName());
377                    preference.setKey(printJob.getId().flattenToString());
378
379                    switch (printJob.getState()) {
380                        case PrintJobInfo.STATE_QUEUED:
381                        case PrintJobInfo.STATE_STARTED: {
382                            if (!printJob.isCancelling()) {
383                                preference.setTitle(getString(
384                                        R.string.print_printing_state_title_template,
385                                        printJob.getLabel()));
386                            } else {
387                                preference.setTitle(getString(
388                                        R.string.print_cancelling_state_title_template,
389                                        printJob.getLabel()));
390                            }
391                        } break;
392
393                        case PrintJobInfo.STATE_FAILED: {
394                            preference.setTitle(getString(
395                                    R.string.print_failed_state_title_template,
396                                    printJob.getLabel()));
397                        } break;
398
399                        case PrintJobInfo.STATE_BLOCKED: {
400                            if (!printJob.isCancelling()) {
401                                preference.setTitle(getString(
402                                        R.string.print_blocked_state_title_template,
403                                        printJob.getLabel()));
404                            } else {
405                                preference.setTitle(getString(
406                                        R.string.print_cancelling_state_title_template,
407                                        printJob.getLabel()));
408                            }
409                        } break;
410                    }
411
412                    preference.setSummary(getString(R.string.print_job_summary,
413                            printJob.getPrinterName(), DateUtils.formatSameDayTime(
414                                    printJob.getCreationTime(), printJob.getCreationTime(),
415                                    DateFormat.SHORT, DateFormat.SHORT)));
416
417                    switch (printJob.getState()) {
418                        case PrintJobInfo.STATE_QUEUED:
419                        case PrintJobInfo.STATE_STARTED: {
420                            preference.setIcon(com.android.internal.R.drawable.ic_print);
421                        } break;
422
423                        case PrintJobInfo.STATE_FAILED:
424                        case PrintJobInfo.STATE_BLOCKED: {
425                            preference.setIcon(com.android.internal.R.drawable.ic_print_error);
426                        } break;
427                    }
428
429                    Bundle extras = preference.getExtras();
430                    extras.putString(EXTRA_PRINT_JOB_ID, printJob.getId().flattenToString());
431
432                    mActivePrintJobsCategory.addPreference(preference);
433                }
434            }
435        }
436
437        @Override
438        public void onLoaderReset(Loader<List<PrintJobInfo>> loader) {
439            getPreferenceScreen().removePreference(mActivePrintJobsCategory);
440        }
441    }
442
443    private static final class PrintJobsLoader extends AsyncTaskLoader<List<PrintJobInfo>> {
444
445        private static final String LOG_TAG = "PrintJobsLoader";
446
447        private static final boolean DEBUG = false;
448
449        private List<PrintJobInfo> mPrintJobs = new ArrayList<PrintJobInfo>();
450
451        private final PrintManager mPrintManager;
452
453        private PrintJobStateChangeListener mPrintJobStateChangeListener;
454
455        public PrintJobsLoader(Context context) {
456            super(context);
457            mPrintManager = ((PrintManager) context.getSystemService(
458                    Context.PRINT_SERVICE)).getGlobalPrintManagerForUser(
459                        ActivityManager.getCurrentUser());
460        }
461
462        @Override
463        public void deliverResult(List<PrintJobInfo> printJobs) {
464            if (isStarted()) {
465                super.deliverResult(printJobs);
466            }
467        }
468
469        @Override
470        protected void onStartLoading() {
471            if (DEBUG) {
472                Log.i(LOG_TAG, "onStartLoading()");
473            }
474            // If we already have a result, deliver it immediately.
475            if (!mPrintJobs.isEmpty()) {
476                deliverResult(new ArrayList<PrintJobInfo>(mPrintJobs));
477            }
478            // Start watching for changes.
479            if (mPrintJobStateChangeListener == null) {
480                mPrintJobStateChangeListener = new PrintJobStateChangeListener() {
481                    @Override
482                    public void onPrintJobStateChanged(PrintJobId printJobId) {
483                        onForceLoad();
484                    }
485                };
486                mPrintManager.addPrintJobStateChangeListener(
487                        mPrintJobStateChangeListener);
488            }
489            // If the data changed or we have no data - load it now.
490            if (mPrintJobs.isEmpty()) {
491                onForceLoad();
492            }
493        }
494
495        @Override
496        protected void onStopLoading() {
497            if (DEBUG) {
498                Log.i(LOG_TAG, "onStopLoading()");
499            }
500            // Cancel the load in progress if possible.
501            onCancelLoad();
502        }
503
504        @Override
505        protected void onReset() {
506            if (DEBUG) {
507                Log.i(LOG_TAG, "onReset()");
508            }
509            // Stop loading.
510            onStopLoading();
511            // Clear the cached result.
512            mPrintJobs.clear();
513            // Stop watching for changes.
514            if (mPrintJobStateChangeListener != null) {
515                mPrintManager.removePrintJobStateChangeListener(
516                        mPrintJobStateChangeListener);
517                mPrintJobStateChangeListener = null;
518            }
519        }
520
521        @Override
522        public List<PrintJobInfo> loadInBackground() {
523            List<PrintJobInfo> printJobInfos = null;
524            List<PrintJob> printJobs = mPrintManager.getPrintJobs();
525            final int printJobCount = printJobs.size();
526            for (int i = 0; i < printJobCount; i++) {
527                PrintJobInfo printJob = printJobs.get(i).getInfo();
528                if (shouldShowToUser(printJob)) {
529                    if (printJobInfos == null) {
530                        printJobInfos = new ArrayList<PrintJobInfo>();
531                    }
532                    printJobInfos.add(printJob);
533                }
534            }
535            return printJobInfos;
536        }
537
538        private static boolean shouldShowToUser(PrintJobInfo printJob) {
539            switch (printJob.getState()) {
540                case PrintJobInfo.STATE_QUEUED:
541                case PrintJobInfo.STATE_STARTED:
542                case PrintJobInfo.STATE_BLOCKED:
543                case PrintJobInfo.STATE_FAILED: {
544                    return true;
545                }
546            }
547            return false;
548        }
549    }
550
551    public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
552            new BaseSearchIndexProvider() {
553        @Override
554        public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
555            List<SearchIndexableRaw> indexables = new ArrayList<SearchIndexableRaw>();
556
557            PackageManager packageManager = context.getPackageManager();
558            PrintManager printManager = (PrintManager) context.getSystemService(
559                    Context.PRINT_SERVICE);
560
561            String screenTitle = context.getResources().getString(R.string.print_settings);
562
563            // Indexing all services, reagardles if enabled.
564            List<PrintServiceInfo> services = printManager.getInstalledPrintServices();
565            final int serviceCount = services.size();
566            for (int i = 0; i < serviceCount; i++) {
567                PrintServiceInfo service = services.get(i);
568
569                ComponentName componentName = new ComponentName(
570                        service.getResolveInfo().serviceInfo.packageName,
571                        service.getResolveInfo().serviceInfo.name);
572
573                SearchIndexableRaw indexable = new SearchIndexableRaw(context);
574                indexable.key = componentName.flattenToString();
575                indexable.title = service.getResolveInfo().loadLabel(packageManager).toString();
576                indexable.summaryOn = context.getString(R.string.print_feature_state_on);
577                indexable.summaryOff = context.getString(R.string.print_feature_state_off);
578                indexable.screenTitle = screenTitle;
579                indexables.add(indexable);
580            }
581
582            return indexables;
583        }
584
585        @Override
586        public List<SearchIndexableResource> getXmlResourcesToIndex(Context context,
587                boolean enabled) {
588            List<SearchIndexableResource> indexables = new ArrayList<SearchIndexableResource>();
589            SearchIndexableResource indexable = new SearchIndexableResource(context);
590            indexable.xmlResId = R.xml.print_settings;
591            indexables.add(indexable);
592            return indexables;
593        }
594    };
595}