• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.discovery;
19 
20 import android.net.Uri;
21 import android.text.TextUtils;
22 import android.util.JsonReader;
23 import android.util.JsonWriter;
24 import android.util.Log;
25 
26 import com.android.bips.BuiltInPrintService;
27 import com.android.bips.ipp.CapabilitiesCache;
28 import com.android.bips.jni.LocalPrinterCapabilities;
29 
30 import java.io.BufferedReader;
31 import java.io.BufferedWriter;
32 import java.io.File;
33 import java.io.FileReader;
34 import java.io.FileWriter;
35 import java.io.IOException;
36 import java.util.ArrayList;
37 import java.util.Arrays;
38 import java.util.LinkedList;
39 import java.util.List;
40 
41 /**
42  * Manage a list of printers manually added by the user.
43  */
44 public class ManualDiscovery extends Discovery implements AutoCloseable {
45     private static final String TAG = ManualDiscovery.class.getSimpleName();
46     private static final boolean DEBUG = false;
47 
48     private static final String CACHE_FILE = TAG + ".json";
49 
50     // Likely paths at which a print service may be found
51     private static final String[] IPP_PATHS = {"ipp/printer", "ipp/print", "ipp", ""};
52 
53     private static final int DEFAULT_IPP_PORT = 631;
54     private static final String DEFAULT_IPP_SCHEME = "ipp";
55 
56     private final List<DiscoveredPrinter> mManualPrinters = new ArrayList<>();
57 
ManualDiscovery(BuiltInPrintService printService)58     public ManualDiscovery(BuiltInPrintService printService) {
59         super(printService);
60         load();
61     }
62 
63     @Override
onStart()64     void onStart() {
65         if (DEBUG) Log.d(TAG, "onStart");
66         for (DiscoveredPrinter printer : mManualPrinters) {
67             if (DEBUG) Log.d(TAG, "reporting " + printer);
68             getPrintService().getCapabilitiesCache().evictOnNetworkChange(printer.getUri());
69             printerFound(printer);
70         }
71     }
72 
73     @Override
onStop()74     void onStop() {
75         if (DEBUG) Log.d(TAG, "onStop");
76     }
77 
78     @Override
close()79     public void close() {
80         if (DEBUG) Log.d(TAG, "close");
81         save();
82     }
83 
84     /**
85      * Asynchronously attempt to add a new manual printer, calling back with success
86      */
addManualPrinter(String hostname, PrinterAddCallback callback)87     public void addManualPrinter(String hostname, PrinterAddCallback callback) {
88         if (DEBUG) Log.d(TAG, "addManualPrinter " + hostname);
89 
90         // Repair supplied hostname as much as possible
91         Uri base = Uri.parse(DEFAULT_IPP_SCHEME + "://" + hostname + ":" + DEFAULT_IPP_PORT);
92 
93         new CapabilitiesFinder(base, callback).startNext();
94     }
95 
addManualPrinter(DiscoveredPrinter printer)96     private void addManualPrinter(DiscoveredPrinter printer) {
97         // Remove any prior printer with the same uri or path
98         for (DiscoveredPrinter other : new ArrayList<>(mManualPrinters)) {
99             if (other.getUri().equals(printer.getUri())) {
100                 printerLost(other.getUri());
101                 mManualPrinters.remove(other);
102             }
103         }
104 
105         // Add new printer at top
106         mManualPrinters.add(0, printer);
107 
108         // Notify if necessary
109         if (isStarted()) printerFound(printer);
110     }
111 
112     /**
113      * Remove an existing manual printer
114      */
removeManualPrinter(DiscoveredPrinter toRemove)115     public void removeManualPrinter(DiscoveredPrinter toRemove) {
116         for (DiscoveredPrinter printer : mManualPrinters) {
117             if (printer.path.equals(toRemove.path)) {
118                 mManualPrinters.remove(printer);
119                 if (isStarted()) {
120                     printerLost(printer.getUri());
121                 }
122                 break;
123             }
124         }
125     }
126 
127     /**
128      * Persist the current set of manual printers to storage
129      */
save()130     private void save() {
131         File cachedPrintersFile = new File(getPrintService().getCacheDir(), CACHE_FILE);
132         if (cachedPrintersFile.exists()) {
133             cachedPrintersFile.delete();
134         }
135 
136         try (JsonWriter writer = new JsonWriter(new BufferedWriter(
137                 new FileWriter(cachedPrintersFile)))) {
138             writer.beginObject();
139             writer.name("manualPrinters");
140             writer.beginArray();
141             for (DiscoveredPrinter printer : mManualPrinters) {
142                 if (DEBUG) Log.d(TAG, "Writing " + printer);
143                 printer.write(writer);
144             }
145             writer.endArray();
146             writer.endObject();
147         } catch (NullPointerException | IOException e) {
148             Log.w(TAG, "Error while storing", e);
149         }
150     }
151 
152     /**
153      * Load the current set of manual printers from storage
154      */
load()155     private void load() {
156         File cachedPrintersFile = new File(getPrintService().getCacheDir(), CACHE_FILE);
157         if (!cachedPrintersFile.exists()) return;
158 
159         try (JsonReader reader = new JsonReader(new BufferedReader(
160                 new FileReader(cachedPrintersFile)))) {
161             reader.beginObject();
162             while (reader.hasNext()) {
163                 String itemName = reader.nextName();
164                 if (itemName.equals("manualPrinters")) {
165                     reader.beginArray();
166                     while (reader.hasNext()) {
167                         addManualPrinter(new DiscoveredPrinter(reader));
168                     }
169                     reader.endArray();
170                 }
171             }
172             reader.endObject();
173         } catch (IllegalStateException | IOException ignored) {
174             Log.w(TAG, "Error while restoring", ignored);
175         }
176         if (DEBUG) Log.d(TAG, "After load we have " + mManualPrinters.size() + " manual printers");
177     }
178 
179     /** Used to convey response to {@link #addManualPrinter} */
180     public interface PrinterAddCallback {
181         /**
182          * The requested manual printer was found.
183          *
184          * @param printer   information about the discovered printer
185          * @param supported true if the printer is supported (and was therefore added), or false
186          *                  if the printer was found but is not supported (and was therefore not
187          *                  added)
188          */
onFound(DiscoveredPrinter printer, boolean supported)189         void onFound(DiscoveredPrinter printer, boolean supported);
190 
191         /**
192          * The requested manual printer was not found.
193          */
onNotFound()194         void onNotFound();
195     }
196 
197     /**
198      * Search common printer paths for a successful response
199      */
200     private class CapabilitiesFinder implements CapabilitiesCache.OnLocalPrinterCapabilities {
201         private final LinkedList<String> mPaths = new LinkedList<>();
202         private final PrinterAddCallback mFinalCallback;
203         private final Uri mBase;
204 
205         /**
206          * Constructs a new callback handler
207          *
208          * @param base     Base URI for print service to find
209          * @param callback Callback to issue when the first successful response arrives, or
210          *                 when all responses have failed.
211          */
CapabilitiesFinder(Uri base, PrinterAddCallback callback)212         CapabilitiesFinder(Uri base, PrinterAddCallback callback) {
213             mPaths.addAll(Arrays.asList(IPP_PATHS));
214             mFinalCallback = callback;
215             mBase = base;
216         }
217 
218         /** Move on to the next path or report failure if none remain */
startNext()219         void startNext() {
220             if (mPaths.isEmpty()) {
221                 mFinalCallback.onNotFound();
222             } else {
223                 Uri uriToTry = mBase.buildUpon().encodedPath(mPaths.pop()).build();
224                 DiscoveredPrinter printer = new DiscoveredPrinter(null, "unknown", uriToTry, null);
225                 getPrintService().getCapabilitiesCache().request(printer, false, this);
226             }
227         }
228 
229         @Override
onCapabilities(LocalPrinterCapabilities capabilities)230         public void onCapabilities(LocalPrinterCapabilities capabilities) {
231             if (DEBUG) Log.d(TAG, "onCapabilities: " + capabilities);
232 
233             if (capabilities == null) {
234                 startNext();
235                 return;
236             }
237 
238             // Deliver a successful response
239             Uri path = Uri.parse(capabilities.path);
240             if (path.getPort() == -1) {
241                 // Fix missing port
242                 path = path.buildUpon().encodedAuthority(path.getAuthority() + ":" +
243                         DEFAULT_IPP_PORT).build();
244             }
245             Uri uuid = TextUtils.isEmpty(capabilities.uuid) ? null : Uri.parse(capabilities.uuid);
246             String name = TextUtils.isEmpty(capabilities.name) ? path.getHost() : capabilities.name;
247 
248             DiscoveredPrinter printer = new DiscoveredPrinter(uuid, name, path,
249                     capabilities.location);
250 
251             // Only add supported printers
252             if (capabilities.isSupported) {
253                 addManualPrinter(printer);
254             }
255 
256             mFinalCallback.onFound(printer, capabilities.isSupported);
257         }
258     }
259 }