Loading core/java/android/print/IPrintSpooler.aidl +1 −0 Original line number Diff line number Diff line Loading @@ -46,4 +46,5 @@ oneway interface IPrintSpooler { void writePrintJobData(in ParcelFileDescriptor fd, in PrintJobId printJobId); void setClient(IPrintSpoolerClient client); void setPrintJobCancelling(in PrintJobId printJobId, boolean cancelling); void removeApprovedPrintService(in ComponentName serviceToRemove); } packages/PrintSpooler/res/values/strings.xml +12 −0 Original line number Diff line number Diff line Loading @@ -187,6 +187,18 @@ <!-- Label for a printer that is not available. [CHAR LIMIT=25] --> <string name="printer_unavailable"><xliff:g id="print_job_name" example="Canon-123GHT">%1$s</xliff:g> – unavailable</string> <!-- Title for a warning message about security implications of using a print service, displayed as a dialog message when the user prints using a print service that has not been used before. [CHAR LIMIT=NONE] --> <string name="print_service_security_warning_title">Use <xliff:g id="service" example="My Print Service">%1$s</xliff:g>?</string> <!-- Summary for a warning message about security implications of using a print service, displayed as a dialog message when the user prints using a print service that has not been used before. [CHAR LIMIT=NONE] --> <string name="print_service_security_warning_summary">Your document may pass through one or more servers on its way to the printer.</string> <!-- Arrays --> <!-- Color mode labels. --> Loading packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java +19 −3 Original line number Diff line number Diff line Loading @@ -50,6 +50,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.os.HandlerCaller; import com.android.internal.util.FastXmlSerializer; import com.android.printspooler.R; import com.android.printspooler.util.ApprovedPrintServices; import libcore.io.IoUtils; Loading @@ -67,6 +68,7 @@ import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Set; /** * Service for exposing some of the {@link PrintSpooler} functionality to Loading Loading @@ -136,10 +138,10 @@ public final class PrintSpoolerService extends Service { @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { synchronized (mLock) { String prefix = (args.length > 0) ? args[0] : ""; String tab = " "; synchronized (mLock) { pw.append(prefix).append("print jobs:").println(); final int printJobCount = mPrintJobs.size(); for (int i = 0; i < printJobCount; i++) { Loading @@ -160,6 +162,14 @@ public final class PrintSpoolerService extends Service { } } } pw.append(prefix).append("approved print services:").println(); Set<String> approvedPrintServices = (new ApprovedPrintServices(this)).getApprovedServices(); if (approvedPrintServices != null) { for (String approvedService : approvedPrintServices) { pw.append(prefix).append(tab).append(approvedService).println(); } } } private void sendOnPrintJobQueued(PrintJobInfo printJob) { Loading Loading @@ -1307,6 +1317,12 @@ public final class PrintSpoolerService extends Service { PrintSpoolerService.this.setPrintJobCancelling(printJobId, cancelling); } @Override public void removeApprovedPrintService(ComponentName serviceToRemove) { (new ApprovedPrintServices(PrintSpoolerService.this)) .removeApprovedService(serviceToRemove); } public PrintSpoolerService getService() { return PrintSpoolerService.this; } Loading packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java +123 −1 Original line number Diff line number Diff line Loading @@ -17,14 +17,21 @@ package com.android.printspooler.ui; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; import android.app.Fragment; import android.app.FragmentTransaction; import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.res.Configuration; Loading Loading @@ -81,6 +88,7 @@ import com.android.printspooler.model.RemotePrintDocument; import com.android.printspooler.model.RemotePrintDocument.RemotePrintDocumentInfo; import com.android.printspooler.renderer.IPdfEditor; import com.android.printspooler.renderer.PdfManipulationService; import com.android.printspooler.util.ApprovedPrintServices; import com.android.printspooler.util.MediaSizeUtils; import com.android.printspooler.util.MediaSizeUtils.MediaSizeComparator; import com.android.printspooler.util.PageRangeUtils; Loading @@ -88,6 +96,7 @@ import com.android.printspooler.util.PrintOptionUtils; import com.android.printspooler.widget.PrintContentView; import com.android.printspooler.widget.PrintContentView.OptionsStateChangeListener; import com.android.printspooler.widget.PrintContentView.OptionsStateController; import libcore.io.IoUtils; import libcore.io.Streams; Loading Loading @@ -1184,12 +1193,125 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat mPrintButton.setOnClickListener(clickListener); } /** * A dialog that asks the user to approve a {@link PrintService}. This dialog is automatically * dismissed if the same {@link PrintService} gets approved by another * {@link PrintServiceApprovalDialog}. */ private static final class PrintServiceApprovalDialog extends DialogFragment implements OnSharedPreferenceChangeListener { private static final String PRINTSERVICE_KEY = "PRINTSERVICE"; private ApprovedPrintServices mApprovedServices; /** * Create a new {@link PrintServiceApprovalDialog} that ask the user to approve a * {@link PrintService}. * * @param printService The {@link ComponentName} of the service to approve * @return A new {@link PrintServiceApprovalDialog} that might approve the service */ static PrintServiceApprovalDialog newInstance(ComponentName printService) { PrintServiceApprovalDialog dialog = new PrintServiceApprovalDialog(); Bundle args = new Bundle(); args.putParcelable(PRINTSERVICE_KEY, printService); dialog.setArguments(args); return dialog; } @Override public void onStop() { super.onStop(); mApprovedServices.unregisterChangeListener(this); } @Override public void onStart() { super.onStart(); ComponentName printService = getArguments().getParcelable(PRINTSERVICE_KEY); synchronized (ApprovedPrintServices.sLock) { if (mApprovedServices.isApprovedService(printService)) { dismiss(); } else { mApprovedServices.registerChangeListenerLocked(this); } } } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { super.onCreateDialog(savedInstanceState); mApprovedServices = new ApprovedPrintServices(getActivity()); PackageManager packageManager = getActivity().getPackageManager(); CharSequence serviceLabel; try { ComponentName printService = getArguments().getParcelable(PRINTSERVICE_KEY); serviceLabel = packageManager.getApplicationInfo(printService.getPackageName(), 0) .loadLabel(packageManager); } catch (NameNotFoundException e) { serviceLabel = null; } AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle(getString(R.string.print_service_security_warning_title, serviceLabel)) .setMessage(getString(R.string.print_service_security_warning_summary, serviceLabel)) .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { ComponentName printService = getArguments().getParcelable(PRINTSERVICE_KEY); // Prevent onSharedPreferenceChanged from getting triggered mApprovedServices .unregisterChangeListener(PrintServiceApprovalDialog.this); mApprovedServices.addApprovedService(printService); ((PrintActivity) getActivity()).confirmPrint(); } }) .setNegativeButton(android.R.string.cancel, null); return builder.create(); } @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { ComponentName printService = getArguments().getParcelable(PRINTSERVICE_KEY); synchronized (ApprovedPrintServices.sLock) { if (mApprovedServices.isApprovedService(printService)) { dismiss(); } } } } private final class MyClickListener implements OnClickListener { @Override public void onClick(View view) { if (view == mPrintButton) { if (mCurrentPrinter != null) { if (mDestinationSpinnerAdapter.getPdfPrinter() == mCurrentPrinter) { confirmPrint(); } else { ApprovedPrintServices approvedServices = new ApprovedPrintServices(PrintActivity.this); ComponentName printService = mCurrentPrinter.getId().getServiceName(); if (approvedServices.isApprovedService(printService)) { confirmPrint(); } else { PrintServiceApprovalDialog.newInstance(printService) .show(getFragmentManager(), "approve"); } } } else { cancelPrint(); } Loading packages/PrintSpooler/src/com/android/printspooler/util/ApprovedPrintServices.java 0 → 100644 +156 −0 Original line number Diff line number Diff line /* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.printspooler.util; import android.content.ComponentName; import android.content.Context; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.printservice.PrintService; import android.util.ArraySet; import java.util.Set; /** * Manage approved print services. These services are stored in the shared preferences. */ public class ApprovedPrintServices { /** * Used for locking accesses to the approved services. */ static final public Object sLock = new Object(); private static final String APPROVED_SERVICES_PREFERENCE = "PRINT_SPOOLER_APPROVED_SERVICES"; private final SharedPreferences mPreferences; /** * Create a new {@link ApprovedPrintServices} * * @param owner The {@link Context} using this object. */ public ApprovedPrintServices(Context owner) { mPreferences = owner.getSharedPreferences(APPROVED_SERVICES_PREFERENCE, Context.MODE_PRIVATE); } /** * Get {@link Set} of approved services. * * @return A {@link Set} containing all currently approved services. */ public Set<String> getApprovedServices() { return mPreferences.getStringSet(APPROVED_SERVICES_PREFERENCE, null); } /** * Check if a {@link PrintService} is approved. * * This function does not acquire the {@link #sLock}. * * @param service The {@link ComponentName} of the {@link PrintService} that might be approved * @return true iff the service is currently approved */ public boolean isApprovedService(ComponentName service) { final Set<String> approvedServices = getApprovedServices(); if (approvedServices != null) { final String flattenedString = service.flattenToShortString(); for (String approvedService : approvedServices) { if (approvedService.equals(flattenedString)) { return true; } } } return false; } /** * Add a {@link PrintService} to the list of approved print services. * * @param serviceToAdd The {@link ComponentName} of the {@link PrintService} to be approved. */ public void addApprovedService(ComponentName serviceToAdd) { synchronized (sLock) { Set<String> oldApprovedServices = mPreferences.getStringSet(APPROVED_SERVICES_PREFERENCE, null); Set<String> newApprovedServices; if (oldApprovedServices == null) { newApprovedServices = new ArraySet<String>(1); } else { // Copy approved services. newApprovedServices = new ArraySet<String>(oldApprovedServices); } newApprovedServices.add(serviceToAdd.flattenToShortString()); SharedPreferences.Editor editor = mPreferences.edit(); editor.putStringSet(APPROVED_SERVICES_PREFERENCE, newApprovedServices); editor.apply(); } } /** * Add a {@link OnSharedPreferenceChangeListener} that listens for changes to the approved * services. Should only be called while holding {@link #sLock} to synchronize against * {@link #addApprovedService}. * * @param listener {@link OnSharedPreferenceChangeListener} to register */ public void registerChangeListenerLocked(OnSharedPreferenceChangeListener listener) { mPreferences.registerOnSharedPreferenceChangeListener(listener); } /** * Unregister a listener registered in {@link #registerChangeListenerLocked}. * * @param listener {@link OnSharedPreferenceChangeListener} to unregister */ public void unregisterChangeListener(OnSharedPreferenceChangeListener listener) { mPreferences.unregisterOnSharedPreferenceChangeListener(listener); } /** * If a {@link PrintService} is approved, remove it from the list of approved services. * * @param serviceToRemove The {@link ComponentName} of the {@link PrintService} to be removed */ public void removeApprovedService(ComponentName serviceToRemove) { synchronized (sLock) { if (isApprovedService(serviceToRemove)) { // Copy approved services. ArraySet<String> approvedServices = new ArraySet<String>( mPreferences.getStringSet(APPROVED_SERVICES_PREFERENCE, null)); SharedPreferences.Editor editor = mPreferences.edit(); final int numApprovedServices = approvedServices.size(); for (int i = 0; i < numApprovedServices; i++) { if (approvedServices.valueAt(i) .equals(serviceToRemove.flattenToShortString())) { approvedServices.removeAt(i); break; } } editor.putStringSet(APPROVED_SERVICES_PREFERENCE, approvedServices); editor.apply(); } } } } Loading
core/java/android/print/IPrintSpooler.aidl +1 −0 Original line number Diff line number Diff line Loading @@ -46,4 +46,5 @@ oneway interface IPrintSpooler { void writePrintJobData(in ParcelFileDescriptor fd, in PrintJobId printJobId); void setClient(IPrintSpoolerClient client); void setPrintJobCancelling(in PrintJobId printJobId, boolean cancelling); void removeApprovedPrintService(in ComponentName serviceToRemove); }
packages/PrintSpooler/res/values/strings.xml +12 −0 Original line number Diff line number Diff line Loading @@ -187,6 +187,18 @@ <!-- Label for a printer that is not available. [CHAR LIMIT=25] --> <string name="printer_unavailable"><xliff:g id="print_job_name" example="Canon-123GHT">%1$s</xliff:g> – unavailable</string> <!-- Title for a warning message about security implications of using a print service, displayed as a dialog message when the user prints using a print service that has not been used before. [CHAR LIMIT=NONE] --> <string name="print_service_security_warning_title">Use <xliff:g id="service" example="My Print Service">%1$s</xliff:g>?</string> <!-- Summary for a warning message about security implications of using a print service, displayed as a dialog message when the user prints using a print service that has not been used before. [CHAR LIMIT=NONE] --> <string name="print_service_security_warning_summary">Your document may pass through one or more servers on its way to the printer.</string> <!-- Arrays --> <!-- Color mode labels. --> Loading
packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java +19 −3 Original line number Diff line number Diff line Loading @@ -50,6 +50,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.os.HandlerCaller; import com.android.internal.util.FastXmlSerializer; import com.android.printspooler.R; import com.android.printspooler.util.ApprovedPrintServices; import libcore.io.IoUtils; Loading @@ -67,6 +68,7 @@ import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Set; /** * Service for exposing some of the {@link PrintSpooler} functionality to Loading Loading @@ -136,10 +138,10 @@ public final class PrintSpoolerService extends Service { @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { synchronized (mLock) { String prefix = (args.length > 0) ? args[0] : ""; String tab = " "; synchronized (mLock) { pw.append(prefix).append("print jobs:").println(); final int printJobCount = mPrintJobs.size(); for (int i = 0; i < printJobCount; i++) { Loading @@ -160,6 +162,14 @@ public final class PrintSpoolerService extends Service { } } } pw.append(prefix).append("approved print services:").println(); Set<String> approvedPrintServices = (new ApprovedPrintServices(this)).getApprovedServices(); if (approvedPrintServices != null) { for (String approvedService : approvedPrintServices) { pw.append(prefix).append(tab).append(approvedService).println(); } } } private void sendOnPrintJobQueued(PrintJobInfo printJob) { Loading Loading @@ -1307,6 +1317,12 @@ public final class PrintSpoolerService extends Service { PrintSpoolerService.this.setPrintJobCancelling(printJobId, cancelling); } @Override public void removeApprovedPrintService(ComponentName serviceToRemove) { (new ApprovedPrintServices(PrintSpoolerService.this)) .removeApprovedService(serviceToRemove); } public PrintSpoolerService getService() { return PrintSpoolerService.this; } Loading
packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java +123 −1 Original line number Diff line number Diff line Loading @@ -17,14 +17,21 @@ package com.android.printspooler.ui; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; import android.app.Fragment; import android.app.FragmentTransaction; import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.res.Configuration; Loading Loading @@ -81,6 +88,7 @@ import com.android.printspooler.model.RemotePrintDocument; import com.android.printspooler.model.RemotePrintDocument.RemotePrintDocumentInfo; import com.android.printspooler.renderer.IPdfEditor; import com.android.printspooler.renderer.PdfManipulationService; import com.android.printspooler.util.ApprovedPrintServices; import com.android.printspooler.util.MediaSizeUtils; import com.android.printspooler.util.MediaSizeUtils.MediaSizeComparator; import com.android.printspooler.util.PageRangeUtils; Loading @@ -88,6 +96,7 @@ import com.android.printspooler.util.PrintOptionUtils; import com.android.printspooler.widget.PrintContentView; import com.android.printspooler.widget.PrintContentView.OptionsStateChangeListener; import com.android.printspooler.widget.PrintContentView.OptionsStateController; import libcore.io.IoUtils; import libcore.io.Streams; Loading Loading @@ -1184,12 +1193,125 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat mPrintButton.setOnClickListener(clickListener); } /** * A dialog that asks the user to approve a {@link PrintService}. This dialog is automatically * dismissed if the same {@link PrintService} gets approved by another * {@link PrintServiceApprovalDialog}. */ private static final class PrintServiceApprovalDialog extends DialogFragment implements OnSharedPreferenceChangeListener { private static final String PRINTSERVICE_KEY = "PRINTSERVICE"; private ApprovedPrintServices mApprovedServices; /** * Create a new {@link PrintServiceApprovalDialog} that ask the user to approve a * {@link PrintService}. * * @param printService The {@link ComponentName} of the service to approve * @return A new {@link PrintServiceApprovalDialog} that might approve the service */ static PrintServiceApprovalDialog newInstance(ComponentName printService) { PrintServiceApprovalDialog dialog = new PrintServiceApprovalDialog(); Bundle args = new Bundle(); args.putParcelable(PRINTSERVICE_KEY, printService); dialog.setArguments(args); return dialog; } @Override public void onStop() { super.onStop(); mApprovedServices.unregisterChangeListener(this); } @Override public void onStart() { super.onStart(); ComponentName printService = getArguments().getParcelable(PRINTSERVICE_KEY); synchronized (ApprovedPrintServices.sLock) { if (mApprovedServices.isApprovedService(printService)) { dismiss(); } else { mApprovedServices.registerChangeListenerLocked(this); } } } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { super.onCreateDialog(savedInstanceState); mApprovedServices = new ApprovedPrintServices(getActivity()); PackageManager packageManager = getActivity().getPackageManager(); CharSequence serviceLabel; try { ComponentName printService = getArguments().getParcelable(PRINTSERVICE_KEY); serviceLabel = packageManager.getApplicationInfo(printService.getPackageName(), 0) .loadLabel(packageManager); } catch (NameNotFoundException e) { serviceLabel = null; } AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setTitle(getString(R.string.print_service_security_warning_title, serviceLabel)) .setMessage(getString(R.string.print_service_security_warning_summary, serviceLabel)) .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { ComponentName printService = getArguments().getParcelable(PRINTSERVICE_KEY); // Prevent onSharedPreferenceChanged from getting triggered mApprovedServices .unregisterChangeListener(PrintServiceApprovalDialog.this); mApprovedServices.addApprovedService(printService); ((PrintActivity) getActivity()).confirmPrint(); } }) .setNegativeButton(android.R.string.cancel, null); return builder.create(); } @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { ComponentName printService = getArguments().getParcelable(PRINTSERVICE_KEY); synchronized (ApprovedPrintServices.sLock) { if (mApprovedServices.isApprovedService(printService)) { dismiss(); } } } } private final class MyClickListener implements OnClickListener { @Override public void onClick(View view) { if (view == mPrintButton) { if (mCurrentPrinter != null) { if (mDestinationSpinnerAdapter.getPdfPrinter() == mCurrentPrinter) { confirmPrint(); } else { ApprovedPrintServices approvedServices = new ApprovedPrintServices(PrintActivity.this); ComponentName printService = mCurrentPrinter.getId().getServiceName(); if (approvedServices.isApprovedService(printService)) { confirmPrint(); } else { PrintServiceApprovalDialog.newInstance(printService) .show(getFragmentManager(), "approve"); } } } else { cancelPrint(); } Loading
packages/PrintSpooler/src/com/android/printspooler/util/ApprovedPrintServices.java 0 → 100644 +156 −0 Original line number Diff line number Diff line /* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.printspooler.util; import android.content.ComponentName; import android.content.Context; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.printservice.PrintService; import android.util.ArraySet; import java.util.Set; /** * Manage approved print services. These services are stored in the shared preferences. */ public class ApprovedPrintServices { /** * Used for locking accesses to the approved services. */ static final public Object sLock = new Object(); private static final String APPROVED_SERVICES_PREFERENCE = "PRINT_SPOOLER_APPROVED_SERVICES"; private final SharedPreferences mPreferences; /** * Create a new {@link ApprovedPrintServices} * * @param owner The {@link Context} using this object. */ public ApprovedPrintServices(Context owner) { mPreferences = owner.getSharedPreferences(APPROVED_SERVICES_PREFERENCE, Context.MODE_PRIVATE); } /** * Get {@link Set} of approved services. * * @return A {@link Set} containing all currently approved services. */ public Set<String> getApprovedServices() { return mPreferences.getStringSet(APPROVED_SERVICES_PREFERENCE, null); } /** * Check if a {@link PrintService} is approved. * * This function does not acquire the {@link #sLock}. * * @param service The {@link ComponentName} of the {@link PrintService} that might be approved * @return true iff the service is currently approved */ public boolean isApprovedService(ComponentName service) { final Set<String> approvedServices = getApprovedServices(); if (approvedServices != null) { final String flattenedString = service.flattenToShortString(); for (String approvedService : approvedServices) { if (approvedService.equals(flattenedString)) { return true; } } } return false; } /** * Add a {@link PrintService} to the list of approved print services. * * @param serviceToAdd The {@link ComponentName} of the {@link PrintService} to be approved. */ public void addApprovedService(ComponentName serviceToAdd) { synchronized (sLock) { Set<String> oldApprovedServices = mPreferences.getStringSet(APPROVED_SERVICES_PREFERENCE, null); Set<String> newApprovedServices; if (oldApprovedServices == null) { newApprovedServices = new ArraySet<String>(1); } else { // Copy approved services. newApprovedServices = new ArraySet<String>(oldApprovedServices); } newApprovedServices.add(serviceToAdd.flattenToShortString()); SharedPreferences.Editor editor = mPreferences.edit(); editor.putStringSet(APPROVED_SERVICES_PREFERENCE, newApprovedServices); editor.apply(); } } /** * Add a {@link OnSharedPreferenceChangeListener} that listens for changes to the approved * services. Should only be called while holding {@link #sLock} to synchronize against * {@link #addApprovedService}. * * @param listener {@link OnSharedPreferenceChangeListener} to register */ public void registerChangeListenerLocked(OnSharedPreferenceChangeListener listener) { mPreferences.registerOnSharedPreferenceChangeListener(listener); } /** * Unregister a listener registered in {@link #registerChangeListenerLocked}. * * @param listener {@link OnSharedPreferenceChangeListener} to unregister */ public void unregisterChangeListener(OnSharedPreferenceChangeListener listener) { mPreferences.unregisterOnSharedPreferenceChangeListener(listener); } /** * If a {@link PrintService} is approved, remove it from the list of approved services. * * @param serviceToRemove The {@link ComponentName} of the {@link PrintService} to be removed */ public void removeApprovedService(ComponentName serviceToRemove) { synchronized (sLock) { if (isApprovedService(serviceToRemove)) { // Copy approved services. ArraySet<String> approvedServices = new ArraySet<String>( mPreferences.getStringSet(APPROVED_SERVICES_PREFERENCE, null)); SharedPreferences.Editor editor = mPreferences.edit(); final int numApprovedServices = approvedServices.size(); for (int i = 0; i < numApprovedServices; i++) { if (approvedServices.valueAt(i) .equals(serviceToRemove.flattenToShortString())) { approvedServices.removeAt(i); break; } } editor.putStringSet(APPROVED_SERVICES_PREFERENCE, approvedServices); editor.apply(); } } } }