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