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