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