1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.bips.p2p;
18 
19 import android.net.wifi.p2p.WifiP2pDevice;
20 import android.net.wifi.p2p.WifiP2pInfo;
21 import android.util.Log;
22 
23 import com.android.bips.BuiltInPrintService;
24 import com.android.bips.DelayedAction;
25 import com.android.bips.discovery.ConnectionListener;
26 import com.android.bips.discovery.DiscoveredPrinter;
27 import com.android.bips.discovery.Discovery;
28 import com.android.bips.discovery.P2pDiscovery;
29 import com.android.bips.jni.LocalPrinterCapabilities;
30 
31 import java.net.Inet4Address;
32 import java.net.NetworkInterface;
33 import java.net.SocketException;
34 import java.net.UnknownHostException;
35 
36 /**
37  * Manage the process of connecting to a P2P device, discovering a printer on the new network, and
38  * verifying its capabilities.
39  */
40 public class P2pPrinterConnection implements Discovery.Listener, P2pConnectionListener,
41         P2pPeerListener {
42     private static final String TAG = P2pPrinterConnection.class.getSimpleName();
43     private static final boolean DEBUG = false;
44     private static final int TIMEOUT_DISCOVERY = 15 * 1000;
45 
46     private final BuiltInPrintService mService;
47     private final Discovery mMdnsDiscovery;
48     private ConnectionListener mListener;
49 
50     private DiscoveredPrinter mPrinter;
51     private WifiP2pDevice mPeer;
52     private NetworkInterface mInterface;
53     private DelayedAction mMdnsDiscoveryTimeout;
54 
P2pPrinterConnection(BuiltInPrintService service, ConnectionListener listener)55     private P2pPrinterConnection(BuiltInPrintService service, ConnectionListener listener) {
56         mService = service;
57         mListener = listener;
58         mMdnsDiscovery = mService.getMdnsDiscovery();
59     }
60 
61     /** Create a new connection to a known P2P peer device */
P2pPrinterConnection(BuiltInPrintService service, WifiP2pDevice peer, ConnectionListener listener)62     public P2pPrinterConnection(BuiltInPrintService service, WifiP2pDevice peer,
63             ConnectionListener listener) {
64         this(service, listener);
65         if (DEBUG) Log.d(TAG, "Connecting to " + P2pMonitor.toString(peer));
66         connectToPeer(peer);
67     }
68 
69     /** Re-discover and create a new connection to a previously discovered P2P device */
P2pPrinterConnection(BuiltInPrintService service, DiscoveredPrinter printer, ConnectionListener listener)70     public P2pPrinterConnection(BuiltInPrintService service, DiscoveredPrinter printer,
71             ConnectionListener listener) {
72         this(service, listener);
73         if (DEBUG) Log.d(TAG, "Connecting to " + printer);
74         if (P2pUtils.isOnConnectedInterface(service, printer)) {
75             WifiP2pDevice peer = service.getP2pMonitor().getConnection().getPeer();
76             connectToPeer(peer);
77             return;
78         }
79 
80         mPrinter = printer;
81         service.getP2pMonitor().discover(this);
82     }
83 
connectToPeer(WifiP2pDevice peer)84     private void connectToPeer(WifiP2pDevice peer) {
85         mPeer = peer;
86         mService.getP2pMonitor().connect(mPeer, this);
87     }
88 
89     @Override
onPeerFound(WifiP2pDevice peer)90     public void onPeerFound(WifiP2pDevice peer) {
91         if (mListener == null) {
92             return;
93         }
94 
95         String address = mPrinter.path.getHost().replaceAll("-", ":");
96 
97         if (peer.deviceAddress.equals(address)) {
98             mService.getP2pMonitor().stopDiscover(this);
99             // Stop discovery and start validation
100             connectToPeer(peer);
101         }
102     }
103 
104     @Override
onPeerLost(WifiP2pDevice peer)105     public void onPeerLost(WifiP2pDevice peer) {
106         // Ignored
107     }
108 
109     @Override
onConnectionOpen(String networkInterface, WifiP2pInfo info)110     public void onConnectionOpen(String networkInterface, WifiP2pInfo info) {
111         if (mListener == null) {
112             return;
113         }
114 
115         try {
116             mInterface = NetworkInterface.getByName(networkInterface);
117         } catch (SocketException ignored) {
118         }
119 
120         if (mInterface == null) {
121             if (DEBUG) Log.d(TAG, "Failed to get interface from " + networkInterface);
122             mListener.onConnectionComplete(null);
123             close();
124             return;
125         }
126 
127         if (DEBUG) Log.d(TAG, "Connected on network interface " + mInterface);
128 
129         // Timeout after a while if MDNS does not find a printer
130         mMdnsDiscoveryTimeout = mService.delay(TIMEOUT_DISCOVERY, () -> {
131             mMdnsDiscovery.stop(this);
132             if (mListener != null) {
133                 mListener.onConnectionComplete(null);
134             }
135             close();
136         });
137 
138         mMdnsDiscovery.start(this);
139     }
140 
141     @Override
onConnectionClosed()142     public void onConnectionClosed() {
143         if (DEBUG) Log.d(TAG, "closed/failed connection to " + P2pMonitor.toString(mPeer));
144         if (mListener != null) {
145             mListener.onConnectionComplete(null);
146         }
147         close();
148     }
149 
150     @Override
onConnectionDelayed(boolean delayed)151     public void onConnectionDelayed(boolean delayed) {
152         if (mListener == null) {
153             return;
154         }
155         mListener.onConnectionDelayed(delayed);
156     }
157 
158     @Override
onPrinterFound(DiscoveredPrinter printer)159     public void onPrinterFound(DiscoveredPrinter printer) {
160         if (DEBUG) Log.d(TAG, "onPrinterFound(" + printer + ")");
161         if (mListener == null) {
162             return;
163         }
164 
165         Inet4Address printerAddress;
166         try {
167             printerAddress = (Inet4Address) Inet4Address.getByName(printer.path.getHost());
168         } catch (UnknownHostException e) {
169             return;
170         }
171 
172         if (mInterface != null && P2pUtils.isOnInterface(mInterface, printerAddress)) {
173             // Stop discovery and start capabilities query
174             mMdnsDiscovery.stop(this);
175             mMdnsDiscoveryTimeout.cancel();
176             mService.getCapabilitiesCache().request(printer, true, capabilities ->
177                     onCapabilities(printer, capabilities));
178         }
179     }
180 
onCapabilities(DiscoveredPrinter printer, LocalPrinterCapabilities capabilities)181     private void onCapabilities(DiscoveredPrinter printer, LocalPrinterCapabilities capabilities) {
182         if (mListener == null) {
183             return;
184         }
185 
186         if (DEBUG) Log.d(TAG, "Printer " + printer + " caps=" + capabilities);
187         if (capabilities == null) {
188             mListener.onConnectionComplete(null);
189             close();
190         } else {
191             // Make a copy of the printer bearing its P2P path
192             DiscoveredPrinter p2pPrinter = new DiscoveredPrinter(printer.uuid, printer.name,
193                     P2pDiscovery.toPath(mPeer), printer.location);
194             mListener.onConnectionComplete(p2pPrinter);
195         }
196     }
197 
198     @Override
onPrinterLost(DiscoveredPrinter printer)199     public void onPrinterLost(DiscoveredPrinter printer) {
200     }
201 
202     /** Close the connection and any intermediate procedures */
close()203     public void close() {
204         if (DEBUG) Log.d(TAG, "close()");
205         mMdnsDiscovery.stop(this);
206         if (mMdnsDiscoveryTimeout != null) {
207             mMdnsDiscoveryTimeout.cancel();
208         }
209         mService.getP2pMonitor().stopDiscover(this);
210         mService.getP2pMonitor().stopConnect(this);
211 
212         // No further notifications
213         mListener = null;
214     }
215 }
216