/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.print; import android.app.Activity; import android.app.LoaderManager.LoaderCallbacks; import android.content.ActivityNotFoundException; import android.content.AsyncTaskLoader; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.Loader; import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; import android.print.PrintJob; import android.print.PrintJobId; import android.print.PrintJobInfo; import android.print.PrintManager; import android.print.PrintManager.PrintJobStateChangeListener; import android.print.PrintServicesLoader; import android.printservice.PrintServiceInfo; import android.provider.SearchIndexableResource; import android.provider.Settings; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceCategory; import android.support.v7.preference.PreferenceScreen; import android.text.TextUtils; import android.text.format.DateUtils; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.Button; import android.widget.TextView; import com.android.internal.logging.MetricsProto.MetricsEvent; import com.android.settings.DialogCreatable; import com.android.settings.R; import com.android.settings.utils.ProfileSettingsPreferenceFragment; import com.android.settings.dashboard.SummaryLoader; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.Indexable; import com.android.settings.search.SearchIndexableRaw; import java.text.DateFormat; import java.util.ArrayList; import java.util.List; /** * Fragment with the top level print settings. */ public class PrintSettingsFragment extends ProfileSettingsPreferenceFragment implements DialogCreatable, Indexable, OnClickListener { public static final String TAG = "PrintSettingsFragment"; private static final int LOADER_ID_PRINT_JOBS_LOADER = 1; private static final int LOADER_ID_PRINT_SERVICES = 2; private static final String PRINT_JOBS_CATEGORY = "print_jobs_category"; private static final String PRINT_SERVICES_CATEGORY = "print_services_category"; static final String EXTRA_CHECKED = "EXTRA_CHECKED"; static final String EXTRA_TITLE = "EXTRA_TITLE"; static final String EXTRA_SERVICE_COMPONENT_NAME = "EXTRA_SERVICE_COMPONENT_NAME"; static final String EXTRA_PRINT_JOB_ID = "EXTRA_PRINT_JOB_ID"; private static final String EXTRA_PRINT_SERVICE_COMPONENT_NAME = "EXTRA_PRINT_SERVICE_COMPONENT_NAME"; private static final int ORDER_LAST = Preference.DEFAULT_ORDER - 1; private PreferenceCategory mActivePrintJobsCategory; private PreferenceCategory mPrintServicesCategory; private PrintJobsController mPrintJobsController; private PrintServicesController mPrintServicesController; private Button mAddNewServiceButton; @Override protected int getMetricsCategory() { return MetricsEvent.PRINT_SETTINGS; } @Override protected int getHelpResource() { return R.string.help_uri_printing; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View root = super.onCreateView(inflater, container, savedInstanceState); addPreferencesFromResource(R.xml.print_settings); mActivePrintJobsCategory = (PreferenceCategory) findPreference( PRINT_JOBS_CATEGORY); mPrintServicesCategory = (PreferenceCategory) findPreference( PRINT_SERVICES_CATEGORY); getPreferenceScreen().removePreference(mActivePrintJobsCategory); mPrintJobsController = new PrintJobsController(); getLoaderManager().initLoader(LOADER_ID_PRINT_JOBS_LOADER, null, mPrintJobsController); mPrintServicesController = new PrintServicesController(); getLoaderManager().initLoader(LOADER_ID_PRINT_SERVICES, null, mPrintServicesController); return root; } @Override public void onStart() { super.onStart(); setHasOptionsMenu(true); startSubSettingsIfNeeded(); } @Override public void onStop() { super.onStop(); } @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); ViewGroup contentRoot = (ViewGroup) getListView().getParent(); View emptyView = getActivity().getLayoutInflater().inflate( R.layout.empty_print_state, contentRoot, false); TextView textView = (TextView) emptyView.findViewById(R.id.message); textView.setText(R.string.print_no_services_installed); final Intent addNewServiceIntent = createAddNewServiceIntentOrNull(); if (addNewServiceIntent != null) { mAddNewServiceButton = (Button) emptyView.findViewById(R.id.add_new_service); mAddNewServiceButton.setOnClickListener(this); // The empty is used elsewhere too so it's hidden by default. mAddNewServiceButton.setVisibility(View.VISIBLE); } contentRoot.addView(emptyView); setEmptyView(emptyView); } @Override protected String getIntentActionString() { return Settings.ACTION_PRINT_SETTINGS; } /** * Adds preferences for all print services to the {@value PRINT_SERVICES_CATEGORY} cathegory. */ private final class PrintServicesController implements LoaderCallbacks<List<PrintServiceInfo>> { @Override public Loader<List<PrintServiceInfo>> onCreateLoader(int id, Bundle args) { PrintManager printManager = (PrintManager) getContext().getSystemService(Context.PRINT_SERVICE); if (printManager != null) { return new PrintServicesLoader(printManager, getContext(), PrintManager.ALL_SERVICES); } else { return null; } } @Override public void onLoadFinished(Loader<List<PrintServiceInfo>> loader, List<PrintServiceInfo> services) { if (services.isEmpty()) { getPreferenceScreen().removePreference(mPrintServicesCategory); return; } else if (getPreferenceScreen().findPreference(PRINT_SERVICES_CATEGORY) == null) { getPreferenceScreen().addPreference(mPrintServicesCategory); } mPrintServicesCategory.removeAll(); PackageManager pm = getActivity().getPackageManager(); final int numServices = services.size(); for (int i = 0; i < numServices; i++) { PrintServiceInfo service = services.get(i); PreferenceScreen preference = getPreferenceManager().createPreferenceScreen( getActivity()); String title = service.getResolveInfo().loadLabel(pm).toString(); preference.setTitle(title); ComponentName componentName = service.getComponentName(); preference.setKey(componentName.flattenToString()); preference.setFragment(PrintServiceSettingsFragment.class.getName()); preference.setPersistent(false); if (service.isEnabled()) { preference.setSummary(getString(R.string.print_feature_state_on)); } else { preference.setSummary(getString(R.string.print_feature_state_off)); } Drawable drawable = service.getResolveInfo().loadIcon(pm); if (drawable != null) { preference.setIcon(drawable); } Bundle extras = preference.getExtras(); extras.putBoolean(EXTRA_CHECKED, service.isEnabled()); extras.putString(EXTRA_TITLE, title); extras.putString(EXTRA_SERVICE_COMPONENT_NAME, componentName.flattenToString()); mPrintServicesCategory.addPreference(preference); } Preference addNewServicePreference = newAddServicePreferenceOrNull(); if (addNewServicePreference != null) { mPrintServicesCategory.addPreference(addNewServicePreference); } } @Override public void onLoaderReset(Loader<List<PrintServiceInfo>> loader) { getPreferenceScreen().removePreference(mPrintServicesCategory); } } private Preference newAddServicePreferenceOrNull() { final Intent addNewServiceIntent = createAddNewServiceIntentOrNull(); if (addNewServiceIntent == null) { return null; } Preference preference = new Preference(getPrefContext()); preference.setTitle(R.string.print_menu_item_add_service); preference.setIcon(R.drawable.ic_menu_add); preference.setOrder(ORDER_LAST); preference.setIntent(addNewServiceIntent); preference.setPersistent(false); return preference; } private Intent createAddNewServiceIntentOrNull() { final String searchUri = Settings.Secure.getString(getContentResolver(), Settings.Secure.PRINT_SERVICE_SEARCH_URI); if (TextUtils.isEmpty(searchUri)) { return null; } return new Intent(Intent.ACTION_VIEW, Uri.parse(searchUri)); } private void startSubSettingsIfNeeded() { if (getArguments() == null) { return; } String componentName = getArguments().getString(EXTRA_PRINT_SERVICE_COMPONENT_NAME); if (componentName != null) { getArguments().remove(EXTRA_PRINT_SERVICE_COMPONENT_NAME); Preference prereference = findPreference(componentName); if (prereference != null) { prereference.performClick(); } } } @Override public void onClick(View v) { if (mAddNewServiceButton == v) { final Intent addNewServiceIntent = createAddNewServiceIntentOrNull(); if (addNewServiceIntent != null) { // check again just in case. try { startActivity(addNewServiceIntent); } catch (ActivityNotFoundException e) { Log.w(TAG, "Unable to start activity", e); } } } } private final class PrintJobsController implements LoaderCallbacks<List<PrintJobInfo>> { @Override public Loader<List<PrintJobInfo>> onCreateLoader(int id, Bundle args) { if (id == LOADER_ID_PRINT_JOBS_LOADER) { return new PrintJobsLoader(getContext()); } return null; } @Override public void onLoadFinished(Loader<List<PrintJobInfo>> loader, List<PrintJobInfo> printJobs) { if (printJobs == null || printJobs.isEmpty()) { getPreferenceScreen().removePreference(mActivePrintJobsCategory); } else { if (getPreferenceScreen().findPreference(PRINT_JOBS_CATEGORY) == null) { getPreferenceScreen().addPreference(mActivePrintJobsCategory); } mActivePrintJobsCategory.removeAll(); final int printJobCount = printJobs.size(); for (int i = 0; i < printJobCount; i++) { PrintJobInfo printJob = printJobs.get(i); PreferenceScreen preference = getPreferenceManager() .createPreferenceScreen(getActivity()); preference.setPersistent(false); preference.setFragment(PrintJobSettingsFragment.class.getName()); preference.setKey(printJob.getId().flattenToString()); switch (printJob.getState()) { case PrintJobInfo.STATE_QUEUED: case PrintJobInfo.STATE_STARTED: { if (!printJob.isCancelling()) { preference.setTitle(getString( R.string.print_printing_state_title_template, printJob.getLabel())); } else { preference.setTitle(getString( R.string.print_cancelling_state_title_template, printJob.getLabel())); } } break; case PrintJobInfo.STATE_FAILED: { preference.setTitle(getString( R.string.print_failed_state_title_template, printJob.getLabel())); } break; case PrintJobInfo.STATE_BLOCKED: { if (!printJob.isCancelling()) { preference.setTitle(getString( R.string.print_blocked_state_title_template, printJob.getLabel())); } else { preference.setTitle(getString( R.string.print_cancelling_state_title_template, printJob.getLabel())); } } break; } preference.setSummary(getString(R.string.print_job_summary, printJob.getPrinterName(), DateUtils.formatSameDayTime( printJob.getCreationTime(), printJob.getCreationTime(), DateFormat.SHORT, DateFormat.SHORT))); switch (printJob.getState()) { case PrintJobInfo.STATE_QUEUED: case PrintJobInfo.STATE_STARTED: { preference.setIcon(R.drawable.ic_print); } break; case PrintJobInfo.STATE_FAILED: case PrintJobInfo.STATE_BLOCKED: { preference.setIcon(R.drawable.ic_print_error); } break; } Bundle extras = preference.getExtras(); extras.putString(EXTRA_PRINT_JOB_ID, printJob.getId().flattenToString()); mActivePrintJobsCategory.addPreference(preference); } } } @Override public void onLoaderReset(Loader<List<PrintJobInfo>> loader) { getPreferenceScreen().removePreference(mActivePrintJobsCategory); } } private static final class PrintJobsLoader extends AsyncTaskLoader<List<PrintJobInfo>> { private static final String LOG_TAG = "PrintJobsLoader"; private static final boolean DEBUG = false; private List<PrintJobInfo> mPrintJobs = new ArrayList<PrintJobInfo>(); private final PrintManager mPrintManager; private PrintJobStateChangeListener mPrintJobStateChangeListener; public PrintJobsLoader(Context context) { super(context); mPrintManager = ((PrintManager) context.getSystemService( Context.PRINT_SERVICE)).getGlobalPrintManagerForUser( context.getUserId()); } @Override public void deliverResult(List<PrintJobInfo> printJobs) { if (isStarted()) { super.deliverResult(printJobs); } } @Override protected void onStartLoading() { if (DEBUG) { Log.i(LOG_TAG, "onStartLoading()"); } // If we already have a result, deliver it immediately. if (!mPrintJobs.isEmpty()) { deliverResult(new ArrayList<PrintJobInfo>(mPrintJobs)); } // Start watching for changes. if (mPrintJobStateChangeListener == null) { mPrintJobStateChangeListener = new PrintJobStateChangeListener() { @Override public void onPrintJobStateChanged(PrintJobId printJobId) { onForceLoad(); } }; mPrintManager.addPrintJobStateChangeListener( mPrintJobStateChangeListener); } // If the data changed or we have no data - load it now. if (mPrintJobs.isEmpty()) { onForceLoad(); } } @Override protected void onStopLoading() { if (DEBUG) { Log.i(LOG_TAG, "onStopLoading()"); } // Cancel the load in progress if possible. onCancelLoad(); } @Override protected void onReset() { if (DEBUG) { Log.i(LOG_TAG, "onReset()"); } // Stop loading. onStopLoading(); // Clear the cached result. mPrintJobs.clear(); // Stop watching for changes. if (mPrintJobStateChangeListener != null) { mPrintManager.removePrintJobStateChangeListener( mPrintJobStateChangeListener); mPrintJobStateChangeListener = null; } } @Override public List<PrintJobInfo> loadInBackground() { List<PrintJobInfo> printJobInfos = null; List<PrintJob> printJobs = mPrintManager.getPrintJobs(); final int printJobCount = printJobs.size(); for (int i = 0; i < printJobCount; i++) { PrintJobInfo printJob = printJobs.get(i).getInfo(); if (shouldShowToUser(printJob)) { if (printJobInfos == null) { printJobInfos = new ArrayList<PrintJobInfo>(); } printJobInfos.add(printJob); } } return printJobInfos; } } /** * Should the print job the shown to the user in the settings app. * * @param printJob The print job in question. * @return true iff the print job should be shown. */ private static boolean shouldShowToUser(PrintJobInfo printJob) { switch (printJob.getState()) { case PrintJobInfo.STATE_QUEUED: case PrintJobInfo.STATE_STARTED: case PrintJobInfo.STATE_BLOCKED: case PrintJobInfo.STATE_FAILED: { return true; } } return false; } /** * Provider for the print settings summary */ private static class PrintSummaryProvider implements SummaryLoader.SummaryProvider, PrintJobStateChangeListener { private final Context mContext; private final PrintManager mPrintManager; private final SummaryLoader mSummaryLoader; /** * Create a new {@link PrintSummaryProvider}. * * @param context The context this provider is for * @param summaryLoader The summary load using this provider */ public PrintSummaryProvider(Context context, SummaryLoader summaryLoader) { mContext = context; mSummaryLoader = summaryLoader; mPrintManager = ((PrintManager) context.getSystemService(Context.PRINT_SERVICE)) .getGlobalPrintManagerForUser(context.getUserId()); } @Override public void setListening(boolean isListening) { if (mPrintManager != null) { if (isListening) { mPrintManager.addPrintJobStateChangeListener(this); onPrintJobStateChanged(null); } else { mPrintManager.removePrintJobStateChangeListener(this); } } } @Override public void onPrintJobStateChanged(PrintJobId printJobId) { List<PrintJob> printJobs = mPrintManager.getPrintJobs(); int numActivePrintJobs = 0; final int numPrintJobs = printJobs.size(); for (int i = 0; i < numPrintJobs; i++) { if (shouldShowToUser(printJobs.get(i).getInfo())) { numActivePrintJobs++; } } mSummaryLoader.setSummary(this, mContext.getResources().getQuantityString( R.plurals.print_settings_title, numActivePrintJobs, numActivePrintJobs)); } } /** * A factory for {@link PrintSummaryProvider providers} the settings app can use to read the * print summary. */ public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY = new SummaryLoader.SummaryProviderFactory() { @Override public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity, SummaryLoader summaryLoader) { return new PrintSummaryProvider(activity, summaryLoader); } }; public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new BaseSearchIndexProvider() { @Override public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) { List<SearchIndexableRaw> indexables = new ArrayList<SearchIndexableRaw>(); PackageManager packageManager = context.getPackageManager(); PrintManager printManager = (PrintManager) context.getSystemService( Context.PRINT_SERVICE); String screenTitle = context.getResources().getString(R.string.print_settings); SearchIndexableRaw data = new SearchIndexableRaw(context); data.title = screenTitle; data.screenTitle = screenTitle; indexables.add(data); // Indexing all services, regardless if enabled. Please note that the index will not be // updated until this function is called again List<PrintServiceInfo> services = printManager.getPrintServices(PrintManager.ALL_SERVICES); if (services != null) { final int serviceCount = services.size(); for (int i = 0; i < serviceCount; i++) { PrintServiceInfo service = services.get(i); ComponentName componentName = new ComponentName( service.getResolveInfo().serviceInfo.packageName, service.getResolveInfo().serviceInfo.name); data = new SearchIndexableRaw(context); data.key = componentName.flattenToString(); data.title = service.getResolveInfo().loadLabel(packageManager).toString(); data.summaryOn = context.getString(R.string.print_feature_state_on); data.summaryOff = context.getString(R.string.print_feature_state_off); data.screenTitle = screenTitle; indexables.add(data); } } return indexables; } @Override public List<SearchIndexableResource> getXmlResourcesToIndex(Context context, boolean enabled) { List<SearchIndexableResource> indexables = new ArrayList<SearchIndexableResource>(); SearchIndexableResource indexable = new SearchIndexableResource(context); indexable.xmlResId = R.xml.print_settings; indexables.add(indexable); return indexables; } }; }