Files
frameworks_base/services/java/com/android/server/print/RemotePrintSpooler.java
Svetoslav Ganov dd68da2741 Print job files and print job records not always cleaned up.
1. We want the files for a print job to be removed as early as possible
   typically because the print job was cancelled, completed, the app
   or the spooler crashed during print job construction. We were keeping
   around in the spooler and hence to disc infos for jobs that are in
   final state since the app that created them may hold a reference to
   a local print job objec whose info it can access to get the latest
   print job state potentially after the job reached final state. The
   issue was that we were persisting to disc created print jobs which
   were during construction which requires careful handling for the
   various cases above. This is tricky and error prone.

   We used to tell the spooler to forget the print jobs infos when the
   app that created them died. The implementation to forget a print
   job was not careful and was nuking currently running print jobs in
   addition to the ones in a terminal state. Further, if the app dies
   before a print job is completed we were left with a stale print
   job in the spooler since we missed the signal to forget it (assuming
   we forget only inactive jobs). These issues suggest that the approach
   is problematic.

   Now we have a cache of print job infos for the jobs an app created.
   This cache is updated when the state of a print jobs changes using
   the new print job state observation code. When the app dies we
   remove the cached jobs for that app. Now if the app calls to get
   the print jobs it gets the cached ones, i.e. the print jobs it
   created during its lifetime, plus the print jobs that are still
   active fetched from the spooler. Note that transient state cannot
   be kept in the spooler since we unbind from it if there is no
   work and it may get killed.

2. Improved the print sub-system logging code to show the cached
   print job infos for apps and also dump the print job PDF file
   names.

bug:10958357

Change-Id: I6f7c1968b6b7ba5be182a10df044ff7ea1fc3a61
2013-09-27 17:26:44 -07:00

613 lines
21 KiB
Java

