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 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
     96     public int getMetricsCategory() {
     97         return MetricsEvent.PRINT_SETTINGS;
     98     }
     99 
    100     @Override
    101     public int getHelpResource() {
    102         return R.string.help_uri_printing;
    103     }
    104 
    105     @Override
    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
    127     public void onStart() {
    128         super.onStart();
    129         setHasOptionsMenu(true);
    130         startSubSettingsIfNeeded();
    131     }
    132 
    133     @Override
    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
    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
    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
    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
    231         public void onLoaderReset(Loader<List<PrintServiceInfo>> loader) {
    232             getPreferenceScreen().removePreference(mPrintServicesCategory);
    233         }
    234     }
    235 
    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 
    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 
    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
    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
    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
    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
    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 
    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
    416         public void deliverResult(List<PrintJobInfo> printJobs) {
    417             if (isStarted()) {
    418                 super.deliverResult(printJobs);
    419             }
    420         }
    421 
    422         @Override
    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
    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
    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
    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