/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.net.wifi.p2p;

import android.app.AlertDialog;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.net.IConnectivityManager;
import android.net.ConnectivityManager;
import android.net.DhcpInfoInternal;
import android.net.DhcpStateMachine;
import android.net.InterfaceConfiguration;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkInfo;
import android.net.NetworkUtils;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiMonitor;
import android.net.wifi.WifiNative;
import android.net.wifi.WifiStateMachine;
import android.net.wifi.WpsInfo;
import android.os.Binder;
import android.os.IBinder;
import android.os.INetworkManagementService;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.os.Messenger;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.provider.Settings;
import android.util.Slog;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.EditText;

import com.android.internal.R;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.util.AsyncChannel;
import com.android.internal.util.Protocol;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Collection;

/**
 * WifiP2pService inclues a state machine to perform Wi-Fi p2p operations. Applications
 * communicate with this service to issue device discovery and connectivity requests
 * through the WifiP2pManager interface. The state machine communicates with the wifi
 * driver through wpa_supplicant and handles the event responses through WifiMonitor.
 *
 * Note that the term Wifi when used without a p2p suffix refers to the client mode
 * of Wifi operation
 * @hide
 */
public class WifiP2pService extends IWifiP2pManager.Stub {
    private static final String TAG = "WifiP2pService";
    private static final boolean DBG = false;
    private static final String NETWORKTYPE = "WIFI_P2P";

    private Context mContext;
    private String mInterface;
    private Notification mNotification;

    INetworkManagementService mNwService;
    private DhcpStateMachine mDhcpStateMachine;

    //Tracked to notify the user about wifi client/hotspot being shut down
    //during p2p bring up
    private int mWifiState = WifiManager.WIFI_STATE_DISABLED;
    private int mWifiApState = WifiManager.WIFI_AP_STATE_DISABLED;

    private P2pStateMachine mP2pStateMachine;
    private AsyncChannel mReplyChannel = new AsyncChannel();
    private AsyncChannel mWifiChannel;

    /* Two minutes comes from the wpa_supplicant setting */
    private static final int GROUP_NEGOTIATION_WAIT_TIME_MS = 120 * 1000;
    private static int mGroupNegotiationTimeoutIndex = 0;

    /**
     * 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;

    /* Message sent to WifiStateMachine to indicate p2p enable is pending */
    public static final int P2P_ENABLE_PENDING              =   BASE + 1;
    /* Message sent to WifiStateMachine to indicate Wi-Fi client/hotspot operation can proceed */
    public static final int WIFI_ENABLE_PROCEED             =   BASE + 2;

    /* Delayed message to timeout of group negotiation */
    public static final int GROUP_NEGOTIATION_TIMED_OUT     =   BASE + 3;

    /* User accepted to disable Wi-Fi in order to enable p2p */
    private static final int WIFI_DISABLE_USER_ACCEPT       =   BASE + 4;
    /* User rejected to disable Wi-Fi in order to enable p2p */
    private static final int WIFI_DISABLE_USER_REJECT       =   BASE + 5;

    /* User accepted a group negotiation request */
    private static final int GROUP_NEGOTIATION_USER_ACCEPT  =   BASE + 6;
    /* User rejected a group negotiation request */
    private static final int GROUP_NEGOTIATION_USER_REJECT  =   BASE + 7;

    /* User accepted a group invitation request */
    private static final int GROUP_INVITATION_USER_ACCEPT   =   BASE + 8;
    /* User rejected a group invitation request */
    private static final int GROUP_INVITATION_USER_REJECT   =   BASE + 9;

    /* Airplane mode changed */
    private static final int AIRPLANE_MODE_CHANGED          =   BASE + 10;
    /* Emergency callback mode */
    private static final int EMERGENCY_CALLBACK_MODE        =   BASE + 11;
    private static final int WPS_PBC                        =   BASE + 12;
    private static final int WPS_PIN                        =   BASE + 13;

    private final boolean mP2pSupported;

    private WifiP2pDevice mThisDevice = new WifiP2pDevice();

    /* When a group has been explicitly created by an app, we persist the group
     * even after all clients have been disconnected until an explicit remove
     * is invoked */
    private boolean mPersistGroup;

    private NetworkInfo mNetworkInfo;

    /* Is chosen as a unique range to avoid conflict with
       the range defined in Tethering.java */
    private static final String[] DHCP_RANGE = {"192.168.49.2", "192.168.49.254"};
    private static final String SERVER_ADDRESS = "192.168.49.1";

    public WifiP2pService(Context context) {
        mContext = context;

        mInterface = SystemProperties.get("wifi.interface", "wlan0");
        mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI_P2P, 0, NETWORKTYPE, "");

        mP2pSupported = mContext.getPackageManager().hasSystemFeature(
                PackageManager.FEATURE_WIFI_DIRECT);

        mThisDevice.primaryDeviceType = mContext.getResources().getString(
                com.android.internal.R.string.config_wifi_p2p_device_type);
        mThisDevice.deviceName = getDefaultDeviceName();

        mP2pStateMachine = new P2pStateMachine(TAG, mP2pSupported);
        mP2pStateMachine.start();

