• 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;
19 
20 import android.app.Notification;
21 import android.app.NotificationChannel;
22 import android.app.NotificationManager;
23 import android.app.PendingIntent;
24 import android.content.BroadcastReceiver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.pm.PackageInfo;
28 import android.content.pm.PackageManager;
29 import android.graphics.drawable.Icon;
30 import android.net.nsd.NsdManager;
31 import android.net.wifi.WifiManager;
32 import android.os.Handler;
33 import android.print.PrinterId;
34 import android.printservice.PrintJob;
35 import android.printservice.PrintService;
36 import android.printservice.PrinterDiscoverySession;
37 import android.text.TextUtils;
38 import android.util.Log;
39 
40 import com.android.bips.discovery.DelayedDiscovery;
41 import com.android.bips.discovery.DiscoveredPrinter;
42 import com.android.bips.discovery.Discovery;
43 import com.android.bips.discovery.ManualDiscovery;
44 import com.android.bips.discovery.MdnsDiscovery;
45 import com.android.bips.discovery.MultiDiscovery;
46 import com.android.bips.discovery.NsdResolveQueue;
47 import com.android.bips.discovery.P2pDiscovery;
48 import com.android.bips.ipp.Backend;
49 import com.android.bips.ipp.CapabilitiesCache;
50 import com.android.bips.ipp.CertificateStore;
51 import com.android.bips.p2p.P2pMonitor;
52 import com.android.bips.p2p.P2pUtils;
53 import com.android.bips.util.BroadcastMonitor;
54 
55 import java.lang.ref.WeakReference;
56 
57 public class BuiltInPrintService extends PrintService {
58     private static final String TAG = BuiltInPrintService.class.getSimpleName();
59     private static final boolean DEBUG = false;
60     private static final int IPPS_PRINTER_DELAY = 150;
61     private static final int P2P_DISCOVERY_DELAY = 1000;
62     private static final String CHANNEL_ID_SECURITY = "security";
63     private static final String TAG_CERTIFICATE_REQUEST =
64             BuiltInPrintService.class.getCanonicalName() + ".CERTIFICATE_REQUEST";
65     private static final String ACTION_CERTIFICATE_ACCEPT =
66             BuiltInPrintService.class.getCanonicalName() + ".CERTIFICATE_ACCEPT";
67     private static final String ACTION_CERTIFICATE_REJECT =
68             BuiltInPrintService.class.getCanonicalName() + ".CERTIFICATE_REJECT";
69     public static final String ACTION_P2P_PERMISSION_CANCEL =
70             BuiltInPrintService.class.getCanonicalName() + ".P2P_PERMISSION_CANCEL";
71     public static final String ACTION_P2P_DISABLE =
72             BuiltInPrintService.class.getCanonicalName() + ".ACTION_P2P_DISABLE";
73     private static final String EXTRA_CERTIFICATE = "certificate";
74     private static final String EXTRA_PRINTER_ID = "printer-id";
75     private static final String EXTRA_PRINTER_UUID = "printer-uuid";
76     private static final int CERTIFICATE_REQUEST_ID = 1000;
77     public static final int P2P_PERMISSION_REQUEST_ID = 1001;
78 
79     // Present because local activities can bind, but cannot access this object directly
80     private static WeakReference<BuiltInPrintService> sInstance;
81 
82     private MultiDiscovery mAllDiscovery;
83     private P2pDiscovery mP2pDiscovery;
84     private Discovery mMdnsDiscovery;
85     private ManualDiscovery mManualDiscovery;
86     private CapabilitiesCache mCapabilitiesCache;
87     private CertificateStore mCertificateStore;
88     private JobQueue mJobQueue;
89     private Handler mMainHandler;
90     private Backend mBackend;
91     private WifiManager.WifiLock mWifiLock;
92     private P2pMonitor mP2pMonitor;
93     private NsdResolveQueue mNsdResolveQueue;
94     private P2pPermissionManager mP2pPermissionManager;
95 
96     /**
97      * Return the current print service instance, if running
98      */
getInstance()99     public static BuiltInPrintService getInstance() {
100         return sInstance == null ? null : sInstance.get();
101     }
102 
103     @Override
onCreate()104     public void onCreate() {
105         if (DEBUG) {
106             try {
107                 PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
108                 String version = pInfo.versionName;
109                 Log.d(TAG, "onCreate() " + version);
110             } catch (PackageManager.NameNotFoundException ignored) {
111             }
112         }
113         super.onCreate();
114         createNotificationChannel();
115         mP2pPermissionManager = new P2pPermissionManager(this);
116         mP2pPermissionManager.reset();
117 
118         sInstance = new WeakReference<>(this);
119         mBackend = new Backend(this);
120         mCertificateStore = new CertificateStore(this);
121         mCapabilitiesCache = new CapabilitiesCache(this, mBackend,
122                 CapabilitiesCache.DEFAULT_MAX_CONCURRENT);
123         mP2pMonitor = new P2pMonitor(this);
124 
125         NsdManager nsdManager = (NsdManager) getSystemService(Context.NSD_SERVICE);
126         mNsdResolveQueue = new NsdResolveQueue(this, nsdManager);
127 
128         // Delay IPP results so that IPP is preferred
129         Discovery ippDiscovery = new MdnsDiscovery(this, MdnsDiscovery.SCHEME_IPP);
130         Discovery ippsDiscovery = new MdnsDiscovery(this, MdnsDiscovery.SCHEME_IPPS);
131         mMdnsDiscovery = new MultiDiscovery(ippDiscovery, new DelayedDiscovery(ippsDiscovery, 0,
132                 IPPS_PRINTER_DELAY));
133         mP2pDiscovery = new P2pDiscovery(this);
134         mManualDiscovery = new ManualDiscovery(this);
135 
136         // Delay P2P discovery so that all others are found first
137         mAllDiscovery = new MultiDiscovery(mMdnsDiscovery, mManualDiscovery, new DelayedDiscovery(
138                 mP2pDiscovery, P2P_DISCOVERY_DELAY, 0));
139 
140         mJobQueue = new JobQueue();
141         mMainHandler = new Handler(getMainLooper());
142         WifiManager wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
143         mWifiLock = wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL, TAG);
144     }
145 
146     @Override
onDestroy()147     public void onDestroy() {
148         if (DEBUG) Log.d(TAG, "onDestroy()");
149         mP2pPermissionManager.closeNotification();
150         mCapabilitiesCache.close();
151         mP2pMonitor.stopAll();
152         mBackend.close();
153         unlockWifi();
154         sInstance = null;
155         mMainHandler.removeCallbacksAndMessages(null);
156         super.onDestroy();
157     }
158 
159     @Override
onCreatePrinterDiscoverySession()160     protected PrinterDiscoverySession onCreatePrinterDiscoverySession() {
161         if (DEBUG) Log.d(TAG, "onCreatePrinterDiscoverySession");
162         return new LocalDiscoverySession(this);
163     }
164 
165     @Override
onPrintJobQueued(PrintJob printJob)166     protected void onPrintJobQueued(PrintJob printJob) {
167         if (DEBUG) Log.d(TAG, "onPrintJobQueued");
168         mJobQueue.print(new LocalPrintJob(this, mBackend, printJob));
169     }
170 
171     @Override
onRequestCancelPrintJob(PrintJob printJob)172     protected void onRequestCancelPrintJob(PrintJob printJob) {
173         if (DEBUG) Log.d(TAG, "onRequestCancelPrintJob");
174         mJobQueue.cancel(printJob.getId());
175     }
176 
177     /**
178      * Return the global discovery object
179      */
getDiscovery()180     public Discovery getDiscovery() {
181         return mAllDiscovery;
182     }
183 
184     /**
185      * Return the global object for MDNS discoveries
186      */
getMdnsDiscovery()187     public Discovery getMdnsDiscovery() {
188         return mMdnsDiscovery;
189     }
190 
191     /**
192      * Return the global object for manual discoveries
193      */
getManualDiscovery()194     public ManualDiscovery getManualDiscovery() {
195         return mManualDiscovery;
196     }
197 
198     /**
199      * Return the global object for Wi-Fi Direct printer discoveries
200      */
getP2pDiscovery()201     public P2pDiscovery getP2pDiscovery() {
202         return mP2pDiscovery;
203     }
204 
205     /**
206      * Return the global object for general Wi-Fi Direct management
207      */
getP2pMonitor()208     public P2pMonitor getP2pMonitor() {
209         return mP2pMonitor;
210     }
211 
212     /**
213      * Return the global {@link NsdResolveQueue}
214      */
getNsdResolveQueue()215     public NsdResolveQueue getNsdResolveQueue() {
216         return mNsdResolveQueue;
217     }
218 
219     /**
220      * Return a general {@link P2pPermissionManager}
221      */
getP2pPermissionManager()222     public P2pPermissionManager getP2pPermissionManager() {
223         return mP2pPermissionManager;
224     }
225 
226     /**
227      * Listen for a set of broadcast messages until stopped
228      */
receiveBroadcasts(BroadcastReceiver receiver, String... actions)229     public BroadcastMonitor receiveBroadcasts(BroadcastReceiver receiver, String... actions) {
230         return new BroadcastMonitor(this, receiver, actions);
231     }
232 
233     /**
234      * Return the global Printer Capabilities cache
235      */
getCapabilitiesCache()236     public CapabilitiesCache getCapabilitiesCache() {
237         return mCapabilitiesCache;
238     }
239 
240     /**
241      * Return a store of certificate public keys for supporting trust-on-first-use.
242      */
getCertificateStore()243     public CertificateStore getCertificateStore() {
244         return mCertificateStore;
245     }
246 
247     /**
248      * Return the main handler for posting {@link Runnable} objects to the main UI
249      */
getMainHandler()250     public Handler getMainHandler() {
251         return mMainHandler;
252     }
253 
254     /** Run something on the main thread, returning an object that can cancel the request */
delay(int delay, Runnable toRun)255     public DelayedAction delay(int delay, Runnable toRun) {
256         mMainHandler.postDelayed(toRun, delay);
257         return () -> mMainHandler.removeCallbacks(toRun);
258     }
259 
260     /**
261      * Return a friendly description string including host and (if present) location
262      */
getDescription(DiscoveredPrinter printer)263     public String getDescription(DiscoveredPrinter printer) {
264         if (P2pUtils.isP2p(printer) || P2pUtils.isOnConnectedInterface(this, printer)) {
265             return getString(R.string.wifi_direct);
266         }
267 
268         String host = printer.getHost();
269         if (!TextUtils.isEmpty(printer.location)) {
270             return getString(R.string.printer_description, host, printer.location);
271         } else {
272             return host;
273         }
274     }
275 
276     /** Prevent Wi-Fi from going to sleep until {@link #unlockWifi} is called */
lockWifi()277     public void lockWifi() {
278         if (!mWifiLock.isHeld()) {
279             mWifiLock.acquire();
280         }
281     }
282 
283     /** Allow Wi-Fi to be disabled during sleep modes. */
unlockWifi()284     public void unlockWifi() {
285         if (mWifiLock.isHeld()) {
286             mWifiLock.release();
287         }
288     }
289 
290     /**
291      * Set up a channel for notifications.
292      */
createNotificationChannel()293     private void createNotificationChannel() {
294         NotificationChannel channel = new NotificationChannel(CHANNEL_ID_SECURITY,
295                 getString(R.string.security), NotificationManager.IMPORTANCE_HIGH);
296 
297         NotificationManager manager = (NotificationManager) getSystemService(
298                 Context.NOTIFICATION_SERVICE);
299         manager.createNotificationChannel(channel);
300     }
301 
302     /**
303      * Notify the user of a certificate change (could be a MITM attack) and allow response.
304      *
305      * When certificate is null, the printer is being downgraded to no-encryption.
306      */
notifyCertificateChange(String printerName, PrinterId printerId, String printerUuid, byte[] certificate)307     void notifyCertificateChange(String printerName, PrinterId printerId, String printerUuid,
308                                  byte[] certificate) {
309         String message;
310         if (certificate == null) {
311             message = getString(R.string.not_encrypted_request);
312         } else {
313             message = getString(R.string.certificate_update_request);
314         }
315 
316         Intent rejectIntent = new Intent(this, BuiltInPrintService.class)
317                 .setAction(ACTION_CERTIFICATE_REJECT)
318                 .putExtra(EXTRA_PRINTER_ID, printerId);
319         PendingIntent pendingRejectIntent = PendingIntent.getService(this, CERTIFICATE_REQUEST_ID,
320                 rejectIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
321         Notification.Action rejectAction = new Notification.Action.Builder(
322                 Icon.createWithResource(this, R.drawable.ic_printservice),
323                 getString(R.string.reject), pendingRejectIntent).build();
324 
325         PendingIntent deleteIntent = PendingIntent.getService(this, CERTIFICATE_REQUEST_ID,
326                 rejectIntent,  PendingIntent.FLAG_IMMUTABLE);
327 
328         Intent acceptIntent = new Intent(this, BuiltInPrintService.class)
329                 .setAction(ACTION_CERTIFICATE_ACCEPT)
330                 .putExtra(EXTRA_PRINTER_UUID, printerUuid)
331                 .putExtra(EXTRA_PRINTER_ID, printerId);
332         if (certificate != null) {
333             acceptIntent.putExtra(EXTRA_CERTIFICATE, certificate);
334         }
335         PendingIntent pendingAcceptIntent = PendingIntent.getService(this, CERTIFICATE_REQUEST_ID,
336                 acceptIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
337         Notification.Action acceptAction = new Notification.Action.Builder(
338                 Icon.createWithResource(this, R.drawable.ic_printservice),
339                 getString(R.string.accept), pendingAcceptIntent).build();
340 
341         Notification notification = new Notification.Builder(this, CHANNEL_ID_SECURITY)
342                 .setContentTitle(printerName)
343                 .setSmallIcon(R.drawable.ic_printservice)
344                 .setStyle(new Notification.BigTextStyle().bigText(message))
345                 .setContentText(message)
346                 .setAutoCancel(true)
347                 .addAction(rejectAction)
348                 .addAction(acceptAction)
349                 .setDeleteIntent(deleteIntent)
350                 .build();
351 
352         NotificationManager manager = (NotificationManager) getSystemService(
353                 Context.NOTIFICATION_SERVICE);
354         manager.notify(TAG_CERTIFICATE_REQUEST, CERTIFICATE_REQUEST_ID, notification);
355     }
356 
357     @Override
onStartCommand(Intent intent, int flags, int startId)358     public int onStartCommand(Intent intent, int flags, int startId) {
359         if (DEBUG) Log.d(TAG, "Received action=" + intent.getAction());
360         NotificationManager manager = (NotificationManager) getSystemService(
361                 Context.NOTIFICATION_SERVICE);
362         if (ACTION_CERTIFICATE_ACCEPT.equals(intent.getAction())) {
363             byte[] certificate = intent.getByteArrayExtra(EXTRA_CERTIFICATE);
364             PrinterId printerId = intent.getParcelableExtra(EXTRA_PRINTER_ID);
365             String printerUuid = intent.getStringExtra(EXTRA_PRINTER_UUID);
366             if (certificate != null) {
367                 mCertificateStore.put(printerUuid, certificate);
368             } else {
369                 mCertificateStore.remove(printerUuid);
370             }
371             // Restart the job with the updated certificate in place
372             mJobQueue.restart(printerId);
373             manager.cancel(TAG_CERTIFICATE_REQUEST, CERTIFICATE_REQUEST_ID);
374         } else if (ACTION_CERTIFICATE_REJECT.equals(intent.getAction())) {
375             // Cancel any job in certificate state for this uuid
376             PrinterId printerId = intent.getParcelableExtra(EXTRA_PRINTER_ID);
377             mJobQueue.cancel(printerId);
378             manager.cancel(TAG_CERTIFICATE_REQUEST, CERTIFICATE_REQUEST_ID);
379         } else if (ACTION_P2P_PERMISSION_CANCEL.equals(intent.getAction())) {
380             // Inform p2pPermissionManager the user canceled the notification (non-permanent)
381             mP2pPermissionManager.applyPermissionChange(false);
382         } else if (ACTION_P2P_DISABLE.equals(intent.getAction())) {
383             mP2pPermissionManager.applyPermissionChange(true);
384         }
385         return START_NOT_STICKY;
386     }
387 }
388