diff options
| author | 2016-03-07 18:14:12 +0000 | |
|---|---|---|
| committer | 2016-03-07 18:14:14 +0000 | |
| commit | 02a465ace7063cc271f5565f78857dc22a14ca56 (patch) | |
| tree | 5275e50b45c91fd893b1f0decc616c51b2b47cfa | |
| parent | 64177e51e4148e13f8ba45c025316660aff7f79c (diff) | |
| parent | 66c96591e2ddb464c67e60dbf4193ef4ec8a620b (diff) | |
Merge "Add "app printer activity" and always keep the print service state updated. Also fiddle with the UI to use more standard values." into nyc-dev
39 files changed, 2005 insertions, 510 deletions
diff --git a/Android.mk b/Android.mk index 3ac5889f61c3..e39ff3bbb3c0 100644 --- a/Android.mk +++ b/Android.mk @@ -246,6 +246,7 @@ LOCAL_SRC_FILES += \ core/java/android/print/IPrintDocumentAdapter.aidl \ core/java/android/print/IPrintDocumentAdapterObserver.aidl \ core/java/android/print/IPrintJobStateChangeListener.aidl \ + core/java/android/print/IPrintServicesChangeListener.aidl \ core/java/android/print/IPrintManager.aidl \ core/java/android/print/IPrintSpooler.aidl \ core/java/android/print/IPrintSpoolerCallbacks.aidl \ diff --git a/core/java/android/print/IPrintManager.aidl b/core/java/android/print/IPrintManager.aidl index 9a80e375ceb2..5eb8cc2f37a4 100644 --- a/core/java/android/print/IPrintManager.aidl +++ b/core/java/android/print/IPrintManager.aidl @@ -16,12 +16,14 @@ package android.print; +import android.content.ComponentName; import android.graphics.drawable.Icon; import android.os.Bundle; import android.print.IPrinterDiscoveryObserver; import android.print.IPrintDocumentAdapter; import android.print.PrintJobId; import android.print.IPrintJobStateChangeListener; +import android.print.IPrintServicesChangeListener; import android.print.PrinterId; import android.print.PrintJobInfo; import android.print.PrintAttributes; @@ -45,8 +47,47 @@ interface IPrintManager { void removePrintJobStateChangeListener(in IPrintJobStateChangeListener listener, int userId); - List<PrintServiceInfo> getInstalledPrintServices(int userId); - List<PrintServiceInfo> getEnabledPrintServices(int userId); + /** + * Listen for changes to the installed and enabled print services. + * + * @param listener the listener to add + * @param userId the id of the user listening + * + * @see android.print.PrintManager#getPrintServices(int, String) + */ + void addPrintServicesChangeListener(in IPrintServicesChangeListener listener, + int userId); + + /** + * Stop listening for changes to the installed and enabled print services. + * + * @param listener the listener to remove + * @param userId the id of the user requesting the removal + * + * @see android.print.PrintManager#getPrintServices(int, String) + */ + void removePrintServicesChangeListener(in IPrintServicesChangeListener listener, + int userId); + + /** + * Get the print services. + * + * @param selectionFlags flags selecting which services to get + * @param selectedService if not null, the id of the print service to get + * @param userId the id of the user requesting the services + * + * @return the list of selected print services. + */ + List<PrintServiceInfo> getPrintServices(int selectionFlags, int userId); + + /** + * Enable or disable a print service. + * + * @param service The service to enabled or disable + * @param isEnabled whether the service should be enabled or disabled + * @param userId the id of the user requesting the services + */ + void setPrintServiceEnabled(in ComponentName service, boolean isEnabled, int userId); void createPrinterDiscoverySession(in IPrinterDiscoveryObserver observer, int userId); void startPrinterDiscovery(in IPrinterDiscoveryObserver observer, diff --git a/core/java/android/print/IPrintServicesChangeListener.aidl b/core/java/android/print/IPrintServicesChangeListener.aidl new file mode 100644 index 000000000000..2a7ce89af98e --- /dev/null +++ b/core/java/android/print/IPrintServicesChangeListener.aidl @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2016 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 android.print; + +/** + * Interface for observing print services changes. + * + * @hide + */ +oneway interface IPrintServicesChangeListener { + void onPrintServicesChanged(); +} diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java index 0540036f1a49..25fc968fec5a 100644 --- a/core/java/android/print/PrintManager.java +++ b/core/java/android/print/PrintManager.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Activity; import android.app.Application.ActivityLifecycleCallbacks; +import android.content.ComponentName; import android.content.Context; import android.content.IntentSender; import android.content.IntentSender.SendIntentException; @@ -111,6 +112,38 @@ public final class PrintManager { private static final boolean DEBUG = false; private static final int MSG_NOTIFY_PRINT_JOB_STATE_CHANGED = 1; + private static final int MSG_NOTIFY_PRINT_SERVICES_CHANGED = 2; + + /** + * Package name of print spooler. + * + * @hide + */ + public static final String PRINT_SPOOLER_PACKAGE_NAME = "com.android.printspooler"; + + /** + * Select enabled services. + * </p> + * @see #getPrintServices + * @hide + */ + public static final int ENABLED_SERVICES = 1 << 0; + + /** + * Select disabled services. + * </p> + * @see #getPrintServices + * @hide + */ + public static final int DISABLED_SERVICES = 1 << 1; + + /** + * Select all services. + * </p> + * @see #getPrintServices + * @hide + */ + public static final int ALL_SERVICES = ENABLED_SERVICES | DISABLED_SERVICES; /** * The action for launching the print dialog activity. @@ -165,7 +198,10 @@ public final class PrintManager { private final Handler mHandler; - private Map<PrintJobStateChangeListener, PrintJobStateChangeListenerWrapper> mPrintJobStateChangeListeners; + private Map<PrintJobStateChangeListener, PrintJobStateChangeListenerWrapper> + mPrintJobStateChangeListeners; + private Map<PrintServicesChangeListener, PrintServicesChangeListenerWrapper> + mPrintServicesChangeListeners; /** @hide */ public interface PrintJobStateChangeListener { @@ -178,6 +214,15 @@ public final class PrintManager { public void onPrintJobStateChanged(PrintJobId printJobId); } + /** @hide */ + public interface PrintServicesChangeListener { + + /** + * Callback notifying that the print services changed. + */ + public void onPrintServicesChanged(); + } + /** * Creates a new instance. * @@ -207,6 +252,15 @@ public final class PrintManager { } args.recycle(); } break; + case MSG_NOTIFY_PRINT_SERVICES_CHANGED: { + PrintServicesChangeListenerWrapper wrapper = + (PrintServicesChangeListenerWrapper) message.obj; + PrintServicesChangeListener listener = wrapper.getListener(); + if (listener != null) { + listener.onPrintServicesChanged(); + } + } break; + } } }; @@ -478,42 +532,82 @@ public final class PrintManager { } /** - * Gets the list of enabled print services. + * Listen for changes to the installed and enabled print services. * - * @return The enabled service list or an empty list. - * @hide + * @param listener the listener to add + * + * @see android.print.PrintManager#getPrintServices */ - public List<PrintServiceInfo> getEnabledPrintServices() { + void addPrintServicesChangeListener(@NonNull PrintServicesChangeListener listener) { if (mService == null) { Log.w(LOG_TAG, "Feature android.software.print not available"); - return Collections.emptyList(); + return; } + if (mPrintServicesChangeListeners == null) { + mPrintServicesChangeListeners = new ArrayMap<PrintServicesChangeListener, + PrintServicesChangeListenerWrapper>(); + } + PrintServicesChangeListenerWrapper wrappedListener = + new PrintServicesChangeListenerWrapper(listener, mHandler); try { - List<PrintServiceInfo> enabledServices = mService.getEnabledPrintServices(mUserId); - if (enabledServices != null) { - return enabledServices; - } + mService.addPrintServicesChangeListener(wrappedListener, mUserId); + mPrintServicesChangeListeners.put(listener, wrappedListener); } catch (RemoteException re) { throw re.rethrowFromSystemServer(); } - return Collections.emptyList(); } /** - * Gets the list of installed print services. + * Stop listening for changes to the installed and enabled print services. * - * @return The installed service list or an empty list. - * @hide + * @param listener the listener to remove + * + * @see android.print.PrintManager#getPrintServices */ - public List<PrintServiceInfo> getInstalledPrintServices() { + void removePrintServicesChangeListener(@NonNull PrintServicesChangeListener listener) { if (mService == null) { Log.w(LOG_TAG, "Feature android.software.print not available"); - return Collections.emptyList(); + return; + } + if (mPrintServicesChangeListeners == null) { + return; } + PrintServicesChangeListenerWrapper wrappedListener = + mPrintServicesChangeListeners.remove(listener); + if (wrappedListener == null) { + return; + } + if (mPrintServicesChangeListeners.isEmpty()) { + mPrintServicesChangeListeners = null; + } + wrappedListener.destroy(); try { - List<PrintServiceInfo> installedServices = mService.getInstalledPrintServices(mUserId); - if (installedServices != null) { - return installedServices; + mService.removePrintServicesChangeListener(wrappedListener, mUserId); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error removing print services change listener", re); + } + } + + + /** + * Gets the list of print services, but does not register for updates. The user has to register + * for updates by itself, or use {@link PrintServicesLoader}. + * + * @param selectionFlags flags selecting which services to get. Either + * {@link #ENABLED_SERVICES},{@link #DISABLED_SERVICES}, or both. + * + * @return The enabled service list or an empty list. + * + * @see #addPrintServicesChangeListener(PrintServicesChangeListener) + * @see #removePrintServicesChangeListener(PrintServicesChangeListener) + * + * @hide + */ + public @NonNull List<PrintServiceInfo> getPrintServices(int selectionFlags) { + try { + List<PrintServiceInfo> services = mService.getPrintServices(selectionFlags, mUserId); + if (services != null) { + return services; } } catch (RemoteException re) { throw re.rethrowFromSystemServer(); @@ -533,6 +627,26 @@ public final class PrintManager { } /** + * Enable or disable a print service. + * + * @param service The service to enabled or disable + * @param isEnabled whether the service should be enabled or disabled + * + * @hide + */ + public void setPrintServiceEnabled(@NonNull ComponentName service, boolean isEnabled) { + if (mService == null) { + Log.w(LOG_TAG, "Feature android.software.print not available"); + return; + } + try { + mService.setPrintServiceEnabled(service, isEnabled, mUserId); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error enabling or disabling " + service, re); + } + } + + /** * @hide */ public static final class PrintDocumentAdapterDelegate extends IPrintDocumentAdapter.Stub @@ -1096,4 +1210,36 @@ public final class PrintManager { return mWeakListener.get(); } } + + /** + * @hide + */ + public static final class PrintServicesChangeListenerWrapper extends + IPrintServicesChangeListener.Stub { + private final WeakReference<PrintServicesChangeListener> mWeakListener; + private final WeakReference<Handler> mWeakHandler; + + public PrintServicesChangeListenerWrapper(PrintServicesChangeListener listener, + Handler handler) { + mWeakListener = new WeakReference<>(listener); + mWeakHandler = new WeakReference<>(handler); + } + + @Override + public void onPrintServicesChanged() { + Handler handler = mWeakHandler.get(); + PrintServicesChangeListener listener = mWeakListener.get(); + if (handler != null && listener != null) { + handler.obtainMessage(MSG_NOTIFY_PRINT_SERVICES_CHANGED, this).sendToTarget(); + } + } + + public void destroy() { + mWeakListener.clear(); + } + + public PrintServicesChangeListener getListener() { + return mWeakListener.get(); + } + } } diff --git a/core/java/android/print/PrintServicesLoader.java b/core/java/android/print/PrintServicesLoader.java new file mode 100644 index 000000000000..ed411141d1fb --- /dev/null +++ b/core/java/android/print/PrintServicesLoader.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2016 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 android.print; + +import android.annotation.NonNull; +import android.content.Context; +import android.content.Loader; +import android.os.Handler; +import android.os.Message; +import android.printservice.PrintServiceInfo; + +import java.util.List; + +/** + * Loader for the list of print services. Can be parametrized to select a subset. + * + * @hide + */ +public class PrintServicesLoader extends Loader<List<PrintServiceInfo>> { + /** What type of services to load. */ + private final int mSelectionFlags; + + /** The print manager to be used by this object */ + private final @NonNull PrintManager mPrintManager; + + /** Handler to sequentialize the delivery of the results to the main thread */ + private Handler mHandler; + + /** Listens for updates to the data from the platform */ + private PrintManager.PrintServicesChangeListener mListener; + + /** + * Create a new PrintServicesLoader. + * + * @param selectionFlags What type of services to load. + */ + public PrintServicesLoader(@NonNull PrintManager printManager, @NonNull Context context, + int selectionFlags) { + super(context); + mPrintManager = printManager; + mSelectionFlags = selectionFlags; + } + + @Override + protected void onForceLoad() { + queueNewResult(); + } + + /** + * Read the print services and queue it to be delivered on the main thread. + */ + private void queueNewResult() { + Message m = mHandler.obtainMessage(0); + m.obj = mPrintManager.getPrintServices(mSelectionFlags); + mHandler.sendMessage(m); + } + + @Override + protected void onStartLoading() { + mHandler = new MyHandler(); + mListener = new PrintManager.PrintServicesChangeListener() { + @Override public void onPrintServicesChanged() { + queueNewResult(); + } + }; + + mPrintManager.addPrintServicesChangeListener(mListener); + + // Immediately deliver a result + deliverResult(mPrintManager.getPrintServices(mSelectionFlags)); + } + + @Override + protected void onStopLoading() { + if (mListener != null) { + mPrintManager.removePrintServicesChangeListener(mListener); + mListener = null; + } + + if (mHandler != null) { + mHandler.removeMessages(0); + mHandler = null; + } + } + + @Override + protected void onReset() { + onStopLoading(); + } + + /** + * Handler to sequentialize all the updates to the main thread. + */ + private class MyHandler extends Handler { + /** + * Create a new handler on the main thread. + */ + public MyHandler() { + super(getContext().getMainLooper()); + } + + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + + if (isStarted()) { + deliverResult((List<PrintServiceInfo>) msg.obj); + } + } + } +} diff --git a/core/java/android/printservice/PrintServiceInfo.java b/core/java/android/printservice/PrintServiceInfo.java index 91e01f2dfd8b..45e3d47c39de 100644 --- a/core/java/android/printservice/PrintServiceInfo.java +++ b/core/java/android/printservice/PrintServiceInfo.java @@ -55,6 +55,8 @@ public final class PrintServiceInfo implements Parcelable { private final String mId; + private boolean mIsEnabled; + private final ResolveInfo mResolveInfo; private final String mSettingsActivityName; @@ -70,6 +72,7 @@ public final class PrintServiceInfo implements Parcelable { */ public PrintServiceInfo(Parcel parcel) { mId = parcel.readString(); + mIsEnabled = parcel.readByte() != 0; mResolveInfo = parcel.readParcelable(null); mSettingsActivityName = parcel.readString(); mAddPrintersActivityName = parcel.readString(); @@ -180,6 +183,24 @@ public final class PrintServiceInfo implements Parcelable { } /** + * If the service was enabled when it was read from the system. + * + * @return The id. + */ + public boolean isEnabled() { + return mIsEnabled; + } + + /** + * Mark a service as enabled or not + * + * @param isEnabled If the service should be marked as enabled. + */ + public void setIsEnabled(boolean isEnabled) { + mIsEnabled = isEnabled; + } + + /** * The service {@link ResolveInfo}. * * @return The info. @@ -238,6 +259,7 @@ public final class PrintServiceInfo implements Parcelable { @Override public void writeToParcel(Parcel parcel, int flagz) { parcel.writeString(mId); + parcel.writeByte((byte)(mIsEnabled ? 1 : 0)); parcel.writeParcelable(mResolveInfo, 0); parcel.writeString(mSettingsActivityName); parcel.writeString(mAddPrintersActivityName); @@ -276,6 +298,7 @@ public final class PrintServiceInfo implements Parcelable { StringBuilder builder = new StringBuilder(); builder.append("PrintServiceInfo{"); builder.append("id=").append(mId); + builder.append("isEnabled=").append(mIsEnabled); builder.append(", resolveInfo=").append(mResolveInfo); builder.append(", settingsActivityName=").append(mSettingsActivityName); builder.append(", addPrintersActivityName=").append(mAddPrintersActivityName); diff --git a/core/tests/coretests/src/android/print/BasePrintTest.java b/core/tests/coretests/src/android/print/BasePrintTest.java index 3feb0e905f1a..c9bc8aaae0cb 100644 --- a/core/tests/coretests/src/android/print/BasePrintTest.java +++ b/core/tests/coretests/src/android/print/BasePrintTest.java @@ -61,7 +61,6 @@ import java.util.concurrent.TimeoutException; public abstract class BasePrintTest extends InstrumentationTestCase { private static final long OPERATION_TIMEOUT = 30000; - private static final String PRINT_SPOOLER_PACKAGE_NAME = "com.android.printspooler"; private static final String PM_CLEAR_SUCCESS_OUTPUT = "Success"; private static final String COMMAND_LIST_ENABLED_IME_COMPONENTS = "ime list -s"; private static final String COMMAND_PREFIX_ENABLE_IME = "ime enable "; @@ -249,8 +248,9 @@ public abstract class BasePrintTest extends InstrumentationTestCase { protected void clearPrintSpoolerData() throws Exception { assertTrue("failed to clear print spooler data", runShellCommand(getInstrumentation(), String.format( - "pm clear --user %d %s", CURRENT_USER_ID, PRINT_SPOOLER_PACKAGE_NAME)) - .contains(PM_CLEAR_SUCCESS_OUTPUT)); + "pm clear --user %d %s", CURRENT_USER_ID, + PrintManager.PRINT_SPOOLER_PACKAGE_NAME)) + .contains(PM_CLEAR_SUCCESS_OUTPUT)); } @SuppressWarnings("unchecked") diff --git a/core/tests/coretests/src/android/print/IPrintManagerParametersTest.java b/core/tests/coretests/src/android/print/IPrintManagerParametersTest.java index 5179b42fd650..cbf17a43dbc6 100644 --- a/core/tests/coretests/src/android/print/IPrintManagerParametersTest.java +++ b/core/tests/coretests/src/android/print/IPrintManagerParametersTest.java @@ -27,24 +27,9 @@ import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.ServiceManager; import android.os.UserHandle; -import android.print.IPrintDocumentAdapter; -import android.print.IPrintJobStateChangeListener; -import android.print.IPrintManager; -import android.print.IPrinterDiscoveryObserver; -import android.print.PageRange; -import android.print.PrintAttributes; import android.print.PrintAttributes.Margins; import android.print.PrintAttributes.MediaSize; import android.print.PrintAttributes.Resolution; -import android.print.PrintDocumentAdapter; -import android.print.PrintJob; -import android.print.PrintJobId; -import android.print.PrintJobInfo; -import android.print.PrintManager; -import android.print.PrinterCapabilitiesInfo; -import android.print.PrinterDiscoverySession; -import android.print.PrinterId; -import android.print.PrinterInfo; import android.printservice.PrintServiceInfo; import android.print.mockservice.MockPrintService; @@ -134,7 +119,7 @@ public class IPrintManagerParametersTest extends BasePrintTest { if (session.getPrinters().isEmpty()) { final String PRINTER_NAME = "good printer"; - List<PrinterInfo> printers = new ArrayList<PrinterInfo>(); + List<PrinterInfo> printers = new ArrayList<>(); // Add the printer. mGoodPrinterId = session.getService() @@ -184,6 +169,18 @@ public class IPrintManagerParametersTest extends BasePrintTest { } /** + * Create a IPrintServicesChangeListener object. + * + * @return the object + * @throws Exception if the object could not be created. + */ + private IPrintServicesChangeListener createMockIPrintServicesChangeListener() throws Exception { + return new PrintManager.PrintServicesChangeListenerWrapper(null, + new Handler(Looper.getMainLooper())); + } + + + /** * Create a IPrinterDiscoveryObserver object. * * @return the object @@ -193,6 +190,16 @@ public class IPrintManagerParametersTest extends BasePrintTest { return new PrinterDiscoverySession.PrinterDiscoveryObserver(null); } + private void startPrinting() { + mGoodPrintJob = print(createMockAdapter(), null); + + // Wait for PrintActivity to be ready + waitForStartAdapterCallbackCalled(); + + // Wait for printer discovery session to be ready + waitForPrinterDiscoverySessionStartCallbackCalled(); + } + @Override public void setUp() throws Exception { super.setUp(); @@ -201,20 +208,12 @@ public class IPrintManagerParametersTest extends BasePrintTest { mGoodComponentName = getActivity().getComponentName(); - mGoodPrintJob = print(createMockAdapter(), null); - mIPrintManager = IPrintManager.Stub .asInterface(ServiceManager.getService(Context.PRINT_SERVICE)); // Generate dummy printerId which is a valid PrinterId object, but does not correspond to a // printer mBadPrinterId = new PrinterId(mGoodComponentName, "dummy printer"); - - // Wait for PrintActivity to be ready - waitForStartAdapterCallbackCalled(); - - // Wait for printer discovery session to be ready - waitForPrinterDiscoverySessionStartCallbackCalled(); } /** @@ -222,11 +221,11 @@ public class IPrintManagerParametersTest extends BasePrintTest { */ private interface Invokable { /** - * Execute the {@link Invokable} + * Execute the invokable * * @throws Exception */ - public void run() throws Exception; + void run() throws Exception; } /** @@ -255,6 +254,8 @@ public class IPrintManagerParametersTest extends BasePrintTest { * test IPrintManager.getPrintJobInfo */ public void testGetPrintJobInfo() throws Exception { + startPrinting(); + assertEquals(mGoodPrintJob.getId(), mIPrintManager.getPrintJobInfo(mGoodPrintJob.getId(), mAppId, mUserId).getId()); assertEquals(null, mIPrintManager.getPrintJobInfo(mBadPrintJobId, mAppId, mUserId)); @@ -274,6 +275,8 @@ public class IPrintManagerParametersTest extends BasePrintTest { * test IPrintManager.getPrintJobInfos */ public void testGetPrintJobInfos() throws Exception { + startPrinting(); + List<PrintJobInfo> infos = mIPrintManager.getPrintJobInfos(mAppId, mUserId); boolean foundPrintJob = false; @@ -304,7 +307,7 @@ public class IPrintManagerParametersTest extends BasePrintTest { final IPrintDocumentAdapter adapter = new PrintManager .PrintDocumentAdapterDelegate(getActivity(), createMockAdapter()); - // Valid parameters are tested in setUp() + startPrinting(); assertException(new Invokable() { @Override @@ -352,6 +355,8 @@ public class IPrintManagerParametersTest extends BasePrintTest { * test IPrintManager.cancelPrintJob */ public void testCancelPrintJob() throws Exception { + startPrinting(); + // Invalid print jobs IDs do not produce an exception mIPrintManager.cancelPrintJob(mBadPrintJobId, mAppId, mUserId); mIPrintManager.cancelPrintJob(null, mAppId, mUserId); @@ -373,6 +378,8 @@ public class IPrintManagerParametersTest extends BasePrintTest { * test IPrintManager.restartPrintJob */ public void testRestartPrintJob() throws Exception { + startPrinting(); + mIPrintManager.restartPrintJob(mGoodPrintJob.getId(), mAppId, mUserId); // Invalid print jobs IDs do not produce an exception @@ -438,22 +445,103 @@ public class IPrintManagerParametersTest extends BasePrintTest { } /** - * test IPrintManager.getInstalledPrintServices + * test IPrintManager.addPrintServicesChangeListener */ - public void testGetInstalledPrintServices() throws Exception { - List<PrintServiceInfo> printServices = mIPrintManager.getInstalledPrintServices(mUserId); - assertTrue(printServices.size() >= 2); + public void testAddPrintServicesChangeListener() throws Exception { + final IPrintServicesChangeListener listener = createMockIPrintServicesChangeListener(); + + mIPrintManager.addPrintServicesChangeListener(listener, mUserId); + + assertException(new Invokable() { + @Override + public void run() throws Exception { + mIPrintManager.addPrintServicesChangeListener(null, mUserId); + } + }, NullPointerException.class); // Cannot test bad user Id as these tests are allowed to call across users } /** - * test IPrintManager.getEnabledPrintServices + * test IPrintManager.removePrintServicesChangeListener */ - public void testGetEnabledPrintServices() throws Exception { - List<PrintServiceInfo> printServices = mIPrintManager.getEnabledPrintServices(mUserId); + public void testRemovePrintServicesChangeListener() throws Exception { + final IPrintServicesChangeListener listener = createMockIPrintServicesChangeListener(); + + mIPrintManager.addPrintServicesChangeListener(listener, mUserId); + mIPrintManager.removePrintServicesChangeListener(listener, mUserId); + + // Removing unknown listeners is a no-op + mIPrintManager.removePrintServicesChangeListener(listener, mUserId); + + mIPrintManager.addPrintServicesChangeListener(listener, mUserId); + assertException(new Invokable() { + @Override + public void run() throws Exception { + mIPrintManager.removePrintServicesChangeListener(null, mUserId); + } + }, NullPointerException.class); + + // Cannot test bad user Id as these tests are allowed to call across users + } + + /** + * test IPrintManager.getPrintServices + */ + public void testGetPrintServices() throws Exception { + List<PrintServiceInfo> printServices = mIPrintManager.getPrintServices( + PrintManager.ALL_SERVICES, mUserId); assertTrue(printServices.size() >= 2); + printServices = mIPrintManager.getPrintServices(0, mUserId); + assertEquals(printServices, null); + + assertException(new Invokable() { + @Override + public void run() throws Exception { + mIPrintManager.getPrintServices(~PrintManager.ALL_SERVICES, mUserId); + } + }, IllegalArgumentException.class); + + // Cannot test bad user Id as these tests are allowed to call across users + } + + /** + * test IPrintManager.setPrintServiceEnabled + */ + public void testSetPrintServiceEnabled() throws Exception { + final ComponentName printService = mIPrintManager.getPrintServices( + PrintManager.ALL_SERVICES, mUserId).get(0).getComponentName(); + + assertException(new Invokable() { + @Override + public void run() throws Exception { + mIPrintManager.setPrintServiceEnabled(printService, false, mUserId); + } + }, SecurityException.class); + + assertException(new Invokable() { + @Override + public void run() throws Exception { + mIPrintManager.setPrintServiceEnabled(printService, true, mUserId); + } + }, SecurityException.class); + + assertException(new Invokable() { + @Override + public void run() throws Exception { + mIPrintManager.setPrintServiceEnabled(new ComponentName("bad", "name"), true, + mUserId); + } + }, SecurityException.class); + + assertException(new Invokable() { + @Override + public void run() throws Exception { + mIPrintManager.setPrintServiceEnabled(null, true, mUserId); + } + }, SecurityException.class); + // Cannot test bad user Id as these tests are allowed to call across users } @@ -486,6 +574,8 @@ public class IPrintManagerParametersTest extends BasePrintTest { * test IPrintManager.startPrinterDiscovery */ public void testStartPrinterDiscovery() throws Exception { + startPrinting(); + final IPrinterDiscoveryObserver listener = createMockIPrinterDiscoveryObserver(); final List<PrinterId> goodPrinters = new ArrayList<>(); goodPrinters.add(mGoodPrinterId); @@ -549,6 +639,8 @@ public class IPrintManagerParametersTest extends BasePrintTest { * test IPrintManager.validatePrinters */ public void testValidatePrinters() throws Exception { + startPrinting(); + final List<PrinterId> goodPrinters = new ArrayList<>(); goodPrinters.add(mGoodPrinterId); @@ -587,6 +679,8 @@ public class IPrintManagerParametersTest extends BasePrintTest { * test IPrintManager.startPrinterStateTracking */ public void testStartPrinterStateTracking() throws Exception { + startPrinting(); + mIPrintManager.startPrinterStateTracking(mGoodPrinterId, mUserId); // Bad printers do no cause exceptions @@ -606,6 +700,8 @@ public class IPrintManagerParametersTest extends BasePrintTest { * test IPrintManager.getCustomPrinterIcon */ public void testGetCustomPrinterIcon() throws Exception { + startPrinting(); + mIPrintManager.getCustomPrinterIcon(mGoodPrinterId, mUserId); // Bad printers do no cause exceptions @@ -625,6 +721,8 @@ public class IPrintManagerParametersTest extends BasePrintTest { * test IPrintManager.stopPrinterStateTracking */ public void testStopPrinterStateTracking() throws Exception { + startPrinting(); + mIPrintManager.startPrinterStateTracking(mGoodPrinterId, mUserId); mIPrintManager.stopPrinterStateTracking(mGoodPrinterId, mUserId); diff --git a/packages/PrintSpooler/AndroidManifest.xml b/packages/PrintSpooler/AndroidManifest.xml index af9d25196bec..1bdb6d8bc4a7 100644 --- a/packages/PrintSpooler/AndroidManifest.xml +++ b/packages/PrintSpooler/AndroidManifest.xml @@ -63,7 +63,7 @@ android:name=".ui.PrintActivity" android:configChanges="screenSize|smallestScreenSize|orientation" android:permission="android.permission.BIND_PRINT_SPOOLER_SERVICE" - android:theme="@style/PrintActivity"> + android:theme="@style/Theme.PrintActivity"> <intent-filter> <action android:name="android.print.PRINT_DIALOG" /> <category android:name="android.intent.category.DEFAULT" /> @@ -74,7 +74,14 @@ <activity android:name=".ui.SelectPrinterActivity" android:label="@string/all_printers_label" - android:theme="@android:style/Theme.Material.Settings" + android:theme="@style/Theme.SelectPrinterActivity" + android:exported="false"> + </activity> + + <activity + android:name=".ui.AddPrinterActivity" + android:label="@string/print_add_printer" + android:theme="@style/Theme.AddPrinterActivity" android:exported="false"> </activity> diff --git a/packages/PrintSpooler/res/drawable/ic_add.xml b/packages/PrintSpooler/res/drawable/ic_add.xml index 1442b1b61f46..f728e7d0668e 100644 --- a/packages/PrintSpooler/res/drawable/ic_add.xml +++ b/packages/PrintSpooler/res/drawable/ic_add.xml @@ -21,5 +21,5 @@ android:viewportHeight="24.0"> <path android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" - android:fillColor="#FFFFFF"/> + android:fillColor="?android:attr/colorAccent"/> </vector>
\ No newline at end of file diff --git a/packages/PrintSpooler/res/drawable/ic_print.xml b/packages/PrintSpooler/res/drawable/ic_print.xml index dc6e0fbc1d23..e5e4d075f019 100644 --- a/packages/PrintSpooler/res/drawable/ic_print.xml +++ b/packages/PrintSpooler/res/drawable/ic_print.xml @@ -16,4 +16,4 @@ <bitmap xmlns:android="http://schemas.android.com/apk/res/android" android:src="@*android:drawable/ic_print" - android:tint="@color/promoted_action_background_color" /> + android:tint="?android:attr/colorAccent" /> diff --git a/packages/PrintSpooler/res/drawable/page_selector_background.xml b/packages/PrintSpooler/res/drawable/page_selector_background.xml index 7f1da314e1ee..4d32328efdc9 100644 --- a/packages/PrintSpooler/res/drawable/page_selector_background.xml +++ b/packages/PrintSpooler/res/drawable/page_selector_background.xml @@ -22,14 +22,14 @@ android:state_selected="true"> <bitmap android:src="@drawable/ic_check_circle" - android:tint="@color/promoted_action_background_color"> + android:tint="?android:attr/colorAccent"> </bitmap> </item> <item> <bitmap android:src="@drawable/ic_remove_circle" - android:tint="@color/promoted_action_background_color"> + android:tint="?android:attr/colorAccent"> </bitmap> </item> diff --git a/packages/PrintSpooler/res/drawable/print_button_background.xml b/packages/PrintSpooler/res/drawable/print_button_background.xml index aec84744af13..ad16547252ae 100644 --- a/packages/PrintSpooler/res/drawable/print_button_background.xml +++ b/packages/PrintSpooler/res/drawable/print_button_background.xml @@ -18,7 +18,7 @@ android:shape="oval"> <solid - android:color="@color/promoted_action_background_color"> + android:color="?android:attr/colorAccent"> </solid> <size diff --git a/packages/PrintSpooler/res/layout/add_printer_activity.xml b/packages/PrintSpooler/res/layout/add_printer_activity.xml new file mode 100644 index 000000000000..117bba140a98 --- /dev/null +++ b/packages/PrintSpooler/res/layout/add_printer_activity.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:paddingTop="16dip" + android:paddingBottom="16dip"> + + <ListView android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="-16dip" + android:id="@android:id/list" /> + +</LinearLayout> diff --git a/packages/PrintSpooler/res/layout/add_printer_list_header.xml b/packages/PrintSpooler/res/layout/add_printer_list_header.xml new file mode 100644 index 000000000000..ff342cbbb0fa --- /dev/null +++ b/packages/PrintSpooler/res/layout/add_printer_list_header.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="?android:attr/listPreferredItemHeightSmall" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:orientation="horizontal" + android:gravity="start|center_vertical"> + + <TextView android:id="@+id/text" + style="?android:attr/listSeparatorTextViewStyle" /> + +</LinearLayout> diff --git a/packages/PrintSpooler/res/layout/add_printer_list_item.xml b/packages/PrintSpooler/res/layout/add_printer_list_item.xml new file mode 100644 index 000000000000..1a2e774d2474 --- /dev/null +++ b/packages/PrintSpooler/res/layout/add_printer_list_item.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="?android:attr/listPreferredItemHeightSmall" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:orientation="horizontal" + android:gravity="start|center_vertical"> + + <ImageView + android:layout_width="24dip" + android:layout_height="24dip" + android:layout_gravity="center_vertical" + android:contentDescription="@null" + android:layout_marginEnd="16dip" + android:src="@drawable/ic_add" /> + + <RelativeLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginStart="16dip"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceListItem" + android:text="@string/print_add_printer" /> + + </RelativeLayout> + +</LinearLayout> diff --git a/packages/PrintSpooler/res/layout/all_print_services_list_item.xml b/packages/PrintSpooler/res/layout/all_print_services_list_item.xml new file mode 100644 index 000000000000..a2471c64b56e --- /dev/null +++ b/packages/PrintSpooler/res/layout/all_print_services_list_item.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="?android:attr/listPreferredItemHeightSmall" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:orientation="horizontal" + android:gravity="start|center_vertical"> + + <RelativeLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginStart="56dip"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceListItem" + android:text="@string/all_services_title" /> + + </RelativeLayout> + +</LinearLayout> diff --git a/packages/PrintSpooler/res/layout/disabled_print_services_list_item.xml b/packages/PrintSpooler/res/layout/disabled_print_services_list_item.xml new file mode 100644 index 000000000000..73d09333bf80 --- /dev/null +++ b/packages/PrintSpooler/res/layout/disabled_print_services_list_item.xml @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="?android:attr/listPreferredItemHeight" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:orientation="horizontal" + android:gravity="start|center_vertical"> + + <ImageView + android:id="@+id/icon" + android:layout_width="40dip" + android:layout_height="40dip" + android:layout_gravity="center_vertical" + android:contentDescription="@null" /> + + <RelativeLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginStart="16dip"> + + <TextView + android:id="@+id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceListItem" + android:singleLine="true" + android:ellipsize="end" /> + + <TextView + android:id="@+id/subtitle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/title" + android:textAppearance="?android:attr/textAppearanceListItemSecondary" + android:text="@string/enable_print_service" /> + + </RelativeLayout> + +</LinearLayout> diff --git a/packages/PrintSpooler/res/layout/enabled_print_services_list_item.xml b/packages/PrintSpooler/res/layout/enabled_print_services_list_item.xml new file mode 100644 index 000000000000..c13487a4851a --- /dev/null +++ b/packages/PrintSpooler/res/layout/enabled_print_services_list_item.xml @@ -0,0 +1,54 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="?android:attr/listPreferredItemHeight" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:orientation="horizontal" + android:gravity="start|center_vertical"> + + <ImageView + android:id="@+id/icon" + android:layout_width="40dip" + android:layout_height="40dip" + android:layout_gravity="center_vertical" + android:contentDescription="@null" /> + + <RelativeLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginStart="16dip"> + + <TextView + android:id="@+id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceListItem" + android:singleLine="true" + android:ellipsize="end" /> + + <TextView + android:id="@+id/subtitle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/title" + android:textAppearance="?android:attr/textAppearanceListItemSecondary" /> + + </RelativeLayout> + +</LinearLayout> diff --git a/packages/PrintSpooler/res/layout/printer_dropdown_item.xml b/packages/PrintSpooler/res/layout/printer_dropdown_item.xml index defbf8db0026..103c157b873f 100644 --- a/packages/PrintSpooler/res/layout/printer_dropdown_item.xml +++ b/packages/PrintSpooler/res/layout/printer_dropdown_item.xml @@ -16,10 +16,8 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:paddingStart="8dip" - android:paddingEnd="8dip" - android:minHeight="56dip" + android:layout_height="?android:attr/listPreferredItemHeightSmall" + style="?android:attr/spinnerItemStyle" android:orientation="horizontal" android:gravity="start|center_vertical"> @@ -33,10 +31,9 @@ android:visibility="invisible"> </ImageView> - <LinearLayout + <RelativeLayout android:layout_width="wrap_content" android:layout_height="wrap_content" - android:orientation="vertical" android:layout_marginStart="8dip" android:duplicateParentState="true"> @@ -47,8 +44,6 @@ android:textAppearance="?android:attr/textAppearanceMedium" android:singleLine="true" android:ellipsize="end" - android:textIsSelectable="false" - android:gravity="top|start" android:textColor="?android:attr/textColorPrimary" android:duplicateParentState="true"> </TextView> @@ -57,15 +52,15 @@ android:id="@+id/subtitle" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_below="@id/title" android:textAppearance="?android:attr/textAppearanceSmall" android:singleLine="true" android:ellipsize="end" - android:textIsSelectable="false" android:visibility="gone" android:textColor="?android:attr/textColorSecondary" android:duplicateParentState="true"> </TextView> - </LinearLayout> + </RelativeLayout> </LinearLayout> diff --git a/packages/PrintSpooler/res/layout/printer_dropdown_prompt.xml b/packages/PrintSpooler/res/layout/printer_dropdown_prompt.xml index 11fef2d21fde..60f708834628 100644 --- a/packages/PrintSpooler/res/layout/printer_dropdown_prompt.xml +++ b/packages/PrintSpooler/res/layout/printer_dropdown_prompt.xml @@ -16,13 +16,10 @@ <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" - android:layout_height="wrap_content" + android:layout_height="?android:attr/listPreferredItemHeightSmall" android:textAppearance="?android:attr/textAppearanceMedium" - android:textIsSelectable="false" android:textColor="?android:attr/textColorPrimary" android:paddingStart="20dip" - android:paddingEnd="8dip" - android:minHeight="56dip" - android:orientation="horizontal" + style="?android:attr/spinnerItemStyle" android:text="@string/destination_default_text" android:gravity="start|center_vertical" /> diff --git a/packages/PrintSpooler/res/layout/printer_list_item.xml b/packages/PrintSpooler/res/layout/printer_list_item.xml index 1209aa6f0fa2..0784bab589bc 100644 --- a/packages/PrintSpooler/res/layout/printer_list_item.xml +++ b/packages/PrintSpooler/res/layout/printer_list_item.xml @@ -16,10 +16,9 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" - android:layout_height="wrap_content" + android:layout_height="?android:attr/listPreferredItemHeight" android:paddingStart="?android:attr/listPreferredItemPaddingStart" android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" - android:minHeight="?android:attr/listPreferredItemHeight" android:orientation="horizontal" android:gravity="start|center_vertical"> @@ -28,18 +27,15 @@ android:layout_width="40dip" android:layout_height="40dip" android:layout_gravity="center_vertical" - android:layout_marginTop="8dip" - android:layout_marginBottom="8dip" android:duplicateParentState="true" android:contentDescription="@null" android:visibility="invisible"> </ImageView> <RelativeLayout - android:layout_width="0dip" + android:layout_width="fill_parent" android:layout_weight="1" android:layout_height="wrap_content" - android:layout_gravity="center_vertical" android:layout_marginStart="16dip" android:duplicateParentState="true"> @@ -47,15 +43,9 @@ android:id="@+id/title" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textAppearance="?android:attr/textAppearanceMedium" + android:textAppearance="?android:attr/textAppearanceListItem" android:singleLine="true" android:ellipsize="end" - android:textIsSelectable="false" - android:layout_alignParentTop="true" - android:layout_alignParentStart="true" - android:fadingEdge="horizontal" - android:textAlignment="viewStart" - android:textColor="?android:attr/textColorPrimary" android:duplicateParentState="true"> </TextView> @@ -64,30 +54,31 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/title" - android:layout_alignParentStart="true" - android:textAppearance="?android:attr/textAppearanceSmall" + android:textAppearance="?android:attr/textAppearanceListItemSecondary" android:singleLine="true" android:ellipsize="end" - android:textIsSelectable="false" android:visibility="gone" - android:textColor="?android:attr/textColorSecondary" - android:textAlignment="viewStart" android:duplicateParentState="true"> </TextView> </RelativeLayout> - <ImageView + <!-- wrapper for image view to increase the touch target size --> + <LinearLayout android:id="@+id/more_info" android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:paddingLeft="16dip" - android:contentDescription="@string/printer_info_desc" - android:src="@drawable/ic_info" - android:tint="?android:attr/colorControlNormal" - android:tintMode="src_in" + android:layout_height="fill_parent" android:visibility="gone"> - </ImageView> + + <ImageView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:paddingLeft="16dip" + android:contentDescription="@string/printer_info_desc" + android:src="@drawable/ic_info" + android:tint="?android:attr/colorControlNormal" + android:tintMode="src_in" /> + </LinearLayout> </LinearLayout> diff --git a/packages/PrintSpooler/res/layout/select_printer_activity.xml b/packages/PrintSpooler/res/layout/select_printer_activity.xml index 77c500ab86b2..564802ae11ab 100644 --- a/packages/PrintSpooler/res/layout/select_printer_activity.xml +++ b/packages/PrintSpooler/res/layout/select_printer_activity.xml @@ -22,11 +22,7 @@ <ListView android:id="@android:id/list" android:layout_width="fill_parent" - android:layout_height="fill_parent" - android:scrollbarStyle="outsideOverlay" - android:cacheColorHint="@android:color/transparent" - android:scrollbarAlwaysDrawVerticalTrack="true" > - </ListView> + android:layout_height="fill_parent" /> <FrameLayout android:id="@+id/empty_print_state" @@ -63,9 +59,18 @@ android:layout_width="fill_parent" android:layout_height="wrap_content" android:indeterminate="true" - style="@android:style/Widget.DeviceDefault.Light.ProgressBar.Horizontal"> + style="?android:attr/progressBarStyleHorizontal"> </ProgressBar> + <Button + android:id="@+id/button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + style="?android:attr/buttonBarButtonStyle" + android:textAppearance="?android:attr/textAppearanceSmall" + android:text="@string/print_add_printer" + android:textAllCaps="true" /> + </LinearLayout> </FrameLayout> diff --git a/packages/PrintSpooler/res/menu/select_printer_activity.xml b/packages/PrintSpooler/res/menu/select_printer_activity.xml index 15cc13939cf0..60dfdcaf1964 100644 --- a/packages/PrintSpooler/res/menu/select_printer_activity.xml +++ b/packages/PrintSpooler/res/menu/select_printer_activity.xml @@ -26,12 +26,4 @@ android:imeOptions="actionSearch"> </item> - <item - android:id="@+id/action_add_printer" - android:title="@string/print_add_printer" - android:icon="@drawable/ic_add" - android:showAsAction="ifRoom" - android:alphabeticShortcut="a"> - </item> - </menu> diff --git a/packages/PrintSpooler/res/values/colors.xml b/packages/PrintSpooler/res/values/colors.xml index d1bec32cf1bb..47e616ef40c7 100644 --- a/packages/PrintSpooler/res/values/colors.xml +++ b/packages/PrintSpooler/res/values/colors.xml @@ -22,8 +22,6 @@ <color name="print_preview_background_color">#F2F1F2</color> - <color name="promoted_action_background_color">#FF80CBC4</color> - <color name="material_grey_500">#ffa3a3a3</color> </resources> diff --git a/packages/PrintSpooler/res/values/donottranslate.xml b/packages/PrintSpooler/res/values/donottranslate.xml index 8069a1da89b3..589043b02517 100644 --- a/packages/PrintSpooler/res/values/donottranslate.xml +++ b/packages/PrintSpooler/res/values/donottranslate.xml @@ -25,4 +25,6 @@ <string name="mediasize_default">ISO_A4</string> <string name="mediasize_standard">@string/mediasize_standard_iso</string> + <string name="uri_package_details">market://details?id=%1$s</string> + </resources> diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml index 76292a16e9b1..4b566221e4fa 100644 --- a/packages/PrintSpooler/res/values/strings.xml +++ b/packages/PrintSpooler/res/values/strings.xml @@ -129,7 +129,7 @@ <!-- Utterance to announce that the search box is hidden. This is spoken to a blind user. [CHAR LIMIT=none] --> <string name="print_search_box_hidden_utterance">Search box hidden</string> - <!-- Title of the action bar button to got to add a printer. [CHAR LIMIT=25] --> + <!-- Label of add printers button when no printers are found. [CHAR LIMIT=25] --> <string name="print_add_printer">Add printer</string> <!-- Title of the menu item to select a printer. [CHAR LIMIT=25] --> @@ -151,12 +151,7 @@ <string name="printer_info_desc">More information about this printer</string> <!-- Notification that print services as disabled. [CHAR LIMIT=50] --> - <string name="print_services_disabled_toast">Some print services are disabled.</string> - - <!-- Add printer dialog --> - - <!-- Title for the alert dialog for selecting a print service. [CHAR LIMIT=50] --> - <string name="choose_print_service">Choose print service</string> + <string name="print_services_disabled_toast">Some print services are disabled</string> <!-- Title for the prompt shown as a placeholder if no printers are found while not searching. [CHAR LIMIT=50] --> <string name="print_searching_for_printers">Searching for printers</string> @@ -167,6 +162,29 @@ <!-- Title for the prompt shown as a placeholder if there are no printers while searching. [CHAR LIMIT=50] --> <string name="print_no_printers">No printers found</string> + <!-- Add printer activity --> + + <!-- Subtitle for services that cannot add printers. [CHAR LIMIT=50] --> + <string name="cannot_add_printer">Cannot add printers</string> + + <!-- Subtitle for services that can add printers. [CHAR LIMIT=50] --> + <string name="select_to_add_printers">Select to add printer</string> + + <!-- Subtitle for disabled services. [CHAR LIMIT=50] --> + <string name="enable_print_service">Select to enable</string> + + <!-- Header for the list of enabled print services. [CHAR LIMIT=50] --> + <string name="enabled_services_title">Enabled services</string> + + <!-- Header for the list of recommended print services. [CHAR LIMIT=50] --> + <string name="recommended_services_title">Recommended services</string> + + <!-- Header for the list of disabled print services. [CHAR LIMIT=50] --> + <string name="disabled_services_title">Disabled services</string> + + <!-- Label for the list item that links to the list of all print services. [CHAR LIMIT=50] --> + <string name="all_services_title">All services</string> + <!-- Notifications --> <!-- Template for the notification label for a printing print job. [CHAR LIMIT=25] --> diff --git a/packages/PrintSpooler/res/values/styles.xml b/packages/PrintSpooler/res/values/styles.xml new file mode 100644 index 000000000000..1e63a67e50a4 --- /dev/null +++ b/packages/PrintSpooler/res/values/styles.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 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. +--> + +<resources> + <!-- Preference styles --> + <eat-comment/> + + <style name="ListItemSecondary" parent="@android:style/TextAppearance.Material.Body1"> + <item name="android:textColor">?android:attr/textColorSecondary</item> + </style> + + <style name="ListSeparator"> + <item name="android:layout_width">match_parent</item> + <item name="android:layout_height">wrap_content</item> + <item name="android:layout_marginTop">16dip</item> + <item name="android:layout_marginBottom">16dip</item> + <item name="android:textColor">?android:attr/colorAccent</item> + <item name="android:fontFamily">sans-serif-medium</item> + <item name="android:textSize">14sp</item> + </style> +</resources> diff --git a/packages/PrintSpooler/res/values/themes.xml b/packages/PrintSpooler/res/values/themes.xml index 05de5b79a6f9..a968ffa9ed1d 100644 --- a/packages/PrintSpooler/res/values/themes.xml +++ b/packages/PrintSpooler/res/values/themes.xml @@ -15,8 +15,17 @@ --> <resources> + <style name="Theme.AddPrinterActivity" parent="@android:style/Theme.DeviceDefault.Light.Dialog"> + <item name="android:listSeparatorTextViewStyle">@style/ListSeparator</item> + <item name="android:textAppearanceListItemSecondary">@style/ListItemSecondary</item> + </style> + + <style name="Theme.SelectPrinterActivity" + parent="android:style/Theme.DeviceDefault.Light.DarkActionBar"> + <item name="android:textAppearanceListItemSecondary">@style/ListItemSecondary</item> + </style> - <style name="PrintActivity" parent="@android:style/Theme.DeviceDefault"> + <style name="Theme.PrintActivity" parent="@android:style/Theme.DeviceDefault"> <item name="android:windowIsTranslucent">true</item> <item name="android:windowBackground">@android:color/transparent</item> <item name="android:windowContentOverlay">@null</item> diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/AddPrinterActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/AddPrinterActivity.java new file mode 100644 index 000000000000..f2b3e6ee04c7 --- /dev/null +++ b/packages/PrintSpooler/src/com/android/printspooler/ui/AddPrinterActivity.java @@ -0,0 +1,563 @@ +/* + * Copyright (C) 2016 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.ui; + +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ListActivity; +import android.app.LoaderManager; +import android.content.ActivityNotFoundException; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.Loader; +import android.database.DataSetObserver; +import android.net.Uri; +import android.os.Bundle; +import android.print.PrintManager; +import android.print.PrintServicesLoader; +import android.printservice.PrintServiceInfo; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Log; +import android.util.Pair; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Adapter; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.TextView; +import com.android.printspooler.R; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * This is an activity for adding a printer or. It consists of a list fed from three adapters: + * <ul> + * <li>{@link #mEnabledServicesAdapter} for all enabled services. If a service has an {@link + * PrintServiceInfo#getAddPrintersActivityName() add printer activity} this is started + * when the item is clicked.</li> + * <li>{@link #mDisabledServicesAdapter} for all disabled services. Once clicked the settings page + * for this service is opened.</li> + * <li>{@link RecommendedServicesAdapter} for a link to all services. If this item is clicked + * the market app is opened to show all print services.</li> + * </ul> + */ +public class AddPrinterActivity extends ListActivity implements + LoaderManager.LoaderCallbacks<List<PrintServiceInfo>>, + AdapterView.OnItemClickListener { + private static final String LOG_TAG = "AddPrinterActivity"; + + /** Ids for the loaders */ + private static final int LOADER_ID_ENABLED_SERVICES = 1; + private static final int LOADER_ID_DISABLED_SERVICES = 2; + + /** + * The enabled services list. This is filled from the {@link #LOADER_ID_ENABLED_SERVICES} + * loader in {@link #onLoadFinished}. + */ + private EnabledServicesAdapter mEnabledServicesAdapter; + + /** + * The disabled services list. This is filled from the {@link #LOADER_ID_DISABLED_SERVICES} + * loader in {@link #onLoadFinished}. + */ + private DisabledServicesAdapter mDisabledServicesAdapter; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.add_printer_activity); + + mEnabledServicesAdapter = new EnabledServicesAdapter(); + mDisabledServicesAdapter = new DisabledServicesAdapter(); + + ArrayList<ActionAdapter> adapterList = new ArrayList<>(3); + adapterList.add(mEnabledServicesAdapter); + adapterList.add(new RecommendedServicesAdapter()); + adapterList.add(mDisabledServicesAdapter); + + setListAdapter(new CombinedAdapter(adapterList)); + + getListView().setOnItemClickListener(this); + + getLoaderManager().initLoader(LOADER_ID_ENABLED_SERVICES, null, this); + getLoaderManager().initLoader(LOADER_ID_DISABLED_SERVICES, null, this); + // TODO: Load recommended services + } + + @Override + public Loader<List<PrintServiceInfo>> onCreateLoader(int id, Bundle args) { + switch (id) { + case LOADER_ID_ENABLED_SERVICES: + return new PrintServicesLoader( + (PrintManager) getSystemService(Context.PRINT_SERVICE), this, + PrintManager.ENABLED_SERVICES); + case LOADER_ID_DISABLED_SERVICES: + return new PrintServicesLoader( + (PrintManager) getSystemService(Context.PRINT_SERVICE), this, + PrintManager.DISABLED_SERVICES); + // TODO: Load recommended services + default: + // not reached + return null; + } + } + + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + ((ActionAdapter) getListAdapter()).performAction(position); + } + + @Override + public void onLoadFinished(Loader<List<PrintServiceInfo>> loader, + List<PrintServiceInfo> data) { + switch (loader.getId()) { + case LOADER_ID_ENABLED_SERVICES: + mEnabledServicesAdapter.updateData(data); + break; + case LOADER_ID_DISABLED_SERVICES: + mDisabledServicesAdapter.updateData(data); + break; + // TODO: Load recommended services + default: + // not reached + } + } + + @Override + public void onLoaderReset(Loader<List<PrintServiceInfo>> loader) { + if (!isFinishing()) { + switch (loader.getId()) { + case LOADER_ID_ENABLED_SERVICES: + mEnabledServicesAdapter.updateData(null); + break; + case LOADER_ID_DISABLED_SERVICES: + mDisabledServicesAdapter.updateData(null); + break; + // TODO: Reset recommended services + default: + // not reached + } + } + } + + /** + * Marks an adapter that can can perform an action for a position in it's list. + */ + private abstract class ActionAdapter extends BaseAdapter { + /** + * Perform the action for a position in the list. + * + * @param position The position of the item + */ + abstract void performAction(@IntRange(from = 0) int position); + + @Override + public boolean areAllItemsEnabled() { + return false; + } + } + + /** + * An adapter presenting multiple sub adapters as a single combined adapter. + */ + private class CombinedAdapter extends ActionAdapter { + /** The adapters to combine */ + private final @NonNull ArrayList<ActionAdapter> mAdapters; + + /** + * Create a combined adapter. + * + * @param adapters the list of adapters to combine + */ + CombinedAdapter(@NonNull ArrayList<ActionAdapter> adapters) { + mAdapters = adapters; + + final int numAdapters = mAdapters.size(); + for (int i = 0; i < numAdapters; i++) { + mAdapters.get(i).registerDataSetObserver(new DataSetObserver() { + @Override + public void onChanged() { + notifyDataSetChanged(); + } + + @Override + public void onInvalidated() { + notifyDataSetChanged(); + } + }); + } + } + + @Override + public int getCount() { + int totalCount = 0; + + final int numAdapters = mAdapters.size(); + for (int i = 0; i < numAdapters; i++) { + totalCount += mAdapters.get(i).getCount(); + } + + return totalCount; + } + + /** + * Find the sub adapter and the position in the sub-adapter the position in the combined + * adapter refers to. + * + * @param position The position in the combined adapter + * + * @return The pair of adapter and position in sub adapter + */ + private @NonNull Pair<ActionAdapter, Integer> getSubAdapter(int position) { + final int numAdapters = mAdapters.size(); + for (int i = 0; i < numAdapters; i++) { + ActionAdapter adapter = mAdapters.get(i); + + if (position < adapter.getCount()) { + return new Pair<>(adapter, position); + } else { + position -= adapter.getCount(); + } + } + + throw new IllegalArgumentException("Invalid position"); + } + + @Override + public int getItemViewType(int position) { + int numLowerViewTypes = 0; + + final int numAdapters = mAdapters.size(); + for (int i = 0; i < numAdapters; i++) { + Adapter adapter = mAdapters.get(i); + + if (position < adapter.getCount()) { + return numLowerViewTypes + adapter.getItemViewType(position); + } else { + numLowerViewTypes += adapter.getViewTypeCount(); + position -= adapter.getCount(); + } + } + + throw new IllegalArgumentException("Invalid position"); + } + + @Override + public int getViewTypeCount() { + int totalViewCount = 0; + + final int numAdapters = mAdapters.size(); + for (int i = 0; i < numAdapters; i++) { + totalViewCount += mAdapters.get(i).getViewTypeCount(); + } + + return totalViewCount; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + Pair<ActionAdapter, Integer> realPosition = getSubAdapter(position); + + return realPosition.first.getView(realPosition.second, convertView, parent); + } + + @Override + public Object getItem(int position) { + Pair<ActionAdapter, Integer> realPosition = getSubAdapter(position); + + return realPosition.first.getItem(realPosition.second); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public boolean isEnabled(int position) { + Pair<ActionAdapter, Integer> realPosition = getSubAdapter(position); + + return realPosition.first.isEnabled(realPosition.second); + } + + @Override + public void performAction(@IntRange(from = 0) int position) { + Pair<ActionAdapter, Integer> realPosition = getSubAdapter(position); + + realPosition.first.performAction(realPosition.second); + } + } + + /** + * Superclass for all adapters that just display a list of {@link PrintServiceInfo}. + */ + private abstract class PrintServiceInfoAdapter extends ActionAdapter { + /** + * Raw data of the list. + * + * @see #updateData(List) + */ + private @NonNull List<PrintServiceInfo> mServices; + + /** + * Create a new adapter. + */ + PrintServiceInfoAdapter() { + mServices = Collections.emptyList(); + } + + /** + * Update the data. + * + * @param services The new raw data. + */ + void updateData(@Nullable List<PrintServiceInfo> services) { + if (services == null || services.isEmpty()) { + mServices = Collections.emptyList(); + } else { + mServices = services; + } + + notifyDataSetChanged(); + } + + @Override + public int getViewTypeCount() { + return 2; + } + + @Override + public int getItemViewType(int position) { + if (position == 0) { + return 0; + } else { + return 1; + } + } + + @Override + public int getCount() { + if (mServices.isEmpty()) { + return 0; + } else { + return mServices.size() + 1; + } + } + + @Override + public Object getItem(int position) { + if (position == 0) { + return null; + } else { + return mServices.get(position - 1); + } + } + + @Override + public boolean isEnabled(int position) { + return position != 0; + } + + @Override + public long getItemId(int position) { + return position; + } + } + + /** + * Adapter for the enabled services. + */ + private class EnabledServicesAdapter extends PrintServiceInfoAdapter { + @Override + public void performAction(@IntRange(from = 0) int position) { + PrintServiceInfo service = (PrintServiceInfo) getItem(position); + String addPrinterActivityName = service.getAddPrintersActivityName(); + + if (!TextUtils.isEmpty(addPrinterActivityName)) { + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.setComponent(new ComponentName(service.getComponentName().getPackageName(), + addPrinterActivityName)); + + try { + startActivity(intent); + } catch (ActivityNotFoundException e) { + Log.e(LOG_TAG, "Cannot start add printers activity", e); + } + } + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (position == 0) { + if (convertView == null) { + convertView = getLayoutInflater().inflate(R.layout.add_printer_list_header, + parent, false); + } + + ((TextView) convertView.findViewById(R.id.text)) + .setText(R.string.enabled_services_title); + + return convertView; + } + + if (convertView == null) { + convertView = getLayoutInflater().inflate(R.layout.enabled_print_services_list_item, + parent, false); + } + + PrintServiceInfo service = (PrintServiceInfo) getItem(position); + + TextView title = (TextView) convertView.findViewById(R.id.title); + ImageView icon = (ImageView) convertView.findViewById(R.id.icon); + TextView subtitle = (TextView) convertView.findViewById(R.id.subtitle); + + title.setText(service.getResolveInfo().loadLabel(getPackageManager())); + icon.setImageDrawable(service.getResolveInfo().loadIcon(getPackageManager())); + + if (TextUtils.isEmpty(service.getAddPrintersActivityName())) { + subtitle.setText(getString(R.string.cannot_add_printer)); + } else { + subtitle.setText(getString(R.string.select_to_add_printers)); + } + + return convertView; + } + } + + /** + * Adapter for the disabled services. + */ + private class DisabledServicesAdapter extends PrintServiceInfoAdapter { + @Override + public void performAction(@IntRange(from = 0) int position) { + ((PrintManager) getSystemService(Context.PRINT_SERVICE)).setPrintServiceEnabled( + ((PrintServiceInfo) getItem(position)).getComponentName(), true); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (position == 0) { + if (convertView == null) { + convertView = getLayoutInflater().inflate(R.layout.add_printer_list_header, + parent, false); + } + + ((TextView) convertView.findViewById(R.id.text)) + .setText(R.string.disabled_services_title); + + return convertView; + } + + if (convertView == null) { + convertView = getLayoutInflater().inflate( + R.layout.disabled_print_services_list_item, parent, false); + } + + PrintServiceInfo service = (PrintServiceInfo) getItem(position); + + TextView title = (TextView) convertView.findViewById(R.id.title); + ImageView icon = (ImageView) convertView.findViewById(R.id.icon); + + title.setText(service.getResolveInfo().loadLabel(getPackageManager())); + icon.setImageDrawable(service.getResolveInfo().loadIcon(getPackageManager())); + + return convertView; + } + } + + /** + * Adapter for the recommended services. + */ + private class RecommendedServicesAdapter extends ActionAdapter { + @Override + public int getCount() { + return 2; + } + + @Override + public int getViewTypeCount() { + return 2; + } + + @Override + public int getItemViewType(int position) { + if (position == 0) { + return 0; + } else { + return 1; + } + } + + @Override + public Object getItem(int position) { + return null; + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (position == 0) { + if (convertView == null) { + convertView = getLayoutInflater().inflate(R.layout.add_printer_list_header, + parent, false); + } + + ((TextView) convertView.findViewById(R.id.text)) + .setText(R.string.recommended_services_title); + + return convertView; + } + + if (convertView == null) { + convertView = getLayoutInflater().inflate(R.layout.all_print_services_list_item, + parent, false); + } + + return convertView; + } + + @Override + public boolean isEnabled(int position) { + return position != 0; + } + + @Override + public void performAction(@IntRange(from = 0) int position) { + String searchUri = Settings.Secure + .getString(getContentResolver(), Settings.Secure.PRINT_SERVICE_SEARCH_URI); + + if (searchUri != null) { + try { + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(searchUri))); + } catch (ActivityNotFoundException e) { + Log.e(LOG_TAG, "Cannot start market", e); + } + } + } + } +} diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java b/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java index 46a2098ad5da..3b5513ab12cf 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java +++ b/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java @@ -16,7 +16,10 @@ package com.android.printspooler.ui; +import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.Activity; +import android.app.LoaderManager; import android.content.ComponentName; import android.content.Context; import android.content.Loader; @@ -28,9 +31,11 @@ import android.location.LocationManager; import android.location.LocationRequest; import android.os.AsyncTask; import android.os.Bundle; +import android.os.Handler; import android.os.Looper; import android.os.SystemClock; import android.print.PrintManager; +import android.print.PrintServicesLoader; import android.print.PrinterDiscoverySession; import android.print.PrinterDiscoverySession.OnPrintersChangeListener; import android.print.PrinterId; @@ -127,11 +132,11 @@ public final class FusedPrintersProvider extends Loader<List<PrinterInfo>> } } - public FusedPrintersProvider(Context context) { - super(context); + public FusedPrintersProvider(Activity activity, int internalLoaderId) { + super(activity); mLocationLock = new Object(); - mPersistenceManager = new PersistenceManager(context); - mLocationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); + mPersistenceManager = new PersistenceManager(activity, internalLoaderId); + mLocationManager = (LocationManager) activity.getSystemService(Context.LOCATION_SERVICE); } public void addHistoricalPrinter(PrinterInfo printer) { @@ -383,7 +388,6 @@ public final class FusedPrintersProvider extends Loader<List<PrinterInfo>> mPrinters.clear(); if (mDiscoverySession != null) { mDiscoverySession.destroy(); - mDiscoverySession = null; } } @@ -499,7 +503,8 @@ public final class FusedPrintersProvider extends Loader<List<PrinterInfo>> updatePrinters(mDiscoverySession.getPrinters(), newFavoritePrinters, getCurrentLocation()); } - private final class PersistenceManager { + private final class PersistenceManager implements + LoaderManager.LoaderCallbacks<List<PrintServiceInfo>> { private static final String PERSIST_FILE_NAME = "printer_history.xml"; private static final String TAG_PRINTERS = "printers"; @@ -520,6 +525,15 @@ public final class FusedPrintersProvider extends Loader<List<PrinterInfo>> private final AtomicFile mStatePersistFile; + /** + * Whether the enabled print services have been updated since last time the history was + * read. + */ + private boolean mAreEnabledServicesUpdated; + + /** The enabled services read when they were last updated */ + private @NonNull List<PrintServiceInfo> mEnabledServices; + private List<Pair<PrinterInfo, Location>> mHistoricalPrinters = new ArrayList<>(); private boolean mReadHistoryCompleted; @@ -528,9 +542,52 @@ public final class FusedPrintersProvider extends Loader<List<PrinterInfo>> private volatile long mLastReadHistoryTimestamp; - private PersistenceManager(Context context) { - mStatePersistFile = new AtomicFile(new File(context.getFilesDir(), + private PersistenceManager(final Activity activity, final int internalLoaderId) { + mStatePersistFile = new AtomicFile(new File(activity.getFilesDir(), PERSIST_FILE_NAME)); + + // Initialize enabled services to make sure they are set are the read task might be done + // before the loader updated the services the first time. + mEnabledServices = ((PrintManager) activity + .getSystemService(Context.PRINT_SERVICE)) + .getPrintServices(PrintManager.ENABLED_SERVICES); + + mAreEnabledServicesUpdated = true; + + // Cannot start a loader while starting another, hence delay this loader + (new Handler(activity.getMainLooper())).post(new Runnable() { + @Override + public void run() { + activity.getLoaderManager().initLoader(internalLoaderId, null, + PersistenceManager.this); + } + }); + } + + + @Override + public Loader<List<PrintServiceInfo>> onCreateLoader(int id, Bundle args) { + return new PrintServicesLoader( + (PrintManager) getContext().getSystemService(Context.PRINT_SERVICE), + getContext(), PrintManager.ENABLED_SERVICES); + } + + @Override + public void onLoadFinished(Loader<List<PrintServiceInfo>> loader, + List<PrintServiceInfo> services) { + mAreEnabledServicesUpdated = true; + mEnabledServices = services; + + // Ask the fused printer provider to reload which will cause the persistence manager to + // reload the history and reconsider the enabled services. + if (isStarted()) { + forceLoad(); + } + } + + @Override + public void onLoaderReset(Loader<List<PrintServiceInfo>> loader) { + // no data is cached } public boolean isReadHistoryInProgress() { @@ -644,7 +701,8 @@ public final class FusedPrintersProvider extends Loader<List<PrinterInfo>> } public boolean isHistoryChanged() { - return mLastReadHistoryTimestamp != mStatePersistFile.getBaseFile().lastModified(); + return mAreEnabledServicesUpdated || + mLastReadHistoryTimestamp != mStatePersistFile.getBaseFile().lastModified(); } /** @@ -738,19 +796,15 @@ public final class FusedPrintersProvider extends Loader<List<PrinterInfo>> } // Ignore printer records whose target services are not enabled. - PrintManager printManager = (PrintManager) getContext() - .getSystemService(Context.PRINT_SERVICE); - List<PrintServiceInfo> services = printManager - .getEnabledPrintServices(); - Set<ComponentName> enabledComponents = new ArraySet<>(); - final int installedServiceCount = services.size(); + final int installedServiceCount = mEnabledServices.size(); for (int i = 0; i < installedServiceCount; i++) { - ServiceInfo serviceInfo = services.get(i).getResolveInfo().serviceInfo; + ServiceInfo serviceInfo = mEnabledServices.get(i).getResolveInfo().serviceInfo; ComponentName componentName = new ComponentName( serviceInfo.packageName, serviceInfo.name); enabledComponents.add(componentName); } + mAreEnabledServicesUpdated = false; final int printerCount = printers.size(); for (int i = printerCount - 1; i >= 0; i--) { diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java index bad3ad7cecc1..3920c62f526e 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java +++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java @@ -22,11 +22,13 @@ import android.app.Dialog; import android.app.DialogFragment; import android.app.Fragment; import android.app.FragmentTransaction; +import android.app.LoaderManager; import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.content.Loader; import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; @@ -51,10 +53,12 @@ import android.print.PrintAttributes.Resolution; import android.print.PrintDocumentInfo; import android.print.PrintJobInfo; import android.print.PrintManager; +import android.print.PrintServicesLoader; import android.print.PrinterCapabilitiesInfo; import android.print.PrinterId; import android.print.PrinterInfo; import android.printservice.PrintService; +import android.printservice.PrintServiceInfo; import android.provider.DocumentsContract; import android.text.Editable; import android.text.TextUtils; @@ -94,7 +98,6 @@ 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; -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; @@ -113,12 +116,14 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; public class PrintActivity extends Activity implements RemotePrintDocument.UpdateResultCallbacks, PrintErrorFragment.OnActionListener, PageAdapter.ContentCallbacks, - OptionsStateChangeListener, OptionsStateController { + OptionsStateChangeListener, OptionsStateController, + LoaderManager.LoaderCallbacks<List<PrintServiceInfo>> { private static final String LOG_TAG = "PrintActivity"; private static final boolean DEBUG = false; @@ -129,6 +134,10 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat private static final String HAS_PRINTED_PREF = "has_printed"; + private static final int LOADER_ID_ENABLED_PRINT_SERVICES = 1; + private static final int LOADER_ID_PRINT_REGISTRY = 2; + private static final int LOADER_ID_PRINT_REGISTRY_INT = 3; + private static final int ORIENTATION_PORTRAIT = 0; private static final int ORIENTATION_LANDSCAPE = 1; @@ -139,7 +148,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat private static final int DEST_ADAPTER_MAX_ITEM_COUNT = 9; private static final int DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF = Integer.MAX_VALUE; - private static final int DEST_ADAPTER_ITEM_ID_ALL_PRINTERS = Integer.MAX_VALUE - 1; + private static final int DEST_ADAPTER_ITEM_ID_MORE = Integer.MAX_VALUE - 1; private static final int STATE_INITIALIZING = 0; private static final int STATE_CONFIGURING = 1; @@ -239,6 +248,12 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat /** Observer for changes to the printers */ private PrintersObserver mPrintersObserver; + /** Advances options activity name for current printer */ + private ComponentName mAdvancedPrintOptionsActivity; + + /** Whether at least one print services is enabled or not */ + private boolean mArePrintServicesEnabled; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -278,6 +293,8 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat } } }); + + getLoaderManager().initLoader(LOADER_ID_ENABLED_PRINT_SERVICES, null, this); } private void onConnectedToPrintSpooler(final IBinder documentAdapter) { @@ -292,7 +309,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat public void run() { onPrinterRegistryReady(documentAdapter); } - }); + }, LOADER_ID_PRINT_REGISTRY, LOADER_ID_PRINT_REGISTRY_INT); } private void onPrinterRegistryReady(IBinder documentAdapter) { @@ -716,15 +733,12 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat } private void startAdvancedPrintOptionsActivity(PrinterInfo printer) { - ComponentName serviceName = printer.getId().getServiceName(); - - String activityName = PrintOptionUtils.getAdvancedOptionsActivityName(this, serviceName); - if (TextUtils.isEmpty(activityName)) { + if (mAdvancedPrintOptionsActivity == null) { return; } Intent intent = new Intent(Intent.ACTION_MAIN); - intent.setComponent(new ComponentName(serviceName.getPackageName(), activityName)); + intent.setComponent(mAdvancedPrintOptionsActivity); List<ResolveInfo> resolvedActivities = getPackageManager() .queryIntentActivities(intent, 0); @@ -1283,6 +1297,59 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat } } + @Override + public Loader<List<PrintServiceInfo>> onCreateLoader(int id, Bundle args) { + return new PrintServicesLoader((PrintManager) getSystemService(Context.PRINT_SERVICE), this, + PrintManager.ENABLED_SERVICES); + } + + @Override + public void onLoadFinished(Loader<List<PrintServiceInfo>> loader, + List<PrintServiceInfo> services) { + ComponentName newAdvancedPrintOptionsActivity = null; + if (mCurrentPrinter != null && services != null) { + final int numServices = services.size(); + for (int i = 0; i < numServices; i++) { + PrintServiceInfo service = services.get(i); + + if (service.getComponentName().equals(mCurrentPrinter.getId().getServiceName())) { + String advancedOptionsActivityName = service.getAdvancedOptionsActivityName(); + + if (!TextUtils.isEmpty(advancedOptionsActivityName)) { + newAdvancedPrintOptionsActivity = new ComponentName( + service.getComponentName().getPackageName(), + advancedOptionsActivityName); + + break; + } + } + } + } + + if (!Objects.equals(newAdvancedPrintOptionsActivity, mAdvancedPrintOptionsActivity)) { + mAdvancedPrintOptionsActivity = newAdvancedPrintOptionsActivity; + updateOptionsUi(); + } + + boolean newArePrintServicesEnabled = services != null && !services.isEmpty(); + if (mArePrintServicesEnabled != newArePrintServicesEnabled) { + mArePrintServicesEnabled = newArePrintServicesEnabled; + + // Reload mDestinationSpinnerAdapter as mArePrintServicesEnabled changed and the adapter + // reads that in DestinationAdapter#getMoreItemTitle + if (mDestinationSpinnerAdapter != null) { + mDestinationSpinnerAdapter.notifyDataSetChanged(); + } + } + } + + @Override + public void onLoaderReset(Loader<List<PrintServiceInfo>> loader) { + if (!isFinishing()) { + onLoadFinished(loader, null); + } + } + /** * 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 @@ -1722,9 +1789,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat } // Advanced print options - ComponentName serviceName = mCurrentPrinter.getId().getServiceName(); - if (!TextUtils.isEmpty(PrintOptionUtils.getAdvancedOptionsActivityName( - this, serviceName))) { + if (mAdvancedPrintOptionsActivity != null) { mMoreOptionsButton.setVisibility(View.VISIBLE); mMoreOptionsButton.setEnabled(true); } else { @@ -2216,14 +2281,14 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat if (position == 0) { return DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF; } else if (position == 1) { - return DEST_ADAPTER_ITEM_ID_ALL_PRINTERS; + return DEST_ADAPTER_ITEM_ID_MORE; } } else { if (position == 1) { return DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF; } if (position == getCount() - 1) { - return DEST_ADAPTER_ITEM_ID_ALL_PRINTERS; + return DEST_ADAPTER_ITEM_ID_MORE; } } return position; @@ -2236,6 +2301,14 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat return view; } + private String getMoreItemTitle() { + if (mArePrintServicesEnabled) { + return getString(R.string.all_printers); + } else { + return getString(R.string.print_add_printer); + } + } + @Override public View getView(int position, View convertView, ViewGroup parent) { if (mShowDestinationPrompt) { @@ -2264,7 +2337,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat title = printerHolder.printer.getName(); icon = getResources().getDrawable(R.drawable.ic_menu_savetopdf, null); } else if (position == 1) { - title = getString(R.string.all_printers); + title = getMoreItemTitle(); } } else { if (position == 1 && getPdfPrinter() != null) { @@ -2272,7 +2345,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat title = printerHolder.printer.getName(); icon = getResources().getDrawable(R.drawable.ic_menu_savetopdf, null); } else if (position == getCount() - 1) { - title = getString(R.string.all_printers); + title = getMoreItemTitle(); } else { PrinterHolder printerHolder = (PrinterHolder) getItem(position); PrinterInfo printInfo = printerHolder.printer; @@ -2307,7 +2380,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat } iconView.setImageDrawable(icon); } else { - iconView.setVisibility(View.GONE); + iconView.setVisibility(View.INVISIBLE); } return convertView; @@ -2352,6 +2425,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat PrinterInfo updatedPrinter = newPrintersMap.remove(oldPrinterId); if (updatedPrinter != null) { printerHolder.printer = updatedPrinter; + printerHolder.removed = false; } else { printerHolder.removed = true; } @@ -2497,6 +2571,10 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat updateDocument(false); } + // Force a reload of the enabled print services to update mAdvancedPrintOptionsActivity + // in onLoadFinished(); + getLoaderManager().getLoader(LOADER_ID_ENABLED_PRINT_SERVICES).forceLoad(); + updateOptionsUi(); updateSummary(); } @@ -2522,7 +2600,7 @@ public class PrintActivity extends Activity implements RemotePrintDocument.Updat return; } - if (id == DEST_ADAPTER_ITEM_ID_ALL_PRINTERS) { + if (id == DEST_ADAPTER_ITEM_ID_MORE) { startSelectPrinterActivity(); return; } diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrinterRegistry.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrinterRegistry.java index 6d60bb86cd7e..86366dd21ead 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrinterRegistry.java +++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrinterRegistry.java @@ -33,7 +33,7 @@ import java.util.List; public class PrinterRegistry { - private static final int LOADER_ID_PRINTERS_LOADER = 1; + private final int mLoaderId; private final Activity mActivity; @@ -52,12 +52,17 @@ public class PrinterRegistry { public void onPrintersInvalid(); } - public PrinterRegistry(Activity activity, Runnable readyCallback) { + public PrinterRegistry(Activity activity, Runnable readyCallback, int loaderId, + int internalLoaderId) { + mLoaderId = loaderId; mActivity = activity; mReadyCallback = readyCallback; mHandler = new MyHandler(activity.getMainLooper()); - activity.getLoaderManager().initLoader(LOADER_ID_PRINTERS_LOADER, - null, mLoaderCallbacks); + + Bundle loaderData = new Bundle(1); + loaderData.putInt(null, internalLoaderId); + + activity.getLoaderManager().initLoader(loaderId, loaderData, mLoaderCallbacks); } public void setOnPrintersChangeListener(OnPrintersChangeListener listener) { @@ -106,7 +111,7 @@ public class PrinterRegistry { } private FusedPrintersProvider getPrinterProvider() { - Loader<?> loader = mActivity.getLoaderManager().getLoader(LOADER_ID_PRINTERS_LOADER); + Loader<?> loader = mActivity.getLoaderManager().getLoader(mLoaderId); return (FusedPrintersProvider) loader; } @@ -114,38 +119,34 @@ public class PrinterRegistry { new LoaderCallbacks<List<PrinterInfo>>() { @Override public void onLoaderReset(Loader<List<PrinterInfo>> loader) { - if (loader.getId() == LOADER_ID_PRINTERS_LOADER) { - mPrinters.clear(); - if (mOnPrintersChangeListener != null) { - // Post a message as we are in onLoadFinished and certain operations - // are not allowed in this callback, such as fragment transactions. - // Clients should not handle this explicitly. - mHandler.obtainMessage(MyHandler.MSG_PRINTERS_INVALID, - mOnPrintersChangeListener).sendToTarget(); - } + mPrinters.clear(); + if (mOnPrintersChangeListener != null) { + // Post a message as we are in onLoadFinished and certain operations + // are not allowed in this callback, such as fragment transactions. + // Clients should not handle this explicitly. + mHandler.obtainMessage(MyHandler.MSG_PRINTERS_INVALID, + mOnPrintersChangeListener).sendToTarget(); } } // LoaderCallbacks#onLoadFinished @Override public void onLoadFinished(Loader<List<PrinterInfo>> loader, List<PrinterInfo> printers) { - if (loader.getId() == LOADER_ID_PRINTERS_LOADER) { - mPrinters.clear(); - mPrinters.addAll(printers); - if (mOnPrintersChangeListener != null) { - // Post a message as we are in onLoadFinished and certain operations - // are not allowed in this callback, such as fragment transactions. - // Clients should not handle this explicitly. - SomeArgs args = SomeArgs.obtain(); - args.arg1 = mOnPrintersChangeListener; - args.arg2 = printers; - mHandler.obtainMessage(MyHandler.MSG_PRINTERS_CHANGED, args).sendToTarget(); - } - if (!mReady) { - mReady = true; - if (mReadyCallback != null) { - mReadyCallback.run(); - } + mPrinters.clear(); + mPrinters.addAll(printers); + if (mOnPrintersChangeListener != null) { + // Post a message as we are in onLoadFinished and certain operations + // are not allowed in this callback, such as fragment transactions. + // Clients should not handle this explicitly. + SomeArgs args = SomeArgs.obtain(); + args.arg1 = mOnPrintersChangeListener; + args.arg2 = printers; + mHandler.obtainMessage(MyHandler.MSG_PRINTERS_CHANGED, args).sendToTarget(); + } + if (!mReady) { + mReady = true; + if (mReadyCallback != null) { + mReadyCallback.run(); } } } @@ -153,10 +154,7 @@ public class PrinterRegistry { // LoaderCallbacks#onCreateLoader @Override public Loader<List<PrinterInfo>> onCreateLoader(int id, Bundle args) { - if (id == LOADER_ID_PRINTERS_LOADER) { - return new FusedPrintersProvider(mActivity); - } - return null; + return new FusedPrintersProvider(mActivity, args.getInt(null)); } }; diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java index 4f7624adc9ad..e53a522adb12 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java +++ b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java @@ -17,31 +17,18 @@ 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.app.LoaderManager; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.content.IntentSender.SendIntentException; -import android.content.pm.ActivityInfo; +import android.content.Loader; import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.ResolveInfo; -import android.content.pm.ServiceInfo; -import android.database.ContentObserver; import android.database.DataSetObserver; import android.graphics.drawable.Drawable; -import android.graphics.drawable.Icon; -import android.net.Uri; import android.os.Bundle; -import android.os.Handler; import android.print.PrintManager; +import android.print.PrintServicesLoader; import android.print.PrinterId; import android.print.PrinterInfo; import android.printservice.PrintServiceInfo; @@ -59,17 +46,16 @@ import android.view.ViewGroup; import android.view.accessibility.AccessibilityManager; import android.widget.AdapterView; import android.widget.AdapterView.AdapterContextMenuInfo; -import android.widget.ArrayAdapter; import android.widget.BaseAdapter; import android.widget.Filter; import android.widget.Filterable; import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.ListView; import android.widget.SearchView; import android.widget.TextView; import android.widget.Toast; -import com.android.internal.content.PackageMonitor; import com.android.printspooler.R; import java.util.ArrayList; @@ -78,35 +64,33 @@ import java.util.List; /** * This is an activity for selecting a printer. */ -public final class SelectPrinterActivity extends Activity { +public final class SelectPrinterActivity extends Activity implements + LoaderManager.LoaderCallbacks<List<PrintServiceInfo>> { private static final String LOG_TAG = "SelectPrinterFragment"; - public static final String INTENT_EXTRA_PRINTER_ID = "INTENT_EXTRA_PRINTER_ID"; - - private static final String FRAGMENT_TAG_ADD_PRINTER_DIALOG = - "FRAGMENT_TAG_ADD_PRINTER_DIALOG"; + private static final int LOADER_ID_PRINT_REGISTRY = 1; + private static final int LOADER_ID_PRINT_REGISTRY_INT = 2; + private static final int LOADER_ID_ENABLED_PRINT_SERVICES = 3; - private static final String FRAGMENT_ARGUMENT_PRINT_SERVICE_INFOS = - "FRAGMENT_ARGUMENT_PRINT_SERVICE_INFOS"; + public static final String INTENT_EXTRA_PRINTER_ID = "INTENT_EXTRA_PRINTER_ID"; private static final String EXTRA_PRINTER_ID = "EXTRA_PRINTER_ID"; + private static final String KEY_NOT_FIRST_CREATE = "KEY_NOT_FIRST_CREATE"; + /** If there are any enabled print services */ private boolean mHasEnabledPrintServices; - private final ArrayList<PrintServiceInfo> mAddPrinterServices = - new ArrayList<>(); - private PrinterRegistry mPrinterRegistry; private ListView mListView; private AnnounceFilterResult mAnnounceFilterResult; - /** Monitor if new print services get enabled or disabled */ - private ContentObserver mPrintServicesDisabledObserver; - private PackageMonitor mPackageObserver; + private void startAddPrinterActivity() { + startActivity(new Intent(this, AddPrinterActivity.class)); + } @Override public void onCreate(Bundle savedInstanceState) { @@ -115,7 +99,8 @@ public final class SelectPrinterActivity extends Activity { setContentView(R.layout.select_printer_activity); - mPrinterRegistry = new PrinterRegistry(this, null); + mPrinterRegistry = new PrinterRegistry(this, null, LOADER_ID_PRINT_REGISTRY, + LOADER_ID_PRINT_REGISTRY_INT); // Hook up the list view. mListView = (ListView) findViewById(android.R.id.list); @@ -145,22 +130,67 @@ public final class SelectPrinterActivity extends Activity { } PrinterInfo printer = (PrinterInfo) mListView.getAdapter().getItem(position); - onPrinterSelected(printer.getId()); + + if (printer == null) { + startAddPrinterActivity(); + } else { + onPrinterSelected(printer.getId()); + } + } + }); + + findViewById(R.id.button).setOnClickListener(new OnClickListener() { + @Override public void onClick(View v) { + startAddPrinterActivity(); } }); registerForContextMenu(mListView); - // Display a notification about disabled services if there are disabled services - String disabledServicesSetting = Settings.Secure.getString(getContentResolver(), - Settings.Secure.DISABLED_PRINT_SERVICES); - if (!TextUtils.isEmpty(disabledServicesSetting)) { - Toast.makeText(this, getString(R.string.print_services_disabled_toast), - Toast.LENGTH_LONG).show(); + getLoaderManager().initLoader(LOADER_ID_ENABLED_PRINT_SERVICES, null, this); + + // On first creation: + // + // If no services are installed, instantly open add printer dialog. + // If some are disabled and some are enabled show a toast to notify the user + if (savedInstanceState == null || !savedInstanceState.getBoolean(KEY_NOT_FIRST_CREATE)) { + List<PrintServiceInfo> allServices = + ((PrintManager) getSystemService(Context.PRINT_SERVICE)) + .getPrintServices(PrintManager.ALL_SERVICES); + boolean hasEnabledServices = false; + boolean hasDisabledServices = false; + + if (allServices != null) { + final int numServices = allServices.size(); + for (int i = 0; i < numServices; i++) { + if (allServices.get(i).isEnabled()) { + hasEnabledServices = true; + } else { + hasDisabledServices = true; + } + } + } + + if (!hasEnabledServices) { + startAddPrinterActivity(); + } else if (hasDisabledServices) { + String disabledServicesSetting = Settings.Secure.getString(getContentResolver(), + Settings.Secure.DISABLED_PRINT_SERVICES); + if (!TextUtils.isEmpty(disabledServicesSetting)) { + Toast.makeText(this, getString(R.string.print_services_disabled_toast), + Toast.LENGTH_LONG).show(); + } + } } } @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putBoolean(KEY_NOT_FIRST_CREATE, true); + } + + @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); @@ -249,60 +279,13 @@ public final class SelectPrinterActivity extends Activity { * Adjust the UI if the enabled print services changed. */ private synchronized void onPrintServicesUpdate() { - updateServicesWithAddPrinterActivity(); updateEmptyView((DestinationAdapter)mListView.getAdapter()); invalidateOptionsMenu(); } - /** - * Register listener for changes to the enabled print services. - */ - private void registerServiceMonitor() { - // Listen for services getting disabled - mPrintServicesDisabledObserver = new ContentObserver(new Handler()) { - @Override - public void onChange(boolean selfChange) { - onPrintServicesUpdate(); - } - }; - - // Listen for services getting installed or uninstalled - mPackageObserver = new PackageMonitor() { - @Override - public void onPackageModified(String packageName) { - onPrintServicesUpdate(); - } - - @Override - public void onPackageRemoved(String packageName, int uid) { - onPrintServicesUpdate(); - } - - @Override - public void onPackageAdded(String packageName, int uid) { - onPrintServicesUpdate(); - } - }; - - getContentResolver().registerContentObserver( - Settings.Secure.getUriFor(Settings.Secure.DISABLED_PRINT_SERVICES), false, - mPrintServicesDisabledObserver); - - mPackageObserver.register(this, getMainLooper(), false); - } - - /** - * Unregister the listeners for changes to the enabled print services. - */ - private void unregisterServiceMonitorIfNeeded() { - getContentResolver().unregisterContentObserver(mPrintServicesDisabledObserver); - mPackageObserver.unregister(); - } - @Override public void onStart() { super.onStart(); - registerServiceMonitor(); onPrintServicesUpdate(); } @@ -316,19 +299,9 @@ public final class SelectPrinterActivity extends Activity { @Override public void onStop() { - unregisterServiceMonitorIfNeeded(); super.onStop(); } - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == R.id.action_add_printer) { - showAddPrinterSelectionDialog(); - return true; - } - return super.onOptionsItemSelected(item); - } - private void onPrinterSelected(PrinterId printerId) { Intent intent = new Intent(); intent.putExtra(INTENT_EXTRA_PRINTER_ID, printerId); @@ -336,68 +309,6 @@ public final class SelectPrinterActivity extends Activity { finish(); } - private void updateServicesWithAddPrinterActivity() { - mHasEnabledPrintServices = true; - mAddPrinterServices.clear(); - - // Get all enabled print services. - PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE); - List<PrintServiceInfo> enabledServices = printManager.getEnabledPrintServices(); - - // No enabled print services - done. - if (enabledServices.isEmpty()) { - mHasEnabledPrintServices = false; - return; - } - - // Find the services with valid add printers activities. - final int enabledServiceCount = enabledServices.size(); - for (int i = 0; i < enabledServiceCount; i++) { - PrintServiceInfo enabledService = enabledServices.get(i); - - // No add printers activity declared - next. - if (TextUtils.isEmpty(enabledService.getAddPrintersActivityName())) { - continue; - } - - ServiceInfo serviceInfo = enabledService.getResolveInfo().serviceInfo; - ComponentName addPrintersComponentName = new ComponentName( - serviceInfo.packageName, enabledService.getAddPrintersActivityName()); - Intent addPritnersIntent = new Intent() - .setComponent(addPrintersComponentName); - - // The add printers activity is valid - add it. - PackageManager pm = getPackageManager(); - List<ResolveInfo> resolvedActivities = pm.queryIntentActivities(addPritnersIntent, 0); - if (!resolvedActivities.isEmpty()) { - // The activity is a component name, therefore it is one or none. - ActivityInfo activityInfo = resolvedActivities.get(0).activityInfo; - if (activityInfo.exported - && (activityInfo.permission == null - || pm.checkPermission(activityInfo.permission, getPackageName()) - == PackageManager.PERMISSION_GRANTED)) { - mAddPrinterServices.add(enabledService); - } - } - } - } - - private void showAddPrinterSelectionDialog() { - FragmentTransaction transaction = getFragmentManager().beginTransaction(); - Fragment oldFragment = getFragmentManager().findFragmentByTag( - FRAGMENT_TAG_ADD_PRINTER_DIALOG); - if (oldFragment != null) { - transaction.remove(oldFragment); - } - AddPrinterAlertDialogFragment newFragment = new AddPrinterAlertDialogFragment(); - Bundle arguments = new Bundle(); - arguments.putParcelableArrayList(FRAGMENT_ARGUMENT_PRINT_SERVICE_INFOS, - mAddPrinterServices); - newFragment.setArguments(arguments); - transaction.add(newFragment, FRAGMENT_TAG_ADD_PRINTER_DIALOG); - transaction.commit(); - } - public void updateEmptyView(DestinationAdapter adapter) { if (mListView.getEmptyView() == null) { View emptyView = findViewById(R.id.empty_print_state); @@ -426,71 +337,28 @@ public final class SelectPrinterActivity extends Activity { } } - public static class AddPrinterAlertDialogFragment extends DialogFragment { - - private String mAddPrintServiceItem; - - @Override - @SuppressWarnings("unchecked") - public Dialog onCreateDialog(Bundle savedInstanceState) { - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) - .setTitle(R.string.choose_print_service); - - final List<PrintServiceInfo> printServices = (List<PrintServiceInfo>) (List<?>) - getArguments().getParcelableArrayList(FRAGMENT_ARGUMENT_PRINT_SERVICE_INFOS); - - final ArrayAdapter<String> adapter = new ArrayAdapter<>( - getActivity(), android.R.layout.simple_list_item_1); - final int printServiceCount = printServices.size(); - for (int i = 0; i < printServiceCount; i++) { - PrintServiceInfo printService = printServices.get(i); - adapter.add(printService.getResolveInfo().loadLabel( - getActivity().getPackageManager()).toString()); - } + @Override + public Loader<List<PrintServiceInfo>> onCreateLoader(int id, Bundle args) { + return new PrintServicesLoader((PrintManager) getSystemService(Context.PRINT_SERVICE), this, + PrintManager.ENABLED_SERVICES); + } - final String searchUri = Settings.Secure.getString(getActivity().getContentResolver(), - Settings.Secure.PRINT_SERVICE_SEARCH_URI); - final Intent viewIntent; - if (!TextUtils.isEmpty(searchUri)) { - Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(searchUri)); - if (getActivity().getPackageManager().resolveActivity(intent, 0) != null) { - viewIntent = intent; - mAddPrintServiceItem = getString(R.string.add_print_service_label); - adapter.add(mAddPrintServiceItem); - } else { - viewIntent = null; - } - } else { - viewIntent = null; - } + @Override + public void onLoadFinished(Loader<List<PrintServiceInfo>> loader, + List<PrintServiceInfo> data) { + if (data == null || data.isEmpty()) { + mHasEnabledPrintServices = false; + } else { + mHasEnabledPrintServices = true; + } - builder.setAdapter(adapter, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - String item = adapter.getItem(which); - if (item.equals(mAddPrintServiceItem)) { - try { - startActivity(viewIntent); - } catch (ActivityNotFoundException anfe) { - Log.w(LOG_TAG, "Couldn't start add printer activity", anfe); - } - } else { - PrintServiceInfo printService = printServices.get(which); - ComponentName componentName = new ComponentName( - printService.getResolveInfo().serviceInfo.packageName, - printService.getAddPrintersActivityName()); - Intent intent = new Intent(Intent.ACTION_MAIN); - intent.setComponent(componentName); - try { - startActivity(intent); - } catch (ActivityNotFoundException anfe) { - Log.w(LOG_TAG, "Couldn't start add printer activity", anfe); - } - } - } - }); + onPrintServicesUpdate(); + } - return builder.create(); + @Override + public void onLoaderReset(Loader<List<PrintServiceInfo>> loader) { + if (!isFinishing()) { + onLoadFinished(loader, null); } } @@ -592,14 +460,40 @@ public final class SelectPrinterActivity extends Activity { @Override public int getCount() { synchronized (mLock) { - return mFilteredPrinters.size(); + if (mFilteredPrinters.isEmpty()) { + return 0; + } else { + // Add "add printer" item to the end of the list. If the list is empty there is + // a link on the empty view + return mFilteredPrinters.size() + 1; + } + } + } + + @Override + public int getViewTypeCount() { + return 2; + } + + @Override + public int getItemViewType(int position) { + // Use separate view types for the "add printer" item an the items referring to printers + if (getItem(position) == null) { + return 0; + } else { + return 1; } } @Override public Object getItem(int position) { synchronized (mLock) { - return mFilteredPrinters.get(position); + if (position < mFilteredPrinters.size()) { + return mFilteredPrinters.get(position); + } else { + // Return null to mark this as the "add printer item" + return null; + } } } @@ -615,6 +509,18 @@ public final class SelectPrinterActivity extends Activity { @Override public View getView(int position, View convertView, ViewGroup parent) { + final PrinterInfo printer = (PrinterInfo) getItem(position); + + // Handle "add printer item" + if (printer == null) { + if (convertView == null) { + convertView = getLayoutInflater().inflate(R.layout.add_printer_list_item, + parent, false); + } + + return convertView; + } + if (convertView == null) { convertView = getLayoutInflater().inflate( R.layout.printer_list_item, parent, false); @@ -622,7 +528,6 @@ public final class SelectPrinterActivity extends Activity { convertView.setEnabled(isActionable(position)); - final PrinterInfo printer = (PrinterInfo) getItem(position); CharSequence title = printer.getName(); Drawable icon = printer.loadIcon(SelectPrinterActivity.this); @@ -661,7 +566,7 @@ public final class SelectPrinterActivity extends Activity { subtitleView.setVisibility(View.GONE); } - ImageView moreInfoView = (ImageView) convertView.findViewById(R.id.more_info); + LinearLayout moreInfoView = (LinearLayout) convertView.findViewById(R.id.more_info); if (printer.getInfoIntent() != null) { moreInfoView.setVisibility(View.VISIBLE); moreInfoView.setOnClickListener(new OnClickListener() { @@ -699,7 +604,12 @@ public final class SelectPrinterActivity extends Activity { public boolean isActionable(int position) { PrinterInfo printer = (PrinterInfo) getItem(position); - return printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE; + + if (printer == null) { + return true; + } else { + return printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE; + } } } diff --git a/packages/PrintSpooler/src/com/android/printspooler/util/PrintOptionUtils.java b/packages/PrintSpooler/src/com/android/printspooler/util/PrintOptionUtils.java deleted file mode 100644 index 446952d143ff..000000000000 --- a/packages/PrintSpooler/src/com/android/printspooler/util/PrintOptionUtils.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2014 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.pm.ServiceInfo; -import android.print.PrintManager; -import android.printservice.PrintServiceInfo; - -import java.util.List; - -public class PrintOptionUtils { - - private PrintOptionUtils() { - /* ignore - hide constructor */ - } - - /** - * Gets the advanced options activity name for a print service. - * - * @param context Context for accessing system resources. - * @param serviceName The print service name. - * @return The advanced options activity name or null. - */ - public static String getAdvancedOptionsActivityName(Context context, - ComponentName serviceName) { - PrintManager printManager = (PrintManager) context.getSystemService( - Context.PRINT_SERVICE); - List<PrintServiceInfo> printServices = printManager.getEnabledPrintServices(); - final int printServiceCount = printServices.size(); - for (int i = 0; i < printServiceCount; i ++) { - PrintServiceInfo printServiceInfo = printServices.get(i); - ServiceInfo serviceInfo = printServiceInfo.getResolveInfo().serviceInfo; - if (serviceInfo.name.equals(serviceName.getClassName()) - && serviceInfo.packageName.equals(serviceName.getPackageName())) { - return printServiceInfo.getAdvancedOptionsActivityName(); - } - } - return null; - } -} diff --git a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java index 9ec6da0cd148..27c829318665 100644 --- a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java @@ -31,6 +31,7 @@ import android.content.pm.ResolveInfo; import android.net.Uri; import android.os.Build; import android.os.UserHandle; +import android.print.PrintManager; import android.provider.CalendarContract; import android.provider.ContactsContract; import android.provider.MediaStore; @@ -579,7 +580,7 @@ final class DefaultPermissionGrantPolicy { // Print Spooler PackageParser.Package printSpoolerPackage = getSystemPackageLPr( - "com.android.printspooler"); + PrintManager.PRINT_SPOOLER_PACKAGE_NAME); if (printSpoolerPackage != null && doesPackageSupportRuntimePermissions(printSpoolerPackage)) { grantRuntimePermissionsLPw(printSpoolerPackage, LOCATION_PERMISSIONS, true, userId); diff --git a/services/print/java/com/android/server/print/PrintManagerService.java b/services/print/java/com/android/server/print/PrintManagerService.java index e6f41775111c..a9ab1d74b614 100644 --- a/services/print/java/com/android/server/print/PrintManagerService.java +++ b/services/print/java/com/android/server/print/PrintManagerService.java @@ -41,13 +41,16 @@ import android.os.UserManager; import android.print.IPrintDocumentAdapter; import android.print.IPrintJobStateChangeListener; import android.print.IPrintManager; +import android.print.IPrintServicesChangeListener; import android.print.IPrinterDiscoveryObserver; import android.print.PrintAttributes; import android.print.PrintJobId; import android.print.PrintJobInfo; +import android.print.PrintManager; import android.print.PrinterId; import android.printservice.PrintServiceInfo; import android.provider.Settings; +import android.util.Log; import android.util.SparseArray; import com.android.internal.content.PackageMonitor; @@ -66,6 +69,8 @@ import java.util.List; * PrintManager implementation is contained within. */ public final class PrintManagerService extends SystemService { + private static final String LOG_TAG = "PrintManagerService"; + private final PrintManagerImpl mPrintManagerImpl; public PrintManagerService(Context context) { @@ -253,7 +258,10 @@ public final class PrintManagerService extends SystemService { } @Override - public List<PrintServiceInfo> getEnabledPrintServices(int userId) { + public List<PrintServiceInfo> getPrintServices(int selectionFlags, int userId) { + Preconditions.checkFlagsArgument(selectionFlags, + PrintManager.DISABLED_SERVICES | PrintManager.ENABLED_SERVICES); + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); final UserState userState; synchronized (mLock) { @@ -262,34 +270,44 @@ public final class PrintManagerService extends SystemService { return null; } userState = getOrCreateUserStateLocked(resolvedUserId); - - // The user state might be updated via the same observer-set as the caller of this - // interface. If the caller is called back first the user state is not yet updated - // and the user gets and inconsistent view. Hence force an update. - userState.updateIfNeededLocked(); } final long identity = Binder.clearCallingIdentity(); try { - return userState.getEnabledPrintServices(); + return userState.getPrintServices(selectionFlags); } finally { Binder.restoreCallingIdentity(identity); } } @Override - public List<PrintServiceInfo> getInstalledPrintServices(int userId) { + public void setPrintServiceEnabled(ComponentName service, boolean isEnabled, int userId) { final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + final int appId = UserHandle.getAppId(Binder.getCallingUid()); + + try { + if (appId != Process.SYSTEM_UID && appId != UserHandle.getAppId( + mContext.getPackageManager().getPackageUidAsUser( + PrintManager.PRINT_SPOOLER_PACKAGE_NAME, resolvedUserId))) { + throw new SecurityException("Only system and print spooler can call this"); + } + } catch (PackageManager.NameNotFoundException e) { + Log.e(LOG_TAG, "Could not verify caller", e); + return; + } + + service = Preconditions.checkNotNull(service); + final UserState userState; synchronized (mLock) { - // Only the current group members can get installed services. + // Only the current group members can enable / disable services. if (resolveCallingProfileParentLocked(resolvedUserId) != getCurrentUserId()) { - return null; + return; } userState = getOrCreateUserStateLocked(resolvedUserId); } final long identity = Binder.clearCallingIdentity(); try { - return userState.getInstalledPrintServices(); + userState.setPrintServiceEnabled(service, isEnabled); } finally { Binder.restoreCallingIdentity(identity); } @@ -496,6 +514,50 @@ public final class PrintManagerService extends SystemService { } @Override + public void addPrintServicesChangeListener(IPrintServicesChangeListener listener, + int userId) throws RemoteException { + listener = Preconditions.checkNotNull(listener); + + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + final UserState userState; + synchronized (mLock) { + // Only the current group members can add a print services listener. + if (resolveCallingProfileParentLocked(resolvedUserId) != getCurrentUserId()) { + return; + } + userState = getOrCreateUserStateLocked(resolvedUserId); + } + final long identity = Binder.clearCallingIdentity(); + try { + userState.addPrintServicesChangeListener(listener); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void removePrintServicesChangeListener(IPrintServicesChangeListener listener, + int userId) { + listener = Preconditions.checkNotNull(listener); + + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + final UserState userState; + synchronized (mLock) { + // Only the current group members can remove a print job listener. + if (resolveCallingProfileParentLocked(resolvedUserId) != getCurrentUserId()) { + return; + } + userState = getOrCreateUserStateLocked(resolvedUserId); + } + final long identity = Binder.clearCallingIdentity(); + try { + userState.removePrintServicesChangeListener(listener); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { fd = Preconditions.checkNotNull(fd); pw = Preconditions.checkNotNull(pw); @@ -560,7 +622,7 @@ public final class PrintManagerService extends SystemService { UserState userState = getOrCreateUserStateLocked(getChangingUserId()); List<PrintServiceInfo> installedServices = userState - .getInstalledPrintServices(); + .getPrintServices(PrintManager.ALL_SERVICES); final int numInstalledServices = installedServices.size(); for (int i = 0; i < numInstalledServices; i++) { if (installedServices.get(i).getResolveInfo().serviceInfo.packageName @@ -601,7 +663,7 @@ public final class PrintManagerService extends SystemService { boolean stoppedSomePackages = false; List<PrintServiceInfo> enabledServices = userState - .getEnabledPrintServices(); + .getPrintServices(PrintManager.ENABLED_SERVICES); if (enabledServices == null) { return false; } diff --git a/services/print/java/com/android/server/print/RemotePrintSpooler.java b/services/print/java/com/android/server/print/RemotePrintSpooler.java index d179b95548f4..e1d8c6cdc3a1 100644 --- a/services/print/java/com/android/server/print/RemotePrintSpooler.java +++ b/services/print/java/com/android/server/print/RemotePrintSpooler.java @@ -36,6 +36,7 @@ import android.print.IPrintSpoolerCallbacks; import android.print.IPrintSpoolerClient; import android.print.PrintJobId; import android.print.PrintJobInfo; +import android.print.PrintManager; import android.print.PrinterId; import android.printservice.PrintService; import android.util.Slog; @@ -115,8 +116,8 @@ final class RemotePrintSpooler { mCallbacks = callbacks; mClient = new PrintSpoolerClient(this); mIntent = new Intent(); - mIntent.setComponent(new ComponentName("com.android.printspooler", - "com.android.printspooler.model.PrintSpoolerService")); + mIntent.setComponent(new ComponentName(PrintManager.PRINT_SPOOLER_PACKAGE_NAME, + PrintManager.PRINT_SPOOLER_PACKAGE_NAME + ".model.PrintSpoolerService")); } public final List<PrintJobInfo> getPrintJobInfos(ComponentName componentName, int state, diff --git a/services/print/java/com/android/server/print/UserState.java b/services/print/java/com/android/server/print/UserState.java index fcf2fc8e7860..f2f555b553d5 100644 --- a/services/print/java/com/android/server/print/UserState.java +++ b/services/print/java/com/android/server/print/UserState.java @@ -44,6 +44,7 @@ import android.os.RemoteException; import android.os.UserHandle; import android.print.IPrintDocumentAdapter; import android.print.IPrintJobStateChangeListener; +import android.print.IPrintServicesChangeListener; import android.print.IPrinterDiscoveryObserver; import android.print.PrintAttributes; import android.print.PrintJobId; @@ -121,6 +122,8 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks { private List<PrintJobStateChangeListenerRecord> mPrintJobStateChangeListenerRecords; + private List<PrintServicesChangeListenerRecord> mPrintServicesChangeListenerRecords; + private boolean mDestroyed; public UserState(Context context, int userId, Object lock) { @@ -342,29 +345,63 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks { mSpooler.setPrintJobState(printJobId, PrintJobInfo.STATE_QUEUED, null); } - public @Nullable List<PrintServiceInfo> getEnabledPrintServices() { + public @Nullable List<PrintServiceInfo> getPrintServices(int selectionFlags) { synchronized (mLock) { - List<PrintServiceInfo> enabledServices = null; + List<PrintServiceInfo> selectedServices = null; final int installedServiceCount = mInstalledServices.size(); for (int i = 0; i < installedServiceCount; i++) { PrintServiceInfo installedService = mInstalledServices.get(i); + ComponentName componentName = new ComponentName( installedService.getResolveInfo().serviceInfo.packageName, installedService.getResolveInfo().serviceInfo.name); - if (mActiveServices.containsKey(componentName)) { - if (enabledServices == null) { - enabledServices = new ArrayList<PrintServiceInfo>(); + + // Update isEnabled under the same lock the final returned list is created + installedService.setIsEnabled(mActiveServices.containsKey(componentName)); + + if (installedService.isEnabled()) { + if ((selectionFlags & PrintManager.ENABLED_SERVICES) == 0) { + continue; } - enabledServices.add(installedService); + } else { + if ((selectionFlags & PrintManager.DISABLED_SERVICES) == 0) { + continue; + } + } + + if (selectedServices == null) { + selectedServices = new ArrayList<>(); } + selectedServices.add(installedService); } - return enabledServices; + return selectedServices; } } - public List<PrintServiceInfo> getInstalledPrintServices() { + public void setPrintServiceEnabled(@NonNull ComponentName serviceName, boolean isEnabled) { synchronized (mLock) { - return mInstalledServices; + boolean isChanged = false; + if (isEnabled) { + isChanged = mDisabledServices.remove(serviceName); + } else { + // Make sure to only disable services that are currently installed + final int numServices = mInstalledServices.size(); + for (int i = 0; i < numServices; i++) { + PrintServiceInfo service = mInstalledServices.get(i); + + if (service.getComponentName().equals(serviceName)) { + mDisabledServices.add(serviceName); + isChanged = true; + break; + } + } + } + + if (isChanged) { + writeDisabledPrintServicesLocked(mDisabledServices); + + onConfigurationChangedLocked(); + } } } @@ -523,6 +560,44 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks { } } + public void addPrintServicesChangeListener(@NonNull IPrintServicesChangeListener listener) + throws RemoteException { + synchronized (mLock) { + throwIfDestroyedLocked(); + if (mPrintServicesChangeListenerRecords == null) { + mPrintServicesChangeListenerRecords = new ArrayList<>(); + } + mPrintServicesChangeListenerRecords.add( + new PrintServicesChangeListenerRecord(listener) { + @Override + public void onBinderDied() { + mPrintServicesChangeListenerRecords.remove(this); + } + }); + } + } + + public void removePrintServicesChangeListener(@NonNull IPrintServicesChangeListener listener) { + synchronized (mLock) { + throwIfDestroyedLocked(); + if (mPrintServicesChangeListenerRecords == null) { + return; + } + final int recordCount = mPrintServicesChangeListenerRecords.size(); + for (int i = 0; i < recordCount; i++) { + PrintServicesChangeListenerRecord record = + mPrintServicesChangeListenerRecords.get(i); + if (record.listener.asBinder().equals(listener.asBinder())) { + mPrintServicesChangeListenerRecords.remove(i); + break; + } + } + if (mPrintServicesChangeListenerRecords.isEmpty()) { + mPrintServicesChangeListenerRecords = null; + } + } + } + @Override public void onPrintJobStateChanged(PrintJobInfo printJob) { mPrintJobForAppCache.onPrintJobStateChanged(printJob); @@ -530,6 +605,10 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks { printJob.getAppId(), 0, printJob.getId()).sendToTarget(); } + public void onPrintServicesChanged() { + mHandler.obtainMessage(UserStateHandler.MSG_DISPATCH_PRINT_SERVICES_CHANGED).sendToTarget(); + } + @Override public void onPrintersAdded(List<PrinterInfo> printers) { synchronized (mLock) { @@ -894,6 +973,8 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks { iterator.remove(); } } + + onPrintServicesChanged(); } private void addServiceLocked(RemotePrintService service) { @@ -978,8 +1059,29 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks { } } + private void handleDispatchPrintServicesChanged() { + final List<PrintServicesChangeListenerRecord> records; + synchronized (mLock) { + if (mPrintServicesChangeListenerRecords == null) { + return; + } + records = new ArrayList<>(mPrintServicesChangeListenerRecords); + } + final int recordCount = records.size(); + for (int i = 0; i < recordCount; i++) { + PrintServicesChangeListenerRecord record = records.get(i); + + try { + record.listener.onPrintServicesChanged();; + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error notifying for print services change", re); + } + } + } + private final class UserStateHandler extends Handler { public static final int MSG_DISPATCH_PRINT_JOB_STATE_CHANGED = 1; + public static final int MSG_DISPATCH_PRINT_SERVICES_CHANGED = 2; public UserStateHandler(Looper looper) { super(looper, null, false); @@ -987,10 +1089,17 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks { @Override public void handleMessage(Message message) { - if (message.what == MSG_DISPATCH_PRINT_JOB_STATE_CHANGED) { - PrintJobId printJobId = (PrintJobId) message.obj; - final int appId = message.arg1; - handleDispatchPrintJobStateChanged(printJobId, appId); + switch (message.what) { + case MSG_DISPATCH_PRINT_JOB_STATE_CHANGED: + PrintJobId printJobId = (PrintJobId) message.obj; + final int appId = message.arg1; + handleDispatchPrintJobStateChanged(printJobId, appId); + break; + case MSG_DISPATCH_PRINT_SERVICES_CHANGED: + handleDispatchPrintServicesChanged(); + break; + default: + // not reached } } } @@ -1015,6 +1124,23 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks { public abstract void onBinderDied(); } + private abstract class PrintServicesChangeListenerRecord implements DeathRecipient { + @NonNull final IPrintServicesChangeListener listener; + + public PrintServicesChangeListenerRecord(@NonNull IPrintServicesChangeListener listener) throws RemoteException { + this.listener = listener; + listener.asBinder().linkToDeath(this, 0); + } + + @Override + public void binderDied() { + listener.asBinder().unlinkToDeath(this, 0); + onBinderDied(); + } + + public abstract void onBinderDied(); + } + private class PrinterDiscoverySessionMediator { private final ArrayMap<PrinterId, PrinterInfo> mPrinters = new ArrayMap<PrinterId, PrinterInfo>(); |