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 static com.android.settings.print.PrintSettingPreferenceController.shouldShowToUser; 20 21 import android.app.LoaderManager.LoaderCallbacks; 22 import android.content.ActivityNotFoundException; 23 import android.content.AsyncTaskLoader; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.Loader; 28 import android.content.pm.PackageManager; 29 import android.content.res.TypedArray; 30 import android.graphics.drawable.Drawable; 31 import android.net.Uri; 32 import android.os.Bundle; 33 import android.print.PrintJob; 34 import android.print.PrintJobId; 35 import android.print.PrintJobInfo; 36 import android.print.PrintManager; 37 import android.print.PrintManager.PrintJobStateChangeListener; 38 import android.print.PrintServicesLoader; 39 import android.printservice.PrintServiceInfo; 40 import android.provider.SearchIndexableResource; 41 import android.provider.Settings; 42 import android.support.v7.preference.Preference; 43 import android.support.v7.preference.PreferenceCategory; 44 import android.text.TextUtils; 45 import android.text.format.DateUtils; 46 import android.util.Log; 47 import android.view.LayoutInflater; 48 import android.view.View; 49 import android.view.View.OnClickListener; 50 import android.view.ViewGroup; 51 import android.widget.Button; 52 import android.widget.TextView; 53 54 import com.android.internal.logging.nano.MetricsProto.MetricsEvent; 55 import com.android.settings.R; 56 import com.android.settings.search.BaseSearchIndexProvider; 57 import com.android.settings.search.Indexable; 58 import com.android.settings.utils.ProfileSettingsPreferenceFragment; 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 ProfileSettingsPreferenceFragment 68 implements Indexable, OnClickListener { 69 public static final String TAG = "PrintSettingsFragment"; 70 private static final int LOADER_ID_PRINT_JOBS_LOADER = 1; 71 private static final int LOADER_ID_PRINT_SERVICES = 2; 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 static final String EXTRA_CHECKED = "EXTRA_CHECKED"; 77 static final String EXTRA_TITLE = "EXTRA_TITLE"; 78 static final String EXTRA_SERVICE_COMPONENT_NAME = "EXTRA_SERVICE_COMPONENT_NAME"; 79 80 static final String EXTRA_PRINT_JOB_ID = "EXTRA_PRINT_JOB_ID"; 81 82 private static final String EXTRA_PRINT_SERVICE_COMPONENT_NAME = 83 "EXTRA_PRINT_SERVICE_COMPONENT_NAME"; 84 85 private static final int ORDER_LAST = Preference.DEFAULT_ORDER - 1; 86 87 private PreferenceCategory mActivePrintJobsCategory; 88 private PreferenceCategory mPrintServicesCategory; 89 90 private PrintJobsController mPrintJobsController; 91 private PrintServicesController mPrintServicesController; 92 93 private Button mAddNewServiceButton; 94 95 @Override 96 public int getMetricsCategory() { 97 return MetricsEvent.PRINT_SETTINGS; 98 } 99 100 @Override 101 public int getHelpResource() { 102 return R.string.help_uri_printing; 103 } 104 105 @Override 106 public View onCreateView(LayoutInflater inflater, ViewGroup container, 107 Bundle savedInstanceState) { 108 View root = super.onCreateView(inflater, container, savedInstanceState); 109 addPreferencesFromResource(R.xml.print_settings); 110 111 mActivePrintJobsCategory = (PreferenceCategory) findPreference( 112 PRINT_JOBS_CATEGORY); 113 mPrintServicesCategory = (PreferenceCategory) findPreference( 114 PRINT_SERVICES_CATEGORY); 115 getPreferenceScreen().removePreference(mActivePrintJobsCategory); 116 117 mPrintJobsController = new PrintJobsController(); 118 getLoaderManager().initLoader(LOADER_ID_PRINT_JOBS_LOADER, null, mPrintJobsController); 119 120 mPrintServicesController = new PrintServicesController(); 121 getLoaderManager().initLoader(LOADER_ID_PRINT_SERVICES, null, mPrintServicesController); 122 123 return root; 124 } 125 126 @Override 127 public void onStart() { 128 super.onStart(); 129 setHasOptionsMenu(true); 130 startSubSettingsIfNeeded(); 131 } 132 133 @Override 134 public void onViewCreated(View view, Bundle savedInstanceState) { 135 super.onViewCreated(view, savedInstanceState); 136 ViewGroup contentRoot = (ViewGroup) getListView().getParent(); 137 View emptyView = getActivity().getLayoutInflater().inflate( 138 R.layout.empty_print_state, contentRoot, false); 139 TextView textView = (TextView) emptyView.findViewById(R.id.message); 140 textView.setText(R.string.print_no_services_installed); 141 142 final Intent addNewServiceIntent = createAddNewServiceIntentOrNull(); 143 if (addNewServiceIntent != null) { 144 mAddNewServiceButton = (Button) emptyView.findViewById(R.id.add_new_service); 145 mAddNewServiceButton.setOnClickListener(this); 146 // The empty is used elsewhere too so it's hidden by default. 147 mAddNewServiceButton.setVisibility(View.VISIBLE); 148 } 149 150 contentRoot.addView(emptyView); 151 setEmptyView(emptyView); 152 } 153 154 @Override 155 protected String getIntentActionString() { 156 return Settings.ACTION_PRINT_SETTINGS; 157 } 158 159 /** 160 * Adds preferences for all print services to the {@value PRINT_SERVICES_CATEGORY} cathegory. 161 */ 162 private final class PrintServicesController implements LoaderCallbacks<List<PrintServiceInfo>> { 163 @Override 164 public Loader<List<PrintServiceInfo>> onCreateLoader(int id, Bundle args) { 165 PrintManager printManager = 166 (PrintManager) getContext().getSystemService(Context.PRINT_SERVICE); 167 if (printManager != null) { 168 return new PrintServicesLoader(printManager, getContext(), 169 PrintManager.ALL_SERVICES); 170 } else { 171 return null; 172 } 173 } 174 175 @Override 176 public void onLoadFinished(Loader<List<PrintServiceInfo>> loader, 177 List<PrintServiceInfo> services) { 178 if (services.isEmpty()) { 179 getPreferenceScreen().removePreference(mPrintServicesCategory); 180 return; 181 } else if (getPreferenceScreen().findPreference(PRINT_SERVICES_CATEGORY) == null) { 182 getPreferenceScreen().addPreference(mPrintServicesCategory); 183 } 184 185 mPrintServicesCategory.removeAll(); 186 PackageManager pm = getActivity().getPackageManager(); 187 final Context context = getPrefContext(); 188 if (context == null) { 189 Log.w(TAG, "No preference context, skip adding print services"); 190 return; 191 } 192 193 for (PrintServiceInfo service : services) { 194 Preference preference = new Preference(context); 195 196 String title = service.getResolveInfo().loadLabel(pm).toString(); 197 preference.setTitle(title); 198 199 ComponentName componentName = service.getComponentName(); 200 preference.setKey(componentName.flattenToString()); 201 202 preference.setFragment(PrintServiceSettingsFragment.class.getName()); 203 preference.setPersistent(false); 204 205 if (service.isEnabled()) { 206 preference.setSummary(getString(R.string.print_feature_state_on)); 207 } else { 208 preference.setSummary(getString(R.string.print_feature_state_off)); 209 } 210 211 Drawable drawable = service.getResolveInfo().loadIcon(pm); 212 if (drawable != null) { 213 preference.setIcon(drawable); 214 } 215 216 Bundle extras = preference.getExtras(); 217 extras.putBoolean(EXTRA_CHECKED, service.isEnabled()); 218 extras.putString(EXTRA_TITLE, title); 219 extras.putString(EXTRA_SERVICE_COMPONENT_NAME, componentName.flattenToString()); 220 221 mPrintServicesCategory.addPreference(preference); 222 } 223 224 Preference addNewServicePreference = newAddServicePreferenceOrNull(); 225 if (addNewServicePreference != null) { 226 mPrintServicesCategory.addPreference(addNewServicePreference); 227 } 228 } 229 230 @Override 231 public void onLoaderReset(Loader<List<PrintServiceInfo>> loader) { 232 getPreferenceScreen().removePreference(mPrintServicesCategory); 233 } 234 } 235 236 private Preference newAddServicePreferenceOrNull() { 237 final Intent addNewServiceIntent = createAddNewServiceIntentOrNull(); 238 if (addNewServiceIntent == null) { 239 return null; 240 } 241 Preference preference = new Preference(getPrefContext()); 242 preference.setTitle(R.string.print_menu_item_add_service); 243 preference.setIcon(R.drawable.ic_menu_add); 244 preference.setOrder(ORDER_LAST); 245 preference.setIntent(addNewServiceIntent); 246 preference.setPersistent(false); 247 return preference; 248 } 249 250 private Intent createAddNewServiceIntentOrNull() { 251 final String searchUri = Settings.Secure.getString(getContentResolver(), 252 Settings.Secure.PRINT_SERVICE_SEARCH_URI); 253 if (TextUtils.isEmpty(searchUri)) { 254 return null; 255 } 256 return new Intent(Intent.ACTION_VIEW, Uri.parse(searchUri)); 257 } 258 259 private void startSubSettingsIfNeeded() { 260 if (getArguments() == null) { 261 return; 262 } 263 String componentName = getArguments().getString(EXTRA_PRINT_SERVICE_COMPONENT_NAME); 264 if (componentName != null) { 265 getArguments().remove(EXTRA_PRINT_SERVICE_COMPONENT_NAME); 266 Preference prereference = findPreference(componentName); 267 if (prereference != null) { 268 prereference.performClick(); 269 } 270 } 271 } 272 273 @Override 274 public void onClick(View v) { 275 if (mAddNewServiceButton == v) { 276 final Intent addNewServiceIntent = createAddNewServiceIntentOrNull(); 277 if (addNewServiceIntent != null) { // check again just in case. 278 try { 279 startActivity(addNewServiceIntent); 280 } catch (ActivityNotFoundException e) { 281 Log.w(TAG, "Unable to start activity", e); 282 } 283 } 284 } 285 } 286 287 private final class PrintJobsController implements LoaderCallbacks<List<PrintJobInfo>> { 288 289 @Override 290 public Loader<List<PrintJobInfo>> onCreateLoader(int id, Bundle args) { 291 if (id == LOADER_ID_PRINT_JOBS_LOADER) { 292 return new PrintJobsLoader(getContext()); 293 } 294 return null; 295 } 296 297 @Override 298 public void onLoadFinished(Loader<List<PrintJobInfo>> loader, 299 List<PrintJobInfo> printJobs) { 300 if (printJobs == null || printJobs.isEmpty()) { 301 getPreferenceScreen().removePreference(mActivePrintJobsCategory); 302 } else { 303 if (getPreferenceScreen().findPreference(PRINT_JOBS_CATEGORY) == null) { 304 getPreferenceScreen().addPreference(mActivePrintJobsCategory); 305 } 306 307 mActivePrintJobsCategory.removeAll(); 308 final Context context = getPrefContext(); 309 if (context == null) { 310 Log.w(TAG, "No preference context, skip adding print jobs"); 311 return; 312 } 313 314 for (PrintJobInfo printJob : printJobs) { 315 Preference preference = new Preference(context); 316 317 preference.setPersistent(false); 318 preference.setFragment(PrintJobSettingsFragment.class.getName()); 319 preference.setKey(printJob.getId().flattenToString()); 320 321 switch (printJob.getState()) { 322 case PrintJobInfo.STATE_QUEUED: 323 case PrintJobInfo.STATE_STARTED: 324 if (!printJob.isCancelling()) { 325 preference.setTitle(getString( 326 R.string.print_printing_state_title_template, 327 printJob.getLabel())); 328 } else { 329 preference.setTitle(getString( 330 R.string.print_cancelling_state_title_template, 331 printJob.getLabel())); 332 } 333 break; 334 case PrintJobInfo.STATE_FAILED: 335 preference.setTitle(getString( 336 R.string.print_failed_state_title_template, 337 printJob.getLabel())); 338 break; 339 case PrintJobInfo.STATE_BLOCKED: 340 if (!printJob.isCancelling()) { 341 preference.setTitle(getString( 342 R.string.print_blocked_state_title_template, 343 printJob.getLabel())); 344 } else { 345 preference.setTitle(getString( 346 R.string.print_cancelling_state_title_template, 347 printJob.getLabel())); 348 } 349 break; 350 } 351 352 preference.setSummary(getString(R.string.print_job_summary, 353 printJob.getPrinterName(), DateUtils.formatSameDayTime( 354 printJob.getCreationTime(), printJob.getCreationTime(), 355 DateFormat.SHORT, DateFormat.SHORT))); 356 357 TypedArray a = getActivity().obtainStyledAttributes(new int[] { 358 android.R.attr.colorControlNormal}); 359 int tintColor = a.getColor(0, 0); 360 a.recycle(); 361 362 switch (printJob.getState()) { 363 case PrintJobInfo.STATE_QUEUED: 364 case PrintJobInfo.STATE_STARTED: { 365 Drawable icon = getActivity().getDrawable( 366 com.android.internal.R.drawable.ic_print); 367 icon.setTint(tintColor); 368 preference.setIcon(icon); 369 break; 370 } 371 372 case PrintJobInfo.STATE_FAILED: 373 case PrintJobInfo.STATE_BLOCKED: { 374 Drawable icon = getActivity().getDrawable( 375 com.android.internal.R.drawable.ic_print_error); 376 icon.setTint(tintColor); 377 preference.setIcon(icon); 378 break; 379 } 380 } 381 382 Bundle extras = preference.getExtras(); 383 extras.putString(EXTRA_PRINT_JOB_ID, printJob.getId().flattenToString()); 384 385 mActivePrintJobsCategory.addPreference(preference); 386 } 387 } 388 } 389 390 @Override 391 public void onLoaderReset(Loader<List<PrintJobInfo>> loader) { 392 getPreferenceScreen().removePreference(mActivePrintJobsCategory); 393 } 394 } 395 396 private static final class PrintJobsLoader extends AsyncTaskLoader<List<PrintJobInfo>> { 397 398 private static final String LOG_TAG = "PrintJobsLoader"; 399 400 private static final boolean DEBUG = false; 401 402 private List<PrintJobInfo> mPrintJobs = new ArrayList<PrintJobInfo>(); 403 404 private final PrintManager mPrintManager; 405 406 private PrintJobStateChangeListener mPrintJobStateChangeListener; 407 408 public PrintJobsLoader(Context context) { 409 super(context); 410 mPrintManager = ((PrintManager) context.getSystemService( 411 Context.PRINT_SERVICE)).getGlobalPrintManagerForUser( 412 context.getUserId()); 413 } 414 415 @Override 416 public void deliverResult(List<PrintJobInfo> printJobs) { 417 if (isStarted()) { 418 super.deliverResult(printJobs); 419 } 420 } 421 422 @Override 423 protected void onStartLoading() { 424 if (DEBUG) { 425 Log.i(LOG_TAG, "onStartLoading()"); 426 } 427 // If we already have a result, deliver it immediately. 428 if (!mPrintJobs.isEmpty()) { 429 deliverResult(new ArrayList<PrintJobInfo>(mPrintJobs)); 430 } 431 // Start watching for changes. 432 if (mPrintJobStateChangeListener == null) { 433 mPrintJobStateChangeListener = new PrintJobStateChangeListener() { 434 @Override 435 public void onPrintJobStateChanged(PrintJobId printJobId) { 436 onForceLoad(); 437 } 438 }; 439 mPrintManager.addPrintJobStateChangeListener( 440 mPrintJobStateChangeListener); 441 } 442 // If the data changed or we have no data - load it now. 443 if (mPrintJobs.isEmpty()) { 444 onForceLoad(); 445 } 446 } 447 448 @Override 449 protected void onStopLoading() { 450 if (DEBUG) { 451 Log.i(LOG_TAG, "onStopLoading()"); 452 } 453 // Cancel the load in progress if possible. 454 onCancelLoad(); 455 } 456 457 @Override 458 protected void onReset() { 459 if (DEBUG) { 460 Log.i(LOG_TAG, "onReset()"); 461 } 462 // Stop loading. 463 onStopLoading(); 464 // Clear the cached result. 465 mPrintJobs.clear(); 466 // Stop watching for changes. 467 if (mPrintJobStateChangeListener != null) { 468 mPrintManager.removePrintJobStateChangeListener( 469 mPrintJobStateChangeListener); 470 mPrintJobStateChangeListener = null; 471 } 472 } 473 474 @Override 475 public List<PrintJobInfo> loadInBackground() { 476 List<PrintJobInfo> printJobInfos = null; 477 List<PrintJob> printJobs = mPrintManager.getPrintJobs(); 478 final int printJobCount = printJobs.size(); 479 for (int i = 0; i < printJobCount; i++) { 480 PrintJobInfo printJob = printJobs.get(i).getInfo(); 481 if (shouldShowToUser(printJob)) { 482 if (printJobInfos == null) { 483 printJobInfos = new ArrayList<>(); 484 } 485 printJobInfos.add(printJob); 486 } 487 } 488 return printJobInfos; 489 } 490 } 491 492 public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 493 new BaseSearchIndexProvider() { 494 495 @Override 496 public List<SearchIndexableResource> getXmlResourcesToIndex(Context context, 497 boolean enabled) { 498 List<SearchIndexableResource> indexables = new ArrayList<>(); 499 SearchIndexableResource indexable = new SearchIndexableResource(context); 500 indexable.xmlResId = R.xml.print_settings; 501 indexables.add(indexable); 502 return indexables; 503 } 504 }; 505 } 506