blob: 5b0e424894902dbd7f6c035c73c98cc117eae899 [file] [log] [blame]
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.net.wifi.p2p;
import android.app.AlertDialog;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.net.IConnectivityManager;
import android.net.ConnectivityManager;
import android.net.DhcpInfoInternal;
import android.net.DhcpStateMachine;
import android.net.InterfaceConfiguration;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkInfo;
import android.net.NetworkUtils;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiMonitor;
import android.net.wifi.WifiNative;
import android.net.wifi.WifiStateMachine;
import android.net.wifi.WpsInfo;
import android.os.Binder;
import android.os.IBinder;
import android.os.INetworkManagementService;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Slog;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.TextView;
import com.android.internal.R;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.util.AsyncChannel;
import com.android.internal.util.Protocol;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Collection;
/**
* WifiP2pService inclues a state machine to perform Wi-Fi p2p operations. Applications
* communicate with this service to issue device discovery and connectivity requests
* through the WifiP2pManager interface. The state machine communicates with the wifi
* driver through wpa_supplicant and handles the event responses through WifiMonitor.
*
* Note that the term Wifi when used without a p2p suffix refers to the client mode
* of Wifi operation
* @hide
*/
public class WifiP2pService extends IWifiP2pManager.Stub {
private static final String TAG = "WifiP2pService";
private static final boolean DBG = false;
private static final String NETWORKTYPE = "WIFI_P2P";
private Context mContext;
private String mInterface;
private Notification mNotification;
INetworkManagementService mNwService;
private DhcpStateMachine mDhcpStateMachine;
private P2pStateMachine mP2pStateMachine;
private AsyncChannel mReplyChannel = new AsyncChannel();
private AsyncChannel mWifiChannel;
private static final Boolean JOIN_GROUP = true;
private static final Boolean FORM_GROUP = false;
/* Two minutes comes from the wpa_supplicant setting */
private static final int GROUP_CREATING_WAIT_TIME_MS = 120 * 1000;
private static int mGroupCreatingTimeoutIndex = 0;
/* Set a two minute discover timeout to avoid STA scans from being blocked */
private static final int DISCOVER_TIMEOUT_S = 120;
/**
* Delay between restarts upon failure to setup connection with supplicant
*/
private static final int P2P_RESTART_INTERVAL_MSECS = 5000;
/**
* Number of times we attempt to restart p2p
*/
private static final int P2P_RESTART_TRIES = 5;
private int mP2pRestartCount = 0;
private static final int BASE = Protocol.BASE_WIFI_P2P_SERVICE;
/* Delayed message to timeout group creation */
public static final int GROUP_CREATING_TIMED_OUT = BASE + 1;
/* User accepted a peer request */
private static final int PEER_CONNECTION_USER_ACCEPT = BASE + 2;
/* User rejected a peer request */
private static final int PEER_CONNECTION_USER_REJECT = BASE + 3;
private final boolean mP2pSupported;
private WifiP2pDevice mThisDevice = new WifiP2pDevice();
/* When a group has been explicitly created by an app, we persist the group
* even after all clients have been disconnected until an explicit remove
* is invoked */
private boolean mPersistGroup;
private NetworkInfo mNetworkInfo;
/* Is chosen as a unique range to avoid conflict with
the range defined in Tethering.java */
private static final String[] DHCP_RANGE = {"192.168.49.2", "192.168.49.254"};
private static final String SERVER_ADDRESS = "192.168.49.1";
public WifiP2pService(Context context) {
mContext = context;
//STOPSHIP: get this from native side
mInterface = "p2p0";
mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI_P2P, 0, NETWORKTYPE, "");
mP2pSupported = mContext.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_WIFI_DIRECT);
mThisDevice.primaryDeviceType = mContext.getResources().getString(
com.android.internal.R.string.config_wifi_p2p_device_type);
mThisDevice.deviceName = getDefaultDeviceName();
mP2pStateMachine = new P2pStateMachine(TAG, mP2pSupported);
mP2pStateMachine.start();
}
public void connectivityServiceReady() {
IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
mNwService = INetworkManagementService.Stub.asInterface(b);
}
private void enforceAccessPermission() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_WIFI_STATE,
"WifiP2pService");
}
private void enforceChangePermission() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE,
"WifiP2pService");
}
/* We use the 4 digits of the ANDROID_ID to have a friendly
* default that has low likelihood of collision with a peer */
private String getDefaultDeviceName() {
String id = Settings.Secure.getString(mContext.getContentResolver(),
Settings.Secure.ANDROID_ID);
return "Android_" + id.substring(0,4);
}
/**
* Get a reference to handler. This is used by a client to establish
* an AsyncChannel communication with WifiP2pService
*/
public Messenger getMessenger() {
enforceAccessPermission();
enforceChangePermission();
return new Messenger(mP2pStateMachine.getHandler());
}
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
pw.println("Permission Denial: can't dump WifiP2pService from from pid="
+ Binder.getCallingPid()
+ ", uid=" + Binder.getCallingUid());
return;
}
}
/**
* Handles interaction with WifiStateMachine
*/
private class P2pStateMachine extends StateMachine {
private DefaultState mDefaultState = new DefaultState();
private P2pNotSupportedState mP2pNotSupportedState = new P2pNotSupportedState();
private P2pDisablingState mP2pDisablingState = new P2pDisablingState();
private P2pDisabledState mP2pDisabledState = new P2pDisabledState();
private P2pEnablingState mP2pEnablingState = new P2pEnablingState();
private P2pEnabledState mP2pEnabledState = new P2pEnabledState();
// Inactive is when p2p is enabled with no connectivity
private InactiveState mInactiveState = new InactiveState();
private GroupCreatingState mGroupCreatingState = new GroupCreatingState();
private UserAuthorizingInvitationState mUserAuthorizingInvitationState
= new UserAuthorizingInvitationState();
private ProvisionDiscoveryState mProvisionDiscoveryState = new ProvisionDiscoveryState();
private GroupNegotiationState mGroupNegotiationState = new GroupNegotiationState();
private GroupCreatedState mGroupCreatedState = new GroupCreatedState();
private UserAuthorizingJoinState mUserAuthorizingJoinState = new UserAuthorizingJoinState();
private WifiNative mWifiNative = new WifiNative(mInterface);
private WifiMonitor mWifiMonitor = new WifiMonitor(this, mWifiNative);
private WifiP2pDeviceList mPeers = new WifiP2pDeviceList();
private WifiP2pInfo mWifiP2pInfo = new WifiP2pInfo();
private WifiP2pGroup mGroup;
// Saved WifiP2pConfig for a peer connection
private WifiP2pConfig mSavedPeerConfig;
// Saved WifiP2pGroup from invitation request
private WifiP2pGroup mSavedP2pGroup;
P2pStateMachine(String name, boolean p2pSupported) {
super(name);
addState(mDefaultState);
addState(mP2pNotSupportedState, mDefaultState);
addState(mP2pDisablingState, mDefaultState);
addState(mP2pDisabledState, mDefaultState);
addState(mP2pEnablingState, mDefaultState);
addState(mP2pEnabledState, mDefaultState);
addState(mInactiveState, mP2pEnabledState);
addState(mGroupCreatingState, mP2pEnabledState);
addState(mUserAuthorizingInvitationState, mGroupCreatingState);
addState(mProvisionDiscoveryState, mGroupCreatingState);
addState(mGroupNegotiationState, mGroupCreatingState);
addState(mGroupCreatedState, mP2pEnabledState);
addState(mUserAuthorizingJoinState, mGroupCreatedState);
if (p2pSupported) {
setInitialState(mP2pDisabledState);
} else {
setInitialState(mP2pNotSupportedState);
}
}
class DefaultState extends State {
@Override
public boolean processMessage(Message message) {
if (DBG) logd(getName() + message.toString());
switch (message.what) {
case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
if (message.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
if (DBG) logd("Full connection with WifiStateMachine established");
mWifiChannel = (AsyncChannel) message.obj;
} else {
loge("Full connection failure, error = " + message.arg1);
mWifiChannel = null;
}
break;
case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
if (message.arg1 == AsyncChannel.STATUS_SEND_UNSUCCESSFUL) {
loge("Send failed, client connection lost");
} else {
loge("Client connection lost with reason: " + message.arg1);
}
mWifiChannel = null;
break;
case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION:
AsyncChannel ac = new AsyncChannel();
ac.connect(mContext, getHandler(), message.replyTo);
break;
case WifiP2pManager.DISCOVER_PEERS:
replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED,
WifiP2pManager.BUSY);
break;
case WifiP2pManager.STOP_DISCOVERY:
replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_FAILED,
WifiP2pManager.BUSY);
break;
case WifiP2pManager.CONNECT:
replyToMessage(message, WifiP2pManager.CONNECT_FAILED,
WifiP2pManager.BUSY);
break;
case WifiP2pManager.CANCEL_CONNECT:
replyToMessage(message, WifiP2pManager.CANCEL_CONNECT_FAILED,
WifiP2pManager.BUSY);
break;
case WifiP2pManager.CREATE_GROUP:
replyToMessage(message, WifiP2pManager.CREATE_GROUP_FAILED,
WifiP2pManager.BUSY);
break;
case WifiP2pManager.REMOVE_GROUP:
replyToMessage(message, WifiP2pManager.REMOVE_GROUP_FAILED,
WifiP2pManager.BUSY);
break;
case WifiP2pManager.REQUEST_PEERS:
replyToMessage(message, WifiP2pManager.RESPONSE_PEERS, mPeers);
break;
case WifiP2pManager.REQUEST_CONNECTION_INFO:
replyToMessage(message, WifiP2pManager.RESPONSE_CONNECTION_INFO, mWifiP2pInfo);
break;
case WifiP2pManager.REQUEST_GROUP_INFO:
replyToMessage(message, WifiP2pManager.RESPONSE_GROUP_INFO, mGroup);
break;
// Ignore
case WifiMonitor.P2P_INVITATION_RESULT_EVENT:
case WifiMonitor.SCAN_RESULTS_EVENT:
case WifiMonitor.SUP_CONNECTION_EVENT:
case WifiMonitor.SUP_DISCONNECTION_EVENT:
case WifiMonitor.NETWORK_CONNECTION_EVENT:
case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
case PEER_CONNECTION_USER_ACCEPT:
case PEER_CONNECTION_USER_REJECT:
case GROUP_CREATING_TIMED_OUT:
break;
default:
loge("Unhandled message " + message);
return NOT_HANDLED;
}
return HANDLED;
}
}
class P2pNotSupportedState extends State {
@Override
public boolean processMessage(Message message) {
switch (message.what) {
case WifiP2pManager.DISCOVER_PEERS:
replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED,
WifiP2pManager.P2P_UNSUPPORTED);
break;
case WifiP2pManager.STOP_DISCOVERY:
replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_FAILED,
WifiP2pManager.P2P_UNSUPPORTED);
break;
case WifiP2pManager.CONNECT:
replyToMessage(message, WifiP2pManager.CONNECT_FAILED,
WifiP2pManager.P2P_UNSUPPORTED);
break;
case WifiP2pManager.CANCEL_CONNECT:
replyToMessage(message, WifiP2pManager.CANCEL_CONNECT_FAILED,
WifiP2pManager.P2P_UNSUPPORTED);
break;
case WifiP2pManager.CREATE_GROUP:
replyToMessage(message, WifiP2pManager.CREATE_GROUP_FAILED,
WifiP2pManager.P2P_UNSUPPORTED);
break;
case WifiP2pManager.REMOVE_GROUP:
replyToMessage(message, WifiP2pManager.REMOVE_GROUP_FAILED,
WifiP2pManager.P2P_UNSUPPORTED);
break;
default:
return NOT_HANDLED;
}
return HANDLED;
}
}
class P2pDisablingState extends State {
@Override
public boolean processMessage(Message message) {
if (DBG) logd(getName() + message.toString());
switch (message.what) {
case WifiMonitor.SUP_DISCONNECTION_EVENT:
if (DBG) logd("p2p socket connection lost");
transitionTo(mP2pDisabledState);
break;
case WifiStateMachine.CMD_ENABLE_P2P:
case WifiStateMachine.CMD_DISABLE_P2P:
deferMessage(message);
break;
default:
return NOT_HANDLED;
}
return HANDLED;
}
}
class P2pDisabledState extends State {
@Override
public void enter() {
if (DBG) logd(getName());
}
@Override
public boolean processMessage(Message message) {
if (DBG) logd(getName() + message.toString());
switch (message.what) {
case WifiStateMachine.CMD_ENABLE_P2P:
try {
mNwService.setInterfaceUp(mInterface);
} catch (RemoteException re) {
loge("Unable to change interface settings: " + re);
} catch (IllegalStateException ie) {
loge("Unable to change interface settings: " + ie);
}
mWifiMonitor.startMonitoring();
transitionTo(mP2pEnablingState);
break;
case WifiStateMachine.CMD_DISABLE_P2P:
//Nothing to do
break;
default:
return NOT_HANDLED;
}
return HANDLED;
}
}
class P2pEnablingState extends State {
@Override
public void enter() {
if (DBG) logd(getName());
}
@Override
public boolean processMessage(Message message) {
if (DBG) logd(getName() + message.toString());
switch (message.what) {
case WifiMonitor.SUP_CONNECTION_EVENT:
if (DBG) logd("P2p socket connection successful");
transitionTo(mInactiveState);
break;
case WifiMonitor.SUP_DISCONNECTION_EVENT:
loge("P2p socket connection failed");
transitionTo(mP2pDisabledState);
break;
case WifiStateMachine.CMD_ENABLE_P2P:
case WifiStateMachine.CMD_DISABLE_P2P:
deferMessage(message);
break;
default:
return NOT_HANDLED;
}
return HANDLED;
}
}
class P2pEnabledState extends State {
@Override
public void enter() {
if (DBG) logd(getName());
sendP2pStateChangedBroadcast(true);
mNetworkInfo.setIsAvailable(true);
initializeP2pSettings();
}
@Override
public boolean processMessage(Message message) {
if (DBG) logd(getName() + message.toString());
switch (message.what) {
case WifiStateMachine.CMD_ENABLE_P2P:
//Nothing to do
break;
case WifiStateMachine.CMD_DISABLE_P2P:
if (mPeers.clear()) sendP2pPeersChangedBroadcast();
mWifiNative.closeSupplicantConnection();
transitionTo(mP2pDisablingState);
break;
case WifiP2pManager.DISCOVER_PEERS:
if (mWifiNative.p2pFind(DISCOVER_TIMEOUT_S)) {
replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_SUCCEEDED);
} else {
replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED,
WifiP2pManager.ERROR);
}
break;
case WifiP2pManager.STOP_DISCOVERY:
if (mWifiNative.p2pStopFind()) {
replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_SUCCEEDED);
} else {
replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_FAILED,
WifiP2pManager.ERROR);
}
break;
case WifiMonitor.P2P_DEVICE_FOUND_EVENT:
WifiP2pDevice device = (WifiP2pDevice) message.obj;
if (mThisDevice.deviceAddress.equals(device.deviceAddress)) break;
mPeers.update(device);
sendP2pPeersChangedBroadcast();
break;
case WifiMonitor.P2P_DEVICE_LOST_EVENT:
device = (WifiP2pDevice) message.obj;
if (mPeers.remove(device)) sendP2pPeersChangedBroadcast();
break;
default:
return NOT_HANDLED;
}
return HANDLED;
}
@Override
public void exit() {
sendP2pStateChangedBroadcast(false);
mNetworkInfo.setIsAvailable(false);
}
}
class InactiveState extends State {
@Override
public void enter() {
if (DBG) logd(getName());
//Start listening every time we get inactive
//TODO: Fix listen after driver behavior is fixed
//mWifiNative.p2pListen();
}
@Override
public boolean processMessage(Message message) {
if (DBG) logd(getName() + message.toString());
switch (message.what) {
case WifiP2pManager.CONNECT:
if (DBG) logd(getName() + " sending connect");
mSavedPeerConfig = (WifiP2pConfig) message.obj;
String updatedPeerDetails = mWifiNative.p2pPeer(mSavedPeerConfig.deviceAddress);
mPeers.update(new WifiP2pDevice(updatedPeerDetails));
mPersistGroup = false;
int netId = configuredNetworkId(mSavedPeerConfig.deviceAddress);
if (netId >= 0) {
//TODO: if failure, remove config and do a regular p2pConnect()
mWifiNative.p2pReinvoke(netId, mSavedPeerConfig.deviceAddress);
} else {
//Stop discovery before issuing connect
mWifiNative.p2pStopFind();
//If peer is a GO, we do not need to send provisional discovery,
//the supplicant takes care of it.
if (isGroupOwner(mSavedPeerConfig.deviceAddress)) {
p2pConnectWithPinDisplay(mSavedPeerConfig, JOIN_GROUP);
transitionTo(mGroupNegotiationState);
} else {
transitionTo(mProvisionDiscoveryState);
}
}
updateDeviceStatus(mSavedPeerConfig.deviceAddress, WifiP2pDevice.INVITED);
sendP2pPeersChangedBroadcast();
replyToMessage(message, WifiP2pManager.CONNECT_SUCCEEDED);
break;
case WifiMonitor.P2P_GO_NEGOTIATION_REQUEST_EVENT:
mSavedPeerConfig = (WifiP2pConfig) message.obj;
transitionTo(mUserAuthorizingInvitationState);
break;
case WifiMonitor.P2P_INVITATION_RECEIVED_EVENT:
WifiP2pGroup group = (WifiP2pGroup) message.obj;
WifiP2pDevice owner = group.getOwner();
if (owner == null) {
if (DBG) loge("Ignored invitation from null owner");
break;
}
mSavedPeerConfig = new WifiP2pConfig();
mSavedPeerConfig.deviceAddress = group.getOwner().deviceAddress;
//Check if we have the owner in peer list and use appropriate
//wps method. Default is to use PBC.
if ((owner = getDeviceFromPeerList(owner.deviceAddress)) != null) {
if (owner.wpsPbcSupported()) {
mSavedPeerConfig.wps.setup = WpsInfo.PBC;
} else if (owner.wpsKeypadSupported()) {
mSavedPeerConfig.wps.setup = WpsInfo.KEYPAD;
} else if (owner.wpsDisplaySupported()) {
mSavedPeerConfig.wps.setup = WpsInfo.DISPLAY;
}
}
transitionTo(mUserAuthorizingInvitationState);
break;
case WifiMonitor.P2P_PROV_DISC_PBC_REQ_EVENT:
case WifiMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT:
case WifiMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT:
WifiP2pProvDiscEvent provDisc = (WifiP2pProvDiscEvent) message.obj;
mSavedPeerConfig = new WifiP2pConfig();
mSavedPeerConfig.deviceAddress = provDisc.device.deviceAddress;
if (message.what == WifiMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT) {
mSavedPeerConfig.wps.setup = WpsInfo.KEYPAD;
if (DBG) logd("Keypad prov disc request");
} else if (message.what == WifiMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT) {
mSavedPeerConfig.wps.setup = WpsInfo.DISPLAY;
mSavedPeerConfig.wps.pin = provDisc.pin;
if (DBG) logd("Display prov disc request");
} else {
mSavedPeerConfig.wps.setup = WpsInfo.PBC;
if (DBG) logd("PBC prov disc request");
}
transitionTo(mUserAuthorizingInvitationState);
break;
case WifiP2pManager.CREATE_GROUP:
mPersistGroup = true;
if (mWifiNative.p2pGroupAdd()) {
replyToMessage(message, WifiP2pManager.CREATE_GROUP_SUCCEEDED);
} else {
replyToMessage(message, WifiP2pManager.CREATE_GROUP_FAILED,
WifiP2pManager.ERROR);
}
transitionTo(mGroupNegotiationState);
break;
default:
return NOT_HANDLED;
}
return HANDLED;
}
}
class GroupCreatingState extends State {
@Override
public void enter() {
if (DBG) logd(getName());
sendMessageDelayed(obtainMessage(GROUP_CREATING_TIMED_OUT,
++mGroupCreatingTimeoutIndex, 0), GROUP_CREATING_WAIT_TIME_MS);
}
@Override
public boolean processMessage(Message message) {
if (DBG) logd(getName() + message.toString());
switch (message.what) {
case GROUP_CREATING_TIMED_OUT:
if (mGroupCreatingTimeoutIndex == message.arg1) {
if (DBG) logd("Group negotiation timed out");
updateDeviceStatus(mSavedPeerConfig.deviceAddress, WifiP2pDevice.FAILED);
mSavedPeerConfig = null;
sendP2pPeersChangedBroadcast();
transitionTo(mInactiveState);
}
break;
case WifiP2pManager.DISCOVER_PEERS:
/* Discovery will break negotiation */
replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED,
WifiP2pManager.BUSY);
break;
case WifiP2pManager.CANCEL_CONNECT:
if (mWifiNative.p2pCancelConnect()) {
replyToMessage(message, WifiP2pManager.CANCEL_CONNECT_SUCCEEDED);
} else {
replyToMessage(message, WifiP2pManager.CANCEL_CONNECT_FAILED,
WifiP2pManager.ERROR);
}
break;
default:
return NOT_HANDLED;
}
return HANDLED;
}
}
class UserAuthorizingInvitationState extends State {
@Override
public void enter() {
if (DBG) logd(getName());
notifyInvitationReceived();
}
@Override
public boolean processMessage(Message message) {
if (DBG) logd(getName() + message.toString());
switch (message.what) {
case PEER_CONNECTION_USER_ACCEPT:
//TODO: handle persistence
if (isGroupOwner(mSavedPeerConfig.deviceAddress)) {
p2pConnectWithPinDisplay(mSavedPeerConfig, JOIN_GROUP);
} else {
p2pConnectWithPinDisplay(mSavedPeerConfig, FORM_GROUP);
}
updateDeviceStatus(mSavedPeerConfig.deviceAddress, WifiP2pDevice.INVITED);
sendP2pPeersChangedBroadcast();
transitionTo(mGroupNegotiationState);
break;
case PEER_CONNECTION_USER_REJECT:
if (DBG) logd("User rejected invitation " + mSavedPeerConfig);
mSavedPeerConfig = null;
transitionTo(mInactiveState);
break;
default:
return NOT_HANDLED;
}
return HANDLED;
}
@Override
public void exit() {
//TODO: dismiss dialog if not already done
}
}
class ProvisionDiscoveryState extends State {
@Override
public void enter() {
if (DBG) logd(getName());
mWifiNative.p2pProvisionDiscovery(mSavedPeerConfig);
}
@Override
public boolean processMessage(Message message) {
if (DBG) logd(getName() + message.toString());
WifiP2pProvDiscEvent provDisc;
WifiP2pDevice device;
switch (message.what) {
case WifiMonitor.P2P_PROV_DISC_PBC_RSP_EVENT:
provDisc = (WifiP2pProvDiscEvent) message.obj;
device = provDisc.device;
if (!device.deviceAddress.equals(mSavedPeerConfig.deviceAddress)) break;
if (mSavedPeerConfig.wps.setup == WpsInfo.PBC) {
if (DBG) logd("Found a match " + mSavedPeerConfig);
mWifiNative.p2pConnect(mSavedPeerConfig, FORM_GROUP);
transitionTo(mGroupNegotiationState);
}
break;
case WifiMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT:
provDisc = (WifiP2pProvDiscEvent) message.obj;
device = provDisc.device;
if (!device.deviceAddress.equals(mSavedPeerConfig.deviceAddress)) break;
if (mSavedPeerConfig.wps.setup == WpsInfo.KEYPAD) {
if (DBG) logd("Found a match " + mSavedPeerConfig);
/* we already have the pin */
if (!TextUtils.isEmpty(mSavedPeerConfig.wps.pin)) {
mWifiNative.p2pConnect(mSavedPeerConfig, FORM_GROUP);
transitionTo(mGroupNegotiationState);
} else {
transitionTo(mUserAuthorizingInvitationState);
}
}
break;
case WifiMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT:
provDisc = (WifiP2pProvDiscEvent) message.obj;
device = provDisc.device;
if (!device.deviceAddress.equals(mSavedPeerConfig.deviceAddress)) break;
if (mSavedPeerConfig.wps.setup == WpsInfo.DISPLAY) {
if (DBG) logd("Found a match " + mSavedPeerConfig);
mSavedPeerConfig.wps.pin = provDisc.pin;
mWifiNative.p2pConnect(mSavedPeerConfig, FORM_GROUP);
notifyInvitationSent(provDisc.pin, device.deviceAddress);
transitionTo(mGroupNegotiationState);
}
break;
default:
return NOT_HANDLED;
}
return HANDLED;
}
}
class GroupNegotiationState extends State {
@Override
public void enter() {
if (DBG) logd(getName());
}
@Override
public boolean processMessage(Message message) {
if (DBG) logd(getName() + message.toString());
switch (message.what) {
// We ignore these right now, since we get a GROUP_STARTED notification
// afterwards
case WifiMonitor.P2P_GO_NEGOTIATION_SUCCESS_EVENT:
case WifiMonitor.P2P_GROUP_FORMATION_SUCCESS_EVENT:
if (DBG) logd(getName() + " go success");
break;
case WifiMonitor.P2P_GROUP_STARTED_EVENT:
mGroup = (WifiP2pGroup) message.obj;
if (DBG) logd(getName() + " group started");
if (mGroup.isGroupOwner()) {
startDhcpServer(mGroup.getInterface());
} else {
mDhcpStateMachine = DhcpStateMachine.makeDhcpStateMachine(mContext,
P2pStateMachine.this, mGroup.getInterface());
mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_START_DHCP);
WifiP2pDevice groupOwner = mGroup.getOwner();
updateDeviceStatus(groupOwner.deviceAddress, WifiP2pDevice.CONNECTED);
sendP2pPeersChangedBroadcast();
}
mSavedPeerConfig = null;
transitionTo(mGroupCreatedState);
break;
case WifiMonitor.P2P_GO_NEGOTIATION_FAILURE_EVENT:
case WifiMonitor.P2P_GROUP_FORMATION_FAILURE_EVENT:
if (DBG) logd(getName() + " go failure");
updateDeviceStatus(mSavedPeerConfig.deviceAddress, WifiP2pDevice.FAILED);
mSavedPeerConfig = null;
sendP2pPeersChangedBroadcast();
transitionTo(mInactiveState);
break;
default:
return NOT_HANDLED;
}
return HANDLED;
}
}
class GroupCreatedState extends State {
@Override
public void enter() {
if (DBG) logd(getName());
mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.CONNECTED, null, null);
updateThisDevice(WifiP2pDevice.CONNECTED);
//DHCP server has already been started if I am a group owner
if (mGroup.isGroupOwner()) {
setWifiP2pInfoOnGroupFormation(SERVER_ADDRESS);
sendP2pConnectionChangedBroadcast();
}
}
@Override
public boolean processMessage(Message message) {
if (DBG) logd(getName() + message.toString());
switch (message.what) {
case WifiMonitor.AP_STA_CONNECTED_EVENT:
WifiP2pDevice device = (WifiP2pDevice) message.obj;
String deviceAddress = device.deviceAddress;
if (deviceAddress != null) {
mGroup.addClient(deviceAddress);
mPeers.updateInterfaceAddress(device);
updateDeviceStatus(deviceAddress, WifiP2pDevice.CONNECTED);
if (DBG) logd(getName() + " ap sta connected");
sendP2pPeersChangedBroadcast();
} else {
loge("Connect on null device address, ignore");
}
break;
case WifiMonitor.AP_STA_DISCONNECTED_EVENT:
//TODO: the disconnection event is still inconsistent and reports
//interface address. Fix this after wpa_supplicant is fixed.
String interfaceAddress = (String) message.obj;
deviceAddress = getDeviceAddress(interfaceAddress);
if (deviceAddress != null) {
updateDeviceStatus(deviceAddress, WifiP2pDevice.AVAILABLE);
if (mGroup.removeClient(deviceAddress)) {
if (DBG) logd("Removed client " + deviceAddress);
if (!mPersistGroup && mGroup.isClientListEmpty()) {
Slog.d(TAG, "Client list empty, remove non-persistent p2p group");
mWifiNative.p2pGroupRemove(mGroup.getInterface());
}
} else {
if (DBG) logd("Failed to remove client " + deviceAddress);
for (WifiP2pDevice c : mGroup.getClientList()) {
if (DBG) logd("client " + c.deviceAddress);
}
}
sendP2pPeersChangedBroadcast();
if (DBG) loge(getName() + " ap sta disconnected");
} else {
loge("Disconnect on unknown interface address : " + interfaceAddress);
}
break;
case DhcpStateMachine.CMD_POST_DHCP_ACTION:
DhcpInfoInternal dhcpInfo = (DhcpInfoInternal) message.obj;
if (message.arg1 == DhcpStateMachine.DHCP_SUCCESS &&
dhcpInfo != null) {
if (DBG) logd("DhcpInfo: " + dhcpInfo);
setWifiP2pInfoOnGroupFormation(dhcpInfo.serverAddress);
sendP2pConnectionChangedBroadcast();
} else {
mWifiNative.p2pGroupRemove(mGroup.getInterface());
}
break;
case WifiP2pManager.REMOVE_GROUP:
if (DBG) loge(getName() + " remove group");
if (mWifiNative.p2pGroupRemove(mGroup.getInterface())) {
replyToMessage(message, WifiP2pManager.REMOVE_GROUP_SUCCEEDED);
} else {
replyToMessage(message, WifiP2pManager.REMOVE_GROUP_FAILED,
WifiP2pManager.ERROR);
}
break;
case WifiMonitor.P2P_GROUP_REMOVED_EVENT:
if (DBG) loge(getName() + " group removed");
Collection <WifiP2pDevice> devices = mGroup.getClientList();
boolean changed = false;
for (WifiP2pDevice d : mPeers.getDeviceList()) {
if (devices.contains(d) || mGroup.getOwner().equals(d)) {
d.status = WifiP2pDevice.AVAILABLE;
changed = true;
}
}
if (mGroup.isGroupOwner()) {
stopDhcpServer();
} else {
if (DBG) logd("stop DHCP client");
mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_STOP_DHCP);
mDhcpStateMachine.quit();
mDhcpStateMachine = null;
}
mGroup = null;
if (changed) sendP2pPeersChangedBroadcast();
transitionTo(mInactiveState);
break;
case WifiMonitor.P2P_DEVICE_LOST_EVENT:
device = (WifiP2pDevice) message.obj;
//Device loss for a connected device indicates it is not in discovery any more
if (mGroup.contains(device)) {
if (DBG) logd("Lost " + device +" , do nothing");
return HANDLED;
}
// Do the regular device lost handling
return NOT_HANDLED;
case WifiStateMachine.CMD_DISABLE_P2P:
sendMessage(WifiP2pManager.REMOVE_GROUP);
deferMessage(message);
break;
case WifiP2pManager.CONNECT:
WifiP2pConfig config = (WifiP2pConfig) message.obj;
logd("Inviting device : " + config.deviceAddress);
if (mWifiNative.p2pInvite(mGroup, config.deviceAddress)) {
updateDeviceStatus(config.deviceAddress, WifiP2pDevice.INVITED);
sendP2pPeersChangedBroadcast();
replyToMessage(message, WifiP2pManager.CONNECT_SUCCEEDED);
} else {
replyToMessage(message, WifiP2pManager.CONNECT_FAILED,
WifiP2pManager.ERROR);
}
// TODO: figure out updating the status to declined when invitation is rejected
break;
case WifiMonitor.P2P_PROV_DISC_PBC_REQ_EVENT:
case WifiMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT:
case WifiMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT:
WifiP2pProvDiscEvent provDisc = (WifiP2pProvDiscEvent) message.obj;
mSavedPeerConfig = new WifiP2pConfig();
mSavedPeerConfig.deviceAddress = provDisc.device.deviceAddress;
if (message.what == WifiMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT) {
mSavedPeerConfig.wps.setup = WpsInfo.KEYPAD;
} else if (message.what == WifiMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT) {
mSavedPeerConfig.wps.setup = WpsInfo.DISPLAY;
mSavedPeerConfig.wps.pin = provDisc.pin;
} else {
mSavedPeerConfig.wps.setup = WpsInfo.PBC;
}
transitionTo(mUserAuthorizingJoinState);
break;
case WifiMonitor.P2P_GROUP_STARTED_EVENT:
Slog.e(TAG, "Duplicate group creation event notice, ignore");
break;
default:
return NOT_HANDLED;
}
return HANDLED;
}
public void exit() {
updateThisDevice(WifiP2pDevice.AVAILABLE);
setWifiP2pInfoOnGroupTermination();
mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, null, null);
sendP2pConnectionChangedBroadcast();
}
}
class UserAuthorizingJoinState extends State {
@Override
public void enter() {
if (DBG) logd(getName());
notifyInvitationReceived();
}
@Override
public boolean processMessage(Message message) {
if (DBG) logd(getName() + message.toString());
switch (message.what) {
case WifiMonitor.P2P_PROV_DISC_PBC_REQ_EVENT:
case WifiMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT:
case WifiMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT:
//Ignore more client requests
break;
case PEER_CONNECTION_USER_ACCEPT:
if (mSavedPeerConfig.wps.setup == WpsInfo.PBC) {
mWifiNative.startWpsPbc();
} else {
mWifiNative.startWpsPinKeypad(mSavedPeerConfig.wps.pin);
}
mSavedPeerConfig = null;
transitionTo(mGroupCreatedState);
break;
case PEER_CONNECTION_USER_REJECT:
if (DBG) logd("User rejected incoming request");
mSavedPeerConfig = null;
transitionTo(mGroupCreatedState);
break;
default:
return NOT_HANDLED;
}
return HANDLED;
}
@Override
public void exit() {
//TODO: dismiss dialog if not already done
}
}
private void sendP2pStateChangedBroadcast(boolean enabled) {
final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
if (enabled) {
intent.putExtra(WifiP2pManager.EXTRA_WIFI_STATE,
WifiP2pManager.WIFI_P2P_STATE_ENABLED);
} else {
intent.putExtra(WifiP2pManager.EXTRA_WIFI_STATE,
WifiP2pManager.WIFI_P2P_STATE_DISABLED);
}
mContext.sendStickyBroadcast(intent);
}
private void sendThisDeviceChangedBroadcast() {
final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_DEVICE, new WifiP2pDevice(mThisDevice));
mContext.sendStickyBroadcast(intent);
}
private void sendP2pPeersChangedBroadcast() {
final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
mContext.sendBroadcast(intent);
}
private void sendP2pConnectionChangedBroadcast() {
if (DBG) logd("sending p2p connection changed broadcast");
Intent intent = new Intent(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
| Intent.FLAG_RECEIVER_REPLACE_PENDING);
intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO, new WifiP2pInfo(mWifiP2pInfo));
intent.putExtra(WifiP2pManager.EXTRA_NETWORK_INFO, new NetworkInfo(mNetworkInfo));
mContext.sendStickyBroadcast(intent);
}
private void startDhcpServer(String intf) {
InterfaceConfiguration ifcg = null;
try {
ifcg = mNwService.getInterfaceConfig(intf);
ifcg.setLinkAddress(new LinkAddress(NetworkUtils.numericToInetAddress(
SERVER_ADDRESS), 24));
ifcg.setInterfaceUp();
mNwService.setInterfaceConfig(intf, ifcg);
/* This starts the dnsmasq server */
mNwService.startTethering(DHCP_RANGE);
} catch (Exception e) {
loge("Error configuring interface " + intf + ", :" + e);
return;
}
logd("Started Dhcp server on " + intf);
}
private void stopDhcpServer() {
try {
mNwService.stopTethering();
} catch (Exception e) {
loge("Error stopping Dhcp server" + e);
return;
}
logd("Stopped Dhcp server");
}
private void notifyP2pEnableFailure() {
Resources r = Resources.getSystem();
AlertDialog dialog = new AlertDialog.Builder(mContext)
.setTitle(r.getString(R.string.wifi_p2p_dialog_title))
.setMessage(r.getString(R.string.wifi_p2p_failed_message))
.setPositiveButton(r.getString(R.string.ok), null)
.create();
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
dialog.show();
}
private void addRowToDialog(ViewGroup group, int stringId, String value) {
Resources r = Resources.getSystem();
View row = LayoutInflater.from(mContext).inflate(R.layout.wifi_p2p_dialog_row,
group, false);
((TextView) row.findViewById(R.id.name)).setText(r.getString(stringId));
((TextView) row.findViewById(R.id.value)).setText(value);
group.addView(row);
}
private void notifyInvitationSent(String pin, String peerAddress) {
Resources r = Resources.getSystem();
final View textEntryView = LayoutInflater.from(mContext)
.inflate(R.layout.wifi_p2p_dialog, null);
ViewGroup group = (ViewGroup) textEntryView.findViewById(R.id.info);
addRowToDialog(group, R.string.wifi_p2p_to_message, getDeviceName(peerAddress));
addRowToDialog(group, R.string.wifi_p2p_show_pin_message, pin);
AlertDialog dialog = new AlertDialog.Builder(mContext)
.setTitle(r.getString(R.string.wifi_p2p_invitation_sent_title))
.setView(textEntryView)
.setPositiveButton(r.getString(R.string.ok), null)
.create();
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
dialog.show();
}
private void notifyInvitationReceived() {
Resources r = Resources.getSystem();
final WpsInfo wps = mSavedPeerConfig.wps;
final View textEntryView = LayoutInflater.from(mContext)
.inflate(R.layout.wifi_p2p_dialog, null);
ViewGroup group = (ViewGroup) textEntryView.findViewById(R.id.info);
addRowToDialog(group, R.string.wifi_p2p_from_message, getDeviceName(
mSavedPeerConfig.deviceAddress));
final EditText pin = (EditText) textEntryView.findViewById(R.id.wifi_p2p_wps_pin);
AlertDialog dialog = new AlertDialog.Builder(mContext)
.setTitle(r.getString(R.string.wifi_p2p_invitation_to_connect_title))
.setView(textEntryView)
.setPositiveButton(r.getString(R.string.accept), new OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
if (wps.setup == WpsInfo.KEYPAD) {
mSavedPeerConfig.wps.pin = pin.getText().toString();
}
if (DBG) logd(getName() + " accept invitation " + mSavedPeerConfig);
sendMessage(PEER_CONNECTION_USER_ACCEPT);
}
})
.setNegativeButton(r.getString(R.string.decline), new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (DBG) logd(getName() + " ignore connect");
sendMessage(PEER_CONNECTION_USER_REJECT);
}
})
.create();
//make the enter pin area or the display pin area visible
switch (wps.setup) {
case WpsInfo.KEYPAD:
if (DBG) logd("Enter pin section visible");
textEntryView.findViewById(R.id.enter_pin_section).setVisibility(View.VISIBLE);
break;
case WpsInfo.DISPLAY:
if (DBG) logd("Shown pin section visible");
addRowToDialog(group, R.string.wifi_p2p_show_pin_message, wps.pin);
break;
default:
break;
}
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
dialog.show();
}
private void updateDeviceStatus(String deviceAddress, int status) {
for (WifiP2pDevice d : mPeers.getDeviceList()) {
if (d.deviceAddress.equals(deviceAddress)) {
d.status = status;
}
}
}
private boolean isGroupOwner(String deviceAddress) {
for (WifiP2pDevice d : mPeers.getDeviceList()) {
if (d.deviceAddress.equals(deviceAddress)) {
return d.isGroupOwner();
}
}
return false;
}
//TODO: implement when wpa_supplicant is fixed
private int configuredNetworkId(String deviceAddress) {
return -1;
}
private void setWifiP2pInfoOnGroupFormation(String serverAddress) {
mWifiP2pInfo.groupFormed = true;
mWifiP2pInfo.isGroupOwner = mGroup.isGroupOwner();
mWifiP2pInfo.groupOwnerAddress = NetworkUtils.numericToInetAddress(serverAddress);
}
private void setWifiP2pInfoOnGroupTermination() {
mWifiP2pInfo.groupFormed = false;
mWifiP2pInfo.isGroupOwner = false;
mWifiP2pInfo.groupOwnerAddress = null;
}
private String getDeviceName(String deviceAddress) {
for (WifiP2pDevice d : mPeers.getDeviceList()) {
if (d.deviceAddress.equals(deviceAddress)) {
return d.deviceName;
}
}
//Treat the address as name if there is no match
return deviceAddress;
}
private String getDeviceAddress(String interfaceAddress) {
for (WifiP2pDevice d : mPeers.getDeviceList()) {
if (interfaceAddress.equals(d.interfaceAddress)) {
return d.deviceAddress;
}
}
return null;
}
private WifiP2pDevice getDeviceFromPeerList(String deviceAddress) {
for (WifiP2pDevice d : mPeers.getDeviceList()) {
if (d.deviceAddress.equals(deviceAddress)) {
return d;
}
}
return null;
}
private void p2pConnectWithPinDisplay(WifiP2pConfig config, boolean join) {
String pin = mWifiNative.p2pConnect(config, join);
try {
Integer.parseInt(pin);
notifyInvitationSent(pin, config.deviceAddress);
} catch (NumberFormatException ignore) {
// do nothing if p2pConnect did not return a pin
}
}
private void initializeP2pSettings() {
mWifiNative.setPersistentReconnect(true);
mWifiNative.setDeviceName(mThisDevice.deviceName);
//DIRECT-XY-DEVICENAME (XY is randomly generated)
mWifiNative.setP2pSsidPostfix("-" + mThisDevice.deviceName);
mWifiNative.setDeviceType(mThisDevice.primaryDeviceType);
//The supplicant default is to support everything, but a bug necessitates
//the framework to specify this explicitly
mWifiNative.setConfigMethods("keypad display push_button");
mThisDevice.deviceAddress = mWifiNative.p2pGetDeviceAddress();
updateThisDevice(WifiP2pDevice.AVAILABLE);
if (DBG) Slog.d(TAG, "DeviceAddress: " + mThisDevice.deviceAddress);
}
private void updateThisDevice(int status) {
mThisDevice.status = status;
sendThisDeviceChangedBroadcast();
}
//State machine initiated requests can have replyTo set to null indicating
//there are no recepients, we ignore those reply actions
private void replyToMessage(Message msg, int what) {
if (msg.replyTo == null) return;
Message dstMsg = obtainMessage(msg);
dstMsg.what = what;
mReplyChannel.replyToMessage(msg, dstMsg);
}
private void replyToMessage(Message msg, int what, int arg1) {
if (msg.replyTo == null) return;
Message dstMsg = obtainMessage(msg);
dstMsg.what = what;
dstMsg.arg1 = arg1;
mReplyChannel.replyToMessage(msg, dstMsg);
}
private void replyToMessage(Message msg, int what, Object obj) {
if (msg.replyTo == null) return;
Message dstMsg = obtainMessage(msg);
dstMsg.what = what;
dstMsg.obj = obj;
mReplyChannel.replyToMessage(msg, dstMsg);
}
/* arg2 on the source message has a hash code that needs to be retained in replies
* see WifiP2pManager for details */
private Message obtainMessage(Message srcMsg) {
Message msg = Message.obtain();
msg.arg2 = srcMsg.arg2;
return msg;
}
private void logd(String s) {
Slog.d(TAG, s);
}
private void loge(String s) {
Slog.e(TAG, s);
}
}
}