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.print.PrintJobId;
     22 import android.printservice.PrintJob;
     23 import android.util.Log;
     24 
     25 import com.android.bips.discovery.ConnectionListener;
     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.CapabilitiesCache;
     30 import com.android.bips.ipp.JobStatus;
     31 import com.android.bips.jni.BackendConstants;
     32 import com.android.bips.jni.LocalPrinterCapabilities;
     33 import com.android.bips.p2p.P2pPrinterConnection;
     34 import com.android.bips.p2p.P2pUtils;
     35 
     36 import java.util.function.Consumer;
     37 
     38 /**
     39  * Manage the process of delivering a print job
     40  */
     41 class LocalPrintJob implements MdnsDiscovery.Listener, ConnectionListener,
     42         CapabilitiesCache.OnLocalPrinterCapabilities {
     43     private static final String TAG = LocalPrintJob.class.getSimpleName();
     44     private static final boolean DEBUG = false;
     45 
     46     /** Maximum time to wait to find a printer before failing the job */
     47     private static final int DISCOVERY_TIMEOUT = 2 * 60 * 1000;
     48 
     49     // Internal job states
     50     private static final int STATE_INIT = 0;
     51     private static final int STATE_DISCOVERY = 1;
     52     private static final int STATE_CAPABILITIES = 2;
     53     private static final int STATE_DELIVERING = 3;
     54     private static final int STATE_CANCEL = 4;
     55     private static final int STATE_DONE = 5;
     56 
     57     private final BuiltInPrintService mPrintService;
     58     private final PrintJob mPrintJob;
     59     private final Backend mBackend;
     60 
     61     private int mState;
     62     private Consumer<LocalPrintJob> mCompleteConsumer;
     63     private Uri mPath;
     64     private DelayedAction mDiscoveryTimeout;
     65     private P2pPrinterConnection mConnection;
     66 
     67     /**
     68      * Construct the object; use {@link #start(Consumer)} to begin job processing.
     69      */
     70     LocalPrintJob(BuiltInPrintService printService, Backend backend, PrintJob printJob) {
     71         mPrintService = printService;
     72         mBackend = backend;
     73         mPrintJob = printJob;
     74         mState = STATE_INIT;
     75 
     76         // Tell the job it is blocked (until start())
     77         mPrintJob.start();
     78         mPrintJob.block(printService.getString(R.string.waiting_to_send));
     79     }
     80 
     81     /**
     82      * Begin the process of delivering the job. Internally, discovers the target printer,
     83      * obtains its capabilities, delivers the job to the printer, and waits for job completion.
     84      *
     85      * @param callback Callback to be issued when job processing is complete
     86      */
     87     void start(Consumer<LocalPrintJob> callback) {
     88         if (DEBUG) Log.d(TAG, "start() " + mPrintJob);
     89         if (mState != STATE_INIT) {
     90             Log.w(TAG, "Invalid start state " + mState);
     91             return;
     92         }
     93         mPrintJob.start();
     94 
     95         // Acquire a lock so that WiFi isn't put to sleep while we send the job
     96         mPrintService.lockWifi();
     97 
     98         mState = STATE_DISCOVERY;
     99         mCompleteConsumer = callback;
    100         mDiscoveryTimeout = mPrintService.delay(DISCOVERY_TIMEOUT, () -> {
    101             if (DEBUG) Log.d(TAG, "Discovery timeout");
    102             if (mState == STATE_DISCOVERY) {
    103                 finish(false, mPrintService.getString(R.string.printer_offline));
    104             }
    105         });
    106 
    107         mPrintService.getDiscovery().start(this);
    108     }
    109 
    110     void cancel() {
    111         if (DEBUG) Log.d(TAG, "cancel() " + mPrintJob + " in state " + mState);
    112 
    113         switch (mState) {
    114             case STATE_DISCOVERY:
    115             case STATE_CAPABILITIES:
    116                 // Cancel immediately
    117                 mState = STATE_CANCEL;
    118                 finish(false, null);
    119                 break;
    120 
    121             case STATE_DELIVERING:
    122                 // Request cancel and wait for completion
    123                 mState = STATE_CANCEL;
    124                 mBackend.cancel();
    125                 break;
    126         }
    127     }
    128 
    129     PrintJobId getPrintJobId() {
    130         return mPrintJob.getId();
    131     }
    132 
    133     @Override
    134     public void onPrinterFound(DiscoveredPrinter printer) {
    135         if (mState != STATE_DISCOVERY) {
    136             return;
    137         }
    138         if (!printer.getId(mPrintService).equals(mPrintJob.getInfo().getPrinterId())) {
    139             return;
    140         }
    141 
    142         if (DEBUG) Log.d(TAG, "onPrinterFound() " + printer.name + " state=" + mState);
    143 
    144         if (P2pUtils.isP2p(printer)) {
    145             // Launch a P2P connection attempt
    146             mConnection = new P2pPrinterConnection(mPrintService, printer, this);
    147             return;
    148         }
    149 
    150         if (P2pUtils.isOnConnectedInterface(mPrintService, printer) && mConnection == null) {
    151             // Hold the P2P connection up during printing
    152             mConnection = new P2pPrinterConnection(mPrintService, printer, this);
    153         }
    154 
    155         // We have a good path so stop discovering and get capabilities
    156         mPrintService.getDiscovery().stop(this);
    157         mState = STATE_CAPABILITIES;
    158         mPath = printer.path;
    159         mPrintService.getCapabilitiesCache().request(printer, true, this);
    160     }
    161 
    162     @Override
    163     public void onPrinterLost(DiscoveredPrinter printer) {
    164         // Ignore (the capability request, if any, will fail)
    165     }
    166 
    167     @Override
    168     public void onConnectionComplete(DiscoveredPrinter printer) {
    169         // Ignore late connection events
    170         if (mState != STATE_DISCOVERY) {
    171             return;
    172         }
    173 
    174         if (printer == null) {
    175             finish(false, mPrintService.getString(R.string.failed_printer_connection));
    176         } else if (mPrintJob.isBlocked()) {
    177             mPrintJob.start();
    178         }
    179     }
    180 
    181     @Override
    182     public void onConnectionDelayed(boolean delayed) {
    183         if (DEBUG) Log.d(TAG, "onConnectionDelayed " + delayed);
    184 
    185         // Ignore late events
    186         if (mState != STATE_DISCOVERY) {
    187             return;
    188         }
    189 
    190         if (delayed) {
    191             mPrintJob.block(mPrintService.getString(R.string.connect_hint_text));
    192         } else {
    193             // Remove block message
    194             mPrintJob.start();
    195         }
    196     }
    197 
    198     PrintJob getPrintJob() {
    199         return mPrintJob;
    200     }
    201 
    202     @Override
    203     public void onCapabilities(LocalPrinterCapabilities capabilities) {
    204         if (DEBUG) Log.d(TAG, "Capabilities for " + mPath + " are " + capabilities);
    205         if (mState != STATE_CAPABILITIES) {
    206             return;
    207         }
    208 
    209         if (capabilities == null) {
    210             finish(false, mPrintService.getString(R.string.printer_offline));
    211         } else {
    212             if (DEBUG) Log.d(TAG, "Starting backend print of " + mPrintJob);
    213             if (mDiscoveryTimeout != null) {
    214                 mDiscoveryTimeout.cancel();
    215             }
    216             mState = STATE_DELIVERING;
    217             mPrintJob.start();
    218             mBackend.print(mPath, mPrintJob, capabilities, this::handleJobStatus);
    219         }
    220     }
    221 
    222     private void handleJobStatus(JobStatus jobStatus) {
    223         if (DEBUG) Log.d(TAG, "onJobStatus() " + jobStatus);
    224         switch (jobStatus.getJobState()) {
    225             case BackendConstants.JOB_STATE_DONE:
    226                 switch (jobStatus.getJobResult()) {
    227                     case BackendConstants.JOB_DONE_OK:
    228                         finish(true, null);
    229                         break;
    230                     case BackendConstants.JOB_DONE_CANCELLED:
    231                         mState = STATE_CANCEL;
    232                         finish(false, null);
    233                         break;
    234                     case BackendConstants.JOB_DONE_CORRUPT:
    235                         finish(false, mPrintService.getString(R.string.unreadable_input));
    236                         break;
    237                     default:
    238                         // Job failed
    239                         finish(false, null);
    240                         break;
    241                 }
    242                 break;
    243 
    244             case BackendConstants.JOB_STATE_BLOCKED:
    245                 if (mState == STATE_CANCEL) {
    246                     return;
    247                 }
    248                 int blockedId = jobStatus.getBlockedReasonId();
    249                 blockedId = (blockedId == 0) ? R.string.printer_check : blockedId;
    250                 String blockedReason = mPrintService.getString(blockedId);
    251                 mPrintJob.block(blockedReason);
    252                 break;
    253 
    254             case BackendConstants.JOB_STATE_RUNNING:
    255                 if (mState == STATE_CANCEL) {
    256                     return;
    257                 }
    258                 mPrintJob.start();
    259                 break;
    260         }
    261     }
    262 
    263     /**
    264      * Terminate the job, issuing appropriate notifications.
    265      *
    266      * @param success true if the printer reported successful job completion
    267      * @param error   reason for job failure if known
    268      */
    269     private void finish(boolean success, String error) {
    270         if (DEBUG) Log.d(TAG, "finish() success=" + success + ", error=" + error);
    271         mPrintService.getDiscovery().stop(this);
    272         if (mDiscoveryTimeout != null) {
    273             mDiscoveryTimeout.cancel();
    274         }
    275         if (mConnection != null) {
    276             mConnection.close();
    277         }
    278         mPrintService.unlockWifi();
    279         mBackend.closeDocument();
    280         if (success) {
    281             // Job must not be blocked before completion
    282             mPrintJob.start();
    283             mPrintJob.complete();
    284         } else if (mState == STATE_CANCEL) {
    285             mPrintJob.cancel();
    286         } else {
    287             mPrintJob.fail(error);
    288         }
    289         mState = STATE_DONE;
    290         mCompleteConsumer.accept(LocalPrintJob.this);
    291     }
    292 }
    293