blob: 723bdc92858fb0ee58a550d2513b22bad290ea74 [file] [log] [blame]
Svetoslav Ganovd4c7b492013-06-11 21:15:11 -07001/*
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;
18
19import android.app.ActionBar;
20import android.app.Activity;
21import android.app.AlertDialog;
22import android.app.Dialog;
23import android.content.ComponentName;
24import android.content.ContentResolver;
25import android.content.Context;
26import android.content.DialogInterface;
27import android.content.Intent;
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.preference.Preference;
36import android.preference.PreferenceActivity;
37import android.preference.PreferenceCategory;
38import android.preference.PreferenceScreen;
39import android.printservice.PrintServiceInfo;
40import android.provider.Settings;
41import android.text.TextUtils;
42import android.text.TextUtils.SimpleStringSplitter;
43import android.view.Gravity;
44import android.view.Menu;
45import android.view.MenuInflater;
46import android.view.MenuItem;
47import android.view.View;
48import android.widget.Switch;
49import android.widget.TextView;
50
51import com.android.internal.content.PackageMonitor;
52import com.android.settings.PrintingSettings.ToggleSwitch.OnBeforeCheckedChangeListener;
53
54import java.util.ArrayList;
55import java.util.HashSet;
56import java.util.List;
57import java.util.Set;
58
59/**
60 * Activity with the printing settings.
61 */
62public class PrintingSettings extends SettingsPreferenceFragment implements DialogCreatable {
63
64 private static final char ENABLED_PRINT_SERVICES_SEPARATOR = ':';
65
66 // Preference categories
67 private static final String SERVICES_CATEGORY = "services_category";
68
69 // Extras passed to sub-fragments.
70 private static final String EXTRA_PREFERENCE_KEY = "preference_key";
71 private static final String EXTRA_CHECKED = "checked";
72 private static final String EXTRA_TITLE = "title";
73 private static final String EXTRA_ENABLE_WARNING_TITLE = "enable_warning_title";
74 private static final String EXTRA_ENABLE_WARNING_MESSAGE = "enable_warning_message";
75 private static final String EXTRA_SETTINGS_TITLE = "settings_title";
76 private static final String EXTRA_SETTINGS_COMPONENT_NAME = "settings_component_name";
77 private static final String EXTRA_ADD_PRINTERS_TITLE = "add_printers_title";
78 private static final String EXTRA_ADD_PRINTERS_COMPONENT_NAME = "add_printers_component_name";
79 private static final String EXTRA_SERVICE_COMPONENT_NAME = "service_component_name";
80
81 // Auxiliary members.
82 private final static SimpleStringSplitter sStringColonSplitter =
83 new SimpleStringSplitter(ENABLED_PRINT_SERVICES_SEPARATOR);
84
85 private static final List<ResolveInfo> sInstalledServicesList = new ArrayList<ResolveInfo>();
86
87 private static final Set<ComponentName> sEnabledServiceNameSet = new HashSet<ComponentName>();
88
89 private final PackageMonitor mSettingsPackageMonitor = new SettingsPackageMonitor();
90
91 private final Handler mHandler = new Handler() {
92 @Override
93 public void dispatchMessage(Message msg) {
94 super.dispatchMessage(msg);
95 loadInstalledServices(getActivity());
96 loadEnabledServices(getActivity());
97 updateServicesPreferences();
98 }
99 };
100
101 private final SettingsContentObserver mSettingsContentObserver =
102 new SettingsContentObserver(mHandler) {
103 @Override
104 public void onChange(boolean selfChange, Uri uri) {
105 loadInstalledServices(getActivity());
106 loadEnabledServices(getActivity());
107 updateServicesPreferences();
108 }
109 };
110
111 // Preference controls.
112 private PreferenceCategory mServicesCategory;
113
114 private Preference mNoServicesMessagePreference;
115
116 @Override
117 public void onCreate(Bundle icicle) {
118 super.onCreate(icicle);
119 addPreferencesFromResource(R.xml.print_settings);
120 mServicesCategory = (PreferenceCategory) findPreference(SERVICES_CATEGORY);
121 }
122
123 @Override
124 public void onResume() {
125 super.onResume();
126 mSettingsPackageMonitor.register(getActivity(), getActivity().getMainLooper(), false);
127 mSettingsContentObserver.register(getContentResolver());
128 loadInstalledServices(getActivity());
129 loadEnabledServices(getActivity());
130 updateServicesPreferences();
131 }
132
133 @Override
134 public void onPause() {
135 mSettingsPackageMonitor.unregister();
136 mSettingsContentObserver.unregister(getContentResolver());
137 super.onPause();
138 }
139
140 private void updateServicesPreferences() {
141 // Since services category is auto generated we have to do a pass
142 // to generate it since services can come and go.
143 mServicesCategory.removeAll();
144
145 final int installedServiceCount = sInstalledServicesList.size();
146 for (int i = 0; i < installedServiceCount; i++) {
147 ResolveInfo installedService = sInstalledServicesList.get(i);
148
149 PreferenceScreen preference = getPreferenceManager().createPreferenceScreen(
150 getActivity());
151
152 String title = installedService.loadLabel(getPackageManager()).toString();
153 preference.setTitle(title);
154
155 ComponentName componentName = new ComponentName(
156 installedService.serviceInfo.packageName,
157 installedService.serviceInfo.name);
158 preference.setKey(componentName.flattenToString());
159
160 final boolean serviceEnabled = sEnabledServiceNameSet.contains(componentName);
161 if (serviceEnabled) {
Svetoslav51a671c2013-06-24 17:49:38 -0700162 preference.setSummary(getString(R.string.print_feature_state_on));
Svetoslav Ganovd4c7b492013-06-11 21:15:11 -0700163 } else {
Svetoslav51a671c2013-06-24 17:49:38 -0700164 preference.setSummary(getString(R.string.print_feature_state_off));
Svetoslav Ganovd4c7b492013-06-11 21:15:11 -0700165 }
166
167 preference.setOrder(i);
168 preference.setFragment(TogglePrintServicePreferenceFragment.class.getName());
169 preference.setPersistent(false);
170
171 Bundle extras = preference.getExtras();
172 extras.putString(EXTRA_PREFERENCE_KEY, preference.getKey());
173 extras.putBoolean(EXTRA_CHECKED, serviceEnabled);
174 extras.putString(EXTRA_TITLE, title);
175
176 PrintServiceInfo printServiceInfo = PrintServiceInfo.create(
177 installedService, getActivity());
178
179 CharSequence applicationLabel = installedService.loadLabel(getPackageManager());
180
181 extras.putString(EXTRA_ENABLE_WARNING_TITLE, getString(
182 R.string.print_service_security_warning_title, applicationLabel));
183 extras.putString(EXTRA_ENABLE_WARNING_MESSAGE, getString(
184 R.string.print_service_security_warning_summary, applicationLabel));
185
186 String settingsClassName = printServiceInfo.getSettingsActivityName();
187 if (!TextUtils.isEmpty(settingsClassName)) {
188 extras.putString(EXTRA_SETTINGS_TITLE,
189 getString(R.string.print_menu_item_settings));
190 extras.putString(EXTRA_SETTINGS_COMPONENT_NAME,
191 new ComponentName(installedService.serviceInfo.packageName,
192 settingsClassName).flattenToString());
193 }
194
195 String addPrinterClassName = printServiceInfo.getAddPrintersActivityName();
196 if (!TextUtils.isEmpty(addPrinterClassName)) {
197 extras.putString(EXTRA_ADD_PRINTERS_TITLE,
198 getString(R.string.print_menu_item_add_printers));
199 extras.putString(EXTRA_ADD_PRINTERS_COMPONENT_NAME,
200 new ComponentName(installedService.serviceInfo.packageName,
201 addPrinterClassName).flattenToString());
202 }
203
204 extras.putString(EXTRA_SERVICE_COMPONENT_NAME, componentName.flattenToString());
205
206 mServicesCategory.addPreference(preference);
207 }
208
209 if (mServicesCategory.getPreferenceCount() == 0) {
210 if (mNoServicesMessagePreference == null) {
211 mNoServicesMessagePreference = new Preference(getActivity()) {
212 @Override
213 protected void onBindView(View view) {
214 super.onBindView(view);
215 TextView summaryView = (TextView) view.findViewById(R.id.summary);
216 String title = getString(R.string.print_no_services_installed);
217 summaryView.setText(title);
218 }
219 };
220 mNoServicesMessagePreference.setPersistent(false);
221 mNoServicesMessagePreference.setLayoutResource(
222 R.layout.text_description_preference);
223 mNoServicesMessagePreference.setSelectable(false);
224 }
225 mServicesCategory.addPreference(mNoServicesMessagePreference);
226 }
227 }
228
229 private static void loadInstalledServices(Context context) {
230 sInstalledServicesList.clear();
231 List<ResolveInfo> resolveInfos = context.getPackageManager().queryIntentServices(
232 new Intent(android.printservice.PrintService.SERVICE_INTERFACE),
233 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
234 final int resolveInfoCount = resolveInfos.size();
235 for (int i = 0, count = resolveInfoCount; i < count; i++) {
236 ResolveInfo resolveInfo = resolveInfos.get(i);
237 sInstalledServicesList.add(resolveInfo);
238 }
239 }
240
241 private static void loadEnabledServices(Context context) {
242 sEnabledServiceNameSet.clear();
243
244 String enabledServicesSetting = Settings.Secure.getString(context
245 .getContentResolver(), Settings.Secure.ENABLED_PRINT_SERVICES);
246 if (enabledServicesSetting == null) {
247 enabledServicesSetting = "";
248 }
249
250 SimpleStringSplitter colonSplitter = sStringColonSplitter;
251 colonSplitter.setString(enabledServicesSetting);
252
253 while (colonSplitter.hasNext()) {
254 String componentNameString = colonSplitter.next();
255 ComponentName enabledService = ComponentName.unflattenFromString(
256 componentNameString);
257 sEnabledServiceNameSet.add(enabledService);
258 }
259 }
260
261 private class SettingsPackageMonitor extends PackageMonitor {
262 @Override
263 public void onPackageAdded(String packageName, int uid) {
264 mHandler.obtainMessage().sendToTarget();
265 }
266
267 @Override
268 public void onPackageAppeared(String packageName, int reason) {
269 mHandler.obtainMessage().sendToTarget();
270 }
271
272 @Override
273 public void onPackageDisappeared(String packageName, int reason) {
274 mHandler.obtainMessage().sendToTarget();
275 }
276
277 @Override
278 public void onPackageRemoved(String packageName, int uid) {
279 mHandler.obtainMessage().sendToTarget();
280 }
281 }
282
283 public static class ToggleSwitch extends Switch {
284
285 private OnBeforeCheckedChangeListener mOnBeforeListener;
286
287 public static interface OnBeforeCheckedChangeListener {
288 public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked);
289 }
290
291 public ToggleSwitch(Context context) {
292 super(context);
293 }
294
295 public void setOnBeforeCheckedChangeListener(OnBeforeCheckedChangeListener listener) {
296 mOnBeforeListener = listener;
297 }
298
299 @Override
300 public void setChecked(boolean checked) {
301 if (mOnBeforeListener != null
302 && mOnBeforeListener.onBeforeCheckedChanged(this, checked)) {
303 return;
304 }
305 super.setChecked(checked);
306 }
307
308 public void setCheckedInternal(boolean checked) {
309 super.setChecked(checked);
310 }
311 }
312
313 public static class TogglePrintServicePreferenceFragment
314 extends ToggleFeaturePreferenceFragment implements DialogInterface.OnClickListener {
315
316 private static final int DIALOG_ID_ENABLE_WARNING = 1;
317
318 private final SettingsContentObserver mSettingsContentObserver =
319 new SettingsContentObserver(new Handler()) {
320 @Override
321 public void onChange(boolean selfChange, Uri uri) {
322 String settingValue = Settings.Secure.getString(getContentResolver(),
323 Settings.Secure.ENABLED_PRINT_SERVICES);
324 final boolean enabled = settingValue.contains(mComponentName);
325 mToggleSwitch.setCheckedInternal(enabled);
326 }
327 };
328
329 private CharSequence mEnableWarningTitle;
330 private CharSequence mEnableWarningMessage;
331
332 private String mComponentName;
333
334 @Override
335 public void onResume() {
336 mSettingsContentObserver.register(getContentResolver());
337 super.onResume();
338 }
339
340 @Override
341 public void onPause() {
342 mSettingsContentObserver.unregister(getContentResolver());
343 super.onPause();
344 }
345
346 @Override
347 public void onPreferenceToggled(String preferenceKey, boolean enabled) {
348 Set<ComponentName> enabledServices = sEnabledServiceNameSet;
349 ComponentName toggledService = ComponentName.unflattenFromString(preferenceKey);
350 if (enabled) {
351 enabledServices.add(toggledService);
352 } else {
353 enabledServices.remove(toggledService);
354 }
355 StringBuilder enabledServicesBuilder = new StringBuilder();
356 for (ComponentName enabledService : enabledServices) {
357 enabledServicesBuilder.append(enabledService.flattenToString());
358 enabledServicesBuilder.append(ENABLED_PRINT_SERVICES_SEPARATOR);
359 }
360 final int enabledServicesBuilderLength = enabledServicesBuilder.length();
361 if (enabledServicesBuilderLength > 0) {
362 enabledServicesBuilder.deleteCharAt(enabledServicesBuilderLength - 1);
363 }
364 Settings.Secure.putString(getContentResolver(),
365 Settings.Secure.ENABLED_PRINT_SERVICES,
366 enabledServicesBuilder.toString());
367 }
368
369 @Override
370 public Dialog onCreateDialog(int dialogId) {
371 CharSequence title = null;
372 CharSequence message = null;
373 switch (dialogId) {
374 case DIALOG_ID_ENABLE_WARNING:
375 title = mEnableWarningTitle;
376 message = mEnableWarningMessage;
377 break;
378 default:
379 throw new IllegalArgumentException();
380 }
381 return new AlertDialog.Builder(getActivity())
382 .setTitle(title)
383 .setIconAttribute(android.R.attr.alertDialogIcon)
384 .setMessage(message)
385 .setCancelable(true)
386 .setPositiveButton(android.R.string.ok, this)
387 .setNegativeButton(android.R.string.cancel, this)
388 .create();
389 }
390
391 @Override
392 public void onClick(DialogInterface dialog, int which) {
393 final boolean checked;
394 switch (which) {
395 case DialogInterface.BUTTON_POSITIVE:
396 checked = true;
397 mToggleSwitch.setCheckedInternal(checked);
398 getArguments().putBoolean(EXTRA_CHECKED, checked);
399 onPreferenceToggled(mPreferenceKey, checked);
400 break;
401 case DialogInterface.BUTTON_NEGATIVE:
402 checked = false;
403 mToggleSwitch.setCheckedInternal(checked);
404 getArguments().putBoolean(EXTRA_CHECKED, checked);
405 onPreferenceToggled(mPreferenceKey, checked);
406 break;
407 default:
408 throw new IllegalArgumentException();
409 }
410 }
411
412 @Override
413 protected void onInstallActionBarToggleSwitch() {
414 super.onInstallActionBarToggleSwitch();
415 mToggleSwitch.setOnBeforeCheckedChangeListener(new OnBeforeCheckedChangeListener() {
416 @Override
417 public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked) {
418 if (checked) {
419 if (!TextUtils.isEmpty(mEnableWarningMessage)) {
420 toggleSwitch.setCheckedInternal(false);
421 getArguments().putBoolean(EXTRA_CHECKED, false);
422 showDialog(DIALOG_ID_ENABLE_WARNING);
423 return true;
424 }
425 onPreferenceToggled(mPreferenceKey, true);
426 } else {
427 onPreferenceToggled(mPreferenceKey, false);
428 }
429 return false;
430 }
431 });
432 }
433
434 @Override
435 protected void onProcessArguments(Bundle arguments) {
436 super.onProcessArguments(arguments);
437 // Settings title and intent.
438 String settingsTitle = arguments.getString(EXTRA_SETTINGS_TITLE);
439 String settingsComponentName = arguments.getString(EXTRA_SETTINGS_COMPONENT_NAME);
440 if (!TextUtils.isEmpty(settingsTitle) && !TextUtils.isEmpty(settingsComponentName)) {
441 Intent settingsIntent = new Intent(Intent.ACTION_MAIN).setComponent(
442 ComponentName.unflattenFromString(settingsComponentName.toString()));
443 if (!getPackageManager().queryIntentActivities(settingsIntent, 0).isEmpty()) {
444 mSettingsTitle = settingsTitle;
445 mSettingsIntent = settingsIntent;
446 setHasOptionsMenu(true);
447 }
448 }
449 // Add printers title and intent.
450 String addPrintersTitle = arguments.getString(EXTRA_ADD_PRINTERS_TITLE);
451 String addPrintersComponentName =
452 arguments.getString(EXTRA_ADD_PRINTERS_COMPONENT_NAME);
453 if (!TextUtils.isEmpty(addPrintersTitle)
454 && !TextUtils.isEmpty(addPrintersComponentName)) {
455 Intent addPritnersIntent = new Intent(Intent.ACTION_MAIN).setComponent(
456 ComponentName.unflattenFromString(addPrintersComponentName.toString()));
457 if (!getPackageManager().queryIntentActivities(addPritnersIntent, 0).isEmpty()) {
458 mAddPrintersTitle = addPrintersTitle;
459 mAddPrintersIntent = addPritnersIntent;
460 setHasOptionsMenu(true);
461 }
462 }
463 // Enable warning title.
464 mEnableWarningTitle = arguments.getCharSequence(
465 PrintingSettings.EXTRA_ENABLE_WARNING_TITLE);
466 // Enable warning message.
467 mEnableWarningMessage = arguments.getCharSequence(
468 PrintingSettings.EXTRA_ENABLE_WARNING_MESSAGE);
469 // Component name.
470 mComponentName = arguments.getString(EXTRA_SERVICE_COMPONENT_NAME);
471 }
472 }
473
474 public static abstract class ToggleFeaturePreferenceFragment
475 extends SettingsPreferenceFragment {
476
477 protected ToggleSwitch mToggleSwitch;
478
479 protected String mPreferenceKey;
480
481 protected CharSequence mSettingsTitle;
482 protected Intent mSettingsIntent;
483
484 protected CharSequence mAddPrintersTitle;
485 protected Intent mAddPrintersIntent;
486
487 // TODO: Showing sub-sub fragment does not handle the activity title
488 // so we do it but this is wrong. Do a real fix when there is time.
489 private CharSequence mOldActivityTitle;
490
491 @Override
492 public void onCreate(Bundle savedInstanceState) {
493 super.onCreate(savedInstanceState);
494 PreferenceScreen preferenceScreen = getPreferenceManager().createPreferenceScreen(
495 getActivity());
496 setPreferenceScreen(preferenceScreen);
497 }
498
499 @Override
500 public void onViewCreated(View view, Bundle savedInstanceState) {
501 super.onViewCreated(view, savedInstanceState);
502 onInstallActionBarToggleSwitch();
503 onProcessArguments(getArguments());
504 getListView().setDivider(null);
505 getListView().setEnabled(false);
506 }
507
508 @Override
509 public void onDestroyView() {
510 getActivity().getActionBar().setCustomView(null);
511 if (mOldActivityTitle != null) {
512 getActivity().getActionBar().setTitle(mOldActivityTitle);
513 }
514 mToggleSwitch.setOnBeforeCheckedChangeListener(null);
515 super.onDestroyView();
516 }
517
518 protected abstract void onPreferenceToggled(String preferenceKey, boolean enabled);
519
520 @Override
521 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
522 super.onCreateOptionsMenu(menu, inflater);
523 if (!TextUtils.isEmpty(mSettingsTitle) && mSettingsIntent != null) {
524 MenuItem menuItem = menu.add(mSettingsTitle);
525 menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
526 menuItem.setIntent(mSettingsIntent);
527 }
528 if (!TextUtils.isEmpty(mAddPrintersTitle) && mAddPrintersIntent != null) {
529 MenuItem menuItem = menu.add(mAddPrintersTitle);
530 menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
531 menuItem.setIntent(mAddPrintersIntent);
532 }
533 }
534
535 protected void onInstallActionBarToggleSwitch() {
536 mToggleSwitch = createAndAddActionBarToggleSwitch(getActivity());
537 }
538
539 private ToggleSwitch createAndAddActionBarToggleSwitch(Activity activity) {
540 ToggleSwitch toggleSwitch = new ToggleSwitch(activity);
541 final int padding = activity.getResources().getDimensionPixelSize(
542 R.dimen.action_bar_switch_padding);
543 toggleSwitch.setPaddingRelative(0, 0, padding, 0);
544 activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
545 ActionBar.DISPLAY_SHOW_CUSTOM);
546 activity.getActionBar().setCustomView(toggleSwitch,
547 new ActionBar.LayoutParams(ActionBar.LayoutParams.WRAP_CONTENT,
548 ActionBar.LayoutParams.WRAP_CONTENT,
549 Gravity.CENTER_VERTICAL | Gravity.END));
550 return toggleSwitch;
551 }
552
553 protected void onProcessArguments(Bundle arguments) {
554 // Key.
555 mPreferenceKey = arguments.getString(EXTRA_PREFERENCE_KEY);
556 // Enabled.
557 final boolean enabled = arguments.getBoolean(EXTRA_CHECKED);
558 mToggleSwitch.setCheckedInternal(enabled);
559 // Title.
560 PreferenceActivity activity = (PreferenceActivity) getActivity();
561 if (!activity.onIsMultiPane() || activity.onIsHidingHeaders()) {
562 mOldActivityTitle = getActivity().getTitle();
563 String title = arguments.getString(EXTRA_TITLE);
564 getActivity().getActionBar().setTitle(title);
565 }
566 }
567 }
568
569 private static abstract class SettingsContentObserver extends ContentObserver {
570
571 public SettingsContentObserver(Handler handler) {
572 super(handler);
573 }
574
575 public void register(ContentResolver contentResolver) {
576 contentResolver.registerContentObserver(Settings.Secure.getUriFor(
577 Settings.Secure.ENABLED_PRINT_SERVICES), false, this);
578 }
579
580 public void unregister(ContentResolver contentResolver) {
581 contentResolver.unregisterContentObserver(this);
582 }
583
584 @Override
585 public abstract void onChange(boolean selfChange, Uri uri);
586 }
587}