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