Home | History | Annotate | Download | only in printspooler
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.printspooler;
     18 
     19 import android.app.Notification;
     20 import android.app.Notification.InboxStyle;
     21 import android.app.NotificationManager;
     22 import android.app.PendingIntent;
     23 import android.content.BroadcastReceiver;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.graphics.drawable.BitmapDrawable;
     27 import android.net.Uri;
     28 import android.os.AsyncTask;
     29 import android.os.PowerManager;
     30 import android.os.PowerManager.WakeLock;
     31 import android.os.RemoteException;
     32 import android.os.ServiceManager;
     33 import android.os.UserHandle;
     34 import android.print.IPrintManager;
     35 import android.print.PrintJobId;
     36 import android.print.PrintJobInfo;
     37 import android.print.PrintManager;
     38 import android.provider.Settings;
     39 import android.util.Log;
     40 
     41 import java.util.ArrayList;
     42 import java.util.List;
     43 
     44 /**
     45  * This class is responsible for updating the print notifications
     46  * based on print job state transitions.
     47  */
     48 public class NotificationController {
     49     public static final boolean DEBUG = false;
     50 
     51     public static final String LOG_TAG = "NotificationController";
     52 
     53     private static final String INTENT_ACTION_CANCEL_PRINTJOB = "INTENT_ACTION_CANCEL_PRINTJOB";
     54     private static final String INTENT_ACTION_RESTART_PRINTJOB = "INTENT_ACTION_RESTART_PRINTJOB";
     55 
     56     private static final String EXTRA_PRINT_JOB_ID = "EXTRA_PRINT_JOB_ID";
     57     private static final String EXTRA_PRINTJOB_LABEL = "EXTRA_PRINTJOB_LABEL";
     58     private static final String EXTRA_PRINTER_NAME = "EXTRA_PRINTER_NAME";
     59 
     60     private final Context mContext;
     61     private final NotificationManager mNotificationManager;
     62 
     63     public NotificationController(Context context) {
     64         mContext = context;
     65         mNotificationManager = (NotificationManager)
     66                 mContext.getSystemService(Context.NOTIFICATION_SERVICE);
     67     }
     68 
     69     public void onUpdateNotifications(List<PrintJobInfo> printJobs) {
     70         List<PrintJobInfo> notifyPrintJobs = new ArrayList<PrintJobInfo>();
     71 
     72         final int printJobCount = printJobs.size();
     73         for (int i = 0; i < printJobCount; i++) {
     74             PrintJobInfo printJob = printJobs.get(i);
     75             if (shouldNotifyForState(printJob.getState())) {
     76                 notifyPrintJobs.add(printJob);
     77             }
     78         }
     79 
     80         updateNotification(notifyPrintJobs);
     81     }
     82 
     83     private void updateNotification(List<PrintJobInfo> printJobs) {
     84         if (printJobs.size() <= 0) {
     85             removeNotification();
     86         } else if (printJobs.size() == 1) {
     87             createSimpleNotification(printJobs.get(0));
     88         } else {
     89             createStackedNotification(printJobs);
     90         }
     91     }
     92 
     93     private void createSimpleNotification(PrintJobInfo printJob) {
     94         switch (printJob.getState()) {
     95             case PrintJobInfo.STATE_FAILED: {
     96                 createFailedNotification(printJob);
     97             } break;
     98 
     99             case PrintJobInfo.STATE_BLOCKED: {
    100                 if (!printJob.isCancelling()) {
    101                     createBlockedNotification(printJob);
    102                 } else {
    103                     createCancellingNotification(printJob);
    104                 }
    105             } break;
    106 
    107             default: {
    108                 if (!printJob.isCancelling()) {
    109                     createPrintingNotification(printJob);
    110                 } else {
    111                     createCancellingNotification(printJob);
    112                 }
    113             } break;
    114         }
    115     }
    116 
    117     private void createPrintingNotification(PrintJobInfo printJob) {
    118         Notification.Builder builder = new Notification.Builder(mContext)
    119                 .setContentIntent(createContentIntent(printJob.getId()))
    120                 .setSmallIcon(computeNotificationIcon(printJob))
    121                 .setContentTitle(computeNotificationTitle(printJob))
    122                 .addAction(R.drawable.stat_notify_cancelling, mContext.getString(R.string.cancel),
    123                         createCancelIntent(printJob))
    124                 .setContentText(printJob.getPrinterName())
    125                 .setWhen(System.currentTimeMillis())
    126                 .setOngoing(true)
    127                 .setShowWhen(true);
    128         mNotificationManager.notify(0, builder.build());
    129     }
    130 
    131     private void createFailedNotification(PrintJobInfo printJob) {
    132         Notification.Builder builder = new Notification.Builder(mContext)
    133                 .setContentIntent(createContentIntent(printJob.getId()))
    134                 .setSmallIcon(computeNotificationIcon(printJob))
    135                 .setContentTitle(computeNotificationTitle(printJob))
    136                 .addAction(R.drawable.stat_notify_cancelling, mContext.getString(R.string.cancel),
    137                         createCancelIntent(printJob))
    138                 .addAction(R.drawable.ic_restart, mContext.getString(R.string.restart),
    139                         createRestartIntent(printJob.getId()))
    140                 .setContentText(printJob.getPrinterName())
    141                 .setWhen(System.currentTimeMillis())
    142                 .setOngoing(true)
    143                 .setShowWhen(true);
    144         mNotificationManager.notify(0, builder.build());
    145     }
    146 
    147     private void createBlockedNotification(PrintJobInfo printJob) {
    148         Notification.Builder builder = new Notification.Builder(mContext)
    149                 .setContentIntent(createContentIntent(printJob.getId()))
    150                 .setSmallIcon(computeNotificationIcon(printJob))
    151                 .setContentTitle(computeNotificationTitle(printJob))
    152                 .addAction(R.drawable.stat_notify_cancelling, mContext.getString(R.string.cancel),
    153                         createCancelIntent(printJob))
    154                 .setContentText(printJob.getPrinterName())
    155                 .setWhen(System.currentTimeMillis())
    156                 .setOngoing(true)
    157                 .setShowWhen(true);
    158            mNotificationManager.notify(0, builder.build());
    159     }
    160 
    161     private void createCancellingNotification(PrintJobInfo printJob) {
    162         Notification.Builder builder = new Notification.Builder(mContext)
    163                 .setContentIntent(createContentIntent(printJob.getId()))
    164                 .setSmallIcon(computeNotificationIcon(printJob))
    165                 .setContentTitle(computeNotificationTitle(printJob))
    166                 .setContentText(printJob.getPrinterName())
    167                 .setWhen(System.currentTimeMillis())
    168                 .setOngoing(true)
    169                 .setShowWhen(true);
    170         mNotificationManager.notify(0, builder.build());
    171     }
    172 
    173     private void createStackedNotification(List<PrintJobInfo> printJobs) {
    174         Notification.Builder builder = new Notification.Builder(mContext)
    175                 .setContentIntent(createContentIntent(null))
    176                 .setWhen(System.currentTimeMillis())
    177                 .setOngoing(true)
    178                 .setShowWhen(true);
    179 
    180         final int printJobCount = printJobs.size();
    181 
    182         InboxStyle inboxStyle = new InboxStyle();
    183         inboxStyle.setBigContentTitle(String.format(mContext.getResources().getQuantityText(
    184                 R.plurals.composite_notification_title_template,
    185                 printJobCount).toString(), printJobCount));
    186 
    187         for (int i = printJobCount - 1; i>= 0; i--) {
    188             PrintJobInfo printJob = printJobs.get(i);
    189             if (i == printJobCount - 1) {
    190                 builder.setLargeIcon(((BitmapDrawable) mContext.getResources().getDrawable(
    191                         computeNotificationIcon(printJob))).getBitmap());
    192                 builder.setSmallIcon(computeNotificationIcon(printJob));
    193                 builder.setContentTitle(computeNotificationTitle(printJob));
    194                 builder.setContentText(printJob.getPrinterName());
    195             }
    196             inboxStyle.addLine(computeNotificationTitle(printJob));
    197         }
    198 
    199         builder.setNumber(printJobCount);
    200         builder.setStyle(inboxStyle);
    201 
    202         mNotificationManager.notify(0, builder.build());
    203     }
    204 
    205     private String computeNotificationTitle(PrintJobInfo printJob) {
    206         switch (printJob.getState()) {
    207             case PrintJobInfo.STATE_FAILED: {
    208                 return mContext.getString(R.string.failed_notification_title_template,
    209                         printJob.getLabel());
    210             }
    211 
    212             case PrintJobInfo.STATE_BLOCKED: {
    213                 if (!printJob.isCancelling()) {
    214                     return mContext.getString(R.string.blocked_notification_title_template,
    215                             printJob.getLabel());
    216                 } else {
    217                     return mContext.getString(
    218                             R.string.cancelling_notification_title_template,
    219                             printJob.getLabel());
    220                 }
    221             }
    222 
    223             default: {
    224                 if (!printJob.isCancelling()) {
    225                     return mContext.getString(R.string.printing_notification_title_template,
    226                             printJob.getLabel());
    227                 } else {
    228                     return mContext.getString(
    229                             R.string.cancelling_notification_title_template,
    230                             printJob.getLabel());
    231                 }
    232             }
    233         }
    234     }
    235 
    236     private void removeNotification() {
    237         mNotificationManager.cancel(0);
    238     }
    239 
    240     private PendingIntent createContentIntent(PrintJobId printJobId) {
    241         Intent intent = new Intent(Settings.ACTION_PRINT_SETTINGS);
    242         if (printJobId != null) {
    243             intent.putExtra(EXTRA_PRINT_JOB_ID, printJobId.flattenToString());
    244             intent.setData(Uri.fromParts("printjob", printJobId.flattenToString(), null));
    245         }
    246         return PendingIntent.getActivity(mContext, 0, intent, 0);
    247     }
    248 
    249     private PendingIntent createCancelIntent(PrintJobInfo printJob) {
    250         Intent intent = new Intent(mContext, NotificationBroadcastReceiver.class);
    251         intent.setAction(INTENT_ACTION_CANCEL_PRINTJOB + "_" + printJob.getId().flattenToString());
    252         intent.putExtra(EXTRA_PRINT_JOB_ID, printJob.getId());
    253         intent.putExtra(EXTRA_PRINTJOB_LABEL, printJob.getLabel());
    254         intent.putExtra(EXTRA_PRINTER_NAME, printJob.getPrinterName());
    255         return PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT);
    256     }
    257 
    258     private PendingIntent createRestartIntent(PrintJobId printJobId) {
    259         Intent intent = new Intent(mContext, NotificationBroadcastReceiver.class);
    260         intent.setAction(INTENT_ACTION_RESTART_PRINTJOB + "_" + printJobId.flattenToString());
    261         intent.putExtra(EXTRA_PRINT_JOB_ID, printJobId);
    262         return PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT);
    263     }
    264 
    265     private static boolean shouldNotifyForState(int state) {
    266         switch (state) {
    267             case PrintJobInfo.STATE_QUEUED:
    268             case PrintJobInfo.STATE_STARTED:
    269             case PrintJobInfo.STATE_FAILED:
    270             case PrintJobInfo.STATE_COMPLETED:
    271             case PrintJobInfo.STATE_CANCELED:
    272             case PrintJobInfo.STATE_BLOCKED: {
    273                 return true;
    274             }
    275         }
    276         return false;
    277     }
    278 
    279     private static int computeNotificationIcon(PrintJobInfo printJob) {
    280         switch (printJob.getState()) {
    281             case PrintJobInfo.STATE_FAILED:
    282             case PrintJobInfo.STATE_BLOCKED: {
    283                 return com.android.internal.R.drawable.ic_print_error;
    284             }
    285             default: {
    286                 if (!printJob.isCancelling()) {
    287                     return com.android.internal.R.drawable.ic_print;
    288                 } else {
    289                     return R.drawable.stat_notify_cancelling;
    290                 }
    291             }
    292         }
    293     }
    294 
    295     public static final class NotificationBroadcastReceiver extends BroadcastReceiver {
    296         private static final String LOG_TAG = "NotificationBroadcastReceiver";
    297 
    298         @Override
    299         public void onReceive(Context context, Intent intent) {
    300             String action = intent.getAction();
    301             if (action != null && action.startsWith(INTENT_ACTION_CANCEL_PRINTJOB)) {
    302                 PrintJobId printJobId = intent.getExtras().getParcelable(EXTRA_PRINT_JOB_ID);
    303                 String printJobLabel = intent.getExtras().getString(EXTRA_PRINTJOB_LABEL);
    304                 String printerName = intent.getExtras().getString(EXTRA_PRINTER_NAME);
    305                 handleCancelPrintJob(context, printJobId, printJobLabel, printerName);
    306             } else if (action != null && action.startsWith(INTENT_ACTION_RESTART_PRINTJOB)) {
    307                 PrintJobId printJobId = intent.getExtras().getParcelable(EXTRA_PRINT_JOB_ID);
    308                 handleRestartPrintJob(context, printJobId);
    309             }
    310         }
    311 
    312         private void handleCancelPrintJob(final Context context, final PrintJobId printJobId,
    313                 final String printJobLabel, final String printerName) {
    314             if (DEBUG) {
    315                 Log.i(LOG_TAG, "handleCancelPrintJob() printJobId:" + printJobId);
    316             }
    317 
    318             // Call into the print manager service off the main thread since
    319             // the print manager service may end up binding to the print spooler
    320             // service which binding is handled on the main thread.
    321             PowerManager powerManager = (PowerManager)
    322                     context.getSystemService(Context.POWER_SERVICE);
    323             final WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
    324                     LOG_TAG);
    325             wakeLock.acquire();
    326 
    327             new AsyncTask<Void, Void, Void>() {
    328                 @Override
    329                 protected Void doInBackground(Void... params) {
    330                     // We need to request the cancellation to be done by the print
    331                     // manager service since it has to communicate with the managing
    332                     // print service to request the cancellation. Also we need the
    333                     // system service to be bound to the spooler since canceling a
    334                     // print job will trigger persistence of current jobs which is
    335                     // done on another thread and until it finishes the spooler has
    336                     // to be kept around.
    337                     try {
    338                         IPrintManager printManager = IPrintManager.Stub.asInterface(
    339                                 ServiceManager.getService(Context.PRINT_SERVICE));
    340                         printManager.cancelPrintJob(printJobId, PrintManager.APP_ID_ANY,
    341                                 UserHandle.myUserId());
    342                     } catch (RemoteException re) {
    343                         Log.i(LOG_TAG, "Error requestion print job cancellation", re);
    344                     } finally {
    345                         wakeLock.release();
    346                     }
    347                     return null;
    348                 }
    349             }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
    350         }
    351 
    352         private void handleRestartPrintJob(final Context context, final PrintJobId printJobId) {
    353             if (DEBUG) {
    354                 Log.i(LOG_TAG, "handleRestartPrintJob() printJobId:" + printJobId);
    355             }
    356 
    357             // Call into the print manager service off the main thread since
    358             // the print manager service may end up binding to the print spooler
    359             // service which binding is handled on the main thread.
    360             PowerManager powerManager = (PowerManager)
    361                     context.getSystemService(Context.POWER_SERVICE);
    362             final WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
    363                     LOG_TAG);
    364             wakeLock.acquire();
    365 
    366             new AsyncTask<Void, Void, Void>() {
    367                 @Override
    368                 protected Void doInBackground(Void... params) {
    369                     // We need to request the restart to be done by the print manager
    370                     // service since the latter must be bound to the spooler because
    371                     // restarting a print job will trigger persistence of current jobs
    372                     // which is done on another thread and until it finishes the spooler has
    373                     // to be kept around.
    374                     try {
    375                         IPrintManager printManager = IPrintManager.Stub.asInterface(
    376                                 ServiceManager.getService(Context.PRINT_SERVICE));
    377                         printManager.restartPrintJob(printJobId, PrintManager.APP_ID_ANY,
    378                                 UserHandle.myUserId());
    379                     } catch (RemoteException re) {
    380                         Log.i(LOG_TAG, "Error requestion print job restart", re);
    381                     } finally {
    382                         wakeLock.release();
    383                     }
    384                     return null;
    385                 }
    386             }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
    387         }
    388     }
    389 }
    390