From 44720af55a8fdf991929983dad5d53c02851dd1e Mon Sep 17 00:00:00 2001 From: Svetoslav Ganov Date: Tue, 20 Aug 2013 16:32:53 -0700 Subject: [PATCH] Print UI bug fixing and printer discovery refactoring. 1. Added support for selecting a printer from the all printers activity that is not in the initial printer selection drop down. The user initially sees a sub set of the printers in the drop down and the last option is to see all printers in a separate activity. Some of the printers in the all printers activity are not shown in the initial drop down. 2. Refactored printer discovery by adding (private for now) printer discovery app facing APIs. These APIs are needed to support multiple printer selection activities (print dialog and all printers activities) and also the settings for showing all printers for a service. Now multiple apps can request observing for printers and there is a centralized mediator that ensures the same printer discovery session is used. The mediator dispatches printer discovery specific requests to print services. It also aggregates discovered printers and delivers them to the interested apps. The mediator minimizes printer discovery session creation and starting and stopping discovery by sharing the same discovery session and discovery window with multiple apps. Lastly, the mediator takes care of print services enabled during discovery by bringing them up to the current discovery state (create discovery session and start discovery if needed). The mediator also reports disappearing of the printers of a service removed during discovery and notifies a newly registered observers for the currnet printers if the observers are added during an active printer discovery session. 3. Fixed bugs in the print UI and implemented some UX tweaks. Change-Id: I4d0b0c5a6c6f1809b2ba5dbc8e9d63ab3d48f1ef --- Android.mk | 1 + core/java/android/os/RemoteCallbackList.java | 2 - core/java/android/print/IPrintManager.aidl | 10 + core/java/android/print/IPrintSpooler.aidl | 6 - .../android/print/IPrintSpoolerClient.aidl | 8 - .../print/IPrinterDiscoveryObserver.aidl | 32 + core/java/android/print/PrintManager.java | 7 + .../print/PrinterDiscoverySession.java | 297 ++++++++ packages/PrintSpooler/AndroidManifest.xml | 8 +- ...nt_job_config_activity_content_editing.xml | 378 +++++----- packages/PrintSpooler/res/values/strings.xml | 11 +- packages/PrintSpooler/res/values/styles.xml | 14 + packages/PrintSpooler/res/values/themes.xml | 2 +- .../printspooler/FusedPrintersProvider.java | 108 +-- .../printspooler/PrintJobConfigActivity.java | 475 ++++++++----- .../printspooler/PrintSpoolerService.java | 218 ------ .../server/print/PrintManagerService.java | 80 +++ .../server/print/RemotePrintService.java | 11 +- .../server/print/RemotePrintSpooler.java | 144 ---- .../com/android/server/print/UserState.java | 669 ++++++++++++++++-- 20 files changed, 1626 insertions(+), 855 deletions(-) create mode 100644 core/java/android/print/IPrinterDiscoveryObserver.aidl create mode 100644 core/java/android/print/PrinterDiscoverySession.java diff --git a/Android.mk b/Android.mk index 998e65d4b9c5a..f6066403620e9 100644 --- a/Android.mk +++ b/Android.mk @@ -162,6 +162,7 @@ LOCAL_SRC_FILES += \ core/java/android/os/IVibratorService.aidl \ core/java/android/service/notification/INotificationListener.aidl \ core/java/android/print/ILayoutResultCallback.aidl \ + core/java/android/print/IPrinterDiscoveryObserver.aidl \ core/java/android/print/IPrintDocumentAdapter.aidl \ core/java/android/print/IPrintClient.aidl \ core/java/android/print/IPrintManager.aidl \ diff --git a/core/java/android/os/RemoteCallbackList.java b/core/java/android/os/RemoteCallbackList.java index c1d4ae9be79ef..d2a9cdc0a1308 100644 --- a/core/java/android/os/RemoteCallbackList.java +++ b/core/java/android/os/RemoteCallbackList.java @@ -18,8 +18,6 @@ package android.os; import android.util.ArrayMap; -import java.util.HashMap; - /** * Takes care of the grunt work of maintaining a list of remote interfaces, * typically for the use of performing callbacks from a diff --git a/core/java/android/print/IPrintManager.aidl b/core/java/android/print/IPrintManager.aidl index 71550960abcd5..3bfd9a1cc32b2 100644 --- a/core/java/android/print/IPrintManager.aidl +++ b/core/java/android/print/IPrintManager.aidl @@ -16,8 +16,10 @@ package android.print; +import android.print.IPrinterDiscoveryObserver; import android.print.IPrintDocumentAdapter; import android.print.IPrintClient; +import android.print.PrinterId; import android.print.PrintJobInfo; import android.print.PrintAttributes; @@ -34,4 +36,12 @@ interface IPrintManager { int appId, int userId); void cancelPrintJob(int printJobId, int appId, int userId); void restartPrintJob(int printJobId, int appId, int userId); + + void createPrinterDiscoverySession(in IPrinterDiscoveryObserver observer, int userId); + void startPrinterDiscovery(in IPrinterDiscoveryObserver observer, + in List priorityList, int userId); + void stopPrinterDiscovery(in IPrinterDiscoveryObserver observer, int userId); + void requestPrinterUpdate(in PrinterId printerId, int userId); + void destroyPrinterDiscoverySession(in IPrinterDiscoveryObserver observer, + int userId); } diff --git a/core/java/android/print/IPrintSpooler.aidl b/core/java/android/print/IPrintSpooler.aidl index 5c8a22ae5de04..0a77dab63e965 100644 --- a/core/java/android/print/IPrintSpooler.aidl +++ b/core/java/android/print/IPrintSpooler.aidl @@ -18,7 +18,6 @@ package android.print; import android.content.ComponentName; import android.os.ParcelFileDescriptor; -import android.print.PrinterId; import android.print.IPrintDocumentAdapter; import android.print.IPrintClient; import android.print.IPrintSpoolerClient; @@ -47,9 +46,4 @@ oneway interface IPrintSpooler { int sequence); void writePrintJobData(in ParcelFileDescriptor fd, int printJobId); void setClient(IPrintSpoolerClient client); - - // Printer discovery APIs - void onPrintersAdded(in List printers); - void onPrintersRemoved(in List printerIds); - void onPrintersUpdated(in List printerIds); } diff --git a/core/java/android/print/IPrintSpoolerClient.aidl b/core/java/android/print/IPrintSpoolerClient.aidl index da6012029c686..8b511d6815d12 100644 --- a/core/java/android/print/IPrintSpoolerClient.aidl +++ b/core/java/android/print/IPrintSpoolerClient.aidl @@ -17,7 +17,6 @@ package android.print; import android.content.ComponentName; -import android.print.PrinterId; import android.print.PrintJobInfo; @@ -30,11 +29,4 @@ oneway interface IPrintSpoolerClient { void onPrintJobQueued(in PrintJobInfo printJob); void onAllPrintJobsForServiceHandled(in ComponentName printService); void onAllPrintJobsHandled(); - - // Printer discovery APIs - void createPrinterDiscoverySession(); - void startPrinterDiscovery(in List priorityList); - void stopPrinterDiscovery(); - void requestPrinterUpdate(in PrinterId printerId); - void destroyPrinterDiscoverySession(); } diff --git a/core/java/android/print/IPrinterDiscoveryObserver.aidl b/core/java/android/print/IPrinterDiscoveryObserver.aidl new file mode 100644 index 0000000000000..625f38384a6ce --- /dev/null +++ b/core/java/android/print/IPrinterDiscoveryObserver.aidl @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2013 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.print.IPrintClient; +import android.print.PrinterId; +import android.print.PrinterInfo; + +/** + * Interface for observing discovered printers by a discovery session. + * + * @hide + */ +oneway interface IPrinterDiscoveryObserver { + void onPrintersAdded(in List printers); + void onPrintersRemoved(in List printerIds); + void onPrintersUpdated(in List printerIds); +} diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java index 531dcb224a3c7..d3e35c3f6445a 100644 --- a/core/java/android/print/PrintManager.java +++ b/core/java/android/print/PrintManager.java @@ -204,6 +204,13 @@ public final class PrintManager { return null; } + /** + * @hide + */ + public PrinterDiscoverySession createPrinterDiscoverySession() { + return new PrinterDiscoverySession(mService, mContext, mUserId); + } + private static final class PrintClient extends IPrintClient.Stub { private final WeakReference mWeakPrintManager; diff --git a/core/java/android/print/PrinterDiscoverySession.java b/core/java/android/print/PrinterDiscoverySession.java new file mode 100644 index 0000000000000..8fbdd9c005777 --- /dev/null +++ b/core/java/android/print/PrinterDiscoverySession.java @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2013 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.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.util.ArrayMap; +import android.util.Log; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * @hide + */ +public final class PrinterDiscoverySession { + + private static final String LOG_TAG ="PrinterDiscoverySession"; + + private static final int MSG_PRINTERS_ADDED = 1; + private static final int MSG_PRINTERS_REMOVED = 2; + private static final int MSG_PRINTERS_UPDATED = 3; + + private final ArrayMap mPrinters = + new ArrayMap(); + + private final IPrintManager mPrintManager; + + private final int mUserId; + + private final Handler mHandler; + + private IPrinterDiscoveryObserver mObserver; + + private OnPrintersChangeListener mListener; + + private boolean mIsPrinterDiscoveryStarted; + + public static interface OnPrintersChangeListener { + public void onPrintersChanged(); + } + + PrinterDiscoverySession(IPrintManager printManager, Context context, int userId) { + mPrintManager = printManager; + mUserId = userId; + mHandler = new SessionHandler(context.getMainLooper()); + mObserver = new PrinterDiscoveryObserver(this); + try { + mPrintManager.createPrinterDiscoverySession(mObserver, mUserId); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error creating printer discovery session", re); + } + } + + public final void startPrinterDisovery(List priorityList) { + if (isDestroyed()) { + Log.w(LOG_TAG, "Ignoring start printers dsicovery - session destroyed"); + } + if (!mIsPrinterDiscoveryStarted) { + mIsPrinterDiscoveryStarted = true; + try { + mPrintManager.startPrinterDiscovery(mObserver, priorityList, mUserId); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error starting printer discovery", re); + } + } + } + + public final void stopPrinterDiscovery() { + if (isDestroyed()) { + Log.w(LOG_TAG, "Ignoring stop printers discovery - session destroyed"); + } + if (mIsPrinterDiscoveryStarted) { + mIsPrinterDiscoveryStarted = false; + try { + mPrintManager.stopPrinterDiscovery(mObserver, mUserId); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error stopping printer discovery", re); + } + } + } + + public final void requestPrinterUpdate(PrinterId printerId) { + if (isDestroyed()) { + Log.w(LOG_TAG, "Ignoring reqeust printer update - session destroyed"); + } + try { + mPrintManager.requestPrinterUpdate(printerId, mUserId); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error requesting printer update", re); + } + } + + public final void destroy() { + if (isDestroyed()) { + Log.w(LOG_TAG, "Ignoring destroy - session destroyed"); + } + destroyNoCheck(); + } + + public final List getPrinters() { + if (isDestroyed()) { + Log.w(LOG_TAG, "Ignoring get printers - session destroyed"); + return Collections.emptyList(); + } + return new ArrayList(mPrinters.values()); + } + + public final boolean isDestroyed() { + throwIfNotCalledOnMainThread(); + return isDestroyedNoCheck(); + } + + public final boolean isPrinterDiscoveryStarted() { + throwIfNotCalledOnMainThread(); + return mIsPrinterDiscoveryStarted; + } + + public final void setOnPrintersChangeListener(OnPrintersChangeListener listener) { + throwIfNotCalledOnMainThread(); + mListener = listener; + } + + @Override + protected final void finalize() throws Throwable { + if (!isDestroyedNoCheck()) { + Log.e(LOG_TAG, "Destroying leaked printer discovery session"); + destroyNoCheck(); + } + super.finalize(); + } + + private boolean isDestroyedNoCheck() { + return (mObserver == null); + } + + private void destroyNoCheck() { + stopPrinterDiscovery(); + try { + mPrintManager.destroyPrinterDiscoverySession(mObserver, mUserId); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error destroying printer discovery session", re); + } finally { + mObserver = null; + mPrinters.clear(); + } + } + + private void handlePrintersAdded(List printers) { + if (isDestroyed()) { + return; + } + boolean printersChanged = false; + final int addedPrinterCount = printers.size(); + for (int i = 0; i < addedPrinterCount; i++) { + PrinterInfo addedPrinter = printers.get(i); + if (mPrinters.get(addedPrinter.getId()) == null) { + mPrinters.put(addedPrinter.getId(), addedPrinter); + printersChanged = true; + } + } + if (printersChanged) { + notifyOnPrintersChanged(); + } + } + + private void handlePrintersRemoved(List printerIds) { + if (isDestroyed()) { + return; + } + boolean printersChanged = false; + final int removedPrinterIdCount = printerIds.size(); + for (int i = 0; i < removedPrinterIdCount; i++) { + PrinterId removedPrinterId = printerIds.get(i); + if (mPrinters.remove(removedPrinterId) != null) { + printersChanged = true; + } + } + if (printersChanged) { + notifyOnPrintersChanged(); + } + } + + private void handlePrintersUpdated(List printers) { + if (isDestroyed()) { + return; + } + boolean printersChanged = false; + final int updatedPrinterCount = printers.size(); + for (int i = 0; i < updatedPrinterCount; i++) { + PrinterInfo updatedPrinter = printers.get(i); + PrinterInfo oldPrinter = mPrinters.get(updatedPrinter.getId()); + if (oldPrinter != null && !oldPrinter.equals(updatedPrinter)) { + mPrinters.put(updatedPrinter.getId(), updatedPrinter); + printersChanged = true; + } + } + if (printersChanged) { + notifyOnPrintersChanged(); + } + } + + private void notifyOnPrintersChanged() { + if (mListener != null) { + mListener.onPrintersChanged(); + } + } + + private static void throwIfNotCalledOnMainThread() { + if (!Looper.getMainLooper().isCurrentThread()) { + throw new IllegalAccessError("must be called from the main thread"); + } + } + + private final class SessionHandler extends Handler { + + public SessionHandler(Looper looper) { + super(looper, null, false); + } + + @Override + @SuppressWarnings("unchecked") + public void handleMessage(Message message) { + switch (message.what) { + case MSG_PRINTERS_ADDED: { + List printers = (List) message.obj; + handlePrintersAdded(printers); + } break; + + case MSG_PRINTERS_REMOVED: { + List printerIds = (List) message.obj; + handlePrintersRemoved(printerIds); + } break; + + case MSG_PRINTERS_UPDATED: { + List printers = (List) message.obj; + handlePrintersUpdated(printers); + } break; + } + } + } + + private static final class PrinterDiscoveryObserver extends IPrinterDiscoveryObserver.Stub { + + private final WeakReference mWeakSession; + + public PrinterDiscoveryObserver(PrinterDiscoverySession session) { + mWeakSession = new WeakReference(session); + } + + @Override + public void onPrintersAdded(List printers) { + PrinterDiscoverySession session = mWeakSession.get(); + if (session != null) { + session.mHandler.obtainMessage(MSG_PRINTERS_ADDED, + printers).sendToTarget(); + } + } + + @Override + public void onPrintersRemoved(List printerIds) { + PrinterDiscoverySession session = mWeakSession.get(); + if (session != null) { + session.mHandler.obtainMessage(MSG_PRINTERS_REMOVED, + printerIds).sendToTarget(); + } + } + + @Override + public void onPrintersUpdated(List printers) { + PrinterDiscoverySession session = mWeakSession.get(); + if (session != null) { + session.mHandler.obtainMessage(MSG_PRINTERS_UPDATED, + printers).sendToTarget(); + } + } + } +} diff --git a/packages/PrintSpooler/AndroidManifest.xml b/packages/PrintSpooler/AndroidManifest.xml index 1f10af82e8767..ab7ea0973df7a 100644 --- a/packages/PrintSpooler/AndroidManifest.xml +++ b/packages/PrintSpooler/AndroidManifest.xml @@ -18,12 +18,12 @@ --> - + @@ -36,7 +36,8 @@ + android:allowBackup= "false" + android:supportsRtl="true"> diff --git a/packages/PrintSpooler/res/layout/print_job_config_activity_content_editing.xml b/packages/PrintSpooler/res/layout/print_job_config_activity_content_editing.xml index 84c41de039213..252dd81b387c3 100644 --- a/packages/PrintSpooler/res/layout/print_job_config_activity_content_editing.xml +++ b/packages/PrintSpooler/res/layout/print_job_config_activity_content_editing.xml @@ -14,208 +14,234 @@ limitations under the License. --> - + android:orientation="vertical" + android:scrollbars="vertical" + android:background="@color/editable_background"> - + android:columnCount="2"> - + + + + + + + + + + + + + + + + android:layout_row="2" + android:layout_column="1" + android:layout_gravity="fill_horizontal" + style="@style/PrintOptionSpinnerStyle"> + - + + - - + - + + - - + + - - + - + + - - + + - - + - + + - - + + - - + - + + - - + + - - + - + + - - + - - + - - + - - - - - - - - - - diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml index 41fc5168791f2..ee3cf84089c9b 100644 --- a/packages/PrintSpooler/res/values/strings.xml +++ b/packages/PrintSpooler/res/values/strings.xml @@ -22,6 +22,9 @@ Print + + Save + DESTIINATION @@ -41,7 +44,7 @@ PAGES (%1$s) - e.g. 1–5, 8 + e.g. 1–5, 8, 11–13 Print preview @@ -62,11 +65,7 @@ Save as PDF - All printers\.\.\. - - - Searching for printers\.\.\. + All printers… diff --git a/packages/PrintSpooler/res/values/styles.xml b/packages/PrintSpooler/res/values/styles.xml index 702adf4714162..fe11c93a12fc4 100644 --- a/packages/PrintSpooler/res/values/styles.xml +++ b/packages/PrintSpooler/res/values/styles.xml @@ -27,7 +27,21 @@ 0dip 0dip 150dip + 200dip ?android:attr/listPreferredItemHeightSmall + + + + diff --git a/packages/PrintSpooler/res/values/themes.xml b/packages/PrintSpooler/res/values/themes.xml index 831b0ecb2a47d..bb41527baacc3 100644 --- a/packages/PrintSpooler/res/values/themes.xml +++ b/packages/PrintSpooler/res/values/themes.xml @@ -18,7 +18,7 @@