/*
 * 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 com.android.server.wifi.p2p;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.hardware.wifi.V1_0.IWifiP2pIface;
import android.net.ConnectivityManager;
import android.net.DhcpResults;
import android.net.InterfaceConfiguration;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkInfo;
import android.net.NetworkUtils;
import android.net.ip.IpManager;
import android.net.wifi.WpsInfo;
import android.net.wifi.p2p.IWifiP2pManager;
import android.net.wifi.p2p.WifiP2pConfig;
import android.net.wifi.p2p.WifiP2pDevice;
import android.net.wifi.p2p.WifiP2pDeviceList;
import android.net.wifi.p2p.WifiP2pGroup;
import android.net.wifi.p2p.WifiP2pGroupList;
import android.net.wifi.p2p.WifiP2pGroupList.GroupDeleteListener;
import android.net.wifi.p2p.WifiP2pInfo;
import android.net.wifi.p2p.WifiP2pManager;
import android.net.wifi.p2p.WifiP2pProvDiscEvent;
import android.net.wifi.p2p.WifiP2pWfdInfo;
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.Build;
import android.os.Bundle;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.INetworkManagementService;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
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.util.AsyncChannel;
import com.android.internal.util.Protocol;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import com.android.server.wifi.HalDeviceManager;
import com.android.server.wifi.WifiInjector;
import com.android.server.wifi.WifiStateMachine;
import com.android.server.wifi.util.WifiAsyncChannel;
import com.android.server.wifi.util.WifiHandler;
import com.android.server.wifi.util.WifiPermissionsUtil;
import com.android.server.wifi.util.WifiPermissionsWrapper;

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;
import java.util.Map;

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

    INetworkManagementService mNwService;
    private IpManager mIpManager;
    private DhcpResults mDhcpResults;

    private P2pStateMachine mP2pStateMachine;
    private AsyncChannel mReplyChannel = new WifiAsyncChannel(TAG);
    private AsyncChannel mWifiChannel;
    private WifiInjector mWifiInjector;

    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 sGroupCreatingTimeoutIndex = 0;

    private static final int DISABLE_P2P_WAIT_TIME_MS = 5 * 1000;
    private static int sDisableP2pTimeoutIndex = 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;

    // Messages for interaction with IpManager.
    private static final int IPM_PRE_DHCP_ACTION            =   BASE + 30;
    private static final int IPM_POST_DHCP_ACTION           =   BASE + 31;
    private static final int IPM_DHCP_RESULTS               =   BASE + 32;
    private static final int IPM_PROVISIONING_SUCCESS       =   BASE + 33;
    private static final int IPM_PROVISIONING_FAILURE       =   BASE + 34;

    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 mTemporarilyDisconnectedWifi = 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 address to avoid conflict with
    // the ranges defined in Tethering.java
    private static final String SERVER_ADDRESS = "192.168.49.1";

    // The empty device address set by wpa_supplicant.
    private static final String EMPTY_DEVICE_ADDRESS = "00:00:00:00:00:00";

    /**
     * Error code definition.
     * see the Table.8 in the WiFi Direct specification for the detail.
     */
    public 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;

        /**
         * Returns P2p status corresponding to a given error value
         * @param error integer error value
         * @return P2pStatus enum for value
         */
        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;
            }
        }
    }

    /**
     * Handles client connections
     */
    private class ClientHandler extends WifiHandler {

        ClientHandler(String tag, android.os.Looper looper) {
            super(tag, looper);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case WifiP2pManager.SET_DEVICE_NAME:
                case WifiP2pManager.SET_WFD_INFO:
                case WifiP2pManager.DISCOVER_PEERS:
                case WifiP2pManager.STOP_DISCOVERY:
                case WifiP2pManager.CONNECT:
                case WifiP2pManager.CANCEL_CONNECT:
                case WifiP2pManager.CREATE_GROUP:
                case WifiP2pManager.REMOVE_GROUP:
                case WifiP2pManager.START_LISTEN:
                case WifiP2pManager.STOP_LISTEN:
                case WifiP2pManager.SET_CHANNEL:
                case WifiP2pManager.START_WPS:
                case WifiP2pManager.ADD_LOCAL_SERVICE:
                case WifiP2pManager.REMOVE_LOCAL_SERVICE:
                case WifiP2pManager.CLEAR_LOCAL_SERVICES:
                case WifiP2pManager.DISCOVER_SERVICES:
                case WifiP2pManager.ADD_SERVICE_REQUEST:
                case WifiP2pManager.REMOVE_SERVICE_REQUEST:
                case WifiP2pManager.CLEAR_SERVICE_REQUESTS:
                case WifiP2pManager.REQUEST_PEERS:
                case WifiP2pManager.REQUEST_CONNECTION_INFO:
                case WifiP2pManager.REQUEST_GROUP_INFO:
                case WifiP2pManager.DELETE_PERSISTENT_GROUP:
                case WifiP2pManager.REQUEST_PERSISTENT_GROUP_INFO:
                    mP2pStateMachine.sendMessage(Message.obtain(msg));
                    break;
                default:
                    Slog.d(TAG, "ClientHandler.handleMessage ignoring msg=" + msg);
                    break;
            }
        }
    }
    private ClientHandler mClientHandler;

    private class DeathHandlerData {
        DeathHandlerData(DeathRecipient dr, Messenger m) {
            mDeathRecipient = dr;
            mMessenger = m;
        }

        @Override
        public String toString() {
            return "deathRecipient=" + mDeathRecipient + ", messenger=" + mMessenger;
        }

        DeathRecipient mDeathRecipient;
        Messenger mMessenger;
    }
    private Object mLock = new Object();
    private final Map<IBinder, DeathHandlerData> mDeathDataByBinder = new HashMap<>();
    private HalDeviceManager mHalDeviceManager;
    private IWifiP2pIface mIWifiP2pIface;

    public WifiP2pServiceImpl(Context context) {
        mContext = context;

        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);

        HandlerThread wifiP2pThread = new HandlerThread("WifiP2pService");
        wifiP2pThread.start();
        mClientHandler = new ClientHandler(TAG, wifiP2pThread.getLooper());
        mP2pStateMachine = new P2pStateMachine(TAG, wifiP2pThread.getLooper(), mP2pSupported);
        mP2pStateMachine.start();
    }

    /**
     * Obtains the service interface for Managements services
     */
    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");
    }

    private int checkConnectivityInternalPermission() {
        return mContext.checkCallingOrSelfPermission(
                android.Manifest.permission.CONNECTIVITY_INTERNAL);
    }

    private int checkLocationHardwarePermission() {
        return mContext.checkCallingOrSelfPermission(
                android.Manifest.permission.LOCATION_HARDWARE);
    }

    private void enforceConnectivityInternalOrLocationHardwarePermission() {
        if (checkConnectivityInternalPermission() != PackageManager.PERMISSION_GRANTED
                && checkLocationHardwarePermission() != PackageManager.PERMISSION_GRANTED) {
            enforceConnectivityInternalPermission();
        }
    }

    private void stopIpManager() {
        if (mIpManager != null) {
            mIpManager.stop();
            mIpManager = null;
        }
        mDhcpResults = null;
    }

    private void startIpManager(String ifname) {
        stopIpManager();

        mIpManager = new IpManager(mContext, ifname,
                new IpManager.Callback() {
                    @Override
                    public void onPreDhcpAction() {
                        mP2pStateMachine.sendMessage(IPM_PRE_DHCP_ACTION);
                    }
                    @Override
                    public void onPostDhcpAction() {
                        mP2pStateMachine.sendMessage(IPM_POST_DHCP_ACTION);
                    }
                    @Override
                    public void onNewDhcpResults(DhcpResults dhcpResults) {
                        mP2pStateMachine.sendMessage(IPM_DHCP_RESULTS, dhcpResults);
                    }
                    @Override
                    public void onProvisioningSuccess(LinkProperties newLp) {
                        mP2pStateMachine.sendMessage(IPM_PROVISIONING_SUCCESS);
                    }
                    @Override
                    public void onProvisioningFailure(LinkProperties newLp) {
                        mP2pStateMachine.sendMessage(IPM_PROVISIONING_FAILURE);
                    }
                },
                mNwService);

        final IpManager.ProvisioningConfiguration config =
                mIpManager.buildProvisioningConfiguration()
                          .withoutIPv6()
                          .withoutIpReachabilityMonitor()
                          .withPreDhcpAction(30 * 1000)
                          .withProvisioningTimeoutMs(36 * 1000)
                          .build();
        mIpManager.startProvisioning(config);
    }

    /**
     * Get a reference to handler. This is used by a client to establish
     * an AsyncChannel communication with WifiP2pService
     */
    @Override
    public Messenger getMessenger(final IBinder binder) {
        enforceAccessPermission();
        enforceChangePermission();

        synchronized (mLock) {
            final Messenger messenger = new Messenger(mClientHandler);
            if (DBG) {
                Log.d(TAG, "getMessenger: uid=" + getCallingUid() + ", binder=" + binder
                        + ", messenger=" + messenger);
            }

            IBinder.DeathRecipient dr = () -> {
                if (DBG) Log.d(TAG, "binderDied: binder=" + binder);
                close(binder);
            };

            try {
                binder.linkToDeath(dr, 0);
                mDeathDataByBinder.put(binder, new DeathHandlerData(dr, messenger));
            } catch (RemoteException e) {
                Log.e(TAG, "Error on linkToDeath: e=" + e);
                // fall-through here - won't clean up
            }

            if (mIWifiP2pIface == null) {
                if (mHalDeviceManager == null) {
                    if (mWifiInjector == null) {
                        mWifiInjector = WifiInjector.getInstance();
                    }
                    mHalDeviceManager = mWifiInjector.getHalDeviceManager();
                }
                mIWifiP2pIface = mHalDeviceManager.createP2pIface(() -> {
                    if (DBG) Log.d(TAG, "IWifiP2pIface destroyedListener");
                    synchronized (mLock) {
                        mIWifiP2pIface = null;
                    }
                }, mP2pStateMachine.getHandler().getLooper());
            }

            return messenger;
        }
    }

    /**
     * Get a reference to handler. This is used by a WifiStateMachine to establish
     * an AsyncChannel communication with P2pStateMachine
     * @hide
     */
    @Override
    public Messenger getP2pStateMachineMessenger() {
        enforceConnectivityInternalOrLocationHardwarePermission();
        enforceAccessPermission();
        enforceChangePermission();
        return new Messenger(mP2pStateMachine.getHandler());
    }

    /**
     * Clean-up the state and configuration requested by the closing app. Takes same action as
     * when the app dies (binder death).
     */
    @Override
    public void close(IBinder binder) {
        enforceAccessPermission();
        enforceChangePermission();

        DeathHandlerData dhd;
        synchronized (mLock) {
            dhd = mDeathDataByBinder.get(binder);
            if (dhd == null) {
                Log.w(TAG, "close(): no death recipient for binder");
                return;
            }

            binder.unlinkToDeath(dhd.mDeathRecipient, 0);
            mDeathDataByBinder.remove(binder);

            // clean-up if there are no more clients registered
            // TODO: what does the WifiStateMachine client do? It isn't tracked through here!
            if (dhd.mMessenger != null && mDeathDataByBinder.isEmpty()) {
                try {
                    dhd.mMessenger.send(
                            mClientHandler.obtainMessage(WifiP2pManager.STOP_DISCOVERY));
                    dhd.mMessenger.send(mClientHandler.obtainMessage(WifiP2pManager.REMOVE_GROUP));
                } catch (RemoteException e) {
                    Log.e(TAG, "close: Failed sending clean-up commands: e=" + e);
                }

                if (mIWifiP2pIface != null) {
                    mHalDeviceManager.removeIface(mIWifiP2pIface);
                    mIWifiP2pIface = null;
                }
            }
        }
    }

    /** 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.
     * @param int mode of operation
     */
    @Override
    public void setMiracastMode(int mode) {
        enforceConnectivityInternalPermission();
        checkConfigureWifiDisplayPermission();
        mP2pStateMachine.sendMessage(SET_MIRACAST_MODE, mode);
    }

    @Override
    public void checkConfigureWifiDisplayPermission() {
        if (!getWfdPermission(Binder.getCallingUid())) {
            throw new SecurityException("Wifi Display Permission denied for uid = "
                    + Binder.getCallingUid());
        }
    }

    private boolean getWfdPermission(int uid) {
        if (mWifiInjector == null) {
            mWifiInjector = WifiInjector.getInstance();
        }
        WifiPermissionsWrapper wifiPermissionsWrapper = mWifiInjector.getWifiPermissionsWrapper();
        return wifiPermissionsWrapper.getUidPermission(
                android.Manifest.permission.CONFIGURE_WIFI_DISPLAY, uid)
                != PackageManager.PERMISSION_DENIED;
    }

    @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("mTemporarilyDisconnectedWifi " + mTemporarilyDisconnectedWifi);
        pw.println("mServiceDiscReqId " + mServiceDiscReqId);
        pw.println("mDeathDataByBinder " + mDeathDataByBinder);
        pw.println();

        final IpManager ipManager = mIpManager;
        if (ipManager != null) {
            pw.println("mIpManager:");
            ipManager.dump(fd, pw, args);
        }
    }


    /**
     * 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 WifiP2pNative mWifiNative = WifiInjector.getInstance().getWifiP2pNative();
        private WifiP2pMonitor mWifiMonitor = WifiInjector.getInstance().getWifiP2pMonitor();
        private final WifiP2pDeviceList mPeers = new WifiP2pDeviceList();
        // WifiInjector is lazy initialized in P2p Service
        private WifiInjector mWifiInjector;
        // 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.removeP2pNetwork(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();

        P2pStateMachine(String name, Looper looper, boolean p2pSupported) {
            super(name, looper);

            // CHECKSTYLE:OFF IndentationCheck
            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);
            // CHECKSTYLE:ON IndentationCheck

            if (p2pSupported) {
                setInitialState(mP2pDisabledState);
            } else {
                setInitialState(mP2pNotSupportedState);
            }
            setLogRecSize(50);
            setLogOnlyTransitions(true);
            String interfaceName = mWifiNative.getInterfaceName();
            mWifiMonitor.registerHandler(interfaceName,
                    WifiP2pMonitor.AP_STA_CONNECTED_EVENT, getHandler());
            mWifiMonitor.registerHandler(interfaceName,
                    WifiP2pMonitor.AP_STA_DISCONNECTED_EVENT, getHandler());
            mWifiMonitor.registerHandler(interfaceName,
                    WifiP2pMonitor.P2P_DEVICE_FOUND_EVENT, getHandler());
            mWifiMonitor.registerHandler(interfaceName,
                    WifiP2pMonitor.P2P_DEVICE_LOST_EVENT, getHandler());
            mWifiMonitor.registerHandler(interfaceName,
                    WifiP2pMonitor.P2P_FIND_STOPPED_EVENT, getHandler());
            mWifiMonitor.registerHandler(interfaceName,
                    WifiP2pMonitor.P2P_GO_NEGOTIATION_FAILURE_EVENT, getHandler());
            mWifiMonitor.registerHandler(interfaceName,
                    WifiP2pMonitor.P2P_GO_NEGOTIATION_REQUEST_EVENT, getHandler());
            mWifiMonitor.registerHandler(interfaceName,
                    WifiP2pMonitor.P2P_GO_NEGOTIATION_SUCCESS_EVENT, getHandler());
            mWifiMonitor.registerHandler(interfaceName,
                    WifiP2pMonitor.P2P_GROUP_FORMATION_FAILURE_EVENT, getHandler());
            mWifiMonitor.registerHandler(interfaceName,
                    WifiP2pMonitor.P2P_GROUP_FORMATION_SUCCESS_EVENT, getHandler());
            mWifiMonitor.registerHandler(interfaceName,
                    WifiP2pMonitor.P2P_GROUP_REMOVED_EVENT, getHandler());
            mWifiMonitor.registerHandler(interfaceName,
                    WifiP2pMonitor.P2P_GROUP_STARTED_EVENT, getHandler());
            mWifiMonitor.registerHandler(interfaceName,
                    WifiP2pMonitor.P2P_INVITATION_RECEIVED_EVENT, getHandler());
            mWifiMonitor.registerHandler(interfaceName,
                    WifiP2pMonitor.P2P_INVITATION_RESULT_EVENT, getHandler());
            mWifiMonitor.registerHandler(interfaceName,
                    WifiP2pMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT, getHandler());
            mWifiMonitor.registerHandler(interfaceName,
                    WifiP2pMonitor.P2P_PROV_DISC_FAILURE_EVENT, getHandler());
            mWifiMonitor.registerHandler(interfaceName,
                    WifiP2pMonitor.P2P_PROV_DISC_PBC_REQ_EVENT, getHandler());
            mWifiMonitor.registerHandler(interfaceName,
                    WifiP2pMonitor.P2P_PROV_DISC_PBC_RSP_EVENT, getHandler());
            mWifiMonitor.registerHandler(interfaceName,
                    WifiP2pMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT, getHandler());
            mWifiMonitor.registerHandler(interfaceName,
                    WifiP2pMonitor.P2P_SERV_DISC_RESP_EVENT, getHandler());
            mWifiMonitor.registerHandler(interfaceName,
                    WifiP2pMonitor.SUP_CONNECTION_EVENT, getHandler());
            mWifiMonitor.registerHandler(interfaceName,
                    WifiP2pMonitor.SUP_DISCONNECTION_EVENT, getHandler());
        }

        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;
                            transitionTo(mP2pDisabledState);
                        }
                        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;
                        transitionTo(mP2pDisabledState);
                        break;

                    case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION:
                        AsyncChannel ac = new WifiAsyncChannel(TAG);
                        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) {
                            if (message.obj == null) {
                                Log.e(TAG, "Illegal argument(s)");
                                break;
                            }
                            StateMachine m = (StateMachine) message.obj;
                            try {
                                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:
                        if (!getWfdPermission(message.sendingUid)) {
                            replyToMessage(message, WifiP2pManager.SET_WFD_INFO_FAILED,
                                    WifiP2pManager.ERROR);
                        } else {
                            replyToMessage(message, WifiP2pManager.SET_WFD_INFO_FAILED,
                                    WifiP2pManager.BUSY);
                        }
                        break;
                    case WifiP2pManager.REQUEST_PEERS:
                        replyToMessage(message, WifiP2pManager.RESPONSE_PEERS,
                                getPeers((Bundle) message.obj, message.sendingUid));
                        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;
                    case WifiP2pManager.GET_HANDOVER_REQUEST:
                    case WifiP2pManager.GET_HANDOVER_SELECT:
                        replyToMessage(message, WifiP2pManager.RESPONSE_GET_HANDOVER_MESSAGE, null);
                        break;
                    case WifiP2pManager.INITIATOR_REPORT_NFC_HANDOVER:
                    case WifiP2pManager.RESPONDER_REPORT_NFC_HANDOVER:
                        replyToMessage(message, WifiP2pManager.REPORT_NFC_HANDOVER_FAILED,
                                WifiP2pManager.BUSY);
                        break;
                    case WifiP2pMonitor.P2P_INVITATION_RESULT_EVENT:
                    case WifiP2pMonitor.SUP_CONNECTION_EVENT:
                    case WifiP2pMonitor.SUP_DISCONNECTION_EVENT:
                    case WifiP2pMonitor.P2P_GROUP_REMOVED_EVENT:
                    case WifiP2pMonitor.P2P_DEVICE_FOUND_EVENT:
                    case WifiP2pMonitor.P2P_DEVICE_LOST_EVENT:
                    case WifiP2pMonitor.P2P_FIND_STOPPED_EVENT:
                    case WifiP2pMonitor.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 IPM_PRE_DHCP_ACTION:
                    case IPM_POST_DHCP_ACTION:
                    case IPM_DHCP_RESULTS:
                    case IPM_PROVISIONING_SUCCESS:
                    case IPM_PROVISIONING_FAILURE:
                    case WifiP2pMonitor.P2P_PROV_DISC_FAILURE_EVENT:
                    case SET_MIRACAST_MODE:
                    case WifiP2pManager.START_LISTEN:
                    case WifiP2pManager.STOP_LISTEN:
                    case WifiP2pManager.SET_CHANNEL:
                    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
                        if (mWifiChannel !=  null) {
                            mWifiChannel.sendMessage(WifiStateMachine.CMD_DISABLE_P2P_RSP);
                        } else {
                            loge("Unexpected disable request when WifiChannel is null");
                        }
                        break;
                    case WifiP2pMonitor.P2P_GROUP_STARTED_EVENT:
                        // unexpected group created, remove
                        if (message.obj == null) {
                            Log.e(TAG, "Illegal arguments");
                            break;
                        }
                        mGroup = (WifiP2pGroup) message.obj;
                        loge("Unexpected group creation, remove " + mGroup);
                        mWifiNative.p2pGroupRemove(mGroup.getInterface());
                        break;
                    case WifiP2pMonitor.P2P_GROUP_FORMATION_FAILURE_EVENT:
                        // A group formation failure is always followed by
                        // a group removed event. Flushing things at group formation
                        // failure causes supplicant issues. Ignore right now.
                        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:
                        if (!getWfdPermission(message.sendingUid)) {
                            replyToMessage(message, WifiP2pManager.SET_WFD_INFO_FAILED,
                                    WifiP2pManager.ERROR);
                        } else {
                            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,
                        ++sDisableP2pTimeoutIndex, 0), DISABLE_P2P_WAIT_TIME_MS);
            }

            @Override
            public boolean processMessage(Message message) {
                if (DBG) logd(getName() + message.toString());
                switch (message.what) {
                    case WifiP2pMonitor.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 (sDisableP2pTimeoutIndex == message.arg1) {
                            loge("P2p disable timed out");
                            transitionTo(mP2pDisabledState);
                        }
                        break;
                    default:
                        return NOT_HANDLED;
                }
                return HANDLED;
            }

            @Override
            public void exit() {
                if (mWifiChannel != null) {
                    mWifiChannel.sendMessage(WifiStateMachine.CMD_DISABLE_P2P_RSP);
                } else {
                    loge("P2pDisablingState exit(): WifiChannel is null");
                }
            }
        }

        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(mWifiNative.getInterfaceName());
                        } catch (RemoteException re) {
                            loge("Unable to change interface settings: " + re);
                        } catch (IllegalStateException ie) {
                            loge("Unable to change interface settings: " + ie);
                        }
                        mWifiMonitor.startMonitoring(mWifiNative.getInterfaceName());
                        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 WifiP2pMonitor.SUP_CONNECTION_EVENT:
                        if (DBG) logd("P2p socket connection successful");
                        transitionTo(mInactiveState);
                        break;
                    case WifiP2pMonitor.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 WifiP2pMonitor.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(mWifiNative.getInterfaceName());
                        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 (!getWfdPermission(message.sendingUid)) {
                            replyToMessage(message, WifiP2pManager.SET_WFD_INFO_FAILED,
                                    WifiP2pManager.ERROR);
                        } else 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) {
                            if (message.obj == null) {
                                Log.e(TAG, "Illegal argument(s)");
                                break;
                            }
                            StateMachine m = (StateMachine) message.obj;
                            try {
                                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 WifiP2pMonitor.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 WifiP2pMonitor.P2P_DEVICE_FOUND_EVENT:
                        if (message.obj == null) {
                            Log.e(TAG, "Illegal argument(s)");
                            break;
                        }
                        WifiP2pDevice device = (WifiP2pDevice) message.obj;
                        if (mThisDevice.deviceAddress.equals(device.deviceAddress)) break;
                        mPeers.updateSupplicantDetails(device);
                        sendPeersChangedBroadcast();
                        break;
                    case WifiP2pMonitor.P2P_DEVICE_LOST_EVENT:
                        if (message.obj == null) {
                            Log.e(TAG, "Illegal argument(s)");
                            break;
                        }
                        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 WifiP2pMonitor.P2P_SERV_DISC_RESP_EVENT:
                        if (DBG) logd(getName() + " receive service response");
                        if (message.obj == null) {
                            Log.e(TAG, "Illegal argument(s)");
                            break;
                        }
                        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 WifiP2pManager.GET_HANDOVER_REQUEST:
                        Bundle requestBundle = new Bundle();
                        requestBundle.putString(WifiP2pManager.EXTRA_HANDOVER_MESSAGE,
                                mWifiNative.getNfcHandoverRequest());
                        replyToMessage(message, WifiP2pManager.RESPONSE_GET_HANDOVER_MESSAGE,
                                requestBundle);
                        break;
                    case WifiP2pManager.GET_HANDOVER_SELECT:
                        Bundle selectBundle = new Bundle();
                        selectBundle.putString(WifiP2pManager.EXTRA_HANDOVER_MESSAGE,
                                mWifiNative.getNfcHandoverSelect());
                        replyToMessage(message, WifiP2pManager.RESPONSE_GET_HANDOVER_MESSAGE,
                                selectBundle);
                        break;
                    default:
                        return NOT_HANDLED;
                }
                return HANDLED;
            }

            @Override
            public void exit() {
                sendP2pDiscoveryChangedBroadcast(false);
                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 WifiP2pMonitor.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 WifiP2pMonitor.P2P_INVITATION_RECEIVED_EVENT:
                        if (message.obj == null) {
                            Log.e(TAG, "Invalid argument(s)");
                            break;
                        }
                        WifiP2pGroup group = (WifiP2pGroup) message.obj;
                        WifiP2pDevice owner = group.getOwner();
                        if (owner == null) {
                            int id = group.getNetworkId();
                            if (id < 0) {
                                loge("Ignored invitation from null owner");
                                break;
                            }

                            String addr = mGroups.getOwnerAddr(id);
                            if (addr != null) {
                                group.setOwner(new WifiP2pDevice(addr));
                                owner = group.getOwner();
                            } else {
                                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 != null && ((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 WifiP2pMonitor.P2P_PROV_DISC_PBC_REQ_EVENT:
                    case WifiP2pMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT:
                    case WifiP2pMonitor.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 WifiP2pMonitor.P2P_GROUP_STARTED_EVENT:
                        if (message.obj == null) {
                            Log.e(TAG, "Invalid argument(s)");
                            break;
                        }
                        mGroup = (WifiP2pGroup) message.obj;
                        if (DBG) logd(getName() + " group started");
                        if (mGroup.isGroupOwner()
                                && EMPTY_DEVICE_ADDRESS.equals(mGroup.getOwner().deviceAddress)) {
                            // wpa_supplicant doesn't set own device address to go_dev_addr.
                            mGroup.getOwner().deviceAddress = mThisDevice.deviceAddress;
                        }
                        // 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:
                        if (message.obj == null) {
                            Log.e(TAG, "Illegal arguments(s)");
                            break;
                        }
                        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 WifiP2pManager.INITIATOR_REPORT_NFC_HANDOVER:
                        String handoverSelect = null;

                        if (message.obj != null) {
                            handoverSelect = ((Bundle) message.obj)
                                    .getString(WifiP2pManager.EXTRA_HANDOVER_MESSAGE);
                        }

                        if (handoverSelect != null
                                && mWifiNative.initiatorReportNfcHandover(handoverSelect)) {
                            replyToMessage(message, WifiP2pManager.REPORT_NFC_HANDOVER_SUCCEEDED);
                            transitionTo(mGroupCreatingState);
                        } else {
                            replyToMessage(message, WifiP2pManager.REPORT_NFC_HANDOVER_FAILED);
                        }
                        break;
                    case WifiP2pManager.RESPONDER_REPORT_NFC_HANDOVER:
                        String handoverRequest = null;

                        if (message.obj != null) {
                            handoverRequest = ((Bundle) message.obj)
                                    .getString(WifiP2pManager.EXTRA_HANDOVER_MESSAGE);
                        }

                        if (handoverRequest != null
                                && mWifiNative.responderReportNfcHandover(handoverRequest)) {
                            replyToMessage(message, WifiP2pManager.REPORT_NFC_HANDOVER_SUCCEEDED);
                            transitionTo(mGroupCreatingState);
                        } else {
                            replyToMessage(message, WifiP2pManager.REPORT_NFC_HANDOVER_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,
                        ++sGroupCreatingTimeoutIndex, 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 (sGroupCreatingTimeoutIndex == message.arg1) {
                            if (DBG) logd("Group negotiation timed out");
                            handleGroupCreationFailure();
                            transitionTo(mInactiveState);
                        }
                        break;
                    case WifiP2pMonitor.P2P_DEVICE_LOST_EVENT:
                        if (message.obj == null) {
                            Log.e(TAG, "Illegal argument(s)");
                            break;
                        }
                        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;
                    case WifiP2pMonitor.P2P_GO_NEGOTIATION_SUCCESS_EVENT:
                        // We hit this scenario when NFC handover is invoked.
                        mAutonomousGroup = false;
                        transitionTo(mGroupNegotiationState);
                        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 = null;
                WifiP2pDevice device = null;
                switch (message.what) {
                    case WifiP2pMonitor.P2P_PROV_DISC_PBC_RSP_EVENT:
                        if (message.obj == null) {
                            Log.e(TAG, "Invalid argument(s)");
                            break;
                        }
                        provDisc = (WifiP2pProvDiscEvent) message.obj;
                        device = provDisc.device;
                        if (device != null
                                && !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 WifiP2pMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT:
                        if (message.obj == null) {
                            Log.e(TAG, "Illegal argument(s)");
                            break;
                        }
                        provDisc = (WifiP2pProvDiscEvent) message.obj;
                        device = provDisc.device;
                        if (device != null
                                && !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 WifiP2pMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT:
                        if (message.obj == null) {
                            Log.e(TAG, "Illegal argument(s)");
                            break;
                        }
                        provDisc = (WifiP2pProvDiscEvent) message.obj;
                        device = provDisc.device;
                        if (device == null) {
                            Log.e(TAG, "Invalid device");
                            break;
                        }
                        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 WifiP2pMonitor.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 WifiP2pMonitor.P2P_GO_NEGOTIATION_SUCCESS_EVENT:
                    case WifiP2pMonitor.P2P_GROUP_FORMATION_SUCCESS_EVENT:
                        if (DBG) logd(getName() + " go success");
                        break;
                    case WifiP2pMonitor.P2P_GROUP_STARTED_EVENT:
                        if (message.obj == null) {
                            Log.e(TAG, "Illegal argument(s)");
                            break;
                        }
                        mGroup = (WifiP2pGroup) message.obj;
                        if (DBG) logd(getName() + " group started");
                        if (mGroup.isGroupOwner()
                                && EMPTY_DEVICE_ADDRESS.equals(mGroup.getOwner().deviceAddress)) {
                            // wpa_supplicant doesn't set own device address to go_dev_addr.
                            mGroup.getOwner().deviceAddress = mThisDevice.deviceAddress;
                        }
                        if (mGroup.getNetworkId() == WifiP2pGroup.PERSISTENT_NET_ID) {
                             // update cache information and set network id to mGroup.
                            updatePersistentNetworks(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);
                            startIpManager(mGroup.getInterface());
                            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 WifiP2pMonitor.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 WifiP2pMonitor.P2P_GROUP_REMOVED_EVENT:
                        if (DBG) logd(getName() + " go failure");
                        handleGroupCreationFailure();
                        transitionTo(mInactiveState);
                        break;
                    case WifiP2pMonitor.P2P_GROUP_FORMATION_FAILURE_EVENT:
                        // A group formation failure is always followed by
                        // a group removed event. Flushing things at group formation
                        // failure causes supplicant issues. Ignore right now.
                        status = (P2pStatus) message.obj;
                        if (status == P2pStatus.NO_COMMON_CHANNEL) {
                            transitionTo(mFrequencyConflictState);
                            break;
                        }
                        break;
                    case WifiP2pMonitor.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 unavailable" 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.setCanceledOnTouchOutside(false);

                dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
                WindowManager.LayoutParams attrs = dialog.getWindow().getAttributes();
                attrs.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
                dialog.getWindow().setAttributes(attrs);
                dialog.show();
                mFrequencyConflictDialog = dialog;
            }

            @Override
            public boolean processMessage(Message message) {
                if (DBG) logd(getName() + message.toString());
                switch (message.what) {
                    case WifiP2pMonitor.P2P_GO_NEGOTIATION_SUCCESS_EVENT:
                    case WifiP2pMonitor.P2P_GROUP_FORMATION_SUCCESS_EVENT:
                        loge(getName() + "group sucess during freq conflict!");
                        break;
                    case WifiP2pMonitor.P2P_GROUP_STARTED_EVENT:
                        loge(getName() + "group started after freq conflict, handle anyway");
                        deferMessage(message);
                        transitionTo(mGroupNegotiationState);
                        break;
                    case WifiP2pMonitor.P2P_GO_NEGOTIATION_FAILURE_EVENT:
                    case WifiP2pMonitor.P2P_GROUP_REMOVED_EVENT:
                    case WifiP2pMonitor.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
                        if (mWifiChannel != null) {
                            mWifiChannel.sendMessage(WifiP2pServiceImpl.DISCONNECT_WIFI_REQUEST, 1);
                        } else {
                            loge("DROP_WIFI_USER_ACCEPT message received when WifiChannel is null");
                        }
                        mTemporarilyDisconnectedWifi = 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());
                WifiP2pDevice device = null;
                String deviceAddress = null;
                switch (message.what) {
                    case WifiP2pMonitor.AP_STA_CONNECTED_EVENT:
                        if (message.obj == null) {
                            Log.e(TAG, "Illegal argument(s)");
                            break;
                        }
                        device = (WifiP2pDevice) message.obj;
                        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 WifiP2pMonitor.AP_STA_DISCONNECTED_EVENT:
                        if (message.obj == null) {
                            Log.e(TAG, "Illegal argument(s)");
                            break;
                        }
                        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 IPM_PRE_DHCP_ACTION:
                        mWifiNative.setP2pPowerSave(mGroup.getInterface(), false);
                        mIpManager.completedPreDhcpAction();
                        break;
                    case IPM_POST_DHCP_ACTION:
                        mWifiNative.setP2pPowerSave(mGroup.getInterface(), true);
                        break;
                    case IPM_DHCP_RESULTS:
                        mDhcpResults = (DhcpResults) message.obj;
                        break;
                    case IPM_PROVISIONING_SUCCESS:
                        if (DBG) logd("mDhcpResults: " + mDhcpResults);
                        if (mDhcpResults != null) {
                            setWifiP2pInfoOnGroupFormation(mDhcpResults.serverAddress);
                        }
                        sendP2pConnectionChangedBroadcast();
                        try {
                            final String ifname = mGroup.getInterface();
                            if (mDhcpResults != null) {
                                mNwService.addInterfaceToLocalNetwork(
                                        ifname, mDhcpResults.getRoutes(ifname));
                            }
                        } catch (RemoteException e) {
                            loge("Failed to add iface to local network " + e);
                        }
                        break;
                    case IPM_PROVISIONING_FAILURE:
                        loge("IP provisioning 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;
                    case WifiP2pMonitor.P2P_GROUP_REMOVED_EVENT:
                        // 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.
                        if (DBG) logd(getName() + " group removed");
                        handleGroupRemoved();
                        transitionTo(mInactiveState);
                        break;
                    case WifiP2pMonitor.P2P_DEVICE_LOST_EVENT:
                        if (message.obj == null) {
                            Log.e(TAG, "Illegal argument(s)");
                            return NOT_HANDLED;
                        }
                        device = (WifiP2pDevice) message.obj;
                        if (!mGroup.contains(device)) {
                            // do the regular device lost handling
                            return NOT_HANDLED;
                        }
                        // Device loss for a connected device indicates
                        // it is not in discovery any more
                        if (DBG) logd("Add device to lost list " + device);
                        mPeersLostDuringConnection.updateSupplicantDetails(device);
                        return 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(), null);
                                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 request " + 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 WifiP2pMonitor.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");
                                removeClientFromList(netId, mSavedPeerConfig.deviceAddress, false);
                                // try invitation.
                                sendMessage(WifiP2pManager.CONNECT, mSavedPeerConfig);
                            }
                        }
                        break;
                    case WifiP2pMonitor.P2P_PROV_DISC_PBC_REQ_EVENT:
                    case WifiP2pMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT:
                    case WifiP2pMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT:
                        WifiP2pProvDiscEvent provDisc = (WifiP2pProvDiscEvent) message.obj;
                        mSavedPeerConfig = new WifiP2pConfig();
                        if (provDisc != null && provDisc.device != null) {
                            mSavedPeerConfig.deviceAddress = provDisc.device.deviceAddress;
                        }
                        if (message.what == WifiP2pMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT) {
                            mSavedPeerConfig.wps.setup = WpsInfo.KEYPAD;
                        } else if (message.what == WifiP2pMonitor.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 WifiP2pMonitor.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 WifiP2pMonitor.P2P_PROV_DISC_PBC_REQ_EVENT:
                    case WifiP2pMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT:
                    case WifiP2pMonitor.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("mGroups" + mGroups);
            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);
            if (mWifiChannel != null) {
                mWifiChannel.sendMessage(WifiP2pServiceImpl.P2P_CONNECTION_CHANGED,
                        new NetworkInfo(mNetworkInfo));
            } else {
                loge("sendP2pConnectionChangedBroadcast(): WifiChannel is null");
            }
        }

        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
                ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService(
                        Context.CONNECTIVITY_SERVICE);
                String[] tetheringDhcpRanges = cm.getTetheredDhcpRanges();
                if (mNwService.isTetheringStarted()) {
                    if (DBG) logd("Stop existing tethering and restart it");
                    mNwService.stopTethering();
                }
                mNwService.tetherInterface(intf);
                mNwService.startTethering(tetheringDhcpRanges);
            } catch (Exception e) {
                loge("Error configuring interface " + intf + ", :" + e);
                return;
            }

            logd("Started Dhcp server on " + intf);
        }

        private void stopDhcpServer(String intf) {
            try {
                mNwService.untetherInterface(intf);
                for (String temp : mNwService.listTetheredInterfaces()) {
                    logd("List all interfaces " + temp);
                    if (temp.compareTo(intf) != 0) {
                        logd("Found other tethering interfaces, so keep tethering alive");
                        return;
                    }
                }
                mNwService.stopTethering();
            } catch (Exception e) {
                loge("Error stopping Dhcp server" + e);
                return;
            } finally {
                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.setCanceledOnTouchOutside(false);
            dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
            WindowManager.LayoutParams attrs = dialog.getWindow().getAttributes();
            attrs.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
            dialog.getWindow().setAttributes(attrs);
            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.setCanceledOnTouchOutside(false);
            dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
            WindowManager.LayoutParams attrs = dialog.getWindow().getAttributes();
            attrs.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
            dialog.getWindow().setAttributes(attrs);
            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();
            dialog.setCanceledOnTouchOutside(false);

            // 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);
            WindowManager.LayoutParams attrs = dialog.getWindow().getAttributes();
            attrs.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
            dialog.getWindow().setAttributes(attrs);
            dialog.show();
        }

        /**
         * This method unifies the persisent group list, cleans up unused
         * networks and if required, updates corresponding broadcast receivers
         * @param boolean if true, reload the group list from scratch
         *                and send broadcast message with fresh list
         */
        private void updatePersistentNetworks(boolean reload) {
            if (reload) mGroups.clear();

            // Save in all cases, including when reload was requested, but
            // no network has been found.
            if (mWifiNative.p2pListNetworks(mGroups) || reload) {
                for (WifiP2pGroup group : mGroups.getGroupList()) {
                    if (mThisDevice.deviceAddress.equals(group.getOwner().deviceAddress)) {
                        group.setOwner(mThisDevice);
                    }
                }
                mWifiNative.saveConfig();
                sendP2pPersistentGroupsChangedBroadcast();
            }
        }

        /**
         * A config is valid if it has a peer address that has already been
         * discovered
         * @param WifiP2pConfig config to be validated
         * @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;
        }

        private WifiP2pDevice fetchCurrentDeviceDetails(WifiP2pConfig config) {
            if (config == null) return null;
            // Fetch & update group capability from supplicant on the device
            int gc = mWifiNative.getGroupCapability(config.deviceAddress);
            // 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
            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) {
            if (config == null) {
                Log.e(TAG, "Illegal argument(s)");
                return;
            }
            WifiP2pDevice dev = fetchCurrentDeviceDetails(config);
            if (dev == null) {
                Log.e(TAG, "Invalid device");
                return;
            }
            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) {
            if (config == null) {
                Log.e(TAG, "Illegal argument(s)");
                return false;
            }
            WifiP2pDevice dev = fetchCurrentDeviceDetails(config);
            if (dev == null) {
                Log.e(TAG, "Invalid device");
                return false;
            }
            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.getP2pClientList(netId);
            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.setP2pClientList(netId, 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() {
            mThisDevice.deviceName = getPersistedDeviceName();
            mWifiNative.setP2pDeviceName(mThisDevice.deviceName);
            // DIRECT-XY-DEVICENAME (XY is randomly generated)
            mWifiNative.setP2pSsidPostfix("-" + mThisDevice.deviceName);
            mWifiNative.setP2pDeviceType(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");

            mThisDevice.deviceAddress = mWifiNative.p2pGetDeviceAddress();
            updateThisDevice(WifiP2pDevice.AVAILABLE);
            if (DBG) logd("DeviceAddress: " + mThisDevice.deviceAddress);

            mClientInfoList.clear();
            mWifiNative.p2pFlush();
            mWifiNative.p2pServiceFlush();
            mServiceTransactionId = 0;
            mServiceDiscReqId = null;

            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 (!TextUtils.isEmpty(mSavedPeerConfig.deviceAddress)
                    && 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 IpManager");
                stopIpManager();
                try {
                    mNwService.removeInterfaceFromLocalNetwork(mGroup.getInterface());
                } catch (RemoteException e) {
                    loge("Failed to remove iface from local network " + e);
                }
            }

            try {
                mNwService.clearInterfaceAddresses(mGroup.getInterface());
            } catch (Exception e) {
                loge("Failed to clear addresses " + e);
            }

            // 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 (mTemporarilyDisconnectedWifi) {
                if (mWifiChannel != null) {
                    mWifiChannel.sendMessage(WifiP2pServiceImpl.DISCONNECT_WIFI_REQUEST, 0);
                } else {
                    loge("handleGroupRemoved(): WifiChannel is null");
                }
                mTemporarilyDisconnectedWifi = false;
            }
        }

        private void replyToMessage(Message msg, int what) {
            // State machine initiated requests can have replyTo set to null
            // indicating there are no recipients, we ignore those reply actions
            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);
        }

        private Message obtainMessage(Message srcMsg) {
            // arg2 on the source message has a hash code that needs to
            // be retained in replies see WifiP2pManager for details
            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;
        }

        private boolean addServiceRequest(Messenger m, WifiP2pServiceRequest req) {
            if (m == null || req == null) {
                Log.e(TAG, "Illegal argument(s)");
                return false;
            }
            // TODO: We could track individual service adds separately and avoid
            // having to do update all service requests on every new request
            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) {
            if (m == null || req == null) {
                Log.e(TAG, "Illegal argument(s)");
            }

            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) {
            if (m == null) {
                Log.e(TAG, "Illegal argument(s)");
                return;
            }

            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) {
            if (m == null || servInfo == null) {
                Log.e(TAG, "Illegal arguments");
                return false;
            }

            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) {
            if (m == null || servInfo == null) {
                Log.e(TAG, "Illegal arguments");
                return;
            }

            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) {
            if (m == null) {
                Log.e(TAG, "Illegal argument(s)");
                return;
            }

            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 WifiP2pServiceResponse response to service discovery
         */
        private void sendServiceResponse(WifiP2pServiceResponse resp) {
            if (resp == null) {
                Log.e(TAG, "sendServiceResponse with null response");
                return;
            }
            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;
                    if (c.mMessenger == null) {
                        continue;
                    }
                    try {
                        c.mMessenger.send(msg);
                    } catch (RemoteException e) {
                        if (DBG) logd("detect dead channel");
                        clearClientInfo(c.mMessenger);
                        return;
                    }
                }
            }
        }

        /**
         * We don't 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;
                if (c.mMessenger == null) {
                    continue;
                }
                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;
        }

        /**
         * Enforces permissions on the caller who is requesting for P2p Peers
         * @param pkg Bundle containing the calling package string
         * @param uid of the caller
         * @return WifiP2pDeviceList the peer list
         */
        private WifiP2pDeviceList getPeers(Bundle pkg, int uid) {
            String pkgName = pkg.getString(WifiP2pManager.CALLING_PACKAGE);
            boolean scanPermission = false;
            WifiPermissionsUtil wifiPermissionsUtil;
            // getPeers() is guaranteed to be invoked after Wifi Service is up
            // This ensures getInstance() will return a non-null object now
            if (mWifiInjector == null) {
                mWifiInjector = WifiInjector.getInstance();
            }
            wifiPermissionsUtil = mWifiInjector.getWifiPermissionsUtil();
            // Minimum Version to enforce location permission is O or later
            try {
                scanPermission = wifiPermissionsUtil.canAccessScanResults(pkgName, uid,
                        Build.VERSION_CODES.O);
            } catch (SecurityException e) {
                Log.e(TAG, "Security Exception, cannot access peer list");
            }
            if (scanPermission) {
                return new WifiP2pDeviceList(mPeers);
            } else {
                return new WifiP2pDeviceList();
            }
        }
    }

    /**
     * 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>();
        }
    }
}
