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