private long mChangingStagger = 0;
/**
+ * Static interpolators - these are stateless and can be shared across the instances
+ */
+ private static TimeInterpolator ACCEL_DECEL_INTERPOLATOR =
+ new AccelerateDecelerateInterpolator();
+ private static TimeInterpolator DECEL_INTERPOLATOR = new DecelerateInterpolator();
+ private static TimeInterpolator sAppearingInterpolator = ACCEL_DECEL_INTERPOLATOR;
+ private static TimeInterpolator sDisappearingInterpolator = ACCEL_DECEL_INTERPOLATOR;
+ private static TimeInterpolator sChangingAppearingInterpolator = DECEL_INTERPOLATOR;
+ private static TimeInterpolator sChangingDisappearingInterpolator = DECEL_INTERPOLATOR;
+ private static TimeInterpolator sChangingInterpolator = DECEL_INTERPOLATOR;
+
+ /**
* The default interpolators used for the animations
*/
- private TimeInterpolator mAppearingInterpolator = new AccelerateDecelerateInterpolator();
- private TimeInterpolator mDisappearingInterpolator = new AccelerateDecelerateInterpolator();
- private TimeInterpolator mChangingAppearingInterpolator = new DecelerateInterpolator();
- private TimeInterpolator mChangingDisappearingInterpolator = new DecelerateInterpolator();
- private TimeInterpolator mChangingInterpolator = new DecelerateInterpolator();
+ private TimeInterpolator mAppearingInterpolator = sAppearingInterpolator;
+ private TimeInterpolator mDisappearingInterpolator = sDisappearingInterpolator;
+ private TimeInterpolator mChangingAppearingInterpolator = sChangingAppearingInterpolator;
+ private TimeInterpolator mChangingDisappearingInterpolator = sChangingDisappearingInterpolator;
+ private TimeInterpolator mChangingInterpolator = sChangingInterpolator;
/**
* These hashmaps are used to store the animations that are currently running as part of
case APPEARING:
startDelay = mChangingAppearingDelay + staggerDelay;
staggerDelay += mChangingAppearingStagger;
+ if (mChangingAppearingInterpolator != sChangingAppearingInterpolator) {
+ anim.setInterpolator(mChangingAppearingInterpolator);
+ }
break;
case DISAPPEARING:
startDelay = mChangingDisappearingDelay + staggerDelay;
staggerDelay += mChangingDisappearingStagger;
+ if (mChangingDisappearingInterpolator !=
+ sChangingDisappearingInterpolator) {
+ anim.setInterpolator(mChangingDisappearingInterpolator);
+ }
break;
case CHANGING:
startDelay = mChangingDelay + staggerDelay;
staggerDelay += mChangingStagger;
+ if (mChangingInterpolator != sChangingInterpolator) {
+ anim.setInterpolator(mChangingInterpolator);
+ }
break;
}
anim.setStartDelay(startDelay);
anim.setTarget(child);
anim.setStartDelay(mAppearingDelay);
anim.setDuration(mAppearingDuration);
+ if (mAppearingInterpolator != sAppearingInterpolator) {
+ anim.setInterpolator(mAppearingInterpolator);
+ }
if (anim instanceof ObjectAnimator) {
((ObjectAnimator) anim).setCurrentPlayTime(0);
}
Animator anim = mDisappearingAnim.clone();
anim.setStartDelay(mDisappearingDelay);
anim.setDuration(mDisappearingDuration);
+ if (mDisappearingInterpolator != sDisappearingInterpolator) {
+ anim.setInterpolator(mDisappearingInterpolator);
+ }
anim.setTarget(child);
final float preAnimAlpha = child.getAlpha();
anim.addListener(new AnimatorListenerAdapter() {
int sequence);
void writePrintJobData(in ParcelFileDescriptor fd, in PrintJobId printJobId);
void setClient(IPrintSpoolerClient client);
+ void setPrintJobCancelling(in PrintJobId printJobId, boolean cancelling);
}
/** Information about the printed document. */
private PrintDocumentInfo mDocumentInfo;
+ /** Whether we are trying to cancel this print job. */
+ private boolean mCanceling;
+
/** @hide*/
public PrintJobInfo() {
/* do nothing */
mPageRanges = other.mPageRanges;
mAttributes = other.mAttributes;
mDocumentInfo = other.mDocumentInfo;
+ mCanceling = other.mCanceling;
}
private PrintJobInfo(Parcel parcel) {
if (parcel.readInt() == 1) {
mDocumentInfo = PrintDocumentInfo.CREATOR.createFromParcel(parcel);
}
+ mCanceling = (parcel.readInt() == 1);
}
/**
mDocumentInfo = info;
}
+ /**
+ * Gets whether this print is being cancelled.
+ *
+ * @return True if the print job is being cancelled.
+ *
+ * @hide
+ */
+ public boolean isCancelling() {
+ return mCanceling;
+ }
+
+ /**
+ * Sets whether this print is being cancelled.
+ *
+ * @param cancelling True if the print job is being cancelled.
+ *
+ * @hide
+ */
+ public void setCancelling(boolean cancelling) {
+ mCanceling = cancelling;
+ }
+
@Override
public int describeContents() {
return 0;
} else {
parcel.writeInt(0);
}
+ parcel.writeInt(mCanceling ? 1 : 0);
}
@Override
? mAttributes.toString() : null));
builder.append(", documentInfo: " + (mDocumentInfo != null
? mDocumentInfo.toString() : null));
+ builder.append(", cancelling: " + mCanceling);
builder.append(", pages: " + (mPageRanges != null
? Arrays.toString(mPageRanges) : null));
builder.append("}");
* <p>
* To obtain a handle to the print manager do the following:
* </p>
+ *
* <pre>
* PrintManager printManager =
* (PrintManager) context.getSystemService(Context.PRINT_SERVICE);
private static final boolean DEBUG = false;
+ private static final int MSG_START_PRINT_JOB_CONFIG_ACTIVITY = 1;
+ private static final int MSG_NOTIFY_PRINT_JOB_STATE_CHANGED = 2;
+
/** @hide */
public static final int APP_ID_ANY = -2;
/**
* Callback notifying that a print job state changed.
- *
+ *
* @param printJobId The print job id.
*/
- public void onPrintJobsStateChanged(PrintJobId printJobId);
+ public void onPrintJobStateChanged(PrintJobId printJobId);
}
/**
* Creates a new instance.
- *
+ *
* @param context The current context in which to operate.
* @param service The backing system service.
- *
* @hide
*/
public PrintManager(Context context, IPrintManager service, int userId, int appId) {
mHandler = new Handler(context.getMainLooper(), null, false) {
@Override
public void handleMessage(Message message) {
- SomeArgs args = (SomeArgs) message.obj;
- Context context = (Context) args.arg1;
- IntentSender intent = (IntentSender) args.arg2;
- args.recycle();
- try {
- context.startIntentSender(intent, null, 0, 0, 0);
- } catch (SendIntentException sie) {
- Log.e(LOG_TAG, "Couldn't start print job config activity.", sie);
+ switch (message.what) {
+ case MSG_START_PRINT_JOB_CONFIG_ACTIVITY: {
+ SomeArgs args = (SomeArgs) message.obj;
+ Context context = (Context) args.arg1;
+ IntentSender intent = (IntentSender) args.arg2;
+ args.recycle();
+ try {
+ context.startIntentSender(intent, null, 0, 0, 0);
+ } catch (SendIntentException sie) {
+ Log.e(LOG_TAG, "Couldn't start print job config activity.", sie);
+ }
+ }
+ break;
+
+ case MSG_NOTIFY_PRINT_JOB_STATE_CHANGED: {
+ SomeArgs args = (SomeArgs) message.obj;
+ PrintJobStateChangeListener listener =
+ (PrintJobStateChangeListener) args.arg1;
+ PrintJobId printJobId = (PrintJobId) args.arg2;
+ args.recycle();
+ listener.onPrintJobStateChanged(printJobId);
+ }
+ break;
}
}
};
/**
* Creates an instance that can access all print jobs.
- *
+ *
* @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.
+ * @return An instance if the caller has the permission to access all print
+ * jobs, null otherwise.
* @hide
*/
public PrintManager getGlobalPrintManagerForUser(int userId) {
/**
* Adds a listener for observing the state of print jobs.
- *
+ *
* @param listener The listener to add.
- *
* @hide
*/
public void addPrintJobStateChangeListener(PrintJobStateChangeListener listener) {
PrintJobStateChangeListenerWrapper>();
}
PrintJobStateChangeListenerWrapper wrappedListener =
- new PrintJobStateChangeListenerWrapper(listener);
+ new PrintJobStateChangeListenerWrapper(listener, mHandler);
try {
mService.addPrintJobStateChangeListener(wrappedListener, mAppId, mUserId);
mPrintJobStateChangeListeners.put(listener, wrappedListener);
/**
* Removes a listener for observing the state of print jobs.
- *
+ *
* @param listener The listener to remove.
- *
* @hide
*/
public void removePrintJobStateChangeListener(PrintJobStateChangeListener listener) {
/**
* Gets a print job given its id.
- *
+ *
* @return The print job list.
- *
* @see PrintJob
- *
* @hide
*/
public PrintJob getPrintJob(PrintJobId printJobId) {
/**
* Gets the print jobs for this application.
- *
+ *
* @return The print job list.
- *
* @see PrintJob
*/
public List<PrintJob> getPrintJobs() {
}
/**
- * Creates a print job for printing a {@link PrintDocumentAdapter} with default print
- * attributes.
- *
+ * Creates a print job for printing a {@link PrintDocumentAdapter} with
+ * default print attributes.
+ *
* @param printJobName A name for the new print job.
* @param documentAdapter An adapter that emits the document to print.
* @param attributes The default print job attributes.
/**
* Gets the list of enabled print services.
- *
+ *
* @return The enabled service list or an empty list.
- *
* @hide
*/
public List<PrintServiceInfo> getEnabledPrintServices() {
/**
* Gets the list of installed print services.
- *
+ *
* @return The installed service list or an empty list.
- *
* @hide
*/
public List<PrintServiceInfo> getInstalledPrintServices() {
SomeArgs args = SomeArgs.obtain();
args.arg1 = manager.mContext;
args.arg2 = intent;
- manager.mHandler.obtainMessage(0, args).sendToTarget();
+ manager.mHandler.obtainMessage(MSG_START_PRINT_JOB_CONFIG_ACTIVITY,
+ args).sendToTarget();
}
}
}
private CancellationSignal mLayoutOrWriteCancellation;
- private PrintDocumentAdapter mDocumentAdapter; // Strong reference OK - cleared in finish()
+ private PrintDocumentAdapter mDocumentAdapter; // Strong reference OK -
+ // cleared in finish()
private Handler mHandler; // Strong reference OK - cleared in finish()
switch (message.what) {
case MSG_START: {
mDocumentAdapter.onStart();
- } break;
+ }
+ break;
case MSG_LAYOUT: {
final CancellationSignal cancellation;
new MyLayoutResultCallback(layoutSpec.callback,
layoutSpec.sequence), layoutSpec.metadata);
}
- } break;
+ }
+ break;
case MSG_WRITE: {
final CancellationSignal cancellation;
final WriteSpec writeSpec;
synchronized (mLock) {
- writeSpec= mLastWriteSpec;
+ writeSpec = mLastWriteSpec;
mLastWriteSpec = null;
cancellation = new CancellationSignal();
mLayoutOrWriteCancellation = cancellation;
cancellation, new MyWriteResultCallback(writeSpec.callback,
writeSpec.fd, writeSpec.sequence));
}
- } break;
+ }
+ break;
case MSG_FINISH: {
if (DEBUG) {
}
mDocumentAdapter.onFinish();
doFinish();
- } break;
+ }
+ break;
default: {
throw new IllegalArgumentException("Unknown message: "
private static final class PrintJobStateChangeListenerWrapper extends
IPrintJobStateChangeListener.Stub {
private final WeakReference<PrintJobStateChangeListener> mWeakListener;
+ private final WeakReference<Handler> mWeakHandler;
- public PrintJobStateChangeListenerWrapper(PrintJobStateChangeListener listener) {
+ public PrintJobStateChangeListenerWrapper(PrintJobStateChangeListener listener,
+ Handler handler) {
mWeakListener = new WeakReference<PrintJobStateChangeListener>(listener);
+ mWeakHandler = new WeakReference<Handler>(handler);
}
@Override
public void onPrintJobStateChanged(PrintJobId printJobId) {
+ Handler handler = mWeakHandler.get();
PrintJobStateChangeListener listener = mWeakListener.get();
- if (listener != null) {
- listener.onPrintJobsStateChanged(printJobId);
+ if (handler != null && listener != null) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = listener;
+ args.arg2 = printJobId;
+ handler.obtainMessage(MSG_NOTIFY_PRINT_JOB_STATE_CHANGED,
+ args).sendToTarget();
}
}
}
+
}
if (ENABLED) {
final int eventType = event.getEventType();
switch (eventType) {
+ case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END:
case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER:
case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT: {
+ // If the active window changes, clear the cache.
final int windowId = event.getWindowId();
- // If a new window, we clear the cache.
if (mWindowId != windowId) {
mWindowId = windowId;
clear();
package com.android.internal.inputmethod;
import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
import android.widget.LinearLayout;
public class InputMethodRoot extends LinearLayout {
- private final Rect mGuardRect = new Rect();
- private final Paint mGuardPaint = new Paint();
+
+ private View mNavigationGuard;
public InputMethodRoot(Context context) {
this(context, null);
public InputMethodRoot(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs);
- setWillNotDraw(false);
- mGuardPaint.setColor(context.getResources()
- .getColor(com.android.internal.R.color.input_method_navigation_guard));
}
@Override
protected boolean fitSystemWindows(Rect insets) {
- setPadding(0, 0, 0, insets.bottom);
+ if (mNavigationGuard == null) {
+ mNavigationGuard = findViewById(com.android.internal.R.id.navigationGuard);
+ }
+ if (mNavigationGuard == null) {
+ return super.fitSystemWindows(insets);
+ }
+ ViewGroup.LayoutParams lp = mNavigationGuard.getLayoutParams();
+ lp.height = insets.bottom;
+ mNavigationGuard.setLayoutParams(lp);
return true;
}
-
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
-
- // draw navigation bar guard
- final int w = getMeasuredWidth();
- final int h = getMeasuredHeight();
- mGuardRect.set(0, h - getPaddingBottom(), w, h);
- canvas.drawRect(mGuardRect, mGuardPaint);
- }
}
android:layout_height="wrap_content"
android:visibility="gone">
</FrameLayout>
+
+ <View android:id="@+id/navigationGuard"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:background="@+color/input_method_navigation_guard"/>
</com.android.internal.inputmethod.InputMethodRoot>
<java-symbol type="id" name="month" />
<java-symbol type="id" name="month_name" />
<java-symbol type="id" name="name" />
+ <java-symbol type="id" name="navigationGuard" />
<java-symbol type="id" name="next" />
<java-symbol type="id" name="next_button" />
<java-symbol type="id" name="new_app_action" />
<!-- Template for the notificaiton label for a blocked print job. [CHAR LIMIT=25] -->
<string name="blocked_notification_title_template">Printer blocked <xliff:g id="print_job_name" example="foo.jpg">%1$s</xliff:g></string>
+ <!-- Template for the notificaiton label for a composite (multiple items) print jobs notification. [CHAR LIMIT=25] -->
+ <plurals name="composite_notification_title_template">
+ <item quantity="one"><xliff:g id="print_job_name" example="foo.jpg">%1$d</xliff:g> print job</item>
+ <item quantity="other"><xliff:g id="print_job_name" example="foo.jpg">%1$d</xliff:g> print jobs</item>
+ </plurals>
+
<!-- Label for the notification button for cancelling a print job. [CHAR LIMIT=25] -->
<string name="cancel">Cancel</string>
package com.android.printspooler;
import android.app.Notification;
+import android.app.Notification.InboxStyle;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.graphics.drawable.BitmapDrawable;
+import android.net.Uri;
import android.os.AsyncTask;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.provider.Settings;
import android.util.Log;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* This class is responsible for updating the print notifications
* based on print job state transitions.
mContext.getSystemService(Context.NOTIFICATION_SERVICE);
}
- public void onPrintJobStateChanged(PrintJobInfo printJob) {
- if (DEBUG) {
- Log.i(LOG_TAG, "onPrintJobStateChanged() printJobId: "
- + printJob.getId().flattenToString() + " state:"
- + PrintJobInfo.stateToString(printJob.getState()));
+ public void onUpdateNotifications(List<PrintJobInfo> printJobs) {
+ List<PrintJobInfo> notifyPrintJobs = new ArrayList<PrintJobInfo>();
+
+ final int printJobCount = printJobs.size();
+ for (int i = 0; i < printJobCount; i++) {
+ PrintJobInfo printJob = printJobs.get(i);
+ if (shouldNotifyForState(printJob.getState())) {
+ notifyPrintJobs.add(printJob);
+ }
}
- switch (printJob.getState()) {
- case PrintJobInfo.STATE_QUEUED:
- case PrintJobInfo.STATE_STARTED: {
- createPrintingNotification(printJob);
- } break;
+ updateNotification(notifyPrintJobs);
+ }
+
+ private void updateNotification(List<PrintJobInfo> printJobs) {
+ if (printJobs.size() <= 0) {
+ removeNotification();
+ } else if (printJobs.size() == 1) {
+ createSimpleNotification(printJobs.get(0));
+ } else {
+ createStackedNotification(printJobs);
+ }
+ }
+
+ private void createSimpleNotification(PrintJobInfo printJob) {
+ switch (printJob.getState()) {
case PrintJobInfo.STATE_FAILED: {
createFailedNotification(printJob);
} break;
- case PrintJobInfo.STATE_COMPLETED:
- case PrintJobInfo.STATE_CANCELED: {
- removeNotification(printJob.getId());
+ case PrintJobInfo.STATE_BLOCKED: {
+ if (!printJob.isCancelling()) {
+ createBlockedNotification(printJob);
+ } else {
+ createCancellingNotification(printJob);
+ }
} break;
- case PrintJobInfo.STATE_BLOCKED: {
- createBlockedNotification(printJob);
+ default: {
+ if (!printJob.isCancelling()) {
+ createPrintingNotification(printJob);
+ } else {
+ createCancellingNotification(printJob);
+ }
} break;
}
}
private void createPrintingNotification(PrintJobInfo printJob) {
Notification.Builder builder = new Notification.Builder(mContext)
.setContentIntent(createContentIntent(printJob.getId()))
- .setSmallIcon(com.android.internal.R.drawable.ic_print)
- .setContentTitle(mContext.getString(R.string.printing_notification_title_template,
- printJob.getLabel()))
+ .setSmallIcon(computeNotificationIcon(printJob))
+ .setContentTitle(computeNotificationTitle(printJob))
.addAction(R.drawable.stat_notify_cancelling, mContext.getString(R.string.cancel),
createCancelIntent(printJob))
.setContentText(printJob.getPrinterName())
.setWhen(System.currentTimeMillis())
.setOngoing(true)
.setShowWhen(true);
- mNotificationManager.notify(printJob.getId().flattenToString(), 0, builder.build());
+ mNotificationManager.notify(0, builder.build());
}
private void createFailedNotification(PrintJobInfo printJob) {
Notification.Builder builder = new Notification.Builder(mContext)
.setContentIntent(createContentIntent(printJob.getId()))
- .setSmallIcon(com.android.internal.R.drawable.ic_print_error)
- .setContentTitle(mContext.getString(R.string.failed_notification_title_template,
- printJob.getLabel()))
+ .setSmallIcon(computeNotificationIcon(printJob))
+ .setContentTitle(computeNotificationTitle(printJob))
.addAction(R.drawable.stat_notify_cancelling, mContext.getString(R.string.cancel),
createCancelIntent(printJob))
.addAction(R.drawable.ic_restart, mContext.getString(R.string.restart),
.setWhen(System.currentTimeMillis())
.setOngoing(true)
.setShowWhen(true);
- mNotificationManager.notify(printJob.getId().flattenToString(), 0, builder.build());
+ mNotificationManager.notify(0, builder.build());
}
private void createBlockedNotification(PrintJobInfo printJob) {
Notification.Builder builder = new Notification.Builder(mContext)
.setContentIntent(createContentIntent(printJob.getId()))
- .setSmallIcon(com.android.internal.R.drawable.ic_print_error)
- .setContentTitle(mContext.getString(R.string.blocked_notification_title_template,
- printJob.getLabel()))
+ .setSmallIcon(computeNotificationIcon(printJob))
+ .setContentTitle(computeNotificationTitle(printJob))
.addAction(R.drawable.stat_notify_cancelling, mContext.getString(R.string.cancel),
createCancelIntent(printJob))
.setContentText(printJob.getPrinterName())
.setWhen(System.currentTimeMillis())
.setOngoing(true)
.setShowWhen(true);
- mNotificationManager.notify(printJob.getId().flattenToString(), 0, builder.build());
+ mNotificationManager.notify(0, builder.build());
}
- private void removeNotification(PrintJobId printJobId) {
- mNotificationManager.cancel(printJobId.flattenToString(), 0);
+ private void createCancellingNotification(PrintJobInfo printJob) {
+ Notification.Builder builder = new Notification.Builder(mContext)
+ .setContentIntent(createContentIntent(printJob.getId()))
+ .setSmallIcon(computeNotificationIcon(printJob))
+ .setContentTitle(computeNotificationTitle(printJob))
+ .setContentText(printJob.getPrinterName())
+ .setWhen(System.currentTimeMillis())
+ .setOngoing(true)
+ .setShowWhen(true);
+ mNotificationManager.notify(0, builder.build());
+ }
+
+ private void createStackedNotification(List<PrintJobInfo> printJobs) {
+ Notification.Builder builder = new Notification.Builder(mContext)
+ .setContentIntent(createContentIntent(null))
+ .setWhen(System.currentTimeMillis())
+ .setOngoing(true)
+ .setShowWhen(true);
+
+ final int printJobCount = printJobs.size();
+
+ InboxStyle inboxStyle = new InboxStyle();
+ inboxStyle.setBigContentTitle(String.format(mContext.getResources().getQuantityText(
+ R.plurals.composite_notification_title_template,
+ printJobCount).toString(), printJobCount));
+
+ for (int i = printJobCount - 1; i>= 0; i--) {
+ PrintJobInfo printJob = printJobs.get(i);
+ if (i == printJobCount - 1) {
+ builder.setLargeIcon(((BitmapDrawable) mContext.getResources().getDrawable(
+ computeNotificationIcon(printJob))).getBitmap());
+ builder.setSmallIcon(com.android.internal.R.drawable.ic_print);
+ builder.setContentTitle(computeNotificationTitle(printJob));
+ builder.setContentText(printJob.getPrinterName());
+ }
+ inboxStyle.addLine(computeNotificationTitle(printJob));
+ }
+
+ builder.setNumber(printJobCount);
+ builder.setStyle(inboxStyle);
+
+ mNotificationManager.notify(0, builder.build());
+ }
+
+ private String computeNotificationTitle(PrintJobInfo printJob) {
+ switch (printJob.getState()) {
+ case PrintJobInfo.STATE_FAILED: {
+ return mContext.getString(R.string.failed_notification_title_template,
+ printJob.getLabel());
+ }
+
+ case PrintJobInfo.STATE_BLOCKED: {
+ if (!printJob.isCancelling()) {
+ return mContext.getString(R.string.blocked_notification_title_template,
+ printJob.getLabel());
+ } else {
+ return mContext.getString(
+ R.string.cancelling_notification_title_template,
+ printJob.getLabel());
+ }
+ }
+
+ default: {
+ if (!printJob.isCancelling()) {
+ return mContext.getString(R.string.printing_notification_title_template,
+ printJob.getLabel());
+ } else {
+ return mContext.getString(
+ R.string.cancelling_notification_title_template,
+ printJob.getLabel());
+ }
+ }
+ }
+ }
+
+ private void removeNotification() {
+ mNotificationManager.cancel(0);
}
private PendingIntent createContentIntent(PrintJobId printJobId) {
Intent intent = new Intent(Settings.ACTION_PRINT_SETTINGS);
- intent.putExtra(EXTRA_PRINT_JOB_ID, printJobId.flattenToString());
- return PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT);
+ if (printJobId != null) {
+ intent.putExtra(EXTRA_PRINT_JOB_ID, printJobId.flattenToString());
+ intent.setData(Uri.fromParts("printjob", printJobId.flattenToString(), null));
+ }
+ return PendingIntent.getActivity(mContext, 0, intent, 0);
}
private PendingIntent createCancelIntent(PrintJobInfo printJob) {
return PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT);
}
+ private static boolean shouldNotifyForState(int state) {
+ switch (state) {
+ case PrintJobInfo.STATE_QUEUED:
+ case PrintJobInfo.STATE_STARTED:
+ case PrintJobInfo.STATE_FAILED:
+ case PrintJobInfo.STATE_COMPLETED:
+ case PrintJobInfo.STATE_CANCELED:
+ case PrintJobInfo.STATE_BLOCKED: {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static int computeNotificationIcon(PrintJobInfo printJob) {
+ switch (printJob.getState()) {
+ case PrintJobInfo.STATE_FAILED:
+ case PrintJobInfo.STATE_BLOCKED: {
+ return com.android.internal.R.drawable.ic_print_error;
+ }
+ default: {
+ if (!printJob.isCancelling()) {
+ return com.android.internal.R.drawable.ic_print;
+ } else {
+ return R.drawable.stat_notify_cancelling;
+ }
+ }
+ }
+ }
+
public static final class NotificationBroadcastReceiver extends BroadcastReceiver {
private static final String LOG_TAG = "NotificationBroadcastReceiver";
Log.i(LOG_TAG, "handleCancelPrintJob() printJobId:" + printJobId);
}
- // Put up a notification that we are trying to cancel.
- NotificationManager notificationManager = (NotificationManager)
- context.getSystemService(Context.NOTIFICATION_SERVICE);
- Notification.Builder builder = new Notification.Builder(context)
- .setSmallIcon(R.drawable.stat_notify_cancelling)
- .setContentTitle(context.getString(
- R.string.cancelling_notification_title_template,
- printJobLabel))
- .setContentText(printerName)
- .setWhen(System.currentTimeMillis())
- .setOngoing(true)
- .setShowWhen(true);
- notificationManager.notify(printJobId.flattenToString(), 0, builder.build());
-
// Call into the print manager service off the main thread since
// the print manager service may end up binding to the print spooler
// service which binding is handled on the main thread.
// done on another thread and until it finishes the spooler has
// to be kept around.
try {
- IPrintManager printManager = IPrintManager.Stub.asInterface(
- ServiceManager.getService(Context.PRINT_SERVICE));
+ IPrintManager printManager = IPrintManager.Stub.asInterface(
+ ServiceManager.getService(Context.PRINT_SERVICE));
printManager.cancelPrintJob(printJobId, PrintManager.APP_ID_ANY,
UserHandle.myUserId());
} catch (RemoteException re) {
// Then update the print jobs's pages as we will not do a write
// and we usually update the pages in the write complete callback.
updatePrintJobPages(mDocument.pages, mRequestedPages);
+ mEditor.updateUi();
if (mEditor.isDone()) {
requestCreatePdfFileOrFinish();
}
protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
PrintSpoolerService.this.dump(fd, writer, args);
}
+
+ @Override
+ public void setPrintJobCancelling(PrintJobId printJobId, boolean cancelling) {
+ PrintSpoolerService.this.setPrintJobCancelling(printJobId, cancelling);
+ }
};
}
fileForJobMap.remove(printJob.getId());
}
- // Update the notification.
- mNotificationController.onPrintJobStateChanged(printJob);
switch (printJob.getState()) {
case PrintJobInfo.STATE_QUEUED:
case PrintJobInfo.STATE_STARTED:
}
}
+ if (!mPrintJobs.isEmpty()) {
+ // Update the notification.
+ mNotificationController.onUpdateNotifications(mPrintJobs);
+ }
+
// Delete the orphan files.
if (fileForJobMap != null) {
final int orphanFileCount = fileForJobMap.size();
printJob.setState(state);
printJob.setStateReason(error);
- mNotificationController.onPrintJobStateChanged(printJob);
+ printJob.setCancelling(false);
if (DEBUG_PRINT_JOB_LIFECYCLE) {
Slog.i(LOG_TAG, "[STATE CHANGED] " + printJob);
HandlerCallerCallback.MSG_ON_PRINT_JOB_STATE_CHANGED,
printJob);
mHandlerCaller.executeOrSendMessage(message);
+
+ mNotificationController.onUpdateNotifications(mPrintJobs);
}
}
return false;
}
+ public void setPrintJobCancelling(PrintJobId printJobId, boolean cancelling) {
+ synchronized (mLock) {
+ PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
+ if (printJob != null) {
+ printJob.setCancelling(cancelling);
+ if (shouldPersistPrintJob(printJob)) {
+ mPersistanceManager.writeStateLocked();
+ }
+ mNotificationController.onUpdateNotifications(mPrintJobs);
+
+ Message message = mHandlerCaller.obtainMessageO(
+ HandlerCallerCallback.MSG_ON_PRINT_JOB_STATE_CHANGED,
+ printJob);
+ mHandlerCaller.executeOrSendMessage(message);
+ }
+ }
+ }
+
public void setPrintJobCopiesNoPersistence(PrintJobId printJobId, int copies) {
synchronized (mLock) {
PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY);
private static final String ATTR_COPIES = "copies";
private static final String ATTR_PRINTER_NAME = "printerName";
private static final String ATTR_STATE_REASON = "stateReason";
+ private static final String ATTR_CANCELLING = "cancelling";
private static final String TAG_MEDIA_SIZE = "mediaSize";
private static final String TAG_RESOLUTION = "resolution";
if (!TextUtils.isEmpty(stateReason)) {
serializer.attribute(null, ATTR_STATE_REASON, stateReason);
}
+ serializer.attribute(null, ATTR_CANCELLING, String.valueOf(
+ printJob.isCancelling()));
PrinterId printerId = printJob.getPrinterId();
if (printerId != null) {
printJob.setPrinterName(printerName);
String stateReason = parser.getAttributeValue(null, ATTR_STATE_REASON);
printJob.setStateReason(stateReason);
+ String cancelling = parser.getAttributeValue(null, ATTR_CANCELLING);
+ printJob.setCancelling(!TextUtils.isEmpty(cancelling)
+ ? Boolean.parseBoolean(cancelling) : false);
parser.next();
}
@Override
+ public void onPause() {
+ if (mAnnounceFilterResult != null) {
+ mAnnounceFilterResult.remove();
+ }
+ super.onPause();
+ }
+
+ @Override
public void onListItemClick(ListView list, View view, int position, long id) {
PrinterInfo printer = (PrinterInfo) list.getAdapter().getItem(position);
Activity activity = getActivity();
}
}
- private void announceSearchResult() {
- if (mAnnounceFilterResult == null) {
- mAnnounceFilterResult = new AnnounceFilterResult();
+ private void announceSearchResultIfNeeded() {
+ if (AccessibilityManager.getInstance(getActivity()).isEnabled()) {
+ if (mAnnounceFilterResult == null) {
+ mAnnounceFilterResult = new AnnounceFilterResult();
+ }
+ mAnnounceFilterResult.post();
}
- mAnnounceFilterResult.post();
}
public static class AddPrinterAlertDialogFragment extends DialogFragment {
resultCountChanged = (oldPrinterCount != mFilteredPrinters.size());
}
if (resultCountChanged) {
- announceSearchResult();
+ announceSearchResultIfNeeded();
}
notifyDataSetChanged();
}
+++ /dev/null
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 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.
--->
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:orientation="vertical">
- <AnalogClock
- android:id="@+id/analog_clock"
- android:layout_width="32dp"
- android:layout_height="32dp"
- android:layout_gravity="center"
- android:layout_marginBottom="10dp"
- android:dial="@drawable/ic_qs_clock_circle"
- android:hand_hour="@drawable/ic_qs_clock_hour"
- android:hand_minute="@drawable/ic_qs_clock_minute"
- />
- <com.android.systemui.statusbar.policy.DateView
- android:textAppearance="@style/TextAppearance.QuickSettings.TileView"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal"
- android:gravity="center"
- />
-</LinearLayout>
parent.addView(brightnessTile);
mDynamicSpannedTiles.add(brightnessTile);
- // Time tile
- /*
- QuickSettingsTileView timeTile = (QuickSettingsTileView)
- inflater.inflate(R.layout.quick_settings_tile, parent, false);
- timeTile.setContent(R.layout.quick_settings_tile_time, inflater);
- timeTile.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- // Quick. Clock. Quick. Clock. Quick. Clock.
- startSettingsActivity(Intent.ACTION_QUICK_CLOCK);
- }
- });
- mModel.addTimeTile(timeTile, new QuickSettingsModel.RefreshCallback() {
- @Override
- public void refreshView(QuickSettingsTileView view, State alarmState) {}
- });
- parent.addView(timeTile);
- mDynamicSpannedTiles.add(timeTile);
- */
-
// Settings tile
final QuickSettingsBasicTile settingsTile = new QuickSettingsBasicTile(mContext);
settingsTile.setImageResource(R.drawable.ic_qs_settings);
|| Intent.ACTION_TIME_CHANGED.equals(action)
|| Intent.ACTION_TIMEZONE_CHANGED.equals(action)
|| Intent.ACTION_LOCALE_CHANGED.equals(action)) {
- if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
+ if (Intent.ACTION_LOCALE_CHANGED.equals(action)
+ || Intent.ACTION_TIMEZONE_CHANGED.equals(action)) {
// need to get a fresh date format
mDateFormat = null;
}
pf.top = df.top = of.top = cf.top = vf.top = mDockTop;
pf.right = df.right = of.right = cf.right = vf.right = mDockRight;
// IM dock windows layout below the nav bar...
- pf.bottom = df.bottom = of.bottom = mRestrictedScreenTop + mRestrictedScreenHeight;
+ pf.bottom = df.bottom = of.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
// ...with content insets above the nav bar
cf.bottom = vf.bottom = mStableBottom;
// IM dock windows always go to the bottom of the screen.
+ "there is at least one pointer down!");
}
case MotionEvent.ACTION_UP: {
+ mAms.onTouchInteractionEnd();
// Announce the end of a the touch interaction.
sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
- mAms.onTouchInteractionEnd();
mLongPressingPointerId = -1;
mLongPressingPointerDeltaX = 0;
mLongPressingPointerDeltaY = 0;
AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext);
if (accessibilityManager.isEnabled()) {
AccessibilityEvent event = AccessibilityEvent.obtain(type);
+ event.setWindowId(mAms.getActiveWindowId());
accessibilityManager.sendAccessibilityEvent(event);
switch (type) {
case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START: {
// Use existing process if already started
ProcessRecord proc = getProcessRecordLocked(
cpi.processName, cpr.appInfo.uid, false);
- if (proc != null) {
+ if (proc != null && proc.thread != null) {
if (DEBUG_PROVIDER) {
Slog.d(TAG, "Installing in existing process " + proc);
}
return false;
}
+ public final void setPrintJobCancelling(PrintJobId printJobId, boolean cancelling) {
+ throwIfCalledOnMainThread();
+ synchronized (mLock) {
+ throwIfDestroyedLocked();
+ mCanUnbind = false;
+ }
+ try {
+ getRemoteInstanceLazy().setPrintJobCancelling(printJobId,
+ cancelling);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG, "Error setting print job cancelling.", re);
+ } catch (TimeoutException te) {
+ Slog.e(LOG_TAG, "Error setting print job cancelling.", te);
+ } finally {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier()
+ + "] setPrintJobCancelling()");
+ }
+ synchronized (mLock) {
+ mCanUnbind = true;
+ mLock.notifyAll();
+ }
+ }
+ }
+
public final void removeObsoletePrintJobs() {
throwIfCalledOnMainThread();
synchronized (mLock) {
if (printJobInfo == null) {
return;
}
+
+ // Take a note that we are trying to cancel the job.
+ mSpooler.setPrintJobCancelling(printJobId, true);
+
if (printJobInfo.getState() != PrintJobInfo.STATE_FAILED) {
ComponentName printServiceName = printJobInfo.getPrinterId().getServiceName();
RemotePrintService printService = null;
@Override
public void exit() {
- // if we're leaving before this is done, cleanup
- if (mDhcpActive) {
- handlePostDhcpSetup();
- }
+ handleNetworkDisconnect();
}
@Override