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