Adding hidden APIs for observing the print jobs state.

This is needed for implementing the print job settigns UI.

bug:10935736

Change-Id: I63b42cbf4ce6a259fa1af47fa368b148ca5621c1
diff --git a/core/java/android/print/IPrintJobStateChangeListener.aidl b/core/java/android/print/IPrintJobStateChangeListener.aidl
new file mode 100644
index 0000000..c1d39f0
--- /dev/null
+++ b/core/java/android/print/IPrintJobStateChangeListener.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.print;
+
+import android.print.PrintJobId;
+
+/**
+ * Interface for observing print job state changes.
+ *
+ * @hide
+ */
+oneway interface IPrintJobStateChangeListener {
+    void onPrintJobStateChanged(in PrintJobId printJobId);
+}
diff --git a/core/java/android/print/IPrintManager.aidl b/core/java/android/print/IPrintManager.aidl
index 4e839c6..4044b31 100644
--- a/core/java/android/print/IPrintManager.aidl
+++ b/core/java/android/print/IPrintManager.aidl
@@ -20,6 +20,7 @@
 import android.print.IPrintDocumentAdapter;
 import android.print.IPrintClient;
 import android.print.PrintJobId;
+import android.print.IPrintJobStateChangeListener;
 import android.print.PrinterId;
 import android.print.PrintJobInfo;
 import android.print.PrintAttributes;
@@ -39,6 +40,11 @@
     void cancelPrintJob(in PrintJobId printJobId, int appId, int userId);
     void restartPrintJob(in PrintJobId printJobId, int appId, int userId);
 
+    void addPrintJobStateChangeListener(in IPrintJobStateChangeListener listener,
+            int appId, int userId);
+    void removePrintJobStateChangeListener(in IPrintJobStateChangeListener listener,
+            int userId);
+
     List<PrintServiceInfo> getEnabledPrintServices(int userId);
 
     void createPrinterDiscoverySession(in IPrinterDiscoveryObserver observer, int userId);
diff --git a/core/java/android/print/IPrintSpoolerClient.aidl b/core/java/android/print/IPrintSpoolerClient.aidl
index 8b511d6..0cf00cc 100644
--- a/core/java/android/print/IPrintSpoolerClient.aidl
+++ b/core/java/android/print/IPrintSpoolerClient.aidl
@@ -18,7 +18,7 @@
 
 import android.content.ComponentName;
 import android.print.PrintJobInfo;
