• 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 static com.android.settings.print.PrintSettingPreferenceController.shouldShowToUser;
20 
21 import android.app.LoaderManager.LoaderCallbacks;
22 import android.content.ActivityNotFoundException;
23 import android.content.AsyncTaskLoader;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.Loader;
28 import android.content.pm.PackageManager;
29 import android.content.res.TypedArray;
30 import android.graphics.drawable.Drawable;
31 import android.net.Uri;
32 import android.os.Bundle;
33 import android.print.PrintJob;
34 import android.print.PrintJobId;
35 import android.print.PrintJobInfo;
36 import android.print.PrintManager;
37 import android.print.PrintManager.PrintJobStateChangeListener;
38 import android.print.PrintServicesLoader;
39 import android.printservice.PrintServiceInfo;
40 import android.provider.SearchIndexableResource;
41 import android.provider.Settings;
42 import android.support.v7.preference.Preference;
43 import android.support.v7.preference.PreferenceCategory;
44 import android.text.TextUtils;
45 import android.text.format.DateUtils;
46 import android.util.Log;
47 import android.view.LayoutInflater;
48 import android.view.View;
49 import android.view.View.OnClickListener;
50 import android.view.ViewGroup;
51 import android.widget.Button;
52 import android.widget.TextView;
53 
54 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
55 import com.android.settings.R;
56 import com.android.settings.search.BaseSearchIndexProvider;
57 import com.android.settings.search.Indexable;
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     public 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
onViewCreated(View view, Bundle savedInstanceState)134     public void onViewCreated(View view, Bundle savedInstanceState) {
135         super.onViewCreated(view, savedInstanceState);
136         ViewGroup contentRoot = (ViewGroup) getListView().getParent();
137         View emptyView = getActivity().getLayoutInflater().inflate(
138                 R.layout.empty_print_state, contentRoot, false);
139         TextView textView = (TextView) emptyView.findViewById(R.id.message);
140         textView.setText(R.string.print_no_services_installed);
141 
142         final Intent addNewServiceIntent = createAddNewServiceIntentOrNull();
143         if (addNewServiceIntent != null) {
144             mAddNewServiceButton = (Button) emptyView.findViewById(R.id.add_new_service);
145             mAddNewServiceButton.setOnClickListener(this);
146             // The empty is used elsewhere too so it's hidden by default.
147             mAddNewServiceButton.setVisibility(View.VISIBLE);
148         }
149 
150         contentRoot.addView(emptyView);
151         setEmptyView(emptyView);
152     }
153 
154     @Override
getIntentActionString()155     protected String getIntentActionString() {
156         return Settings.ACTION_PRINT_SETTINGS;
157     }
158 
159     /**
160      * Adds preferences for all print services to the {@value PRINT_SERVICES_CATEGORY} cathegory.
161      */
162     private final class PrintServicesController implements LoaderCallbacks<List<PrintServiceInfo>> {
163         @Override
onCreateLoader(int id, Bundle args)164         public Loader<List<PrintServiceInfo>> onCreateLoader(int id, Bundle args) {
165             PrintManager printManager =
166                     (PrintManager) getContext().getSystemService(Context.PRINT_SERVICE);
167             if (printManager != null) {
168                 return new PrintServicesLoader(printManager, getContext(),
169                         PrintManager.ALL_SERVICES);
170             } else {
171                 return null;
172             }
173         }
174 
175         @Override
onLoadFinished(Loader<List<PrintServiceInfo>> loader, List<PrintServiceInfo> services)176         public void onLoadFinished(Loader<List<PrintServiceInfo>> loader,
177                 List<PrintServiceInfo> services) {
178             if (services.isEmpty()) {
179                 getPreferenceScreen().removePreference(mPrintServicesCategory);
180                 return;
181             } else if (getPreferenceScreen().findPreference(PRINT_SERVICES_CATEGORY) == null) {
182                 getPreferenceScreen().addPreference(mPrintServicesCategory);
183             }
184 
185             mPrintServicesCategory.removeAll();
186             PackageManager pm = getActivity().getPackageManager();
187             final Context context = getPrefContext();
188             if (context == null) {
189                 Log.w(TAG, "No preference context, skip adding print services");
190                 return;
191             }
192 
193             for (PrintServiceInfo service : services) {
194                 Preference preference = new Preference(context);
195 
196                 String title = service.getResolveInfo().loadLabel(pm).toString();
197                 preference.setTitle(title);
198 
199                 ComponentName componentName = service.getComponentName();
200                 preference.setKey(componentName.flattenToString());
201 
202                 preference.setFragment(PrintServiceSettingsFragment.class.getName());
203                 preference.setPersistent(false);
204 
205                 if (service.isEnabled()) {
206                     preference.setSummary(getString(R.string.print_feature_state_on));
207                 } else {
208                     preference.setSummary(getString(R.string.print_feature_state_off));
209                 }
210 
211                 Drawable drawable = service.getResolveInfo().loadIcon(pm);
212                 if (drawable != null) {
213                     preference.setIcon(drawable);
214                 }
215 
216                 Bundle extras = preference.getExtras();
217                 extras.putBoolean(EXTRA_CHECKED, service.isEnabled());
218                 extras.putString(EXTRA_TITLE, title);
219                 extras.putString(EXTRA_SERVICE_COMPONENT_NAME, componentName.flattenToString());
220 
221                 mPrintServicesCategory.addPreference(preference);
222             }
223 
224             Preference addNewServicePreference = newAddServicePreferenceOrNull();
225             if (addNewServicePreference != null) {
226                 mPrintServicesCategory.addPreference(addNewServicePreference);
227             }
228         }
229 
230         @Override
onLoaderReset(Loader<List<PrintServiceInfo>> loader)231         public void onLoaderReset(Loader<List<PrintServiceInfo>> loader) {
232             getPreferenceScreen().removePreference(mPrintServicesCategory);
233         }
234     }
235 
newAddServicePreferenceOrNull()236     private Preference newAddServicePreferenceOrNull() {
237         final Intent addNewServiceIntent = createAddNewServiceIntentOrNull();
238         if (addNewServiceIntent == null) {
239             return null;
240         }
241         Preference preference = new Preference(getPrefContext());
242         preference.setTitle(R.string.print_menu_item_add_service);
243         preference.setIcon(R.drawable.ic_menu_add);
244         preference.setOrder(ORDER_LAST);
245         preference.setIntent(addNewServiceIntent);
246         preference.setPersistent(false);
247         return preference;
248     }
249 
createAddNewServiceIntentOrNull()250     private Intent createAddNewServiceIntentOrNull() {
251         final String searchUri = Settings.Secure.getString(getContentResolver(),
252                 Settings.Secure.PRINT_SERVICE_SEARCH_URI);
253         if (TextUtils.isEmpty(searchUri)) {
254             return null;
255         }
256         return new Intent(Intent.ACTION_VIEW, Uri.parse(searchUri));
257     }
258 
startSubSettingsIfNeeded()259     private void startSubSettingsIfNeeded() {
260         if (getArguments() == null) {
261             return;
262         }
263         String componentName = getArguments().getString(EXTRA_PRINT_SERVICE_COMPONENT_NAME);
264         if (componentName != null) {
265             getArguments().remove(EXTRA_PRINT_SERVICE_COMPONENT_NAME);
266             Preference prereference = findPreference(componentName);
267             if (prereference != null) {
268                 prereference.performClick();
269             }
270         }
271     }
272 
273     @Override
onClick(View v)274     public void onClick(View v) {
275         if (mAddNewServiceButton == v) {
276             final Intent addNewServiceIntent = createAddNewServiceIntentOrNull();
277             if (addNewServiceIntent != null) { // check again just in case.
278                 try {
279                     startActivity(addNewServiceIntent);
280                 } catch (ActivityNotFoundException e) {
281                     Log.w(TAG, "Unable to start activity", e);
282                 }
283             }
284         }
285     }
286 
287     private final class PrintJobsController implements LoaderCallbacks<List<PrintJobInfo>> {
288 
289         @Override
onCreateLoader(int id, Bundle args)290         public Loader<List<PrintJobInfo>> onCreateLoader(int id, Bundle args) {
291             if (id == LOADER_ID_PRINT_JOBS_LOADER) {
292                 return new PrintJobsLoader(getContext());
293             }
294             return null;
295         }
296 
297         @Override
onLoadFinished(Loader<List<PrintJobInfo>> loader, List<PrintJobInfo> printJobs)298         public void onLoadFinished(Loader<List<PrintJobInfo>> loader,
299                 List<PrintJobInfo> printJobs) {
300             if (printJobs == null || printJobs.isEmpty()) {
301                 getPreferenceScreen().removePreference(mActivePrintJobsCategory);
302             } else {
303                 if (getPreferenceScreen().findPreference(PRINT_JOBS_CATEGORY) == null) {
304                     getPreferenceScreen().addPreference(mActivePrintJobsCategory);
305                 }
306 
307                 mActivePrintJobsCategory.removeAll();
308                 final Context context = getPrefContext();
309                 if (context == null) {
310                     Log.w(TAG, "No preference context, skip adding print jobs");
311                     return;
312                 }
313 
314                 for (PrintJobInfo printJob : printJobs) {
315                     Preference preference = new Preference(context);
316 
317                     preference.setPersistent(false);
318                     preference.setFragment(PrintJobSettingsFragment.class.getName());
319                     preference.setKey(printJob.getId().flattenToString());
320 
321                     switch (printJob.getState()) {
322                         case PrintJobInfo.STATE_QUEUED:
323                         case PrintJobInfo.STATE_STARTED:
324                             if (!printJob.isCancelling()) {
325                                 preference.setTitle(getString(
326                                         R.string.print_printing_state_title_template,
327                                         printJob.getLabel()));
328                             } else {
329                                 preference.setTitle(getString(
330                                         R.string.print_cancelling_state_title_template,
331                                         printJob.getLabel()));
332                             }
333                             break;
334                         case PrintJobInfo.STATE_FAILED:
335                             preference.setTitle(getString(
336                                     R.string.print_failed_state_title_template,
337                                     printJob.getLabel()));
338                             break;
339                         case PrintJobInfo.STATE_BLOCKED:
340                             if (!printJob.isCancelling()) {
341                                 preference.setTitle(getString(
342                                         R.string.print_blocked_state_title_template,
343                                         printJob.getLabel()));
344                             } else {
345                                 preference.setTitle(getString(
346                                         R.string.print_cancelling_state_title_template,
347                                         printJob.getLabel()));
348                             }
349                             break;
350                     }
351 
352                     preference.setSummary(getString(R.string.print_job_summary,
353                             printJob.getPrinterName(), DateUtils.formatSameDayTime(
354                                     printJob.getCreationTime(), printJob.getCreationTime(),
355                                     DateFormat.SHORT, DateFormat.SHORT)));
356 
357                     TypedArray a = getActivity().obtainStyledAttributes(new int[] {
358                             android.R.attr.colorControlNormal});
359                     int tintColor = a.getColor(0, 0);
360                     a.recycle();
361 
362                     switch (printJob.getState()) {
363                         case PrintJobInfo.STATE_QUEUED:
364                         case PrintJobInfo.STATE_STARTED: {
365                             Drawable icon = getActivity().getDrawable(
366                                     com.android.internal.R.drawable.ic_print);
367                             icon.setTint(tintColor);
368                             preference.setIcon(icon);
369                             break;
370                         }
371 
372                         case PrintJobInfo.STATE_FAILED:
373                         case PrintJobInfo.STATE_BLOCKED: {
374                             Drawable icon = getActivity().getDrawable(
375                                     com.android.internal.R.drawable.ic_print_error);
376                             icon.setTint(tintColor);
377                             preference.setIcon(icon);
378                             break;
379                         }
380                     }
381 
382                     Bundle extras = preference.getExtras();
383                     extras.putString(EXTRA_PRINT_JOB_ID, printJob.getId().flattenToString());
384 
385                     mActivePrintJobsCategory.addPreference(preference);
386                 }
387             }
388         }
389 
390         @Override
onLoaderReset(Loader<List<PrintJobInfo>> loader)391         public void onLoaderReset(Loader<List<PrintJobInfo>> loader) {
392             getPreferenceScreen().removePreference(mActivePrintJobsCategory);
393         }
394     }
395 
396     private static final class PrintJobsLoader extends AsyncTaskLoader<List<PrintJobInfo>> {
397 
398         private static final String LOG_TAG = "PrintJobsLoader";
399 
400         private static final boolean DEBUG = false;
401 
402         private List<PrintJobInfo> mPrintJobs = new ArrayList<PrintJobInfo>();
403 
404         private final PrintManager mPrintManager;
405 
406         private PrintJobStateChangeListener mPrintJobStateChangeListener;
407 
PrintJobsLoader(Context context)408         public PrintJobsLoader(Context context) {
409             super(context);
410             mPrintManager = ((PrintManager) context.getSystemService(
411                     Context.PRINT_SERVICE)).getGlobalPrintManagerForUser(
412                     context.getUserId());
413         }
414 
415         @Override
deliverResult(List<PrintJobInfo> printJobs)416         public void deliverResult(List<PrintJobInfo> printJobs) {
417             if (isStarted()) {
418                 super.deliverResult(printJobs);
419             }
420         }
421 
422         @Override
onStartLoading()423         protected void onStartLoading() {
424             if (DEBUG) {
425                 Log.i(LOG_TAG, "onStartLoading()");
426             }
427             // If we already have a result, deliver it immediately.
428             if (!mPrintJobs.isEmpty()) {
429                 deliverResult(new ArrayList<PrintJobInfo>(mPrintJobs));
430             }
431             // Start watching for changes.
432             if (mPrintJobStateChangeListener == null) {
433                 mPrintJobStateChangeListener = new PrintJobStateChangeListener() {
434                     @Override
435                     public void onPrintJobStateChanged(PrintJobId printJobId) {
436                         onForceLoad();
437                     }
438                 };
439                 mPrintManager.addPrintJobStateChangeListener(
440                         mPrintJobStateChangeListener);
441             }
442             // If the data changed or we have no data - load it now.
443             if (mPrintJobs.isEmpty()) {
444                 onForceLoad();
445             }
446         }
447 
448         @Override
onStopLoading()449         protected void onStopLoading() {
450             if (DEBUG) {
451                 Log.i(LOG_TAG, "onStopLoading()");
452             }
453             // Cancel the load in progress if possible.
454             onCancelLoad();
455         }
456 
457         @Override
onReset()458         protected void onReset() {
459             if (DEBUG) {
460                 Log.i(LOG_TAG, "onReset()");
461             }
462             // Stop loading.
463             onStopLoading();
464             // Clear the cached result.
465             mPrintJobs.clear();
466             // Stop watching for changes.
467             if (mPrintJobStateChangeListener != null) {
468                 mPrintManager.removePrintJobStateChangeListener(
469                         mPrintJobStateChangeListener);
470                 mPrintJobStateChangeListener = null;
471             }
472         }
473 
474         @Override
loadInBackground()475         public List<PrintJobInfo> loadInBackground() {
476             List<PrintJobInfo> printJobInfos = null;
477             List<PrintJob> printJobs = mPrintManager.getPrintJobs();
478             final int printJobCount = printJobs.size();
479             for (int i = 0; i < printJobCount; i++) {
480                 PrintJobInfo printJob = printJobs.get(i).getInfo();
481                 if (shouldShowToUser(printJob)) {
482                     if (printJobInfos == null) {
483                         printJobInfos = new ArrayList<>();
484                     }
485                     printJobInfos.add(printJob);
486                 }
487             }
488             return printJobInfos;
489         }
490     }
491 
492     public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
493             new BaseSearchIndexProvider() {
494 
495                 @Override
496                 public List<SearchIndexableResource> getXmlResourcesToIndex(Context context,
497                         boolean enabled) {
498                     List<SearchIndexableResource> indexables = new ArrayList<>();
499                     SearchIndexableResource indexable = new SearchIndexableResource(context);
500                     indexable.xmlResId = R.xml.print_settings;
501                     indexables.add(indexable);
502                     return indexables;
503                 }
504             };
505 }
506