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
17package com.android.bips.p2p;
18
19import android.content.BroadcastReceiver;
20import android.content.Context;
21import android.content.Intent;
22import android.net.NetworkInfo;
23import android.net.wifi.WpsInfo;
24import android.net.wifi.p2p.WifiP2pConfig;
25import android.net.wifi.p2p.WifiP2pDevice;
26import android.net.wifi.p2p.WifiP2pDeviceList;
27import android.net.wifi.p2p.WifiP2pGroup;
28import android.net.wifi.p2p.WifiP2pInfo;
29import android.net.wifi.p2p.WifiP2pManager;
30import android.os.Looper;
31import android.util.Log;
32
33import com.android.bips.BuiltInPrintService;
34import com.android.bips.DelayedAction;
35import com.android.bips.util.BroadcastMonitor;
36
37import java.util.List;
38import java.util.concurrent.CopyOnWriteArrayList;
39
40/**
41 * Manage the process of connecting to a previously discovered P2P device
42 */
43public 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
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
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 */
89    public WifiP2pDevice getPeer() {
90        return mPeer;
91    }
92
93    /** Return true if the specified listener is currently listening to this object */
94    boolean hasListener(P2pConnectionListener listener) {
95        return mListeners.contains(listener);
96    }
97
98    void addListener(P2pConnectionListener listener) {
99        if (mInfo != null) {
100            listener.onConnectionOpen(mNetwork, mInfo);
101        }
102        mListeners.add(listener);
103    }
104
105    void removeListener(P2pConnectionListener listener) {
106        mListeners.remove(listener);
107    }
108
109    int getListenerCount() {
110        return mListeners.size();
111    }
112
113    /** Close this connection */
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
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 */
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