summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Philip P. Moltmann <moltmann@google.com> 2016-03-07 18:14:12 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2016-03-07 18:14:14 +0000
commit02a465ace7063cc271f5565f78857dc22a14ca56 (patch)
tree5275e50b45c91fd893b1f0decc616c51b2b47cfa
parent64177e51e4148e13f8ba45c025316660aff7f79c (diff)
parent66c96591e2ddb464c67e60dbf4193ef4ec8a620b (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
-rw-r--r--Android.mk1
-rw-r--r--core/java/android/print/IPrintManager.aidl45
-rw-r--r--core/java/android/print/IPrintServicesChangeListener.aidl26
-rw-r--r--core/java/android/print/PrintManager.java184
-rw-r--r--core/java/android/print/PrintServicesLoader.java125
-rw-r--r--core/java/android/printservice/PrintServiceInfo.java23
-rw-r--r--core/tests/coretests/src/android/print/BasePrintTest.java6
-rw-r--r--core/tests/coretests/src/android/print/IPrintManagerParametersTest.java166
-rw-r--r--packages/PrintSpooler/AndroidManifest.xml11
-rw-r--r--packages/PrintSpooler/res/drawable/ic_add.xml2
-rw-r--r--packages/PrintSpooler/res/drawable/ic_print.xml2
-rw-r--r--packages/PrintSpooler/res/drawable/page_selector_background.xml4
-rw-r--r--packages/PrintSpooler/res/drawable/print_button_background.xml2
-rw-r--r--packages/PrintSpooler/res/layout/add_printer_activity.xml29
-rw-r--r--packages/PrintSpooler/res/layout/add_printer_list_header.xml28
-rw-r--r--packages/PrintSpooler/res/layout/add_printer_list_item.xml46
-rw-r--r--packages/PrintSpooler/res/layout/all_print_services_list_item.xml38
-rw-r--r--packages/PrintSpooler/res/layout/disabled_print_services_list_item.xml55
-rw-r--r--packages/PrintSpooler/res/layout/enabled_print_services_list_item.xml54
-rw-r--r--packages/PrintSpooler/res/layout/printer_dropdown_item.xml15
-rw-r--r--packages/PrintSpooler/res/layout/printer_dropdown_prompt.xml7
-rw-r--r--packages/PrintSpooler/res/layout/printer_list_item.xml45
-rw-r--r--packages/PrintSpooler/res/layout/select_printer_activity.xml17
-rw-r--r--packages/PrintSpooler/res/menu/select_printer_activity.xml8
-rw-r--r--packages/PrintSpooler/res/values/colors.xml2
-rw-r--r--packages/PrintSpooler/res/values/donottranslate.xml2
-rw-r--r--packages/PrintSpooler/res/values/strings.xml32
-rw-r--r--packages/PrintSpooler/res/values/styles.xml34
-rw-r--r--packages/PrintSpooler/res/values/themes.xml11
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/ui/AddPrinterActivity.java563
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java86
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java114
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/ui/PrinterRegistry.java68
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java360
-rw-r--r--packages/PrintSpooler/src/com/android/printspooler/util/PrintOptionUtils.java56
-rw-r--r--services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java3
-rw-r--r--services/print/java/com/android/server/print/PrintManagerService.java88
-rw-r--r--services/print/java/com/android/server/print/RemotePrintSpooler.java5
-rw-r--r--services/print/java/com/android/server/print/UserState.java152
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>();