Merge "Print job files and print job records not always cleaned up." into klp-dev

This commit is contained in:
Svetoslav Ganov
2013-09-28 01:06:51 +00:00
committed by Android (Google) Code Review
5 changed files with 237 additions and 129 deletions

View File

@@ -36,7 +36,6 @@ import android.print.PrintJobInfo;
*/
oneway interface IPrintSpooler {
void removeObsoletePrintJobs();
void forgetPrintJobs(in List<PrintJobId> printJob);
void getPrintJobInfos(IPrintSpoolerCallbacks callback, in ComponentName componentName,
int state, int appId, int sequence);
void getPrintJobInfo(in PrintJobId printJobId, IPrintSpoolerCallbacks callback,

View File

@@ -29,5 +29,5 @@ oneway interface IPrintSpoolerClient {
void onPrintJobQueued(in PrintJobInfo printJob);
void onAllPrintJobsForServiceHandled(in ComponentName printService);
void onAllPrintJobsHandled();
void onPrintJobStateChanged(in PrintJobId printJobId, int appId);
void onPrintJobStateChanged(in PrintJobInfo printJob);
}

View File

@@ -43,6 +43,7 @@ import android.print.PrintManager;
import android.print.PrinterId;
import android.print.PrinterInfo;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.AtomicFile;
import android.util.Log;
import android.util.Slog;
@@ -59,10 +60,12 @@ import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -82,6 +85,8 @@ public final class PrintSpoolerService extends Service {
private static final long CHECK_ALL_PRINTJOBS_HANDLED_DELAY = 5000;
private static final String PRINT_JOB_FILE_PREFIX = "print_job_";
private static final String PRINT_FILE_EXTENSION = "pdf";
private static final Object sLock = new Object();
@@ -168,9 +173,9 @@ public final class PrintSpoolerService extends Service {
PrintSpoolerService.this, 0, intent, PendingIntent.FLAG_ONE_SHOT
| PendingIntent.FLAG_CANCEL_CURRENT).getIntentSender();
Message message = mHandlerCaller.obtainMessageIIO(
Message message = mHandlerCaller.obtainMessageO(
HandlerCallerCallback.MSG_ON_PRINT_JOB_STATE_CHANGED,
printJob.getAppId(), 0, printJob.getId());
printJob);
mHandlerCaller.executeOrSendMessage(message);
message = mHandlerCaller.obtainMessageOO(
@@ -179,9 +184,6 @@ public final class PrintSpoolerService extends Service {
mHandlerCaller.executeOrSendMessage(message);
printJob.setCreationTime(System.currentTimeMillis());
synchronized (mLock) {
mPersistanceManager.writeStateLocked();
}
}
@Override
@@ -225,12 +227,40 @@ public final class PrintSpoolerService extends Service {
}
@Override
public void forgetPrintJobs(List<PrintJobId> printJobIds) {
PrintSpoolerService.this.forgetPrintJobs(printJobIds);
protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
PrintSpoolerService.this.dump(fd, writer, args);
}
};
}
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
synchronized (mLock) {
String prefix = args[0];
String tab = " ";
pw.append(prefix).append("print jobs:").println();
final int printJobCount = mPrintJobs.size();
for (int i = 0; i < printJobCount; i++) {
PrintJobInfo printJob = mPrintJobs.get(i);
pw.append(prefix).append(tab).append(printJob.toString());
pw.println();
}
pw.append(prefix).append("print job files:").println();
File[] files = getFilesDir().listFiles();
if (files != null) {
final int fileCount = files.length;
for (int i = 0; i < fileCount; i++) {
File file = files[i];
if (file.isFile() && file.getName().startsWith(PRINT_JOB_FILE_PREFIX)) {
pw.append(prefix).append(tab).append(file.getName()).println();
}
}
}
}
}
private void sendOnPrintJobQueued(PrintJobInfo printJob) {
Message message = mHandlerCaller.obtainMessageO(
HandlerCallerCallback.MSG_ON_PRINT_JOB_QUEUED, printJob);
@@ -324,10 +354,9 @@ public final class PrintSpoolerService extends Service {
case MSG_ON_PRINT_JOB_STATE_CHANGED: {
if (mClient != null) {
PrintJobId printJobId = (PrintJobId) message.obj;
final int appId = message.arg1;
PrintJobInfo printJob = (PrintJobInfo) message.obj;
try {
mClient.onPrintJobStateChanged(printJobId, appId);
mClient.onPrintJobStateChanged(printJob);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error notify for print job state change.", re);
}
@@ -391,17 +420,46 @@ public final class PrintSpoolerService extends Service {
public void createPrintJob(PrintJobInfo printJob) {
synchronized (mLock) {
addPrintJobLocked(printJob);
setPrintJobState(printJob.getId(), PrintJobInfo.STATE_CREATED, null);
}
}
private void handleReadPrintJobsLocked() {
// Make a map with the files for a print job since we may have
// to delete some. One example of getting orphan files if the
// spooler crashes while constructing a print job. We do not
// persist partially populated print jobs under construction to
// avoid special handling for various attributes missing.
ArrayMap<PrintJobId, File> fileForJobMap = null;
File[] files = getFilesDir().listFiles();
if (files != null) {
final int fileCount = files.length;
for (int i = 0; i < fileCount; i++) {
File file = files[i];
if (file.isFile() && file.getName().startsWith(PRINT_JOB_FILE_PREFIX)) {
if (fileForJobMap == null) {
fileForJobMap = new ArrayMap<PrintJobId, File>();
}
String printJobIdString = file.getName().substring(0,
PRINT_JOB_FILE_PREFIX.length());
PrintJobId printJobId = PrintJobId.unflattenFromString(
printJobIdString);
fileForJobMap.put(printJobId, file);
}
}
}
final int printJobCount = mPrintJobs.size();
for (int i = 0; i < printJobCount; i++) {
PrintJobInfo printJob = mPrintJobs.get(i);
// We want to have only the orphan files at the end.
if (fileForJobMap != null) {
fileForJobMap.remove(printJob.getId());
}
// Update the notification.
mNotificationController.onPrintJobStateChanged(printJob);
switch (printJob.getState()) {
case PrintJobInfo.STATE_QUEUED:
case PrintJobInfo.STATE_STARTED:
@@ -415,6 +473,15 @@ public final class PrintSpoolerService extends Service {
} break;
}
}
// Delete the orphan files.
if (fileForJobMap != null) {
final int orphanFileCount = fileForJobMap.size();
for (int i = 0; i < orphanFileCount; i++) {
File file = fileForJobMap.valueAt(i);
file.delete();
}
}
}
public void checkAllPrintJobsHandled() {
@@ -465,7 +532,7 @@ public final class PrintSpoolerService extends Service {
}
public File generateFileForPrintJob(PrintJobId printJobId) {
return new File(getFilesDir(), "print_job_"
return new File(getFilesDir(), PRINT_JOB_FILE_PREFIX
+ printJobId.flattenToString() + "." + PRINT_FILE_EXTENSION);
}
@@ -476,31 +543,6 @@ public final class PrintSpoolerService extends Service {
}
}
private void forgetPrintJobs(List<PrintJobId> printJobIds) {
synchronized (mLock) {
boolean printJobsRemoved = false;
final int removedPrintJobCount = printJobIds.size();
for (int i = 0; i < removedPrintJobCount; i++) {
PrintJobId removedPrintJobId = printJobIds.get(i);
final int printJobCount = mPrintJobs.size();
for (int j = printJobCount - 1; j >= 0; j--) {
PrintJobInfo printJob = mPrintJobs.get(j);
if (removedPrintJobId.equals(printJob.getId())) {
mPrintJobs.remove(j);
printJobsRemoved = true;
if (DEBUG_PRINT_JOB_LIFECYCLE) {
Slog.i(LOG_TAG, "[FORGOT] " + printJob.getId().flattenToString());
}
removePrintJobFileLocked(printJob.getId());
}
}
}
if (printJobsRemoved) {
mPersistanceManager.writeStateLocked();
}
}
}
private void removeObsoletePrintJobs() {
synchronized (mLock) {
final int printJobCount = mPrintJobs.size();
@@ -523,7 +565,7 @@ public final class PrintSpoolerService extends Service {
if (file.exists()) {
file.delete();
if (DEBUG_PRINT_JOB_LIFECYCLE) {
Slog.i(LOG_TAG, "[REMOVE FILE FOR] " + printJobId.flattenToString());
Slog.i(LOG_TAG, "[REMOVE FILE FOR] " + printJobId);
}
}
}
@@ -552,10 +594,7 @@ public final class PrintSpoolerService extends Service {
switch (state) {
case PrintJobInfo.STATE_COMPLETED:
case PrintJobInfo.STATE_CANCELED:
// Just remove the file but keep the print job info since
// the app that created it may be holding onto the PrintJob
// instance and query it for its most recent state. We will
// remove the info for this job when told so by the system.
mPrintJobs.remove(printJob);
removePrintJobFileLocked(printJob.getId());
// $fall-through$
@@ -582,9 +621,9 @@ public final class PrintSpoolerService extends Service {
notifyOnAllPrintJobsHandled();
}
Message message = mHandlerCaller.obtainMessageIIO(
Message message = mHandlerCaller.obtainMessageO(
HandlerCallerCallback.MSG_ON_PRINT_JOB_STATE_CHANGED,
printJob.getAppId(), 0, printJob.getId());
printJob);
mHandlerCaller.executeOrSendMessage(message);
}
}

View File

@@ -33,7 +33,6 @@ import android.print.IPrintSpoolerCallbacks;
import android.print.IPrintSpoolerClient;
import android.print.PrintJobId;
import android.print.PrintJobInfo;
import android.print.PrintManager;
import android.util.Slog;
import android.util.TimedRemoteCaller;
@@ -91,7 +90,7 @@ final class RemotePrintSpooler {
public static interface PrintSpoolerCallbacks {
public void onPrintJobQueued(PrintJobInfo printJob);
public void onAllPrintJobsForServiceHandled(ComponentName printService);
public void onPrintJobStateChanged(PrintJobId printJobId, int appId);
public void onPrintJobStateChanged(PrintJobInfo printJob);
}
public RemotePrintSpooler(Context context, int userId,
@@ -280,30 +279,6 @@ final class RemotePrintSpooler {
}
}
public final void forgetPrintJobs(List<PrintJobId> printJobIds) {
throwIfCalledOnMainThread();
synchronized (mLock) {
throwIfDestroyedLocked();
mCanUnbind = false;
}
try {
getRemoteInstanceLazy().forgetPrintJobs(printJobIds);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error forgeting print jobs", re);
} catch (TimeoutException te) {
Slog.e(LOG_TAG, "Error forgeting print jobs", te);
} finally {
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier()
+ "] forgetPrintJobs()");
}
synchronized (mLock) {
mCanUnbind = true;
mLock.notifyAll();
}
}
}
public final void destroy() {
throwIfCalledOnMainThread();
if (DEBUG) {
@@ -323,18 +298,15 @@ final class RemotePrintSpooler {
.append(String.valueOf(mDestroyed)).println();
pw.append(prefix).append("bound=")
.append((mRemoteInstance != null) ? "true" : "false").println();
pw.append(prefix).append("print jobs:").println();
if (mRemoteInstance != null) {
List<PrintJobInfo> printJobs = getPrintJobInfos(null,
PrintJobInfo.STATE_ANY, PrintManager.APP_ID_ANY);
if (printJobs != null) {
final int printJobCount = printJobs.size();
for (int i = 0; i < printJobCount; i++) {
PrintJobInfo printJob = printJobs.get(i);
pw.append(prefix).append(prefix).append(printJob.toString());
pw.println();
}
}
pw.flush();
try {
getRemoteInstanceLazy().asBinder().dump(fd, new String[]{prefix});
} catch (TimeoutException te) {
/* ignore */
} catch (RemoteException re) {
/* ignore */
}
}
}
@@ -346,8 +318,8 @@ final class RemotePrintSpooler {
}
}
private void onPrintJobStateChanged(PrintJobId printJobId, int appId) {
mCallbacks.onPrintJobStateChanged(printJobId, appId);
private void onPrintJobStateChanged(PrintJobInfo printJob) {
mCallbacks.onPrintJobStateChanged(printJob);
}
private IPrintSpooler getRemoteInstanceLazy() throws TimeoutException {
@@ -625,12 +597,12 @@ final class RemotePrintSpooler {
}
@Override
public void onPrintJobStateChanged(PrintJobId printJobId, int appId) {
public void onPrintJobStateChanged(PrintJobInfo printJob) {
RemotePrintSpooler spooler = mWeakSpooler.get();
if (spooler != null) {
final long identity = Binder.clearCallingIdentity();
try {
spooler.onPrintJobStateChanged(printJobId, appId);
spooler.onPrintJobStateChanged(printJob);
} finally {
Binder.restoreCallingIdentity(identity);
}

View File

@@ -33,7 +33,6 @@ import android.os.Looper;
import android.os.Message;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.UserManager;
import android.print.IPrintClient;
import android.print.IPrintDocumentAdapter;
import android.print.IPrintJobStateChangeListener;
@@ -52,6 +51,7 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.R;
import com.android.internal.os.BackgroundThread;
@@ -62,6 +62,7 @@ import com.android.server.print.RemotePrintSpooler.PrintSpoolerCallbacks;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@@ -93,8 +94,8 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks {
private final Set<ComponentName> mEnabledServices =
new ArraySet<ComponentName>();
private final CreatedPrintJobTracker mCreatedPrintJobTracker =
new CreatedPrintJobTracker();
private final PrintJobForAppCache mPrintJobForAppCache =
new PrintJobForAppCache();
private final Object mLock;
@@ -155,23 +156,22 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks {
public PrintJobInfo print(String printJobName, final IPrintClient client,
final IPrintDocumentAdapter documentAdapter, PrintAttributes attributes,
int appId) {
PrintJobId printJobId = new PrintJobId();
// Track this job so we can forget it when the creator dies.
if (!mCreatedPrintJobTracker.onPrintJobCreatedLocked(client.asBinder(), printJobId)) {
// Not adding a print job means the client is dead - done.
return null;
}
// Create print job place holder.
final PrintJobInfo printJob = new PrintJobInfo();
printJob.setId(printJobId);
printJob.setId(new PrintJobId());
printJob.setAppId(appId);
printJob.setLabel(printJobName);
printJob.setAttributes(attributes);
printJob.setState(PrintJobInfo.STATE_CREATED);
printJob.setCopies(1);
// Track this job so we can forget it when the creator dies.
if (!mPrintJobForAppCache.onPrintJobCreated(client.asBinder(), appId,
printJob)) {
// Not adding a print job means the client is dead - done.
return null;
}
// Spin the spooler to add the job and show the config UI.
new AsyncTask<Void, Void, Void>() {
@Override
@@ -185,10 +185,40 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks {
}
public List<PrintJobInfo> getPrintJobInfos(int appId) {
return mSpooler.getPrintJobInfos(null, PrintJobInfo.STATE_ANY, appId);
List<PrintJobInfo> cachedPrintJobs = mPrintJobForAppCache.getPrintJobs(appId);
// Note that the print spooler is not storing print jobs that
// are in a terminal state as it is non-trivial to properly update
// the spooler state for when to forget print jobs in terminal state.
// Therefore, we fuse the cached print jobs for running apps (some
// jobs are in a terminal state) with the ones that the print
// spooler knows about (some jobs are being processed).
ArrayMap<PrintJobId, PrintJobInfo> result =
new ArrayMap<PrintJobId, PrintJobInfo>();
// Add the cached print jobs for running apps.
final int cachedPrintJobCount = cachedPrintJobs.size();
for (int i = 0; i < cachedPrintJobCount; i++) {
PrintJobInfo cachedPrintJob = cachedPrintJobs.get(i);
result.put(cachedPrintJob.getId(), cachedPrintJob);
}
// Add everything else the spooler knows about.
List<PrintJobInfo> printJobs = mSpooler.getPrintJobInfos(null,
PrintJobInfo.STATE_ANY, appId);
final int printJobCount = printJobs.size();
for (int i = 0; i < printJobCount; i++) {
PrintJobInfo printJob = printJobs.get(i);
result.put(printJob.getId(), printJob);
}
return new ArrayList<PrintJobInfo>(result.values());
}
public PrintJobInfo getPrintJobInfo(PrintJobId printJobId, int appId) {
PrintJobInfo printJob = mPrintJobForAppCache.getPrintJob(printJobId, appId);
if (printJob != null) {
return printJob;
}
return mSpooler.getPrintJobInfo(printJobId, appId);
}
@@ -398,9 +428,10 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks {
}
@Override
public void onPrintJobStateChanged(PrintJobId printJobId, int appId) {
public void onPrintJobStateChanged(PrintJobInfo printJob) {
mPrintJobForAppCache.onPrintJobStateChanged(printJob);
mHandler.obtainMessage(UserStateHandler.MSG_DISPATCH_PRINT_JOB_STATE_CHANGED,
appId, 0, printJobId).sendToTarget();
printJob.getAppId(), 0, printJob.getId()).sendToTarget();
}
@Override
@@ -525,6 +556,9 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks {
pw.println();
}
pw.append(prefix).append(tab).append("cached print jobs:").println();
mPrintJobForAppCache.dump(pw, prefix + tab + tab);
pw.append(prefix).append(tab).append("discovery mediator:").println();
if (mPrinterDiscoverySession != null) {
mPrinterDiscoverySession.dump(pw, prefix + tab + tab);
@@ -1424,34 +1458,19 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks {
}
}
private final class CreatedPrintJobTracker {
private final ArrayMap<IBinder, List<PrintJobId>> mCreatedPrintJobs =
new ArrayMap<IBinder, List<PrintJobId>>();
private final class PrintJobForAppCache {
private final SparseArray<List<PrintJobInfo>> mPrintJobsForRunningApp =
new SparseArray<List<PrintJobInfo>>();
public boolean onPrintJobCreatedLocked(final IBinder creator, PrintJobId printJobId) {
public boolean onPrintJobCreated(final IBinder creator, final int appId,
PrintJobInfo printJob) {
try {
creator.linkToDeath(new DeathRecipient() {
@Override
public void binderDied() {
creator.unlinkToDeath(this, 0);
UserManager userManager = (UserManager) mContext.getSystemService(
Context.USER_SERVICE);
// If the death is a result of the user being removed, then
// do nothing since the spooler data for this user will be
// wiped and we cannot bind to the spooler at this point.
if (userManager.getUserInfo(mUserId) == null) {
return;
}
List<PrintJobId> printJobIds = null;
synchronized (mLock) {
printJobIds = mCreatedPrintJobs.remove(creator);
if (printJobIds == null) {
return;
}
printJobIds = new ArrayList<PrintJobId>(printJobIds);
}
if (printJobIds != null) {
mSpooler.forgetPrintJobs(printJobIds);
mPrintJobsForRunningApp.remove(appId);
}
}
}, 0);
@@ -1460,14 +1479,93 @@ final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks {
return false;
}
synchronized (mLock) {
List<PrintJobId> printJobIds = mCreatedPrintJobs.get(creator);
if (printJobIds == null) {
printJobIds = new ArrayList<PrintJobId>();
mCreatedPrintJobs.put(creator, printJobIds);
List<PrintJobInfo> printJobsForApp = mPrintJobsForRunningApp.get(appId);
if (printJobsForApp == null) {
printJobsForApp = new ArrayList<PrintJobInfo>();
mPrintJobsForRunningApp.put(appId, printJobsForApp);
}
printJobIds.add(printJobId);
printJobsForApp.add(printJob);
}
return true;
}
public void onPrintJobStateChanged(PrintJobInfo printJob) {
synchronized (mLock) {
List<PrintJobInfo> printJobsForApp = mPrintJobsForRunningApp.get(
printJob.getAppId());
if (printJobsForApp == null) {
return;
}
final int printJobCount = printJobsForApp.size();
for (int i = 0; i < printJobCount; i++) {
PrintJobInfo oldPrintJob = printJobsForApp.get(i);
if (oldPrintJob.getId().equals(printJob.getId())) {
printJobsForApp.set(i, printJob);
}
}
}
}
public PrintJobInfo getPrintJob(PrintJobId printJobId, int appId) {
synchronized (mLock) {
List<PrintJobInfo> printJobsForApp = mPrintJobsForRunningApp.get(appId);
if (printJobsForApp == null) {
return null;
}
final int printJobCount = printJobsForApp.size();
for (int i = 0; i < printJobCount; i++) {
PrintJobInfo printJob = printJobsForApp.get(i);
if (printJob.getId().equals(printJobId)) {
return printJob;
}
}
}
return null;
}
public List<PrintJobInfo> getPrintJobs(int appId) {
synchronized (mLock) {
List<PrintJobInfo> printJobs = null;
if (appId == PrintManager.APP_ID_ANY) {
final int bucketCount = mPrintJobsForRunningApp.size();
for (int i = 0; i < bucketCount; i++) {
List<PrintJobInfo> bucket = mPrintJobsForRunningApp.valueAt(i);
if (printJobs == null) {
printJobs = new ArrayList<PrintJobInfo>();
}
printJobs.addAll(bucket);
}
} else {
List<PrintJobInfo> bucket = mPrintJobsForRunningApp.get(appId);
if (bucket != null) {
if (printJobs == null) {
printJobs = new ArrayList<PrintJobInfo>();
}
printJobs.addAll(bucket);
}
}
if (printJobs != null) {
return printJobs;
}
return Collections.emptyList();
}
}
public void dump(PrintWriter pw, String prefix) {
synchronized (mLock) {
String tab = " ";
final int bucketCount = mPrintJobsForRunningApp.size();
for (int i = 0; i < bucketCount; i++) {
final int appId = mPrintJobsForRunningApp.keyAt(i);
pw.append(prefix).append("appId=" + appId).append(':').println();
List<PrintJobInfo> bucket = mPrintJobsForRunningApp.valueAt(i);
final int printJobCount = bucket.size();
for (int j = 0; j < printJobCount; j++) {
PrintJobInfo printJob = bucket.get(j);
pw.append(prefix).append(tab).append(printJob.toString()).println();
}
}
}
}
}
}