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