Merge "Only use DecorContext with main activity windows."
diff --git a/Android.mk b/Android.mk
index 51dfa57..d3e1cf5 100644
--- a/Android.mk
+++ b/Android.mk
@@ -499,6 +499,7 @@
frameworks/base/graphics/java/android/graphics/PointF.aidl \
frameworks/base/graphics/java/android/graphics/RectF.aidl \
frameworks/base/graphics/java/android/graphics/Rect.aidl \
+ frameworks/base/graphics/java/android/graphics/drawable/Icon.aidl \
frameworks/base/core/java/android/accounts/AuthenticatorDescription.aidl \
frameworks/base/core/java/android/accounts/Account.aidl \
frameworks/base/core/java/android/app/admin/SystemUpdatePolicy.aidl \
diff --git a/api/current.txt b/api/current.txt
index 6610e37..ec65b25 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3504,6 +3504,7 @@
method public boolean releaseInstance();
method public final deprecated void removeDialog(int);
method public void reportFullyDrawn();
+ method public android.view.DropPermissions requestDropPermissions(android.view.DragEvent);
method public final void requestPermissions(java.lang.String[], int);
method public boolean requestVisibleBehind(boolean);
method public final boolean requestWindowFeature(int);
@@ -29146,6 +29147,9 @@
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 setIconResourceId(int);
+ method public android.print.PrinterInfo.Builder setInfoIntent(android.app.PendingIntent);
method public android.print.PrinterInfo.Builder setName(java.lang.String);
method public android.print.PrinterInfo.Builder setStatus(int);
}
@@ -29166,6 +29170,10 @@
package android.printservice {
+ public class CustomPrinterIconCallback {
+ method public boolean onCustomPrinterIconLoaded(android.graphics.drawable.Icon);
+ }
+
public final class PrintDocument {
method public android.os.ParcelFileDescriptor getData();
method public android.print.PrintDocumentInfo getInfo();
@@ -29221,6 +29229,7 @@
method public final boolean isDestroyed();
method public final boolean isPrinterDiscoveryStarted();
method public abstract void onDestroy();
+ method public void onRequestCustomPrinterIcon(android.print.PrinterId, android.printservice.CustomPrinterIconCallback);
method public abstract void onStartPrinterDiscovery(java.util.List<android.print.PrinterId>);
method public abstract void onStartPrinterStateTracking(android.print.PrinterId);
method public abstract void onStopPrinterDiscovery();
@@ -39596,7 +39605,6 @@
method public boolean getResult();
method public float getX();
method public float getY();
- method public android.view.DropPermissions requestDropPermissions();
method public void writeToParcel(android.os.Parcel, int);
field public static final int ACTION_DRAG_ENDED = 4; // 0x4
field public static final int ACTION_DRAG_ENTERED = 5; // 0x5
diff --git a/api/system-current.txt b/api/system-current.txt
index ff37365..e992a77 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3609,6 +3609,7 @@
method public boolean releaseInstance();
method public final deprecated void removeDialog(int);
method public void reportFullyDrawn();
+ method public android.view.DropPermissions requestDropPermissions(android.view.DragEvent);
method public final void requestPermissions(java.lang.String[], int);
method public boolean requestVisibleBehind(boolean);
method public final boolean requestWindowFeature(int);
@@ -31156,6 +31157,9 @@
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 setIconResourceId(int);
+ method public android.print.PrinterInfo.Builder setInfoIntent(android.app.PendingIntent);
method public android.print.PrinterInfo.Builder setName(java.lang.String);
method public android.print.PrinterInfo.Builder setStatus(int);
}
@@ -31176,6 +31180,10 @@
package android.printservice {
+ public class CustomPrinterIconCallback {
+ method public boolean onCustomPrinterIconLoaded(android.graphics.drawable.Icon);
+ }
+
public final class PrintDocument {
method public android.os.ParcelFileDescriptor getData();
method public android.print.PrintDocumentInfo getInfo();
@@ -31231,6 +31239,7 @@
method public final boolean isDestroyed();
method public final boolean isPrinterDiscoveryStarted();
method public abstract void onDestroy();
+ method public void onRequestCustomPrinterIcon(android.print.PrinterId, android.printservice.CustomPrinterIconCallback);
method public abstract void onStartPrinterDiscovery(java.util.List<android.print.PrinterId>);
method public abstract void onStartPrinterStateTracking(android.print.PrinterId);
method public abstract void onStopPrinterDiscovery();
@@ -41950,7 +41959,6 @@
method public boolean getResult();
method public float getX();
method public float getY();
- method public android.view.DropPermissions requestDropPermissions();
method public void writeToParcel(android.os.Parcel, int);
field public static final int ACTION_DRAG_ENDED = 4; // 0x4
field public static final int ACTION_DRAG_ENTERED = 5; // 0x5
diff --git a/api/test-current.txt b/api/test-current.txt
index 883dd31..42a6a86 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -3504,6 +3504,7 @@
method public boolean releaseInstance();
method public final deprecated void removeDialog(int);
method public void reportFullyDrawn();
+ method public android.view.DropPermissions requestDropPermissions(android.view.DragEvent);
method public final void requestPermissions(java.lang.String[], int);
method public boolean requestVisibleBehind(boolean);
method public final boolean requestWindowFeature(int);
@@ -29135,6 +29136,7 @@
method public android.print.PrinterId getId();
method public java.lang.String getName();
method public int getStatus();
+ method public android.graphics.drawable.Drawable loadIcon(android.content.Context);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.print.PrinterInfo> CREATOR;
field public static final int STATUS_BUSY = 2; // 0x2
@@ -29148,6 +29150,9 @@
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 setIconResourceId(int);
+ method public android.print.PrinterInfo.Builder setInfoIntent(android.app.PendingIntent);
method public android.print.PrinterInfo.Builder setName(java.lang.String);
method public android.print.PrinterInfo.Builder setStatus(int);
}
@@ -29168,6 +29173,10 @@
package android.printservice {
+ public class CustomPrinterIconCallback {
+ method public boolean onCustomPrinterIconLoaded(android.graphics.drawable.Icon);
+ }
+
public final class PrintDocument {
method public android.os.ParcelFileDescriptor getData();
method public android.print.PrintDocumentInfo getInfo();
@@ -29223,6 +29232,7 @@
method public final boolean isDestroyed();
method public final boolean isPrinterDiscoveryStarted();
method public abstract void onDestroy();
+ method public void onRequestCustomPrinterIcon(android.print.PrinterId, android.printservice.CustomPrinterIconCallback);
method public abstract void onStartPrinterDiscovery(java.util.List<android.print.PrinterId>);
method public abstract void onStartPrinterStateTracking(android.print.PrinterId);
method public abstract void onStopPrinterDiscovery();
@@ -39598,7 +39608,6 @@
method public boolean getResult();
method public float getX();
method public float getY();
- method public android.view.DropPermissions requestDropPermissions();
method public void writeToParcel(android.os.Parcel, int);
field public static final int ACTION_DRAG_ENDED = 4; // 0x4
field public static final int ACTION_DRAG_ENTERED = 5; // 0x5
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 6d72059..800ebc6 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -31,6 +31,8 @@
import android.transition.TransitionManager;
import android.util.ArrayMap;
import android.util.SuperNotCalledException;
+import android.view.DragEvent;
+import android.view.DropPermissions;
import android.view.Window.WindowControllerCallback;
import android.widget.Toolbar;
@@ -6336,6 +6338,21 @@
mActivityTransitionState.startPostponedEnterTransition();
}
+ /**
+ * Create {@link DropPermissions} object bound to this activity and controlling the access
+ * permissions for content URIs associated with the {@link DragEvent}.
+ * @param event Drag event
+ * @return The DropPermissions object used to control access to the content URIs. Null if
+ * no content URIs are associated with the event or if permissions could not be granted.
+ */
+ public DropPermissions requestDropPermissions(DragEvent event) {
+ DropPermissions dropPermissions = DropPermissions.obtain(event);
+ if (dropPermissions != null && dropPermissions.take(getActivityToken())) {
+ return dropPermissions;
+ }
+ return null;
+ }
+
// ------------------ Internal API ------------------
final void setParent(Activity parent) {
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 1b08273..3a3aa92 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -1845,6 +1845,15 @@
return true;
}
+ case GET_URI_PERMISSION_OWNER_FOR_ACTIVITY_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ IBinder activityToken = data.readStrongBinder();
+ IBinder perm = getUriPermissionOwnerForActivity(activityToken);
+ reply.writeNoException();
+ reply.writeStrongBinder(perm);
+ return true;
+ }
+
case GRANT_URI_PERMISSION_FROM_OWNER_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
IBinder owner = data.readStrongBinder();
@@ -5121,6 +5130,19 @@
return res;
}
+ public IBinder getUriPermissionOwnerForActivity(IBinder activityToken) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ data.writeStrongBinder(activityToken);
+ mRemote.transact(GET_URI_PERMISSION_OWNER_FOR_ACTIVITY_TRANSACTION, data, reply, 0);
+ reply.readException();
+ IBinder res = reply.readStrongBinder();
+ data.recycle();
+ reply.recycle();
+ return res;
+ }
+
public void grantUriPermissionFromOwner(IBinder owner, int fromUid, String targetPkg,
Uri uri, int mode, int sourceUserId, int targetUserId) throws RemoteException {
Parcel data = Parcel.obtain();
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 0ecf223..61b1bc8 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -363,6 +363,7 @@
public String getProviderMimeType(Uri uri, int userId) throws RemoteException;
public IBinder newUriPermissionOwner(String name) throws RemoteException;
+ public IBinder getUriPermissionOwnerForActivity(IBinder activityToken) throws RemoteException;
public void grantUriPermissionFromOwner(IBinder owner, int fromUid, String targetPkg,
Uri uri, int mode, int sourceUserId, int targetUserId) throws RemoteException;
public void revokeUriPermissionFromOwner(IBinder owner, Uri uri,
@@ -920,4 +921,5 @@
int KILL_PACKAGE_DEPENDENTS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 354;
int ENTER_PICTURE_IN_PICTURE_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 355;
int SET_VR_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 356;
+ int GET_URI_PERMISSION_OWNER_FOR_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 357;
}
diff --git a/core/java/android/print/IPrintManager.aidl b/core/java/android/print/IPrintManager.aidl
index 8fa7ab9..9a80e37 100644
--- a/core/java/android/print/IPrintManager.aidl
+++ b/core/java/android/print/IPrintManager.aidl
@@ -16,6 +16,7 @@
package android.print;
+import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.print.IPrinterDiscoveryObserver;
import android.print.IPrintDocumentAdapter;
@@ -53,6 +54,19 @@
void stopPrinterDiscovery(in IPrinterDiscoveryObserver observer, int userId);
void validatePrinters(in List<PrinterId> printerIds, int userId);
void startPrinterStateTracking(in PrinterId printerId, int userId);
+
+ /**
+ * Get the custom icon for a printer. If the icon is not cached, the icon is
+ * requested asynchronously. Once it is available the printer is updated.
+ *
+ * @param printerId the id of the printer the icon should be loaded for
+ * @param userId the id of the user requesting the printer
+ * @return the custom icon to be used for the printer or null if the icon is
+ * not yet available
+ * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon()
+ */
+ Icon getCustomPrinterIcon(in PrinterId printerId, int userId);
+
void stopPrinterStateTracking(in PrinterId printerId, int userId);
void destroyPrinterDiscoverySession(in IPrinterDiscoveryObserver observer,
int userId);
diff --git a/core/java/android/print/IPrintSpooler.aidl b/core/java/android/print/IPrintSpooler.aidl
index b7cfbea..c3625b8 100644
--- a/core/java/android/print/IPrintSpooler.aidl
+++ b/core/java/android/print/IPrintSpooler.aidl
@@ -17,9 +17,11 @@
package android.print;
import android.content.ComponentName;
+import android.graphics.drawable.Icon;
import android.os.ParcelFileDescriptor;
import android.print.IPrintSpoolerClient;
import android.print.IPrintSpoolerCallbacks;
+import android.print.PrinterId;
import android.print.PrinterInfo;
import android.print.PrintAttributes;
import android.print.PrintJobId;
@@ -58,6 +60,39 @@
*/
void setStatus(in PrintJobId printJobId, in CharSequence status);
+ /**
+ * Handle that a custom icon for a printer was loaded.
+ *
+ * @param printerId the id of the printer the icon belongs to
+ * @param icon the icon that was loaded
+ * @param callbacks the callback to call once icon is stored in case
+ * @param sequence the sequence number of the call
+ * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon()
+ */
+ void onCustomPrinterIconLoaded(in PrinterId printerId, in Icon icon,
+ in IPrintSpoolerCallbacks callbacks, in int sequence);
+
+ /**
+ * Get the custom icon for a printer. If the icon is not cached, the icon is
+ * requested asynchronously. Once it is available the printer is updated.
+ *
+ * @param printerId the id of the printer the icon should be loaded for
+ * @param callbacks the callback to call once icon is retrieved
+ * @param sequence the sequence number of the call
+ * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon()
+ */
+ void getCustomPrinterIcon(in PrinterId printerId,
+ in IPrintSpoolerCallbacks callbacks, in int sequence);
+
+ /**
+ * Clear all state from the custom printer icon cache.
+ *
+ * @param callbacks the callback to call once cache is cleared
+ * @param sequence the sequence number of the call
+ */
+ void clearCustomPrinterIconCache(in IPrintSpoolerCallbacks callbacks,
+ in int sequence);
+
void setPrintJobTag(in PrintJobId printJobId, String tag, IPrintSpoolerCallbacks callback,
int sequence);
void writePrintJobData(in ParcelFileDescriptor fd, in PrintJobId printJobId);
diff --git a/core/java/android/print/IPrintSpoolerCallbacks.aidl b/core/java/android/print/IPrintSpoolerCallbacks.aidl
index 45c5332..23d706a 100644
--- a/core/java/android/print/IPrintSpoolerCallbacks.aidl
+++ b/core/java/android/print/IPrintSpoolerCallbacks.aidl
@@ -16,7 +16,9 @@
package android.print;
+import android.graphics.drawable.Icon;
import android.print.PrintJobInfo;
+import android.print.PrinterId;
import java.util.List;
/**
@@ -32,4 +34,27 @@
void onSetPrintJobStateResult(boolean success, int sequence);
void onSetPrintJobTagResult(boolean success, int sequence);
void onGetPrintJobInfoResult(in PrintJobInfo printJob, int sequence);
+
+ /**
+ * Deliver the result of a request of a custom printer icon.
+ *
+ * @param icon the icon that was retrieved, or null if no icon could be
+ * found
+ * @param sequence the sequence number of the call to get the icon
+ */
+ void onGetCustomPrinterIconResult(in Icon icon, int sequence);
+
+ /**
+ * Declare that the print spooler cached a custom printer icon.
+ *
+ * @param sequence the sequence number of the call to cache the icon
+ */
+ void onCustomPrinterIconCached(int sequence);
+
+ /**
+ * Declare that the custom printer icon cache was cleared.
+ *
+ * @param sequence the sequence number of the call to clear the cache
+ */
+ void customPrinterIconCacheCleared(int sequence);
}
diff --git a/core/java/android/print/PageRange.java b/core/java/android/print/PageRange.java
index 8bc157a..57c7718 100644
--- a/core/java/android/print/PageRange.java
+++ b/core/java/android/print/PageRange.java
@@ -16,6 +16,8 @@
package android.print;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
@@ -42,7 +44,7 @@
* @throws IllegalArgumentException If start is less than zero or end
* is less than zero or start greater than end.
*/
- public PageRange(int start, int end) {
+ public PageRange(@IntRange(from = 0) int start, @IntRange(from = 0) int end) {
if (start < 0) {
throw new IllegalArgumentException("start cannot be less than zero.");
}
@@ -56,7 +58,7 @@
mEnd = end;
}
- private PageRange (Parcel parcel) {
+ private PageRange(@NonNull Parcel parcel) {
this(parcel.readInt(), parcel.readInt());
}
@@ -65,7 +67,7 @@
*
* @return The start page index.
*/
- public int getStart() {
+ public @IntRange(from = 0) int getStart() {
return mStart;
}
@@ -74,7 +76,7 @@
*
* @return The end page index.
*/
- public int getEnd() {
+ public @IntRange(from = 0) int getEnd() {
return mEnd;
}
diff --git a/core/java/android/print/PrintAttributes.java b/core/java/android/print/PrintAttributes.java
index 2afbb99..8892e34 100644
--- a/core/java/android/print/PrintAttributes.java
+++ b/core/java/android/print/PrintAttributes.java
@@ -16,6 +16,10 @@
package android.print;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources.NotFoundException;
@@ -27,6 +31,8 @@
import com.android.internal.R;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Map;
/**
@@ -37,6 +43,13 @@
* 10 mills (thousand of an inch) on all sides, and be black and white.
*/
public final class PrintAttributes implements Parcelable {
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, value = {
+ COLOR_MODE_MONOCHROME, COLOR_MODE_COLOR
+ })
+ public @interface ColorMode {
+ }
/** Color mode: Monochrome color scheme, for example one color is used. */
public static final int COLOR_MODE_MONOCHROME = 1 << 0;
/** Color mode: Color color scheme, for example many colors are used. */
@@ -45,6 +58,13 @@
private static final int VALID_COLOR_MODES =
COLOR_MODE_MONOCHROME | COLOR_MODE_COLOR;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, value = {
+ DUPLEX_MODE_NONE, DUPLEX_MODE_LONG_EDGE, DUPLEX_MODE_SHORT_EDGE
+ })
+ public @interface DuplexMode {
+ }
/** Duplex mode: No duplexing. */
public static final int DUPLEX_MODE_NONE = 1 << 0;
/** Duplex mode: Pages are turned sideways along the long edge - like a book. */
@@ -66,7 +86,7 @@
/* hide constructor */
}
- private PrintAttributes(Parcel parcel) {
+ private PrintAttributes(@NonNull Parcel parcel) {
mMediaSize = (parcel.readInt() == 1) ? MediaSize.createFromParcel(parcel) : null;
mResolution = (parcel.readInt() == 1) ? Resolution.createFromParcel(parcel) : null;
mMinMargins = (parcel.readInt() == 1) ? Margins.createFromParcel(parcel) : null;
@@ -79,7 +99,7 @@
*
* @return The media size or <code>null</code> if not set.
*/
- public MediaSize getMediaSize() {
+ public @Nullable MediaSize getMediaSize() {
return mMediaSize;
}
@@ -99,7 +119,7 @@
*
* @return The resolution or <code>null</code> if not set.
*/
- public Resolution getResolution() {
+ public @Nullable Resolution getResolution() {
return mResolution;
}
@@ -127,7 +147,7 @@
*
* @return The margins or <code>null</code> if not set.
*/
- public Margins getMinMargins() {
+ public @Nullable Margins getMinMargins() {
return mMinMargins;
}
@@ -158,7 +178,7 @@
* @see #COLOR_MODE_COLOR
* @see #COLOR_MODE_MONOCHROME
*/
- public int getColorMode() {
+ public @ColorMode int getColorMode() {
return mColorMode;
}
@@ -199,7 +219,7 @@
* @see #DUPLEX_MODE_LONG_EDGE
* @see #DUPLEX_MODE_SHORT_EDGE
*/
- public int getDuplexMode() {
+ public @DuplexMode int getDuplexMode() {
return mDuplexMode;
}
@@ -828,7 +848,8 @@
* or the widthMils is less than or equal to zero or the heightMils is less
* than or equal to zero.
*/
- public MediaSize(String id, String label, int widthMils, int heightMils) {
+ public MediaSize(@NonNull String id, @NonNull String label,
+ @IntRange(from = 1) int widthMils, @IntRange(from = 1) int heightMils) {
if (TextUtils.isEmpty(id)) {
throw new IllegalArgumentException("id cannot be empty.");
}
@@ -872,7 +893,7 @@
*
* @return The unique media size id.
*/
- public String getId() {
+ public @NonNull String getId() {
return mId;
}
@@ -882,7 +903,7 @@
* @param packageManager The package manager for loading the label.
* @return The human readable label.
*/
- public String getLabel(PackageManager packageManager) {
+ public @NonNull String getLabel(@NonNull PackageManager packageManager) {
if (!TextUtils.isEmpty(mPackageName) && mLabelResId > 0) {
try {
return packageManager.getResourcesForApplication(
@@ -903,7 +924,7 @@
*
* @return The media width.
*/
- public int getWidthMils() {
+ public @IntRange(from = 1) int getWidthMils() {
return mWidthMils;
}
@@ -912,7 +933,7 @@
*
* @return The media height.
*/
- public int getHeightMils() {
+ public @IntRange(from = 1) int getHeightMils() {
return mHeightMils;
}
@@ -934,7 +955,7 @@
* @return New instance in landscape orientation if this one
* is in landscape, otherwise this instance.
*/
- public MediaSize asPortrait() {
+ public @NonNull MediaSize asPortrait() {
if (isPortrait()) {
return this;
}
@@ -951,7 +972,7 @@
* @return New instance in landscape orientation if this one
* is in portrait, otherwise this instance.
*/
- public MediaSize asLandscape() {
+ public @NonNull MediaSize asLandscape() {
if (!isPortrait()) {
return this;
}
@@ -1063,7 +1084,8 @@
* or the horizontalDpi is less than or equal to zero or the verticalDpi is
* less than or equal to zero.
*/
- public Resolution(String id, String label, int horizontalDpi, int verticalDpi) {
+ public Resolution(@NonNull String id, @NonNull String label,
+ @IntRange(from = 1) int horizontalDpi, @IntRange(from = 1) int verticalDpi) {
if (TextUtils.isEmpty(id)) {
throw new IllegalArgumentException("id cannot be empty.");
}
@@ -1094,7 +1116,7 @@
*
* @return The unique resolution id.
*/
- public String getId() {
+ public @NonNull String getId() {
return mId;
}
@@ -1103,7 +1125,7 @@
*
* @return The human readable label.
*/
- public String getLabel() {
+ public @NonNull String getLabel() {
return mLabel;
}
@@ -1112,7 +1134,7 @@
*
* @return The horizontal resolution.
*/
- public int getHorizontalDpi() {
+ public @IntRange(from = 1) int getHorizontalDpi() {
return mHorizontalDpi;
}
@@ -1121,7 +1143,7 @@
*
* @return The vertical resolution.
*/
- public int getVerticalDpi() {
+ public @IntRange(from = 1) int getVerticalDpi() {
return mVerticalDpi;
}
@@ -1204,7 +1226,8 @@
* @param rightMils The right margin in mils (thousands of an inch).
* @param bottomMils The bottom margin in mils (thousands of an inch).
*/
- public Margins(int leftMils, int topMils, int rightMils, int bottomMils) {
+ public Margins(@IntRange(from = 0) int leftMils, @IntRange(from = 0) int topMils,
+ @IntRange(from = 0) int rightMils, @IntRange(from = 0) int bottomMils) {
mTopMils = topMils;
mLeftMils = leftMils;
mRightMils = rightMils;
@@ -1216,7 +1239,7 @@
*
* @return The left margin.
*/
- public int getLeftMils() {
+ public @IntRange(from = 0) int getLeftMils() {
return mLeftMils;
}
@@ -1225,7 +1248,7 @@
*
* @return The top margin.
*/
- public int getTopMils() {
+ public @IntRange(from = 0) int getTopMils() {
return mTopMils;
}
@@ -1234,7 +1257,7 @@
*
* @return The right margin.
*/
- public int getRightMils() {
+ public @IntRange(from = 0) int getRightMils() {
return mRightMils;
}
@@ -1243,7 +1266,7 @@
*
* @return The bottom margin.
*/
- public int getBottomMils() {
+ public @IntRange(from = 0) int getBottomMils() {
return mBottomMils;
}
@@ -1368,7 +1391,7 @@
* @param mediaSize The media size.
* @return This builder.
*/
- public Builder setMediaSize(MediaSize mediaSize) {
+ public @NonNull Builder setMediaSize(@NonNull MediaSize mediaSize) {
mAttributes.setMediaSize(mediaSize);
return this;
}
@@ -1379,7 +1402,7 @@
* @param resolution The resolution.
* @return This builder.
*/
- public Builder setResolution(Resolution resolution) {
+ public @NonNull Builder setResolution(@NonNull Resolution resolution) {
mAttributes.setResolution(resolution);
return this;
}
@@ -1391,7 +1414,7 @@
* @param margins The margins.
* @return This builder.
*/
- public Builder setMinMargins(Margins margins) {
+ public @NonNull Builder setMinMargins(@NonNull Margins margins) {
mAttributes.setMinMargins(margins);
return this;
}
@@ -1405,7 +1428,7 @@
* @see PrintAttributes#COLOR_MODE_MONOCHROME
* @see PrintAttributes#COLOR_MODE_COLOR
*/
- public Builder setColorMode(int colorMode) {
+ public @NonNull Builder setColorMode(@ColorMode int colorMode) {
mAttributes.setColorMode(colorMode);
return this;
}
@@ -1420,7 +1443,7 @@
* @see PrintAttributes#DUPLEX_MODE_LONG_EDGE
* @see PrintAttributes#DUPLEX_MODE_SHORT_EDGE
*/
- public Builder setDuplexMode(int duplexMode) {
+ public @NonNull Builder setDuplexMode(@DuplexMode int duplexMode) {
mAttributes.setDuplexMode(duplexMode);
return this;
}
@@ -1430,7 +1453,7 @@
*
* @return The new instance.
*/
- public PrintAttributes build() {
+ public @NonNull PrintAttributes build() {
return mAttributes;
}
}
diff --git a/core/java/android/print/PrintDocumentInfo.java b/core/java/android/print/PrintDocumentInfo.java
index 44e6410..db3b6f4 100644
--- a/core/java/android/print/PrintDocumentInfo.java
+++ b/core/java/android/print/PrintDocumentInfo.java
@@ -16,10 +16,16 @@
package android.print;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* This class encapsulates information about a document for printing
* purposes. This meta-data is used by the platform and print services,
@@ -74,6 +80,13 @@
*/
public static final int PAGE_COUNT_UNKNOWN = -1;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ CONTENT_TYPE_UNKNOWN, CONTENT_TYPE_DOCUMENT, CONTENT_TYPE_PHOTO
+ })
+ public @interface ContentType {
+ }
/**
* Content type: unknown.
*/
@@ -116,9 +129,9 @@
/**
* Creates a new instance.
*
- * @param Prototype from which to clone.
+ * @param prototype from which to clone.
*/
- private PrintDocumentInfo(PrintDocumentInfo prototype) {
+ private PrintDocumentInfo(@NonNull PrintDocumentInfo prototype) {
mName = prototype.mName;
mPageCount = prototype.mPageCount;
mContentType = prototype.mContentType;
@@ -143,7 +156,7 @@
*
* @return The document name.
*/
- public String getName() {
+ public @NonNull String getName() {
return mName;
}
@@ -154,7 +167,7 @@
*
* @see #PAGE_COUNT_UNKNOWN
*/
- public int getPageCount() {
+ public @IntRange(from = -1) int getPageCount() {
return mPageCount;
}
@@ -167,7 +180,7 @@
* @see #CONTENT_TYPE_DOCUMENT
* @see #CONTENT_TYPE_PHOTO
*/
- public int getContentType() {
+ public @ContentType int getContentType() {
return mContentType;
}
@@ -176,7 +189,7 @@
*
* @return The data size.
*/
- public long getDataSize() {
+ public @IntRange(from = 0) long getDataSize() {
return mDataSize;
}
@@ -187,7 +200,7 @@
*
* @hide
*/
- public void setDataSize(long dataSize) {
+ public void setDataSize(@IntRange(from = 0) long dataSize) {
mDataSize = dataSize;
}
@@ -288,7 +301,7 @@
* is the file name if the content it describes is saved as a PDF.
* Cannot be empty.
*/
- public Builder(String name) {
+ public Builder(@NonNull String name) {
if (TextUtils.isEmpty(name)) {
throw new IllegalArgumentException("name cannot be empty");
}
@@ -302,10 +315,11 @@
* <strong>Default: </strong> {@link #PAGE_COUNT_UNKNOWN}
* </p>
*
- * @param pageCount The number of pages. Must be greater than
- * or equal to zero or {@link PrintDocumentInfo#PAGE_COUNT_UNKNOWN}.
+ * @param pageCount The number of pages. Must be greater than or equal to zero or
+ * {@link PrintDocumentInfo#PAGE_COUNT_UNKNOWN}.
+ * @return This builder.
*/
- public Builder setPageCount(int pageCount) {
+ public @NonNull Builder setPageCount(@IntRange(from = -1) int pageCount) {
if (pageCount < 0 && pageCount != PAGE_COUNT_UNKNOWN) {
throw new IllegalArgumentException("pageCount"
+ " must be greater than or equal to zero or"
@@ -322,12 +336,12 @@
* </p>
*
* @param type The content type.
- *
+ * @return This builder.
* @see #CONTENT_TYPE_UNKNOWN
* @see #CONTENT_TYPE_DOCUMENT
* @see #CONTENT_TYPE_PHOTO
*/
- public Builder setContentType(int type) {
+ public @NonNull Builder setContentType(@ContentType int type) {
mPrototype.mContentType = type;
return this;
}
@@ -337,7 +351,7 @@
*
* @return The new instance.
*/
- public PrintDocumentInfo build() {
+ public @NonNull PrintDocumentInfo build() {
// Zero pages is the same as unknown as in this case
// we will have to ask for all pages and look a the
// wiritten PDF file for the page count.
diff --git a/core/java/android/print/PrintJob.java b/core/java/android/print/PrintJob.java
index 0abe219..777baab 100644
--- a/core/java/android/print/PrintJob.java
+++ b/core/java/android/print/PrintJob.java
@@ -16,6 +16,9 @@
package android.print;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
/**
* This class represents a print job from the perspective of an
* application. It contains behavior methods for performing operations
@@ -41,7 +44,7 @@
*
* @return The id.
*/
- public PrintJobId getId() {
+ public @NonNull PrintJobId getId() {
return mCachedInfo.getId();
}
@@ -55,7 +58,7 @@
*
* @return The print job info.
*/
- public PrintJobInfo getInfo() {
+ public @Nullable PrintJobInfo getInfo() {
if (isInImmutableState()) {
return mCachedInfo;
}
diff --git a/core/java/android/print/PrintJobId.java b/core/java/android/print/PrintJobId.java
index 01550e2..a2ee02b 100644
--- a/core/java/android/print/PrintJobId.java
+++ b/core/java/android/print/PrintJobId.java
@@ -16,6 +16,7 @@
package android.print;
+import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -91,14 +92,14 @@
*
* @hide
*/
- public String flattenToString() {
+ public @NonNull String flattenToString() {
return mValue;
}
/**
* Unflattens a print job id from a string.
*
- * @string The string.
+ * @param string The string.
* @return The unflattened id, or null if the string is malformed.
*
* @hide
diff --git a/core/java/android/print/PrintJobInfo.java b/core/java/android/print/PrintJobInfo.java
index 7148c87..21836b3 100644
--- a/core/java/android/print/PrintJobInfo.java
+++ b/core/java/android/print/PrintJobInfo.java
@@ -17,6 +17,9 @@
package android.print;
import android.annotation.FloatRange;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.os.Bundle;
@@ -25,6 +28,8 @@
import com.android.internal.util.Preconditions;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
/**
@@ -35,6 +40,15 @@
*/
public final class PrintJobInfo implements Parcelable {
+ /** @hide */
+ @IntDef({
+ STATE_CREATED, STATE_QUEUED, STATE_STARTED, STATE_BLOCKED, STATE_COMPLETED,
+ STATE_FAILED, STATE_CANCELED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface State {
+ }
+
/**
* Constant for matching any print job state.
*
@@ -200,7 +214,7 @@
mAdvancedOptions = other.mAdvancedOptions;
}
- private PrintJobInfo(Parcel parcel) {
+ private PrintJobInfo(@NonNull Parcel parcel) {
mId = parcel.readParcelable(null);
mLabel = parcel.readString();
mPrinterId = parcel.readParcelable(null);
@@ -230,7 +244,7 @@
*
* @return The id.
*/
- public PrintJobId getId() {
+ public @NonNull PrintJobId getId() {
return mId;
}
@@ -241,7 +255,7 @@
*
* @hide
*/
- public void setId(PrintJobId id) {
+ public void setId(@NonNull PrintJobId id) {
this.mId = id;
}
@@ -250,7 +264,7 @@
*
* @return The label.
*/
- public String getLabel() {
+ public @NonNull String getLabel() {
return mLabel;
}
@@ -261,7 +275,7 @@
*
* @hide
*/
- public void setLabel(String label) {
+ public void setLabel(@NonNull String label) {
mLabel = label;
}
@@ -270,7 +284,7 @@
*
* @return The target printer id.
*/
- public PrinterId getPrinterId() {
+ public @Nullable PrinterId getPrinterId() {
return mPrinterId;
}
@@ -281,7 +295,7 @@
*
* @hide
*/
- public void setPrinterId(PrinterId printerId) {
+ public void setPrinterId(@NonNull PrinterId printerId) {
mPrinterId = printerId;
}
@@ -292,7 +306,7 @@
*
* @hide
*/
- public String getPrinterName() {
+ public @Nullable String getPrinterName() {
return mPrinterName;
}
@@ -303,7 +317,7 @@
*
* @hide
*/
- public void setPrinterName(String printerName) {
+ public void setPrinterName(@NonNull String printerName) {
mPrinterName = printerName;
}
@@ -320,7 +334,7 @@
* @see #STATE_FAILED
* @see #STATE_CANCELED
*/
- public int getState() {
+ public @State int getState() {
return mState;
}
@@ -431,7 +445,7 @@
*
* @return The number of copies or zero if not set.
*/
- public int getCopies() {
+ public @IntRange(from = 0) int getCopies() {
return mCopies;
}
@@ -454,7 +468,7 @@
*
* @return The included pages or <code>null</code> if not set.
*/
- public PageRange[] getPages() {
+ public @Nullable PageRange[] getPages() {
return mPageRanges;
}
@@ -474,7 +488,7 @@
*
* @return The attributes.
*/
- public PrintAttributes getAttributes() {
+ public @NonNull PrintAttributes getAttributes() {
return mAttributes;
}
@@ -713,7 +727,7 @@
* @param prototype Prototype to use as a starting point.
* Can be <code>null</code>.
*/
- public Builder(PrintJobInfo prototype) {
+ public Builder(@Nullable PrintJobInfo prototype) {
mPrototype = (prototype != null)
? new PrintJobInfo(prototype)
: new PrintJobInfo();
@@ -724,7 +738,7 @@
*
* @param copies The number of copies.
*/
- public void setCopies(int copies) {
+ public void setCopies(@IntRange(from = 1) int copies) {
mPrototype.mCopies = copies;
}
@@ -733,7 +747,7 @@
*
* @param attributes The attributes.
*/
- public void setAttributes(PrintAttributes attributes) {
+ public void setAttributes(@NonNull PrintAttributes attributes) {
mPrototype.mAttributes = attributes;
}
@@ -742,7 +756,7 @@
*
* @param pages The included pages.
*/
- public void setPages(PageRange[] pages) {
+ public void setPages(@NonNull PageRange[] pages) {
mPrototype.mPageRanges = pages;
}
@@ -774,7 +788,7 @@
* @param key The option key.
* @param value The option value.
*/
- public void putAdvancedOption(String key, String value) {
+ public void putAdvancedOption(@NonNull String key, @Nullable String value) {
if (mPrototype.mAdvancedOptions == null) {
mPrototype.mAdvancedOptions = new Bundle();
}
@@ -787,7 +801,7 @@
* @param key The option key.
* @param value The option value.
*/
- public void putAdvancedOption(String key, int value) {
+ public void putAdvancedOption(@NonNull String key, int value) {
if (mPrototype.mAdvancedOptions == null) {
mPrototype.mAdvancedOptions = new Bundle();
}
@@ -799,7 +813,7 @@
*
* @return The new instance.
*/
- public PrintJobInfo build() {
+ public @NonNull PrintJobInfo build() {
return mPrototype;
}
}
diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java
index 3fb812e..3eb4874 100644
--- a/core/java/android/print/PrintManager.java
+++ b/core/java/android/print/PrintManager.java
@@ -16,11 +16,14 @@
package android.print;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.Activity;
import android.app.Application.ActivityLifecycleCallbacks;
import android.content.Context;
import android.content.IntentSender;
import android.content.IntentSender.SendIntentException;
+import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.CancellationSignal;
import android.os.Handler;
@@ -180,6 +183,8 @@
*
* @param context The current context in which to operate.
* @param service The backing system service.
+ * @param userId The user id in which to operate.
+ * @param appId The application id in which to operate.
* @hide
*/
public PrintManager(Context context, IPrintManager service, int userId, int appId) {
@@ -290,6 +295,7 @@
/**
* Gets a print job given its id.
*
+ * @param printJobId The id of the print job.
* @return The print job list.
* @see PrintJob
* @hide
@@ -311,12 +317,35 @@
}
/**
+ * Get the custom icon for a printer. If the icon is not cached, the icon is
+ * requested asynchronously. Once it is available the printer is updated.
+ *
+ * @param printerId the id of the printer the icon should be loaded for
+ * @return the custom icon to be used for the printer or null if the icon is
+ * not yet available
+ * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon()
+ * @hide
+ */
+ public Icon getCustomPrinterIcon(PrinterId printerId) {
+ if (mService == null) {
+ Log.w(LOG_TAG, "Feature android.software.print not available");
+ return null;
+ }
+ try {
+ return mService.getCustomPrinterIcon(printerId, mUserId);
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error getting custom printer icon", re);
+ }
+ return null;
+ }
+
+ /**
* Gets the print jobs for this application.
*
* @return The print job list.
* @see PrintJob
*/
- public List<PrintJob> getPrintJobs() {
+ public @NonNull List<PrintJob> getPrintJobs() {
if (mService == null) {
Log.w(LOG_TAG, "Feature android.software.print not available");
return Collections.emptyList();
@@ -411,8 +440,9 @@
*
* @see PrintJob
*/
- public PrintJob print(String printJobName, PrintDocumentAdapter documentAdapter,
- PrintAttributes attributes) {
+ public @NonNull PrintJob print(@NonNull String printJobName,
+ @NonNull PrintDocumentAdapter documentAdapter,
+ @Nullable PrintAttributes attributes) {
if (mService == null) {
Log.w(LOG_TAG, "Feature android.software.print not available");
return null;
diff --git a/core/java/android/print/PrinterCapabilitiesInfo.java b/core/java/android/print/PrinterCapabilitiesInfo.java
index 96f3185..d13879b 100644
--- a/core/java/android/print/PrinterCapabilitiesInfo.java
+++ b/core/java/android/print/PrinterCapabilitiesInfo.java
@@ -16,8 +16,11 @@
package android.print;
+import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
+import android.print.PrintAttributes.ColorMode;
+import android.print.PrintAttributes.DuplexMode;
import android.print.PrintAttributes.Margins;
import android.print.PrintAttributes.MediaSize;
import android.print.PrintAttributes.Resolution;
@@ -121,7 +124,7 @@
*
* @return The media sizes.
*/
- public List<MediaSize> getMediaSizes() {
+ public @NonNull List<MediaSize> getMediaSizes() {
return Collections.unmodifiableList(mMediaSizes);
}
@@ -130,7 +133,7 @@
*
* @return The resolutions.
*/
- public List<Resolution> getResolutions() {
+ public @NonNull List<Resolution> getResolutions() {
return Collections.unmodifiableList(mResolutions);
}
@@ -140,7 +143,7 @@
*
* @return The minimal margins.
*/
- public Margins getMinMargins() {
+ public @NonNull Margins getMinMargins() {
return mMinMargins;
}
@@ -152,7 +155,7 @@
* @see PrintAttributes#COLOR_MODE_COLOR
* @see PrintAttributes#COLOR_MODE_MONOCHROME
*/
- public int getColorModes() {
+ public @ColorMode int getColorModes() {
return mColorModes;
}
@@ -165,7 +168,7 @@
* @see PrintAttributes#DUPLEX_MODE_LONG_EDGE
* @see PrintAttributes#DUPLEX_MODE_SHORT_EDGE
*/
- public int getDuplexModes() {
+ public @DuplexMode int getDuplexModes() {
return mDuplexModes;
}
@@ -174,7 +177,7 @@
*
* @return The default attributes.
*/
- public PrintAttributes getDefaults() {
+ public @NonNull PrintAttributes getDefaults() {
PrintAttributes.Builder builder = new PrintAttributes.Builder();
builder.setMinMargins(mMinMargins);
@@ -425,7 +428,7 @@
*
* @throws IllegalArgumentException If the printer id is <code>null</code>.
*/
- public Builder(PrinterId printerId) {
+ public Builder(@NonNull PrinterId printerId) {
if (printerId == null) {
throw new IllegalArgumentException("printerId cannot be null.");
}
@@ -446,7 +449,7 @@
*
* @see PrintAttributes.MediaSize
*/
- public Builder addMediaSize(MediaSize mediaSize, boolean isDefault) {
+ public @NonNull Builder addMediaSize(@NonNull MediaSize mediaSize, boolean isDefault) {
if (mPrototype.mMediaSizes == null) {
mPrototype.mMediaSizes = new ArrayList<MediaSize>();
}
@@ -474,7 +477,7 @@
*
* @see PrintAttributes.Resolution
*/
- public Builder addResolution(Resolution resolution, boolean isDefault) {
+ public @NonNull Builder addResolution(@NonNull Resolution resolution, boolean isDefault) {
if (mPrototype.mResolutions == null) {
mPrototype.mResolutions = new ArrayList<Resolution>();
}
@@ -502,7 +505,7 @@
*
* @see PrintAttributes.Margins
*/
- public Builder setMinMargins(Margins margins) {
+ public @NonNull Builder setMinMargins(@NonNull Margins margins) {
if (margins == null) {
throw new IllegalArgumentException("margins cannot be null");
}
@@ -532,7 +535,8 @@
* @see PrintAttributes#COLOR_MODE_COLOR
* @see PrintAttributes#COLOR_MODE_MONOCHROME
*/
- public Builder setColorModes(int colorModes, int defaultColorMode) {
+ public @NonNull Builder setColorModes(@ColorMode int colorModes,
+ @ColorMode int defaultColorMode) {
int currentModes = colorModes;
while (currentModes > 0) {
final int currentMode = (1 << Integer.numberOfTrailingZeros(currentModes));
@@ -562,7 +566,8 @@
* @see PrintAttributes#DUPLEX_MODE_LONG_EDGE
* @see PrintAttributes#DUPLEX_MODE_SHORT_EDGE
*/
- public Builder setDuplexModes(int duplexModes, int defaultDuplexMode) {
+ public @NonNull Builder setDuplexModes(@DuplexMode int duplexModes,
+ @DuplexMode int defaultDuplexMode) {
int currentModes = duplexModes;
while (currentModes > 0) {
final int currentMode = (1 << Integer.numberOfTrailingZeros(currentModes));
@@ -589,7 +594,7 @@
*
* @throws IllegalStateException If a required attribute was not specified.
*/
- public PrinterCapabilitiesInfo build() {
+ public @NonNull PrinterCapabilitiesInfo build() {
if (mPrototype.mMediaSizes == null || mPrototype.mMediaSizes.isEmpty()) {
throw new IllegalStateException("No media size specified.");
}
diff --git a/core/java/android/print/PrinterId.java b/core/java/android/print/PrinterId.java
index a3f3b2bf..83efe80 100644
--- a/core/java/android/print/PrinterId.java
+++ b/core/java/android/print/PrinterId.java
@@ -16,6 +16,7 @@
package android.print;
+import android.annotation.NonNull;
import android.content.ComponentName;
import android.os.Parcel;
import android.os.Parcelable;
@@ -65,7 +66,7 @@
*
* @return The printer name.
*/
- public String getLocalId() {
+ public @NonNull String getLocalId() {
return mLocalId;
}
diff --git a/core/java/android/print/PrinterInfo.java b/core/java/android/print/PrinterInfo.java
index 7fcc81f..3ac78ea 100644
--- a/core/java/android/print/PrinterInfo.java
+++ b/core/java/android/print/PrinterInfo.java
@@ -16,10 +16,26 @@
package android.print;
+import android.annotation.IntDef;
+import android.annotation.DrawableRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* This class represents the description of a printer. Instances of
* this class are created by print services to report to the system
@@ -30,6 +46,13 @@
*/
public final class PrinterInfo implements Parcelable {
+ /** @hide */
+ @IntDef({
+ STATUS_IDLE, STATUS_BUSY, STATUS_UNAVAILABLE
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Status {
+ }
/** Printer status: the printer is idle and ready to print. */
public static final int STATUS_IDLE = 1;
@@ -41,6 +64,18 @@
private PrinterId mId;
+ /** Resource inside the printer's services's package to be used as an icon */
+ private int mIconResourceId;
+
+ /** If a custom icon can be loaded for the printer */
+ private boolean mHasCustomPrinterIcon;
+
+ /** The generation of the icon in the cache. */
+ private int mCustomPrinterIconGen;
+
+ /** Intent that launches the activity showing more information about the printer. */
+ private PendingIntent mInfoIntent;
+
private String mName;
private int mStatus;
@@ -77,6 +112,10 @@
} else {
mCapabilities = null;
}
+ mIconResourceId = other.mIconResourceId;
+ mHasCustomPrinterIcon = other.mHasCustomPrinterIcon;
+ mCustomPrinterIconGen = other.mCustomPrinterIconGen;
+ mInfoIntent = other.mInfoIntent;
}
/**
@@ -84,16 +123,64 @@
*
* @return The printer id.
*/
- public PrinterId getId() {
+ public @NonNull PrinterId getId() {
return mId;
}
/**
+ * Get the icon to be used for this printer. If no per printer icon is available, the printer's
+ * service's icon is returned. If the printer has a custom icon this icon might get requested
+ * asynchronously. Once the icon is loaded the discovery sessions will be notified that the
+ * printer changed.
+ *
+ * @param context The context that will be using the icons
+ * @return The icon to be used for the printer or null if no icon could be found.
+ * @hide
+ */
+ @TestApi
+ public @Nullable Drawable loadIcon(@NonNull Context context) {
+ Drawable drawable = null;
+ PackageManager packageManager = context.getPackageManager();
+
+ if (mHasCustomPrinterIcon) {
+ PrintManager printManager = (PrintManager) context
+ .getSystemService(Context.PRINT_SERVICE);
+
+ Icon icon = printManager.getCustomPrinterIcon(mId);
+
+ if (icon != null) {
+ drawable = icon.loadDrawable(context);
+ }
+ }
+
+ if (drawable == null) {
+ try {
+ String packageName = mId.getServiceName().getPackageName();
+ PackageInfo packageInfo = packageManager.getPackageInfo(packageName, 0);
+ ApplicationInfo appInfo = packageInfo.applicationInfo;
+
+ // If no custom icon is available, try the icon from the resources
+ if (mIconResourceId != 0) {
+ drawable = packageManager.getDrawable(packageName, mIconResourceId, appInfo);
+ }
+
+ // Fall back to the printer's service's icon if no per printer icon could be found
+ if (drawable == null) {
+ drawable = appInfo.loadIcon(packageManager);
+ }
+ } catch (NameNotFoundException e) {
+ }
+ }
+
+ return drawable;
+ }
+
+ /**
* Get the printer name.
*
* @return The printer name.
*/
- public String getName() {
+ public @Nullable String getName() {
return mName;
}
@@ -106,7 +193,7 @@
* @see #STATUS_IDLE
* @see #STATUS_UNAVAILABLE
*/
- public int getStatus() {
+ public @Status int getStatus() {
return mStatus;
}
@@ -115,16 +202,28 @@
*
* @return The description.
*/
- public String getDescription() {
+ public @Nullable String getDescription() {
return mDescription;
}
/**
+ * Get the {@link PendingIntent} that launches the activity showing more information about the
+ * printer.
+ *
+ * @return the {@link PendingIntent} that launches the activity showing more information about
+ * the printer or null if it is not configured
+ * @hide
+ */
+ public @Nullable PendingIntent getInfoIntent() {
+ return mInfoIntent;
+ }
+
+ /**
* Gets the printer capabilities.
*
* @return The capabilities.
*/
- public PrinterCapabilitiesInfo getCapabilities() {
+ public @Nullable PrinterCapabilitiesInfo getCapabilities() {
return mCapabilities;
}
@@ -134,6 +233,10 @@
mStatus = parcel.readInt();
mDescription = parcel.readString();
mCapabilities = parcel.readParcelable(null);
+ mIconResourceId = parcel.readInt();
+ mHasCustomPrinterIcon = parcel.readByte() != 0;
+ mCustomPrinterIconGen = parcel.readInt();
+ mInfoIntent = parcel.readParcelable(null);
}
@Override
@@ -148,6 +251,10 @@
parcel.writeInt(mStatus);
parcel.writeString(mDescription);
parcel.writeParcelable(mCapabilities, flags);
+ parcel.writeInt(mIconResourceId);
+ parcel.writeByte((byte) (mHasCustomPrinterIcon ? 1 : 0));
+ parcel.writeInt(mCustomPrinterIconGen);
+ parcel.writeParcelable(mInfoIntent, flags);
}
@Override
@@ -159,9 +266,61 @@
result = prime * result + mStatus;
result = prime * result + ((mDescription != null) ? mDescription.hashCode() : 0);
result = prime * result + ((mCapabilities != null) ? mCapabilities.hashCode() : 0);
+ result = prime * result + mIconResourceId;
+ result = prime * result + (mHasCustomPrinterIcon ? 1 : 0);
+ result = prime * result + mCustomPrinterIconGen;
+ result = prime * result + ((mInfoIntent != null) ? mInfoIntent.hashCode() : 0);
return result;
}
+ /**
+ * Compare two {@link PrinterInfo printerInfos} in all aspects beside being null and the
+ * {@link #mStatus}.
+ *
+ * @param other the other {@link PrinterInfo}
+ * @return true iff the infos are equivalent
+ * @hide
+ */
+ public boolean equalsIgnoringStatus(PrinterInfo other) {
+ if (mId == null) {
+ if (other.mId != null) {
+ return false;
+ }
+ } else if (!mId.equals(other.mId)) {
+ return false;
+ }
+ if (!TextUtils.equals(mName, other.mName)) {
+ return false;
+ }
+ if (!TextUtils.equals(mDescription, other.mDescription)) {
+ return false;
+ }
+ if (mCapabilities == null) {
+ if (other.mCapabilities != null) {
+ return false;
+ }
+ } else if (!mCapabilities.equals(other.mCapabilities)) {
+ return false;
+ }
+ if (mIconResourceId != other.mIconResourceId) {
+ return false;
+ }
+ if (mHasCustomPrinterIcon != other.mHasCustomPrinterIcon) {
+ return false;
+ }
+ if (mCustomPrinterIconGen != other.mCustomPrinterIconGen) {
+ return false;
+ }
+ if (mInfoIntent == null) {
+ if (other.mInfoIntent != null) {
+ return false;
+ }
+ } else if (!mInfoIntent.equals(other.mInfoIntent)) {
+ return false;
+ }
+ return true;
+ }
+
@Override
public boolean equals(Object obj) {
if (this == obj) {
@@ -174,29 +333,12 @@
return false;
}
PrinterInfo other = (PrinterInfo) obj;
- if (mId == null) {
- if (other.mId != null) {
- return false;
- }
- } else if (!mId.equals(other.mId)) {
- return false;
- }
- if (!TextUtils.equals(mName, other.mName)) {
+ if (!equalsIgnoringStatus(other)) {
return false;
}
if (mStatus != other.mStatus) {
return false;
}
- if (!TextUtils.equals(mDescription, other.mDescription)) {
- return false;
- }
- if (mCapabilities == null) {
- if (other.mCapabilities != null) {
- return false;
- }
- } else if (!mCapabilities.equals(other.mCapabilities)) {
- return false;
- }
return true;
}
@@ -209,6 +351,10 @@
builder.append(", status=").append(mStatus);
builder.append(", description=").append(mDescription);
builder.append(", capabilities=").append(mCapabilities);
+ builder.append(", iconResId=").append(mIconResourceId);
+ builder.append(", hasCustomPrinterIcon=").append(mHasCustomPrinterIcon);
+ builder.append(", customPrinterIconGen=").append(mCustomPrinterIconGen);
+ builder.append(", infoIntent=").append(mInfoIntent);
builder.append("\"}");
return builder.toString();
}
@@ -228,7 +374,7 @@
* @throws IllegalArgumentException If the printer id is null, or the
* printer name is empty or the status is not a valid one.
*/
- public Builder(PrinterId printerId, String name, int status) {
+ public Builder(@NonNull PrinterId printerId, @NonNull String name, @Status int status) {
if (printerId == null) {
throw new IllegalArgumentException("printerId cannot be null.");
}
@@ -249,7 +395,7 @@
*
* @param other Other info from which to start building.
*/
- public Builder(PrinterInfo other) {
+ public Builder(@NonNull PrinterInfo other) {
mPrototype = new PrinterInfo();
mPrototype.copyFrom(other);
}
@@ -264,19 +410,49 @@
* @see PrinterInfo#STATUS_BUSY
* @see PrinterInfo#STATUS_UNAVAILABLE
*/
- public Builder setStatus(int status) {
+ public @Nullable Builder setStatus(@Status int status) {
mPrototype.mStatus = status;
return this;
}
/**
+ * Set a drawable resource as icon for this printer. If no icon is set the printer's
+ * service's icon is used for the printer.
+ *
+ * @return This builder.
+ * @see PrinterInfo.Builder#setHasCustomPrinterIcon
+ */
+ public @NonNull Builder setIconResourceId(@DrawableRes int iconResourceId) {
+ mPrototype.mIconResourceId = iconResourceId;
+ return this;
+ }
+
+ /**
+ * Declares that the print service can load a custom per printer's icon. If both
+ * {@link PrinterInfo.Builder#setIconResourceId} and a custom icon are set the resource icon
+ * is shown while the custom icon loads but then the custom icon is used. If
+ * {@link PrinterInfo.Builder#setIconResourceId} is not set the printer's service's icon is
+ * shown while loading.
+ * <p>
+ * The icon is requested asynchronously and only when needed via
+ * {@link android.printservice.PrinterDiscoverySession#onRequestCustomPrinterIcon}.
+ * </p>
+ *
+ * @return This builder.
+ */
+ public @NonNull Builder setHasCustomPrinterIcon() {
+ mPrototype.mHasCustomPrinterIcon = true;
+ return this;
+ }
+
+ /**
* Sets the <strong>localized</strong> printer name which
* is shown to the user
*
* @param name The name.
* @return This builder.
*/
- public Builder setName(String name) {
+ public @NonNull Builder setName(@NonNull String name) {
mPrototype.mName = name;
return this;
}
@@ -288,18 +464,30 @@
* @param description The description.
* @return This builder.
*/
- public Builder setDescription(String description) {
+ public @NonNull Builder setDescription(@NonNull String description) {
mPrototype.mDescription = description;
return this;
}
/**
+ * Sets the {@link PendingIntent} that launches an activity showing more information about
+ * the printer.
+ *
+ * @param infoIntent The {@link PendingIntent intent}.
+ * @return This builder.
+ */
+ public @NonNull Builder setInfoIntent(@NonNull PendingIntent infoIntent) {
+ mPrototype.mInfoIntent = infoIntent;
+ return this;
+ }
+
+ /**
* Sets the printer capabilities.
*
* @param capabilities The capabilities.
* @return This builder.
*/
- public Builder setCapabilities(PrinterCapabilitiesInfo capabilities) {
+ public @NonNull Builder setCapabilities(@NonNull PrinterCapabilitiesInfo capabilities) {
mPrototype.mCapabilities = capabilities;
return this;
}
@@ -309,7 +497,7 @@
*
* @return A new {@link PrinterInfo}.
*/
- public PrinterInfo build() {
+ public @NonNull PrinterInfo build() {
return mPrototype;
}
@@ -318,6 +506,19 @@
|| status == STATUS_BUSY
|| status == STATUS_UNAVAILABLE);
}
+
+ /**
+ * Increments the generation number of the custom printer icon. As the {@link PrinterInfo}
+ * does not match the previous one anymore, users of the {@link PrinterInfo} will reload the
+ * icon if needed.
+ *
+ * @return This builder.
+ * @hide
+ */
+ public @NonNull Builder incCustomPrinterIconGen() {
+ mPrototype.mCustomPrinterIconGen++;
+ return this;
+ }
}
public static final Parcelable.Creator<PrinterInfo> CREATOR =
diff --git a/core/java/android/print/pdf/PrintedPdfDocument.java b/core/java/android/print/pdf/PrintedPdfDocument.java
index 2d8aafa..df7c054 100644
--- a/core/java/android/print/pdf/PrintedPdfDocument.java
+++ b/core/java/android/print/pdf/PrintedPdfDocument.java
@@ -16,26 +16,25 @@
package android.print.pdf;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.pdf.PdfDocument;
-import android.graphics.pdf.PdfDocument.Page;
-import android.graphics.pdf.PdfDocument.PageInfo;
import android.print.PrintAttributes;
import android.print.PrintAttributes.Margins;
import android.print.PrintAttributes.MediaSize;
/**
- * This class is a helper for creating a PDF file for given print
- * attributes. It is useful for implementing printing via the native
- * Android graphics APIs.
+ * This class is a helper for creating a PDF file for given print attributes. It is useful for
+ * implementing printing via the native Android graphics APIs.
* <p>
- * This class computes the page width, page height, and content rectangle
- * from the provided print attributes and these precomputed values can be
- * accessed via {@link #getPageWidth()}, {@link #getPageHeight()}, and
- * {@link #getPageContentRect()}, respectively. The {@link #startPage(int)}
- * methods creates pages whose {@link PageInfo} is initialized with the
- * precomputed values for width, height, and content rectangle.
+ * This class computes the page width, page height, and content rectangle from the provided print
+ * attributes and these precomputed values can be accessed via {@link #getPageWidth()},
+ * {@link #getPageHeight()}, and {@link #getPageContentRect()}, respectively. The
+ * {@link #startPage(int)} methods creates pages whose
+ * {@link android.graphics.pdf.PdfDocument.PageInfo PageInfo} is initialized with the precomputed
+ * values for width, height, and content rectangle.
* <p>
* A typical use of the APIs looks like this:
* </p>
@@ -81,7 +80,7 @@
* @param context Context instance for accessing resources.
* @param attributes The print attributes.
*/
- public PrintedPdfDocument(Context context, PrintAttributes attributes) {
+ public PrintedPdfDocument(@NonNull Context context, @NonNull PrintAttributes attributes) {
MediaSize mediaSize = attributes.getMediaSize();
// Compute the size of the target canvas from the attributes.
@@ -105,28 +104,28 @@
}
/**
- * Starts a new page. The page is created using width, height and content
- * rectangle computed from the print attributes passed in the constructor
- * and the given page number to create an appropriate {@link PageInfo}.
+ * Starts a new page. The page is created using width, height and content rectangle computed
+ * from the print attributes passed in the constructor and the given page number to create an
+ * appropriate {@link android.graphics.pdf.PdfDocument.PageInfo PageInfo}.
* <p>
- * After the page is created you can draw arbitrary content on the page's
- * canvas which you can get by calling {@link Page#getCanvas() Page.getCanvas()}.
+ * After the page is created you can draw arbitrary content on the page's canvas which you can
+ * get by calling {@link android.graphics.pdf.PdfDocument.Page#getCanvas() Page.getCanvas()}.
* After you are done drawing the content you should finish the page by calling
- * {@link #finishPage(Page)}. After the page is finished you should no longer
- * access the page or its canvas.
+ * {@link #finishPage(Page)}. After the page is finished you should no longer access the page or
+ * its canvas.
* </p>
* <p>
- * <strong>Note:</strong> Do not call this method after {@link #close()}.
- * Also do not call this method if the last page returned by this method
- * is not finished by calling {@link #finishPage(Page)}.
+ * <strong>Note:</strong> Do not call this method after {@link #close()}. Also do not call this
+ * method if the last page returned by this method is not finished by calling
+ * {@link #finishPage(Page)}.
* </p>
*
- * @param pageNumber The page number. Must be a positive value.
+ * @param pageNumber The page number. Must be a non negative.
* @return A blank page.
*
* @see #finishPage(Page)
*/
- public Page startPage(int pageNumber) {
+ public @NonNull Page startPage(@IntRange(from = 0) int pageNumber) {
PageInfo pageInfo = new PageInfo
.Builder(mPageWidth, mPageHeight, pageNumber)
.setContentRect(mContentRect)
@@ -139,7 +138,7 @@
*
* @return The page width in PostScript points (1/72th of an inch).
*/
- public int getPageWidth() {
+ public @IntRange(from = 0) int getPageWidth() {
return mPageWidth;
}
@@ -148,7 +147,7 @@
*
* @return The page height in PostScript points (1/72th of an inch).
*/
- public int getPageHeight() {
+ public @IntRange(from = 0) int getPageHeight() {
return mPageHeight;
}
@@ -158,7 +157,7 @@
*
* @return The content rectangle.
*/
- public Rect getPageContentRect() {
+ public @NonNull Rect getPageContentRect() {
return mContentRect;
}
}
diff --git a/core/java/android/printservice/CustomPrinterIconCallback.java b/core/java/android/printservice/CustomPrinterIconCallback.java
new file mode 100644
index 0000000..ea9ea8b
--- /dev/null
+++ b/core/java/android/printservice/CustomPrinterIconCallback.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 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.printservice;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.drawable.Icon;
+import android.os.RemoteException;
+import android.print.PrinterId;
+import android.util.Log;
+
+
+/**
+ * Callback for {@link PrinterDiscoverySession#onRequestCustomPrinterIcon}.
+ */
+public class CustomPrinterIconCallback {
+ /** The printer the call back is for */
+ private final @NonNull PrinterId mPrinterId;
+ private final @NonNull IPrintServiceClient mObserver;
+ private static final String LOG_TAG = "CustomPrinterIconCallback";
+
+ /**
+ * Create a callback class to be used once a icon is loaded
+ *
+ * @param printerId The printer the icon should be loaded for
+ * @param observer The observer that needs to be notified about the update.
+ */
+ CustomPrinterIconCallback(@NonNull PrinterId printerId, @NonNull IPrintServiceClient observer) {
+ mPrinterId = printerId;
+ mObserver = observer;
+ }
+
+ /**
+ * Provide a new icon for a printer. Can be called more than once to update the icon.
+ *
+ * @param icon The new icon for the printer or null to unset the current icon
+ * @return true iff the icon could be updated
+ */
+ public boolean onCustomPrinterIconLoaded(@Nullable Icon icon) {
+ try {
+ mObserver.onCustomPrinterIconLoaded(mPrinterId, icon);
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG , "Could not update icon", e);
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/core/java/android/printservice/IPrintService.aidl b/core/java/android/printservice/IPrintService.aidl
index ee36619..3750d7a 100644
--- a/core/java/android/printservice/IPrintService.aidl
+++ b/core/java/android/printservice/IPrintService.aidl
@@ -10,7 +10,7 @@
* 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 languagÿe governing permissions and
+ * See the License for the specific language governing permissions and
* limitations under the License.
*/
@@ -35,6 +35,15 @@
void stopPrinterDiscovery();
void validatePrinters(in List<PrinterId> printerIds);
void startPrinterStateTracking(in PrinterId printerId);
+
+ /**
+ * Request the custom icon for a printer.
+ *
+ * @param printerId the id of the printer the icon should be loaded for
+ * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon()
+ */
+ void requestCustomPrinterIcon(in PrinterId printerId);
+
void stopPrinterStateTracking(in PrinterId printerId);
void destroyPrinterDiscoverySession();
}
diff --git a/core/java/android/printservice/IPrintServiceClient.aidl b/core/java/android/printservice/IPrintServiceClient.aidl
index b4baa48..0ae1e18 100644
--- a/core/java/android/printservice/IPrintServiceClient.aidl
+++ b/core/java/android/printservice/IPrintServiceClient.aidl
@@ -16,6 +16,7 @@
package android.printservice;
+import android.graphics.drawable.Icon;
import android.os.ParcelFileDescriptor;
import android.print.PrintJobInfo;
import android.print.PrinterId;
@@ -53,4 +54,13 @@
void onPrintersAdded(in ParceledListSlice printers);
void onPrintersRemoved(in ParceledListSlice printerIds);
+
+ /**
+ * Handle that a custom icon for a printer was loaded.
+ *
+ * @param printerId the id of the printer the icon belongs to
+ * @param icon the icon that was loaded
+ * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon()
+ */
+ void onCustomPrinterIconLoaded(in PrinterId printerId, in Icon icon);
}
diff --git a/core/java/android/printservice/PrintDocument.java b/core/java/android/printservice/PrintDocument.java
index e43f2a8..0121ae1 100644
--- a/core/java/android/printservice/PrintDocument.java
+++ b/core/java/android/printservice/PrintDocument.java
@@ -16,6 +16,8 @@
package android.printservice;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.print.PrintDocumentInfo;
@@ -54,7 +56,7 @@
*
* @return The document info.
*/
- public PrintDocumentInfo getInfo() {
+ public @NonNull PrintDocumentInfo getInfo() {
PrintService.throwIfNotCalledOnMainThread();
return mInfo;
}
@@ -69,7 +71,7 @@
*
* @return A file descriptor for reading the data.
*/
- public ParcelFileDescriptor getData() {
+ public @Nullable ParcelFileDescriptor getData() {
PrintService.throwIfNotCalledOnMainThread();
ParcelFileDescriptor source = null;
ParcelFileDescriptor sink = null;
diff --git a/core/java/android/printservice/PrintJob.java b/core/java/android/printservice/PrintJob.java
index 86fc292..6414b6a 100644
--- a/core/java/android/printservice/PrintJob.java
+++ b/core/java/android/printservice/PrintJob.java
@@ -344,7 +344,7 @@
* @return True if the tag was set, false otherwise.
*/
@MainThread
- public boolean setTag(String tag) {
+ public boolean setTag(@NonNull String tag) {
PrintService.throwIfNotCalledOnMainThread();
if (isInImmutableState()) {
return false;
@@ -364,7 +364,8 @@
*
* @see #setTag(String)
*/
- public String getTag() {
+ @MainThread
+ public @Nullable String getTag() {
PrintService.throwIfNotCalledOnMainThread();
return getInfo().getTag();
}
diff --git a/core/java/android/printservice/PrintService.java b/core/java/android/printservice/PrintService.java
index 6295822..d0037b7 100644
--- a/core/java/android/printservice/PrintService.java
+++ b/core/java/android/printservice/PrintService.java
@@ -16,6 +16,7 @@
package android.printservice;
+import android.annotation.Nullable;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
@@ -191,30 +192,29 @@
/**
* If you declared an optional activity with advanced print options via the
- * {@link android.R.attr#advancedPrintOptionsActivity advancedPrintOptionsActivity}
- * attribute, this extra is used to pass in the currently constructed {@link
- * PrintJobInfo} to your activity allowing you to modify it. After you are
- * done, you must return the modified {@link PrintJobInfo} via the same extra.
+ * {@link android.R.attr#advancedPrintOptionsActivity advancedPrintOptionsActivity} attribute,
+ * this extra is used to pass in the currently constructed {@link PrintJobInfo} to your activity
+ * allowing you to modify it. After you are done, you must return the modified
+ * {@link PrintJobInfo} via the same extra.
* <p>
- * You cannot modify the passed in {@link PrintJobInfo} directly, rather you
- * should build another one using the {@link PrintJobInfo.Builder} class. You
- * can specify any standard properties and add advanced, printer specific,
- * ones via {@link PrintJobInfo.Builder#putAdvancedOption(String, String)
- * PrintJobInfo.Builder.putAdvancedOption(String, String)} and {@link
- * PrintJobInfo.Builder#putAdvancedOption(String, int)
- * PrintJobInfo.Builder.putAdvancedOption(String, int)}. The advanced options
- * are not interpreted by the system, they will not be visible to applications,
- * and can only be accessed by your print service via {@link
- * PrintJob#getAdvancedStringOption(String) PrintJob.getAdvancedStringOption(String)}
- * and {@link PrintJob#getAdvancedIntOption(String) PrintJob.getAdvancedIntOption(String)}.
+ * You cannot modify the passed in {@link PrintJobInfo} directly, rather you should build
+ * another one using the {@link android.print.PrintJobInfo.Builder PrintJobInfo.Builder} class.
+ * You can specify any standard properties and add advanced, printer specific, ones via
+ * {@link android.print.PrintJobInfo.Builder#putAdvancedOption(String, String)
+ * PrintJobInfo.Builder.putAdvancedOption(String, String)} and
+ * {@link android.print.PrintJobInfo.Builder#putAdvancedOption(String, int)
+ * PrintJobInfo.Builder.putAdvancedOption(String, int)}. The advanced options are not
+ * interpreted by the system, they will not be visible to applications, and can only be accessed
+ * by your print service via {@link PrintJob#getAdvancedStringOption(String)
+ * PrintJob.getAdvancedStringOption(String)} and {@link PrintJob#getAdvancedIntOption(String)
+ * PrintJob.getAdvancedIntOption(String)}.
* </p>
* <p>
- * If the advanced print options activity offers changes to the standard print
- * options, you can get the current {@link android.print.PrinterInfo} using the
- * {@link #EXTRA_PRINTER_INFO} extra which will allow you to present the user
- * with UI options supported by the current printer. For example, if the current
- * printer does not support a given media size, you should not offer it in the
- * advanced print options UI.
+ * If the advanced print options activity offers changes to the standard print options, you can
+ * get the current {@link android.print.PrinterInfo PrinterInfo} using the
+ * {@link #EXTRA_PRINTER_INFO} extra which will allow you to present the user with UI options
+ * supported by the current printer. For example, if the current printer does not support a
+ * given media size, you should not offer it in the advanced print options UI.
* </p>
*
* @see #EXTRA_PRINTER_INFO
@@ -275,9 +275,10 @@
/**
* Callback asking you to create a new {@link PrinterDiscoverySession}.
*
+ * @return The created session.
* @see PrinterDiscoverySession
*/
- protected abstract PrinterDiscoverySession onCreatePrinterDiscoverySession();
+ protected abstract @Nullable PrinterDiscoverySession onCreatePrinterDiscoverySession();
/**
* Called when cancellation of a print job is requested. The service
@@ -368,6 +369,7 @@
mHandler.sendEmptyMessage(ServiceHandler.MSG_DESTROY_PRINTER_DISCOVERY_SESSION);
}
+ @Override
public void startPrinterDiscovery(List<PrinterId> priorityList) {
mHandler.obtainMessage(ServiceHandler.MSG_START_PRINTER_DISCOVERY,
priorityList).sendToTarget();
@@ -391,6 +393,12 @@
}
@Override
+ public void requestCustomPrinterIcon(PrinterId printerId) {
+ mHandler.obtainMessage(ServiceHandler.MSG_REQUEST_CUSTOM_PRINTER_ICON,
+ printerId).sendToTarget();
+ }
+
+ @Override
public void stopPrinterStateTracking(PrinterId printerId) {
mHandler.obtainMessage(ServiceHandler.MSG_STOP_PRINTER_STATE_TRACKING,
printerId).sendToTarget();
@@ -423,10 +431,11 @@
public static final int MSG_STOP_PRINTER_DISCOVERY = 4;
public static final int MSG_VALIDATE_PRINTERS = 5;
public static final int MSG_START_PRINTER_STATE_TRACKING = 6;
- public static final int MSG_STOP_PRINTER_STATE_TRACKING = 7;
- public static final int MSG_ON_PRINTJOB_QUEUED = 8;
- public static final int MSG_ON_REQUEST_CANCEL_PRINTJOB = 9;
- public static final int MSG_SET_CLIENT = 10;
+ public static final int MSG_REQUEST_CUSTOM_PRINTER_ICON = 7;
+ public static final int MSG_STOP_PRINTER_STATE_TRACKING = 8;
+ public static final int MSG_ON_PRINTJOB_QUEUED = 9;
+ public static final int MSG_ON_REQUEST_CANCEL_PRINTJOB = 10;
+ public static final int MSG_SET_CLIENT = 11;
public ServiceHandler(Looper looper) {
super(looper, null, true);
@@ -508,6 +517,17 @@
}
} break;
+ case MSG_REQUEST_CUSTOM_PRINTER_ICON: {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "MSG_REQUEST_CUSTOM_PRINTER_ICON "
+ + getPackageName());
+ }
+ if (mDiscoverySession != null) {
+ PrinterId printerId = (PrinterId) message.obj;
+ mDiscoverySession.requestCustomPrinterIcon(printerId);
+ }
+ } break;
+
case MSG_STOP_PRINTER_STATE_TRACKING: {
if (DEBUG) {
Log.i(LOG_TAG, "MSG_STOP_PRINTER_STATE_TRACKING "
diff --git a/core/java/android/printservice/PrintServiceInfo.java b/core/java/android/printservice/PrintServiceInfo.java
index a2c6c09..b33ef83 100644
--- a/core/java/android/printservice/PrintServiceInfo.java
+++ b/core/java/android/printservice/PrintServiceInfo.java
@@ -98,8 +98,7 @@
*
* @param resolveInfo The service resolve info.
* @param context Context for accessing resources.
- * @throws XmlPullParserException If a XML parsing error occurs.
- * @throws IOException If a I/O error occurs.
+ * @return The created instance.
*/
public static PrintServiceInfo create(ResolveInfo resolveInfo, Context context) {
String settingsActivityName = null;
@@ -220,10 +219,12 @@
/**
* {@inheritDoc}
*/
+ @Override
public int describeContents() {
return 0;
}
+ @Override
public void writeToParcel(Parcel parcel, int flagz) {
parcel.writeString(mId);
parcel.writeParcelable(mResolveInfo, 0);
@@ -275,10 +276,12 @@
public static final Parcelable.Creator<PrintServiceInfo> CREATOR =
new Parcelable.Creator<PrintServiceInfo>() {
+ @Override
public PrintServiceInfo createFromParcel(Parcel parcel) {
return new PrintServiceInfo(parcel);
}
+ @Override
public PrintServiceInfo[] newArray(int size) {
return new PrintServiceInfo[size];
}
diff --git a/core/java/android/printservice/PrinterDiscoverySession.java b/core/java/android/printservice/PrinterDiscoverySession.java
index 17cb68f..cd5a903 100644
--- a/core/java/android/printservice/PrinterDiscoverySession.java
+++ b/core/java/android/printservice/PrinterDiscoverySession.java
@@ -16,6 +16,7 @@
package android.printservice;
+import android.annotation.NonNull;
import android.content.pm.ParceledListSlice;
import android.os.RemoteException;
import android.print.PrinterCapabilitiesInfo;
@@ -138,7 +139,7 @@
* @see #removePrinters(List)
* @see #isDestroyed()
*/
- public final List<PrinterInfo> getPrinters() {
+ public final @NonNull List<PrinterInfo> getPrinters() {
PrintService.throwIfNotCalledOnMainThread();
if (mIsDestroyed) {
return Collections.emptyList();
@@ -161,7 +162,7 @@
* @see #getPrinters()
* @see #isDestroyed()
*/
- public final void addPrinters(List<PrinterInfo> printers) {
+ public final void addPrinters(@NonNull List<PrinterInfo> printers) {
PrintService.throwIfNotCalledOnMainThread();
// If the session is destroyed - nothing do to.
@@ -225,7 +226,7 @@
* @see #getPrinters()
* @see #isDestroyed()
*/
- public final void removePrinters(List<PrinterId> printerIds) {
+ public final void removePrinters(@NonNull List<PrinterId> printerIds) {
PrintService.throwIfNotCalledOnMainThread();
// If the session is destroyed - nothing do to.
@@ -350,7 +351,7 @@
* @see #removePrinters(List)
* @see #isPrinterDiscoveryStarted()
*/
- public abstract void onStartPrinterDiscovery(List<PrinterId> priorityList);
+ public abstract void onStartPrinterDiscovery(@NonNull List<PrinterId> priorityList);
/**
* Callback notifying you that you should stop printer discovery.
@@ -372,10 +373,10 @@
*
* @param printerIds The printers to validate.
*
- * @see PrinterInfo.Builder#setCapabilities(PrinterCapabilitiesInfo)
+ * @see android.print.PrinterInfo.Builder#setCapabilities(PrinterCapabilitiesInfo)
* PrinterInfo.Builder.setCapabilities(PrinterCapabilitiesInfo)
*/
- public abstract void onValidatePrinters(List<PrinterId> printerIds);
+ public abstract void onValidatePrinters(@NonNull List<PrinterId> printerIds);
/**
* Callback asking you to start tracking the state of a printer. Tracking
@@ -400,10 +401,24 @@
* @param printerId The printer to start tracking.
*
* @see #onStopPrinterStateTracking(PrinterId)
- * @see PrinterInfo.Builder#setCapabilities(PrinterCapabilitiesInfo)
+ * @see android.print.PrinterInfo.Builder#setCapabilities(PrinterCapabilitiesInfo)
* PrinterInfo.Builder.setCapabilities(PrinterCapabilitiesInfo)
*/
- public abstract void onStartPrinterStateTracking(PrinterId printerId);
+ public abstract void onStartPrinterStateTracking(@NonNull PrinterId printerId);
+
+ /**
+ * Request the custom icon for a printer. Once the icon is available use
+ * {@link CustomPrinterIconCallback#onCustomPrinterIconLoaded} to send the data to the print
+ * service.
+ *
+ * @param printerId The printer to icon belongs to.
+ * @param callback Callback for returning the icon to the print spooler.
+ *
+ * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon()
+ */
+ public void onRequestCustomPrinterIcon(@NonNull PrinterId printerId,
+ @NonNull CustomPrinterIconCallback callback) {
+ }
/**
* Callback asking you to stop tracking the state of a printer. The passed
@@ -414,7 +429,7 @@
*
* @see #onStartPrinterStateTracking(PrinterId)
*/
- public abstract void onStopPrinterStateTracking(PrinterId printerId);
+ public abstract void onStopPrinterStateTracking(@NonNull PrinterId printerId);
/**
* Gets the printers that should be tracked. These are printers that are
@@ -434,7 +449,7 @@
* @see #onStopPrinterStateTracking(PrinterId)
* @see #isDestroyed()
*/
- public final List<PrinterId> getTrackedPrinters() {
+ public final @NonNull List<PrinterId> getTrackedPrinters() {
PrintService.throwIfNotCalledOnMainThread();
if (mIsDestroyed) {
return Collections.emptyList();
@@ -476,7 +491,7 @@
return mIsDiscoveryStarted;
}
- void startPrinterDiscovery(List<PrinterId> priorityList) {
+ void startPrinterDiscovery(@NonNull List<PrinterId> priorityList) {
if (!mIsDestroyed) {
mIsDiscoveryStarted = true;
sendOutOfDiscoveryPeriodPrinterChanges();
@@ -494,13 +509,13 @@
}
}
- void validatePrinters(List<PrinterId> printerIds) {
+ void validatePrinters(@NonNull List<PrinterId> printerIds) {
if (!mIsDestroyed && mObserver != null) {
onValidatePrinters(printerIds);
}
}
- void startPrinterStateTracking(PrinterId printerId) {
+ void startPrinterStateTracking(@NonNull PrinterId printerId) {
if (!mIsDestroyed && mObserver != null
&& !mTrackedPrinters.contains(printerId)) {
mTrackedPrinters.add(printerId);
@@ -508,7 +523,21 @@
}
}
- void stopPrinterStateTracking(PrinterId printerId) {
+ /**
+ * Request the custom icon for a printer.
+ *
+ * @param printerId The printer to icon belongs to.
+ * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon()
+ */
+ void requestCustomPrinterIcon(@NonNull PrinterId printerId) {
+ if (!mIsDestroyed && mObserver != null) {
+ CustomPrinterIconCallback callback = new CustomPrinterIconCallback(printerId,
+ mObserver);
+ onRequestCustomPrinterIcon(printerId, callback);
+ }
+ }
+
+ void stopPrinterStateTracking(@NonNull PrinterId printerId) {
if (!mIsDestroyed && mObserver != null
&& mTrackedPrinters.remove(printerId)) {
onStopPrinterStateTracking(printerId);
diff --git a/core/java/android/view/DragEvent.java b/core/java/android/view/DragEvent.java
index 5903d4a..71db0b5 100644
--- a/core/java/android/view/DragEvent.java
+++ b/core/java/android/view/DragEvent.java
@@ -364,20 +364,9 @@
return mClipDescription;
}
- /**
- * Requests the permissions for the content URIs contained in {@link android.content.ClipData}
- * object associated with this event. Which permissions will be granted is defined by the set of
- * flags passed to {@link View#startDragAndDrop(ClipData, View.DragShadowBuilder, Object, int)}.
- * Returns the {@link DropPermissions} object that can be used by the receiving app to release
- * the permissions for the content URIs when they are no longer needed.
- * This method only returns valid data if the event action is {@link #ACTION_DROP}.
- * @return The DropPermissions object used to control access to the content URIs.
- */
- public DropPermissions requestDropPermissions() {
- if (mDropPermissions == null) {
- return null;
- }
- return new DropPermissions(mDropPermissions);
+ /** @hide */
+ public IDropPermissions getDropPermissions() {
+ return mDropPermissions;
}
/**
diff --git a/core/java/android/view/DropPermissions.java b/core/java/android/view/DropPermissions.java
index 780461f..8c948a9 100644
--- a/core/java/android/view/DropPermissions.java
+++ b/core/java/android/view/DropPermissions.java
@@ -16,11 +16,27 @@
package android.view;
+import android.os.IBinder;
import android.os.RemoteException;
import com.android.internal.view.IDropPermissions;
import dalvik.system.CloseGuard;
+/**
+ * {@link DropPermissions} controls the access permissions for the content URIs associated with a
+ * {@link DragEvent}.
+ * <p>
+ * Permission are granted when this object is created by {@link
+ * android.app.Activity#requestDropPermissions(DragEvent) Activity.requestDropPermissions}.
+ * Which permissions are granted is defined by the set of flags passed to {@link
+ * View#startDragAndDrop(android.content.ClipData, View.DragShadowBuilder, Object, int)
+ * View.startDragAndDrop} by the app that started the drag operation.
+ * <p>
+ * The life cycle of the permissions is bound to the activity used to call {@link
+ * android.app.Activity#requestDropPermissions(DragEvent) requestDropPermissions}. The
+ * permissions are revoked when this activity is destroyed, or when {@link #release()} is called,
+ * whichever occurs first.
+ */
public final class DropPermissions {
private final IDropPermissions mDropPermissions;
@@ -28,21 +44,43 @@
private final CloseGuard mCloseGuard = CloseGuard.get();
/**
- * Create a new DropPermissions object to be passed to the client with a DragEvent.
- *
+ * Create a new {@link DropPermissions} object to control the access permissions for content
+ * URIs associated with {@link DragEvent}.
+ * @param dragEvent Drag event
+ * @return {@link DropPermissions} object or null if there are no content URIs associated with
+ * the {@link DragEvent}.
* @hide
*/
- DropPermissions(IDropPermissions dropPermissions) {
- mDropPermissions = dropPermissions;
- try {
- mDropPermissions.take();
- } catch (RemoteException e) {
+ public static DropPermissions obtain(DragEvent dragEvent) {
+ if (dragEvent.getDropPermissions() == null) {
+ return null;
}
- mCloseGuard.open("release");
+ return new DropPermissions(dragEvent.getDropPermissions());
+ }
+
+ /** @hide */
+ private DropPermissions(IDropPermissions dropPermissions) {
+ mDropPermissions = dropPermissions;
}
/**
- * Revoke permissions taken by {@link DragEvent#requestDropPermissions()}.
+ * Take the permissions and bind their lifetime to the activity.
+ * @param activityToken Binder pointing to an Activity instance to bind the lifetime to.
+ * @return True if permissions are successfully taken.
+ * @hide
+ */
+ public boolean take(IBinder activityToken) {
+ try {
+ mDropPermissions.take(activityToken);
+ } catch (RemoteException e) {
+ return false;
+ }
+ mCloseGuard.open("release");
+ return true;
+ }
+
+ /**
+ * Revoke permissions explicitly.
*/
public void release() {
try {
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 394660f..ef50fdc 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -57,6 +57,7 @@
private static native int nativeGetHeight(long nativeObject);
private static native long nativeGetNextFrameNumber(long nativeObject);
+ private static native int nativeSetScalingMode(long nativeObject, int scalingMode);
public static final Parcelable.Creator<Surface> CREATOR =
new Parcelable.Creator<Surface>() {
@@ -95,6 +96,21 @@
private HwuiContext mHwuiContext;
/** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({SCALING_MODE_FREEZE, SCALING_MODE_SCALE_TO_WINDOW,
+ SCALING_MODE_SCALE_CROP, SCALING_MODE_NO_SCALE_CROP})
+ public @interface ScalingMode {}
+ // From system/window.h
+ /** @hide */
+ static final int SCALING_MODE_FREEZE = 0;
+ /** @hide */
+ static final int SCALING_MODE_SCALE_TO_WINDOW = 1;
+ /** @hide */
+ static final int SCALING_MODE_SCALE_CROP = 2;
+ /** @hide */
+ static final int SCALING_MODE_NO_SCALE_CROP = 3;
+
+ /** @hide */
@IntDef({ROTATION_0, ROTATION_90, ROTATION_180, ROTATION_270})
@Retention(RetentionPolicy.SOURCE)
public @interface Rotation {}
@@ -500,6 +516,20 @@
}
/**
+ * Set the scaling mode to be used for this surfaces buffers
+ * @hide
+ */
+ void setScalingMode(@ScalingMode int scalingMode) {
+ synchronized (mLock) {
+ checkNotReleasedLocked();
+ int err = nativeSetScalingMode(mNativeObject, scalingMode);
+ if (err != 0) {
+ throw new IllegalArgumentException("Invalid scaling mode: " + scalingMode);
+ }
+ }
+ }
+
+ /**
* Exception thrown when a Canvas couldn't be locked with {@link Surface#lockCanvas}, or
* when a SurfaceTexture could not successfully be allocated.
*/
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 589c0dc..f4fa980 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -578,8 +578,19 @@
}
mSurface.transferFrom(mNewSurface);
-
if (visible && mSurface.isValid()) {
+ // We set SCALING_MODE_NO_SCALE_CROP to allow the WindowManager
+ // to update our Surface crop without requiring a new buffer from
+ // us. In the default mode of SCALING_MODE_FREEZE, surface geometry
+ // state (which includes crop) is only applied when a buffer
+ // with appropriate geometry is available. During drag resize
+ // it is quite frequent that a matching buffer will not be available
+ // (because we are constantly being resized and have fallen behind).
+ // However in such situations the WindowManager still needs to be able
+ // to update our crop to ensure we stay within the bounds of the containing
+ // window.
+ mSurface.setScalingMode(Surface.SCALING_MODE_NO_SCALE_CROP);
+
if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
mSurfaceCreated = true;
mIsCreating = true;
diff --git a/core/java/com/android/internal/view/IDropPermissions.aidl b/core/java/com/android/internal/view/IDropPermissions.aidl
index 86d27e7..2438bda 100644
--- a/core/java/com/android/internal/view/IDropPermissions.aidl
+++ b/core/java/com/android/internal/view/IDropPermissions.aidl
@@ -16,11 +16,13 @@
package com.android.internal.view;
+import android.os.IBinder;
+
/**
* Interface to allow a drop receiver to request permissions for URIs passed along with ClipData
* contained in DragEvent.
*/
interface IDropPermissions {
- void take();
+ void take(IBinder activityToken);
void release();
}
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index f6e68c4..cf68449 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -465,11 +465,17 @@
anw->query(anw, NATIVE_WINDOW_HEIGHT, &value);
return value;
}
+
static jlong nativeGetNextFrameNumber(JNIEnv *env, jclass clazz, jlong nativeObject) {
Surface* surface = reinterpret_cast<Surface*>(nativeObject);
return surface->getNextFrameNumber();
}
+static jint nativeSetScalingMode(JNIEnv *env, jclass clazz, jlong nativeObject, jint scalingMode) {
+ Surface* surface = reinterpret_cast<Surface*>(nativeObject);
+ return surface->setScalingMode(scalingMode);
+}
+
namespace uirenderer {
using namespace android::uirenderer::renderthread;
@@ -546,6 +552,7 @@
{"nativeGetWidth", "(J)I", (void*)nativeGetWidth },
{"nativeGetHeight", "(J)I", (void*)nativeGetHeight },
{"nativeGetNextFrameNumber", "(J)J", (void*)nativeGetNextFrameNumber },
+ {"nativeSetScalingMode", "(JI)I", (void*)nativeSetScalingMode },
// HWUI context
{"nHwuiCreate", "(JJ)J", (void*) hwui::create },
diff --git a/graphics/java/android/graphics/drawable/Icon.aidl b/graphics/java/android/graphics/drawable/Icon.aidl
new file mode 100644
index 0000000..b82cfc4
--- /dev/null
+++ b/graphics/java/android/graphics/drawable/Icon.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2015, 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.graphics.drawable;
+
+parcelable Icon;
diff --git a/packages/DocumentsUI/res/layout/item_dir_grid.xml b/packages/DocumentsUI/res/layout/item_dir_grid.xml
new file mode 100644
index 0000000..c17b4c8
--- /dev/null
+++ b/packages/DocumentsUI/res/layout/item_dir_grid.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_margin="@dimen/grid_item_margin"
+ android:background="@color/item_doc_background"
+ android:elevation="5dp"
+ android:focusable="true">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:paddingTop="16dp"
+ android:paddingBottom="16dp"
+ android:paddingLeft="12dp"
+ android:paddingRight="12dp">
+
+ <ImageView
+ android:src="@drawable/ic_doc_folder"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_marginEnd="8dp"
+ android:scaleType="centerInside"
+ android:contentDescription="@null"/>
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:ellipsize="middle"
+ android:textAlignment="viewStart"
+ android:textAppearance="@android:style/TextAppearance.Material.Subhead"
+ android:textColor="@*android:color/primary_text_default_material_light" />
+
+ </LinearLayout>
+
+ <!-- An overlay that draws the item border when it is focused. -->
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:contentDescription="@null"
+ android:background="@drawable/item_doc_grid_border"
+ android:duplicateParentState="true" />
+
+</FrameLayout>
diff --git a/packages/DocumentsUI/res/layout/item_doc_grid.xml b/packages/DocumentsUI/res/layout/item_doc_grid.xml
index dcd5cfd..c0fc2c3 100644
--- a/packages/DocumentsUI/res/layout/item_doc_grid.xml
+++ b/packages/DocumentsUI/res/layout/item_doc_grid.xml
@@ -19,6 +19,7 @@
android:layout_height="wrap_content"
android:layout_margin="@dimen/grid_item_margin"
android:background="@color/item_doc_background"
+ android:elevation="5dp"
android:focusable="true">
<!-- Main item thumbnail. Comprised of two overlapping images, the
diff --git a/packages/DocumentsUI/res/values-sw720dp/dimens.xml b/packages/DocumentsUI/res/values-sw720dp/dimens.xml
index 83ceb55..2488fa2 100644
--- a/packages/DocumentsUI/res/values-sw720dp/dimens.xml
+++ b/packages/DocumentsUI/res/values-sw720dp/dimens.xml
@@ -18,5 +18,4 @@
<dimen name="grid_padding_horiz">16dp</dimen>
<dimen name="grid_padding_vert">16dp</dimen>
- <dimen name="grid_item_margin">8dp</dimen>
</resources>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
index 5cb3b0d..9c0a04c 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
@@ -755,11 +755,19 @@
* search currently.
*/
boolean cancelSearch() {
+ boolean collapsed = false;
+ boolean closed = false;
+
if (mActionBar.hasExpandedActionView()) {
mActionBar.collapseActionView();
- return true;
+ collapsed = true;
}
- return false;
+
+ if (isExpanded() || isSearching()) {
+ onClose();
+ closed = true;
+ }
+ return collapsed || closed;
}
boolean isSearching() {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 035ae77..0d5e34e 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -964,7 +964,16 @@
final State state = getDisplayState();
switch (state.derivedMode) {
case MODE_GRID:
- holder = new GridDocumentHolder(getContext(), parent, mIconHelper, viewType);
+ switch (viewType) {
+ case ITEM_TYPE_DIRECTORY:
+ holder = new GridDirectoryHolder(getContext(), parent);
+ break;
+ case ITEM_TYPE_DOCUMENT:
+ holder = new GridDocumentHolder(getContext(), parent, mIconHelper);
+ break;
+ default:
+ throw new IllegalStateException("Unsupported layout type.");
+ }
break;
case MODE_LIST:
holder = new ListDocumentHolder(getContext(), parent, mIconHelper);
@@ -1031,7 +1040,10 @@
@Override
public void onModelUpdate(Model model) {
mModelIds = Lists.newArrayList(model.getModelIds());
- mDividerPosition = 0;
+ // Start the divider at the end. That way if the code below encounters no documents
+ // (i.e. in a directory containing only directories), the divider is placed at the end
+ // of the list, as expected.
+ mDividerPosition = mModelIds.size();
// Walk down the list of IDs till we encounter something that's not a directory, and
// insert a whitespace element - this introduces a visual break in the grid between
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentHolder.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentHolder.java
index a01021f..9ac9057 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentHolder.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentHolder.java
@@ -40,16 +40,15 @@
final int mDefaultItemColor;
final boolean mAlwaysShowSummary;
final Context mContext;
- final IconHelper mIconHelper;
private ListDocumentHolder.ClickListener mClickListener;
private View.OnKeyListener mKeyListener;
- public DocumentHolder(Context context, ViewGroup parent, int layout, IconHelper iconHelper) {
- this(context, inflateLayout(context, parent, layout), iconHelper);
+ public DocumentHolder(Context context, ViewGroup parent, int layout) {
+ this(context, inflateLayout(context, parent, layout));
}
- public DocumentHolder(Context context, View item, IconHelper iconHelper) {
+ public DocumentHolder(Context context, View item) {
super(item);
itemView.setOnKeyListener(this);
@@ -59,8 +58,6 @@
mDefaultItemColor = context.getColor(R.color.item_doc_background);
mSelectedItemColor = context.getColor(R.color.item_doc_background_selected);
mAlwaysShowSummary = context.getResources().getBoolean(R.bool.always_show_summary);
-
- mIconHelper = iconHelper;
}
/**
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/EmptyDocumentHolder.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/EmptyDocumentHolder.java
index 0bdf530..ab67a5b 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/EmptyDocumentHolder.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/EmptyDocumentHolder.java
@@ -18,14 +18,18 @@
import android.content.Context;
import android.database.Cursor;
-import android.view.View;
+import android.widget.Space;
+import com.android.documentsui.R;
import com.android.documentsui.State;
final class EmptyDocumentHolder extends DocumentHolder {
public EmptyDocumentHolder(Context context) {
- super(context, new View(context), null);
- itemView.setVisibility(View.GONE);
+ super(context, new Space(context));
+
+ // Per UX spec, this puts a bigger gap between the folders and documents in the grid.
+ final int gridMargin = context.getResources().getDimensionPixelSize(R.dimen.grid_item_margin);
+ itemView.setMinimumHeight(gridMargin * 2);
}
public void bind(Cursor cursor, String modelId, State state) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/GridDirectoryHolder.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/GridDirectoryHolder.java
new file mode 100644
index 0000000..11ff263
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/GridDirectoryHolder.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 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.documentsui.dirlist;
+
+import static com.android.documentsui.model.DocumentInfo.getCursorString;
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.provider.DocumentsContract.Document;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.android.documentsui.R;
+import com.android.documentsui.State;
+
+final class GridDirectoryHolder extends DocumentHolder {
+ final TextView mTitle;
+ public GridDirectoryHolder(Context context, ViewGroup parent) {
+ super(context, parent, R.layout.item_dir_grid);
+
+ mTitle = (TextView) itemView.findViewById(android.R.id.title);
+ }
+
+ /**
+ * Bind this view to the given document for display.
+ * @param cursor Pointing to the item to be bound.
+ * @param modelId The model ID of the item.
+ * @param state Current display state.
+ */
+ public void bind(Cursor cursor, String modelId, State state) {
+ checkNotNull(cursor, "Cursor cannot be null.");
+
+ this.modelId = modelId;
+
+ final String docDisplayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
+ mTitle.setText(docDisplayName);
+ }
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/GridDocumentHolder.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/GridDocumentHolder.java
index 43256c3..63c667b 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/GridDocumentHolder.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/GridDocumentHolder.java
@@ -40,9 +40,22 @@
final class GridDocumentHolder extends DocumentHolder {
private static boolean mHideTitles;
- public GridDocumentHolder(
- Context context, ViewGroup parent, IconHelper thumbnailLoader, int viewType) {
- super(context, parent, R.layout.item_doc_grid, thumbnailLoader);
+ final TextView mTitle;
+ final TextView mDate;
+ final TextView mSize;
+ final ImageView mIconMime;
+ final ImageView mIconThumb;
+ final IconHelper mIconHelper;
+
+ public GridDocumentHolder(Context context, ViewGroup parent, IconHelper iconHelper) {
+ super(context, parent, R.layout.item_doc_grid);
+
+ mTitle = (TextView) itemView.findViewById(android.R.id.title);
+ mDate = (TextView) itemView.findViewById(R.id.date);
+ mSize = (TextView) itemView.findViewById(R.id.size);
+ mIconMime = (ImageView) itemView.findViewById(R.id.icon_mime);
+ mIconThumb = (ImageView) itemView.findViewById(R.id.icon_thumb);
+ mIconHelper = iconHelper;
}
/**
@@ -65,40 +78,34 @@
final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
final long docSize = getCursorLong(cursor, Document.COLUMN_SIZE);
- final TextView title = (TextView) itemView.findViewById(android.R.id.title);
- final TextView date = (TextView) itemView.findViewById(R.id.date);
- final TextView size = (TextView) itemView.findViewById(R.id.size);
- final ImageView iconMime = (ImageView) itemView.findViewById(R.id.icon_mime);
- final ImageView iconThumb = (ImageView) itemView.findViewById(R.id.icon_thumb);
+ mIconHelper.stopLoading(mIconThumb);
- mIconHelper.stopLoading(iconThumb);
-
- iconMime.animate().cancel();
- iconMime.setAlpha(1f);
- iconThumb.animate().cancel();
- iconThumb.setAlpha(0f);
+ mIconMime.animate().cancel();
+ mIconMime.setAlpha(1f);
+ mIconThumb.animate().cancel();
+ mIconThumb.setAlpha(0f);
final Uri uri = DocumentsContract.buildDocumentUri(docAuthority, docId);
- mIconHelper.loadThumbnail(uri, docMimeType, docFlags, docIcon, iconThumb, iconMime);
+ mIconHelper.loadThumbnail(uri, docMimeType, docFlags, docIcon, mIconThumb, mIconMime);
if (mHideTitles) {
- title.setVisibility(View.GONE);
+ mTitle.setVisibility(View.GONE);
} else {
- title.setText(docDisplayName);
- title.setVisibility(View.VISIBLE);
+ mTitle.setText(docDisplayName);
+ mTitle.setVisibility(View.VISIBLE);
}
if (docLastModified == -1) {
- date.setText(null);
+ mDate.setText(null);
} else {
- date.setText(Shared.formatTime(mContext, docLastModified));
+ mDate.setText(Shared.formatTime(mContext, docLastModified));
}
if (!state.showSize || Document.MIME_TYPE_DIR.equals(docMimeType) || docSize == -1) {
- size.setVisibility(View.GONE);
+ mSize.setVisibility(View.GONE);
} else {
- size.setVisibility(View.VISIBLE);
- size.setText(Formatter.formatFileSize(mContext, docSize));
+ mSize.setVisibility(View.VISIBLE);
+ mSize.setText(Formatter.formatFileSize(mContext, docSize));
}
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/ListDocumentHolder.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/ListDocumentHolder.java
index b46a0e5a..c22e91d 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/ListDocumentHolder.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/ListDocumentHolder.java
@@ -38,16 +38,27 @@
import com.android.documentsui.State;
final class ListDocumentHolder extends DocumentHolder {
+ final TextView mTitle;
+ final TextView mSummary;
+ final TextView mDate;
+ final TextView mSize;
final ImageView mIconMime;
final ImageView mIconThumb;
final ImageView mIcon1;
+ final IconHelper mIconHelper;
- public ListDocumentHolder(Context context, ViewGroup parent, IconHelper thumbnailLoader) {
- super(context, parent, R.layout.item_doc_list, thumbnailLoader);
+ public ListDocumentHolder(Context context, ViewGroup parent, IconHelper iconHelper) {
+ super(context, parent, R.layout.item_doc_list);
+ mTitle = (TextView) itemView.findViewById(android.R.id.title);
+ mSummary = (TextView) itemView.findViewById(android.R.id.summary);
+ mDate = (TextView) itemView.findViewById(R.id.date);
+ mSize = (TextView) itemView.findViewById(R.id.size);
mIconMime = (ImageView) itemView.findViewById(R.id.icon_mime);
mIconThumb = (ImageView) itemView.findViewById(R.id.icon_thumb);
mIcon1 = (ImageView) itemView.findViewById(android.R.id.icon1);
+
+ mIconHelper = iconHelper;
}
/**
@@ -72,11 +83,6 @@
final String docSummary = getCursorString(cursor, Document.COLUMN_SUMMARY);
final long docSize = getCursorLong(cursor, Document.COLUMN_SIZE);
- final TextView title = (TextView) itemView.findViewById(android.R.id.title);
- final TextView summary = (TextView) itemView.findViewById(android.R.id.summary);
- final TextView date = (TextView) itemView.findViewById(R.id.date);
- final TextView size = (TextView) itemView.findViewById(R.id.size);
-
mIconHelper.stopLoading(mIconThumb);
mIconMime.animate().cancel();
@@ -85,27 +91,27 @@
final Uri uri = DocumentsContract.buildDocumentUri(docAuthority, docId);
mIconHelper.loadThumbnail(uri, docMimeType, docFlags, docIcon, mIconThumb, mIconMime);
- title.setText(docDisplayName);
- title.setVisibility(View.VISIBLE);
+ mTitle.setText(docDisplayName);
+ mTitle.setVisibility(View.VISIBLE);
if (docSummary != null) {
- summary.setText(docSummary);
- summary.setVisibility(View.VISIBLE);
+ mSummary.setText(docSummary);
+ mSummary.setVisibility(View.VISIBLE);
} else {
- summary.setVisibility(View.INVISIBLE);
+ mSummary.setVisibility(View.INVISIBLE);
}
if (docLastModified == -1) {
- date.setText(null);
+ mDate.setText(null);
} else {
- date.setText(Shared.formatTime(mContext, docLastModified));
+ mDate.setText(Shared.formatTime(mContext, docLastModified));
}
if (!state.showSize || Document.MIME_TYPE_DIR.equals(docMimeType) || docSize == -1) {
- size.setVisibility(View.GONE);
+ mSize.setVisibility(View.GONE);
} else {
- size.setVisibility(View.VISIBLE);
- size.setText(Formatter.formatFileSize(mContext, docSize));
+ mSize.setVisibility(View.VISIBLE);
+ mSize.setText(Formatter.formatFileSize(mContext, docSize));
}
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
index 26eac26..e47af67 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
@@ -383,6 +383,10 @@
* @param position
*/
void setSelectionRangeBegin(int position) {
+ if (position == RecyclerView.NO_POSITION) {
+ return;
+ }
+
if (mSelection.contains(mEnvironment.getModelIdFromAdapterPosition(position))) {
mRanger = new Range(position);
}
@@ -1160,13 +1164,15 @@
mSelection.applyProvisionalSelection();
mModel.endSelection();
int firstSelected = mModel.getPositionNearestOrigin();
- if (!mSelection.contains(mEnvironment.getModelIdFromAdapterPosition(firstSelected))) {
- Log.w(TAG, "First selected by band is NOT in selection!");
- // Sadly this is really happening. Need to figure out what's going on.
- } else if (firstSelected != NOT_SET) {
- // TODO: firstSelected should really be lastSelected, we want to anchor the item
- // where the mouse-up occurred.
- setSelectionRangeBegin(firstSelected);
+ if (firstSelected != NOT_SET) {
+ if (mSelection.contains(mEnvironment.getModelIdFromAdapterPosition(firstSelected))) {
+ // TODO: firstSelected should really be lastSelected, we want to anchor the item
+ // where the mouse-up occurred.
+ setSelectionRangeBegin(firstSelected);
+ } else {
+ // TODO: Check if this is really happening.
+ Log.w(TAG, "First selected by band is NOT in selection!");
+ }
}
mModel = null;
@@ -1558,18 +1564,22 @@
for (int column = columnStartIndex; column <= columnEndIndex; column++) {
SparseIntArray items = mColumns.get(mColumnBounds.get(column).lowerLimit);
for (int row = rowStartIndex; row <= rowEndIndex; row++) {
- int position = items.get(items.keyAt(row));
- String id = mHelper.getModelIdFromAdapterPosition(position);
- if (id != null) {
- // The adapter inserts items for UI layout purposes that aren't associated
- // with files. Those will have a null model ID. Don't select them.
- mSelection.add(id);
- }
- if (isPossiblePositionNearestOrigin(column, columnStartIndex, columnEndIndex,
- row, rowStartIndex, rowEndIndex)) {
- // If this is the position nearest the origin, record it now so that it
- // can be returned by endSelection() later.
- mPositionNearestOrigin = position;
+ // The default return value for SparseIntArray.get is 0, which is a valid
+ // position. Use a sentry value to prevent erroneously selecting item 0.
+ int position = items.get(items.keyAt(row), NOT_SET);
+ if (position != NOT_SET) {
+ String id = mHelper.getModelIdFromAdapterPosition(position);
+ if (id != null) {
+ // The adapter inserts items for UI layout purposes that aren't associated
+ // with files. Those will have a null model ID. Don't select them.
+ mSelection.add(id);
+ }
+ if (isPossiblePositionNearestOrigin(column, columnStartIndex, columnEndIndex,
+ row, rowStartIndex, rowEndIndex)) {
+ // If this is the position nearest the origin, record it now so that it
+ // can be returned by endSelection() later.
+ mPositionNearestOrigin = position;
+ }
}
}
}
diff --git a/packages/PrintSpooler/AndroidManifest.xml b/packages/PrintSpooler/AndroidManifest.xml
index 5a6f1d1..937eb15 100644
--- a/packages/PrintSpooler/AndroidManifest.xml
+++ b/packages/PrintSpooler/AndroidManifest.xml
@@ -17,9 +17,7 @@
*/
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.printspooler"
- android:versionName="1"
- android:versionCode="1">
+ package="com.android.printspooler">
<!-- Allows an application to call APIs that give it access to all print jobs
on the device. Usually an app can access only the print jobs it created. -->
diff --git a/packages/PrintSpooler/res/drawable/ic_info.xml b/packages/PrintSpooler/res/drawable/ic_info.xml
new file mode 100644
index 0000000..2ecd1c7
--- /dev/null
+++ b/packages/PrintSpooler/res/drawable/ic_info.xml
@@ -0,0 +1,24 @@
+<!--
+ Copyright (C) 2015 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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-6h2v6zM13,9h-2L11,7h2v2z"
+ android:fillColor="#757575"/>
+</vector>
\ No newline at end of file
diff --git a/packages/PrintSpooler/res/layout/printer_dropdown_item.xml b/packages/PrintSpooler/res/layout/printer_dropdown_item.xml
index 4381a7a..e0efbc4 100644
--- a/packages/PrintSpooler/res/layout/printer_dropdown_item.xml
+++ b/packages/PrintSpooler/res/layout/printer_dropdown_item.xml
@@ -62,7 +62,7 @@
android:ellipsize="end"
android:textIsSelectable="false"
android:visibility="gone"
- android:textColor="?android:attr/textColorPrimary"
+ android:textColor="?android:attr/textColorSecondary"
android:duplicateParentState="true">
</TextView>
diff --git a/packages/PrintSpooler/res/layout/printer_list_item.xml b/packages/PrintSpooler/res/layout/printer_list_item.xml
index 7bc144a..50f44c2 100644
--- a/packages/PrintSpooler/res/layout/printer_list_item.xml
+++ b/packages/PrintSpooler/res/layout/printer_list_item.xml
@@ -38,6 +38,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
+ android:layout_weight="1"
android:duplicateParentState="true">
<TextView
@@ -62,10 +63,20 @@
android:ellipsize="end"
android:textIsSelectable="false"
android:visibility="gone"
- android:textColor="?android:attr/textColorPrimary"
+ android:textColor="?android:attr/textColorSecondary"
android:duplicateParentState="true">
</TextView>
</LinearLayout>
+ <ImageView
+ android:id="@+id/more_info"
+ android:layout_width="24dip"
+ android:layout_height="24dip"
+ android:layout_gravity="center_vertical"
+ android:contentDescription="@string/printer_info_desc"
+ android:src="@drawable/ic_info"
+ android:visibility="gone">
+ </ImageView>
+
</LinearLayout>
diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml
index 6d81788..97a7bff 100644
--- a/packages/PrintSpooler/res/values/strings.xml
+++ b/packages/PrintSpooler/res/values/strings.xml
@@ -144,6 +144,12 @@
<item quantity="other"><xliff:g id="count" example="2">%1$s</xliff:g> printers found</item>
</plurals>
+ <!-- Template for an extended description of a printer. [CHAR LIMIT=50] -->
+ <string name="printer_extended_description_template"><xliff:g id="print_service_label" example="Canon Print Service">%1$s</xliff:g> - <xliff:g id="printer_description" example="Printer under the stairs">%2$s</xliff:g></string>
+
+ <!-- Description of printer info icon. [CHAR LIMIT=50] -->
+ <string name="printer_info_desc">More information about this printer</string>
+
<!-- Add printer dialog -->
<!-- Title for the alert dialog for selecting a print service. [CHAR LIMIT=50] -->
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/CustomPrinterIconCache.java b/packages/PrintSpooler/src/com/android/printspooler/model/CustomPrinterIconCache.java
new file mode 100644
index 0000000..7274268
--- /dev/null
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/CustomPrinterIconCache.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2015 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.model;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.drawable.Icon;
+import android.print.PrinterId;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+/**
+ * A fixed size cache for custom printer icons. Old icons get removed with a last recently used
+ * policy.
+ */
+public class CustomPrinterIconCache {
+
+ private final static String LOG_TAG = "CustomPrinterIconCache";
+
+ /** Maximum number of icons in the cache */
+ private final static int MAX_SIZE = 1024;
+
+ /** Directory used to persist state and icons */
+ private final File mCacheDirectory;
+
+ /**
+ * Create a new icon cache.
+ */
+ public CustomPrinterIconCache(@NonNull File cacheDirectory) {
+ mCacheDirectory = new File(cacheDirectory, "icons");
+ if (!mCacheDirectory.exists()) {
+ mCacheDirectory.mkdir();
+ }
+ }
+
+ /**
+ * Return the file name to be used for the icon of a printer
+ *
+ * @param printerId the id of the printer
+ *
+ * @return The file to be used for the icon of the printer
+ */
+ private @Nullable File getIconFileName(@NonNull PrinterId printerId) {
+ StringBuffer sb = new StringBuffer(printerId.getServiceName().getPackageName());
+ sb.append("-");
+
+ try {
+ MessageDigest md = MessageDigest.getInstance("SHA-1");
+ md.update(
+ (printerId.getServiceName().getClassName() + ":" + printerId.getLocalId())
+ .getBytes("UTF-16"));
+ sb.append(String.format("%#040x", new java.math.BigInteger(1, md.digest())));
+ } catch (UnsupportedEncodingException|NoSuchAlgorithmException e) {
+ Log.e(LOG_TAG, "Could not compute custom printer icon file name", e);
+ return null;
+ }
+
+ return new File(mCacheDirectory, sb.toString());
+ }
+
+ /**
+ * Get the {@link Icon} to be used as a custom icon for the printer. If not available request
+ * the icon to be loaded.
+ *
+ * @param printerId the printer the icon belongs to
+ * @return the {@link Icon} if already available or null if icon is not loaded yet
+ */
+ public synchronized @Nullable Icon getIcon(@NonNull PrinterId printerId) {
+ Icon icon;
+
+ File iconFile = getIconFileName(printerId);
+ if (iconFile != null && iconFile.exists()) {
+ try (FileInputStream is = new FileInputStream(iconFile)) {
+ icon = Icon.createFromStream(is);
+ } catch (IOException e) {
+ icon = null;
+ Log.e(LOG_TAG, "Could not read icon from " + iconFile, e);
+ }
+
+ // Touch file so that it is the not likely to be removed
+ iconFile.setLastModified(System.currentTimeMillis());
+ } else {
+ icon = null;
+ }
+
+ return icon;
+ }
+
+ /**
+ * Remove old icons so that only between numFilesToKeep and twice as many icons are left.
+ *
+ * @param numFilesToKeep the number of icons to keep
+ */
+ public void removeOldFiles(int numFilesToKeep) {
+ File files[] = mCacheDirectory.listFiles();
+
+ // To reduce the number of shrink operations, let the cache grow to twice the max size
+ if (files.length > numFilesToKeep * 2) {
+ SortedMap<Long, File> sortedFiles = new TreeMap<>();
+
+ for (File f : files) {
+ sortedFiles.put(f.lastModified(), f);
+ }
+
+ while (sortedFiles.size() > numFilesToKeep) {
+ sortedFiles.remove(sortedFiles.firstKey());
+ }
+ }
+ }
+
+ /**
+ * Handle that a custom icon for a printer was loaded
+ *
+ * @param printerId the id of the printer the icon belongs to
+ * @param icon the icon that was loaded
+ */
+ public synchronized void onCustomPrinterIconLoaded(@NonNull PrinterId printerId,
+ @Nullable Icon icon) {
+ File iconFile = getIconFileName(printerId);
+
+ if (iconFile == null) {
+ return;
+ }
+
+ try (FileOutputStream os = new FileOutputStream(iconFile)) {
+ icon.writeToStream(os);
+ } catch (IOException e) {
+ Log.e(LOG_TAG, "Could not write icon for " + printerId + " to storage", e);
+ }
+
+ removeOldFiles(MAX_SIZE);
+ }
+
+ /**
+ * Clear all persisted and non-persisted state from this cache.
+ */
+ public synchronized void clear() {
+ for (File f : mCacheDirectory.listFiles()) {
+ f.delete();
+ }
+ }
+}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java b/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java
index ea6281d..ac97ad0 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/PageContentRepository.java
@@ -116,6 +116,7 @@
});
return;
}
+ mCloseGuard.close();
mState = STATE_DESTROYED;
if (DEBUG) {
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java b/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java
index 90eef83..496a0b0 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/PrintSpoolerService.java
@@ -23,6 +23,7 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.graphics.drawable.Icon;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.IBinder;
@@ -109,6 +110,9 @@
private NotificationController mNotificationController;
+ /** Cache for custom printer icons loaded from the print service */
+ private CustomPrinterIconCache mCustomIconCache;
+
public static PrintSpoolerService peekInstance() {
synchronized (sLock) {
return sInstance;
@@ -123,6 +127,7 @@
mPersistanceManager = new PersistenceManager();
mNotificationController = new NotificationController(PrintSpoolerService.this);
+ mCustomIconCache = new CustomPrinterIconCache(getCacheDir());
synchronized (mLock) {
mPersistanceManager.readStateLocked();
@@ -135,6 +140,11 @@
}
@Override
+ public void onDestroy() {
+ super.onDestroy();
+ }
+
+ @Override
public IBinder onBind(Intent intent) {
return new PrintSpooler();
}
@@ -703,6 +713,37 @@
}.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
}
+ /**
+ * Handle that a custom icon for a printer was loaded.
+ *
+ * @param printerId the id of the printer the icon belongs to
+ * @param icon the icon that was loaded
+ * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon()
+ */
+ public void onCustomPrinterIconLoaded(PrinterId printerId, Icon icon) {
+ mCustomIconCache.onCustomPrinterIconLoaded(printerId, icon);
+ }
+
+ /**
+ * Get the custom icon for a printer. If the icon is not cached, the icon is
+ * requested asynchronously. Once it is available the printer is updated.
+ *
+ * @param printerId the id of the printer the icon should be loaded for
+ * @return the custom icon to be used for the printer or null if the icon is
+ * not yet available
+ * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon()
+ */
+ public Icon getCustomPrinterIcon(PrinterId printerId) {
+ return mCustomIconCache.getIcon(printerId);
+ }
+
+ /**
+ * Clear the custom printer icon cache.
+ */
+ public void clearCustomPrinterIconCache() {
+ mCustomIconCache.clear();
+ }
+
private final class PersistenceManager {
private static final String PERSIST_FILE_NAME = "print_spooler_state.xml";
@@ -1262,7 +1303,7 @@
}
private void expect(XmlPullParser parser, int type, String tag)
- throws IOException, XmlPullParserException {
+ throws XmlPullParserException {
if (!accept(parser, type, tag)) {
throw new XmlPullParserException("Exepected event: " + type
+ " and tag: " + tag + " but got event: " + parser.getEventType()
@@ -1279,7 +1320,7 @@
}
private boolean accept(XmlPullParser parser, int type, String tag)
- throws IOException, XmlPullParserException {
+ throws XmlPullParserException {
if (parser.getEventType() != type) {
return false;
}
@@ -1395,5 +1436,38 @@
public PrintSpoolerService getService() {
return PrintSpoolerService.this;
}
+
+ @Override
+ public void onCustomPrinterIconLoaded(PrinterId printerId, Icon icon,
+ IPrintSpoolerCallbacks callbacks, int sequence)
+ throws RemoteException {
+ try {
+ PrintSpoolerService.this.onCustomPrinterIconLoaded(printerId, icon);
+ } finally {
+ callbacks.onCustomPrinterIconCached(sequence);
+ }
+ }
+
+ @Override
+ public void getCustomPrinterIcon(PrinterId printerId, IPrintSpoolerCallbacks callbacks,
+ int sequence) throws RemoteException {
+ Icon icon = null;
+ try {
+ icon = PrintSpoolerService.this.getCustomPrinterIcon(printerId);
+ } finally {
+ callbacks.onGetCustomPrinterIconResult(icon, sequence);
+ }
+ }
+
+ @Override
+ public void clearCustomPrinterIconCache(IPrintSpoolerCallbacks callbacks,
+ int sequence) throws RemoteException {
+ try {
+ PrintSpoolerService.this.clearCustomPrinterIconCache();
+ } finally {
+ callbacks.customPrinterIconCacheCleared(sequence);
+ }
+ }
+
}
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java b/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java
index 1b6e9ce..ea11ae4 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/model/RemotePrintDocument.java
@@ -870,7 +870,7 @@
private final MutexFileProvider mFileProvider;
private final IWriteResultCallback mRemoteResultCallback;
- private final CommandDoneCallback mDoneCallback;
+ private final CommandDoneCallback mWriteDoneCallback;
private final Context mContext;
private final Handler mHandler;
@@ -885,7 +885,7 @@
mPageCount = pageCount;
mPages = Arrays.copyOf(pages, pages.length);
mFileProvider = fileProvider;
- mDoneCallback = callback;
+ mWriteDoneCallback = callback;
}
@Override
@@ -997,7 +997,7 @@
mCancellation = null;
// Done.
- mDoneCallback.onDone();
+ mWriteDoneCallback.onDone();
}
private void handleOnWriteFailed(CharSequence error, int sequence) {
@@ -1015,7 +1015,7 @@
mCancellation = null;
// Done.
- mDoneCallback.onDone();
+ mWriteDoneCallback.onDone();
}
private void handleOnWriteCanceled(int sequence) {
@@ -1033,7 +1033,7 @@
mCancellation = null;
// Done.
- mDoneCallback.onDone();
+ mWriteDoneCallback.onDone();
}
private final class WriteHandler extends Handler {
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java b/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java
index 80c28e0..c3c118a 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/FusedPrintersProvider.java
@@ -27,7 +27,6 @@
import android.print.PrinterId;
import android.print.PrinterInfo;
import android.printservice.PrintServiceInfo;
-import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
@@ -52,6 +51,7 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import libcore.io.IoUtils;
@@ -213,9 +213,9 @@
mPrintersUpdatedBefore = true;
// Some of the found printers may have be a printer that is in the
- // history but with its name changed. Hence, we try to update the
- // printer to use its current name instead of the historical one.
- mPersistenceManager.updatePrintersHistoricalNamesIfNeeded(printers);
+ // history but with its properties changed. Hence, we try to update the
+ // printer to use its current properties instead of the historical one.
+ mPersistenceManager.updateHistoricalPrintersIfNeeded(printers);
Map<PrinterId, PrinterInfo> printersMap = new LinkedHashMap<>();
final int printerCount = printers.size();
@@ -340,7 +340,6 @@
private static final String ATTR_NAME = "name";
private static final String ATTR_DESCRIPTION = "description";
- private static final String ATTR_STATUS = "status";
private final AtomicFile mStatePersistFile;
@@ -378,13 +377,13 @@
mReadTask.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, (Void[]) null);
}
- public void updatePrintersHistoricalNamesIfNeeded(List<PrinterInfo> printers) {
+ public void updateHistoricalPrintersIfNeeded(List<PrinterInfo> printers) {
boolean writeHistory = false;
final int printerCount = printers.size();
for (int i = 0; i < printerCount; i++) {
PrinterInfo printer = printers.get(i);
- writeHistory |= renamePrinterIfNeeded(printer);
+ writeHistory |= updateHistoricalPrinterIfNeeded(printer);
}
if (writeHistory) {
@@ -392,18 +391,42 @@
}
}
- public boolean renamePrinterIfNeeded(PrinterInfo printer) {
- boolean renamed = false;
+ /**
+ * Updates the historical printer state with the given printer.
+ *
+ * @param printer the printer to update
+ *
+ * @return true iff the historical printer list needs to be updated
+ */
+ public boolean updateHistoricalPrinterIfNeeded(PrinterInfo printer) {
+ boolean writeHistory = false;
final int printerCount = mHistoricalPrinters.size();
for (int i = 0; i < printerCount; i++) {
PrinterInfo historicalPrinter = mHistoricalPrinters.get(i);
- if (historicalPrinter.getId().equals(printer.getId())
- && !TextUtils.equals(historicalPrinter.getName(), printer.getName())) {
- mHistoricalPrinters.set(i, printer);
- renamed = true;
+
+ if (!historicalPrinter.getId().equals(printer.getId())) {
+ continue;
+ }
+
+ // Overwrite the historical printer with the updated printer as some properties
+ // changed. We ignore the status as this is a volatile state.
+ if (historicalPrinter.equalsIgnoringStatus(printer)) {
+ continue;
+ }
+
+ mHistoricalPrinters.set(i, printer);
+
+ // We only persist limited information in the printer history, hence check if
+ // we need to persist the update.
+ // @see PersistenceManager.WriteTask#doWritePrinterHistory
+ if (!historicalPrinter.getName().equals(printer.getName())) {
+ if (Objects.equals(historicalPrinter.getDescription(),
+ printer.getDescription())) {
+ writeHistory = true;
+ }
}
}
- return renamed;
+ return writeHistory;
}
public void addPrinterAndWritePrinterHistory(PrinterInfo printer) {
@@ -610,7 +633,6 @@
String name = parser.getAttributeValue(null, ATTR_NAME);
String description = parser.getAttributeValue(null, ATTR_DESCRIPTION);
- final int status = Integer.parseInt(parser.getAttributeValue(null, ATTR_STATUS));
parser.next();
@@ -625,7 +647,11 @@
expect(parser, XmlPullParser.END_TAG, TAG_PRINTER_ID);
parser.next();
- PrinterInfo.Builder builder = new PrinterInfo.Builder(printerId, name, status);
+ // If the printer is available the printer will be replaced by the one read from the
+ // discovery session, hence the only time when this object is used is when the
+ // printer is unavailable.
+ PrinterInfo.Builder builder = new PrinterInfo.Builder(printerId, name,
+ PrinterInfo.STATUS_UNAVAILABLE);
builder.setDescription(description);
PrinterInfo printer = builder.build();
@@ -642,7 +668,7 @@
}
private void expect(XmlPullParser parser, int type, String tag)
- throws IOException, XmlPullParserException {
+ throws XmlPullParserException {
if (!accept(parser, type, tag)) {
throw new XmlPullParserException("Exepected event: " + type
+ " and tag: " + tag + " but got event: " + parser.getEventType()
@@ -659,7 +685,7 @@
}
private boolean accept(XmlPullParser parser, int type, String tag)
- throws IOException, XmlPullParserException {
+ throws XmlPullParserException {
if (parser.getEventType() != type) {
return false;
}
@@ -676,7 +702,8 @@
private final class WriteTask extends AsyncTask<List<PrinterInfo>, Void, Void> {
@Override
- protected Void doInBackground(List<PrinterInfo>... printers) {
+ protected Void doInBackground(
+ @SuppressWarnings("unchecked") List<PrinterInfo>... printers) {
doWritePrinterHistory(printers[0]);
return null;
}
@@ -698,9 +725,6 @@
serializer.startTag(null, TAG_PRINTER);
serializer.attribute(null, ATTR_NAME, printer.getName());
- // Historical printers are always stored as unavailable.
- serializer.attribute(null, ATTR_STATUS, String.valueOf(
- PrinterInfo.STATUS_UNAVAILABLE));
String description = printer.getDescription();
if (description != null) {
serializer.attribute(null, ATTR_DESCRIPTION, description);
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
index 2757b81..606f4eb 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PageAdapter.java
@@ -52,7 +52,7 @@
/**
* This class represents the adapter for the pages in the print preview list.
*/
-public final class PageAdapter extends Adapter {
+public final class PageAdapter extends Adapter<ViewHolder> {
private static final String LOG_TAG = "PageAdapter";
private static final int MAX_PREVIEW_PAGES_BATCH = 50;
@@ -409,7 +409,7 @@
- horizontalPaddingAndMargins) / columnCount) + 0.5f);
// Compute max page height.
- final int pageContentDesiredHeight = (int) (((float) pageContentDesiredWidth
+ final int pageContentDesiredHeight = (int) ((pageContentDesiredWidth
/ pageAspectRatio) + 0.5f);
// If the page does not fit entirely in a vertical direction,
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
index 6521565..9c1cf64 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintActivity.java
@@ -30,7 +30,6 @@
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
-import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
@@ -462,6 +461,7 @@
}
}
+ @Override
public void onUpdateCanceled() {
if (DEBUG) {
Log.i(LOG_TAG, "onUpdateCanceled()");
@@ -1738,8 +1738,9 @@
}
private void updatePageRangeOptions(int pageCount) {
+ @SuppressWarnings("unchecked")
ArrayAdapter<SpinnerItem<Integer>> rangeOptionsSpinnerAdapter =
- (ArrayAdapter) mRangeOptionsSpinner.getAdapter();
+ (ArrayAdapter<SpinnerItem<Integer>>) mRangeOptionsSpinner.getAdapter();
rangeOptionsSpinnerAdapter.clear();
final int[] rangeOptionsValues = getResources().getIntArray(
@@ -1928,6 +1929,7 @@
this.label = label;
}
+ @Override
public String toString() {
return label.toString();
}
@@ -2187,7 +2189,7 @@
if (position == 0 && getPdfPrinter() != null) {
PrinterHolder printerHolder = (PrinterHolder) getItem(position);
title = printerHolder.printer.getName();
- icon = getResources().getDrawable(R.drawable.ic_menu_savetopdf);
+ icon = getResources().getDrawable(R.drawable.ic_menu_savetopdf, null);
} else if (position == 1) {
title = getString(R.string.all_printers);
}
@@ -2195,20 +2197,16 @@
if (position == 1 && getPdfPrinter() != null) {
PrinterHolder printerHolder = (PrinterHolder) getItem(position);
title = printerHolder.printer.getName();
- icon = getResources().getDrawable(R.drawable.ic_menu_savetopdf);
+ icon = getResources().getDrawable(R.drawable.ic_menu_savetopdf, null);
} else if (position == getCount() - 1) {
title = getString(R.string.all_printers);
} else {
PrinterHolder printerHolder = (PrinterHolder) getItem(position);
- title = printerHolder.printer.getName();
- try {
- PackageInfo packageInfo = getPackageManager().getPackageInfo(
- printerHolder.printer.getId().getServiceName().getPackageName(), 0);
- subtitle = packageInfo.applicationInfo.loadLabel(getPackageManager());
- icon = packageInfo.applicationInfo.loadIcon(getPackageManager());
- } catch (NameNotFoundException nnfe) {
- /* ignore */
- }
+ PrinterInfo printInfo = printerHolder.printer;
+
+ title = printInfo.getName();
+ icon = printInfo.loadIcon(PrintActivity.this);
+ subtitle = printInfo.getDescription();
}
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewController.java b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewController.java
index 8716fd2..ce54204 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewController.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/PrintPreviewController.java
@@ -78,8 +78,8 @@
mRecyclerView.setLayoutManager(mLayoutManger);
mRecyclerView.setAdapter(mPageAdapter);
mRecyclerView.setItemViewCacheSize(0);
- mPreloadController = new PreloadController(mRecyclerView);
- mRecyclerView.setOnScrollListener(mPreloadController);
+ mPreloadController = new PreloadController();
+ mRecyclerView.addOnScrollListener(mPreloadController);
mContentView = (PrintContentView) activity.findViewById(R.id.options_content);
mEmbeddedContentContainer = (EmbeddedContentContainer) activity.findViewById(
@@ -314,12 +314,9 @@
}
private final class PreloadController extends RecyclerView.OnScrollListener {
- private final RecyclerView mRecyclerView;
-
private int mOldScrollState;
- public PreloadController(RecyclerView recyclerView) {
- mRecyclerView = recyclerView;
+ public PreloadController() {
mOldScrollState = mRecyclerView.getScrollState();
}
@@ -371,7 +368,8 @@
View lastChild = layoutManager.getChildAt(layoutManager.getChildCount() - 1);
ViewHolder lastHolder = mRecyclerView.getChildViewHolder(lastChild);
- return new PageRange(firstHolder.getPosition(), lastHolder.getPosition());
+ return new PageRange(firstHolder.getLayoutPosition(),
+ lastHolder.getLayoutPosition());
}
return null;
}
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
index ab0b2f1..cdfc7ee 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
@@ -27,6 +27,7 @@
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.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -51,6 +52,7 @@
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
+import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityManager;
import android.widget.AdapterView;
@@ -587,20 +589,31 @@
convertView.setEnabled(isActionable(position));
- PrinterInfo printer = (PrinterInfo) getItem(position);
+ final PrinterInfo printer = (PrinterInfo) getItem(position);
CharSequence title = printer.getName();
- CharSequence subtitle = null;
- Drawable icon = null;
+ Drawable icon = printer.loadIcon(SelectPrinterActivity.this);
+ CharSequence printServiceLabel;
try {
- PackageManager pm = getPackageManager();
- PackageInfo packageInfo = pm.getPackageInfo(printer.getId()
- .getServiceName().getPackageName(), 0);
- subtitle = packageInfo.applicationInfo.loadLabel(pm);
- icon = packageInfo.applicationInfo.loadIcon(pm);
- } catch (NameNotFoundException nnfe) {
- /* ignore */
+ PackageInfo packageInfo = getPackageManager().getPackageInfo(
+ printer.getId().getServiceName().getPackageName(), 0);
+
+ printServiceLabel = packageInfo.applicationInfo.loadLabel(getPackageManager());
+ } catch (NameNotFoundException e) {
+ printServiceLabel = null;
+ }
+
+ CharSequence description = printer.getDescription();
+
+ CharSequence subtitle;
+ if (printServiceLabel == null) {
+ subtitle = description;
+ } else if (description == null) {
+ subtitle = printServiceLabel;
+ } else {
+ subtitle = getString(R.string.printer_extended_description_template,
+ printServiceLabel, description);
}
TextView titleView = (TextView) convertView.findViewById(R.id.title);
@@ -615,6 +628,20 @@
subtitleView.setVisibility(View.GONE);
}
+ ImageView moreInfoView = (ImageView) convertView.findViewById(R.id.more_info);
+ if (printer.getInfoIntent() != null) {
+ moreInfoView.setVisibility(View.VISIBLE);
+ moreInfoView.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ try {
+ startIntentSender(printer.getInfoIntent().getIntentSender(), null, 0, 0, 0);
+ } catch (SendIntentException e) {
+ Log.e(LOG_TAG, "Could not execute pending info intent: %s", e);
+ }
+ }
+ });
+ }
ImageView iconView = (ImageView) convertView.findViewById(R.id.icon);
if (icon != null) {
diff --git a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
index e6613fa..0bb4bfa 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/widget/PrintContentView.java
@@ -415,6 +415,7 @@
onDragProgress(progress);
}
+ @Override
public void onViewReleased(View child, float velocityX, float velocityY) {
final int childTop = child.getTop();
@@ -435,14 +436,17 @@
invalidate();
}
+ @Override
public int getOrderedChildIndex(int index) {
return getChildCount() - index - 1;
}
+ @Override
public int getViewVerticalDragRange(View child) {
return mDraggableContent.getHeight();
}
+ @Override
public int clampViewPositionVertical(View child, int top, int dy) {
final int staticOptionBottom = mStaticContent.getBottom();
return Math.max(Math.min(top, getOpenedOptionsY()), getClosedOptionsY());
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
index d931856..481b9180 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardUI.java
@@ -70,17 +70,19 @@
// time for us to receive the signal that it's starting.
private static final long BLUETOOTH_START_DELAY_MILLIS = 10 * 1000;
+ // We will be scanning up to 30 seconds, after which we'll stop.
+ private static final long BLUETOOTH_SCAN_TIMEOUT_MILLIS = 30 * 1000;
+
private static final int STATE_NOT_ENABLED = -1;
private static final int STATE_UNKNOWN = 0;
private static final int STATE_WAITING_FOR_BOOT_COMPLETED = 1;
private static final int STATE_WAITING_FOR_TABLET_MODE_EXIT = 2;
private static final int STATE_WAITING_FOR_DEVICE_DISCOVERY = 3;
private static final int STATE_WAITING_FOR_BLUETOOTH = 4;
- private static final int STATE_WAITING_FOR_STATE_PAIRED = 5;
- private static final int STATE_PAIRING = 6;
- private static final int STATE_PAIRED = 7;
- private static final int STATE_USER_CANCELLED = 8;
- private static final int STATE_DEVICE_NOT_FOUND = 9;
+ private static final int STATE_PAIRING = 5;
+ private static final int STATE_PAIRED = 6;
+ private static final int STATE_USER_CANCELLED = 7;
+ private static final int STATE_DEVICE_NOT_FOUND = 8;
private static final int MSG_INIT = 0;
private static final int MSG_ON_BOOT_COMPLETED = 1;
@@ -92,6 +94,7 @@
private static final int MSG_ON_BLE_SCAN_FAILED = 7;
private static final int MSG_SHOW_BLUETOOTH_DIALOG = 8;
private static final int MSG_DISMISS_BLUETOOTH_DIALOG = 9;
+ private static final int MSG_BLE_ABORT_SCAN = 10;
private volatile KeyboardHandler mHandler;
private volatile KeyboardUIHandler mUIHandler;
@@ -107,6 +110,7 @@
private long mBootCompletedTime;
private int mInTabletMode = InputManager.SWITCH_STATE_UNKNOWN;
+ private int mScanAttempt = 0;
private ScanCallback mScanCallback;
private BluetoothDialog mDialog;
@@ -328,6 +332,9 @@
.build();
mScanCallback = new KeyboardScanCallback();
scanner.startScan(Arrays.asList(filter), settings, mScanCallback);
+
+ Message abortMsg = mHandler.obtainMessage(MSG_BLE_ABORT_SCAN, ++mScanAttempt, 0);
+ mHandler.sendMessageDelayed(abortMsg, BLUETOOTH_SCAN_TIMEOUT_MILLIS);
}
private void stopScanning() {
@@ -338,6 +345,19 @@
}
// Should only be called on the handler thread
+ private void bleAbortScanInternal(int scanAttempt) {
+ if (mState == STATE_WAITING_FOR_DEVICE_DISCOVERY && scanAttempt == mScanAttempt) {
+ if (DEBUG) {
+ Slog.d(TAG, "Bluetooth scan timed out");
+ }
+ stopScanning();
+ // FIXME: should we also try shutting off bluetooth if we enabled
+ // it in the first place?
+ mState = STATE_DEVICE_NOT_FOUND;
+ }
+ }
+
+ // Should only be called on the handler thread
private void onDeviceAddedInternal(CachedBluetoothDevice d) {
if (mState == STATE_WAITING_FOR_DEVICE_DISCOVERY && d.getName().equals(mKeyboardName)) {
stopScanning();
@@ -425,6 +445,12 @@
} else {
mState = STATE_USER_CANCELLED;
}
+ break;
+ }
+ case MSG_BLE_ABORT_SCAN: {
+ int scanAttempt = msg.arg1;
+ bleAbortScanInternal(scanAttempt);
+ break;
}
case MSG_ON_BLUETOOTH_STATE_CHANGED: {
int bluetoothState = msg.arg1;
@@ -555,8 +581,6 @@
return "STATE_WAITING_FOR_DEVICE_DISCOVERY";
case STATE_WAITING_FOR_BLUETOOTH:
return "STATE_WAITING_FOR_BLUETOOTH";
- case STATE_WAITING_FOR_STATE_PAIRED:
- return "STATE_WAITING_FOR_STATE_PAIRED";
case STATE_PAIRING:
return "STATE_PAIRING";
case STATE_PAIRED:
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 552db7d..3e8dc4e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8230,6 +8230,18 @@
}
}
+ @Override
+ public IBinder getUriPermissionOwnerForActivity(IBinder activityToken) {
+ enforceNotIsolatedCaller("getUriPermissionOwnerForActivity");
+ synchronized(this) {
+ ActivityRecord r = ActivityRecord.isInStackLocked(activityToken);
+ if (r == null) {
+ throw new IllegalArgumentException("Activity does not exist; token="
+ + activityToken);
+ }
+ return r.getUriPermissionsLocked().getExternalTokenLocked();
+ }
+ }
/**
* @param uri This uri must NOT contain an embedded userId.
* @param sourceUserId The userId in which the uri is to be resolved.
diff --git a/services/core/java/com/android/server/wm/DropPermissionsHandler.java b/services/core/java/com/android/server/wm/DropPermissionsHandler.java
index 2ac1ef4..68cfaab 100644
--- a/services/core/java/com/android/server/wm/DropPermissionsHandler.java
+++ b/services/core/java/com/android/server/wm/DropPermissionsHandler.java
@@ -37,7 +37,7 @@
private final ArrayList<Uri> mUris = new ArrayList<Uri>();
- private IBinder mPermissionOwner = null;
+ private IBinder mActivityToken = null;
DropPermissionsHandler(ClipData clipData, int sourceUid, String targetPackage, int mode,
int sourceUserId, int targetUserId) {
@@ -51,18 +51,21 @@
}
@Override
- public void take() throws RemoteException {
- if (mPermissionOwner != null) {
+ public void take(IBinder activityToken) throws RemoteException {
+ if (mActivityToken != null) {
return;
}
+ mActivityToken = activityToken;
- mPermissionOwner = ActivityManagerNative.getDefault().newUriPermissionOwner("drop");
+ // Will throw if Activity is not found.
+ IBinder permissionOwner = ActivityManagerNative.getDefault().
+ getUriPermissionOwnerForActivity(mActivityToken);
long origId = Binder.clearCallingIdentity();
try {
for (int i = 0; i < mUris.size(); i++) {
ActivityManagerNative.getDefault().grantUriPermissionFromOwner(
- mPermissionOwner, mSourceUid, mTargetPackage, mUris.get(i), mMode,
+ permissionOwner, mSourceUid, mTargetPackage, mUris.get(i), mMode,
mSourceUserId, mTargetUserId);
}
} finally {
@@ -72,15 +75,24 @@
@Override
public void release() throws RemoteException {
- if (mPermissionOwner == null) {
+ if (mActivityToken == null) {
return;
}
+ IBinder permissionOwner = null;
+ try {
+ permissionOwner = ActivityManagerNative.getDefault().
+ getUriPermissionOwnerForActivity(mActivityToken);
+ } catch (Exception e) {
+ // Activity is destroyed, permissions already revoked.
+ return;
+ } finally {
+ mActivityToken = null;
+ }
+
for (int i = 0; i < mUris.size(); ++i) {
ActivityManagerNative.getDefault().revokeUriPermissionFromOwner(
- mPermissionOwner, mUris.get(i), mMode, mSourceUserId);
+ permissionOwner, mUris.get(i), mMode, mSourceUserId);
}
-
- mPermissionOwner = null;
}
}
diff --git a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
index af109d4..98033f6 100644
--- a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
+++ b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java
@@ -20,6 +20,7 @@
import android.graphics.Region;
import android.view.DisplayInfo;
import android.view.GestureDetector;
+import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.WindowManagerPolicy.PointerEventListener;
@@ -108,7 +109,8 @@
final int x = (int) motionEvent.getX();
final int y = (int) motionEvent.getY();
final Task task = mDisplayContent.findTaskForControlPoint(x, y);
- if (task == null) {
+ InputDevice inputDevice = motionEvent.getDevice();
+ if (task == null || inputDevice == null) {
mPointerIconShape = STYLE_NOT_SPECIFIED;
break;
}
@@ -130,7 +132,7 @@
}
if (mPointerIconShape != iconShape) {
mPointerIconShape = iconShape;
- motionEvent.getDevice().setPointerShape(iconShape);
+ inputDevice.setPointerShape(iconShape);
}
} else {
mPointerIconShape = STYLE_NOT_SPECIFIED;
@@ -139,7 +141,10 @@
case MotionEvent.ACTION_HOVER_EXIT:
mPointerIconShape = STYLE_NOT_SPECIFIED;
- motionEvent.getDevice().setPointerShape(STYLE_DEFAULT);
+ InputDevice inputDevice = motionEvent.getDevice();
+ if (inputDevice != null) {
+ inputDevice.setPointerShape(STYLE_DEFAULT);
+ }
break;
case MotionEvent.ACTION_UP:
diff --git a/services/print/java/com/android/server/print/PrintManagerService.java b/services/print/java/com/android/server/print/PrintManagerService.java
index f18617e..d5f384d 100644
--- a/services/print/java/com/android/server/print/PrintManagerService.java
+++ b/services/print/java/com/android/server/print/PrintManagerService.java
@@ -27,6 +27,7 @@
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
+import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
@@ -177,6 +178,25 @@
}
@Override
+ public Icon getCustomPrinterIcon(PrinterId printerId, int userId) {
+ final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
+ final UserState userState;
+ synchronized (mLock) {
+ // Only the current group members can get the printer icons.
+ if (resolveCallingProfileParentLocked(resolvedUserId) != getCurrentUserId()) {
+ return null;
+ }
+ userState = getOrCreateUserStateLocked(resolvedUserId);
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return userState.getCustomPrinterIcon(printerId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void cancelPrintJob(PrintJobId printJobId, int appId, int userId) {
final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId);
final int resolvedAppId;
diff --git a/services/print/java/com/android/server/print/RemotePrintService.java b/services/print/java/com/android/server/print/RemotePrintService.java
index 77a47f8..0af1525 100644
--- a/services/print/java/com/android/server/print/RemotePrintService.java
+++ b/services/print/java/com/android/server/print/RemotePrintService.java
@@ -24,6 +24,7 @@
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.ParceledListSlice;
+import android.graphics.drawable.Icon;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -98,6 +99,15 @@
public void onPrintersAdded(List<PrinterInfo> printers);
public void onPrintersRemoved(List<PrinterId> printerIds);
public void onServiceDied(RemotePrintService service);
+
+ /**
+ * Handle that a custom icon for a printer was loaded.
+ *
+ * @param printerId the id of the printer the icon belongs to
+ * @param icon the icon that was loaded
+ * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon()
+ */
+ public void onCustomPrinterIconLoaded(PrinterId printerId, Icon icon);
}
public RemotePrintService(Context context, ComponentName componentName, int userId,
@@ -404,6 +414,22 @@
printerId).sendToTarget();
}
+ /**
+ * Request the custom printer icon for a printer.
+ *
+ * @param printerId the id of the printer the icon should be loaded for
+ * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon()
+ */
+ public void requestCustomPrinterIcon(PrinterId printerId) {
+ try {
+ if (isBound()) {
+ mPrintService.requestCustomPrinterIcon(printerId);
+ }
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error requesting icon for " + printerId, re);
+ }
+ }
+
private void handleStartPrinterStateTracking(final PrinterId printerId) {
throwIfDestroyed();
// Take a note we are tracking the printer.
@@ -842,5 +868,19 @@
throw new IllegalArgumentException("Invalid printer id: " + printerId);
}
}
+
+ @Override
+ public void onCustomPrinterIconLoaded(PrinterId printerId, Icon icon)
+ throws RemoteException {
+ RemotePrintService service = mWeakService.get();
+ if (service != null) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ service.mCallbacks.onCustomPrinterIconLoaded(printerId, icon);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
}
}
diff --git a/services/print/java/com/android/server/print/RemotePrintSpooler.java b/services/print/java/com/android/server/print/RemotePrintSpooler.java
index c506b6f..40a8880 100644
--- a/services/print/java/com/android/server/print/RemotePrintSpooler.java
+++ b/services/print/java/com/android/server/print/RemotePrintSpooler.java
@@ -23,6 +23,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.graphics.drawable.Icon;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
@@ -35,6 +36,7 @@
import android.print.IPrintSpoolerClient;
import android.print.PrintJobId;
import android.print.PrintJobInfo;
+import android.print.PrinterId;
import android.util.Slog;
import android.util.TimedRemoteCaller;
@@ -72,6 +74,15 @@
private final SetPrintJobTagCaller mSetPrintJobTagCaller = new SetPrintJobTagCaller();
+ private final OnCustomPrinterIconLoadedCaller mCustomPrinterIconLoadedCaller =
+ new OnCustomPrinterIconLoadedCaller();
+
+ private final ClearCustomPrinterIconCacheCaller mClearCustomPrinterIconCache =
+ new ClearCustomPrinterIconCacheCaller();
+
+ private final GetCustomPrinterIconCaller mGetCustomPrinterIconCaller =
+ new GetCustomPrinterIconCaller();
+
private final ServiceConnection mServiceConnection = new MyServiceConnection();
private final Context mContext;
@@ -287,6 +298,96 @@
}
}
+ /**
+ * Handle that a custom icon for a printer was loaded.
+ *
+ * @param printerId the id of the printer the icon belongs to
+ * @param icon the icon that was loaded
+ * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon()
+ */
+ public final void onCustomPrinterIconLoaded(@NonNull PrinterId printerId,
+ @Nullable Icon icon) {
+ throwIfCalledOnMainThread();
+ synchronized (mLock) {
+ throwIfDestroyedLocked();
+ mCanUnbind = false;
+ }
+ try {
+ mCustomPrinterIconLoadedCaller.onCustomPrinterIconLoaded(getRemoteInstanceLazy(),
+ printerId, icon);
+ } catch (RemoteException|TimeoutException re) {
+ Slog.e(LOG_TAG, "Error loading new custom printer icon.", re);
+ } finally {
+ if (DEBUG) {
+ Slog.i(LOG_TAG,
+ "[user: " + mUserHandle.getIdentifier() + "] onCustomPrinterIconLoaded()");
+ }
+ synchronized (mLock) {
+ mCanUnbind = true;
+ mLock.notifyAll();
+ }
+ }
+ }
+
+ /**
+ * Get the custom icon for a printer. If the icon is not cached, the icon is
+ * requested asynchronously. Once it is available the printer is updated.
+ *
+ * @param printerId the id of the printer the icon should be loaded for
+ * @return the custom icon to be used for the printer or null if the icon is
+ * not yet available
+ * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon()
+ */
+ public final @Nullable Icon getCustomPrinterIcon(@NonNull PrinterId printerId) {
+ throwIfCalledOnMainThread();
+ synchronized (mLock) {
+ throwIfDestroyedLocked();
+ mCanUnbind = false;
+ }
+ try {
+ return mGetCustomPrinterIconCaller.getCustomPrinterIcon(getRemoteInstanceLazy(),
+ printerId);
+ } catch (RemoteException|TimeoutException re) {
+ Slog.e(LOG_TAG, "Error getting custom printer icon.", re);
+ return null;
+ } finally {
+ if (DEBUG) {
+ Slog.i(LOG_TAG,
+ "[user: " + mUserHandle.getIdentifier() + "] getCustomPrinterIcon()");
+ }
+ synchronized (mLock) {
+ mCanUnbind = true;
+ mLock.notifyAll();
+ }
+ }
+ }
+
+ /**
+ * Clear the custom printer icon cache
+ */
+ public void clearCustomPrinterIconCache() {
+ throwIfCalledOnMainThread();
+ synchronized (mLock) {
+ throwIfDestroyedLocked();
+ mCanUnbind = false;
+ }
+ try {
+ mClearCustomPrinterIconCache.clearCustomPrinterIconCache(getRemoteInstanceLazy());
+ } catch (RemoteException|TimeoutException re) {
+ Slog.e(LOG_TAG, "Error clearing custom printer icon cache.", re);
+ } finally {
+ if (DEBUG) {
+ Slog.i(LOG_TAG,
+ "[user: " + mUserHandle.getIdentifier()
+ + "] clearCustomPrinterIconCache()");
+ }
+ synchronized (mLock) {
+ mCanUnbind = true;
+ mLock.notifyAll();
+ }
+ }
+ }
+
public final boolean setPrintJobTag(PrintJobId printJobId, String tag) {
throwIfCalledOnMainThread();
synchronized (mLock) {
@@ -632,6 +733,69 @@
}
}
+ private static final class OnCustomPrinterIconLoadedCaller extends TimedRemoteCaller<Void> {
+ private final IPrintSpoolerCallbacks mCallback;
+
+ public OnCustomPrinterIconLoadedCaller() {
+ super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
+ mCallback = new BasePrintSpoolerServiceCallbacks() {
+ @Override
+ public void onCustomPrinterIconCached(int sequence) {
+ onRemoteMethodResult(null, sequence);
+ }
+ };
+ }
+
+ public Void onCustomPrinterIconLoaded(IPrintSpooler target, PrinterId printerId,
+ Icon icon) throws RemoteException, TimeoutException {
+ final int sequence = onBeforeRemoteCall();
+ target.onCustomPrinterIconLoaded(printerId, icon, mCallback, sequence);
+ return getResultTimed(sequence);
+ }
+ }
+
+ private static final class ClearCustomPrinterIconCacheCaller extends TimedRemoteCaller<Void> {
+ private final IPrintSpoolerCallbacks mCallback;
+
+ public ClearCustomPrinterIconCacheCaller() {
+ super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
+ mCallback = new BasePrintSpoolerServiceCallbacks() {
+ @Override
+ public void customPrinterIconCacheCleared(int sequence) {
+ onRemoteMethodResult(null, sequence);
+ }
+ };
+ }
+
+ public Void clearCustomPrinterIconCache(IPrintSpooler target)
+ throws RemoteException, TimeoutException {
+ final int sequence = onBeforeRemoteCall();
+ target.clearCustomPrinterIconCache(mCallback, sequence);
+ return getResultTimed(sequence);
+ }
+ }
+
+ private static final class GetCustomPrinterIconCaller extends TimedRemoteCaller<Icon> {
+ private final IPrintSpoolerCallbacks mCallback;
+
+ public GetCustomPrinterIconCaller() {
+ super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
+ mCallback = new BasePrintSpoolerServiceCallbacks() {
+ @Override
+ public void onGetCustomPrinterIconResult(Icon icon, int sequence) {
+ onRemoteMethodResult(icon, sequence);
+ }
+ };
+ }
+
+ public Icon getCustomPrinterIcon(IPrintSpooler target, PrinterId printerId)
+ throws RemoteException, TimeoutException {
+ final int sequence = onBeforeRemoteCall();
+ target.getCustomPrinterIcon(printerId, mCallback, sequence);
+ return getResultTimed(sequence);
+ }
+ }
+
private static abstract class BasePrintSpoolerServiceCallbacks
extends IPrintSpoolerCallbacks.Stub {
@Override
@@ -658,6 +822,21 @@
public void onSetPrintJobTagResult(boolean success, int sequence) {
/* do nothing */
}
+
+ @Override
+ public void onCustomPrinterIconCached(int sequence) {
+ /* do nothing */
+ }
+
+ @Override
+ public void onGetCustomPrinterIconResult(@Nullable Icon icon, int sequence) {
+ /* do nothing */
+ }
+
+ @Override
+ public void customPrinterIconCacheCleared(int sequence) {
+ /* do nothing */
+ }
}
private static final class PrintSpoolerClient extends IPrintSpoolerClient.Stub {
diff --git a/services/print/java/com/android/server/print/UserState.java b/services/print/java/com/android/server/print/UserState.java
index 6a50a6e..65d6ce2 100644
--- a/services/print/java/com/android/server/print/UserState.java
+++ b/services/print/java/com/android/server/print/UserState.java
@@ -26,6 +26,7 @@
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
+import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Binder;
@@ -271,6 +272,28 @@
return printJob;
}
+ /**
+ * Get the custom icon for a printer. If the icon is not cached, the icon is
+ * requested asynchronously. Once it is available the printer is updated.
+ *
+ * @param printerId the id of the printer the icon should be loaded for
+ * @return the custom icon to be used for the printer or null if the icon is
+ * not yet available
+ * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon()
+ */
+ public Icon getCustomPrinterIcon(PrinterId printerId) {
+ Icon icon = mSpooler.getCustomPrinterIcon(printerId);
+
+ if (icon == null) {
+ RemotePrintService service = mActiveServices.get(printerId.getServiceName());
+ if (service != null) {
+ service.requestCustomPrinterIcon(printerId);
+ }
+ }
+
+ return icon;
+ }
+
public void cancelPrintJob(PrintJobId printJobId, int appId) {
PrintJobInfo printJobInfo = mSpooler.getPrintJobInfo(printJobId, appId);
if (printJobInfo == null) {
@@ -345,6 +368,8 @@
throwIfDestroyedLocked();
if (mPrinterDiscoverySession == null) {
+ mSpooler.clearCustomPrinterIconCache();
+
// If we do not have a session, tell all service to create one.
mPrinterDiscoverySession = new PrinterDiscoverySessionMediator(mContext) {
@Override
@@ -533,6 +558,20 @@
}
@Override
+ public void onCustomPrinterIconLoaded(PrinterId printerId, Icon icon) {
+ synchronized (mLock) {
+ throwIfDestroyedLocked();
+
+ // No session - nothing to do.
+ if (mPrinterDiscoverySession == null) {
+ return;
+ }
+ mSpooler.onCustomPrinterIconLoaded(printerId, icon);
+ mPrinterDiscoverySession.onCustomPrinterIconLoadedLocked(printerId);
+ }
+ }
+
+ @Override
public void onServiceDied(RemotePrintService service) {
synchronized (mLock) {
throwIfDestroyedLocked();
@@ -1250,6 +1289,37 @@
service.destroy();
}
+ /**
+ * Handle that a custom icon for a printer was loaded.
+ *
+ * This increments the icon generation and adds the printer again which triggers an update
+ * in all users of the currently known printers.
+ *
+ * @param printerId the id of the printer the icon belongs to
+ * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon()
+ */
+ public void onCustomPrinterIconLoadedLocked(PrinterId printerId) {
+ if (DEBUG) {
+ Log.i(LOG_TAG, "onCustomPrinterIconLoadedLocked()");
+ }
+ if (mIsDestroyed) {
+ Log.w(LOG_TAG, "Not updating printer - session destroyed");
+ return;
+ }
+
+ PrinterInfo printer = mPrinters.get(printerId);
+ if (printer != null) {
+ PrinterInfo newPrinter = (new PrinterInfo.Builder(printer))
+ .incCustomPrinterIconGen().build();
+ mPrinters.put(printerId, newPrinter);
+
+ ArrayList<PrinterInfo> addedPrinters = new ArrayList<>(1);
+ addedPrinters.add(newPrinter);
+ mHandler.obtainMessage(SessionHandler.MSG_DISPATCH_PRINTERS_ADDED,
+ addedPrinters).sendToTarget();
+ }
+ }
+
public void onServiceDiedLocked(RemotePrintService service) {
// Remove the reported by that service.
removePrintersForServiceLocked(service.getComponentName());
diff --git a/tools/aapt2/compile/XmlIdCollector_test.cpp b/tools/aapt2/compile/XmlIdCollector_test.cpp
index 45b7af2..a37ea86 100644
--- a/tools/aapt2/compile/XmlIdCollector_test.cpp
+++ b/tools/aapt2/compile/XmlIdCollector_test.cpp
@@ -37,13 +37,13 @@
XmlIdCollector collector;
ASSERT_TRUE(collector.consume(context.get(), doc.get()));
- EXPECT_EQ(1u, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(),
+ EXPECT_EQ(1, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(),
SourcedResourceName{ test::parseNameOrDie(u"@id/foo"), 3u }));
- EXPECT_EQ(1u, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(),
+ EXPECT_EQ(1, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(),
SourcedResourceName{ test::parseNameOrDie(u"@id/bar"), 3u }));
- EXPECT_EQ(1u, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(),
+ EXPECT_EQ(1, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(),
SourcedResourceName{ test::parseNameOrDie(u"@id/car"), 6u }));
}