• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.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.Context;
25 import android.content.Intent;
26 import android.content.Loader;
27 import android.content.pm.PackageManager;
28 import android.graphics.drawable.Drawable;
29 import android.net.Uri;
30 import android.os.Bundle;
31 import android.print.PrintJob;
32 import android.print.PrintJobId;
33 import android.print.PrintJobInfo;
34 import android.print.PrintManager;
35 import android.print.PrintManager.PrintJobStateChangeListener;
36 import android.print.PrintServicesLoader;
37 import android.printservice.PrintServiceInfo;
38 import android.provider.SearchIndexableResource;
39 import android.provider.Settings;
40 import android.support.v7.preference.Preference;
41 import android.support.v7.preference.PreferenceCategory;
42 import android.support.v7.preference.PreferenceScreen;
43 import android.text.TextUtils;
44 import android.text.format.DateUtils;
45 import android.util.Log;
46 import android.view.LayoutInflater;
47 import android.view.View;
48 import android.view.View.OnClickListener;
49 import android.view.ViewGroup;
50 import android.widget.Button;
51 import android.widget.TextView;
52 
53 import com.android.internal.logging.MetricsProto.MetricsEvent;
54 import com.android.settings.DialogCreatable;
55 import com.android.settings.R;
56 import com.android.settings.utils.ProfileSettingsPreferenceFragment;
57 import com.android.settings.dashboard.SummaryLoader;
58 import com.android.settings.search.BaseSearchIndexProvider;
59 import com.android.settings.search.Indexable;
60 import com.android.settings.search.SearchIndexableRaw;
61 
62 import java.text.DateFormat;
63 import java.util.ArrayList;
64 import java.util.List;
65 
66 /**
67  * Fragment with the top level print settings.
68  */
69 public class PrintSettingsFragment extends ProfileSettingsPreferenceFragment
70         implements DialogCreatable, Indexable, OnClickListener {
71     public static final String TAG = "PrintSettingsFragment";
72     private static final int LOADER_ID_PRINT_JOBS_LOADER = 1;
73     private static final int LOADER_ID_PRINT_SERVICES = 2;
74 
75     private static final String PRINT_JOBS_CATEGORY = "print_jobs_category";
76     private static final String PRINT_SERVICES_CATEGORY = "print_services_category";
77 
78     static final String EXTRA_CHECKED = "EXTRA_CHECKED";
79     static final String EXTRA_TITLE = "EXTRA_TITLE";
80     static final String EXTRA_SERVICE_COMPONENT_NAME = "EXTRA_SERVICE_COMPONENT_NAME";
81 
82     static final String EXTRA_PRINT_JOB_ID = "EXTRA_PRINT_JOB_ID";
83 
84     private static final String EXTRA_PRINT_SERVICE_COMPONENT_NAME =
85             "EXTRA_PRINT_SERVICE_COMPONENT_NAME";
86 
87     private static final int ORDER_LAST = Preference.DEFAULT_ORDER - 1;
88 
89     private PreferenceCategory mActivePrintJobsCategory;
90     private PreferenceCategory mPrintServicesCategory;
91 
92     private PrintJobsController mPrintJobsController;
93     private PrintServicesController mPrintServicesController;
94 
95     private Button mAddNewServiceButton;
96 
97     @Override
getMetricsCategory()98     protected int getMetricsCategory() {
99         return MetricsEvent.PRINT_SETTINGS;
100     }
101 
102     @Override
getHelpResource()103     protected int getHelpResource() {
104         return R.string.help_uri_printing;
105     }
106 
107     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)108     public View onCreateView(LayoutInflater inflater, ViewGroup container,
109             Bundle savedInstanceState) {
110         View root = super.onCreateView(inflater, container, savedInstanceState);
111         addPreferencesFromResource(R.xml.print_settings);
112 
113         mActivePrintJobsCategory = (PreferenceCategory) findPreference(
114                 PRINT_JOBS_CATEGORY);
115         mPrintServicesCategory = (PreferenceCategory) findPreference(
116                 PRINT_SERVICES_CATEGORY);
117         getPreferenceScreen().removePreference(mActivePrintJobsCategory);
118 
119         mPrintJobsController = new PrintJobsController();
120         getLoaderManager().initLoader(LOADER_ID_PRINT_JOBS_LOADER, null, mPrintJobsController);
121 
122         mPrintServicesController = new PrintServicesController();
123         getLoaderManager().initLoader(LOADER_ID_PRINT_SERVICES, null, mPrintServicesController);
124 
125         return root;
126     }
127 
128     @Override
onStart()129     public void onStart() {
130         super.onStart();
131         setHasOptionsMenu(true);
132         startSubSettingsIfNeeded();
133     }
134 
135     @Override
onStop()136     public void onStop() {
137         super.onStop();
138     }
139 
140     @Override
onViewCreated(View view, Bundle savedInstanceState)141     public void onViewCreated(View view, Bundle savedInstanceState) {
142         super.onViewCreated(view, savedInstanceState);
143         ViewGroup contentRoot = (ViewGroup) getListView().getParent();
144         View emptyView = getActivity().getLayoutInflater().inflate(
145                 R.layout.empty_print_state, contentRoot, false);
146         TextView textView = (TextView) emptyView.findViewById(R.id.message);
147         textView.setText(R.string.print_no_services_installed);
148 
149         final Intent addNewServiceIntent = createAddNewServiceIntentOrNull();
150         if (addNewServiceIntent != null) {
151             mAddNewServiceButton = (Button) emptyView.findViewById(R.id.add_new_service);
152             mAddNewServiceButton.setOnClickListener(this);
153             // The empty is used elsewhere too so it's hidden by default.
154             mAddNewServiceButton.setVisibility(View.VISIBLE);
155         }
156 
157         contentRoot.addView(emptyView);
158         setEmptyView(emptyView);
159     }
160 
161     @Override
getIntentActionString()162     protected String getIntentActionString() {
163         return Settings.ACTION_PRINT_SETTINGS;
164     }
165 
166     /**
167      * Adds preferences for all print services to the {@value PRINT_SERVICES_CATEGORY} cathegory.
168      */
169     private final class PrintServicesController implements
170            LoaderCallbacks<List<PrintServiceInfo>> {
171         @Override
onCreateLoader(int id, Bundle args)172         public Loader<List<PrintServiceInfo>> onCreateLoader(int id, Bundle args) {
173             PrintManager printManager =
174                     (PrintManager) getContext().getSystemService(Context.PRINT_SERVICE);
175             if (printManager != null) {
176                 return new PrintServicesLoader(printManager, getContext(),
177                         PrintManager.ALL_SERVICES);
178             } else {
179                 return null;
180             }
181         }
182 
183         @Override
onLoadFinished(Loader<List<PrintServiceInfo>> loader, List<PrintServiceInfo> services)184         public void onLoadFinished(Loader<List<PrintServiceInfo>> loader,
185                 List<PrintServiceInfo> services) {
186             if (services.isEmpty()) {
187                 getPreferenceScreen().removePreference(mPrintServicesCategory);
188                 return;
189             } else if (getPreferenceScreen().findPreference(PRINT_SERVICES_CATEGORY) == null) {
190                 getPreferenceScreen().addPreference(mPrintServicesCategory);
191             }
192 
193             mPrintServicesCategory.removeAll();
194             PackageManager pm = getActivity().getPackageManager();
195 
196             final int numServices = services.size();
197             for (int i = 0; i < numServices; i++) {
198                 PrintServiceInfo service = services.get(i);
199                 PreferenceScreen preference = getPreferenceManager().createPreferenceScreen(
200                         getActivity());
201 
202                 String title = service.getResolveInfo().loadLabel(pm).toString();
203                 preference.setTitle(title);
204 
205                 ComponentName componentName = service.getComponentName();
206                 preference.setKey(componentName.flattenToString());
207 
208                 preference.setFragment(PrintServiceSettingsFragment.class.getName());
209                 preference.setPersistent(false);
210 
211                 if (service.isEnabled()) {
212                     preference.setSummary(getString(R.string.print_feature_state_on));
213                 } else {
214                     preference.setSummary(getString(R.string.print_feature_state_off));
215                 }
216 
217                 Drawable drawable = service.getResolveInfo().loadIcon(pm);
218                 if (drawable != null) {
219                     preference.setIcon(drawable);
220                 }
221 
222                 Bundle extras = preference.getExtras();
223                 extras.putBoolean(EXTRA_CHECKED, service.isEnabled());
224                 extras.putString(EXTRA_TITLE, title);
225                 extras.putString(EXTRA_SERVICE_COMPONENT_NAME, componentName.flattenToString());
226 
227                 mPrintServicesCategory.addPreference(preference);
228             }
229 
230             Preference addNewServicePreference = newAddServicePreferenceOrNull();
231             if (addNewServicePreference != null) {
232                 mPrintServicesCategory.addPreference(addNewServicePreference);
233             }
234         }
235 
236         @Override
onLoaderReset(Loader<List<PrintServiceInfo>> loader)237         public void onLoaderReset(Loader<List<PrintServiceInfo>> loader) {
238             getPreferenceScreen().removePreference(mPrintServicesCategory);
239         }
240     }
241 
newAddServicePreferenceOrNull()242     private Preference newAddServicePreferenceOrNull() {
243         final Intent addNewServiceIntent = createAddNewServiceIntentOrNull();
244         if (addNewServiceIntent == null) {
245             return null;
246         }
247         Preference preference = new Preference(getPrefContext());
248         preference.setTitle(R.string.print_menu_item_add_service);
249         preference.setIcon(R.drawable.ic_menu_add);
250         preference.setOrder(ORDER_LAST);
251         preference.setIntent(addNewServiceIntent);
252         preference.setPersistent(false);
253         return preference;
254     }
255 
createAddNewServiceIntentOrNull()256     private Intent createAddNewServiceIntentOrNull() {
257         final String searchUri = Settings.Secure.getString(getContentResolver(),
258                 Settings.Secure.PRINT_SERVICE_SEARCH_URI);
259         if (TextUtils.isEmpty(searchUri)) {
260             return null;
261         }
262         return new Intent(Intent.ACTION_VIEW, Uri.parse(searchUri));
263     }
264 
startSubSettingsIfNeeded()265     private void startSubSettingsIfNeeded() {
266         if (getArguments() == null) {
267             return;
268         }
269         String componentName = getArguments().getString(EXTRA_PRINT_SERVICE_COMPONENT_NAME);
270         if (componentName != null) {
271             getArguments().remove(EXTRA_PRINT_SERVICE_COMPONENT_NAME);
272             Preference prereference = findPreference(componentName);
273             if (prereference != null) {
274                 prereference.performClick();
275             }
276         }
277     }
278 
279     @Override
onClick(View v)280     public void onClick(View v) {
281         if (mAddNewServiceButton == v) {
282             final Intent addNewServiceIntent = createAddNewServiceIntentOrNull();
283             if (addNewServiceIntent != null) { // check again just in case.
284                 try {
285                     startActivity(addNewServiceIntent);
286                 } catch (ActivityNotFoundException e) {
287                     Log.w(TAG, "Unable to start activity", e);
288                 }
289             }
290         }
291     }
292 
293      private final class PrintJobsController implements LoaderCallbacks<List<PrintJobInfo>> {
294 
295         @Override
onCreateLoader(int id, Bundle args)296         public Loader<List<PrintJobInfo>> onCreateLoader(int id, Bundle args) {
297             if (id == LOADER_ID_PRINT_JOBS_LOADER) {
298                 return new PrintJobsLoader(getContext());
299             }
300             return null;
301         }
302 
303         @Override
onLoadFinished(Loader<List<PrintJobInfo>> loader, List<PrintJobInfo> printJobs)304         public void onLoadFinished(Loader<List<PrintJobInfo>> loader,
305                 List<PrintJobInfo> printJobs) {
306             if (printJobs == null || printJobs.isEmpty()) {
307                 getPreferenceScreen().removePreference(mActivePrintJobsCategory);
308             } else {
309                 if (getPreferenceScreen().findPreference(PRINT_JOBS_CATEGORY) == null) {
310                     getPreferenceScreen().addPreference(mActivePrintJobsCategory);
311                 }
312 
313                 mActivePrintJobsCategory.removeAll();
314 
315                 final int printJobCount = printJobs.size();
316                 for (int i = 0; i < printJobCount; i++) {
317                     PrintJobInfo printJob = printJobs.get(i);
318 
319                     PreferenceScreen preference = getPreferenceManager()
320                             .createPreferenceScreen(getActivity());
321 
322                     preference.setPersistent(false);
323                     preference.setFragment(PrintJobSettingsFragment.class.getName());
324                     preference.setKey(printJob.getId().flattenToString());
325 
326                     switch (printJob.getState()) {
327                         case PrintJobInfo.STATE_QUEUED:
328                         case PrintJobInfo.STATE_STARTED: {
329                             if (!printJob.isCancelling()) {
330                                 preference.setTitle(getString(
331                                         R.string.print_printing_state_title_template,
332                                         printJob.getLabel()));
333                             } else {
334                                 preference.setTitle(getString(
335                                         R.string.print_cancelling_state_title_template,
336                                         printJob.getLabel()));
337                             }
338                         } break;
339 
340                         case PrintJobInfo.STATE_FAILED: {
341                             preference.setTitle(getString(
342                                     R.string.print_failed_state_title_template,
343                                     printJob.getLabel()));
344                         } break;
345 
346                         case PrintJobInfo.STATE_BLOCKED: {
347                             if (!printJob.isCancelling()) {
348                                 preference.setTitle(getString(
349                                         R.string.print_blocked_state_title_template,
350                                         printJob.getLabel()));
351                             } else {
352                                 preference.setTitle(getString(
353                                         R.string.print_cancelling_state_title_template,
354                                         printJob.getLabel()));
355                             }
356                         } break;
357                     }
358 
359                     preference.setSummary(getString(R.string.print_job_summary,
360                             printJob.getPrinterName(), DateUtils.formatSameDayTime(
361                                     printJob.getCreationTime(), printJob.getCreationTime(),
362                                     DateFormat.SHORT, DateFormat.SHORT)));
363 
364                     switch (printJob.getState()) {
365                         case PrintJobInfo.STATE_QUEUED:
366                         case PrintJobInfo.STATE_STARTED: {
367                             preference.setIcon(R.drawable.ic_print);
368                         } break;
369 
370                         case PrintJobInfo.STATE_FAILED:
371                         case PrintJobInfo.STATE_BLOCKED: {
372                             preference.setIcon(R.drawable.ic_print_error);
373                         } break;
374                     }
375 
376                     Bundle extras = preference.getExtras();
377                     extras.putString(EXTRA_PRINT_JOB_ID, printJob.getId().flattenToString());
378 
379                     mActivePrintJobsCategory.addPreference(preference);
380                 }
381             }
382         }
383 
384         @Override
onLoaderReset(Loader<List<PrintJobInfo>> loader)385         public void onLoaderReset(Loader<List<PrintJobInfo>> loader) {
386             getPreferenceScreen().removePreference(mActivePrintJobsCategory);
387         }
388     }
389 
390     private static final class PrintJobsLoader extends AsyncTaskLoader<List<PrintJobInfo>> {
391 
392         private static final String LOG_TAG = "PrintJobsLoader";
393 
394         private static final boolean DEBUG = false;
395 
396         private List<PrintJobInfo> mPrintJobs = new ArrayList<PrintJobInfo>();
397 
398         private final PrintManager mPrintManager;
399 
400         private PrintJobStateChangeListener mPrintJobStateChangeListener;
401 
PrintJobsLoader(Context context)402         public PrintJobsLoader(Context context) {
403             super(context);
404             mPrintManager = ((PrintManager) context.getSystemService(
405                     Context.PRINT_SERVICE)).getGlobalPrintManagerForUser(
406                     context.getUserId());
407         }
408 
409         @Override
deliverResult(List<PrintJobInfo> printJobs)410         public void deliverResult(List<PrintJobInfo> printJobs) {
411             if (isStarted()) {
412                 super.deliverResult(printJobs);
413             }
414         }
415 
416         @Override
onStartLoading()417         protected void onStartLoading() {
418             if (DEBUG) {
419                 Log.i(LOG_TAG, "onStartLoading()");
420             }
421             // If we already have a result, deliver it immediately.
422             if (!mPrintJobs.isEmpty()) {
423                 deliverResult(new ArrayList<PrintJobInfo>(mPrintJobs));
424             }
425             // Start watching for changes.
426             if (mPrintJobStateChangeListener == null) {
427                 mPrintJobStateChangeListener = new PrintJobStateChangeListener() {
428                     @Override
429                     public void onPrintJobStateChanged(PrintJobId printJobId) {
430                         onForceLoad();
431                     }
432                 };
433                 mPrintManager.addPrintJobStateChangeListener(
434                         mPrintJobStateChangeListener);
435             }
436             // If the data changed or we have no data - load it now.
437             if (mPrintJobs.isEmpty()) {
438                 onForceLoad();
439             }
440         }
441 
442         @Override
onStopLoading()443         protected void onStopLoading() {
444             if (DEBUG) {
445                 Log.i(LOG_TAG, "onStopLoading()");
446             }
447             // Cancel the load in progress if possible.
448             onCancelLoad();
449         }
450 
451         @Override
onReset()452         protected void onReset() {
453             if (DEBUG) {
454                 Log.i(LOG_TAG, "onReset()");
455             }
456             // Stop loading.
457             onStopLoading();
458             // Clear the cached result.
459             mPrintJobs.clear();
460             // Stop watching for changes.
461             if (mPrintJobStateChangeListener != null) {
462                 mPrintManager.removePrintJobStateChangeListener(
463                         mPrintJobStateChangeListener);
464                 mPrintJobStateChangeListener = null;
465             }
466         }
467 
468         @Override
loadInBackground()469         public List<PrintJobInfo> loadInBackground() {
470             List<PrintJobInfo> printJobInfos = null;
471             List<PrintJob> printJobs = mPrintManager.getPrintJobs();
472             final int printJobCount = printJobs.size();
473             for (int i = 0; i < printJobCount; i++) {
474                 PrintJobInfo printJob = printJobs.get(i).getInfo();
475                 if (shouldShowToUser(printJob)) {
476                     if (printJobInfos == null) {
477                         printJobInfos = new ArrayList<PrintJobInfo>();
478                     }
479                     printJobInfos.add(printJob);
480                 }
481             }
482             return printJobInfos;
483         }
484     }
485 
486     /**
487      * Should the print job the shown to the user in the settings app.
488      *
489      * @param printJob The print job in question.
490      * @return true iff the print job should be shown.
491      */
shouldShowToUser(PrintJobInfo printJob)492     private static boolean shouldShowToUser(PrintJobInfo printJob) {
493         switch (printJob.getState()) {
494             case PrintJobInfo.STATE_QUEUED:
495             case PrintJobInfo.STATE_STARTED:
496             case PrintJobInfo.STATE_BLOCKED:
497             case PrintJobInfo.STATE_FAILED: {
498                 return true;
499             }
500         }
501         return false;
502     }
503 
504     /**
505      * Provider for the print settings summary
506      */
507     private static class PrintSummaryProvider
508             implements SummaryLoader.SummaryProvider, PrintJobStateChangeListener {
509         private final Context mContext;
510         private final PrintManager mPrintManager;
511         private final SummaryLoader mSummaryLoader;
512 
513         /**
514          * Create a new {@link PrintSummaryProvider}.
515          *
516          * @param context The context this provider is for
517          * @param summaryLoader The summary load using this provider
518          */
PrintSummaryProvider(Context context, SummaryLoader summaryLoader)519         public PrintSummaryProvider(Context context, SummaryLoader summaryLoader) {
520             mContext = context;
521             mSummaryLoader = summaryLoader;
522             mPrintManager = ((PrintManager) context.getSystemService(Context.PRINT_SERVICE))
523                     .getGlobalPrintManagerForUser(context.getUserId());
524         }
525 
526         @Override
setListening(boolean isListening)527         public void setListening(boolean isListening) {
528             if (mPrintManager != null) {
529                 if (isListening) {
530                     mPrintManager.addPrintJobStateChangeListener(this);
531                     onPrintJobStateChanged(null);
532                 } else {
533                     mPrintManager.removePrintJobStateChangeListener(this);
534                 }
535             }
536         }
537 
538         @Override
onPrintJobStateChanged(PrintJobId printJobId)539         public void onPrintJobStateChanged(PrintJobId printJobId) {
540             List<PrintJob> printJobs = mPrintManager.getPrintJobs();
541 
542             int numActivePrintJobs = 0;
543             final int numPrintJobs = printJobs.size();
544             for (int i = 0; i < numPrintJobs; i++) {
545                 if (shouldShowToUser(printJobs.get(i).getInfo())) {
546                     numActivePrintJobs++;
547                 }
548             }
549 
550             mSummaryLoader.setSummary(this, mContext.getResources().getQuantityString(
551                     R.plurals.print_settings_title, numActivePrintJobs, numActivePrintJobs));
552         }
553     }
554 
555     /**
556      * A factory for {@link PrintSummaryProvider providers} the settings app can use to read the
557      * print summary.
558      */
559     public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY
560             = new SummaryLoader.SummaryProviderFactory() {
561         @Override
562         public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity,
563                 SummaryLoader summaryLoader) {
564             return new PrintSummaryProvider(activity, summaryLoader);
565         }
566     };
567 
568     public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
569             new BaseSearchIndexProvider() {
570         @Override
571         public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
572             List<SearchIndexableRaw> indexables = new ArrayList<SearchIndexableRaw>();
573 
574             PackageManager packageManager = context.getPackageManager();
575             PrintManager printManager = (PrintManager) context.getSystemService(
576                     Context.PRINT_SERVICE);
577 
578             String screenTitle = context.getResources().getString(R.string.print_settings);
579             SearchIndexableRaw data = new SearchIndexableRaw(context);
580             data.title = screenTitle;
581             data.screenTitle = screenTitle;
582             indexables.add(data);
583 
584             // Indexing all services, regardless if enabled. Please note that the index will not be
585             // updated until this function is called again
586             List<PrintServiceInfo> services =
587                     printManager.getPrintServices(PrintManager.ALL_SERVICES);
588 
589             if (services != null) {
590                 final int serviceCount = services.size();
591                 for (int i = 0; i < serviceCount; i++) {
592                     PrintServiceInfo service = services.get(i);
593 
594                     ComponentName componentName = new ComponentName(
595                             service.getResolveInfo().serviceInfo.packageName,
596                             service.getResolveInfo().serviceInfo.name);
597 
598                     data = new SearchIndexableRaw(context);
599                     data.key = componentName.flattenToString();
600                     data.title = service.getResolveInfo().loadLabel(packageManager).toString();
601                     data.summaryOn = context.getString(R.string.print_feature_state_on);
602                     data.summaryOff = context.getString(R.string.print_feature_state_off);
603                     data.screenTitle = screenTitle;
604                     indexables.add(data);
605                 }
606             }
607 
608             return indexables;
609         }
610 
611         @Override
612         public List<SearchIndexableResource> getXmlResourcesToIndex(Context context,
613                 boolean enabled) {
614             List<SearchIndexableResource> indexables = new ArrayList<SearchIndexableResource>();
615             SearchIndexableResource indexable = new SearchIndexableResource(context);
616             indexable.xmlResId = R.xml.print_settings;
617             indexables.add(indexable);
618             return indexables;
619         }
620     };
621 }
622