Change hasCustomPrintIcon and setStatus as requested by API council.
Bonus: null advanced keys produced a exception deep in the print
spooler. Hence prevent null keys on the surface from now on.
Bug: 27716355
Change-Id: I3c064956f4e670cd7091437ade06605aa8d797b0
diff --git a/api/current.txt b/api/current.txt
index 3fc1673..b67afa1 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -30268,7 +30268,7 @@
method public android.print.PrinterInfo build();
method public android.print.PrinterInfo.Builder setCapabilities(android.print.PrinterCapabilitiesInfo);
method public android.print.PrinterInfo.Builder setDescription(java.lang.String);
- method public android.print.PrinterInfo.Builder setHasCustomPrinterIcon();
+ method public android.print.PrinterInfo.Builder setHasCustomPrinterIcon(boolean);
method public android.print.PrinterInfo.Builder setIconResourceId(int);
method public android.print.PrinterInfo.Builder setInfoIntent(android.app.PendingIntent);
method public android.print.PrinterInfo.Builder setName(java.lang.String);
@@ -30320,6 +30320,7 @@
method public boolean isStarted();
method public void setProgress(float);
method public void setStatus(java.lang.CharSequence);
+ method public void setStatus(int);
method public boolean setTag(java.lang.String);
method public boolean start();
}
diff --git a/api/system-current.txt b/api/system-current.txt
index 48e1bd0..6652243 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -32583,7 +32583,7 @@
method public android.print.PrinterInfo build();
method public android.print.PrinterInfo.Builder setCapabilities(android.print.PrinterCapabilitiesInfo);
method public android.print.PrinterInfo.Builder setDescription(java.lang.String);
- method public android.print.PrinterInfo.Builder setHasCustomPrinterIcon();
+ method public android.print.PrinterInfo.Builder setHasCustomPrinterIcon(boolean);
method public android.print.PrinterInfo.Builder setIconResourceId(int);
method public android.print.PrinterInfo.Builder setInfoIntent(android.app.PendingIntent);
method public android.print.PrinterInfo.Builder setName(java.lang.String);
@@ -32635,6 +32635,7 @@
method public boolean isStarted();
method public void setProgress(float);
method public void setStatus(java.lang.CharSequence);
+ method public void setStatus(int);
method public boolean setTag(java.lang.String);
method public boolean start();
}
diff --git a/api/test-current.txt b/api/test-current.txt
index c1035af..740e8a7 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -30260,7 +30260,7 @@
method public android.print.PrinterId getPrinterId();
method public float getProgress();
method public int getState();
- method public java.lang.CharSequence getStatus();
+ method public java.lang.CharSequence getStatus(android.content.pm.PackageManager);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.print.PrintJobInfo> CREATOR;
field public static final int STATE_BLOCKED = 4; // 0x4
@@ -30337,7 +30337,7 @@
method public android.print.PrinterInfo build();
method public android.print.PrinterInfo.Builder setCapabilities(android.print.PrinterCapabilitiesInfo);
method public android.print.PrinterInfo.Builder setDescription(java.lang.String);
- method public android.print.PrinterInfo.Builder setHasCustomPrinterIcon();
+ method public android.print.PrinterInfo.Builder setHasCustomPrinterIcon(boolean);
method public android.print.PrinterInfo.Builder setIconResourceId(int);
method public android.print.PrinterInfo.Builder setInfoIntent(android.app.PendingIntent);
method public android.print.PrinterInfo.Builder setName(java.lang.String);
@@ -30389,6 +30389,7 @@
method public boolean isStarted();
method public void setProgress(float);
method public void setStatus(java.lang.CharSequence);
+ method public void setStatus(int);
method public boolean setTag(java.lang.String);
method public boolean start();
}
diff --git a/core/java/android/print/IPrintSpooler.aidl b/core/java/android/print/IPrintSpooler.aidl
index 469a4ea..63bf885 100644
--- a/core/java/android/print/IPrintSpooler.aidl
+++ b/core/java/android/print/IPrintSpooler.aidl
@@ -61,6 +61,15 @@
void setStatus(in PrintJobId printJobId, in CharSequence status);
/**
+ * Set the status of this print job
+ *
+ * @param printJobId The print job to update
+ * @param status The new status as a string resource
+ * @param appPackageName App package name the resource belongs to
+ */
+ void setStatusRes(in PrintJobId printJobId, int status, in CharSequence appPackageName);
+
+ /**
* Handle that a custom icon for a printer was loaded.
*
* @param printerId the id of the printer the icon belongs to
diff --git a/core/java/android/print/PrintJobInfo.java b/core/java/android/print/PrintJobInfo.java
index 7e3a72f..f134943 100644
--- a/core/java/android/print/PrintJobInfo.java
+++ b/core/java/android/print/PrintJobInfo.java
@@ -21,7 +21,10 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.StringRes;
import android.annotation.TestApi;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -181,7 +184,11 @@
private float mProgress;
/** A short string describing the status of this job. */
- private CharSequence mStatus;
+ private @Nullable CharSequence mStatus;
+
+ /** A string resource describing the status of this job. */
+ private @StringRes int mStatusRes;
+ private @Nullable CharSequence mStatusResAppPackageName;
/** Advanced printer specific options. */
private Bundle mAdvancedOptions;
@@ -210,6 +217,8 @@
mDocumentInfo = other.mDocumentInfo;
mProgress = other.mProgress;
mStatus = other.mStatus;
+ mStatusRes = other.mStatusRes;
+ mStatusResAppPackageName = other.mStatusResAppPackageName;
mCanceling = other.mCanceling;
mAdvancedOptions = other.mAdvancedOptions;
}
@@ -235,8 +244,14 @@
mDocumentInfo = (PrintDocumentInfo) parcel.readParcelable(null);
mProgress = parcel.readFloat();
mStatus = parcel.readCharSequence();
+ mStatusRes = parcel.readInt();
+ mStatusResAppPackageName = parcel.readCharSequence();
mCanceling = (parcel.readInt() == 1);
mAdvancedOptions = parcel.readBundle();
+
+ if (mAdvancedOptions != null) {
+ Preconditions.checkArgument(!mAdvancedOptions.containsKey(null));
+ }
}
/**
@@ -370,10 +385,28 @@
* @hide
*/
public void setStatus(@Nullable CharSequence status) {
+ mStatusRes = 0;
+ mStatusResAppPackageName = null;
+
mStatus = status;
}
/**
+ * Sets the status of the print job.
+ *
+ * @param status The new status as a string resource
+ * @param appPackageName App package name the resource belongs to
+ *
+ * @hide
+ */
+ public void setStatus(@StringRes int status, @NonNull CharSequence appPackageName) {
+ mStatus = null;
+
+ mStatusRes = status;
+ mStatusResAppPackageName = appPackageName;
+ }
+
+ /**
* Sets the owning application id.
*
* @return The owning app id.
@@ -633,6 +666,8 @@
parcel.writeParcelable(mDocumentInfo, 0);
parcel.writeFloat(mProgress);
parcel.writeCharSequence(mStatus);
+ parcel.writeInt(mStatusRes);
+ parcel.writeCharSequence(mStatusResAppPackageName);
parcel.writeInt(mCanceling ? 1 : 0);
parcel.writeBundle(mAdvancedOptions);
}
@@ -659,6 +694,9 @@
builder.append(", progress: " + mProgress);
builder.append(", status: " + (mStatus != null
? mStatus.toString() : null));
+ builder.append(", statusRes: " + mStatusRes);
+ builder.append(", statusResAppPackageName: " + (mStatusResAppPackageName != null
+ ? mStatusResAppPackageName.toString() : null));
builder.append("}");
return builder.toString();
}
@@ -707,12 +745,23 @@
/**
* Get the status of this job.
*
+ * @param pm Package manager used to resolve the string
+ *
* @return the status of this job or null if not set
* @hide
*/
@TestApi
- public @Nullable CharSequence getStatus() {
- return mStatus;
+ public @Nullable CharSequence getStatus(@NonNull PackageManager pm) {
+ if (mStatusRes == 0) {
+ return mStatus;
+ } else {
+ try {
+ return pm.getResourcesForApplication(mStatusResAppPackageName.toString())
+ .getString(mStatusRes);
+ } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
+ return null;
+ }
+ }
}
/**
@@ -789,6 +838,8 @@
* @param value The option value.
*/
public void putAdvancedOption(@NonNull String key, @Nullable String value) {
+ Preconditions.checkNotNull(key, "key cannot be null");
+
if (mPrototype.mAdvancedOptions == null) {
mPrototype.mAdvancedOptions = new Bundle();
}
diff --git a/core/java/android/print/PrinterInfo.java b/core/java/android/print/PrinterInfo.java
index 0d2d9f4..1ee6389 100644
--- a/core/java/android/print/PrinterInfo.java
+++ b/core/java/android/print/PrinterInfo.java
@@ -467,10 +467,12 @@
* {@link android.printservice.PrinterDiscoverySession#onRequestCustomPrinterIcon}.
* </p>
*
+ * @param hasCustomPrinterIcon If the printer has a custom icon or not.
+ *
* @return This builder.
*/
- public @NonNull Builder setHasCustomPrinterIcon() {
- mHasCustomPrinterIcon = true;
+ public @NonNull Builder setHasCustomPrinterIcon(boolean hasCustomPrinterIcon) {
+ mHasCustomPrinterIcon = hasCustomPrinterIcon;
return this;
}
diff --git a/core/java/android/printservice/IPrintServiceClient.aidl b/core/java/android/printservice/IPrintServiceClient.aidl
index 0ae1e18..f0ea6ae 100644
--- a/core/java/android/printservice/IPrintServiceClient.aidl
+++ b/core/java/android/printservice/IPrintServiceClient.aidl
@@ -52,6 +52,15 @@
*/
void setStatus(in PrintJobId printJobId, in CharSequence status);
+ /**
+ * Set the status of this print job
+ *
+ * @param printJobId The print job to update
+ * @param status The new status as a string resource
+ * @param appPackageName The app package name the string belongs to
+ */
+ void setStatusRes(in PrintJobId printJobId, int status, in CharSequence appPackageName);
+
void onPrintersAdded(in ParceledListSlice printers);
void onPrintersRemoved(in ParceledListSlice printerIds);
diff --git a/core/java/android/printservice/PrintJob.java b/core/java/android/printservice/PrintJob.java
index 6414b6a..7a7ca23 100644
--- a/core/java/android/printservice/PrintJob.java
+++ b/core/java/android/printservice/PrintJob.java
@@ -20,11 +20,14 @@
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.content.Context;
import android.os.RemoteException;
import android.print.PrintJobId;
import android.print.PrintJobInfo;
import android.text.TextUtils;
import android.util.Log;
+import com.android.internal.util.Preconditions;
/**
* This class represents a print job from the perspective of a print
@@ -45,7 +48,12 @@
private PrintJobInfo mCachedInfo;
- PrintJob(@NonNull PrintJobInfo jobInfo, @NonNull IPrintServiceClient client) {
+ /** Context that created the object */
+ private final Context mContext;
+
+ PrintJob(@NonNull Context context, @NonNull PrintJobInfo jobInfo,
+ @NonNull IPrintServiceClient client) {
+ mContext = context;
mCachedInfo = jobInfo;
mPrintServiceClient = client;
mDocument = new PrintDocument(mCachedInfo.getId(), client,
@@ -216,11 +224,10 @@
}
/**
- * Blocks the print job. You should call this method if {@link
- * #isStarted()} or {@link #isBlocked()} returns true and you need
- * to block the print job. For example, the user has to add some
- * paper to continue printing. To resume the print job call {@link
- * #start()}.
+ * Blocks the print job. You should call this method if {@link #isStarted()} returns true and
+ * you need to block the print job. For example, the user has to add some paper to continue
+ * printing. To resume the print job call {@link #start()}. To change the reason call
+ * {@link #setStatus(CharSequence)}.
*
* @param reason The human readable, short, and translated reason why the print job is blocked.
* @return Whether the job was blocked.
@@ -233,9 +240,7 @@
PrintService.throwIfNotCalledOnMainThread();
PrintJobInfo info = getInfo();
final int state = info.getState();
- if (state == PrintJobInfo.STATE_STARTED
- || (state == PrintJobInfo.STATE_BLOCKED
- && !TextUtils.equals(info.getStatus(), reason))) {
+ if (state == PrintJobInfo.STATE_STARTED || state == PrintJobInfo.STATE_BLOCKED) {
return setState(PrintJobInfo.STATE_BLOCKED, reason);
}
return false;
@@ -320,6 +325,9 @@
/**
* Sets the status of this print job. This should be a human readable, short, and translated
* description of the current state of the print job.
+ * <p />
+ * This overrides any previously set status set via {@link #setStatus(CharSequence)},
+ * {@link #setStatus(int)}, {@link #block(String)}, or {@link #fail(String)},
*
* @param status The new status. If null the status will be empty.
*/
@@ -335,6 +343,29 @@
}
/**
+ * Sets the status of this print job as a string resource.
+ * <p />
+ * This overrides any previously set status set via {@link #setStatus(CharSequence)},
+ * {@link #setStatus(int)}, {@link #block(String)}, or {@link #fail(String)},
+ * <p />
+ * To clear the status use {@link #setStatus(CharSequence) <code>setStatus(null)</code>}
+ *
+ * @param status The new status as a String resource.
+ */
+ @MainThread
+ public void setStatus(@StringRes int status) {
+ PrintService.throwIfNotCalledOnMainThread();
+ Preconditions.checkArgument(status != 0, "status has to be != 0");
+
+ try {
+ mPrintServiceClient.setStatusRes(mCachedInfo.getId(), status,
+ mContext.getPackageName());
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error setting status for job: " + mCachedInfo.getId(), re);
+ }
+ }
+
+ /**
* Sets a tag that is valid in the context of a {@link PrintService}
* and is not interpreted by the system. For example, a print service
* may set as a tag the key of the print job returned by a remote
diff --git a/core/java/android/printservice/PrintService.java b/core/java/android/printservice/PrintService.java
index 62d214e..8f73518 100644
--- a/core/java/android/printservice/PrintService.java
+++ b/core/java/android/printservice/PrintService.java
@@ -329,7 +329,7 @@
final int printJobInfoCount = printJobInfos.size();
printJobs = new ArrayList<PrintJob>(printJobInfoCount);
for (int i = 0; i < printJobInfoCount; i++) {
- printJobs.add(new PrintJob(printJobInfos.get(i), mClient));
+ printJobs.add(new PrintJob(this, printJobInfos.get(i), mClient));
}
}
if (printJobs != null) {
@@ -549,7 +549,7 @@
+ getPackageName());
}
PrintJobInfo printJobInfo = (PrintJobInfo) message.obj;
- onRequestCancelPrintJob(new PrintJob(printJobInfo, mClient));
+ onRequestCancelPrintJob(new PrintJob(PrintService.this, printJobInfo, mClient));
} break;
case MSG_ON_PRINTJOB_QUEUED: {
@@ -561,7 +561,7 @@
if (DEBUG) {
Log.i(LOG_TAG, "Queued: " + printJobInfo);
}
- onPrintJobQueued(new PrintJob(printJobInfo, mClient));
+ onPrintJobQueued(new PrintJob(PrintService.this, printJobInfo, mClient));
} break;
case MSG_SET_CLIENT: {
diff --git a/core/java/com/android/internal/util/Preconditions.java b/core/java/com/android/internal/util/Preconditions.java
index 53cb56e..c46851e 100644
--- a/core/java/com/android/internal/util/Preconditions.java
+++ b/core/java/com/android/internal/util/Preconditions.java
@@ -35,6 +35,20 @@
}
/**
+ * Ensures that an expression checking an argument is true.
+ *
+ * @param expression the expression to check
+ * @param errorMessage the exception message to use if the check fails; will
+ * be converted to a string using {@link String#valueOf(Object)}
+ * @throws IllegalArgumentException if {@code expression} is false
+ */
+ public static void checkArgument(boolean expression, final Object errorMessage) {
+ if (!expression) {
+ throw new IllegalArgumentException(String.valueOf(errorMessage));
+ }
+ }
+
+ /**
* Ensures that an string reference passed as a parameter to the calling
* method is not empty.
*
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
index 0210693..cd1d540 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/NotificationController.java
@@ -208,7 +208,7 @@
}
}
- CharSequence status = printJob.getStatus();
+ CharSequence status = printJob.getStatus(mContext.getPackageManager());
if (status != null) {
builder.setContentText(status);
} else {
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java b/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java
index 18160ff..ace4b25 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java
@@ -19,6 +19,7 @@
import android.annotation.FloatRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.StringRes;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
@@ -575,19 +576,35 @@
}
}
- /**
- * Set the status for a print job.
- *
- * @param printJobId ID of the print job to update
- * @param status the new status
- */
- public void setStatus(@NonNull PrintJobId printJobId, @Nullable CharSequence status) {
- synchronized (mLock) {
- getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY).setStatus(status);
+ /**
+ * Set the status for a print job.
+ *
+ * @param printJobId ID of the print job to update
+ * @param status the new status
+ */
+ public void setStatus(@NonNull PrintJobId printJobId, @Nullable CharSequence status) {
+ synchronized (mLock) {
+ getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY).setStatus(status);
- mNotificationController.onUpdateNotifications(mPrintJobs);
- }
- }
+ mNotificationController.onUpdateNotifications(mPrintJobs);
+ }
+ }
+
+ /**
+ * Set the status for a print job.
+ *
+ * @param printJobId ID of the print job to update
+ * @param status the new status as a string resource
+ * @param appPackageName app package the resource belongs to
+ */
+ public void setStatus(@NonNull PrintJobId printJobId, @StringRes int status,
+ @Nullable CharSequence appPackageName) {
+ synchronized (mLock) {
+ getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY).setStatus(status, appPackageName);
+
+ mNotificationController.onUpdateNotifications(mPrintJobs);
+ }
+ }
public boolean hasActivePrintJobsLocked() {
final int printJobCount = mPrintJobs.size();
@@ -884,7 +901,7 @@
serializer.attribute(null, ATTR_PROGRESS, String.valueOf(progress));
}
- CharSequence status = printJob.getStatus();
+ CharSequence status = printJob.getStatus(getPackageManager());
if (!TextUtils.isEmpty(status)) {
serializer.attribute(null, ATTR_STATUS, status.toString());
}
@@ -1433,6 +1450,13 @@
PrintSpoolerService.this.setStatus(printJobId, status);
}
+ @Override
+ public void setStatusRes(@NonNull PrintJobId printJobId, @StringRes int status,
+ @NonNull CharSequence appPackageName) throws RemoteException {
+ PrintSpoolerService.this.setStatus(printJobId, status, appPackageName);
+ }
+
+
public PrintSpoolerService getService() {
return PrintSpoolerService.this;
}
diff --git a/services/print/java/com/android/server/print/RemotePrintService.java b/services/print/java/com/android/server/print/RemotePrintService.java
index 9b99c67..9c3a852 100644
--- a/services/print/java/com/android/server/print/RemotePrintService.java
+++ b/services/print/java/com/android/server/print/RemotePrintService.java
@@ -19,6 +19,7 @@
import android.annotation.FloatRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.StringRes;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -813,6 +814,20 @@
}
@Override
+ public void setStatusRes(@NonNull PrintJobId printJobId, @StringRes int status,
+ @NonNull CharSequence appPackageName) {
+ RemotePrintService service = mWeakService.get();
+ if (service != null) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ service.mSpooler.setStatus(printJobId, status, appPackageName);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ @Override
@SuppressWarnings({"rawtypes", "unchecked"})
public void onPrintersAdded(ParceledListSlice printers) {
RemotePrintService service = mWeakService.get();
diff --git a/services/print/java/com/android/server/print/RemotePrintSpooler.java b/services/print/java/com/android/server/print/RemotePrintSpooler.java
index e1d8c6c..be822c8 100644
--- a/services/print/java/com/android/server/print/RemotePrintSpooler.java
+++ b/services/print/java/com/android/server/print/RemotePrintSpooler.java
@@ -19,6 +19,7 @@
import android.annotation.FloatRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.StringRes;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -301,6 +302,35 @@
}
/**
+ * Set status of a print job.
+ *
+ * @param printJobId The print job to update
+ * @param status The new status as a string resource
+ * @param appPackageName The app package name the string res belongs to
+ */
+ public final void setStatus(@NonNull PrintJobId printJobId, @StringRes int status,
+ @NonNull CharSequence appPackageName) {
+ throwIfCalledOnMainThread();
+ synchronized (mLock) {
+ throwIfDestroyedLocked();
+ mCanUnbind = false;
+ }
+ try {
+ getRemoteInstanceLazy().setStatusRes(printJobId, status, appPackageName);
+ } catch (RemoteException|TimeoutException re) {
+ Slog.e(LOG_TAG, "Error setting status.", re);
+ } finally {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] setStatus()");
+ }
+ synchronized (mLock) {
+ mCanUnbind = true;
+ mLock.notifyAll();
+ }
+ }
+ }
+
+ /**
* Handle that a custom icon for a printer was loaded.
*
* @param printerId the id of the printer the icon belongs to