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.BroadcastReceiver;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.net.NetworkInfo;
23 import android.net.wifi.WpsInfo;
24 import android.net.wifi.p2p.WifiP2pConfig;
25 import android.net.wifi.p2p.WifiP2pDevice;
26 import android.net.wifi.p2p.WifiP2pDeviceList;
27 import android.net.wifi.p2p.WifiP2pGroup;
28 import android.net.wifi.p2p.WifiP2pInfo;
29 import android.net.wifi.p2p.WifiP2pManager;
30 import android.os.Looper;
31 import android.util.Log;
32 
33 import com.android.bips.BuiltInPrintService;
34 import com.android.bips.DelayedAction;
35 import com.android.bips.util.BroadcastMonitor;
36 
37 import java.util.List;
38 import java.util.concurrent.CopyOnWriteArrayList;
39 
40 /**
41  * Manage the process of connecting to a previously discovered P2P device
42  */
43 public class P2pConnectionProcedure extends BroadcastReceiver {
44     private static final String TAG = P2pConnectionProcedure.class.getSimpleName();
45     private static final boolean DEBUG = false;
46 
47     private static final int P2P_CONNECT_DELAYED_PERIOD = 3000;
48 
49     private final BuiltInPrintService mService;
50     private final WifiP2pManager mP2pManager;
51     private final WifiP2pDevice mPeer;
52     private final BroadcastMonitor mConnectionMonitor;
53     private final List<P2pConnectionListener> mListeners = new CopyOnWriteArrayList<>();
54     private WifiP2pManager.Channel mChannel;
55     private String mNetwork;
56     private WifiP2pInfo mInfo;
57     private boolean mInvited = false;
58     private boolean mDelayed = false;
59     private DelayedAction mDetectDelayed;
60 
P2pConnectionProcedure(BuiltInPrintService service, WifiP2pManager p2pManager, WifiP2pDevice peer, P2pConnectionListener listener)61     P2pConnectionProcedure(BuiltInPrintService service, WifiP2pManager p2pManager,
62             WifiP2pDevice peer, P2pConnectionListener listener) {
63         mService = service;
64         mP2pManager = p2pManager;
65         mPeer = peer;
66         mConnectionMonitor = service.receiveBroadcasts(this,
67                 WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION,
68                 WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
69         if (DEBUG) Log.d(TAG, "Connecting to " + mPeer.deviceAddress);
70         mChannel = mP2pManager.initialize(service, Looper.getMainLooper(), null);
71         mListeners.add(listener);
72         mP2pManager.connect(mChannel, configForPeer(peer), null);
73     }
74 
configForPeer(WifiP2pDevice peer)75     private WifiP2pConfig configForPeer(WifiP2pDevice peer) {
76         WifiP2pConfig config = new WifiP2pConfig();
77         config.deviceAddress = peer.deviceAddress;
78         if (peer.wpsPbcSupported()) {
79             config.wps.setup = WpsInfo.PBC;
80         } else if (peer.wpsKeypadSupported()) {
81             config.wps.setup = WpsInfo.KEYPAD;
82         } else {
83             config.wps.setup = WpsInfo.DISPLAY;
84         }
85         return config;
86     }
87 
88     /** Return the peer associated with this connection procedure */
getPeer()89     public WifiP2pDevice getPeer() {
90         return mPeer;
91     }
92 
93     /** Return true if the specified listener is currently listening to this object */
hasListener(P2pConnectionListener listener)94     boolean hasListener(P2pConnectionListener listener) {
95         return mListeners.contains(listener);
96     }
97 
addListener(P2pConnectionListener listener)98     void addListener(P2pConnectionListener listener) {
99         if (mInfo != null) {
100             listener.onConnectionOpen(mNetwork, mInfo);
101         }
102         mListeners.add(listener);
103     }
104 
removeListener(P2pConnectionListener listener)105     void removeListener(P2pConnectionListener listener) {
106         mListeners.remove(listener);
107     }
108 
getListenerCount()109     int getListenerCount() {
110         return mListeners.size();
111     }
112 
113     /** Close this connection */
close()114     public void close() {
115         if (DEBUG) Log.d(TAG, "stop() for " + mPeer.deviceAddress);
116         mListeners.clear();
117         mConnectionMonitor.close();
118         if (mDetectDelayed != null) {
119             mDetectDelayed.cancel();
120         }
121         if (mChannel != null) {
122             mP2pManager.cancelConnect(mChannel, null);
123             mP2pManager.removeGroup(mChannel, null);
124             mChannel.close();
125             mChannel = null;
126         }
127     }
128 
129     @Override
onReceive(Context context, Intent intent)130     public void onReceive(Context context, Intent intent) {
131         if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(intent.getAction())) {
132             NetworkInfo network = intent.getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO);
133             WifiP2pGroup group = intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP);
134             WifiP2pInfo info = intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO);
135 
136             if (DEBUG) Log.d(TAG, "Connection state=" + network.getState());
137 
138             if (network.isConnected()) {
139                 if (isConnectedToPeer(group)) {
140                     if (DEBUG) Log.d(TAG, "Group=" + group.getNetworkName() + ", info=" + info);
141                     if (mDelayed) {
142                         // We notified a delay in the past, remove this
143                         for (P2pConnectionListener listener : mListeners) {
144                             listener.onConnectionDelayed(false);
145                         }
146                     } else {
147                         // Cancel any future delayed indications
148                         if (mDetectDelayed != null) {
149                             mDetectDelayed.cancel();
150                         }
151                     }
152 
153                     mNetwork = group.getInterface();
154                     mInfo = info;
155                     for (P2pConnectionListener listener : mListeners) {
156                         listener.onConnectionOpen(mNetwork, mInfo);
157                     }
158                 }
159             } else if (mInvited) {
160                 // Only signal connection closure if we reached the invitation phase
161                 for (P2pConnectionListener listener : mListeners) {
162                     listener.onConnectionClosed();
163                 }
164                 close();
165             }
166         } else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(intent.getAction())) {
167             WifiP2pDeviceList list = intent.getParcelableExtra(
168                     WifiP2pManager.EXTRA_P2P_DEVICE_LIST);
169             WifiP2pDevice device = list.get(mPeer.deviceAddress);
170             if (DEBUG) Log.d(TAG, "Peers changed, device is " + P2pMonitor.toString(device));
171 
172             if (!mInvited && device != null && device.status == WifiP2pDevice.INVITED) {
173                 // Upon first invite, start timer to detect delayed connection
174                 mInvited = true;
175                 mDetectDelayed = mService.delay(P2P_CONNECT_DELAYED_PERIOD, () -> {
176                     mDelayed = true;
177                     for (P2pConnectionListener listener : mListeners) {
178                         listener.onConnectionDelayed(true);
179                     }
180                 });
181             }
182         }
183     }
184 
185     /** Return true if group is connected to the peer */
isConnectedToPeer(WifiP2pGroup group)186     private boolean isConnectedToPeer(WifiP2pGroup group) {
187         WifiP2pDevice owner = group.getOwner();
188         if (owner != null && owner.deviceAddress.equals(mPeer.deviceAddress)) {
189             return true;
190         }
191         for (WifiP2pDevice client : group.getClientList()) {
192             if (client.deviceAddress.equals(mPeer.deviceAddress)) {
193                 return true;
194             }
195         }
196         return false;
197     }
198 }
199