/* * 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.Activity; import android.app.ActivityManager; import android.app.AlertDialog; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.ActivityManager.RunningTaskInfo; 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.net.wifi.p2p.nsd.WifiP2pServiceInfo; import android.net.wifi.p2p.nsd.WifiP2pServiceRequest; import android.net.wifi.p2p.nsd.WifiP2pServiceResponse; import android.os.Binder; import android.os.Bundle; 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.Parcel; import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; import android.os.Parcelable.Creator; import android.provider.Settings; import android.text.TextUtils; import android.util.Slog; import android.util.SparseArray; 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.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; /** * WifiP2pService includes 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 ActivityManager mActivityMgr; 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; /* Idle time after a peer is gone when the group is torn down */ private static final int GROUP_IDLE_TIME_S = 2; /** * 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 mAutonomousGroup; /* Invitation to join an existing p2p group */ private boolean mJoinExistingGroup; /* Track whether we are in p2p discovery. This is used to avoid sending duplicate * broadcasts */ private boolean mDiscoveryStarted; private NetworkInfo mNetworkInfo; /* The transaction Id of service discovery request */ private byte mServiceTransactionId = 0; /* Service discovery request ID of wpa_supplicant. * null means it's not set yet. */ private String mServiceDiscReqId; /* clients(application) information list. */ private HashMap mClientInfoList = new HashMap(); /* The foreground application's messenger. * The connection request is notified only to foreground application */ private Messenger mForegroundAppMessenger; /* the package name of foreground application. */ private String mForegroundAppPkgName; /* 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"; mActivityMgr = (ActivityManager)context.getSystemService(Activity.ACTIVITY_SERVICE); 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); 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"); } /** * 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; // Saved WifiP2pDevice from provisioning request private WifiP2pDevice mSavedProvDiscDevice; 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.DISCOVER_SERVICES: replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_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.ADD_LOCAL_SERVICE: replyToMessage(message, WifiP2pManager.ADD_LOCAL_SERVICE_FAILED, WifiP2pManager.BUSY); break; case WifiP2pManager.REMOVE_LOCAL_SERVICE: replyToMessage(message, WifiP2pManager.REMOVE_LOCAL_SERVICE_FAILED, WifiP2pManager.BUSY); break; case WifiP2pManager.CLEAR_LOCAL_SERVICES: replyToMessage(message, WifiP2pManager.CLEAR_LOCAL_SERVICES_FAILED, WifiP2pManager.BUSY); break; case WifiP2pManager.ADD_SERVICE_REQUEST: replyToMessage(message, WifiP2pManager.ADD_SERVICE_REQUEST_FAILED, WifiP2pManager.BUSY); break; case WifiP2pManager.REMOVE_SERVICE_REQUEST: replyToMessage(message, WifiP2pManager.REMOVE_SERVICE_REQUEST_FAILED, WifiP2pManager.BUSY); break; case WifiP2pManager.CLEAR_SERVICE_REQUESTS: replyToMessage(message, WifiP2pManager.CLEAR_SERVICE_REQUESTS_FAILED, WifiP2pManager.BUSY); break; case WifiP2pManager.SET_DEVICE_NAME: replyToMessage(message, WifiP2pManager.SET_DEVICE_NAME_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; case WifiP2pManager.SET_DIALOG_LISTENER: String appPkgName = (String)message.getData().getString( WifiP2pManager.APP_PKG_BUNDLE_KEY); boolean isReset = message.getData().getBoolean( WifiP2pManager.RESET_DIALOG_LISTENER_BUNDLE_KEY); if (setDialogListenerApp(message.replyTo, appPkgName, isReset)) { replyToMessage(message, WifiP2pManager.DIALOG_LISTENER_ATTACHED); } else { replyToMessage(message, WifiP2pManager.DIALOG_LISTENER_DETACHED, WifiP2pManager.NOT_IN_FOREGROUND); } 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 WifiMonitor.P2P_GROUP_REMOVED_EVENT: case WifiMonitor.P2P_DEVICE_FOUND_EVENT: case WifiMonitor.P2P_DEVICE_LOST_EVENT: case WifiMonitor.P2P_FIND_STOPPED_EVENT: case WifiMonitor.P2P_SERV_DISC_RESP_EVENT: case WifiStateMachine.CMD_ENABLE_P2P: case WifiStateMachine.CMD_DISABLE_P2P: case PEER_CONNECTION_USER_ACCEPT: case PEER_CONNECTION_USER_REJECT: case GROUP_CREATING_TIMED_OUT: break; /* unexpected group created, remove */ case WifiMonitor.P2P_GROUP_STARTED_EVENT: mGroup = (WifiP2pGroup) message.obj; loge("Unexpected group creation, remove " + mGroup); mWifiNative.p2pGroupRemove(mGroup.getInterface()); break; // A group formation failure is always followed by // a group removed event. Flushing things at group formation // failure causes supplicant issues. Ignore right now. case WifiMonitor.P2P_GROUP_FORMATION_FAILURE_EVENT: 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.DISCOVER_SERVICES: replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_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; case WifiP2pManager.ADD_LOCAL_SERVICE: replyToMessage(message, WifiP2pManager.ADD_LOCAL_SERVICE_FAILED, WifiP2pManager.P2P_UNSUPPORTED); break; case WifiP2pManager.SET_DIALOG_LISTENER: replyToMessage(message, WifiP2pManager.DIALOG_LISTENER_DETACHED, WifiP2pManager.P2P_UNSUPPORTED); break; case WifiP2pManager.REMOVE_LOCAL_SERVICE: replyToMessage(message, WifiP2pManager.REMOVE_LOCAL_SERVICE_FAILED, WifiP2pManager.P2P_UNSUPPORTED); break; case WifiP2pManager.CLEAR_LOCAL_SERVICES: replyToMessage(message, WifiP2pManager.CLEAR_LOCAL_SERVICES_FAILED, WifiP2pManager.P2P_UNSUPPORTED); break; case WifiP2pManager.ADD_SERVICE_REQUEST: replyToMessage(message, WifiP2pManager.ADD_SERVICE_REQUEST_FAILED, WifiP2pManager.P2P_UNSUPPORTED); break; case WifiP2pManager.REMOVE_SERVICE_REQUEST: replyToMessage(message, WifiP2pManager.REMOVE_SERVICE_REQUEST_FAILED, WifiP2pManager.P2P_UNSUPPORTED); break; case WifiP2pManager.CLEAR_SERVICE_REQUESTS: replyToMessage(message, WifiP2pManager.CLEAR_SERVICE_REQUESTS_FAILED, WifiP2pManager.P2P_UNSUPPORTED); break; case WifiP2pManager.SET_DEVICE_NAME: replyToMessage(message, WifiP2pManager.SET_DEVICE_NAME_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); sendP2pConnectionChangedBroadcast(); 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.SET_DEVICE_NAME: WifiP2pDevice d = (WifiP2pDevice) message.obj; if (d != null && setAndPersistDeviceName(d.deviceName)) { if (DBG) logd("set device name " + d.deviceName); replyToMessage(message, WifiP2pManager.SET_DEVICE_NAME_SUCCEEDED); } else { replyToMessage(message, WifiP2pManager.SET_DEVICE_NAME_FAILED, WifiP2pManager.ERROR); } break; case WifiP2pManager.DISCOVER_PEERS: // do not send service discovery request while normal find operation. clearSupplicantServiceRequest(); if (mWifiNative.p2pFind(DISCOVER_TIMEOUT_S)) { replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_SUCCEEDED); sendP2pDiscoveryChangedBroadcast(true); } else { replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED, WifiP2pManager.ERROR); } break; case WifiMonitor.P2P_FIND_STOPPED_EVENT: sendP2pDiscoveryChangedBroadcast(false); 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 WifiP2pManager.DISCOVER_SERVICES: if (DBG) logd(getName() + " discover services"); if (!updateSupplicantServiceRequest()) { replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED, WifiP2pManager.NO_SERVICE_REQUESTS); break; } if (mWifiNative.p2pFind(DISCOVER_TIMEOUT_S)) { replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_SUCCEEDED); } else { replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_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; case WifiP2pManager.ADD_LOCAL_SERVICE: if (DBG) logd(getName() + " add service"); WifiP2pServiceInfo servInfo = (WifiP2pServiceInfo)message.obj; if (addLocalService(message.replyTo, servInfo)) { replyToMessage(message, WifiP2pManager.ADD_LOCAL_SERVICE_SUCCEEDED); } else { replyToMessage(message, WifiP2pManager.ADD_LOCAL_SERVICE_FAILED); } break; case WifiP2pManager.REMOVE_LOCAL_SERVICE: if (DBG) logd(getName() + " remove service"); servInfo = (WifiP2pServiceInfo)message.obj; removeLocalService(message.replyTo, servInfo); replyToMessage(message, WifiP2pManager.REMOVE_LOCAL_SERVICE_SUCCEEDED); break; case WifiP2pManager.CLEAR_LOCAL_SERVICES: if (DBG) logd(getName() + " clear service"); clearLocalServices(message.replyTo); replyToMessage(message, WifiP2pManager.CLEAR_LOCAL_SERVICES_SUCCEEDED); break; case WifiP2pManager.ADD_SERVICE_REQUEST: if (DBG) logd(getName() + " add service request"); if (!addServiceRequest(message.replyTo, (WifiP2pServiceRequest)message.obj)) { replyToMessage(message, WifiP2pManager.ADD_SERVICE_REQUEST_FAILED); break; } replyToMessage(message, WifiP2pManager.ADD_SERVICE_REQUEST_SUCCEEDED); break; case WifiP2pManager.REMOVE_SERVICE_REQUEST: if (DBG) logd(getName() + " remove service request"); removeServiceRequest(message.replyTo, (WifiP2pServiceRequest)message.obj); replyToMessage(message, WifiP2pManager.REMOVE_SERVICE_REQUEST_SUCCEEDED); break; case WifiP2pManager.CLEAR_SERVICE_REQUESTS: if (DBG) logd(getName() + " clear service request"); clearServiceRequests(message.replyTo); replyToMessage(message, WifiP2pManager.CLEAR_SERVICE_REQUESTS_SUCCEEDED); break; case WifiMonitor.P2P_SERV_DISC_RESP_EVENT: if (DBG) logd(getName() + " receive service response"); List sdRespList = (List) message.obj; for (WifiP2pServiceResponse resp : sdRespList) { WifiP2pDevice dev = mPeers.get(resp.getSrcDevice().deviceAddress); resp.setSrcDevice(dev); sendServiceResponse(resp); } 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"); WifiP2pConfig config = (WifiP2pConfig) message.obj; mAutonomousGroup = false; /* Update group capability before connect */ int gc = mWifiNative.getGroupCapability(config.deviceAddress); mPeers.updateGroupCapability(config.deviceAddress, gc); if (mSavedPeerConfig != null && config.deviceAddress.equals( mSavedPeerConfig.deviceAddress)) { mSavedPeerConfig = config; //Stop discovery before issuing connect mWifiNative.p2pStopFind(); if (mPeers.isGroupOwner(mSavedPeerConfig.deviceAddress)) { p2pConnectWithPinDisplay(mSavedPeerConfig, JOIN_GROUP); } else { p2pConnectWithPinDisplay(mSavedPeerConfig, FORM_GROUP); } transitionTo(mGroupNegotiationState); } else { mSavedPeerConfig = config; 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 (mPeers.isGroupOwner(mSavedPeerConfig.deviceAddress)) { if (DBG) logd("Sending join to GO"); p2pConnectWithPinDisplay(mSavedPeerConfig, JOIN_GROUP); transitionTo(mGroupNegotiationState); } else { if (DBG) logd("Sending prov disc"); transitionTo(mProvisionDiscoveryState); } } } mPeers.updateStatus(mSavedPeerConfig.deviceAddress, WifiP2pDevice.INVITED); sendP2pPeersChangedBroadcast(); replyToMessage(message, WifiP2pManager.CONNECT_SUCCEEDED); break; case WifiMonitor.P2P_GO_NEGOTIATION_REQUEST_EVENT: mSavedPeerConfig = (WifiP2pConfig) message.obj; mAutonomousGroup = false; mJoinExistingGroup = false; if (!sendConnectNoticeToApp(mPeers.get(mSavedPeerConfig.deviceAddress), mSavedPeerConfig)) { 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 = mPeers.get(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; } } mAutonomousGroup = false; mJoinExistingGroup = true; //TODO In the p2p client case, we should set source address correctly. if (!sendConnectNoticeToApp(mPeers.get(mSavedPeerConfig.deviceAddress), mSavedPeerConfig)) { transitionTo(mUserAuthorizingInvitationState); } break; case WifiMonitor.P2P_FIND_STOPPED_EVENT: // When discovery stops in inactive state, flush to clear // state peer data mWifiNative.p2pFlush(); mServiceDiscReqId = null; sendP2pDiscoveryChangedBroadcast(false); 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: //We let the supplicant handle the provision discovery response //and wait instead for the GO_NEGOTIATION_REQUEST_EVENT. //Handling provision discovery and issuing a p2p_connect before //group negotiation comes through causes issues break; case WifiP2pManager.CREATE_GROUP: mAutonomousGroup = 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()); boolean ret = HANDLED; switch (message.what) { case GROUP_CREATING_TIMED_OUT: if (mGroupCreatingTimeoutIndex == message.arg1) { if (DBG) logd("Group negotiation timed out"); handleGroupCreationFailure(); transitionTo(mInactiveState); } break; case WifiMonitor.P2P_DEVICE_LOST_EVENT: WifiP2pDevice device = (WifiP2pDevice) message.obj; // If we lose a device during an autonomous group creation, // mSavedPeerConfig can be empty if (mSavedPeerConfig != null && !mSavedPeerConfig.deviceAddress.equals(device.deviceAddress)) { // Do the regular device lost handling ret = NOT_HANDLED; break; } // Do nothing if (DBG) logd("Retain connecting device " + device); break; case WifiP2pManager.DISCOVER_PEERS: /* Discovery will break negotiation */ replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED, WifiP2pManager.BUSY); break; case WifiP2pManager.CANCEL_CONNECT: //Do a supplicant p2p_cancel which only cancels an ongoing //group negotiation. This will fail for a pending provision //discovery or for a pending user action, but at the framework //level, we always treat cancel as succeeded and enter //an inactive state mWifiNative.p2pCancelConnect(); handleGroupCreationFailure(); transitionTo(mInactiveState); replyToMessage(message, WifiP2pManager.CANCEL_CONNECT_SUCCEEDED); break; default: ret = NOT_HANDLED; } return ret; } } class UserAuthorizingInvitationState extends State { @Override public void enter() { if (DBG) logd(getName()); if (!sendConnectNoticeToApp(mPeers.get(mSavedPeerConfig.deviceAddress), mSavedPeerConfig)) { notifyInvitationReceived(); } } @Override public boolean processMessage(Message message) { if (DBG) logd(getName() + message.toString()); boolean ret = HANDLED; switch (message.what) { case PEER_CONNECTION_USER_ACCEPT: //TODO: handle persistence if (mJoinExistingGroup) { p2pConnectWithPinDisplay(mSavedPeerConfig, JOIN_GROUP); } else { p2pConnectWithPinDisplay(mSavedPeerConfig, FORM_GROUP); } mPeers.updateStatus(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 ret; } @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 { mJoinExistingGroup = false; 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); if (!sendShowPinReqToFrontApp(provDisc.pin)) { 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 { // Set group idle only for a client on the group interface to speed up // disconnect when GO is gone. Setting group idle time for a group owner // causes connectivity issues for new clients mWifiNative.setP2pGroupIdle(mGroup.getInterface(), GROUP_IDLE_TIME_S); mDhcpStateMachine = DhcpStateMachine.makeDhcpStateMachine(mContext, P2pStateMachine.this, mGroup.getInterface()); mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_START_DHCP); WifiP2pDevice groupOwner = mGroup.getOwner(); mPeers.updateStatus(groupOwner.deviceAddress, WifiP2pDevice.CONNECTED); sendP2pPeersChangedBroadcast(); } mSavedPeerConfig = null; transitionTo(mGroupCreatedState); break; case WifiMonitor.P2P_GO_NEGOTIATION_FAILURE_EVENT: case WifiMonitor.P2P_GROUP_REMOVED_EVENT: if (DBG) logd(getName() + " go failure"); handleGroupCreationFailure(); transitionTo(mInactiveState); break; // A group formation failure is always followed by // a group removed event. Flushing things at group formation // failure causes supplicant issues. Ignore right now. case WifiMonitor.P2P_GROUP_FORMATION_FAILURE_EVENT: 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) { if (mSavedProvDiscDevice != null && deviceAddress.equals(mSavedProvDiscDevice.deviceAddress)) { mSavedProvDiscDevice = null; } mGroup.addClient(deviceAddress); mPeers.updateStatus(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: device = (WifiP2pDevice) message.obj; deviceAddress = device.deviceAddress; if (deviceAddress != null) { mPeers.updateStatus(deviceAddress, WifiP2pDevice.AVAILABLE); if (mGroup.removeClient(deviceAddress)) { if (DBG) logd("Removed client " + deviceAddress); if (!mAutonomousGroup && 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 device: " + device); } 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(); //Turn on power save on client mWifiNative.setP2pPowerSave(mGroup.getInterface(), true); } else { loge("DHCP failed"); 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 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; mWifiNative.p2pFlush(); mServiceDiscReqId = 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; if (config.deviceAddress == null || (mSavedProvDiscDevice != null && mSavedProvDiscDevice.deviceAddress.equals( config.deviceAddress))) { if (config.wps.setup == WpsInfo.PBC) { mWifiNative.startWpsPbc(mGroup.getInterface(), null); } else { if (config.wps.pin == null) { String pin = mWifiNative.startWpsPinDisplay(mGroup.getInterface()); try { Integer.parseInt(pin); if (!sendShowPinReqToFrontApp(pin)) { notifyInvitationSent(pin, config.deviceAddress != null ? config.deviceAddress : "any"); } } catch (NumberFormatException ignore) { // do nothing if pin is invalid } } else { mWifiNative.startWpsPinKeypad(mGroup.getInterface(), config.wps.pin); } } if (config.deviceAddress != null) { mPeers.updateStatus(config.deviceAddress, WifiP2pDevice.INVITED); sendP2pPeersChangedBroadcast(); } replyToMessage(message, WifiP2pManager.CONNECT_SUCCEEDED); } else { logd("Inviting device : " + config.deviceAddress); if (mWifiNative.p2pInvite(mGroup, config.deviceAddress)) { mPeers.updateStatus(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; mSavedProvDiscDevice = provDisc.device; 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; } if (!sendConnectNoticeToApp(mSavedProvDiscDevice, mSavedPeerConfig)) { 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() { mSavedProvDiscDevice = null; 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(mGroup.getInterface(), null); } else { mWifiNative.startWpsPinKeypad(mGroup.getInterface(), 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 sendP2pDiscoveryChangedBroadcast(boolean started) { if (mDiscoveryStarted == started) return; mDiscoveryStarted = started; if (DBG) logd("discovery change broadcast " + started); final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_DISCOVERY_CHANGED_ACTION); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); intent.putExtra(WifiP2pManager.EXTRA_DISCOVERY_STATE, started ? WifiP2pManager.WIFI_P2P_DISCOVERY_STARTED : WifiP2pManager.WIFI_P2P_DISCOVERY_STOPPED); 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); } }) .setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface arg0) { 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(); } //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) { WifiP2pDevice d = mPeers.get(deviceAddress); if (d != null) { return d.deviceName; } //Treat the address as name if there is no match return deviceAddress; } private void p2pConnectWithPinDisplay(WifiP2pConfig config, boolean join) { String pin = mWifiNative.p2pConnect(config, join); try { Integer.parseInt(pin); if (!sendShowPinReqToFrontApp(pin)) { notifyInvitationSent(pin, config.deviceAddress); } } catch (NumberFormatException ignore) { // do nothing if p2pConnect did not return a pin } } private String getPersistedDeviceName() { String deviceName = Settings.Secure.getString(mContext.getContentResolver(), Settings.Secure.WIFI_P2P_DEVICE_NAME); if (deviceName == null) { /* We use the 4 digits of the ANDROID_ID to have a friendly * default that has low likelihood of collision with a peer */ String id = Settings.Secure.getString(mContext.getContentResolver(), Settings.Secure.ANDROID_ID); return "Android_" + id.substring(0,4); } return deviceName; } private boolean setAndPersistDeviceName(String devName) { if (devName == null) return false; if (!mWifiNative.setDeviceName(devName)) { loge("Failed to set device name " + devName); return false; } mThisDevice.deviceName = devName; mWifiNative.setP2pSsidPostfix("-" + mThisDevice.deviceName); Settings.Secure.putString(mContext.getContentResolver(), Settings.Secure.WIFI_P2P_DEVICE_NAME, devName); sendThisDeviceChangedBroadcast(); return true; } private void initializeP2pSettings() { mWifiNative.setPersistentReconnect(true); mThisDevice.deviceName = getPersistedDeviceName(); mWifiNative.setDeviceName(mThisDevice.deviceName); // DIRECT-XY-DEVICENAME (XY is randomly generated) mWifiNative.setP2pSsidPostfix("-" + mThisDevice.deviceName); mWifiNative.setDeviceType(mThisDevice.primaryDeviceType); // Supplicant defaults to using virtual display with display // which refers to a remote display. Use physical_display mWifiNative.setConfigMethods("virtual_push_button physical_display keypad"); // STA has higher priority over P2P mWifiNative.setConcurrencyPriority("sta"); mThisDevice.deviceAddress = mWifiNative.p2pGetDeviceAddress(); updateThisDevice(WifiP2pDevice.AVAILABLE); if (DBG) Slog.d(TAG, "DeviceAddress: " + mThisDevice.deviceAddress); mClientInfoList.clear(); mWifiNative.p2pFlush(); mWifiNative.p2pServiceFlush(); mServiceTransactionId = 0; mServiceDiscReqId = null; } private void updateThisDevice(int status) { mThisDevice.status = status; sendThisDeviceChangedBroadcast(); } private void handleGroupCreationFailure() { mSavedPeerConfig = null; /* After cancelling group formation, new connections on existing peers can fail * at supplicant. Flush and restart discovery */ mWifiNative.p2pFlush(); mServiceDiscReqId = null; sendMessage(WifiP2pManager.DISCOVER_PEERS); } //State machine initiated requests can have replyTo set to null indicating //there are no recipients, 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); } /** * Update service discovery request to wpa_supplicant. */ private boolean updateSupplicantServiceRequest() { clearSupplicantServiceRequest(); StringBuffer sb = new StringBuffer(); for (ClientInfo c: mClientInfoList.values()) { int key; WifiP2pServiceRequest req; for (int i=0; i < c.mReqList.size(); i++) { req = c.mReqList.valueAt(i); if (req != null) { sb.append(req.getSupplicantQuery()); } } } if (sb.length() == 0) { return false; } mServiceDiscReqId = mWifiNative.p2pServDiscReq("00:00:00:00:00:00", sb.toString()); if (mServiceDiscReqId == null) { return false; } return true; } /** * Clear service discovery request in wpa_supplicant */ private void clearSupplicantServiceRequest() { if (mServiceDiscReqId == null) return; mWifiNative.p2pServDiscCancelReq(mServiceDiscReqId); mServiceDiscReqId = null; } /* TODO: We could track individual service adds separately and avoid * having to do update all service requests on every new request */ private boolean addServiceRequest(Messenger m, WifiP2pServiceRequest req) { clearClientDeadChannels(); ClientInfo clientInfo = getClientInfo(m, true); if (clientInfo == null) { return false; } ++mServiceTransactionId; //The Wi-Fi p2p spec says transaction id should be non-zero if (mServiceTransactionId == 0) ++mServiceTransactionId; req.setTransactionId(mServiceTransactionId); clientInfo.mReqList.put(mServiceTransactionId, req); if (mServiceDiscReqId == null) { return true; } return updateSupplicantServiceRequest(); } private void removeServiceRequest(Messenger m, WifiP2pServiceRequest req) { ClientInfo clientInfo = getClientInfo(m, false); if (clientInfo == null) { return; } //Application does not have transaction id information //go through stored requests to remove boolean removed = false; for (int i=0; i < clientInfo.mReqList.size(); i++) { if (req.equals(clientInfo.mReqList.valueAt(i))) { removed = true; clientInfo.mReqList.removeAt(i); break; } } if (!removed) return; if (clientInfo.mReqList.size() == 0 && clientInfo.mServList.size() == 0) { if (DBG) logd("remove client information from framework"); mClientInfoList.remove(clientInfo.mMessenger); } if (mServiceDiscReqId == null) { return; } updateSupplicantServiceRequest(); } private void clearServiceRequests(Messenger m) { ClientInfo clientInfo = getClientInfo(m, false); if (clientInfo == null) { return; } if (clientInfo.mReqList.size() == 0) { return; } clientInfo.mReqList.clear(); if (clientInfo.mServList.size() == 0) { if (DBG) logd("remove channel information from framework"); mClientInfoList.remove(clientInfo.mMessenger); } if (mServiceDiscReqId == null) { return; } updateSupplicantServiceRequest(); } private boolean addLocalService(Messenger m, WifiP2pServiceInfo servInfo) { clearClientDeadChannels(); ClientInfo clientInfo = getClientInfo(m, true); if (clientInfo == null) { return false; } if (!clientInfo.mServList.add(servInfo)) { return false; } if (!mWifiNative.p2pServiceAdd(servInfo)) { clientInfo.mServList.remove(servInfo); return false; } return true; } private void removeLocalService(Messenger m, WifiP2pServiceInfo servInfo) { ClientInfo clientInfo = getClientInfo(m, false); if (clientInfo == null) { return; } mWifiNative.p2pServiceDel(servInfo); clientInfo.mServList.remove(servInfo); if (clientInfo.mReqList.size() == 0 && clientInfo.mServList.size() == 0) { if (DBG) logd("remove client information from framework"); mClientInfoList.remove(clientInfo.mMessenger); } } private void clearLocalServices(Messenger m) { ClientInfo clientInfo = getClientInfo(m, false); if (clientInfo == null) { return; } for (WifiP2pServiceInfo servInfo: clientInfo.mServList) { mWifiNative.p2pServiceDel(servInfo); } clientInfo.mServList.clear(); if (clientInfo.mReqList.size() == 0) { if (DBG) logd("remove client information from framework"); mClientInfoList.remove(clientInfo.mMessenger); } } private void clearClientInfo(Messenger m) { clearLocalServices(m); clearServiceRequests(m); } /** * Send the service response to the WifiP2pManager.Channel. * * @param resp */ private void sendServiceResponse(WifiP2pServiceResponse resp) { for (ClientInfo c : mClientInfoList.values()) { WifiP2pServiceRequest req = c.mReqList.get(resp.getTransactionId()); if (req != null) { Message msg = Message.obtain(); msg.what = WifiP2pManager.RESPONSE_SERVICE; msg.arg1 = 0; msg.arg2 = 0; msg.obj = resp; try { c.mMessenger.send(msg); } catch (RemoteException e) { if (DBG) logd("detect dead channel"); clearClientInfo(c.mMessenger); return; } } } } /** * We dont get notifications of clients that have gone away. * We detect this actively when services are added and throw * them away. * * TODO: This can be done better with full async channels. */ private void clearClientDeadChannels() { ArrayList deadClients = new ArrayList(); for (ClientInfo c : mClientInfoList.values()) { Message msg = Message.obtain(); msg.what = WifiP2pManager.PING; msg.arg1 = 0; msg.arg2 = 0; msg.obj = null; try { c.mMessenger.send(msg); } catch (RemoteException e) { if (DBG) logd("detect dead channel"); deadClients.add(c.mMessenger); } } for (Messenger m : deadClients) { clearClientInfo(m); } } /** * Return the specified ClientInfo. * @param m Messenger * @param createIfNotExist if true and the specified channel info does not exist, * create new client info. * @return the specified ClientInfo. */ private ClientInfo getClientInfo(Messenger m, boolean createIfNotExist) { ClientInfo clientInfo = mClientInfoList.get(m); if (clientInfo == null && createIfNotExist) { if (DBG) logd("add a new client"); clientInfo = new ClientInfo(m); mClientInfoList.put(m, clientInfo); } return clientInfo; } /** * Send detached message to dialog listener in the foreground application. * @param reason */ private void sendDetachedMsg(int reason) { if (mForegroundAppMessenger == null) return; Message msg = Message.obtain(); msg.what = WifiP2pManager.DIALOG_LISTENER_DETACHED; msg.arg1 = reason; try { mForegroundAppMessenger.send(msg); } catch (RemoteException e) { } mForegroundAppMessenger = null; mForegroundAppPkgName = null; } /** * Send a request to show wps pin to dialog listener in the foreground application. * @param pin WPS pin * @return */ private boolean sendShowPinReqToFrontApp(String pin) { if (!isForegroundApp(mForegroundAppPkgName)) { sendDetachedMsg(WifiP2pManager.NOT_IN_FOREGROUND); return false; } Message msg = Message.obtain(); msg.what = WifiP2pManager.SHOW_PIN_REQUESTED; Bundle bundle = new Bundle(); bundle.putString(WifiP2pManager.WPS_PIN_BUNDLE_KEY, pin); msg.setData(bundle); return sendDialogMsgToFrontApp(msg); } /** * Send a request to establish the connection to dialog listener in the foreground * application. * @param dev source device * @param config * @return */ private boolean sendConnectNoticeToApp(WifiP2pDevice dev, WifiP2pConfig config) { if (dev == null) { dev = new WifiP2pDevice(config.deviceAddress); } if (!isForegroundApp(mForegroundAppPkgName)) { if (DBG) logd("application is NOT foreground"); sendDetachedMsg(WifiP2pManager.NOT_IN_FOREGROUND); return false; } Message msg = Message.obtain(); msg.what = WifiP2pManager.CONNECTION_REQUESTED; Bundle bundle = new Bundle(); bundle.putParcelable(WifiP2pManager.P2P_DEV_BUNDLE_KEY, dev); bundle.putParcelable(WifiP2pManager.P2P_CONFIG_BUNDLE_KEY, config); msg.setData(bundle); return sendDialogMsgToFrontApp(msg); } /** * Send dialog event message to front application's dialog listener. * @param msg * @return true if success. */ private boolean sendDialogMsgToFrontApp(Message msg) { try { mForegroundAppMessenger.send(msg); } catch (RemoteException e) { mForegroundAppMessenger = null; mForegroundAppPkgName = null; return false; } return true; } /** * Set dialog listener application. * @param m * @param appPkgName if null, reset the listener. * @param isReset if true, try to reset. * @return */ private boolean setDialogListenerApp(Messenger m, String appPkgName, boolean isReset) { if (mForegroundAppPkgName != null && !mForegroundAppPkgName.equals(appPkgName)) { if (isForegroundApp(mForegroundAppPkgName)) { // The current dialog listener is foreground app's. if (DBG) logd("application is NOT foreground"); return false; } // detach an old listener. sendDetachedMsg(WifiP2pManager.NOT_IN_FOREGROUND); } if (isReset) { if (DBG) logd("reset dialog listener"); mForegroundAppMessenger = null; mForegroundAppPkgName = null; return true; } if (!isForegroundApp(appPkgName)) { return false; } mForegroundAppMessenger = m; mForegroundAppPkgName = appPkgName; if (DBG) logd("set dialog listener. app=" + appPkgName); return true; } /** * Return true if the specified package name is foreground app's. * * @param pkgName application package name. * @return */ private boolean isForegroundApp(String pkgName) { if (pkgName == null) return false; List tasks = mActivityMgr.getRunningTasks(1); if (tasks.size() == 0) { return false; } return pkgName.equals(tasks.get(0).baseActivity.getPackageName()); } } /** * Information about a particular client and we track the service discovery requests * and the local services registered by the client. */ private class ClientInfo { /* * A reference to WifiP2pManager.Channel handler. * The response of this request is notified to WifiP2pManager.Channel handler */ private Messenger mMessenger; /* * A service discovery request list. */ private SparseArray mReqList; /* * A local service information list. */ private List mServList; private ClientInfo(Messenger m) { mMessenger = m; mReqList = new SparseArray(); mServList = new ArrayList(); } } }