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.ActivityManager;
     20 import android.app.LoaderManager.LoaderCallbacks;
     21 import android.content.AsyncTaskLoader;
     22 import android.content.ComponentName;
     23 import android.content.ContentResolver;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.content.Loader;
     27 import android.content.pm.PackageManager;
     28 import android.content.pm.ResolveInfo;
     29 import android.database.ContentObserver;
     30 import android.net.Uri;
     31 import android.os.Bundle;
     32 import android.os.Handler;
     33 import android.os.Message;
     34 import android.preference.Preference;
     35 import android.preference.PreferenceCategory;
     36 import android.preference.PreferenceScreen;
     37 import android.print.PrintJob;
     38 import android.print.PrintJobId;
     39 import android.print.PrintJobInfo;
     40 import android.print.PrintManager;
     41 import android.print.PrintManager.PrintJobStateChangeListener;
     42 import android.printservice.PrintServiceInfo;
     43 import android.provider.Settings;
     44 import android.text.TextUtils;
     45 import android.text.format.DateUtils;
     46 import android.util.Log;
     47 import android.view.Menu;
     48 import android.view.MenuInflater;
     49 import android.view.MenuItem;
     50 import android.view.View;
     51 import android.view.ViewGroup;
     52 import android.widget.Switch;
     53 import android.widget.TextView;
     54 
     55 import com.android.internal.content.PackageMonitor;
     56 import com.android.settings.DialogCreatable;
     57 import com.android.settings.R;
     58 import com.android.settings.SettingsPreferenceFragment;
     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 SettingsPreferenceFragment implements DialogCreatable {
     68 
     69     static final char ENABLED_PRINT_SERVICES_SEPARATOR = ':';
     70 
     71     private static final int LOADER_ID_PRINT_JOBS_LOADER = 1;
     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     // Extras passed to sub-fragments.
     77     static final String EXTRA_PREFERENCE_KEY = "EXTRA_PREFERENCE_KEY";
     78     static final String EXTRA_CHECKED = "EXTRA_CHECKED";
     79     static final String EXTRA_TITLE = "EXTRA_TITLE";
     80     static final String EXTRA_ENABLE_WARNING_TITLE = "EXTRA_ENABLE_WARNING_TITLE";
     81     static final String EXTRA_ENABLE_WARNING_MESSAGE = "EXTRA_ENABLE_WARNING_MESSAGE";
     82     static final String EXTRA_SETTINGS_TITLE = "EXTRA_SETTINGS_TITLE";
     83     static final String EXTRA_SETTINGS_COMPONENT_NAME = "EXTRA_SETTINGS_COMPONENT_NAME";
     84     static final String EXTRA_ADD_PRINTERS_TITLE = "EXTRA_ADD_PRINTERS_TITLE";
     85     static final String EXTRA_ADD_PRINTERS_COMPONENT_NAME = "EXTRA_ADD_PRINTERS_COMPONENT_NAME";
     86     static final String EXTRA_SERVICE_COMPONENT_NAME = "EXTRA_SERVICE_COMPONENT_NAME";
     87 
     88     static final String EXTRA_PRINT_JOB_ID = "EXTRA_PRINT_JOB_ID";
     89 
     90     private static final String EXTRA_PRINT_SERVICE_COMPONENT_NAME =
     91             "EXTRA_PRINT_SERVICE_COMPONENT_NAME";
     92 
     93     private final PackageMonitor mSettingsPackageMonitor = new SettingsPackageMonitor();
     94 
     95     private final Handler mHandler = new Handler() {
     96         @Override
     97         public void dispatchMessage(Message msg) {
     98             updateServicesPreferences();
     99         }
    100     };
    101 
    102     private final SettingsContentObserver mSettingsContentObserver =
    103             new SettingsContentObserver(mHandler) {
    104         @Override
    105         public void onChange(boolean selfChange, Uri uri) {
    106             updateServicesPreferences();
    107         }
    108     };
    109 
    110     private PreferenceCategory mActivePrintJobsCategory;
    111     private PreferenceCategory mPrintServicesCategory;
    112 
    113     private PrintJobsController mPrintJobsController;
    114 
    115     @Override
    116     public void onCreate(Bundle icicle) {
    117         super.onCreate(icicle);
    118         addPreferencesFromResource(R.xml.print_settings);
    119 
    120         mActivePrintJobsCategory = (PreferenceCategory) findPreference(
    121                 PRINT_JOBS_CATEGORY);
    122         mPrintServicesCategory= (PreferenceCategory) findPreference(
    123                 PRINT_SERVICES_CATEGORY);
    124         getPreferenceScreen().removePreference(mActivePrintJobsCategory);
    125 
    126         mPrintJobsController = new PrintJobsController();
    127         getActivity().getLoaderManager().initLoader(LOADER_ID_PRINT_JOBS_LOADER,
    128                 null, mPrintJobsController);
    129     }
    130 
    131     @Override
    132     public void onResume() {
    133         super.onResume();
    134         mSettingsPackageMonitor.register(getActivity(), getActivity().getMainLooper(), false);
    135         mSettingsContentObserver.register(getContentResolver());
    136         updateServicesPreferences();
    137         setHasOptionsMenu(true);
    138         startSubSettingsIfNeeded();
    139     }
    140 
    141     @Override
    142     public void onPause() {
    143         mSettingsPackageMonitor.unregister();
    144         mSettingsContentObserver.unregister(getContentResolver());
    145         super.onPause();
    146     }
    147 
    148     @Override
    149     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    150         super.onCreateOptionsMenu(menu, inflater);
    151         String searchUri = Settings.Secure.getString(getContentResolver(),
    152                 Settings.Secure.PRINT_SERVICE_SEARCH_URI);
    153         if (!TextUtils.isEmpty(searchUri)) {
    154             MenuItem menuItem = menu.add(R.string.print_menu_item_add_service);
    155             menuItem.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_IF_ROOM);
    156             menuItem.setIntent(new Intent(Intent.ACTION_VIEW,Uri.parse(searchUri)));
    157         }
    158     }
    159 
    160     @Override
    161     public void onViewCreated(View view, Bundle savedInstanceState) {
    162         super.onViewCreated(view, savedInstanceState);
    163         ViewGroup contentRoot = (ViewGroup) getListView().getParent();
    164         View emptyView = getActivity().getLayoutInflater().inflate(
    165                     R.layout.empty_print_state, contentRoot, false);
    166         TextView textView = (TextView) emptyView.findViewById(R.id.message);
    167         textView.setText(R.string.print_no_services_installed);
    168         contentRoot.addView(emptyView);
    169         getListView().setEmptyView(emptyView);
    170     }
    171 
    172     private void updateServicesPreferences() {
    173         if (getPreferenceScreen().findPreference(PRINT_SERVICES_CATEGORY) == null) {
    174             getPreferenceScreen().addPreference(mPrintServicesCategory);
    175         } else {
    176             // Since services category is auto generated we have to do a pass
    177             // to generate it since services can come and go.
    178             mPrintServicesCategory.removeAll();
    179         }
    180 
    181         List<ComponentName> enabledServices = SettingsUtils
    182                 .readEnabledPrintServices(getActivity());
    183 
    184         List<ResolveInfo> installedServices = getActivity().getPackageManager()
    185                 .queryIntentServices(
    186                         new Intent(android.printservice.PrintService.SERVICE_INTERFACE),
    187                         PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
    188 
    189         final int installedServiceCount = installedServices.size();
    190         for (int i = 0; i < installedServiceCount; i++) {
    191             ResolveInfo installedService = installedServices.get(i);
    192 
    193             PreferenceScreen preference = getPreferenceManager().createPreferenceScreen(
    194                     getActivity());
    195 
    196             String title = installedService.loadLabel(getPackageManager()).toString();
    197             preference.setTitle(title);
    198 
    199             ComponentName componentName = new ComponentName(
    200                     installedService.serviceInfo.packageName,
    201                     installedService.serviceInfo.name);
    202             preference.setKey(componentName.flattenToString());
    203 
    204             preference.setOrder(i);
    205             preference.setFragment(PrintServiceSettingsFragment.class.getName());
    206             preference.setPersistent(false);
    207 
    208             final boolean serviceEnabled = enabledServices.contains(componentName);
    209             if (serviceEnabled) {
    210                 preference.setSummary(getString(R.string.print_feature_state_on));
    211             } else {
    212                 preference.setSummary(getString(R.string.print_feature_state_off));
    213             }
    214 
    215             Bundle extras = preference.getExtras();
    216             extras.putString(EXTRA_PREFERENCE_KEY, preference.getKey());
    217             extras.putBoolean(EXTRA_CHECKED, serviceEnabled);
    218             extras.putString(EXTRA_TITLE, title);
    219 
    220             PrintServiceInfo printServiceInfo = PrintServiceInfo.create(
    221                     installedService, getActivity());
    222 
    223             CharSequence applicationLabel = installedService.loadLabel(getPackageManager());
    224 
    225             extras.putString(EXTRA_ENABLE_WARNING_TITLE, getString(
    226                     R.string.print_service_security_warning_title, applicationLabel));
    227             extras.putString(EXTRA_ENABLE_WARNING_MESSAGE, getString(
    228                     R.string.print_service_security_warning_summary, applicationLabel));
    229 
    230             String settingsClassName = printServiceInfo.getSettingsActivityName();
    231             if (!TextUtils.isEmpty(settingsClassName)) {
    232                 extras.putString(EXTRA_SETTINGS_TITLE,
    233                         getString(R.string.print_menu_item_settings));
    234                 extras.putString(EXTRA_SETTINGS_COMPONENT_NAME,
    235                         new ComponentName(installedService.serviceInfo.packageName,
    236                                 settingsClassName).flattenToString());
    237             }
    238 
    239             String addPrinterClassName = printServiceInfo.getAddPrintersActivityName();
    240             if (!TextUtils.isEmpty(addPrinterClassName)) {
    241                 extras.putString(EXTRA_ADD_PRINTERS_TITLE,
    242                         getString(R.string.print_menu_item_add_printers));
    243                 extras.putString(EXTRA_ADD_PRINTERS_COMPONENT_NAME,
    244                         new ComponentName(installedService.serviceInfo.packageName,
    245                                 addPrinterClassName).flattenToString());
    246             }
    247 
    248             extras.putString(EXTRA_SERVICE_COMPONENT_NAME, componentName.flattenToString());
    249 
    250             mPrintServicesCategory.addPreference(preference);
    251         }
    252 
    253         if (mPrintServicesCategory.getPreferenceCount() == 0) {
    254             getPreferenceScreen().removePreference(mPrintServicesCategory);
    255         }
    256     }
    257 
    258     private void startSubSettingsIfNeeded() {
    259         if (getArguments() == null) {
    260             return;
    261         }
    262         String componentName = getArguments().getString(EXTRA_PRINT_SERVICE_COMPONENT_NAME);
    263         if (componentName != null) {
    264             getArguments().remove(EXTRA_PRINT_SERVICE_COMPONENT_NAME);
    265             Preference prereference = findPreference(componentName);
    266             if (prereference != null) {
    267                 prereference.performClick(getPreferenceScreen());
    268             }
    269         }
    270     }
    271 
    272     private class SettingsPackageMonitor extends PackageMonitor {
    273         @Override
    274         public void onPackageAdded(String packageName, int uid) {
    275            mHandler.obtainMessage().sendToTarget();
    276         }
    277 
    278         @Override
    279         public void onPackageAppeared(String packageName, int reason) {
    280             mHandler.obtainMessage().sendToTarget();
    281         }
    282 
    283         @Override
    284         public void onPackageDisappeared(String packageName, int reason) {
    285             mHandler.obtainMessage().sendToTarget();
    286         }
    287 
    288         @Override
    289         public void onPackageRemoved(String packageName, int uid) {
    290             mHandler.obtainMessage().sendToTarget();
    291         }
    292     }
    293 
    294     public static class ToggleSwitch extends Switch {
    295 
    296         private OnBeforeCheckedChangeListener mOnBeforeListener;
    297 
    298         public static interface OnBeforeCheckedChangeListener {
    299             public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked);
    300         }
    301 
    302         public ToggleSwitch(Context context) {
    303             super(context);
    304         }
    305 
    306         public void setOnBeforeCheckedChangeListener(OnBeforeCheckedChangeListener listener) {
    307             mOnBeforeListener = listener;
    308         }
    309 
    310         @Override
    311         public void setChecked(boolean checked) {
    312             if (mOnBeforeListener != null
    313                     && mOnBeforeListener.onBeforeCheckedChanged(this, checked)) {
    314                 return;
    315             }
    316             super.setChecked(checked);
    317         }
    318 
    319         public void setCheckedInternal(boolean checked) {
    320             super.setChecked(checked);
    321         }
    322     }
    323 
    324     private static abstract class SettingsContentObserver extends ContentObserver {
    325 
    326         public SettingsContentObserver(Handler handler) {
    327             super(handler);
    328         }
    329 
    330         public void register(ContentResolver contentResolver) {
    331             contentResolver.registerContentObserver(Settings.Secure.getUriFor(
    332                     Settings.Secure.ENABLED_PRINT_SERVICES), false, this);
    333         }
    334 
    335         public void unregister(ContentResolver contentResolver) {
    336             contentResolver.unregisterContentObserver(this);
    337         }
    338 
    339         @Override
    340         public abstract void onChange(boolean selfChange, Uri uri);
    341     }
    342 
    343     private final class PrintJobsController implements LoaderCallbacks<List<PrintJobInfo>> {
    344 
    345         @Override
    346         public Loader<List<PrintJobInfo>> onCreateLoader(int id, Bundle args) {
    347             if (id == LOADER_ID_PRINT_JOBS_LOADER) {
    348                 return new PrintJobsLoader(getActivity());
    349             }
    350             return null;
    351         }
    352 
    353         @Override
    354         public void onLoadFinished(Loader<List<PrintJobInfo>> loader,
    355                 List<PrintJobInfo> printJobs) {
    356             if (printJobs == null || printJobs.isEmpty()) {
    357                 getPreferenceScreen().removePreference(mActivePrintJobsCategory);
    358             } else {
    359                 if (getPreferenceScreen().findPreference(PRINT_JOBS_CATEGORY) == null) {
    360                     getPreferenceScreen().addPreference(mActivePrintJobsCategory);
    361                 }
    362 
    363                 mActivePrintJobsCategory.removeAll();
    364 
    365                 final int printJobCount = printJobs.size();
    366                 for (int i = 0; i < printJobCount; i++) {
    367                     PrintJobInfo printJob = printJobs.get(i);
    368 
    369                     PreferenceScreen preference = getPreferenceManager()
    370                             .createPreferenceScreen(getActivity());
    371 
    372                     preference.setPersistent(false);
    373                     preference.setFragment(PrintJobSettingsFragment.class.getName());
    374                     preference.setKey(printJob.getId().flattenToString());
    375 
    376                     switch (printJob.getState()) {
    377                         case PrintJobInfo.STATE_QUEUED:
    378                         case PrintJobInfo.STATE_STARTED: {
    379                             if (!printJob.isCancelling()) {
    380                                 preference.setTitle(getString(
    381                                         R.string.print_printing_state_title_template,
    382                                         printJob.getLabel()));
    383                             } else {
    384                                 preference.setTitle(getString(
    385                                         R.string.print_cancelling_state_title_template,
    386                                         printJob.getLabel()));
    387                             }
    388                         } break;
    389 
    390                         case PrintJobInfo.STATE_FAILED: {
    391                             preference.setTitle(getString(
    392                                     R.string.print_failed_state_title_template,
    393                                     printJob.getLabel()));
    394                         } break;
    395 
    396                         case PrintJobInfo.STATE_BLOCKED: {
    397                             if (!printJob.isCancelling()) {
    398                                 preference.setTitle(getString(
    399                                         R.string.print_blocked_state_title_template,
    400                                         printJob.getLabel()));
    401                             } else {
    402                                 preference.setTitle(getString(
    403                                         R.string.print_cancelling_state_title_template,
    404                                         printJob.getLabel()));
    405                             }
    406                         } break;
    407                     }
    408 
    409                     preference.setSummary(getString(R.string.print_job_summary,
    410                             printJob.getPrinterName(), DateUtils.formatSameDayTime(
    411                                     printJob.getCreationTime(), printJob.getCreationTime(),
    412                                     DateFormat.SHORT, DateFormat.SHORT)));
    413 
    414                     switch (printJob.getState()) {
    415                         case PrintJobInfo.STATE_QUEUED:
    416                         case PrintJobInfo.STATE_STARTED: {
    417                             preference.setIcon(com.android.internal.R.drawable.ic_print);
    418                         } break;
    419 
    420                         case PrintJobInfo.STATE_FAILED:
    421                         case PrintJobInfo.STATE_BLOCKED: {
    422                             preference.setIcon(com.android.internal.R.drawable.ic_print_error);
    423                         } break;
    424                     }
    425 
    426                     Bundle extras = preference.getExtras();
    427                     extras.putString(EXTRA_PRINT_JOB_ID, printJob.getId().flattenToString());
    428 
    429                     mActivePrintJobsCategory.addPreference(preference);
    430                 }
    431             }
    432         }
    433 
    434         @Override
    435         public void onLoaderReset(Loader<List<PrintJobInfo>> loader) {
    436             getPreferenceScreen().removePreference(mActivePrintJobsCategory);
    437         }
    438     }
    439 
    440     private static final class PrintJobsLoader extends AsyncTaskLoader<List<PrintJobInfo>> {
    441 
    442         private static final String LOG_TAG = "PrintJobsLoader";
    443 
    444         private static final boolean DEBUG = false;
    445 
    446         private List <PrintJobInfo> mPrintJobs = new ArrayList<PrintJobInfo>();
    447 
    448         private final PrintManager mPrintManager;
    449 
    450         private PrintJobStateChangeListener mPrintJobStateChangeListener;
    451 
    452         public PrintJobsLoader(Context context) {
    453             super(context);
    454             mPrintManager = ((PrintManager) context.getSystemService(
    455                     Context.PRINT_SERVICE)).getGlobalPrintManagerForUser(
    456                             ActivityManager.getCurrentUser());
    457         }
    458 
    459         @Override
    460         public void deliverResult(List<PrintJobInfo> printJobs) {
    461             if (isStarted()) {
    462                 super.deliverResult(printJobs);
    463             }
    464         }
    465 
    466         @Override
    467         protected void onStartLoading() {
    468             if (DEBUG) {
    469                 Log.i(LOG_TAG, "onStartLoading()");
    470             }
    471             // If we already have a result, deliver it immediately.
    472             if (!mPrintJobs.isEmpty()) {
    473                 deliverResult(new ArrayList<PrintJobInfo>(mPrintJobs));
    474             }
    475             // Start watching for changes.
    476             if (mPrintJobStateChangeListener == null) {
    477                 mPrintJobStateChangeListener = new PrintJobStateChangeListener() {
    478                     @Override
    479                     public void onPrintJobStateChanged(PrintJobId printJobId) {
    480                         onForceLoad();
    481                     }
    482                 };
    483                 mPrintManager.addPrintJobStateChangeListener(
    484                         mPrintJobStateChangeListener);
    485             }
    486             // If the data changed or we have no data - load it now.
    487             if (mPrintJobs.isEmpty()) {
    488                 onForceLoad();
    489             }
    490         }
    491 
    492         @Override
    493         protected void onStopLoading() {
    494             if (DEBUG) {
    495                 Log.i(LOG_TAG, "onStopLoading()");
    496             }
    497             // Cancel the load in progress if possible.
    498             onCancelLoad();
    499         }
    500 
    501         @Override
    502         protected void onReset() {
    503             if (DEBUG) {
    504                 Log.i(LOG_TAG, "onReset()");
    505             }
    506             // Stop loading.
    507             onStopLoading();
    508             // Clear the cached result.
    509             mPrintJobs.clear();
    510             // Stop watching for changes.
    511             if (mPrintJobStateChangeListener != null) {
    512                 mPrintManager.removePrintJobStateChangeListener(
    513                         mPrintJobStateChangeListener);
    514                 mPrintJobStateChangeListener = null;
    515             }
    516         }
    517 
    518         @Override
    519         public List<PrintJobInfo> loadInBackground() {
    520             List<PrintJobInfo> printJobInfos = null;
    521             List<PrintJob> printJobs = mPrintManager.getPrintJobs();
    522             final int printJobCount = printJobs.size();
    523             for (int i = 0; i < printJobCount; i++) {
    524                 PrintJobInfo printJob = printJobs.get(i).getInfo();
    525                 if (shouldShowToUser(printJob)) {
    526                     if (printJobInfos == null) {
    527                         printJobInfos = new ArrayList<PrintJobInfo>();
    528                     }
    529                     printJobInfos.add(printJob);
    530                 }
    531             }
    532             return printJobInfos;
    533         }
    534 
    535         private static boolean shouldShowToUser(PrintJobInfo printJob) {
    536             switch (printJob.getState()) {
    537                 case PrintJobInfo.STATE_QUEUED:
    538                 case PrintJobInfo.STATE_STARTED:
    539                 case PrintJobInfo.STATE_BLOCKED:
    540                 case PrintJobInfo.STATE_FAILED: {
    541                     return true;
    542                 }
    543             }
    544             return false;
    545         }
    546     }
    547 }
    548