-
+import android.print.PrintJobId;
 
 /**
  * Interface for receiving interesting state updates from the print spooler.
@@ -29,4 +29,5 @@
     void onPrintJobQueued(in PrintJobInfo printJob);
     void onAllPrintJobsForServiceHandled(in ComponentName printService);
     void onAllPrintJobsHandled();
+    void onPrintJobStateChanged(in PrintJobId printJobId, int appId);
 }
diff --git a/core/java/android/print/PrintJob.java b/core/java/android/print/PrintJob.java
index 00ade07..535ae43 100644
--- a/core/java/android/print/PrintJob.java
+++ b/core/java/android/print/PrintJob.java
@@ -62,14 +62,110 @@
     }
 
     /**
-     * Cancels this print job.
+     * Cancels this print job. You can request cancellation of a
+     * queued, started, blocked, or failed print job.
+     *
+     * @see #isQueued()
+     * @see #isStarted()
+     * @see #isBlocked()
+     * @see #isFailed()
      */
     public void cancel() {
-        if (!isInImmutableState()) {
+        final int state = getInfo().getState();
+        if (state == PrintJobInfo.STATE_QUEUED
+                || state == PrintJobInfo.STATE_STARTED
+                || state == PrintJobInfo.STATE_BLOCKED
+                || state == PrintJobInfo.STATE_FAILED) {
             mPrintManager.cancelPrintJob(mCachedInfo.getId());
         }
     }
 
+    /**
+     * Restarts this print job. You can request restart of a failed
+     * print job.
+     *
+     * @see #isFailed()
+     */
+    public void restart() {
+        if (isFailed()) {
+            mPrintManager.restartPrintJob(mCachedInfo.getId());
+        }
+    }
+
+    /**
+     * Gets whether this print job is queued. Such a print job is
+     * ready to be printed. You can request a cancellation via
+     * {@link #cancel()}.
+     *
+     * @return Whether the print job is queued.
+     *
+     * @see #cancel()
+     */
+    public boolean isQueued() {
+        return getInfo().getState() == PrintJobInfo.STATE_QUEUED;
+    }
+
+    /**
+     * Gets whether this print job is started. Such a print job is
+     * being printed. You can request a cancellation via
+     * {@link #cancel()}.
+     *
+     * @return Whether the print job is started.
+     *
+     * @see #cancel()
+     */
+    public boolean isStarted() {
+        return getInfo().getState() == PrintJobInfo.STATE_STARTED;
+    }
+
+    /**
+     * Gets whether this print job is blocked. Such a print job is halted
+     * due to an abnormal condition. You can request a cancellation via
+     * {@link #cancel()}.
+     *
+     * @return Whether the print job is blocked.
+     *
+     * @see #cancel()
+     */
+    public boolean isBlocked() {
+        return getInfo().getState() == PrintJobInfo.STATE_BLOCKED;
+    }
+
+    /**
+     * Gets whether this print job is completed. Such a print job
+     * is successfully printed. You can neither cancel nor restart
+     * such a print job.
+     *
+     * @return Whether the print job is completed.
+     */
+    public boolean isCompleted() {
+        return getInfo().getState() == PrintJobInfo.STATE_COMPLETED;
+    }
+
+    /**
+     * Gets whether this print job is failed. Such a print job is
+     * not successfully printed due to an error. You can request
+     * a restart via {@link #restart()}.
+     *
+     * @return Whether the print job is failed.
+     *
+     * @see #restart()
+     */
+    public boolean isFailed() {
+        return getInfo().getState() == PrintJobInfo.STATE_FAILED;
+    }
+
+    /**
+     * Gets whether this print job is cancelled. Such a print job was
+     * cancelled as a result of a user request. This is a final state.
+     * You cannot restart such a print job.
+     *
+     * @return Whether the print job is cancelled.
+     */
+    public boolean isCancelled() {
+        return getInfo().getState() == PrintJobInfo.STATE_CANCELED;
+    }
+
     private boolean isInImmutableState() {
         final int state = mCachedInfo.getState();
         return state == PrintJobInfo.STATE_COMPLETED
diff --git a/core/java/android/print/PrintJobInfo.java b/core/java/android/print/PrintJobInfo.java
index 502a9f2..e5d06a2 100644
--- a/core/java/android/print/PrintJobInfo.java
+++ b/core/java/android/print/PrintJobInfo.java
@@ -138,6 +138,9 @@
     /** Optional tag assigned by a print service.*/
     private String mTag;
 
+    /** The wall time when the print job was created. */
+    private long mCreationTime;
+
     /** How many copies to print. */
     private int mCopies;
 
@@ -168,6 +171,7 @@
         mAppId = other.mAppId;
         mUserId = other.mUserId;
         mTag = other.mTag;
+        mCreationTime = other.mCreationTime;
         mCopies = other.mCopies;
         mStateReason = other.mStateReason;
         mPageRanges = other.mPageRanges;
@@ -184,6 +188,7 @@
         mAppId = parcel.readInt();
         mUserId = parcel.readInt();
         mTag = parcel.readString();
+        mCreationTime = parcel.readLong();
         mCopies = parcel.readInt();
         mStateReason = parcel.readString();
         if (parcel.readInt() == 1) {
@@ -368,6 +373,29 @@
     }
 
     /**
+     * Gets the wall time in millisecond when this print job was created.
+     *
+     * @return The creation time in milliseconds.
+     */
+    public long getCreationTime() {
+        return mCreationTime;
+    }
+
+    /**
+     * Sets the wall time in milliseconds when this print job was created.
+     *
+     * @param creationTime The creation time in milliseconds.
+     *
+     * @hide
+     */
+    public void setCreationTime(long creationTime) {
+        if (creationTime < 0) {
+            throw new IllegalArgumentException("creationTime must be non-negative.");
+        }
+        mCreationTime = creationTime;
+    }
+
+    /**
      * Gets the number of copies.
      *
      * @return The number of copies or zero if not set.
@@ -491,6 +519,7 @@
         parcel.writeInt(mAppId);
         parcel.writeInt(mUserId);
         parcel.writeString(mTag);
+        parcel.writeLong(mCreationTime);
         parcel.writeInt(mCopies);
         parcel.writeString(mStateReason);
         if (mPageRanges != null) {
@@ -522,6 +551,7 @@
         builder.append(", status: ").append(stateToString(mState));
         builder.append(", printer: " + mPrinterId);
         builder.append(", tag: ").append(mTag);
+        builder.append(", creationTime: " + mCreationTime);
         builder.append(", copies: ").append(mCopies);
         builder.append(", attributes: " + (mAttributes != null
                 ? mAttributes.toString() : null));
@@ -537,7 +567,7 @@
     public static String stateToString(int state) {
         switch (state) {
             case STATE_CREATED: {
-                return "STATUS_CREATED";
+                return "STATE_CREATED";
             }
             case STATE_QUEUED: {
                 return "STATE_QUEUED";
@@ -546,21 +576,20 @@
                 return "STATE_STARTED";
             }
             case STATE_FAILED: {
-                return "STATUS_FAILED";
+                return "STATE_FAILED";
             }
             case STATE_COMPLETED: {
-                return "STATUS_COMPLETED";
+                return "STATE_COMPLETED";
             }
             case STATE_CANCELED: {
-                return "STATUS_CANCELED";
+                return "STATE_CANCELED";
             }
             default: {
-                return "STATUS_UNKNOWN";
+                return "STATE_UNKNOWN";
             }
         }
     }
 
-
     public static final Parcelable.Creator<PrintJobInfo> CREATOR =
             new Creator<PrintJobInfo>() {
         @Override
diff --git a/core/java/android/print/PrintManager.java b/core/java/android/print/PrintManager.java
index 5429155..a015388 100644
--- a/core/java/android/print/PrintManager.java
+++ b/core/java/android/print/PrintManager.java
@@ -30,6 +30,7 @@
 import android.print.PrintDocumentAdapter.WriteResultCallback;
 import android.printservice.PrintServiceInfo;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.Log;
 
 import com.android.internal.os.SomeArgs;
@@ -40,6 +41,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 
 /**
  * System level service for accessing the printing capabilities of the platform.
@@ -70,6 +72,19 @@
 
     private final Handler mHandler;
 
+    private Map<PrintJobStateChangeListener, PrintJobStateChangeListenerWrapper> mPrintJobStateChangeListeners;
+
+    /** @hide */
+    public interface PrintJobStateChangeListener {
+
+        /**
+         * Callback notifying that a print job state changed.
+         *
+         * @param printJobId The print job id.
+         */
+        public void onPrintJobsStateChanged(PrintJobId printJobId);
+    }
+
     /**
      * Creates a new instance.
      *
@@ -106,7 +121,6 @@
      * @param userId The user id for which to get all print jobs.
      * @return An instance if the caller has the permission to access
      * all print jobs, null otherwise.
-     *
      * @hide
      */
     public PrintManager getGlobalPrintManagerForUser(int userId) {
@@ -123,6 +137,75 @@
     }
 
     /**
+     * Adds a listener for observing the state of print jobs.
+     *
+     * @param listener The listener to add.
+     *
+     * @hide
+     */
+    public void addPrintJobStateChangeListener(PrintJobStateChangeListener listener) {
+        if (mPrintJobStateChangeListeners == null) {
+            mPrintJobStateChangeListeners = new ArrayMap<PrintJobStateChangeListener,
+                    PrintJobStateChangeListenerWrapper>();
+        }
+        PrintJobStateChangeListenerWrapper wrappedListener =
+                new PrintJobStateChangeListenerWrapper(listener);
+        try {
+            mService.addPrintJobStateChangeListener(wrappedListener, mAppId, mUserId);
+            mPrintJobStateChangeListeners.put(listener, wrappedListener);
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error adding print job state change listener", re);
+        }
+    }
+
+    /**
+     * Removes a listener for observing the state of print jobs.
+     *
+     * @param listener The listener to remove.
+     *
+     * @hide
+     */
+    public void removePrintJobStateChangeListener(PrintJobStateChangeListener listener) {
+        if (mPrintJobStateChangeListeners == null) {
+            return;
+        }
+        PrintJobStateChangeListenerWrapper wrappedListener =
+                mPrintJobStateChangeListeners.remove(listener);
+        if (wrappedListener == null) {
+            return;
+        }
+        if (mPrintJobStateChangeListeners.isEmpty()) {
+            mPrintJobStateChangeListeners = null;
+        }
+        try {
+            mService.removePrintJobStateChangeListener(wrappedListener, mUserId);
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error removing print job state change listener", re);
+        }
+    }
+
+    /**
+     * Gets a print job given its id.
+     *
+     * @return The print job list.
+     *
+     * @see PrintJob
+     *
+     * @hide
+     */
+    public PrintJob getPrintJob(PrintJobId printJobId) {
+        try {
+            PrintJobInfo printJob = mService.getPrintJobInfo(printJobId, mAppId, mUserId);
+            if (printJob != null) {
+                return new PrintJob(printJob, this);
+            }
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error getting print job", re);
+        }
+        return null;
+    }
+
+    /**
      * Gets the print jobs for this application.
      *
      * @return The print job list.
@@ -155,6 +238,14 @@
         }
     }
 
+    void restartPrintJob(PrintJobId printJobId) {
+        try {
+            mService.restartPrintJob(printJobId, mAppId, mUserId);
+        } catch (RemoteException re) {
+            Log.e(LOG_TAG, "Error restarting a print job: " + printJobId, re);
+        }
+    }
+
     /**
      * Creates a print job for printing a {@link PrintDocumentAdapter} with default print
      * attributes.
@@ -163,7 +254,6 @@
      * @param documentAdapter An adapter that emits the document to print.
      * @param attributes The default print job attributes.
      * @return The created print job on success or null on failure.
-     *
      * @see PrintJob
      */
     public PrintJob print(String printJobName, PrintDocumentAdapter documentAdapter,
@@ -220,11 +310,11 @@
         }
 
         @Override
-        public void startPrintJobConfigActivity(IntentSender intent)  {
+        public void startPrintJobConfigActivity(IntentSender intent) {
             PrintManager manager = mWeakPrintManager.get();
             if (manager != null) {
                 SomeArgs args = SomeArgs.obtain();
-                args.arg1 =  manager.mContext;
+                args.arg1 = manager.mContext;
                 args.arg2 = intent;
                 manager.mHandler.obtainMessage(0, args).sendToTarget();
             }
@@ -271,7 +361,7 @@
 
         @Override
         public void write(PageRange[] pages, ParcelFileDescriptor fd,
-            IWriteResultCallback callback, int sequence) {
+                IWriteResultCallback callback, int sequence) {
             synchronized (mLock) {
                 if (mLayoutOrWriteCancellation != null) {
                     mLayoutOrWriteCancellation.cancel();
@@ -492,4 +582,21 @@
             }
         }
     }
+
+    private static final class PrintJobStateChangeListenerWrapper extends
+            IPrintJobStateChangeListener.Stub {
+        private final WeakReference<PrintJobStateChangeListener> mWeakListener;
+
+        public PrintJobStateChangeListenerWrapper(PrintJobStateChangeListener listener) {
+            mWeakListener = new WeakReference<PrintJobStateChangeListener>(listener);
+        }
+
+        @Override
+        public void onPrintJobStateChanged(PrintJobId printJobId) {
+            PrintJobStateChangeListener listener = mWeakListener.get();
+            if (listener != null) {
+                listener.onPrintJobsStateChanged(printJobId);
+            }
+        }
+    }
 }
diff --git a/core/java/android/print/PrinterInfo.java b/core/java/android/print/PrinterInfo.java
index a51e28b..ad79a38 100644
--- a/core/java/android/print/PrinterInfo.java
+++ b/core/java/android/print/PrinterInfo.java
@@ -302,7 +302,7 @@
 
         private boolean isValidStatus(int status) {
             return (status == STATUS_IDLE
-                    || status == STATUS_IDLE
+                    || status == STATUS_BUSY
                     || status == STATUS_UNAVAILABLE);
         }
     }