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