Home | History | Annotate | Download | only in bips
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  * Copyright (C) 2016 Mopria Alliance, Inc.
      4  *
      5  * Licensed under the Apache License, Version 2.0 (the "License");
      6  * you may not use this file except in compliance with the License.
      7  * You may obtain a copy of the License at
      8  *
      9  *      http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 
     18 package com.android.bips;
     19 
     20 import android.net.Uri;
     21 import android.os.Handler;
     22 import android.print.PrintJobId;
     23 import android.printservice.PrintJob;
     24 import android.util.Log;
     25 
     26 import com.android.bips.discovery.DiscoveredPrinter;
     27 import com.android.bips.discovery.MdnsDiscovery;
     28 import com.android.bips.ipp.Backend;
     29 import com.android.bips.ipp.JobStatus;
     30 import com.android.bips.jni.BackendConstants;
     31 import com.android.bips.jni.LocalPrinterCapabilities;
     32 
     33 import java.util.function.Consumer;
     34 
     35 /**
     36  * Manage the process of delivering a print job
     37  */
     38 class LocalPrintJob implements MdnsDiscovery.Listener {
     39     private static final String TAG = LocalPrintJob.class.getSimpleName();
     40     private static final boolean DEBUG = false;
     41 
     42     /** Maximum time to wait to find a printer before failing the job */
     43     private static final int DISCOVERY_TIMEOUT = 30 * 1000;
     44 
     45     // Internal job states
     46     private static final int STATE_INIT = 0;
     47     private static final int STATE_DISCOVERY = 1;
     48     private static final int STATE_DELIVERING = 2;
     49     private static final int STATE_CANCEL = 3;
     50     private static final int STATE_DONE = 4;
     51 
     52     private final BuiltInPrintService mPrintService;
     53     private final PrintJob mPrintJob;
     54     private final Backend mBackend;
     55     private final Handler mMainHandler;
     56 
     57     private int mState;
     58     private Consumer<LocalPrintJob> mCompleteConsumer;
     59     private Uri mPath;
     60 
     61     /**
     62      * Construct the object; use {@link #start(Consumer)} to begin job processing.
     63      */
     64     LocalPrintJob(BuiltInPrintService printService, Backend backend, PrintJob printJob) {
     65         mPrintService = printService;
     66         mBackend = backend;
     67         mPrintJob = printJob;
     68         mMainHandler = new Handler(printService.getMainLooper());
     69         mState = STATE_INIT;
     70 
     71         // Tell the job it is blocked (until start())
     72         mPrintJob.start();
     73         mPrintJob.block(printService.getString(R.string.waiting_to_send));
     74     }
     75 
     76     /**
     77      * Begin the process of delivering the job. Internally, discovers the target printer,
     78      * obtains its capabilities, delivers the job to the printer, and waits for job completion.
     79      *
     80      * @param callback Callback to be issued when job processing is complete
     81      */
     82     void start(Consumer<LocalPrintJob> callback) {
     83         if (DEBUG) Log.d(TAG, "start() " + mPrintJob);
     84         if (mState != STATE_INIT) {
     85             Log.w(TAG, "Invalid start state " + mState);
     86             return;
     87         }
     88         mPrintJob.start();
     89 
     90         // Acquire a lock so that WiFi isn't put to sleep while we send the job
     91         mPrintService.lockWifi();
     92 
     93         mState = STATE_DISCOVERY;
     94         mCompleteConsumer = callback;
     95         mPrintService.getDiscovery().start(this);
     96 
     97         mMainHandler.postDelayed(() -> {
     98             if (DEBUG) Log.d(TAG, "Discovery timeout");
     99             if (mState == STATE_DISCOVERY) {
    100                 mPrintService.getDiscovery().stop(LocalPrintJob.this);
    101                 finish(false, mPrintService.getString(R.string.printer_offline));
    102             }
    103         }, DISCOVERY_TIMEOUT);
    104     }
    105 
    106     void cancel() {
    107         if (DEBUG) Log.d(TAG, "cancel() " + mPrintJob + " in state " + mState);
    108 
    109         switch (mState) {
    110             case STATE_DISCOVERY:
    111                 // Cancel immediately
    112                 mPrintService.getDiscovery().stop(this);
    113                 mState = STATE_CANCEL;
    114                 finish(false, null);
    115                 break;
    116 
    117             case STATE_DELIVERING:
    118                 // Request cancel and wait for completion
    119                 mState = STATE_CANCEL;
    120                 mBackend.cancel();
    121                 break;
    122         }
    123     }
    124 
    125     PrintJobId getPrintJobId() {
    126         return mPrintJob.getId();
    127     }
    128 
    129     @Override
    130     public void onPrinterFound(DiscoveredPrinter printer) {
    131         if (mState != STATE_DISCOVERY) return;
    132         if (printer.getId(mPrintService).equals(mPrintJob.getInfo().getPrinterId())) {
    133             if (DEBUG) Log.d(TAG, "onPrinterFound() " + printer.name + " state=" + mState);
    134             mPath = printer.path;
    135             mPrintService.getCapabilitiesCache().request(printer, true,
    136                     this::handleCapabilities);
    137             mPrintService.getDiscovery().stop(this);
    138         }
    139     }
    140 
    141     @Override
    142     public void onPrinterLost(DiscoveredPrinter printer) {
    143         // Ignore (the capability request, if any, will fail)
    144     }
    145 
    146     PrintJob getPrintJob() {
    147         return mPrintJob;
    148     }
    149 
    150     private void handleCapabilities(LocalPrinterCapabilities capabilities) {
    151         if (DEBUG) Log.d(TAG, "Capabilities for " + mPath + " are " + capabilities);
    152         if (mState != STATE_DISCOVERY) return;
    153 
    154         if (capabilities == null) {
    155             finish(false, mPrintService.getString(R.string.printer_offline));
    156         } else {
    157             if (DEBUG) Log.d(TAG, "Starting backend print of " + mPrintJob);
    158             mMainHandler.removeCallbacksAndMessages(null);
    159             mState = STATE_DELIVERING;
    160             mBackend.print(mPath, mPrintJob, capabilities, this::handleJobStatus);
    161         }
    162     }
    163 
    164     private void handleJobStatus(JobStatus jobStatus) {
    165         if (DEBUG) Log.d(TAG, "onJobStatus() " + jobStatus);
    166         switch (jobStatus.getJobState()) {
    167             case BackendConstants.JOB_STATE_DONE:
    168                 switch (jobStatus.getJobResult()) {
    169                     case BackendConstants.JOB_DONE_OK:
    170                         finish(true, null);
    171                         break;
    172                     case BackendConstants.JOB_DONE_CANCELLED:
    173                         mState = STATE_CANCEL;
    174                         finish(false, null);
    175                         break;
    176                     case BackendConstants.JOB_DONE_CORRUPT:
    177                         finish(false, mPrintService.getString(R.string.unreadable_input));
    178                         break;
    179                     default:
    180                         // Job failed
    181                         finish(false, null);
    182                         break;
    183                 }
    184                 break;
    185 
    186             case BackendConstants.JOB_STATE_BLOCKED:
    187                 if (mState == STATE_CANCEL) return;
    188                 int blockedId = jobStatus.getBlockedReasonId();
    189                 blockedId = (blockedId == 0) ? R.string.printer_check : blockedId;
    190                 String blockedReason = mPrintService.getString(blockedId);
    191                 mPrintJob.block(blockedReason);
    192                 break;
    193 
    194             case BackendConstants.JOB_STATE_RUNNING:
    195                 if (mState == STATE_CANCEL) return;
    196                 mPrintJob.start();
    197                 break;
    198         }
    199     }
    200 
    201     /**
    202      * Terminate the job, issuing appropriate notifications.
    203      *
    204      * @param success true if the printer reported successful job completion
    205      * @param error   reason for job failure if known
    206      */
    207     private void finish(boolean success, String error) {
    208         mPrintService.unlockWifi();
    209         mBackend.closeDocument();
    210         mMainHandler.removeCallbacksAndMessages(null);
    211         if (success) {
    212             // Job must not be blocked before completion
    213             mPrintJob.start();
    214             mPrintJob.complete();
    215         } else if (mState == STATE_CANCEL) {
    216             mPrintJob.cancel();
    217         } else {
    218             mPrintJob.fail(error);
    219         }
    220         mState = STATE_DONE;
    221         mCompleteConsumer.accept(LocalPrintJob.this);
    222     }
    223 }