        // broadcasts
        IntentFilter filter = new IntentFilter();
        filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
        filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
        filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
        filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
        mContext.registerReceiver(new WifiStateReceiver(), filter);

    }

    public void connectivityServiceReady() {
        IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
        mNwService = INetworkManagementService.Stub.asInterface(b);
    }

    private class WifiStateReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
                mWifiState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
                        WifiManager.WIFI_STATE_DISABLED);
            } else if (action.equals(WifiManager.WIFI_AP_STATE_CHANGED_ACTION)) {
                mWifiApState = intent.getIntExtra(WifiManager.EXTRA_WIFI_AP_STATE,
                        WifiManager.WIFI_AP_STATE_DISABLED);
            } else if (action.equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) {
                mP2pStateMachine.sendMessage(AIRPLANE_MODE_CHANGED);
            } else if (action.equals(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED)) {
                if (intent.getBooleanExtra("phoneinECMState", false) == true) {
                    mP2pStateMachine.sendMessage(EMERGENCY_CALLBACK_MODE);
                }
            }
        }
    }

    private void enforceAccessPermission() {
        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_WIFI_STATE,
                "WifiP2pService");
    }

    private void enforceChangePermission() {
        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE,
                "WifiP2pService");
    }

    /* We use the 4 digits of the ANDROID_ID to have a friendly
     * default that has low likelihood of collision with a peer */
    private String getDefaultDeviceName() {
        String id = Settings.Secure.getString(mContext.getContentResolver(),
                    Settings.Secure.ANDROID_ID);
        return "Android_" + id.substring(0,4);
    }

    /**
     * Get a reference to handler. This is used by a client to establish
     * an AsyncChannel communication with WifiP2pService
     */
    public Messenger getMessenger() {
        enforceAccessPermission();
        enforceChangePermission();
        return new Messenger(mP2pStateMachine.getHandler());
    }

    @Override
    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
                != PackageManager.PERMISSION_GRANTED) {
            pw.println("Permission Denial: can't dump WifiP2pService from from pid="
                    + Binder.getCallingPid()
                    + ", uid=" + Binder.getCallingUid());
            return;
        }
    }


    /**
     * Handles interaction with WifiStateMachine
     */
    private class P2pStateMachine extends StateMachine {

        private DefaultState mDefaultState = new DefaultState();
        private P2pNotSupportedState mP2pNotSupportedState = new P2pNotSupportedState();
        private P2pDisablingState mP2pDisablingState = new P2pDisablingState();
        private P2pDisabledState mP2pDisabledState = new P2pDisabledState();
        private WaitForUserActionState mWaitForUserActionState = new WaitForUserActionState();
        private WaitForWifiDisableState mWaitForWifiDisableState = new WaitForWifiDisableState();
        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 UserAuthorizingGroupNegotiationState mUserAuthorizingGroupNegotiationState
                = new UserAuthorizingGroupNegotiationState();
        private UserAuthorizingGroupInvitationState mUserAuthorizingGroupInvitationState
                = new UserAuthorizingGroupInvitationState();
        private GroupNegotiationState mGroupNegotiationState = new GroupNegotiationState();
        private GroupCreatedState mGroupCreatedState = new GroupCreatedState();

        private WifiMonitor mWifiMonitor = new WifiMonitor(this);

        private WifiP2pDeviceList mPeers = new WifiP2pDeviceList();
        private WifiP2pInfo mWifiP2pInfo = new WifiP2pInfo();
        private WifiP2pGroup mGroup;

        // Saved WifiP2pConfig from GO negotiation request
        private WifiP2pConfig mSavedGoNegotiationConfig;

        // Saved WifiP2pConfig from connect request
        private WifiP2pConfig mSavedConnectConfig;

        // 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(mWaitForUserActionState, mP2pDisabledState);
                    addState(mWaitForWifiDisableState, mP2pDisabledState);
                addState(mP2pEnablingState, mDefaultState);
                addState(mP2pEnabledState, mDefaultState);
                    addState(mInactiveState, mP2pEnabledState);
                        addState(mUserAuthorizingGroupNegotiationState, mInactiveState);
                        addState(mUserAuthorizingGroupInvitationState, mInactiveState);
                    addState(mGroupNegotiationState, mP2pEnabledState);
                    addState(mGroupCreatedState, mP2pEnabledState);

            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 WifiStateMachine.WIFI_ENABLE_PENDING:
                    // Disable p2p operation before we can respond
                    sendMessage(WifiP2pManager.DISABLE_P2P);
                    deferMessage(message);
                    break;
                case WifiP2pManager.ENABLE_P2P:
                    replyToMessage(message, WifiP2pManager.ENABLE_P2P_FAILED,
                            WifiP2pManager.BUSY);
                    break;
                case WifiP2pManager.DISABLE_P2P:
                    replyToMessage(message, WifiP2pManager.DISABLE_P2P_FAILED,
                            WifiP2pManager.BUSY);
                    break;
                case WifiP2pManager.DISCOVER_PEERS:
                    replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED,
                            WifiP2pManager.BUSY);
                    break;
                case WifiP2pManager.CONNECT:
                    replyToMessage(message, WifiP2pManager.CONNECT_FAILED,
                            WifiP2pManager.BUSY);
                    break;
                case WifiP2pManager.CANCEL_CONNECT:
                    replyToMessage(message, WifiP2pManager.CANCEL_CONNECT_FAILED,
                            WifiP2pManager.BUSY);
                    break;
                case WifiP2pManager.CREATE_GROUP:
                    replyToMessage(message, WifiP2pManager.CREATE_GROUP_FAILED,
                            WifiP2pManager.BUSY);
                    break;
                case WifiP2pManager.REMOVE_GROUP:
                    replyToMessage(message, WifiP2pManager.REMOVE_GROUP_FAILED,
                            WifiP2pManager.BUSY);
                    break;
                case WifiP2pManager.REQUEST_PEERS:
                    replyToMessage(message, WifiP2pManager.RESPONSE_PEERS, mPeers);
                    break;
                case WifiP2pManager.REQUEST_CONNECTION_INFO:
                    replyToMessage(message, WifiP2pManager.RESPONSE_CONNECTION_INFO, mWifiP2pInfo);
                    break;
                case WifiP2pManager.REQUEST_GROUP_INFO:
                    replyToMessage(message, WifiP2pManager.RESPONSE_GROUP_INFO, mGroup);
                    break;
                case AIRPLANE_MODE_CHANGED:
                    if (isAirplaneModeOn()) sendMessage(WifiP2pManager.DISABLE_P2P);
                    break;
                case EMERGENCY_CALLBACK_MODE:
                    sendMessage(WifiP2pManager.DISABLE_P2P);
                    break;
                    // Ignore
                case WIFI_DISABLE_USER_ACCEPT:
                case WIFI_DISABLE_USER_REJECT:
                case GROUP_NEGOTIATION_USER_ACCEPT:
                case GROUP_NEGOTIATION_USER_REJECT:
                case GROUP_INVITATION_USER_ACCEPT:
                case GROUP_INVITATION_USER_REJECT:
                case GROUP_NEGOTIATION_TIMED_OUT:
                    break;
                default:
                    loge("Unhandled message " + message);
                    return NOT_HANDLED;
            }
            return HANDLED;
        }
    }

    class P2pNotSupportedState extends State {
        @Override
        public boolean processMessage(Message message) {
            switch (message.what) {
                // Allow Wi-Fi to proceed
                case WifiStateMachine.WIFI_ENABLE_PENDING:
                    replyToMessage(message, WIFI_ENABLE_PROCEED);
                    break;
                case WifiP2pManager.ENABLE_P2P:
                    replyToMessage(message, WifiP2pManager.ENABLE_P2P_FAILED,
                            WifiP2pManager.P2P_UNSUPPORTED);
                    break;
                case WifiP2pManager.DISABLE_P2P:
                    replyToMessage(message, WifiP2pManager.DISABLE_P2P_FAILED,
                            WifiP2pManager.P2P_UNSUPPORTED);
                    break;
                case WifiP2pManager.DISCOVER_PEERS:
                    replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED,
                            WifiP2pManager.P2P_UNSUPPORTED);
                    break;
                case WifiP2pManager.CONNECT:
                    replyToMessage(message, WifiP2pManager.CONNECT_FAILED,
                            WifiP2pManager.P2P_UNSUPPORTED);
                    break;
                case WifiP2pManager.CANCEL_CONNECT:
                    replyToMessage(message, WifiP2pManager.CANCEL_CONNECT_FAILED,
                            WifiP2pManager.P2P_UNSUPPORTED);
                    break;
                case WifiP2pManager.CREATE_GROUP:
                    replyToMessage(message, WifiP2pManager.CREATE_GROUP_FAILED,
                            WifiP2pManager.P2P_UNSUPPORTED);
                    break;
                case WifiP2pManager.REMOVE_GROUP:
                    replyToMessage(message, WifiP2pManager.REMOVE_GROUP_FAILED,
                            WifiP2pManager.P2P_UNSUPPORTED);
                    break;
               default:
                    return NOT_HANDLED;
            }
            return HANDLED;
        }
    }

    class P2pDisablingState extends State {
        @Override
        public void enter() {
            if (DBG) logd(getName());
            logd("stopping supplicant");
            if (!WifiNative.stopSupplicant()) {
                loge("Failed to stop supplicant, issue kill");
                WifiNative.killSupplicant();
            }
        }

        @Override
        public boolean processMessage(Message message) {
            if (DBG) logd(getName() + message.toString());
            switch (message.what) {
                case WifiMonitor.SUP_DISCONNECTION_EVENT:
                    logd("Supplicant connection lost");
                    WifiNative.closeSupplicantConnection();
                    transitionTo(mP2pDisabledState);
                    break;
                case WifiP2pManager.ENABLE_P2P:
                case WifiP2pManager.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 WifiP2pManager.ENABLE_P2P:
                    OnClickListener listener = new OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            if (which == DialogInterface.BUTTON_POSITIVE) {
                                sendMessage(WIFI_DISABLE_USER_ACCEPT);
                            } else {
                                sendMessage(WIFI_DISABLE_USER_REJECT);
                            }
                        }
                    };

                    // Show a user request dialog if we know Wi-Fi client/hotspot is in operation
                    if (mWifiState != WifiManager.WIFI_STATE_DISABLED ||
                            mWifiApState != WifiManager.WIFI_AP_STATE_DISABLED) {
                        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_turnon_message))
                            .setPositiveButton(r.getString(R.string.ok), listener)
                            .setNegativeButton(r.getString(R.string.cancel), listener)
                            .create();
                        dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
                        dialog.show();
                        transitionTo(mWaitForUserActionState);
                    } else {
                        mWifiChannel.sendMessage(P2P_ENABLE_PENDING);
                        transitionTo(mWaitForWifiDisableState);
                    }
                    replyToMessage(message, WifiP2pManager.ENABLE_P2P_SUCCEEDED);
                    break;
                case WifiP2pManager.DISABLE_P2P:
                    replyToMessage(message, WifiP2pManager.DISABLE_P2P_SUCCEEDED);
                    break;
                case WifiStateMachine.WIFI_ENABLE_PENDING:
                    replyToMessage(message, WIFI_ENABLE_PROCEED);
                    break;
                default:
                    return NOT_HANDLED;
            }
            return HANDLED;
        }
    }

    class WaitForUserActionState 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 WIFI_DISABLE_USER_ACCEPT:
                    mWifiChannel.sendMessage(P2P_ENABLE_PENDING);
                    transitionTo(mWaitForWifiDisableState);
                    break;
                case WIFI_DISABLE_USER_REJECT:
                    logd("User rejected enabling p2p");
                    sendP2pStateChangedBroadcast(false);
                    transitionTo(mP2pDisabledState);
                    break;
                case WifiP2pManager.ENABLE_P2P:
                case WifiP2pManager.DISABLE_P2P:
                    deferMessage(message);
                    break;
                default:
                    return NOT_HANDLED;
            }
            return HANDLED;
        }
    }

    class WaitForWifiDisableState 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.P2P_ENABLE_PROCEED:
                    try {
                        mNwService.wifiFirmwareReload(mInterface, "P2P");
                    } catch (Exception e) {
                        loge("Failed to reload p2p firmware " + e);
                        // continue
                    }

                    //A runtime crash can leave the interface up and
                    //this affects p2p when supplicant starts up.
                    //Ensure interface is down before a supplicant start.
                    try {
                        mNwService.setInterfaceDown(mInterface);
                    } catch (Exception e) {
                        if (DBG) Slog.w(TAG, "Unable to bring down wlan interface: " + e);
                    }

                    if (WifiNative.startP2pSupplicant()) {
                        mWifiMonitor.startMonitoring();
                        transitionTo(mP2pEnablingState);
                    } else {
                        notifyP2pEnableFailure();
                        transitionTo(mP2pDisabledState);
                    }
                    break;
                case WifiP2pManager.ENABLE_P2P:
                case WifiP2pManager.DISABLE_P2P:
                    deferMessage(message);
                    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:
                    logd("P2p start successful");
                    transitionTo(mInactiveState);
                    break;
                case WifiMonitor.SUP_DISCONNECTION_EVENT:
                    if (++mP2pRestartCount <= P2P_RESTART_TRIES) {
                        loge("Failed to start p2p, retry");
                        WifiNative.killSupplicant();
                        sendMessageDelayed(WifiP2pManager.ENABLE_P2P, P2P_RESTART_INTERVAL_MSECS);
                    } else {
                        loge("Failed " + mP2pRestartCount + " times to start p2p, quit ");
                        mP2pRestartCount = 0;
                    }
                    transitionTo(mP2pDisabledState);
                    break;
                case WifiP2pManager.ENABLE_P2P:
                case WifiP2pManager.DISABLE_P2P:
                    deferMessage(message);
                    break;
                default:
                    return NOT_HANDLED;
            }
            return HANDLED;
        }
    }

    class P2pEnabledState extends State {
        @Override
        public void enter() {
            if (DBG) logd(getName());
            sendP2pStateChangedBroadcast(true);
            mNetworkInfo.setIsAvailable(true);
            initializeP2pSettings();
            showNotification();
        }

        @Override
        public boolean processMessage(Message message) {
            if (DBG) logd(getName() + message.toString());
            switch (message.what) {
                case WifiP2pManager.ENABLE_P2P:
                    replyToMessage(message, WifiP2pManager.ENABLE_P2P_SUCCEEDED);
                    break;
                case WifiP2pManager.DISABLE_P2P:
                    if (mPeers.clear()) sendP2pPeersChangedBroadcast();
                    replyToMessage(message, WifiP2pManager.DISABLE_P2P_SUCCEEDED);
                    transitionTo(mP2pDisablingState);
                    break;
                case WifiP2pManager.DISCOVER_PEERS:
                    int timeout = message.arg1;
                    if (WifiNative.p2pFind(timeout)) {
                        replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_SUCCEEDED);
                    } else {
                        replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_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.CONNECT:
                    if (DBG) logd(getName() + " sending connect");
                    mSavedConnectConfig = (WifiP2pConfig) message.obj;
                    mPersistGroup = false;
                    int netId = configuredNetworkId(mSavedConnectConfig.deviceAddress);
                    if (netId >= 0) {
                        //TODO: if failure, remove config and do a regular p2pConnect()
                        WifiNative.p2pReinvoke(netId, mSavedConnectConfig.deviceAddress);
                    } else {
                        boolean join = false;
                        if (isGroupOwner(mSavedConnectConfig.deviceAddress)) join = true;
                        String pin = WifiNative.p2pConnect(mSavedConnectConfig, join);
                        try {
                            Integer.parseInt(pin);
                            notifyWpsPin(pin, mSavedConnectConfig.deviceAddress);
                        } catch (NumberFormatException ignore) {
                            // do nothing if p2pConnect did not return a pin
                        }
                    }
                    updateDeviceStatus(mSavedConnectConfig.deviceAddress, WifiP2pDevice.INVITED);
                    sendP2pPeersChangedBroadcast();
                    replyToMessage(message, WifiP2pManager.CONNECT_SUCCEEDED);
                    transitionTo(mGroupNegotiationState);
                    break;
                case WifiMonitor.SUP_DISCONNECTION_EVENT:  /* Supplicant died */
                    loge("Connection lost, restart p2p");
                    WifiNative.killSupplicant();
                    WifiNative.closeSupplicantConnection();
                    if (mPeers.clear()) sendP2pPeersChangedBroadcast();
                    transitionTo(mP2pDisabledState);
                    sendMessageDelayed(WifiP2pManager.ENABLE_P2P, P2P_RESTART_INTERVAL_MSECS);
                    break;
                case WifiMonitor.P2P_GROUP_STARTED_EVENT:
                    mGroup = (WifiP2pGroup) message.obj;
                    if (DBG) logd(getName() + " group started");
                    if (mGroup.isGroupOwner()) {
                        startDhcpServer(mGroup.getInterface());
                    } else {
                        mDhcpStateMachine = DhcpStateMachine.makeDhcpStateMachine(mContext,
                                P2pStateMachine.this, mGroup.getInterface());
                        mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_START_DHCP);
                        WifiP2pDevice groupOwner = mGroup.getOwner();
                        updateDeviceStatus(groupOwner.deviceAddress, WifiP2pDevice.CONNECTED);
                        sendP2pPeersChangedBroadcast();
                    }
                    transitionTo(mGroupCreatedState);
                    break;
                default:
                    return NOT_HANDLED;
            }
            return HANDLED;
        }

        @Override
        public void exit() {
            sendP2pStateChangedBroadcast(false);
            mNetworkInfo.setIsAvailable(false);
            clearNotification();
        }
    }

    class InactiveState extends State {
        @Override
        public void enter() {
            if (DBG) logd(getName());
            //Start listening every time we get inactive
            WifiNative.p2pListen();
        }

        @Override
        public boolean processMessage(Message message) {
            if (DBG) logd(getName() + message.toString());
            switch (message.what) {
                case WifiMonitor.P2P_GO_NEGOTIATION_REQUEST_EVENT:
                    mSavedGoNegotiationConfig = (WifiP2pConfig) message.obj;
                    notifyP2pGoNegotationRequest(mSavedGoNegotiationConfig);
                    transitionTo(mUserAuthorizingGroupNegotiationState);
                    break;
                case WifiP2pManager.CREATE_GROUP:
                    mPersistGroup = true;
                    if (WifiNative.p2pGroupAdd()) {
                        replyToMessage(message, WifiP2pManager.CREATE_GROUP_SUCCEEDED);
                    } else {
                        replyToMessage(message, WifiP2pManager.CREATE_GROUP_FAILED,
                                WifiP2pManager.ERROR);
                    }
                    transitionTo(mGroupNegotiationState);
                    break;
                case WifiMonitor.P2P_INVITATION_RECEIVED_EVENT:
                    WifiP2pGroup group = (WifiP2pGroup) message.obj;
                    notifyP2pInvitationReceived(group);
                    transitionTo(mUserAuthorizingGroupInvitationState);
                    break;
                default:
                    return NOT_HANDLED;
            }
            return HANDLED;
        }
    }

    class UserAuthorizingGroupNegotiationState 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.P2P_GO_NEGOTIATION_REQUEST_EVENT:
                case WifiMonitor.P2P_INVITATION_RECEIVED_EVENT:
                    //Ignore additional connection requests
                    break;
                case GROUP_NEGOTIATION_USER_ACCEPT:
                    sendMessage(WifiP2pManager.CONNECT, mSavedGoNegotiationConfig);
                    mSavedGoNegotiationConfig = null;
                    break;
                case GROUP_NEGOTIATION_USER_REJECT:
                    if (DBG) logd("User rejected incoming negotiation request");
                    mSavedGoNegotiationConfig = null;
                    transitionTo(mInactiveState);
                    break;
                default:
                    return NOT_HANDLED;
            }
            return HANDLED;
        }
    }

    class UserAuthorizingGroupInvitationState 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.P2P_GO_NEGOTIATION_REQUEST_EVENT:
                case WifiMonitor.P2P_INVITATION_RECEIVED_EVENT:
                    //Ignore additional connection requests
                    break;
                case GROUP_INVITATION_USER_ACCEPT:
                    if (DBG) logd(getName() + " connect to invited group");
                    WifiP2pConfig config = new WifiP2pConfig();
                    config.deviceAddress = mSavedP2pGroup.getOwner().deviceAddress;
                    sendMessage(WifiP2pManager.CONNECT, config);
                    mSavedP2pGroup = null;
                    break;
                case GROUP_INVITATION_USER_REJECT:
                    if (DBG) logd("User rejected incoming invitation request");
                    mSavedP2pGroup = null;
                    transitionTo(mInactiveState);
                    break;
                default:
                    return NOT_HANDLED;
            }
            return HANDLED;
        }
    }


    class GroupNegotiationState extends State {
        @Override
        public void enter() {
            if (DBG) logd(getName());
            sendMessageDelayed(obtainMessage(GROUP_NEGOTIATION_TIMED_OUT,
                    ++mGroupNegotiationTimeoutIndex, 0), GROUP_NEGOTIATION_WAIT_TIME_MS);
        }

        @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_GO_NEGOTIATION_FAILURE_EVENT:
                case WifiMonitor.P2P_GROUP_FORMATION_FAILURE_EVENT:
                    if (DBG) logd(getName() + " go failure");
                    updateDeviceStatus(mSavedConnectConfig.deviceAddress, WifiP2pDevice.FAILED);
                    mSavedConnectConfig = null;
                    sendP2pPeersChangedBroadcast();
                    transitionTo(mInactiveState);
                    break;
                case GROUP_NEGOTIATION_TIMED_OUT:
                    if (mGroupNegotiationTimeoutIndex == message.arg1) {
                        if (DBG) logd("Group negotiation timed out");
                        updateDeviceStatus(mSavedConnectConfig.deviceAddress, WifiP2pDevice.FAILED);
                        mSavedConnectConfig = null;
                        sendP2pPeersChangedBroadcast();
                        transitionTo(mInactiveState);
                    }
                    break;
                case WifiP2pManager.DISCOVER_PEERS:
                    /* Discovery will break negotiation */
                    replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED,
                            WifiP2pManager.BUSY);
                    break;
                case WifiP2pManager.CANCEL_CONNECT:
                    if (WifiNative.p2pCancelConnect()) {
                        replyToMessage(message, WifiP2pManager.CANCEL_CONNECT_SUCCEEDED);
                    } else {
                        replyToMessage(message, WifiP2pManager.CANCEL_CONNECT_FAILED,
                                WifiP2pManager.ERROR);
                    }
                    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:
                    //After a GO setup, STA connected event comes with interface address
                    String interfaceAddress = (String) message.obj;
                    String deviceAddress = getDeviceAddress(interfaceAddress);
                    if (deviceAddress != null) {
                        mGroup.addClient(deviceAddress);
                        updateDeviceStatus(deviceAddress, WifiP2pDevice.CONNECTED);
                        if (DBG) logd(getName() + " ap sta connected");
                        sendP2pPeersChangedBroadcast();
                    } else {
                        loge("Connect on unknown device address : " + interfaceAddress);
                    }
                    break;
                case WifiMonitor.AP_STA_DISCONNECTED_EVENT:
                    interfaceAddress = (String) message.obj;
                    deviceAddress = getDeviceAddress(interfaceAddress);
                    if (deviceAddress != null) {
                        updateDeviceStatus(deviceAddress, WifiP2pDevice.AVAILABLE);
                        if (mGroup.removeClient(deviceAddress)) {
                            if (DBG) logd("Removed client " + deviceAddress);
                            if (!mPersistGroup && mGroup.isClientListEmpty()) {
                                Slog.d(TAG, "Client list empty, remove non-persistent p2p group");
                                WifiNative.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 address : " + interfaceAddress);
                    }
                    break;
                case DhcpStateMachine.CMD_POST_DHCP_ACTION:
                    DhcpInfoInternal dhcpInfo = (DhcpInfoInternal) message.obj;
                    if (message.arg1 == DhcpStateMachine.DHCP_SUCCESS &&
                            dhcpInfo != null) {
                        if (DBG) logd("DhcpInfo: " + dhcpInfo);
                        setWifiP2pInfoOnGroupFormation(dhcpInfo.serverAddress);
                        sendP2pConnectionChangedBroadcast();
                    } else {
                        WifiNative.p2pGroupRemove(mGroup.getInterface());
                    }
                    break;
                case WifiP2pManager.REMOVE_GROUP:
                    if (DBG) loge(getName() + " remove group");
                    if (WifiNative.p2pGroupRemove(mGroup.getInterface())) {
                        replyToMessage(message, WifiP2pManager.REMOVE_GROUP_SUCCEEDED);
                    } else {
                        replyToMessage(message, WifiP2pManager.REMOVE_GROUP_FAILED,
                                WifiP2pManager.ERROR);
                    }
                    break;
                case WifiMonitor.P2P_GROUP_REMOVED_EVENT:
                    if (DBG) loge(getName() + " group removed");
                    Collection <WifiP2pDevice> devices = mGroup.getClientList();
                    boolean changed = false;
                    for (WifiP2pDevice d : mPeers.getDeviceList()) {
                        if (devices.contains(d) || mGroup.getOwner().equals(d)) {
                            d.status = WifiP2pDevice.AVAILABLE;
                            changed = true;
                        }
                    }

                    if (mGroup.isGroupOwner()) {
                        stopDhcpServer();
                    } else {
                        if (DBG) logd("stop DHCP client");
                        mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_STOP_DHCP);
                        mDhcpStateMachine.quit();
                        mDhcpStateMachine = null;
                    }

                    mGroup = null;
                    if (changed) sendP2pPeersChangedBroadcast();
                    transitionTo(mInactiveState);
                    break;
                case WifiMonitor.P2P_DEVICE_LOST_EVENT:
                    WifiP2pDevice device = (WifiP2pDevice) message.obj;
                    if (device.equals(mGroup.getOwner())) {
                        logd("Lost the group owner, killing p2p connection");
                        WifiNative.p2pGroupRemove(mGroup.getInterface());
                    } else if (mGroup.removeClient(device)) {
                        if (!mPersistGroup && mGroup.isClientListEmpty()) {
                            Slog.d(TAG, "Client list empty, removing a non-persistent p2p group");
                            WifiNative.p2pGroupRemove(mGroup.getInterface());
                        }
                    }
                    return NOT_HANDLED; // Do the regular device lost handling
                case WifiP2pManager.DISABLE_P2P:
                    sendMessage(WifiP2pManager.REMOVE_GROUP);
                    deferMessage(message);
                    break;
                case WifiP2pManager.CONNECT:
                    WifiP2pConfig config = (WifiP2pConfig) message.obj;
                    logd("Inviting device : " + config.deviceAddress);
                    if (WifiNative.p2pInvite(mGroup, config.deviceAddress)) {
                        updateDeviceStatus(config.deviceAddress, WifiP2pDevice.INVITED);
                        sendP2pPeersChangedBroadcast();
                        replyToMessage(message, WifiP2pManager.CONNECT_SUCCEEDED);
                    } else {
                        replyToMessage(message, WifiP2pManager.CONNECT_FAILED,
                                WifiP2pManager.ERROR);
                    }
                    // TODO: figure out updating the status to declined when invitation is rejected
                    break;
                case WifiMonitor.P2P_INVITATION_RESULT_EVENT:
                    logd("===> INVITATION RESULT EVENT : " + message.obj);
                    break;
                case WifiMonitor.P2P_PROV_DISC_PBC_REQ_EVENT:
                    notifyP2pProvDiscPbcRequest((WifiP2pDevice) message.obj);
                    break;
                case WifiMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT:
                    notifyP2pProvDiscPinRequest((WifiP2pDevice) message.obj);
                    break;
                case WifiMonitor.P2P_GROUP_STARTED_EVENT:
                    Slog.e(TAG, "Duplicate group creation event notice, ignore");
                    break;
                case WPS_PBC:
                    WifiNative.wpsPbc();
                    break;
                case WPS_PIN:
                    WifiNative.wpsPin((String) message.obj);
                    break;
                default:
                    return NOT_HANDLED;
            }
            return HANDLED;
        }

        public void exit() {
            updateThisDevice(WifiP2pDevice.AVAILABLE);
            setWifiP2pInfoOnGroupTermination();
            mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, null, null);
            sendP2pConnectionChangedBroadcast();
        }
    }

    private void sendP2pStateChangedBroadcast(boolean enabled) {
        final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
        if (enabled) {
            intent.putExtra(WifiP2pManager.EXTRA_WIFI_STATE,
                    WifiP2pManager.WIFI_P2P_STATE_ENABLED);
        } else {
            intent.putExtra(WifiP2pManager.EXTRA_WIFI_STATE,
                    WifiP2pManager.WIFI_P2P_STATE_DISABLED);
        }
        mContext.sendStickyBroadcast(intent);
    }

    private void sendThisDeviceChangedBroadcast() {
        final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
        intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_DEVICE, new WifiP2pDevice(mThisDevice));
        mContext.sendStickyBroadcast(intent);
    }

    private void sendP2pPeersChangedBroadcast() {
        final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
        mContext.sendBroadcast(intent);
    }

    private void sendP2pConnectionChangedBroadcast() {
        if (DBG) logd("sending p2p connection changed broadcast");
        Intent intent = new Intent(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
                | Intent.FLAG_RECEIVER_REPLACE_PENDING);
        intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO, new WifiP2pInfo(mWifiP2pInfo));
        intent.putExtra(WifiP2pManager.EXTRA_NETWORK_INFO, new NetworkInfo(mNetworkInfo));
        mContext.sendStickyBroadcast(intent);
    }

    private void startDhcpServer(String intf) {
        InterfaceConfiguration ifcg = null;
        try {
            ifcg = mNwService.getInterfaceConfig(intf);
            ifcg.addr = new LinkAddress(NetworkUtils.numericToInetAddress(
                        SERVER_ADDRESS), 24);
            ifcg.interfaceFlags = "[up]";
            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 notifyWpsPin(String pin, String peerAddress) {
        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_pin_display_message, pin, peerAddress))
            .setPositiveButton(r.getString(R.string.ok), null)
            .create();
        dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
        dialog.show();
    }

    private void notifyP2pGoNegotationRequest(WifiP2pConfig config) {
        Resources r = Resources.getSystem();
        WpsInfo wps = config.wps;
        final View textEntryView = LayoutInflater.from(mContext)
                .inflate(R.layout.wifi_p2p_go_negotiation_request_alert, null);
        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_dialog_title))
            .setView(textEntryView)
            .setPositiveButton(r.getString(R.string.ok), new OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                            if (DBG) logd(getName() + " connect " + pin.getText());

                            if (pin.getVisibility() == View.GONE) {
                                mSavedGoNegotiationConfig.wps.setup = WpsInfo.PBC;
                            } else {
                                mSavedGoNegotiationConfig.wps.setup = WpsInfo.KEYPAD;
                                mSavedGoNegotiationConfig.wps.pin = pin.getText().toString();
                            }
                            sendMessage(GROUP_NEGOTIATION_USER_ACCEPT);
                        }
                    })
            .setNegativeButton(r.getString(R.string.cancel), new OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            if (DBG) logd(getName() + " ignore connect");
                            sendMessage(GROUP_NEGOTIATION_USER_REJECT);
                        }
                    })
            .create();

        if (wps.setup == WpsInfo.PBC) {
            pin.setVisibility(View.GONE);
            dialog.setMessage(r.getString(R.string.wifi_p2p_pbc_go_negotiation_request_message,
                        config.deviceAddress));
        } else {
            dialog.setMessage(r.getString(R.string.wifi_p2p_pin_go_negotiation_request_message,
                        config.deviceAddress));
        }

        dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
        dialog.show();
    }

    private void notifyP2pProvDiscPbcRequest(WifiP2pDevice peer) {
        Resources r = Resources.getSystem();
        final View textEntryView = LayoutInflater.from(mContext)
                .inflate(R.layout.wifi_p2p_go_negotiation_request_alert, null);
        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_dialog_title))
            .setView(textEntryView)
            .setPositiveButton(r.getString(R.string.ok), new OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                                if (DBG) logd(getName() + " wps_pbc");
                                sendMessage(WPS_PBC);
                        }
                    })
            .setNegativeButton(r.getString(R.string.cancel), null)
            .create();

        pin.setVisibility(View.GONE);
        dialog.setMessage(r.getString(R.string.wifi_p2p_pbc_go_negotiation_request_message,
                        peer.deviceAddress));

        dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
        dialog.show();
    }

    private void notifyP2pProvDiscPinRequest(WifiP2pDevice peer) {
        Resources r = Resources.getSystem();
        final View textEntryView = LayoutInflater.from(mContext)
                .inflate(R.layout.wifi_p2p_go_negotiation_request_alert, null);
        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_dialog_title))
            .setView(textEntryView)
            .setPositiveButton(r.getString(R.string.ok), new OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        if (DBG) logd(getName() + " wps_pin");
                        sendMessage(WPS_PIN, pin.getText().toString());
                    }
                    })
            .setNegativeButton(r.getString(R.string.cancel), null)
            .create();

        dialog.setMessage(r.getString(R.string.wifi_p2p_pin_go_negotiation_request_message,
                        peer.deviceAddress));

        dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
        dialog.show();
    }

    private void notifyP2pInvitationReceived(WifiP2pGroup group) {
        mSavedP2pGroup = group;
        Resources r = Resources.getSystem();
        final View textEntryView = LayoutInflater.from(mContext)
                .inflate(R.layout.wifi_p2p_go_negotiation_request_alert, null);
        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_dialog_title))
            .setView(textEntryView)
            .setPositiveButton(r.getString(R.string.ok), new OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                            sendMessage(GROUP_INVITATION_USER_ACCEPT);
                        }
                    })
            .setNegativeButton(r.getString(R.string.cancel), new OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            if (DBG) logd(getName() + " ignore invite");
                            sendMessage(GROUP_INVITATION_USER_REJECT);
                        }
                    })
            .create();

        pin.setVisibility(View.GONE);
        dialog.setMessage(r.getString(R.string.wifi_p2p_pbc_go_negotiation_request_message,
                        group.getOwner().deviceAddress));

        dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
        dialog.show();
    }

    private void updateDeviceStatus(String deviceAddress, int status) {
        for (WifiP2pDevice d : mPeers.getDeviceList()) {
            if (d.deviceAddress.equals(deviceAddress)) {
                d.status = status;
            }
        }
    }

    private boolean isGroupOwner(String deviceAddress) {
        for (WifiP2pDevice d : mPeers.getDeviceList()) {
            if (d.deviceAddress.equals(deviceAddress)) {
                return d.isGroupOwner();
            }
        }
        return false;
    }

    //TODO: implement when wpa_supplicant is fixed
    private int configuredNetworkId(String deviceAddress) {
        return -1;
    }

    private void setWifiP2pInfoOnGroupFormation(String serverAddress) {
        mWifiP2pInfo.groupFormed = true;
        mWifiP2pInfo.isGroupOwner = mGroup.isGroupOwner();
        mWifiP2pInfo.groupOwnerAddress = NetworkUtils.numericToInetAddress(serverAddress);
    }

    private void setWifiP2pInfoOnGroupTermination() {
        mWifiP2pInfo.groupFormed = false;
        mWifiP2pInfo.isGroupOwner = false;
        mWifiP2pInfo.groupOwnerAddress = null;
    }

    private String getDeviceAddress(String interfaceAddress) {
        for (WifiP2pDevice d : mPeers.getDeviceList()) {
            if (interfaceAddress.equals(WifiNative.p2pGetInterfaceAddress(d.deviceAddress))) {
                return d.deviceAddress;
            }
        }
        return null;
    }

    private void initializeP2pSettings() {
        WifiNative.setPersistentReconnect(true);
        WifiNative.setDeviceName(mThisDevice.deviceName);
        WifiNative.setDeviceType(mThisDevice.primaryDeviceType);

        mThisDevice.deviceAddress = WifiNative.p2pGetDeviceAddress();
        updateThisDevice(WifiP2pDevice.AVAILABLE);
        if (DBG) Slog.d(TAG, "DeviceAddress: " + mThisDevice.deviceAddress);
    }

    private void updateThisDevice(int status) {
        mThisDevice.status = status;
        sendThisDeviceChangedBroadcast();
    }

    //State machine initiated requests can have replyTo set to null indicating
    //there are no recepients, we ignore those reply actions
    private void replyToMessage(Message msg, int what) {
        if (msg.replyTo == null) return;
        Message dstMsg = obtainMessage(msg);
        dstMsg.what = what;
        mReplyChannel.replyToMessage(msg, dstMsg);
    }

    private void replyToMessage(Message msg, int what, int arg1) {
        if (msg.replyTo == null) return;
        Message dstMsg = obtainMessage(msg);
        dstMsg.what = what;
        dstMsg.arg1 = arg1;
        mReplyChannel.replyToMessage(msg, dstMsg);
    }

    private void replyToMessage(Message msg, int what, Object obj) {
        if (msg.replyTo == null) return;
        Message dstMsg = obtainMessage(msg);
        dstMsg.what = what;
        dstMsg.obj = obj;
        mReplyChannel.replyToMessage(msg, dstMsg);
    }

    /* arg2 on the source message has a hash code that needs to be retained in replies
     * see WifiP2pManager for details */
    private Message obtainMessage(Message srcMsg) {
        Message msg = Message.obtain();
        msg.arg2 = srcMsg.arg2;
        return msg;
    }

    private void logd(String s) {
        Slog.d(TAG, s);
    }

    private void loge(String s) {
        Slog.e(TAG, s);
    }

    private void showNotification() {
        NotificationManager notificationManager =
            (NotificationManager)mContext.getSystemService(Context.NOTIFICATION_SERVICE);
        if (notificationManager == null || mNotification != null) {
            return;
        }

        Intent intent = new Intent(android.provider.Settings.ACTION_WIRELESS_SETTINGS);
        intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);

        PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);

        Resources r = Resources.getSystem();
        CharSequence title = r.getText(R.string.wifi_p2p_enabled_notification_title);
        CharSequence message = r.getText(R.string.wifi_p2p_enabled_notification_message);

        mNotification = new Notification();
        mNotification.when = 0;
        //TODO: might change to be a seperate icon
        mNotification.icon = R.drawable.stat_sys_tether_wifi;
        mNotification.defaults &= ~Notification.DEFAULT_SOUND;
        mNotification.flags = Notification.FLAG_ONGOING_EVENT;
        mNotification.tickerText = title;
        mNotification.setLatestEventInfo(mContext, title, message, pi);

        notificationManager.notify(mNotification.icon, mNotification);
    }

    private void clearNotification() {
        NotificationManager notificationManager =
            (NotificationManager)mContext.getSystemService(Context.NOTIFICATION_SERVICE);
        if (notificationManager != null && mNotification != null) {
            notificationManager.cancel(mNotification.icon);
            mNotification = null;
        }
    }

    private boolean isAirplaneSensitive() {
        String airplaneModeRadios = Settings.System.getString(mContext.getContentResolver(),
                Settings.System.AIRPLANE_MODE_RADIOS);
        return airplaneModeRadios == null
            || airplaneModeRadios.contains(Settings.System.RADIO_WIFI);
    }

    private boolean isAirplaneModeOn() {
        return isAirplaneSensitive() && Settings.System.getInt(mContext.getContentResolver(),
                Settings.System.AIRPLANE_MODE_ON, 0) == 1;
    }

    }
}
