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