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

am: 02a465ace7

* commit '02a465ace7063cc271f5565f78857dc22a14ca56':
  Add "app printer activity" and always keep the print service state updated. Also fiddle with the UI to use more standard values.
This commit is contained in:
Philip P. Moltmann
2016-03-07 19:30:45 +00:00
committed by android-build-merger
39 changed files with 2005 additions and 510 deletions

View File

@@ -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 \

View File

@@ -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,

View File

@@ -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();
}

View File

@@ -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();
@@ -532,6 +626,26 @@ public final class PrintManager {
return new PrinterDiscoverySession(mService, mContext, mUserId);
}
/**
* 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
*/
@@ -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();
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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();
@@ -179,6 +182,24 @@ public final class PrintServiceInfo implements Parcelable {
return mId;
}
/**
* 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}.
*
@@ -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);

View File

@@ -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")

View File

@@ -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()
@@ -183,6 +168,18 @@ public class IPrintManagerParametersTest extends BasePrintTest {
new Handler(Looper.getMainLooper()));
}
/**
* 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.
*
@@ -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);

View File

@@ -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>

View File

@@ -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>

View File

@@ -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" />

View File

@@ -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>

View File

@@ -18,7 +18,7 @@
android:shape="oval">
<solid
android:color="@color/promoted_action_background_color">
android:color="?android:attr/colorAccent">
</solid>
<size

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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" />

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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] -->

View File

@@ -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>

View File

@@ -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="PrintActivity" parent="@android:style/Theme.DeviceDefault">
<style name="Theme.SelectPrinterActivity"
parent="android:style/Theme.DeviceDefault.Light.DarkActionBar">
<item name="android:textAppearanceListItemSecondary">@style/ListItemSecondary</item>
</style>
<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>

View File

@@ -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);
}
}
}
}
}

View File

@@ -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--) {

View File

@@ -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;
}

View File

@@ -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));
}
};

View File

@@ -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";
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;
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 String FRAGMENT_ARGUMENT_PRINT_SERVICE_INFOS =
"FRAGMENT_ARGUMENT_PRINT_SERVICE_INFOS";
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,21 +130,66 @@ 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 {
@Override
public Loader<List<PrintServiceInfo>> onCreateLoader(int id, Bundle args) {
return new PrintServicesLoader((PrintManager) getSystemService(Context.PRINT_SERVICE), this,
PrintManager.ENABLED_SERVICES);
}
private String mAddPrintServiceItem;
@Override
public void onLoadFinished(Loader<List<PrintServiceInfo>> loader,
List<PrintServiceInfo> data) {
if (data == null || data.isEmpty()) {
mHasEnabledPrintServices = false;
} else {
mHasEnabledPrintServices = true;
}
@Override
@SuppressWarnings("unchecked")
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
.setTitle(R.string.choose_print_service);
onPrintServicesUpdate();
}
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());
}
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;
}
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);
}
}
}
});
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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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);
}
@@ -495,6 +513,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);
@@ -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;
}

View File

@@ -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,

View File

@@ -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;
}
} else {
if ((selectionFlags & PrintManager.DISABLED_SERVICES) == 0) {
continue;
}
enabledServices.add(installedService);
}
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>();