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.content.Context;
20 import android.net.wifi.p2p.WifiP2pDevice;
21 import android.net.wifi.p2p.WifiP2pInfo;
22 import android.net.wifi.p2p.WifiP2pManager;
23 import android.util.Log;
24 
25 import com.android.bips.BuiltInPrintService;
26 
27 /**
28  * Globally manage P2P discovery and connectivity
29  */
30 public class P2pMonitor {
31     private static final String TAG = P2pMonitor.class.getSimpleName();
32     private static final boolean DEBUG = false;
33 
34     private final BuiltInPrintService mService;
35     private final WifiP2pManager mP2pManager;
36     private P2pDiscoveryProcedure mPeerDiscovery;
37     private P2pConnectionProcedure mConnection;
38     private String mConnectedInterface;
39 
P2pMonitor(BuiltInPrintService service)40     public P2pMonitor(BuiltInPrintService service) {
41         mService = service;
42         mP2pManager = (WifiP2pManager) mService.getSystemService(Context.WIFI_P2P_SERVICE);
43     }
44 
45     /** Return a printable String form of a {@link WifiP2pDevice} */
toString(WifiP2pDevice device)46     public static String toString(WifiP2pDevice device) {
47         if (device == null) {
48             return "null";
49         } else {
50             return device.deviceName + " " + device.deviceAddress + ", status="
51                     + statusString(device.status);
52         }
53     }
54 
statusString(int status)55     private static String statusString(int status) {
56         switch (status) {
57             case WifiP2pDevice.AVAILABLE:
58                 return "available";
59             case WifiP2pDevice.CONNECTED:
60                 return "connected";
61             case WifiP2pDevice.FAILED:
62                 return "failed";
63             case WifiP2pDevice.INVITED:
64                 return "invited";
65             case WifiP2pDevice.UNAVAILABLE:
66                 return "unavailable";
67             default:
68                 return "unknown";
69         }
70     }
71 
72     /**
73      * Start a discovery of Wi-Fi Direct peers until all requests are closed
74      */
discover(P2pPeerListener listener)75     public void discover(P2pPeerListener listener) {
76         if (DEBUG) Log.d(TAG, "discover()");
77 
78         if (mP2pManager == null) {
79             return;
80         }
81         if (mPeerDiscovery == null) {
82             mPeerDiscovery = new P2pDiscoveryProcedure(mService, mP2pManager, listener);
83         } else {
84             mPeerDiscovery.addListener(listener);
85         }
86     }
87 
88     /**
89      * Remove the request to discover having the same listener. When all outstanding requests are
90      * removed, discovery itself is stopped.
91      */
stopDiscover(P2pPeerListener listener)92     public void stopDiscover(P2pPeerListener listener) {
93         if (DEBUG) Log.d(TAG, "stopDiscover");
94         if (mPeerDiscovery != null) {
95             mPeerDiscovery.removeListener(listener);
96             if (mPeerDiscovery.getListeners().isEmpty()) {
97                 mPeerDiscovery.cancel();
98                 mPeerDiscovery = null;
99             }
100         }
101     }
102 
103     /**
104      * Request connection to a peer (which may already be connected) at least until stopped. Keeps
105      * the current connection open as long as it might be useful.
106      */
connect(WifiP2pDevice peer, P2pConnectionListener listener)107     public void connect(WifiP2pDevice peer, P2pConnectionListener listener) {
108         if (DEBUG) Log.d(TAG, "connect(" + toString(peer) + ")");
109 
110         if (mP2pManager == null) {
111             // Device has no P2P support so indicate failure
112             mService.getMainHandler().post(listener::onConnectionClosed);
113             return;
114         }
115 
116         // Check for competing connection
117         if (mConnection != null && !peer.deviceAddress.equals(mConnection.getPeer()
118                 .deviceAddress)) {
119             if (mConnection.getListenerCount() == 1) {
120                 // The only listener is our internal one, so close this connection to make room
121                 mConnection.close();
122                 mConnection = null;
123             } else {
124                 // Cannot open connection
125                 mService.getMainHandler().post(listener::onConnectionClosed);
126                 return;
127             }
128         }
129 
130         // Check for existing connection to the same device
131         if (mConnection == null) {
132             // Create a new connection request with our internal listener
133             mConnection = new P2pConnectionProcedure(mService, mP2pManager, peer,
134                     new P2pConnectionListener() {
135                         @Override
136                         public void onConnectionOpen(String networkInterface, WifiP2pInfo info) {
137                             mConnectedInterface = networkInterface;
138                         }
139 
140                         @Override
141                         public void onConnectionClosed() {
142                             mConnectedInterface = null;
143                         }
144 
145                         @Override
146                         public void onConnectionDelayed(boolean delayed) {
147                         }
148                     });
149         }
150         mConnection.addListener(listener);
151     }
152 
153     /**
154      * Give up on the connection request associated with a listener. The connection will stay
155      * open as long as other requests exist.
156      */
stopConnect(P2pConnectionListener listener)157     void stopConnect(P2pConnectionListener listener) {
158         if (mConnection == null || !mConnection.hasListener(listener)) {
159             return;
160         }
161 
162         if (DEBUG) Log.d(TAG, "stopConnect " + toString(mConnection.getPeer()));
163         mConnection.removeListener(listener);
164 
165         // If current connection attempt is incomplete and no longer required, close it.
166         if (mConnection.getListenerCount() == 1 && mConnectedInterface == null) {
167             if (DEBUG) Log.d(TAG, "Abandoning connection request");
168             mConnection.close();
169             mConnection = null;
170         }
171     }
172 
173     /** Return the current connection procedure, if any */
getConnection()174     P2pConnectionProcedure getConnection() {
175         return mConnection;
176     }
177 
178     /** Return the current connected interface, if any */
getConnectedInterface()179     public String getConnectedInterface() {
180         return mConnectedInterface;
181     }
182 
183     /** Forcibly stops all connections/discoveries in progress, if any */
stopAll()184     public void stopAll() {
185         if (mConnection != null) {
186             mConnection.close();
187             mConnection = null;
188             mConnectedInterface = null;
189         }
190         if (mPeerDiscovery != null) {
191             mPeerDiscovery.cancel();
192             mPeerDiscovery = null;
193         }
194     }
195 }
196