blob: 625ffb8733d4d41eea259798fee0ae02124f058a [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.Configuration;
import android.content.res.Resources;
import android.net.IConnectivityManager;
import android.net.ConnectivityManager;
import android.net.DhcpResults;
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.WifiP2pGroupList.GroupDeleteListener;
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.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Slog;
import android.util.SparseArray;
import android.view.KeyEvent;
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.net.InetAddress;
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 P2pStateMachine mP2pStateMachine;
private AsyncChannel mReplyChannel = new AsyncChannel();
private AsyncChannel mWifiChannel;
private static final Boolean JOIN_GROUP = true;
private static final Boolean FORM_GROUP = false;
private static final Boolean RELOAD = true;
private static final Boolean NO_RELOAD = 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;
private static final int DISABLE_P2P_WAIT_TIME_MS = 5 * 1000;
private static int mDisableP2pTimeoutIndex = 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 = 10;
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;
/* User wants to disconnect wifi in favour of p2p */
private static final int DROP_WIFI_USER_ACCEPT = BASE + 4;
/* User wants to keep his wifi connection and drop p2p */
private static final int DROP_WIFI_USER_REJECT = BASE + 5;
/* Delayed message to timeout p2p disable */
public static final int DISABLE_P2P_TIMED_OUT = BASE + 6;
/* Commands to the WifiStateMachine */
public static final int P2P_CONNECTION_CHANGED = BASE + 11;
/* These commands are used to temporarily disconnect wifi when we detect
* a frequency conflict which would make it impossible to have with p2p
* and wifi active at the same time.
*
* If the user chooses to disable wifi temporarily, we keep wifi disconnected
* until the p2p connection is done and terminated at which point we will
* bring back wifi up
*
* DISCONNECT_WIFI_REQUEST
* msg.arg1 = 1 enables temporary disconnect and 0 disables it.
*/
public static final int DISCONNECT_WIFI_REQUEST = BASE + 12;
public static final int DISCONNECT_WIFI_RESPONSE = BASE + 13;
public static final int SET_MIRACAST_MODE = BASE + 14;
// During dhcp (and perhaps other times) we can't afford to drop packets
// but Discovery will switch our channel enough we will.
// msg.arg1 = ENABLED for blocking, DISABLED for resumed.
// msg.arg2 = msg to send when blocked
// msg.obj = StateMachine to send to when blocked
public static final int BLOCK_DISCOVERY = BASE + 15;
// set country code
public static final int SET_COUNTRY_CODE = BASE + 16;
public static final int ENABLED = 1;
public static final int DISABLED = 0;
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;
/* Track whether servcice/peer discovery is blocked in favor of other wifi actions
* (notably dhcp)
*/
private boolean mDiscoveryBlocked;
/*
* remember if we were in a scan when it had to be stopped
*/
private boolean mDiscoveryPostponed = false;
private NetworkInfo mNetworkInfo;
private boolean mTempoarilyDisconnectedWifi = false;
/* 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<Messenger, ClientInfo> mClientInfoList = new HashMap<Messenger, ClientInfo>();
/* 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";
/**
* Error code definition.
* see the Table.8 in the WiFi Direct specification for the detail.
*/
public static enum P2pStatus {
/* Success. */
SUCCESS,
/* The target device is currently unavailable. */
INFORMATION_IS_CURRENTLY_UNAVAILABLE,
/* Protocol error. */
INCOMPATIBLE_PARAMETERS,
/* The target device reached the limit of the number of the connectable device.
* For example, device limit or group limit is set. */
LIMIT_REACHED,
/* Protocol error. */
INVALID_PARAMETER,
/* Unable to accommodate request. */
UNABLE_TO_ACCOMMODATE_REQUEST,
/* Previous protocol error, or disruptive behavior. */
PREVIOUS_PROTOCOL_ERROR,
/* There is no common channels the both devices can use. */
NO_COMMON_CHANNEL,
/* Unknown p2p group. For example, Device A tries to invoke the previous persistent group,
* but device B has removed the specified credential already. */
UNKNOWN_P2P_GROUP,
/* Both p2p devices indicated an intent of 15 in group owner negotiation. */
BOTH_GO_INTENT_15,
/* Incompatible provisioning method. */
INCOMPATIBLE_PROVISIONING_METHOD,
/* Rejected by user */
REJECTED_BY_USER,
/* Unknown error */
UNKNOWN;
public static P2pStatus valueOf(int error) {
switch(error) {
case 0 :
return SUCCESS;
case 1:
return INFORMATION_IS_CURRENTLY_UNAVAILABLE;
case 2:
return INCOMPATIBLE_PARAMETERS;
case 3:
return LIMIT_REACHED;
case 4:
return INVALID_PARAMETER;
case 5:
return UNABLE_TO_ACCOMMODATE_REQUEST;
case 6:
return PREVIOUS_PROTOCOL_ERROR;
case 7:
return NO_COMMON_CHANNEL;
case 8:
return UNKNOWN_P2P_GROUP;
case 9:
return BOTH_GO_INTENT_15;
case 10:
return INCOMPATIBLE_PROVISIONING_METHOD;
case 11:
return REJECTED_BY_USER;
default:
return UNKNOWN;
}
}
}
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);
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");
}
private void enforceConnectivityInternalPermission() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.CONNECTIVITY_INTERNAL,
"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());
}
/** This is used to provide information to drivers to optimize performance depending
* on the current mode of operation.
* 0 - disabled
* 1 - source operation
* 2 - sink operation
*
* As an example, the driver could reduce the channel dwell time during scanning
* when acting as a source or sink to minimize impact on miracast.
*/
public void setMiracastMode(int mode) {
enforceConnectivityInternalPermission();
mP2pStateMachine.sendMessage(SET_MIRACAST_MODE, mode);
}
@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;
}
mP2pStateMachine.dump(fd, pw, args);
pw.println("mAutonomousGroup " + mAutonomousGroup);
pw.println("mJoinExistingGroup " + mJoinExistingGroup);
pw.println("mDiscoveryStarted " + mDiscoveryStarted);
pw.println("mNetworkInfo " + mNetworkInfo);
pw.println("mTempoarilyDisconnectedWifi " + mTempoarilyDisconnectedWifi);
pw.println("mServiceDiscReqId " + mServiceDiscReqId);
pw.println();
}
/**
* 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 UserAuthorizingInviteRequestState mUserAuthorizingInviteRequestState
= new UserAuthorizingInviteRequestState();
private UserAuthorizingNegotiationRequestState mUserAuthorizingNegotiationRequestState
= new UserAuthorizingNegotiationRequestState();
private ProvisionDiscoveryState mProvisionDiscoveryState = new ProvisionDiscoveryState();
private GroupNegotiationState mGroupNegotiationState = new GroupNegotiationState();
private FrequencyConflictState mFrequencyConflictState =new FrequencyConflictState();
private GroupCreatedState mGroupCreatedState = new GroupCreatedState();
private UserAuthorizingJoinState mUserAuthorizingJoinState = new UserAuthorizingJoinState();
private OngoingGroupRemovalState mOngoingGroupRemovalState = new OngoingGroupRemovalState();
private WifiNative mWifiNative = new WifiNative(mInterface);
private WifiMonitor mWifiMonitor = new WifiMonitor(this, mWifiNative);
private final WifiP2pDeviceList mPeers = new WifiP2pDeviceList();
/* During a connection, supplicant can tell us that a device was lost. From a supplicant's
* perspective, the discovery stops during connection and it purges device since it does
* not get latest updates about the device without being in discovery state.
*
* From the framework perspective, the device is still there since we are connecting or
* connected to it. so we keep these devices in a separate list, so that they are removed
* when connection is cancelled or lost
*/
private final WifiP2pDeviceList mPeersLostDuringConnection = new WifiP2pDeviceList();
private final WifiP2pGroupList mGroups = new WifiP2pGroupList(null,
new GroupDeleteListener() {
@Override
public void onDeleteGroup(int netId) {
if (DBG) logd("called onDeleteGroup() netId=" + netId);
mWifiNative.removeNetwork(netId);
mWifiNative.saveConfig();
sendP2pPersistentGroupsChangedBroadcast();
}
});
private final WifiP2pInfo mWifiP2pInfo = new WifiP2pInfo();
private WifiP2pGroup mGroup;
// Saved WifiP2pConfig for an ongoing peer connection. This will never be null.
// The deviceAddress will be an empty string when the device is inactive
// or if it is connected without any ongoing join request
private WifiP2pConfig mSavedPeerConfig = new WifiP2pConfig();
// 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(mUserAuthorizingInviteRequestState, mGroupCreatingState);
addState(mUserAuthorizingNegotiationRequestState, mGroupCreatingState);
addState(mProvisionDiscoveryState, mGroupCreatingState);
addState(mGroupNegotiationState, mGroupCreatingState);
addState(mFrequencyConflictState, mGroupCreatingState);
addState(mGroupCreatedState, mP2pEnabledState);
addState(mUserAuthorizingJoinState, mGroupCreatedState);
addState(mOngoingGroupRemovalState, mGroupCreatedState);
if (p2pSupported) {
setInitialState(mP2pDisabledState);
} else {
setInitialState(mP2pNotSupportedState);
}
setLogRecSize(50);
setLogOnlyTransitions(true);
}
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 BLOCK_DISCOVERY:
mDiscoveryBlocked = (message.arg1 == ENABLED ? true : false);
// always reset this - we went to a state that doesn't support discovery so
// it would have stopped regardless
mDiscoveryPostponed = false;
if (mDiscoveryBlocked) {
try {
StateMachine m = (StateMachine)message.obj;
m.sendMessage(message.arg2);
} catch (Exception e) {
loge("unable to send BLOCK_DISCOVERY response: " + e);
}
}
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.DELETE_PERSISTENT_GROUP:
replyToMessage(message, WifiP2pManager.DELETE_PERSISTENT_GROUP,
WifiP2pManager.BUSY);
break;
case WifiP2pManager.SET_WFD_INFO:
replyToMessage(message, WifiP2pManager.SET_WFD_INFO_FAILED,
WifiP2pManager.BUSY);
break;
case WifiP2pManager.REQUEST_PEERS:
replyToMessage(message, WifiP2pManager.RESPONSE_PEERS,
new WifiP2pDeviceList(mPeers));
break;
case WifiP2pManager.REQUEST_CONNECTION_INFO:
replyToMessage(message, WifiP2pManager.RESPONSE_CONNECTION_INFO,
new WifiP2pInfo(mWifiP2pInfo));
break;
case WifiP2pManager.REQUEST_GROUP_INFO:
replyToMessage(message, WifiP2pManager.RESPONSE_GROUP_INFO,
mGroup != null ? new WifiP2pGroup(mGroup) : null);
break;
case WifiP2pManager.REQUEST_PERSISTENT_GROUP_INFO:
replyToMessage(message, WifiP2pManager.RESPONSE_PERSISTENT_GROUP_INFO,
new WifiP2pGroupList(mGroups, null));
break;
case WifiP2pManager.START_WPS:
replyToMessage(message, WifiP2pManager.START_WPS_FAILED,
WifiP2pManager.BUSY);
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.AUTHENTICATION_FAILURE_EVENT:
case WifiMonitor.WPS_SUCCESS_EVENT:
case WifiMonitor.WPS_FAIL_EVENT:
case WifiMonitor.WPS_OVERLAP_EVENT:
case WifiMonitor.WPS_TIMEOUT_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 PEER_CONNECTION_USER_ACCEPT:
case PEER_CONNECTION_USER_REJECT:
case DISCONNECT_WIFI_RESPONSE:
case DROP_WIFI_USER_ACCEPT:
case DROP_WIFI_USER_REJECT:
case GROUP_CREATING_TIMED_OUT:
case DISABLE_P2P_TIMED_OUT:
case DhcpStateMachine.CMD_PRE_DHCP_ACTION:
case DhcpStateMachine.CMD_POST_DHCP_ACTION:
case DhcpStateMachine.CMD_ON_QUIT:
case WifiMonitor.P2P_PROV_DISC_FAILURE_EVENT:
case SET_MIRACAST_MODE:
case WifiP2pManager.START_LISTEN:
case WifiP2pManager.STOP_LISTEN:
case WifiP2pManager.SET_CHANNEL:
case SET_COUNTRY_CODE:
break;
case WifiStateMachine.CMD_ENABLE_P2P:
// Enable is lazy and has no response
break;
case WifiStateMachine.CMD_DISABLE_P2P_REQ:
// If we end up handling in default, p2p is not enabled
mWifiChannel.sendMessage(WifiStateMachine.CMD_DISABLE_P2P_RSP);
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.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;
case WifiP2pManager.DELETE_PERSISTENT_GROUP:
replyToMessage(message, WifiP2pManager.DELETE_PERSISTENT_GROUP,
WifiP2pManager.P2P_UNSUPPORTED);
break;
case WifiP2pManager.SET_WFD_INFO:
replyToMessage(message, WifiP2pManager.SET_WFD_INFO_FAILED,
WifiP2pManager.P2P_UNSUPPORTED);
break;
case WifiP2pManager.START_WPS:
replyToMessage(message, WifiP2pManager.START_WPS_FAILED,
WifiP2pManager.P2P_UNSUPPORTED);
break;
case WifiP2pManager.START_LISTEN:
replyToMessage(message, WifiP2pManager.START_LISTEN_FAILED,
WifiP2pManager.P2P_UNSUPPORTED);
break;
case WifiP2pManager.STOP_LISTEN:
replyToMessage(message, WifiP2pManager.STOP_LISTEN_FAILED,
WifiP2pManager.P2P_UNSUPPORTED);
break;
default:
return NOT_HANDLED;
}
return HANDLED;
}
}
class P2pDisablingState extends State {
@Override
public void enter() {
if (DBG) logd(getName());
sendMessageDelayed(obtainMessage(DISABLE_P2P_TIMED_OUT,
++mDisableP2pTimeoutIndex, 0), DISABLE_P2P_WAIT_TIME_MS);
}
@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_REQ:
deferMessage(message);
break;
case DISABLE_P2P_TIMED_OUT:
if (mGroupCreatingTimeoutIndex == message.arg1) {
loge("P2p disable timed out");
transitionTo(mP2pDisabledState);
}
break;
default:
return NOT_HANDLED;
}
return HANDLED;
}
@Override
public void exit() {
mWifiChannel.sendMessage(WifiStateMachine.CMD_DISABLE_P2P_RSP);
}
}
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;
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_REQ:
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 WifiMonitor.SUP_DISCONNECTION_EVENT:
loge("Unexpected loss of p2p socket connection");
transitionTo(mP2pDisabledState);
break;
case WifiStateMachine.CMD_ENABLE_P2P:
//Nothing to do
break;
case WifiStateMachine.CMD_DISABLE_P2P_REQ:
if (mPeers.clear()) {
sendPeersChangedBroadcast();
}
if (mGroups.clear()) sendP2pPersistentGroupsChangedBroadcast();
mWifiMonitor.stopMonitoring();
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.SET_WFD_INFO:
{
WifiP2pWfdInfo d = (WifiP2pWfdInfo) message.obj;
if (d != null && setWfdInfo(d)) {
replyToMessage(message, WifiP2pManager.SET_WFD_INFO_SUCCEEDED);
} else {
replyToMessage(message, WifiP2pManager.SET_WFD_INFO_FAILED,
WifiP2pManager.ERROR);
}
break;
}
case BLOCK_DISCOVERY:
boolean blocked = (message.arg1 == ENABLED ? true : false);
if (mDiscoveryBlocked == blocked) break;
mDiscoveryBlocked = blocked;
if (blocked && mDiscoveryStarted) {
mWifiNative.p2pStopFind();
mDiscoveryPostponed = true;
}
if (!blocked && mDiscoveryPostponed) {
mDiscoveryPostponed = false;
mWifiNative.p2pFind(DISCOVER_TIMEOUT_S);
}
if (blocked) {
try {
StateMachine m = (StateMachine)message.obj;
m.sendMessage(message.arg2);
} catch (Exception e) {
loge("unable to send BLOCK_DISCOVERY response: " + e);
}
}
break;
case WifiP2pManager.DISCOVER_PEERS:
if (mDiscoveryBlocked) {
replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED,
WifiP2pManager.BUSY);
break;
}
// 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 (mDiscoveryBlocked) {
replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED,
WifiP2pManager.BUSY);
break;
}
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.updateSupplicantDetails(device);
sendPeersChangedBroadcast();
break;
case WifiMonitor.P2P_DEVICE_LOST_EVENT:
device = (WifiP2pDevice) message.obj;
// Gets current details for the one removed
device = mPeers.remove(device.deviceAddress);
if (device != null) {
sendPeersChangedBroadcast();
}
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<WifiP2pServiceResponse> sdRespList =
(List<WifiP2pServiceResponse>) message.obj;
for (WifiP2pServiceResponse resp : sdRespList) {
WifiP2pDevice dev =
mPeers.get(resp.getSrcDevice().deviceAddress);
resp.setSrcDevice(dev);
sendServiceResponse(resp);
}
break;
case WifiP2pManager.DELETE_PERSISTENT_GROUP:
if (DBG) logd(getName() + " delete persistent group");
mGroups.remove(message.arg1);
replyToMessage(message, WifiP2pManager.DELETE_PERSISTENT_GROUP_SUCCEEDED);
break;
case SET_MIRACAST_MODE:
mWifiNative.setMiracastMode(message.arg1);
break;
case WifiP2pManager.START_LISTEN:
if (DBG) logd(getName() + " start listen mode");
mWifiNative.p2pFlush();
if (mWifiNative.p2pExtListen(true, 500, 500)) {
replyToMessage(message, WifiP2pManager.START_LISTEN_SUCCEEDED);
} else {
replyToMessage(message, WifiP2pManager.START_LISTEN_FAILED);
}
break;
case WifiP2pManager.STOP_LISTEN:
if (DBG) logd(getName() + " stop listen mode");
if (mWifiNative.p2pExtListen(false, 0, 0)) {
replyToMessage(message, WifiP2pManager.STOP_LISTEN_SUCCEEDED);
} else {
replyToMessage(message, WifiP2pManager.STOP_LISTEN_FAILED);
}
mWifiNative.p2pFlush();
break;
case WifiP2pManager.SET_CHANNEL:
Bundle p2pChannels = (Bundle) message.obj;
int lc = p2pChannels.getInt("lc", 0);
int oc = p2pChannels.getInt("oc", 0);
if (DBG) logd(getName() + " set listen and operating channel");
if (mWifiNative.p2pSetChannel(lc, oc)) {
replyToMessage(message, WifiP2pManager.SET_CHANNEL_SUCCEEDED);
} else {
replyToMessage(message, WifiP2pManager.SET_CHANNEL_FAILED);
}
break;
case SET_COUNTRY_CODE:
String countryCode = (String) message.obj;
mWifiNative.setCountryCode(countryCode);
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());
mSavedPeerConfig.invalidate();
}
@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;
if (isConfigInvalid(config)) {
loge("Dropping connect requeset " + config);
replyToMessage(message, WifiP2pManager.CONNECT_FAILED);
break;
}
mAutonomousGroup = false;
mWifiNative.p2pStopFind();
if (reinvokePersistentGroup(config)) {
transitionTo(mGroupNegotiationState);
} else {
transitionTo(mProvisionDiscoveryState);
}
mSavedPeerConfig = config;
mPeers.updateStatus(mSavedPeerConfig.deviceAddress, WifiP2pDevice.INVITED);
sendPeersChangedBroadcast();
replyToMessage(message, WifiP2pManager.CONNECT_SUCCEEDED);
break;
case WifiP2pManager.STOP_DISCOVERY:
if (mWifiNative.p2pStopFind()) {
// When discovery stops in inactive state, flush to clear
// state peer data
mWifiNative.p2pFlush();
mServiceDiscReqId = null;
replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_SUCCEEDED);
} else {
replyToMessage(message, WifiP2pManager.STOP_DISCOVERY_FAILED,
WifiP2pManager.ERROR);
}
break;
case WifiMonitor.P2P_GO_NEGOTIATION_REQUEST_EVENT:
config = (WifiP2pConfig) message.obj;
if (isConfigInvalid(config)) {
loge("Dropping GO neg request " + config);
break;
}
mSavedPeerConfig = config;
mAutonomousGroup = false;
mJoinExistingGroup = false;
transitionTo(mUserAuthorizingNegotiationRequestState);
break;
case WifiMonitor.P2P_INVITATION_RECEIVED_EVENT:
WifiP2pGroup group = (WifiP2pGroup) message.obj;
WifiP2pDevice owner = group.getOwner();
if (owner == null) {
loge("Ignored invitation from null owner");
break;
}
config = new WifiP2pConfig();
config.deviceAddress = group.getOwner().deviceAddress;
if (isConfigInvalid(config)) {
loge("Dropping invitation request " + config);
break;
}
mSavedPeerConfig = config;
//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;
transitionTo(mUserAuthorizingInviteRequestState);
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;
int netId = message.arg1;
boolean ret = false;
if (netId == WifiP2pGroup.PERSISTENT_NET_ID) {
// check if the go persistent group is present.
netId = mGroups.getNetworkId(mThisDevice.deviceAddress);
if (netId != -1) {
ret = mWifiNative.p2pGroupAdd(netId);
} else {
ret = mWifiNative.p2pGroupAdd(true);
}
} else {
ret = mWifiNative.p2pGroupAdd(false);
}
if (ret) {
replyToMessage(message, WifiP2pManager.CREATE_GROUP_SUCCEEDED);
transitionTo(mGroupNegotiationState);
} else {
replyToMessage(message, WifiP2pManager.CREATE_GROUP_FAILED,
WifiP2pManager.ERROR);
// remain at this state.
}
break;
case WifiMonitor.P2P_GROUP_STARTED_EVENT:
mGroup = (WifiP2pGroup) message.obj;
if (DBG) logd(getName() + " group started");
// We hit this scenario when a persistent group is reinvoked
if (mGroup.getNetworkId() == WifiP2pGroup.PERSISTENT_NET_ID) {
mAutonomousGroup = false;
deferMessage(message);
transitionTo(mGroupNegotiationState);
} else {
loge("Unexpected group creation, remove " + mGroup);
mWifiNative.p2pGroupRemove(mGroup.getInterface());
}
break;
case WifiP2pManager.START_LISTEN:
if (DBG) logd(getName() + " start listen mode");
mWifiNative.p2pFlush();
if (mWifiNative.p2pExtListen(true, 500, 500)) {
replyToMessage(message, WifiP2pManager.START_LISTEN_SUCCEEDED);
} else {
replyToMessage(message, WifiP2pManager.START_LISTEN_FAILED);
}
break;
case WifiP2pManager.STOP_LISTEN:
if (DBG) logd(getName() + " stop listen mode");
if (mWifiNative.p2pExtListen(false, 0, 0)) {
replyToMessage(message, WifiP2pManager.STOP_LISTEN_SUCCEEDED);
} else {
replyToMessage(message, WifiP2pManager.STOP_LISTEN_FAILED);
}
mWifiNative.p2pFlush();
break;
case WifiP2pManager.SET_CHANNEL:
Bundle p2pChannels = (Bundle) message.obj;
int lc = p2pChannels.getInt("lc", 0);
int oc = p2pChannels.getInt("oc", 0);
if (DBG) logd(getName() + " set listen and operating channel");
if (mWifiNative.p2pSetChannel(lc, oc)) {
replyToMessage(message, WifiP2pManager.SET_CHANNEL_SUCCEEDED);
} else {
replyToMessage(message, WifiP2pManager.SET_CHANNEL_FAILED);
}
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 (!mSavedPeerConfig.deviceAddress.equals(device.deviceAddress)) {
if (DBG) {
logd("mSavedPeerConfig " + mSavedPeerConfig.deviceAddress +
"device " + device.deviceAddress);
}
// Do the regular device lost handling
ret = NOT_HANDLED;
break;
}
// Do nothing
if (DBG) logd("Add device to lost list " + device);
mPeersLostDuringConnection.updateSupplicantDetails(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 UserAuthorizingNegotiationRequestState extends State {
@Override
public void enter() {
if (DBG) logd(getName());
notifyInvitationReceived();
}
@Override
public boolean processMessage(Message message) {
if (DBG) logd(getName() + message.toString());
boolean ret = HANDLED;
switch (message.what) {
case PEER_CONNECTION_USER_ACCEPT:
mWifiNative.p2pStopFind();
p2pConnectWithPinDisplay(mSavedPeerConfig);
mPeers.updateStatus(mSavedPeerConfig.deviceAddress, WifiP2pDevice.INVITED);
sendPeersChangedBroadcast();
transitionTo(mGroupNegotiationState);
break;
case PEER_CONNECTION_USER_REJECT:
if (DBG) logd("User rejected negotiation " + mSavedPeerConfig);
transitionTo(mInactiveState);
break;
default:
return NOT_HANDLED;
}
return ret;
}
@Override
public void exit() {
//TODO: dismiss dialog if not already done
}
}
class UserAuthorizingInviteRequestState extends State {
@Override
public void enter() {
if (DBG) logd(getName());
notifyInvitationReceived();
}
@Override
public boolean processMessage(Message message) {
if (DBG) logd(getName() + message.toString());
boolean ret = HANDLED;
switch (message.what) {
case PEER_CONNECTION_USER_ACCEPT:
mWifiNative.p2pStopFind();
if (!reinvokePersistentGroup(mSavedPeerConfig)) {
// Do negotiation when persistence fails
p2pConnectWithPinDisplay(mSavedPeerConfig);
}
mPeers.updateStatus(mSavedPeerConfig.deviceAddress, WifiP2pDevice.INVITED);
sendPeersChangedBroadcast();
transitionTo(mGroupNegotiationState);
break;
case PEER_CONNECTION_USER_REJECT:
if (DBG) logd("User rejected invitation " + mSavedPeerConfig);
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);
p2pConnectWithPinDisplay(mSavedPeerConfig);
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)) {
p2pConnectWithPinDisplay(mSavedPeerConfig);
transitionTo(mGroupNegotiationState);
} else {
mJoinExistingGroup = false;
transitionTo(mUserAuthorizingNegotiationRequestState);
}
}
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;
p2pConnectWithPinDisplay(mSavedPeerConfig);
notifyInvitationSent(provDisc.pin, device.deviceAddress);
transitionTo(mGroupNegotiationState);
}
break;
case WifiMonitor.P2P_PROV_DISC_FAILURE_EVENT:
loge("provision discovery failed");
handleGroupCreationFailure();
transitionTo(mInactiveState);
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.getNetworkId() == WifiP2pGroup.PERSISTENT_NET_ID) {
/*
* update cache information and set network id to mGroup.
*/
updatePersistentNetworks(NO_RELOAD);
String devAddr = mGroup.getOwner().deviceAddress;
mGroup.setNetworkId(mGroups.getNetworkId(devAddr,
mGroup.getNetworkName()));
}
if (mGroup.isGroupOwner()) {
/* Setting an idle time out on GO causes issues with certain scenarios
* on clients where it can be off-channel for longer and with the power
* save modes used.
*
* TODO: Verify multi-channel scenarios and supplicant behavior are
* better before adding a time out in future
*/
//Set group idle timeout of 10 sec, to avoid GO beaconing incase of any
//failure during 4-way Handshake.
if (!mAutonomousGroup) {
mWifiNative.setP2pGroupIdle(mGroup.getInterface(), GROUP_IDLE_TIME_S);
}
startDhcpServer(mGroup.getInterface());
} else {
mWifiNative.setP2pGroupIdle(mGroup.getInterface(), GROUP_IDLE_TIME_S);
mDhcpStateMachine = DhcpStateMachine.makeDhcpStateMachine(mContext,
P2pStateMachine.this, mGroup.getInterface());
// TODO: We should use DHCP state machine PRE message like WifiStateMachine
mWifiNative.setP2pPowerSave(mGroup.getInterface(), false);
mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_START_DHCP);
WifiP2pDevice groupOwner = mGroup.getOwner();
WifiP2pDevice peer = mPeers.get(groupOwner.deviceAddress);
if (peer != null) {
// update group owner details with peer details found at discovery
groupOwner.updateSupplicantDetails(peer);
mPeers.updateStatus(groupOwner.deviceAddress, WifiP2pDevice.CONNECTED);
sendPeersChangedBroadcast();
} else {
// A supplicant bug can lead to reporting an invalid
// group owner address (all zeroes) at times. Avoid a
// crash, but continue group creation since it is not
// essential.
logw("Unknown group owner " + groupOwner);
}
}
transitionTo(mGroupCreatedState);
break;
case WifiMonitor.P2P_GO_NEGOTIATION_FAILURE_EVENT:
P2pStatus status = (P2pStatus) message.obj;
if (status == P2pStatus.NO_COMMON_CHANNEL) {
transitionTo(mFrequencyConflictState);
break;
}
/* continue with group removal handling */
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:
status = (P2pStatus) message.obj;
if (status == P2pStatus.NO_COMMON_CHANNEL) {
transitionTo(mFrequencyConflictState);
break;
}
break;
case WifiMonitor.P2P_INVITATION_RESULT_EVENT:
status = (P2pStatus)message.obj;
if (status == P2pStatus.SUCCESS) {
// invocation was succeeded.
// wait P2P_GROUP_STARTED_EVENT.
break;
}
loge("Invitation result " + status);
if (status == P2pStatus.UNKNOWN_P2P_GROUP) {
// target device has already removed the credential.
// So, remove this credential accordingly.
int netId = mSavedPeerConfig.netId;
if (netId >= 0) {
if (DBG) logd("Remove unknown client from the list");
removeClientFromList(netId, mSavedPeerConfig.deviceAddress, true);
}
// Reinvocation has failed, try group negotiation
mSavedPeerConfig.netId = WifiP2pGroup.PERSISTENT_NET_ID;
p2pConnectWithPinDisplay(mSavedPeerConfig);
} else if (status == P2pStatus.INFORMATION_IS_CURRENTLY_UNAVAILABLE) {
// Devices setting persistent_reconnect to 0 in wpa_supplicant
// always defer the invocation request and return
// "information is currently unable" error.
// So, try another way to connect for interoperability.
mSavedPeerConfig.netId = WifiP2pGroup.PERSISTENT_NET_ID;
p2pConnectWithPinDisplay(mSavedPeerConfig);
} else if (status == P2pStatus.NO_COMMON_CHANNEL) {
transitionTo(mFrequencyConflictState);
} else {
handleGroupCreationFailure();
transitionTo(mInactiveState);
}
break;
default:
return NOT_HANDLED;
}
return HANDLED;
}
}
class FrequencyConflictState extends State {
private AlertDialog mFrequencyConflictDialog;
@Override
public void enter() {
if (DBG) logd(getName());
notifyFrequencyConflict();
}
private void notifyFrequencyConflict() {
logd("Notify frequency conflict");
Resources r = Resources.getSystem();
AlertDialog dialog = new AlertDialog.Builder(mContext)
.setMessage(r.getString(R.string.wifi_p2p_frequency_conflict_message,
getDeviceName(mSavedPeerConfig.deviceAddress)))
.setPositiveButton(r.getString(R.string.dlg_ok), new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
sendMessage(DROP_WIFI_USER_ACCEPT);
}
})
.setNegativeButton(r.getString(R.string.decline), new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
sendMessage(DROP_WIFI_USER_REJECT);
}
})
.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface arg0) {
sendMessage(DROP_WIFI_USER_REJECT);
}
})
.create();
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
dialog.show();
mFrequencyConflictDialog = dialog;
}
@Override
public boolean processMessage(Message message) {
if (DBG) logd(getName() + message.toString());
switch (message.what) {
case WifiMonitor.P2P_GO_NEGOTIATION_SUCCESS_EVENT:
case WifiMonitor.P2P_GROUP_FORMATION_SUCCESS_EVENT:
loge(getName() + "group sucess during freq conflict!");
break;
case WifiMonitor.P2P_GROUP_STARTED_EVENT:
loge(getName() + "group started after freq conflict, handle anyway");
deferMessage(message);
transitionTo(mGroupNegotiationState);
break;
case WifiMonitor.P2P_GO_NEGOTIATION_FAILURE_EVENT:
case WifiMonitor.P2P_GROUP_REMOVED_EVENT:
case WifiMonitor.P2P_GROUP_FORMATION_FAILURE_EVENT:
// Ignore failures since we retry again
break;
case DROP_WIFI_USER_REJECT:
// User rejected dropping wifi in favour of p2p
handleGroupCreationFailure();
transitionTo(mInactiveState);
break;
case DROP_WIFI_USER_ACCEPT:
// User accepted dropping wifi in favour of p2p
mWifiChannel.sendMessage(WifiP2pService.DISCONNECT_WIFI_REQUEST, 1);
mTempoarilyDisconnectedWifi = true;
break;
case DISCONNECT_WIFI_RESPONSE:
// Got a response from wifistatemachine, retry p2p
if (DBG) logd(getName() + "Wifi disconnected, retry p2p");
transitionTo(mInactiveState);
sendMessage(WifiP2pManager.CONNECT, mSavedPeerConfig);
break;
default:
return NOT_HANDLED;
}
return HANDLED;
}
public void exit() {
if (mFrequencyConflictDialog != null) mFrequencyConflictDialog.dismiss();
}
}
class GroupCreatedState extends State {
@Override
public void enter() {
if (DBG) logd(getName());
// Once connected, peer config details are invalid
mSavedPeerConfig.invalidate();
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(NetworkUtils.numericToInetAddress(SERVER_ADDRESS));
}
// In case of a negotiation group, connection changed is sent
// after a client joins. For autonomous, send now
if (mAutonomousGroup) {
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;
// Clear timeout that was set when group was started.
mWifiNative.setP2pGroupIdle(mGroup.getInterface(), 0);
if (deviceAddress != null) {
if (mPeers.get(deviceAddress) != null) {
mGroup.addClient(mPeers.get(deviceAddress));
} else {
mGroup.addClient(deviceAddress);
}
mPeers.updateStatus(deviceAddress, WifiP2pDevice.CONNECTED);
if (DBG) logd(getName() + " ap sta connected");
sendPeersChangedBroadcast();
} else {
loge("Connect on null device address, ignore");
}
sendP2pConnectionChangedBroadcast();
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()) {
logd("Client list empty, remove non-persistent p2p group");
mWifiNative.p2pGroupRemove(mGroup.getInterface());
// We end up sending connection changed broadcast
// when this happens at exit()
} else {
// Notify when a client disconnects from group
sendP2pConnectionChangedBroadcast();
}
} else {
if (DBG) logd("Failed to remove client " + deviceAddress);
for (WifiP2pDevice c : mGroup.getClientList()) {
if (DBG) logd("client " + c.deviceAddress);
}
}
sendPeersChangedBroadcast();
if (DBG) logd(getName() + " ap sta disconnected");
} else {
loge("Disconnect on unknown device: " + device);
}
break;
case DhcpStateMachine.CMD_POST_DHCP_ACTION:
DhcpResults dhcpResults = (DhcpResults) message.obj;
if (message.arg1 == DhcpStateMachine.DHCP_SUCCESS &&
dhcpResults != null) {
if (DBG) logd("DhcpResults: " + dhcpResults);
setWifiP2pInfoOnGroupFormation(dhcpResults.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) logd(getName() + " remove group");
if (mWifiNative.p2pGroupRemove(mGroup.getInterface())) {
transitionTo(mOngoingGroupRemovalState);
replyToMessage(message, WifiP2pManager.REMOVE_GROUP_SUCCEEDED);
} else {
handleGroupRemoved();
transitionTo(mInactiveState);
replyToMessage(message, WifiP2pManager.REMOVE_GROUP_FAILED,
WifiP2pManager.ERROR);
}
break;
/* We do not listen to NETWORK_DISCONNECTION_EVENT for group removal
* handling since supplicant actually tries to reconnect after a temporary
* disconnect until group idle time out. Eventually, a group removal event
* will come when group has been removed.
*
* When there are connectivity issues during temporary disconnect, the application
* will also just remove the group.
*
* Treating network disconnection as group removal causes race conditions since
* supplicant would still maintain the group at that stage.
*/
case WifiMonitor.P2P_GROUP_REMOVED_EVENT:
if (DBG) logd(getName() + " group removed");
handleGroupRemoved();
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("Add device to lost list " + device);
mPeersLostDuringConnection.updateSupplicantDetails(device);
return HANDLED;
}
// Do the regular device lost handling
return NOT_HANDLED;
case WifiStateMachine.CMD_DISABLE_P2P_REQ:
sendMessage(WifiP2pManager.REMOVE_GROUP);
deferMessage(message);
break;
// This allows any client to join the GO during the
// WPS window
case WifiP2pManager.START_WPS:
WpsInfo wps = (WpsInfo) message.obj;
if (wps == null) {
replyToMessage(message, WifiP2pManager.START_WPS_FAILED);
break;
}
boolean ret = true;
if (wps.setup == WpsInfo.PBC) {
ret = mWifiNative.startWpsPbc(mGroup.getInterface(), null);
} else {
if (wps.pin == null) {
String pin = mWifiNative.startWpsPinDisplay(mGroup.getInterface());
try {
Integer.parseInt(pin);
notifyInvitationSent(pin, "any");
} catch (NumberFormatException ignore) {
ret = false;
}
} else {
ret = mWifiNative.startWpsPinKeypad(mGroup.getInterface(),
wps.pin);
}
}
replyToMessage(message, ret ? WifiP2pManager.START_WPS_SUCCEEDED :
WifiP2pManager.START_WPS_FAILED);
break;
case WifiP2pManager.CONNECT:
WifiP2pConfig config = (WifiP2pConfig) message.obj;
if (isConfigInvalid(config)) {
loge("Dropping connect requeset " + config);
replyToMessage(message, WifiP2pManager.CONNECT_FAILED);
break;
}
logd("Inviting device : " + config.deviceAddress);
mSavedPeerConfig = config;
if (mWifiNative.p2pInvite(mGroup, config.deviceAddress)) {
mPeers.updateStatus(config.deviceAddress, WifiP2pDevice.INVITED);
sendPeersChangedBroadcast();
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_INVITATION_RESULT_EVENT:
P2pStatus status = (P2pStatus)message.obj;
if (status == P2pStatus.SUCCESS) {
// invocation was succeeded.
break;
}
loge("Invitation result " + status);
if (status == P2pStatus.UNKNOWN_P2P_GROUP) {
// target device has already removed the credential.
// So, remove this credential accordingly.
int netId = mGroup.getNetworkId();
if (netId >= 0) {
if (DBG) logd("Remove unknown client from the list");
if (!removeClientFromList(netId,
mSavedPeerConfig.deviceAddress, false)) {
// not found the client on the list
loge("Already removed the client, ignore");
break;
}
// try invitation.
sendMessage(WifiP2pManager.CONNECT, mSavedPeerConfig);
}
}
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:
loge("Duplicate group creation event notice, ignore");
break;
default:
return NOT_HANDLED;
}
return HANDLED;
}
public void exit() {
updateThisDevice(WifiP2pDevice.AVAILABLE);
resetWifiP2pInfo();
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:
//Stop discovery to avoid failure due to channel switch
mWifiNative.p2pStopFind();
if (mSavedPeerConfig.wps.setup == WpsInfo.PBC) {
mWifiNative.startWpsPbc(mGroup.getInterface(), null);
} else {
mWifiNative.startWpsPinKeypad(mGroup.getInterface(),
mSavedPeerConfig.wps.pin);
}
transitionTo(mGroupCreatedState);
break;
case PEER_CONNECTION_USER_REJECT:
if (DBG) logd("User rejected incoming request");
transitionTo(mGroupCreatedState);
break;
default:
return NOT_HANDLED;
}
return HANDLED;
}
@Override
public void exit() {
//TODO: dismiss dialog if not already done
}
}
class OngoingGroupRemovalState 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) {
// Group removal ongoing. Multiple calls
// end up removing persisted network. Do nothing.
case WifiP2pManager.REMOVE_GROUP:
replyToMessage(message, WifiP2pManager.REMOVE_GROUP_SUCCEEDED);
break;
// Parent state will transition out of this state
// when removal is complete
default:
return NOT_HANDLED;
}
return HANDLED;
}
}
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
super.dump(fd, pw, args);
pw.println("mWifiP2pInfo " + mWifiP2pInfo);
pw.println("mGroup " + mGroup);
pw.println("mSavedPeerConfig " + mSavedPeerConfig);
pw.println("mSavedP2pGroup " + mSavedP2pGroup);
pw.println();
}
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.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
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.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
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.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
private void sendPeersChangedBroadcast() {
final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
intent.putExtra(WifiP2pManager.EXTRA_P2P_DEVICE_LIST, new WifiP2pDeviceList(mPeers));
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
}
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));
intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP, new WifiP2pGroup(mGroup));
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
mWifiChannel.sendMessage(WifiP2pService.P2P_CONNECTION_CHANGED,
new NetworkInfo(mNetworkInfo));
}
private void sendP2pPersistentGroupsChangedBroadcast() {
if (DBG) logd("sending p2p persistent groups changed broadcast");
Intent intent = new Intent(WifiP2pManager.WIFI_P2P_PERSISTENT_GROUPS_CHANGED_ACTION);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
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(String intf) {
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;
}
if ((r.getConfiguration().uiMode & Configuration.UI_MODE_TYPE_APPLIANCE) ==
Configuration.UI_MODE_TYPE_APPLIANCE) {
// For appliance devices, add a key listener which accepts.
dialog.setOnKeyListener(new DialogInterface.OnKeyListener() {
@Override
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
// TODO: make the actual key come from a config value.
if (keyCode == KeyEvent.KEYCODE_VOLUME_MUTE) {
sendMessage(PEER_CONNECTION_USER_ACCEPT);
dialog.dismiss();
return true;
}
return false;
}
});
// TODO: add timeout for this dialog.
// TODO: update UI in appliance mode to tell user what to do.
}
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
dialog.show();
}
/**
* Synchronize the persistent group list between
* wpa_supplicant and mGroups.
*/
private void updatePersistentNetworks(boolean reload) {
String listStr = mWifiNative.listNetworks();
if (listStr == null) return;
boolean isSaveRequired = false;
String[] lines = listStr.split("\n");
if (lines == null) return;
if (reload) mGroups.clear();
// Skip the first line, which is a header
for (int i = 1; i < lines.length; i++) {
String[] result = lines[i].split("\t");
if (result == null || result.length < 4) {
continue;
}
// network-id | ssid | bssid | flags
int netId = -1;
String ssid = result[1];
String bssid = result[2];
String flags = result[3];
try {
netId = Integer.parseInt(result[0]);
} catch(NumberFormatException e) {
e.printStackTrace();
continue;
}
if (flags.indexOf("[CURRENT]") != -1) {
continue;
}
if (flags.indexOf("[P2P-PERSISTENT]") == -1) {
/*
* The unused profile is sometimes remained when the p2p group formation is failed.
* So, we clean up the p2p group here.
*/
if (DBG) logd("clean up the unused persistent group. netId=" + netId);
mWifiNative.removeNetwork(netId);
isSaveRequired = true;
continue;
}
if (mGroups.contains(netId)) {
continue;
}
WifiP2pGroup group = new WifiP2pGroup();
group.setNetworkId(netId);
group.setNetworkName(ssid);
String mode = mWifiNative.getNetworkVariable(netId, "mode");
if (mode != null && mode.equals("3")) {
group.setIsGroupOwner(true);
}
if (bssid.equalsIgnoreCase(mThisDevice.deviceAddress)) {
group.setOwner(mThisDevice);
} else {
WifiP2pDevice device = new WifiP2pDevice();
device.deviceAddress = bssid;
group.setOwner(device);
}
mGroups.add(group);
isSaveRequired = true;
}
if (reload || isSaveRequired) {
mWifiNative.saveConfig();
sendP2pPersistentGroupsChangedBroadcast();
}
}
/**
* A config is valid if it has a peer address that has already been
* discovered
* @return true if it is invalid, false otherwise
*/
private boolean isConfigInvalid(WifiP2pConfig config) {
if (config == null) return true;
if (TextUtils.isEmpty(config.deviceAddress)) return true;
if (mPeers.get(config.deviceAddress) == null) return true;
return false;
}
/* TODO: The supplicant does not provide group capability changes as an event.
* Having it pushed as an event would avoid polling for this information right
* before a connection
*/
private WifiP2pDevice fetchCurrentDeviceDetails(WifiP2pConfig config) {
/* Fetch & update group capability from supplicant on the device */
int gc = mWifiNative.getGroupCapability(config.deviceAddress);
mPeers.updateGroupCapability(config.deviceAddress, gc);
return mPeers.get(config.deviceAddress);
}
/**
* Start a p2p group negotiation and display pin if necessary
* @param config for the peer
*/
private void p2pConnectWithPinDisplay(WifiP2pConfig config) {
WifiP2pDevice dev = fetchCurrentDeviceDetails(config);
String pin = mWifiNative.p2pConnect(config, dev.isGroupOwner());
try {
Integer.parseInt(pin);
notifyInvitationSent(pin, config.deviceAddress);
} catch (NumberFormatException ignore) {
// do nothing if p2pConnect did not return a pin
}
}
/**
* Reinvoke a persistent group.
*
* @param config for the peer
* @return true on success, false on failure
*/
private boolean reinvokePersistentGroup(WifiP2pConfig config) {
WifiP2pDevice dev = fetchCurrentDeviceDetails(config);
boolean join = dev.isGroupOwner();
String ssid = mWifiNative.p2pGetSsid(dev.deviceAddress);
if (DBG) logd("target ssid is " + ssid + " join:" + join);
if (join && dev.isGroupLimit()) {
if (DBG) logd("target device reaches group limit.");
// if the target group has reached the limit,
// try group formation.
join = false;
} else if (join) {
int netId = mGroups.getNetworkId(dev.deviceAddress, ssid);
if (netId >= 0) {
// Skip WPS and start 4way handshake immediately.
if (!mWifiNative.p2pGroupAdd(netId)) {
return false;
}
return true;
}
}
if (!join && dev.isDeviceLimit()) {
loge("target device reaches the device limit.");
return false;
}
if (!join && dev.isInvitationCapable()) {
int netId = WifiP2pGroup.PERSISTENT_NET_ID;
if (config.netId >= 0) {
if (config.deviceAddress.equals(mGroups.getOwnerAddr(config.netId))) {
netId = config.netId;
}
} else {
netId = mGroups.getNetworkId(dev.deviceAddress);
}
if (netId < 0) {
netId = getNetworkIdFromClientList(dev.deviceAddress);
}
if (DBG) logd("netId related with " + dev.deviceAddress + " = " + netId);
if (netId >= 0) {
// Invoke the persistent group.
if (mWifiNative.p2pReinvoke(netId, dev.deviceAddress)) {
// Save network id. It'll be used when an invitation result event is received.
config.netId = netId;
return true;
} else {
loge("p2pReinvoke() failed, update networks");
updatePersistentNetworks(RELOAD);
return false;
}
}
}
return false;
}
/**
* Return the network id of the group owner profile which has the p2p client with
* the specified device address in it's client list.
* If more than one persistent group of the same address is present in its client
* lists, return the first one.
*
* @param deviceAddress p2p device address.
* @return the network id. if not found, return -1.
*/
private int getNetworkIdFromClientList(String deviceAddress) {
if (deviceAddress == null) return -1;
Collection<WifiP2pGroup> groups = mGroups.getGroupList();
for (WifiP2pGroup group : groups) {
int netId = group.getNetworkId();
String[] p2pClientList = getClientList(netId);
if (p2pClientList == null) continue;
for (String client : p2pClientList) {
if (deviceAddress.equalsIgnoreCase(client)) {
return netId;
}
}
}
return -1;
}
/**
* Return p2p client list associated with the specified network id.
* @param netId network id.
* @return p2p client list. if not found, return null.
*/
private String[] getClientList(int netId) {
String p2pClients = mWifiNative.getNetworkVariable(netId, "p2p_client_list");
if (p2pClients == null) {
return null;
}
return p2pClients.split(" ");
}
/**
* Remove the specified p2p client from the specified profile.
* @param netId network id of the profile.
* @param addr p2p client address to be removed.
* @param isRemovable if true, remove the specified profile if its client list becomes empty.
* @return whether removing the specified p2p client is successful or not.
*/
private boolean removeClientFromList(int netId, String addr, boolean isRemovable) {
StringBuilder modifiedClientList = new StringBuilder();
String[] currentClientList = getClientList(netId);
boolean isClientRemoved = false;
if (currentClientList != null) {
for (String client : currentClientList) {
if (!client.equalsIgnoreCase(addr)) {
modifiedClientList.append(" ");
modifiedClientList.append(client);
} else {
isClientRemoved = true;
}
}
}
if (modifiedClientList.length() == 0 && isRemovable) {
// the client list is empty. so remove it.
if (DBG) logd("Remove unknown network");
mGroups.remove(netId);
return true;
}
if (!isClientRemoved) {
// specified p2p client is not found. already removed.
return false;
}
if (DBG) logd("Modified client list: " + modifiedClientList);
if (modifiedClientList.length() == 0) {
modifiedClientList.append("\"\"");
}
mWifiNative.setNetworkVariable(netId,
"p2p_client_list", modifiedClientList.toString());
mWifiNative.saveConfig();
return true;
}
private void setWifiP2pInfoOnGroupFormation(InetAddress serverInetAddress) {
mWifiP2pInfo.groupFormed = true;
mWifiP2pInfo.isGroupOwner = mGroup.isGroupOwner();
mWifiP2pInfo.groupOwnerAddress = serverInetAddress;
}
private void resetWifiP2pInfo() {
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 String getPersistedDeviceName() {
String deviceName = Settings.Global.getString(mContext.getContentResolver(),
Settings.Global.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.Global.putString(mContext.getContentResolver(),
Settings.Global.WIFI_P2P_DEVICE_NAME, devName);
sendThisDeviceChangedBroadcast();
return true;
}
private boolean setWfdInfo(WifiP2pWfdInfo wfdInfo) {
boolean success;
if (!wfdInfo.isWfdEnabled()) {
success = mWifiNative.setWfdEnable(false);
} else {
success =
mWifiNative.setWfdEnable(true)
&& mWifiNative.setWfdDeviceInfo(wfdInfo.getDeviceInfoHex());
}
if (!success) {
loge("Failed to set wfd properties");
return false;
}
mThisDevice.wfdInfo = wfdInfo;
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) logd("DeviceAddress: " + mThisDevice.deviceAddress);
mClientInfoList.clear();
mWifiNative.p2pFlush();
mWifiNative.p2pServiceFlush();
mServiceTransactionId = 0;
mServiceDiscReqId = null;
String countryCode = Settings.Global.getString(mContext.getContentResolver(),
Settings.Global.WIFI_COUNTRY_CODE);
if (countryCode != null && !countryCode.isEmpty()) {
mP2pStateMachine.sendMessage(SET_COUNTRY_CODE, countryCode);
}
updatePersistentNetworks(RELOAD);
}
private void updateThisDevice(int status) {
mThisDevice.status = status;
sendThisDeviceChangedBroadcast();
}
private void handleGroupCreationFailure() {
resetWifiP2pInfo();
mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.FAILED, null, null);
sendP2pConnectionChangedBroadcast();
// Remove only the peer we failed to connect to so that other devices discovered
// that have not timed out still remain in list for connection
boolean peersChanged = mPeers.remove(mPeersLostDuringConnection);
if (mPeers.remove(mSavedPeerConfig.deviceAddress) != null) {
peersChanged = true;
}
if (peersChanged) {
sendPeersChangedBroadcast();
}
mPeersLostDuringConnection.clear();
mServiceDiscReqId = null;
sendMessage(WifiP2pManager.DISCOVER_PEERS);
}
private void handleGroupRemoved() {
if (mGroup.isGroupOwner()) {
stopDhcpServer(mGroup.getInterface());
} else {
if (DBG) logd("stop DHCP client");
mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_STOP_DHCP);
mDhcpStateMachine.doQuit();
mDhcpStateMachine = null;
}
try {
mNwService.clearInterfaceAddresses(mGroup.getInterface());
} catch (Exception e) {
loge("Failed to clear addresses " + e);
}
NetworkUtils.resetConnections(mGroup.getInterface(), NetworkUtils.RESET_ALL_ADDRESSES);
// Clear any timeout that was set. This is essential for devices
// that reuse the main p2p interface for a created group.
mWifiNative.setP2pGroupIdle(mGroup.getInterface(), 0);
boolean peersChanged = false;
// Remove only peers part of the group, so that other devices discovered
// that have not timed out still remain in list for connection
for (WifiP2pDevice d : mGroup.getClientList()) {
if (mPeers.remove(d)) peersChanged = true;
}
if (mPeers.remove(mGroup.getOwner())) peersChanged = true;
if (mPeers.remove(mPeersLostDuringConnection)) peersChanged = true;
if (peersChanged) {
sendPeersChangedBroadcast();
}
mGroup = null;
mPeersLostDuringConnection.clear();
mServiceDiscReqId = null;
if (mTempoarilyDisconnectedWifi) {
mWifiChannel.sendMessage(WifiP2pService.DISCONNECT_WIFI_REQUEST, 0);
mTempoarilyDisconnectedWifi = false;
}
}
//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;
}
@Override
protected void logd(String s) {
Slog.d(TAG, s);
}
@Override
protected 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<Messenger> deadClients = new ArrayList<Messenger>();
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;
}
}
/**
* 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<WifiP2pServiceRequest> mReqList;
/*
* A local service information list.
*/
private List<WifiP2pServiceInfo> mServList;
private ClientInfo(Messenger m) {
mMessenger = m;
mReqList = new SparseArray();
mServList = new ArrayList<WifiP2pServiceInfo>();
}
}
}