/*
* 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 com.android.server.print;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Binder;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.print.IPrintClient;
import android.print.IPrintDocumentAdapter;
import android.print.IPrintSpooler;
import android.print.IPrintSpoolerCallbacks;
import android.print.IPrintSpoolerClient;
import android.print.PrintJobId;
import android.print.PrintJobInfo;
import android.util.Slog;
import android.util.TimedRemoteCaller;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.List;
import java.util.concurrent.TimeoutException;
import libcore.io.IoUtils;
/**
* This represents the remote print spooler as a local object to the
* PrintManagerSerivce. It is responsible to connecting to the remote
* spooler if needed, to make the timed remote calls, to handle
* remote exceptions, and to bind/unbind to the remote instance as
* needed.
*/
final class RemotePrintSpooler {
private static final String LOG_TAG = "RemotePrintSpooler";
private static final boolean DEBUG = false;
private static final long BIND_SPOOLER_SERVICE_TIMEOUT = 10000;
private final Object mLock = new Object();
private final GetPrintJobInfosCaller mGetPrintJobInfosCaller = new GetPrintJobInfosCaller();
private final GetPrintJobInfoCaller mGetPrintJobInfoCaller = new GetPrintJobInfoCaller();
private final SetPrintJobStateCaller mSetPrintJobStatusCaller = new SetPrintJobStateCaller();
private final SetPrintJobTagCaller mSetPrintJobTagCaller = new SetPrintJobTagCaller();
private final ServiceConnection mServiceConnection = new MyServiceConnection();
private final Context mContext;
private final UserHandle mUserHandle;
private final PrintSpoolerClient mClient;
private final Intent mIntent;
private final PrintSpoolerCallbacks mCallbacks;
private IPrintSpooler mRemoteInstance;
private boolean mDestroyed;
private boolean mCanUnbind;
public static interface PrintSpoolerCallbacks {
public void onPrintJobQueued(PrintJobInfo printJob);
public void onAllPrintJobsForServiceHandled(ComponentName printService);
public void onPrintJobStateChanged(PrintJobInfo printJob);
}
public RemotePrintSpooler(Context context, int userId,
PrintSpoolerCallbacks callbacks) {
mContext = context;
mUserHandle = new UserHandle(userId);
mCallbacks = callbacks;
mClient = new PrintSpoolerClient(this);
mIntent = new Intent();
mIntent.setComponent(new ComponentName("com.android.printspooler",
"com.android.printspooler.PrintSpoolerService"));
}
public final List<PrintJobInfo> getPrintJobInfos(ComponentName componentName, int state,
int appId) {
throwIfCalledOnMainThread();
synchronized (mLock) {
throwIfDestroyedLocked();
mCanUnbind = false;
}
try {
return mGetPrintJobInfosCaller.getPrintJobInfos(getRemoteInstanceLazy(),
componentName, state, appId);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error getting print jobs.", re);
} catch (TimeoutException te) {
Slog.e(LOG_TAG, "Error getting print jobs.", te);
} finally {
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] getPrintJobInfos()");
}
synchronized (mLock) {
mCanUnbind = true;
mLock.notifyAll();
}
}
return null;
}
public final void createPrintJob(PrintJobInfo printJob, IPrintClient client,
IPrintDocumentAdapter documentAdapter) {
throwIfCalledOnMainThread();
synchronized (mLock) {
throwIfDestroyedLocked();
mCanUnbind = false;
}
try {
getRemoteInstanceLazy().createPrintJob(printJob, client, documentAdapter);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error creating print job.", re);
} catch (TimeoutException te) {
Slog.e(LOG_TAG, "Error creating print job.", te);
} finally {
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] createPrintJob()");
}
synchronized (mLock) {
mCanUnbind = true;
mLock.notifyAll();
}
}
}
public final void writePrintJobData(ParcelFileDescriptor fd, PrintJobId printJobId) {
throwIfCalledOnMainThread();
synchronized (mLock) {
throwIfDestroyedLocked();
mCanUnbind = false;
}
try {
getRemoteInstanceLazy().writePrintJobData(fd, printJobId);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error writing print job data.", re);
} catch (TimeoutException te) {
Slog.e(LOG_TAG, "Error writing print job data.", te);
} finally {
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] writePrintJobData()");
}
// We passed the file descriptor across and now the other
// side is responsible to close it, so close the local copy.
IoUtils.closeQuietly(fd);
synchronized (mLock) {
mCanUnbind = true;
mLock.notifyAll();
}
}
}
public final PrintJobInfo getPrintJobInfo(PrintJobId printJobId, int appId) {
throwIfCalledOnMainThread();
synchronized (mLock) {
throwIfDestroyedLocked();
mCanUnbind = false;
}
try {
return mGetPrintJobInfoCaller.getPrintJobInfo(getRemoteInstanceLazy(),
printJobId, appId);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error getting print job info.", re);
} catch (TimeoutException te) {
Slog.e(LOG_TAG, "Error getting print job info.", te);
} finally {
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] getPrintJobInfo()");
}
synchronized (mLock) {
mCanUnbind = true;
mLock.notifyAll();
}
}
return null;
}
public final boolean setPrintJobState(PrintJobId printJobId, int state, String error) {
throwIfCalledOnMainThread();
synchronized (mLock) {
throwIfDestroyedLocked();
mCanUnbind = false;
}
try {
return mSetPrintJobStatusCaller.setPrintJobState(getRemoteInstanceLazy(),
printJobId, state, error);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error setting print job state.", re);
} catch (TimeoutException te) {
Slog.e(LOG_TAG, "Error setting print job state.", te);
} finally {
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] setPrintJobState()");
}
synchronized (mLock) {
mCanUnbind = true;
mLock.notifyAll();
}
}
return false;
}
public final boolean setPrintJobTag(PrintJobId printJobId, String tag) {
throwIfCalledOnMainThread();
synchronized (mLock) {
throwIfDestroyedLocked();
mCanUnbind = false;
}
try {
return mSetPrintJobTagCaller.setPrintJobTag(getRemoteInstanceLazy(),
printJobId, tag);
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error setting print job tag.", re);
} catch (TimeoutException te) {
Slog.e(LOG_TAG, "Error setting print job tag.", te);
} finally {
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] setPrintJobTag()");
}
synchronized (mLock) {
mCanUnbind = true;
mLock.notifyAll();
}
}
return false;
}
public final void removeObsoletePrintJobs() {
throwIfCalledOnMainThread();
synchronized (mLock) {
throwIfDestroyedLocked();
mCanUnbind = false;
}
try {
getRemoteInstanceLazy().removeObsoletePrintJobs();
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error removing obsolete print jobs .", re);
} catch (TimeoutException te) {
Slog.e(LOG_TAG, "Error removing obsolete print jobs .", te);
} finally {
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier()
+ "] removeObsoletePrintJobs()");
}
synchronized (mLock) {
mCanUnbind = true;
mLock.notifyAll();
}
}
}
public final void destroy() {
throwIfCalledOnMainThread();
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] destroy()");
}
synchronized (mLock) {
throwIfDestroyedLocked();
unbindLocked();
mDestroyed = true;
mCanUnbind = false;
}
}
public void dump(FileDescriptor fd, PrintWriter pw, String prefix) {
synchronized (mLock) {
pw.append(prefix).append("destroyed=")
.append(String.valueOf(mDestroyed)).println();
pw.append(prefix).append("bound=")
.append((mRemoteInstance != null) ? "true" : "false").println();
pw.flush();
try {
getRemoteInstanceLazy().asBinder().dump(fd, new String[]{prefix});
} catch (TimeoutException te) {
/* ignore */
} catch (RemoteException re) {
/* ignore */
}
}
}
private void onAllPrintJobsHandled() {
synchronized (mLock) {
throwIfDestroyedLocked();
unbindLocked();
}
}
private void onPrintJobStateChanged(PrintJobInfo printJob) {
mCallbacks.onPrintJobStateChanged(printJob);
}
private IPrintSpooler getRemoteInstanceLazy() throws TimeoutException {
synchronized (mLock) {
if (mRemoteInstance != null) {
return mRemoteInstance;
}
bindLocked();
return mRemoteInstance;
}
}
private void bindLocked() throws TimeoutException {
if (mRemoteInstance != null) {
return;
}
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] bindLocked()");
}
mContext.bindServiceAsUser(mIntent, mServiceConnection,
Context.BIND_AUTO_CREATE, mUserHandle);
final long startMillis = SystemClock.uptimeMillis();
while (true) {
if (mRemoteInstance != null) {
break;
}
final long elapsedMillis = SystemClock.uptimeMillis() - startMillis;
final long remainingMillis = BIND_SPOOLER_SERVICE_TIMEOUT - elapsedMillis;
if (remainingMillis <= 0) {
throw new TimeoutException("Cannot get spooler!");
}
try {
mLock.wait(remainingMillis);
} catch (InterruptedException ie) {
/* ignore */
}
}
mCanUnbind = true;
mLock.notifyAll();
}
private void unbindLocked() {
if (mRemoteInstance == null) {
return;
}
while (true) {
if (mCanUnbind) {
if (DEBUG) {
Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] unbindLocked()");
}
clearClientLocked();
mRemoteInstance = null;
mContext.unbindService(mServiceConnection);
return;
}
try {
mLock.wait();
} catch (InterruptedException ie) {
/* ignore */
}
}
}
private void setClientLocked() {
try {
mRemoteInstance.setClient(mClient);
} catch (RemoteException re) {
Slog.d(LOG_TAG, "Error setting print spooler client", re);
}
}
private void clearClientLocked() {
try {
mRemoteInstance.setClient(null);
} catch (RemoteException re) {
Slog.d(LOG_TAG, "Error clearing print spooler client", re);
}
}
private void throwIfDestroyedLocked() {
if (mDestroyed) {
throw new IllegalStateException("Cannot interact with a destroyed instance.");
}
}
private void throwIfCalledOnMainThread() {
if (Thread.currentThread() == mContext.getMainLooper().getThread()) {
throw new RuntimeException("Cannot invoke on the main thread");
}
}
private final class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mLock) {
mRemoteInstance = IPrintSpooler.Stub.asInterface(service);
setClientLocked();
mLock.notifyAll();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
synchronized (mLock) {
clearClientLocked();
mRemoteInstance = null;
}
}
}
private static final class GetPrintJobInfosCaller
extends TimedRemoteCaller<List<PrintJobInfo>> {
private final IPrintSpoolerCallbacks mCallback;
public GetPrintJobInfosCaller() {
super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
mCallback = new BasePrintSpoolerServiceCallbacks() {
@Override
public void onGetPrintJobInfosResult(List<PrintJobInfo> printJobs, int sequence) {
onRemoteMethodResult(printJobs, sequence);
}
};
}
public List<PrintJobInfo> getPrintJobInfos(IPrintSpooler target,
ComponentName componentName, int state, int appId)
throws RemoteException, TimeoutException {
final int sequence = onBeforeRemoteCall();
target.getPrintJobInfos(mCallback, componentName, state, appId, sequence);
return getResultTimed(sequence);
}
}
private static final class GetPrintJobInfoCaller extends TimedRemoteCaller<PrintJobInfo> {
private final IPrintSpoolerCallbacks mCallback;
public GetPrintJobInfoCaller() {
super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
mCallback = new BasePrintSpoolerServiceCallbacks() {
@Override
public void onGetPrintJobInfoResult(PrintJobInfo printJob, int sequence) {
onRemoteMethodResult(printJob, sequence);
}
};
}
public PrintJobInfo getPrintJobInfo(IPrintSpooler target, PrintJobId printJobId,
int appId) throws RemoteException, TimeoutException {
final int sequence = onBeforeRemoteCall();
target.getPrintJobInfo(printJobId, mCallback, appId, sequence);
return getResultTimed(sequence);
}
}
private static final class SetPrintJobStateCaller extends TimedRemoteCaller<Boolean> {
private final IPrintSpoolerCallbacks mCallback;
public SetPrintJobStateCaller() {
super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
mCallback = new BasePrintSpoolerServiceCallbacks() {
@Override
public void onSetPrintJobStateResult(boolean success, int sequence) {
onRemoteMethodResult(success, sequence);
}
};
}
public boolean setPrintJobState(IPrintSpooler target, PrintJobId printJobId,
int status, String error) throws RemoteException, TimeoutException {
final int sequence = onBeforeRemoteCall();
target.setPrintJobState(printJobId, status, error, mCallback, sequence);
return getResultTimed(sequence);
}
}
private static final class SetPrintJobTagCaller extends TimedRemoteCaller<Boolean> {
private final IPrintSpoolerCallbacks mCallback;
public SetPrintJobTagCaller() {
super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
mCallback = new BasePrintSpoolerServiceCallbacks() {
@Override
public void onSetPrintJobTagResult(boolean success, int sequence) {
onRemoteMethodResult(success, sequence);
}
};
}
public boolean setPrintJobTag(IPrintSpooler target, PrintJobId printJobId,
String tag) throws RemoteException, TimeoutException {
final int sequence = onBeforeRemoteCall();
target.setPrintJobTag(printJobId, tag, mCallback, sequence);
return getResultTimed(sequence);
}
}
private static abstract class BasePrintSpoolerServiceCallbacks
extends IPrintSpoolerCallbacks.Stub {
@Override
public void onGetPrintJobInfosResult(List<PrintJobInfo> printJobIds, int sequence) {
/* do nothing */
}
@Override
public void onGetPrintJobInfoResult(PrintJobInfo printJob, int sequence) {
/* do nothing */
}
@Override
public void onCancelPrintJobResult(boolean canceled, int sequence) {
/* do nothing */
}
@Override
public void onSetPrintJobStateResult(boolean success, int sequece) {
/* do nothing */
}
@Override
public void onSetPrintJobTagResult(boolean success, int sequence) {
/* do nothing */
}
}
private static final class PrintSpoolerClient extends IPrintSpoolerClient.Stub {
private final WeakReference<RemotePrintSpooler> mWeakSpooler;
public PrintSpoolerClient(RemotePrintSpooler spooler) {
mWeakSpooler = new WeakReference<RemotePrintSpooler>(spooler);
}
@Override
public void onPrintJobQueued(PrintJobInfo printJob) {
RemotePrintSpooler spooler = mWeakSpooler.get();
if (spooler != null) {
final long identity = Binder.clearCallingIdentity();
try {
spooler.mCallbacks.onPrintJobQueued(printJob);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
@Override
public void onAllPrintJobsForServiceHandled(ComponentName printService) {
RemotePrintSpooler spooler = mWeakSpooler.get();
if (spooler != null) {
final long identity = Binder.clearCallingIdentity();
try {
spooler.mCallbacks.onAllPrintJobsForServiceHandled(printService);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
@Override
public void onAllPrintJobsHandled() {
RemotePrintSpooler spooler = mWeakSpooler.get();
if (spooler != null) {
final long identity = Binder.clearCallingIdentity();
try {
spooler.onAllPrintJobsHandled();
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
@Override
public void onPrintJobStateChanged(PrintJobInfo printJob) {
RemotePrintSpooler spooler = mWeakSpooler.get();
if (spooler != null) {
final long identity = Binder.clearCallingIdentity();
try {
spooler.onPrintJobStateChanged(printJob);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
}
}