content: /*
   * 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.BroadcastReceiver;
  import android.content.Context;
  import android.content.DialogInterface;
  import android.content.DialogInterface.OnClickListener;
  import android.content.Intent;
  import android.content.IntentFilter;
  import android.content.pm.PackageManager;
  import android.content.res.Configuration;
  import android.content.res.Resources;
  import android.database.ContentObserver;
  import android.location.LocationManager;
  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.IIpClient;
  import android.net.ip.IpClientCallbacks;
  import android.net.ip.IpClientUtil;
  import android.net.shared.ProvisioningConfiguration;
  import android.net.wifi.WifiManager;
  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.Bundle;
  import android.os.Handler;
  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.Process;
  import android.os.RemoteException;
  import android.os.ServiceManager;
  import android.os.UserHandle;
  import android.os.UserManager;
  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.annotations.VisibleForTesting;
  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.FrameworkFacade;
  import com.android.server.wifi.WifiInjector;
  import com.android.server.wifi.WifiLog;
  import com.android.server.wifi.nano.WifiMetricsProto.P2pConnectionEvent;
  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.nio.charset.StandardCharsets;
  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 boolean mVerboseLoggingEnabled = false;
      private static final String NETWORKTYPE = "WIFI_P2P";
  
      private Context mContext;
  
      INetworkManagementService mNwService;
      private IIpClient mIpClient;
      private int mIpClientStartIndex = 0;
      private DhcpResults mDhcpResults;
  
      private P2pStateMachine mP2pStateMachine;
      private AsyncChannel mReplyChannel = new WifiAsyncChannel(TAG);
      private AsyncChannel mWifiChannel;
      private LocationManager mLocationManager;
      private WifiInjector mWifiInjector;
      private WifiPermissionsUtil mWifiPermissionsUtil;
      private FrameworkFacade mFrameworkFacade;
      private WifiP2pMetrics mWifiP2pMetrics;
  
      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;
  
      private static final String[] RECEIVER_PERMISSIONS_FOR_BROADCAST = {
              android.Manifest.permission.ACCESS_FINE_LOCATION,
              android.Manifest.permission.ACCESS_WIFI_STATE
      };
  
      // Maximum number of bytes allowed for a network name, i.e. SSID.
      private static final int MAX_NETWORK_NAME_BYTES = 32;
      // Minimum number of bytes for a network name, i.e. DIRECT-xy.
      private static final int MIN_NETWORK_NAME_BYTES = 9;
  
      // 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;
      // User confirm a peer request
      public static final int PEER_CONNECTION_USER_CONFIRM    =   BASE + 7;
  
      // Commands to the ClientModeImpl
      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;
      public static final int ENABLE_P2P                      =   BASE + 16;
      public static final int DISABLE_P2P                     =   BASE + 17;
      public static final int REMOVE_CLIENT_INFO              =   BASE + 18;
  
      // Messages for interaction with IpClient.
      private static final int IPC_PRE_DHCP_ACTION            =   BASE + 30;
      private static final int IPC_POST_DHCP_ACTION           =   BASE + 31;
      private static final int IPC_DHCP_RESULTS               =   BASE + 32;
      private static final int IPC_PROVISIONING_SUCCESS       =   BASE + 33;
      private static final int IPC_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>();
  
      // clients(application) channel list
      private Map<IBinder, Messenger> mClientChannelList = new HashMap<IBinder, Messenger>();
  
      // 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";
  
      // An anonymized device address. This is used instead of the own device MAC to prevent the
      // latter from leaking to apps
      private static final String ANONYMIZED_DEVICE_ADDRESS = "02: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:
                  case WifiP2pManager.FACTORY_RESET:
                  case WifiP2pManager.SET_ONGOING_PEER_CONFIG:
                  case WifiP2pManager.REQUEST_ONGOING_PEER_CONFIG:
                  case WifiP2pManager.REQUEST_P2P_STATE:
                  case WifiP2pManager.REQUEST_DISCOVERY_STATE:
                  case WifiP2pManager.REQUEST_NETWORK_INFO:
                  case WifiP2pManager.UPDATE_CHANNEL_INFO:
                  case WifiP2pManager.REQUEST_DEVICE_INFO:
                      mP2pStateMachine.sendMessage(Message.obtain(msg));
                      break;
                  default:
                      Slog.d(TAG, "ClientHandler.handleMessage ignoring msg=" + msg);
                      break;
              }
          }
      }
      private ClientHandler mClientHandler;
  
      /**
       * Provide a way for unit tests to set valid log object in the WifiHandler
       * @param log WifiLog object to assign to the clientHandler
       */
      @VisibleForTesting
      void setWifiHandlerLogForTest(WifiLog log) {
          mClientHandler.setWifiLog(log);
  
      }
  
      /**
       * Provide a way for unit tests to set valid log object in the WifiAsyncChannel
       * @param log WifiLog object to assign to the mReplyChannel
       */
      @VisibleForTesting
      void setWifiLogForReplyChannel(WifiLog log) {
          ((WifiAsyncChannel) mReplyChannel).setWifiLog(log);
      }
  
      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<>();
  
      public WifiP2pServiceImpl(Context context, WifiInjector wifiInjector) {
          mContext = context;
          mWifiInjector = wifiInjector;
          mWifiPermissionsUtil = mWifiInjector.getWifiPermissionsUtil();
          mFrameworkFacade = mWifiInjector.getFrameworkFacade();
          mWifiP2pMetrics = mWifiInjector.getWifiP2pMetrics();
  
          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 = mWifiInjector.getWifiP2pServiceHandlerThread();
          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 stopIpClient() {
          // Invalidate all previous start requests
          mIpClientStartIndex++;
          if (mIpClient != null) {
              try {
                  mIpClient.stop();
              } catch (RemoteException e) {
                  e.rethrowFromSystemServer();
              }
              mIpClient = null;
          }
          mDhcpResults = null;
      }
  
      private void startIpClient(String ifname, Handler smHandler) {
          stopIpClient();
          mIpClientStartIndex++;
          IpClientUtil.makeIpClient(mContext, ifname, new IpClientCallbacksImpl(
                  mIpClientStartIndex, smHandler));
      }
  
      private class IpClientCallbacksImpl extends IpClientCallbacks {
          private final int mStartIndex;
          private final Handler mHandler;
  
          private IpClientCallbacksImpl(int startIndex, Handler handler) {
              mStartIndex = startIndex;
              mHandler = handler;
          }
  
          @Override
          public void onIpClientCreated(IIpClient ipClient) {
              mHandler.post(() -> {
                  if (mIpClientStartIndex != mStartIndex) {
                      // This start request is obsolete
                      return;
                  }
                  mIpClient = ipClient;
  
                  final ProvisioningConfiguration config =
                          new ProvisioningConfiguration.Builder()
                                  .withoutIpReachabilityMonitor()
                                  .withPreDhcpAction(30 * 1000)
                                  .withProvisioningTimeoutMs(36 * 1000)
                                  .build();
                  try {
                      mIpClient.startProvisioning(config.toStableParcelable());
                  } catch (RemoteException e) {
                      e.rethrowFromSystemServer();
                  }
              });
          }
  
          @Override
          public void onPreDhcpAction() {
              mP2pStateMachine.sendMessage(IPC_PRE_DHCP_ACTION);
          }
          @Override
          public void onPostDhcpAction() {
              mP2pStateMachine.sendMessage(IPC_POST_DHCP_ACTION);
          }
          @Override
          public void onNewDhcpResults(DhcpResults dhcpResults) {
              mP2pStateMachine.sendMessage(IPC_DHCP_RESULTS, dhcpResults);
          }
          @Override
          public void onProvisioningSuccess(LinkProperties newLp) {
              mP2pStateMachine.sendMessage(IPC_PROVISIONING_SUCCESS);
          }
          @Override
          public void onProvisioningFailure(LinkProperties newLp) {
              mP2pStateMachine.sendMessage(IPC_PROVISIONING_FAILURE);
          }
      }
  
      /**
       * 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 (mVerboseLoggingEnabled) {
                  Log.d(TAG, "getMessenger: uid=" + getCallingUid() + ", binder=" + binder
                          + ", messenger=" + messenger);
              }
  
              IBinder.DeathRecipient dr = () -> {
                  if (mVerboseLoggingEnabled) 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
              }
              mP2pStateMachine.sendMessage(ENABLE_P2P);
  
              return messenger;
          }
      }
  
      /**
       * Get a reference to handler. This is used by a ClientModeImpl 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;
              }
  
              mP2pStateMachine.sendMessage(REMOVE_CLIENT_INFO, 0, 0, binder);
              binder.unlinkToDeath(dhd.mDeathRecipient, 0);
              mDeathDataByBinder.remove(binder);
  
              // clean-up if there are no more clients registered
              // TODO: what does the ClientModeImpl 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);
                  }
                  mP2pStateMachine.sendMessage(DISABLE_P2P);
              }
          }
      }
  
      /** 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) {
          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("mClientInfoList " + mClientInfoList.size());
          pw.println();
  
          final IIpClient ipClient = mIpClient;
          if (ipClient != null) {
              pw.println("mIpClient:");
              IpClientUtil.dumpIpClient(ipClient, fd, pw, args);
          }
      }
  
      /**
       * Handles interaction with ClientModeImpl
       */
      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 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 = mWifiInjector.getWifiP2pNative();
          private WifiP2pMonitor mWifiMonitor = mWifiInjector.getWifiP2pMonitor();
          private final WifiP2pDeviceList mPeers = new WifiP2pDeviceList();
          private String mInterfaceName;
  
          // 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 (mVerboseLoggingEnabled) logd("called onDeleteGroup() netId=" + netId);
                          mWifiNative.removeP2pNetwork(netId);
                          mWifiNative.saveConfig();
                          sendP2pPersistentGroupsChangedBroadcast();
                      }
                  });
          private final WifiP2pInfo mWifiP2pInfo = new WifiP2pInfo();
          private WifiP2pGroup mGroup;
          // Is the HAL (HIDL) interface available for use.
          private boolean mIsHalInterfaceAvailable = false;
          // Is wifi on or off.
          private boolean mIsWifiEnabled = false;
  
          // 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(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);
  
              if (p2pSupported) {
                  // Register for wifi on/off broadcasts
                  mContext.registerReceiver(new BroadcastReceiver() {
                      @Override
                      public void onReceive(Context context, Intent intent) {
                          int wifistate = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
                                  WifiManager.WIFI_STATE_UNKNOWN);
                          if (wifistate == WifiManager.WIFI_STATE_ENABLED) {
                              mIsWifiEnabled = true;
                              checkAndReEnableP2p();
                          } else {
                              mIsWifiEnabled = false;
                              // Teardown P2P if it's up already.
                              sendMessage(DISABLE_P2P);
                          }
                          checkAndSendP2pStateChangedBroadcast();
                      }
                  }, new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION));
                  // Register for location mode on/off broadcasts
                  mContext.registerReceiver(new BroadcastReceiver() {
                      @Override
                      public void onReceive(Context context, Intent intent) {
                          /* if location mode is off, ongoing discovery should be stopped.
                           * possible ongoing discovery:
                           * - peer discovery
                           * - service discovery
                           * - group joining scan in native service
                           */
                          if (!mWifiPermissionsUtil.isLocationModeEnabled()) {
                              sendMessage(WifiP2pManager.STOP_DISCOVERY);
                          }
                      }
                  }, new IntentFilter(LocationManager.MODE_CHANGED_ACTION));
                  // Register for interface availability from HalDeviceManager
                  mWifiNative.registerInterfaceAvailableListener((boolean isAvailable) -> {
                      mIsHalInterfaceAvailable = isAvailable;
                      if (isAvailable) {
                          checkAndReEnableP2p();
                      }
                      checkAndSendP2pStateChangedBroadcast();
                  }, getHandler());
  
                  mFrameworkFacade.registerContentObserver(mContext,
                          Settings.Global.getUriFor(Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED),
                          true, new ContentObserver(new Handler(looper)) {
                              @Override
                              public void onChange(boolean selfChange) {
                                  enableVerboseLogging(mFrameworkFacade.getIntegerSetting(mContext,
                                          Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED, 0));
                              }
                          });
              }
          }
  
          /**
           * Enable verbose logging for all sub modules.
           */
          private void enableVerboseLogging(int verbose) {
              mVerboseLoggingEnabled = verbose > 0;
              mWifiNative.enableVerboseLogging(verbose);
              mWifiMonitor.enableVerboseLogging(verbose);
          }
  
          public void registerForWifiMonitorEvents() {
              mWifiMonitor.registerHandler(mInterfaceName,
                      WifiP2pMonitor.AP_STA_CONNECTED_EVENT, getHandler());
              mWifiMonitor.registerHandler(mInterfaceName,
                      WifiP2pMonitor.AP_STA_DISCONNECTED_EVENT, getHandler());
              mWifiMonitor.registerHandler(mInterfaceName,
                      WifiP2pMonitor.P2P_DEVICE_FOUND_EVENT, getHandler());
              mWifiMonitor.registerHandler(mInterfaceName,
                      WifiP2pMonitor.P2P_DEVICE_LOST_EVENT, getHandler());
              mWifiMonitor.registerHandler(mInterfaceName,
                      WifiP2pMonitor.P2P_FIND_STOPPED_EVENT, getHandler());
              mWifiMonitor.registerHandler(mInterfaceName,
                      WifiP2pMonitor.P2P_GO_NEGOTIATION_FAILURE_EVENT, getHandler());
              mWifiMonitor.registerHandler(mInterfaceName,
                      WifiP2pMonitor.P2P_GO_NEGOTIATION_REQUEST_EVENT, getHandler());
              mWifiMonitor.registerHandler(mInterfaceName,
                      WifiP2pMonitor.P2P_GO_NEGOTIATION_SUCCESS_EVENT, getHandler());
              mWifiMonitor.registerHandler(mInterfaceName,
                      WifiP2pMonitor.P2P_GROUP_FORMATION_FAILURE_EVENT, getHandler());
              mWifiMonitor.registerHandler(mInterfaceName,
                      WifiP2pMonitor.P2P_GROUP_FORMATION_SUCCESS_EVENT, getHandler());
              mWifiMonitor.registerHandler(mInterfaceName,
                      WifiP2pMonitor.P2P_GROUP_REMOVED_EVENT, getHandler());
              mWifiMonitor.registerHandler(mInterfaceName,
                      WifiP2pMonitor.P2P_GROUP_STARTED_EVENT, getHandler());
              mWifiMonitor.registerHandler(mInterfaceName,
                      WifiP2pMonitor.P2P_INVITATION_RECEIVED_EVENT, getHandler());
              mWifiMonitor.registerHandler(mInterfaceName,
                      WifiP2pMonitor.P2P_INVITATION_RESULT_EVENT, getHandler());
              mWifiMonitor.registerHandler(mInterfaceName,
                      WifiP2pMonitor.P2P_PROV_DISC_ENTER_PIN_EVENT, getHandler());
              mWifiMonitor.registerHandler(mInterfaceName,
                      WifiP2pMonitor.P2P_PROV_DISC_FAILURE_EVENT, getHandler());
              mWifiMonitor.registerHandler(mInterfaceName,
                      WifiP2pMonitor.P2P_PROV_DISC_PBC_REQ_EVENT, getHandler());
              mWifiMonitor.registerHandler(mInterfaceName,
                      WifiP2pMonitor.P2P_PROV_DISC_PBC_RSP_EVENT, getHandler());
              mWifiMonitor.registerHandler(mInterfaceName,
                      WifiP2pMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT, getHandler());
              mWifiMonitor.registerHandler(mInterfaceName,
                      WifiP2pMonitor.P2P_SERV_DISC_RESP_EVENT, getHandler());
              mWifiMonitor.registerHandler(mInterfaceName,
                      WifiP2pMonitor.SUP_CONNECTION_EVENT, getHandler());
              mWifiMonitor.registerHandler(mInterfaceName,
                      WifiP2pMonitor.SUP_DISCONNECTION_EVENT, getHandler());
  
              mWifiMonitor.startMonitoring(mInterfaceName);
          }
  
          class DefaultState extends State {
              @Override
              public boolean processMessage(Message message) {
                  if (mVerboseLoggingEnabled) logd(getName() + message.toString());
                  switch (message.what) {
                      case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
                          if (message.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
                              if (mVerboseLoggingEnabled) {
                                  logd("Full connection with ClientModeImpl 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_FAILED,
                                  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(getCallingPkgName(message.sendingUid, message.replyTo),
                                  message.sendingUid));
                          break;
                      case WifiP2pManager.REQUEST_CONNECTION_INFO:
                          replyToMessage(message, WifiP2pManager.RESPONSE_CONNECTION_INFO,
                                  new WifiP2pInfo(mWifiP2pInfo));
                          break;
                      case WifiP2pManager.REQUEST_GROUP_INFO:
                          if (!mWifiPermissionsUtil.checkCanAccessWifiDirect(
                                  getCallingPkgName(message.sendingUid, message.replyTo),
                                  message.sendingUid, false)) {
                              replyToMessage(message, WifiP2pManager.RESPONSE_GROUP_INFO, null);
                              // remain at this state.
                              break;
                          }
                          replyToMessage(message, WifiP2pManager.RESPONSE_GROUP_INFO,
                                  maybeEraseOwnDeviceAddress(mGroup, message.sendingUid));
                          break;
                      case WifiP2pManager.REQUEST_PERSISTENT_GROUP_INFO:
                          replyToMessage(message, WifiP2pManager.RESPONSE_PERSISTENT_GROUP_INFO,
                                  new WifiP2pGroupList(
                                          maybeEraseOwnDeviceAddress(mGroups, message.sendingUid),
                                          null));
                          break;
                      case WifiP2pManager.REQUEST_P2P_STATE:
                          replyToMessage(message, WifiP2pManager.RESPONSE_P2P_STATE,
                                  (mIsWifiEnabled && isHalInterfaceAvailable())
                                  ? WifiP2pManager.WIFI_P2P_STATE_ENABLED
                                  : WifiP2pManager.WIFI_P2P_STATE_DISABLED);
                          break;
                      case WifiP2pManager.REQUEST_DISCOVERY_STATE:
                          replyToMessage(message, WifiP2pManager.RESPONSE_DISCOVERY_STATE,
                                  mDiscoveryStarted
                                  ? WifiP2pManager.WIFI_P2P_DISCOVERY_STARTED
                                  : WifiP2pManager.WIFI_P2P_DISCOVERY_STOPPED);
                          break;
                      case WifiP2pManager.REQUEST_NETWORK_INFO:
                          replyToMessage(message, WifiP2pManager.RESPONSE_NETWORK_INFO,
                                  mNetworkInfo);
                          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 IPC_PRE_DHCP_ACTION:
                      case IPC_POST_DHCP_ACTION:
                      case IPC_DHCP_RESULTS:
                      case IPC_PROVISIONING_SUCCESS:
                      case IPC_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 ENABLE_P2P:
                          // Enable is lazy and has no response
                          break;
                      case DISABLE_P2P:
                          // If we end up handling in default, p2p is not enabled
                          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;
                      case WifiP2pManager.FACTORY_RESET:
                          if (factoryReset(message.sendingUid)) {
                              replyToMessage(message, WifiP2pManager.FACTORY_RESET_SUCCEEDED);
                          } else {
                              replyToMessage(message, WifiP2pManager.FACTORY_RESET_FAILED,
                                      WifiP2pManager.ERROR);
                          }
                          break;
                      case WifiP2pManager.SET_ONGOING_PEER_CONFIG:
                          if (mWifiPermissionsUtil.checkNetworkStackPermission(message.sendingUid)) {
                              WifiP2pConfig peerConfig = (WifiP2pConfig) message.obj;
                              if (isConfigInvalid(peerConfig)) {
                                  loge("Dropping set mSavedPeerConfig requeset" + peerConfig);
                                  replyToMessage(message,
                                          WifiP2pManager.SET_ONGOING_PEER_CONFIG_FAILED);
                              } else {
                                  logd("setSavedPeerConfig to " + peerConfig);
                                  mSavedPeerConfig = peerConfig;
                                  replyToMessage(message,
                                          WifiP2pManager.SET_ONGOING_PEER_CONFIG_SUCCEEDED);
                              }
                          } else {
                              loge("Permission violation - no NETWORK_STACK permission,"
                                      + " uid = " + message.sendingUid);
                              replyToMessage(message,
                                      WifiP2pManager.SET_ONGOING_PEER_CONFIG_FAILED);
                          }
                          break;
                      case WifiP2pManager.REQUEST_ONGOING_PEER_CONFIG:
                          if (mWifiPermissionsUtil.checkNetworkStackPermission(message.sendingUid)) {
                              replyToMessage(message,
                                      WifiP2pManager.RESPONSE_ONGOING_PEER_CONFIG, mSavedPeerConfig);
                          } else {
                              loge("Permission violation - no NETWORK_STACK permission,"
                                      + " uid = " + message.sendingUid);
                              replyToMessage(message,
                                      WifiP2pManager.RESPONSE_ONGOING_PEER_CONFIG, null);
                          }
                          break;
                      case WifiP2pManager.UPDATE_CHANNEL_INFO:
                          if (!(message.obj instanceof Bundle)) {
                              break;
                          }
                          Bundle bundle = (Bundle) message.obj;
                          String pkgName = bundle.getString(WifiP2pManager.CALLING_PACKAGE);
                          IBinder binder = bundle.getBinder(WifiP2pManager.CALLING_BINDER);
                          try {
                              mWifiPermissionsUtil.checkPackage(message.sendingUid, pkgName);
                          } catch (SecurityException se) {
                              loge("Unable to update calling package, " + se);
                              break;
                          }
                          if (binder != null && message.replyTo != null) {
                              mClientChannelList.put(binder, message.replyTo);
                              ClientInfo clientInfo = getClientInfo(message.replyTo, true);
                              clientInfo.mPackageName = pkgName;
                          }
                          break;
                      case WifiP2pManager.REQUEST_DEVICE_INFO:
                          if (!mWifiPermissionsUtil.checkCanAccessWifiDirect(
                                  getCallingPkgName(message.sendingUid, message.replyTo),
                                  message.sendingUid, false)) {
                              replyToMessage(message, WifiP2pManager.RESPONSE_DEVICE_INFO, null);
                              break;
                          }
                          replyToMessage(message, WifiP2pManager.RESPONSE_DEVICE_INFO,
                                  maybeEraseOwnDeviceAddress(mThisDevice, message.sendingUid));
                          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_FAILED,
                                  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;
                      case WifiP2pManager.FACTORY_RESET:
                          replyToMessage(message, WifiP2pManager.FACTORY_RESET_FAILED,
                                  WifiP2pManager.P2P_UNSUPPORTED);
                          break;
  
                      default:
                          return NOT_HANDLED;
                  }
                  return HANDLED;
              }
          }
  
          class P2pDisablingState extends State {
              @Override
              public void enter() {
                  if (mVerboseLoggingEnabled) logd(getName());
                  sendMessageDelayed(obtainMessage(DISABLE_P2P_TIMED_OUT,
                          ++sDisableP2pTimeoutIndex, 0), DISABLE_P2P_WAIT_TIME_MS);
              }
  
              @Override
              public boolean processMessage(Message message) {
                  if (mVerboseLoggingEnabled) logd(getName() + message.toString());
                  switch (message.what) {
                      case WifiP2pMonitor.SUP_DISCONNECTION_EVENT:
                          if (mVerboseLoggingEnabled) logd("p2p socket connection lost");
                          transitionTo(mP2pDisabledState);
                          break;
                      case ENABLE_P2P:
                      case DISABLE_P2P:
                      case REMOVE_CLIENT_INFO:
                          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;
              }
          }
  
          class P2pDisabledState extends State {
              @Override
              public void enter() {
                  if (mVerboseLoggingEnabled) logd(getName());
              }
  
              private void setupInterfaceFeatures(String interfaceName) {
                  if (mContext.getResources().getBoolean(
                          R.bool.config_wifi_p2p_mac_randomization_supported)) {
                      Log.i(TAG, "Supported feature: P2P MAC randomization");
                      mWifiNative.setMacRandomization(true);
                  } else {
                      mWifiNative.setMacRandomization(false);
                  }
              }
  
              @Override
              public boolean processMessage(Message message) {
                  if (mVerboseLoggingEnabled) logd(getName() + message.toString());
                  switch (message.what) {
                      case ENABLE_P2P:
                          if (!mIsWifiEnabled) {
                              Log.e(TAG, "Ignore P2P enable since wifi is " + mIsWifiEnabled);
                              break;
                          }
                          mInterfaceName = mWifiNative.setupInterface((String ifaceName) -> {
                              sendMessage(DISABLE_P2P);
                          }, getHandler());
                          if (mInterfaceName == null) {
                              Log.e(TAG, "Failed to setup interface for P2P");
                              break;
                          }
                          setupInterfaceFeatures(mInterfaceName);
                          try {
                              mNwService.setInterfaceUp(mInterfaceName);
                          } catch (RemoteException re) {
                              loge("Unable to change interface settings: " + re);
                          } catch (IllegalStateException ie) {
                              loge("Unable to change interface settings: " + ie);
                          }
                          registerForWifiMonitorEvents();
                          transitionTo(mInactiveState);
                          break;
                      case REMOVE_CLIENT_INFO:
                          if (!(message.obj instanceof IBinder)) {
                              loge("Invalid obj when REMOVE_CLIENT_INFO");
                              break;
                          }
                          IBinder b = (IBinder) message.obj;
                          // client service info is clear before enter disable p2p,
                          // just need to remove it from list
                          Messenger m = mClientChannelList.remove(b);
                          ClientInfo clientInfo = mClientInfoList.remove(m);
                          if (clientInfo != null) {
                              logd("Remove client - " + clientInfo.mPackageName);
                          }
                          break;
                      default:
                          return NOT_HANDLED;
                  }
                  return HANDLED;
              }
          }
  
          class P2pEnabledState extends State {
              @Override
              public void enter() {
                  if (mVerboseLoggingEnabled) logd(getName());
                  mNetworkInfo.setIsAvailable(true);
  
                  if (isPendingFactoryReset()) {
                      factoryReset(Process.SYSTEM_UID);
                  }
  
                  sendP2pConnectionChangedBroadcast();
                  initializeP2pSettings();
              }
  
              @Override
              public boolean processMessage(Message message) {
                  if (mVerboseLoggingEnabled) logd(getName() + message.toString());
                  switch (message.what) {
                      case WifiP2pMonitor.SUP_DISCONNECTION_EVENT:
                          loge("Unexpected loss of p2p socket connection");
                          transitionTo(mP2pDisabledState);
                          break;
                      case ENABLE_P2P:
                          // Nothing to do
                          break;
                      case DISABLE_P2P:
                          if (mPeers.clear()) {
                              sendPeersChangedBroadcast();
                          }
                          if (mGroups.clear()) sendP2pPersistentGroupsChangedBroadcast();
                          // clear services list for all clients since interface will teardown soon.
                          clearServicesForAllClients();
                          mWifiMonitor.stopMonitoring(mInterfaceName);
                          mWifiNative.teardownInterface();
                          transitionTo(mP2pDisablingState);
                          break;
                      case REMOVE_CLIENT_INFO:
                          if (!(message.obj instanceof IBinder)) {
                              break;
                          }
                          IBinder b = (IBinder) message.obj;
                          // clear client info and remove it from list
                          clearClientInfo(mClientChannelList.get(b));
                          mClientChannelList.remove(b);
                          break;
                      case WifiP2pManager.SET_DEVICE_NAME:
                      {
                          WifiP2pDevice d = (WifiP2pDevice) message.obj;
                          if (d != null && setAndPersistDeviceName(d.deviceName)) {
                              if (mVerboseLoggingEnabled) 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 (!mWifiPermissionsUtil.checkCanAccessWifiDirect(
                                  getCallingPkgName(message.sendingUid, message.replyTo),
                                  message.sendingUid, true)) {
                              replyToMessage(message, WifiP2pManager.DISCOVER_PEERS_FAILED,
                                      WifiP2pManager.ERROR);
                              // remain at this state.
                              break;
                          }
                          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)) {
                              mWifiP2pMetrics.incrementPeerScans();
                              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 (!mWifiPermissionsUtil.checkCanAccessWifiDirect(
                                  getCallingPkgName(message.sendingUid, message.replyTo),
                                  message.sendingUid, true)) {
                              replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED,
                                      WifiP2pManager.ERROR);
                              // remain at this state.
                              break;
                          }
                          if (mDiscoveryBlocked) {
                              replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED,
                                      WifiP2pManager.BUSY);
                              break;
                          }
                          if (mVerboseLoggingEnabled) logd(getName() + " discover services");
                          if (!updateSupplicantServiceRequest()) {
                              replyToMessage(message, WifiP2pManager.DISCOVER_SERVICES_FAILED,
                                      WifiP2pManager.NO_SERVICE_REQUESTS);
                              break;
                          }
                          if (mWifiNative.p2pFind(DISCOVER_TIMEOUT_S)) {
                              mWifiP2pMetrics.incrementServiceScans();
                              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 (!mWifiPermissionsUtil.checkCanAccessWifiDirect(
                                  getCallingPkgName(message.sendingUid, message.replyTo),
                                  message.sendingUid, false)) {
                              replyToMessage(message, WifiP2pManager.ADD_LOCAL_SERVICE_FAILED);
                              // remain at this state.
                              break;
                          }
                          if (mVerboseLoggingEnabled) 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 (mVerboseLoggingEnabled) 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 (mVerboseLoggingEnabled) logd(getName() + " clear service");
                          clearLocalServices(message.replyTo);
                          replyToMessage(message, WifiP2pManager.CLEAR_LOCAL_SERVICES_SUCCEEDED);
                          break;
                      case WifiP2pManager.ADD_SERVICE_REQUEST:
                          if (mVerboseLoggingEnabled) 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 (mVerboseLoggingEnabled) 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 (mVerboseLoggingEnabled) logd(getName() + " clear service request");
                          clearServiceRequests(message.replyTo);
                          replyToMessage(message, WifiP2pManager.CLEAR_SERVICE_REQUESTS_SUCCEEDED);
                          break;
                      case WifiP2pMonitor.P2P_SERV_DISC_RESP_EVENT:
                          if (mVerboseLoggingEnabled) 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 (mVerboseLoggingEnabled) logd(getName() + " delete persistent group");
                          mGroups.remove(message.arg1);
                          mWifiP2pMetrics.updatePersistentGroup(mGroups);
                          replyToMessage(message, WifiP2pManager.DELETE_PERSISTENT_GROUP_SUCCEEDED);
                          break;
                      case SET_MIRACAST_MODE:
                          mWifiNative.setMiracastMode(message.arg1);
                          break;
                      case WifiP2pManager.START_LISTEN:
                          if (!mWifiPermissionsUtil.checkNetworkSettingsPermission(
                                  message.sendingUid)) {
                              loge("Permission violation - no NETWORK_SETTING permission,"
                                      + " uid = " + message.sendingUid);
                              replyToMessage(message, WifiP2pManager.START_LISTEN_FAILED);
                              break;
                          }
                          if (mVerboseLoggingEnabled) 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 (!mWifiPermissionsUtil.checkNetworkSettingsPermission(
                                  message.sendingUid)) {
                              loge("Permission violation - no NETWORK_SETTING permission,"
                                      + " uid = " + message.sendingUid);
                              replyToMessage(message, WifiP2pManager.STOP_LISTEN_FAILED);
                              break;
                          }
                          if (mVerboseLoggingEnabled) 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 (mVerboseLoggingEnabled) {
                              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);
                  mNetworkInfo.setIsAvailable(false);
              }
          }
  
          class InactiveState extends State {
              @Override
              public void enter() {
                  if (mVerboseLoggingEnabled) logd(getName());
                  mSavedPeerConfig.invalidate();
              }
  
              @Override
              public boolean processMessage(Message message) {
                  if (mVerboseLoggingEnabled) logd(getName() + message.toString());
                  switch (message.what) {
                      case WifiP2pManager.CONNECT:
                          if (!mWifiPermissionsUtil.checkCanAccessWifiDirect(
                                  getCallingPkgName(message.sendingUid, message.replyTo),
                                  message.sendingUid, false)) {
                              replyToMessage(message, WifiP2pManager.CONNECT_FAILED);
                              // remain at this state.
                              break;
                          }
                          if (mVerboseLoggingEnabled) logd(getName() + " sending connect");
                          WifiP2pConfig config = (WifiP2pConfig) message.obj;
  
                          boolean isConnectFailed = false;
                          if (isConfigValidAsGroup(config)) {
                              mAutonomousGroup = false;
                              mWifiNative.p2pStopFind();
                              if (mWifiNative.p2pGroupAdd(config, true)) {
                                  mWifiP2pMetrics.startConnectionEvent(
                                          P2pConnectionEvent.CONNECTION_FAST,
                                          config);
                                  transitionTo(mGroupNegotiationState);
                              } else {
                                  loge("Cannot join a group with config.");
                                  isConnectFailed = true;
                                  replyToMessage(message, WifiP2pManager.CONNECT_FAILED);
                              }
                          } else {
                              if (isConfigInvalid(config)) {
                                  loge("Dropping connect request " + config);
                                  isConnectFailed = true;
                                  replyToMessage(message, WifiP2pManager.CONNECT_FAILED);
                              } else {
                                  mAutonomousGroup = false;
                                  mWifiNative.p2pStopFind();
                                  if (reinvokePersistentGroup(config)) {
                                      mWifiP2pMetrics.startConnectionEvent(
                                              P2pConnectionEvent.CONNECTION_REINVOKE,
                                              config);
                                      transitionTo(mGroupNegotiationState);
                                  } else {
                                      mWifiP2pMetrics.startConnectionEvent(
                                              P2pConnectionEvent.CONNECTION_FRESH,
                                              config);
                                      transitionTo(mProvisionDiscoveryState);
                                  }
                              }
                          }
  
                          if (!isConnectFailed) {
                              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;
                          mWifiP2pMetrics.startConnectionEvent(
                                  P2pConnectionEvent.CONNECTION_FRESH,
                                  config);
                          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;
                          mWifiP2pMetrics.startConnectionEvent(
                                  P2pConnectionEvent.CONNECTION_FRESH,
                                  config);
                          transitionTo(mUserAuthorizingInviteRequestState);
                          break;
                      case WifiP2pMonitor.P2P_PROV_DISC_PBC_REQ_EVENT:
                      case WifiP2pMonitor.P2P_PROV_DISC_ENTER_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 WifiP2pMonitor.P2P_PROV_DISC_SHOW_PIN_EVENT:
                          if (message.obj == null) {
                              Log.e(TAG, "Illegal argument(s)");
                              break;
                          }
                          WifiP2pProvDiscEvent provDisc = (WifiP2pProvDiscEvent) message.obj;
                          WifiP2pDevice device = provDisc.device;
                          if (device == null) {
                              loge("Device entry is null");
                              break;
                          }
                          mSavedPeerConfig = new WifiP2pConfig();
                          mSavedPeerConfig.wps.setup = WpsInfo.KEYPAD;
                          mSavedPeerConfig.deviceAddress = device.deviceAddress;
                          mSavedPeerConfig.wps.pin = provDisc.pin;
  
                          notifyP2pProvDiscShowPinRequest(provDisc.pin, device.deviceAddress);
                          mPeers.updateStatus(device.deviceAddress, WifiP2pDevice.INVITED);
                          sendPeersChangedBroadcast();
                          transitionTo(mUserAuthorizingNegotiationRequestState);
                          break;
                      case WifiP2pManager.CREATE_GROUP:
                          if (!mWifiPermissionsUtil.checkCanAccessWifiDirect(
                                  getCallingPkgName(message.sendingUid, message.replyTo),
                                  message.sendingUid, false)) {
                              replyToMessage(message, WifiP2pManager.CREATE_GROUP_FAILED,
                                      WifiP2pManager.ERROR);
                              // remain at this state.
                              break;
                          }
                          mAutonomousGroup = true;
                          int netId = message.arg1;
                          config = (WifiP2pConfig) message.obj;
                          boolean ret = false;
                          if (config != null) {
                              if (isConfigValidAsGroup(config)) {
                                  mWifiP2pMetrics.startConnectionEvent(
                                          P2pConnectionEvent.CONNECTION_FAST,
                                          config);
                                  ret = mWifiNative.p2pGroupAdd(config, false);
                              } else {
                                  ret = false;
                              }
                          } else if (netId == WifiP2pGroup.PERSISTENT_NET_ID) {
                              // check if the go persistent group is present.
                              netId = mGroups.getNetworkId(mThisDevice.deviceAddress);
                              if (netId != -1) {
                                  mWifiP2pMetrics.startConnectionEvent(
                                          P2pConnectionEvent.CONNECTION_REINVOKE,
                                          null);
                                  ret = mWifiNative.p2pGroupAdd(netId);
                              } else {
                                  mWifiP2pMetrics.startConnectionEvent(
                                          P2pConnectionEvent.CONNECTION_LOCAL,
                                          null);
                                  ret = mWifiNative.p2pGroupAdd(true);
                              }
                          } else {
                              mWifiP2pMetrics.startConnectionEvent(
                                      P2pConnectionEvent.CONNECTION_LOCAL,
                                      null);
                              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 (mVerboseLoggingEnabled) 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 (!mWifiPermissionsUtil.checkNetworkSettingsPermission(
                                  message.sendingUid)) {
                              loge("Permission violation - no NETWORK_SETTING permission,"
                                      + " uid = " + message.sendingUid);
                              replyToMessage(message, WifiP2pManager.START_LISTEN_FAILED);
                              break;
                          }
                          if (mVerboseLoggingEnabled) 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 (!mWifiPermissionsUtil.checkNetworkSettingsPermission(
                                  message.sendingUid)) {
                              loge("Permission violation - no NETWORK_SETTING permission,"
                                      + " uid = " + message.sendingUid);
                              replyToMessage(message, WifiP2pManager.STOP_LISTEN_FAILED);
                              break;
                          }
                          if (mVerboseLoggingEnabled) 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 (mVerboseLoggingEnabled) {
                              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 (mVerboseLoggingEnabled) logd(getName());
                  sendMessageDelayed(obtainMessage(GROUP_CREATING_TIMED_OUT,
                          ++sGroupCreatingTimeoutIndex, 0), GROUP_CREATING_WAIT_TIME_MS);
              }
  
              @Override
              public boolean processMessage(Message message) {
                  if (mVerboseLoggingEnabled) logd(getName() + message.toString());
                  boolean ret = HANDLED;
                  switch (message.what) {
                      case GROUP_CREATING_TIMED_OUT:
                          if (sGroupCreatingTimeoutIndex == message.arg1) {
                              if (mVerboseLoggingEnabled) logd("Group negotiation timed out");
                              mWifiP2pMetrics.endConnectionEvent(
                                      P2pConnectionEvent.CLF_TIMEOUT);
                              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 (mVerboseLoggingEnabled) {
                                  logd("mSavedPeerConfig " + mSavedPeerConfig.deviceAddress
                                          + "device " + device.deviceAddress);
                              }
                              // Do the regular device lost handling
                              ret = NOT_HANDLED;
                              break;
                          }
                          // Do nothing
                          if (mVerboseLoggingEnabled) 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();
                          mWifiP2pMetrics.endConnectionEvent(
                                  P2pConnectionEvent.CLF_CANCEL);
                          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 (mVerboseLoggingEnabled) logd(getName());
                  if (mSavedPeerConfig.wps.setup == WpsInfo.PBC
                              || TextUtils.isEmpty(mSavedPeerConfig.wps.pin)) {
                      notifyInvitationReceived();
                  }
              }
  
              @Override
              public boolean processMessage(Message message) {
                  if (mVerboseLoggingEnabled) 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 (mVerboseLoggingEnabled) {
                              logd("User rejected negotiation " + mSavedPeerConfig);
                          }
                          transitionTo(mInactiveState);
                          break;
                      case PEER_CONNECTION_USER_CONFIRM:
                          mSavedPeerConfig.wps.setup = WpsInfo.DISPLAY;
                          mWifiNative.p2pConnect(mSavedPeerConfig, FORM_GROUP);
                          transitionTo(mGroupNegotiationState);
                          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 (mVerboseLoggingEnabled) logd(getName());
                  notifyInvitationReceived();
              }
  
              @Override
              public boolean processMessage(Message message) {
                  if (mVerboseLoggingEnabled) 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 (mVerboseLoggingEnabled) {
                              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 (mVerboseLoggingEnabled) logd(getName());
                  mWifiNative.p2pProvisionDiscovery(mSavedPeerConfig);
              }
  
              @Override
              public boolean processMessage(Message message) {
                  if (mVerboseLoggingEnabled) 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 (mVerboseLoggingEnabled) 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 (mVerboseLoggingEnabled) 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 (mVerboseLoggingEnabled) 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");
                          mWifiP2pMetrics.endConnectionEvent(
                                  P2pConnectionEvent.CLF_PROV_DISC_FAIL);
                          handleGroupCreationFailure();
                          transitionTo(mInactiveState);
                          break;
                      default:
                          return NOT_HANDLED;
                  }
                  return HANDLED;
              }
          }
  
          class GroupNegotiationState extends State {
              @Override
              public void enter() {
                  if (mVerboseLoggingEnabled) logd(getName());
              }
  
              @Override
              public boolean processMessage(Message message) {
                  if (mVerboseLoggingEnabled) 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 (mVerboseLoggingEnabled) 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 (mVerboseLoggingEnabled) 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);
                              startIpClient(mGroup.getInterface(), getHandler());
                              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 (mVerboseLoggingEnabled) logd(getName() + " go failure");
                          mWifiP2pMetrics.endConnectionEvent(
                                  P2pConnectionEvent.CLF_UNKNOWN);
                          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 (mVerboseLoggingEnabled) {
                                      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 {
                              mWifiP2pMetrics.endConnectionEvent(
                                      P2pConnectionEvent.CLF_INVITATION_FAIL);
                              handleGroupCreationFailure();
                              transitionTo(mInactiveState);
                          }
                          break;
                      default:
                          return NOT_HANDLED;
                  }
                  return HANDLED;
              }
          }
  
          class FrequencyConflictState extends State {
              private AlertDialog mFrequencyConflictDialog;
              @Override
              public void enter() {
                  if (mVerboseLoggingEnabled) 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 (mVerboseLoggingEnabled) 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
                          mWifiP2pMetrics.endConnectionEvent(
                                  P2pConnectionEvent.CLF_USER_REJECT);
                          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 ClientModeImpl, retry p2p
                          if (mVerboseLoggingEnabled) {
                              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 (mVerboseLoggingEnabled) 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();
                  }
  
                  mWifiP2pMetrics.endConnectionEvent(
                          P2pConnectionEvent.CLF_NONE);
                  mWifiP2pMetrics.startGroupEvent(mGroup);
              }
  
              @Override
              public boolean processMessage(Message message) {
                  if (mVerboseLoggingEnabled) 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 (mVerboseLoggingEnabled) logd(getName() + " ap sta connected");
                              sendPeersChangedBroadcast();
                              mWifiP2pMetrics.updateGroupEvent(mGroup);
                          } 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 (mVerboseLoggingEnabled) 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();
                                  }
                                  mWifiP2pMetrics.updateGroupEvent(mGroup);
                              } else {
                                  if (mVerboseLoggingEnabled) {
                                      logd("Failed to remove client " + deviceAddress);
                                  }
                                  for (WifiP2pDevice c : mGroup.getClientList()) {
                                      if (mVerboseLoggingEnabled) logd("client " + c.deviceAddress);
                                  }
                              }
                              sendPeersChangedBroadcast();
                              if (mVerboseLoggingEnabled) logd(getName() + " ap sta disconnected");
                          } else {
                              loge("Disconnect on unknown device: " + device);
                          }
                          break;
                      case IPC_PRE_DHCP_ACTION:
                          mWifiNative.setP2pPowerSave(mGroup.getInterface(), false);
                          try {
                              mIpClient.completedPreDhcpAction();
                          } catch (RemoteException e) {
                              e.rethrowFromSystemServer();
                          }
                          break;
                      case IPC_POST_DHCP_ACTION:
                          mWifiNative.setP2pPowerSave(mGroup.getInterface(), true);
                          break;
                      case IPC_DHCP_RESULTS:
                          mDhcpResults = (DhcpResults) message.obj;
                          break;
                      case IPC_PROVISIONING_SUCCESS:
                          if (mVerboseLoggingEnabled) 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 (Exception e) {
                              loge("Failed to add iface to local network " + e);
                          }
                          break;
                      case IPC_PROVISIONING_FAILURE:
                          loge("IP provisioning failed");
                          mWifiNative.p2pGroupRemove(mGroup.getInterface());
                          break;
                      case WifiP2pManager.REMOVE_GROUP:
                          if (mVerboseLoggingEnabled) 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 (mVerboseLoggingEnabled) 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 (mVerboseLoggingEnabled) logd("Add device to lost list " + device);
                          mPeersLostDuringConnection.updateSupplicantDetails(device);
                          return HANDLED;
                      case DISABLE_P2P:
                          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:
                          if (!mWifiPermissionsUtil.checkCanAccessWifiDirect(
                                  getCallingPkgName(message.sendingUid, message.replyTo),
                                  message.sendingUid, false)) {
                              replyToMessage(message, WifiP2pManager.CONNECT_FAILED);
                              // remain at this state.
                              break;
                          }
                          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 (mVerboseLoggingEnabled) {
                                      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;
                          }
  
                          // According to section 3.2.3 in SPEC, only GO can handle group join.
                          // Multiple groups is not supported, ignore this discovery for GC.
                          if (mGroup.isGroupOwner()) {
                              transitionTo(mUserAuthorizingJoinState);
                          } else {
                              if (mVerboseLoggingEnabled) logd("Ignore provision discovery for GC");
                          }
                          break;
                      case WifiP2pMonitor.P2P_GROUP_STARTED_EVENT:
                          loge("Duplicate group creation event notice, ignore");
                          break;
                      default:
                          return NOT_HANDLED;
                  }
                  return HANDLED;
              }
  
              public void exit() {
                  mWifiP2pMetrics.endGroupEvent();
                  updateThisDevice(WifiP2pDevice.AVAILABLE);
                  resetWifiP2pInfo();
                  mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, null, null);
                  sendP2pConnectionChangedBroadcast();
              }
          }
  
          class UserAuthorizingJoinState extends State {
              @Override
              public void enter() {
                  if (mVerboseLoggingEnabled) logd(getName());
                  notifyInvitationReceived();
              }
  
              @Override
              public boolean processMessage(Message message) {
                  if (mVerboseLoggingEnabled) 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 (mVerboseLoggingEnabled) 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 (mVerboseLoggingEnabled) logd(getName());
              }
  
              @Override
              public boolean processMessage(Message message) {
                  if (mVerboseLoggingEnabled) 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();
          }
  
          // Check & re-enable P2P if needed.
          // P2P interface will be created if all of the below are true:
          // a) Wifi is enabled.
          // b) HAL (HIDL) interface is available.
          // c) There is atleast 1 client app which invoked initialize().
          private void checkAndReEnableP2p() {
              boolean isHalInterfaceAvailable = isHalInterfaceAvailable();
              Log.d(TAG, "Wifi enabled=" + mIsWifiEnabled + ", P2P Interface availability="
                      + isHalInterfaceAvailable + ", Number of clients="
                      + mDeathDataByBinder.size());
              if (mIsWifiEnabled && isHalInterfaceAvailable
                      && !mDeathDataByBinder.isEmpty()) {
                  sendMessage(ENABLE_P2P);
              }
          }
  
          // Ignore judgement if the device do not support HAL (HIDL) interface
          private boolean isHalInterfaceAvailable() {
              return mWifiNative.isHalInterfaceSupported() ? mIsHalInterfaceAvailable : true;
          }
  
          private void checkAndSendP2pStateChangedBroadcast() {
              boolean isHalInterfaceAvailable = isHalInterfaceAvailable();
              Log.d(TAG, "Wifi enabled=" + mIsWifiEnabled + ", P2P Interface availability="
                      + isHalInterfaceAvailable);
              sendP2pStateChangedBroadcast(mIsWifiEnabled && isHalInterfaceAvailable);
          }
  
          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 (mVerboseLoggingEnabled) 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,
                      eraseOwnDeviceAddress(mThisDevice));
              mContext.sendBroadcastAsUserMultiplePermissions(intent, UserHandle.ALL,
                      RECEIVER_PERMISSIONS_FOR_BROADCAST);
          }
  
          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.sendBroadcastAsUserMultiplePermissions(intent, UserHandle.ALL,
                      RECEIVER_PERMISSIONS_FOR_BROADCAST);
          }
  
          private void sendP2pConnectionChangedBroadcast() {
              if (mVerboseLoggingEnabled) 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, eraseOwnDeviceAddress(mGroup));
              mContext.sendBroadcastAsUserMultiplePermissions(intent, UserHandle.ALL,
                      RECEIVER_PERMISSIONS_FOR_BROADCAST);
              if (mWifiChannel != null) {
                  mWifiChannel.sendMessage(WifiP2pServiceImpl.P2P_CONNECTION_CHANGED,
                          new NetworkInfo(mNetworkInfo));
              } else {
                  loge("sendP2pConnectionChangedBroadcast(): WifiChannel is null");
              }
          }
  
          private void sendP2pPersistentGroupsChangedBroadcast() {
              if (mVerboseLoggingEnabled) 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 (mVerboseLoggingEnabled) 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 notifyP2pProvDiscShowPinRequest(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.accept), new OnClickListener() {
                              public void onClick(DialogInterface dialog, int which) {
                                  sendMessage(PEER_CONNECTION_USER_CONFIRM);
                              }
                      })
                      .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 (mVerboseLoggingEnabled) {
                                      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 (mVerboseLoggingEnabled) logd(getName() + " ignore connect");
                                  sendMessage(PEER_CONNECTION_USER_REJECT);
                              }
                          })
                      .setOnCancelListener(new DialogInterface.OnCancelListener() {
                              @Override
                              public void onCancel(DialogInterface arg0) {
                                  if (mVerboseLoggingEnabled) 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 (mVerboseLoggingEnabled) logd("Enter pin section visible");
                      textEntryView.findViewById(R.id.enter_pin_section).setVisibility(View.VISIBLE);
                      break;
                  case WpsInfo.DISPLAY:
                      if (mVerboseLoggingEnabled) 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();
                  mWifiP2pMetrics.updatePersistentGroup(mGroups);
                  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;
          }
  
          /**
           * Check the network name complies standard SSID naming rules.
           *
           * The network name of a group is also the broadcasting SSID,
           * as a result, the network name must complies standard SSID naming
           * rules.
           */
          private boolean isValidNetworkName(String networkName) {
              if (TextUtils.isEmpty(networkName)) return false;
  
              byte[] ssidBytes = networkName.getBytes(StandardCharsets.UTF_8);
              if (ssidBytes.length < MIN_NETWORK_NAME_BYTES) return false;
              if (ssidBytes.length > MAX_NETWORK_NAME_BYTES) return false;
  
              return true;
          }
  
          /**
           * A config is valid as a group if it has network name and passphrase.
           * Supplicant can construct a group on the fly for creating a group with specified config
           * or join a group without negotiation and WPS.
           * @param WifiP2pConfig config to be validated
           * @return true if it is valid, false otherwise
           */
          private boolean isConfigValidAsGroup(WifiP2pConfig config) {
              if (config == null) return false;
              if (TextUtils.isEmpty(config.deviceAddress)) return false;
              if (isValidNetworkName(config.networkName)
                      && !TextUtils.isEmpty(config.passphrase)) {
                  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);
          }
  
          /**
           * Erase the MAC address of our interface if it is present in a given device, to prevent
           * apps from having access to persistent identifiers.
           *
           * @param device a device possibly having the same physical address as the wlan interface.
           * @return a copy of the input, possibly with the device address erased.
           */
          private WifiP2pDevice eraseOwnDeviceAddress(WifiP2pDevice device) {
              if (device == null) {
                  return null;
              }
              WifiP2pDevice result = new WifiP2pDevice(device);
              if (device.deviceAddress != null
                      && mThisDevice.deviceAddress != null
                      && device.deviceAddress.length() > 0
                      && mThisDevice.deviceAddress.equals(device.deviceAddress)) {
                  result.deviceAddress = ANONYMIZED_DEVICE_ADDRESS;
              }
              return result;
          }
  
          /**
           * Erase the MAC address of our interface if it is set as the device address for any of the
           * devices in a group.
           *
           * @param group a p2p group containing p2p devices.
           * @return a copy of the input, with any devices corresponding to our wlan interface having
           *      their device address erased.
           */
          private WifiP2pGroup eraseOwnDeviceAddress(WifiP2pGroup group) {
              if (group == null) {
                  return null;
              }
  
              WifiP2pGroup result = new WifiP2pGroup(group);
  
              // Create copies of the clients so they're not shared with the original object.
              for (WifiP2pDevice originalDevice : group.getClientList()) {
                  result.removeClient(originalDevice);
                  result.addClient(eraseOwnDeviceAddress(originalDevice));
              }
  
              WifiP2pDevice groupOwner = group.getOwner();
              result.setOwner(eraseOwnDeviceAddress(groupOwner));
  
              return result;
          }
  
          /**
           * Erase the MAC address of our interface if it is present in a given device, to prevent
           * apps from having access to persistent identifiers. If the requesting party holds the
           * {@link Manifest.permission.LOCAL_MAC_ADDRESS} permission, the address is not erased.
           *
           * @param device a device possibly having the same physical address as the wlan interface.
           * @param uid the user id of the app that requested the information.
           * @return a copy of the input, possibly with the device address erased.
           */
          private WifiP2pDevice maybeEraseOwnDeviceAddress(WifiP2pDevice device, int uid) {
              if (device == null) {
                  return null;
              }
              if (mWifiPermissionsUtil.checkLocalMacAddressPermission(uid)) {
                  // Calling app holds the LOCAL_MAC_ADDRESS permission, and is allowed to see this
                  // device's MAC.
                  return new WifiP2pDevice(device);
              } else {
                  return eraseOwnDeviceAddress(device);
              }
          }
  
          /**
           * Erase the MAC address of our interface if it is set as the device address for any of the
           * devices in a group. If the requesting party holds the
           * {@link Manifest.permission.LOCAL_MAC_ADDRESS} permission, the address is not erased.
           *
           * @param group a p2p group containing p2p devices.
           * @param uid the user id of the app that requested the information.
           * @return a copy of the input, with any devices corresponding to our wlan interface having
           *      their device address erased. If the requesting app holds the LOCAL_MAC_ADDRESS
           *      permission, this method returns a copy of the input.
           */
          private WifiP2pGroup maybeEraseOwnDeviceAddress(WifiP2pGroup group, int uid) {
              if (group == null) {
                  return null;
              }
              if (mWifiPermissionsUtil.checkLocalMacAddressPermission(uid)) {
                  // Calling app holds the LOCAL_MAC_ADDRESS permission, and is allowed to see this
                  // device's MAC.
                  return new WifiP2pGroup(group);
              } else {
                  return eraseOwnDeviceAddress(group);
              }
          }
  
          /**
           * Erase the MAC address of our interface if it is set as the device address for any of the
           * devices in a list of groups. If the requesting party holds the
           * {@link Manifest.permission.LOCAL_MAC_ADDRESS} permission, the address is not erased.
           *
           * @param groupList a list of p2p groups containing p2p devices.
           * @param uid the user id of the app that requested the information.
           * @return a copy of the input, with any devices corresponding to our wlan interface having
           *      their device address erased. If the requesting app holds the LOCAL_MAC_ADDRESS
           *      permission, this method returns a copy of the input.
           */
          private WifiP2pGroupList maybeEraseOwnDeviceAddress(WifiP2pGroupList groupList, int uid) {
              if (groupList == null) {
                  return null;
              }
              WifiP2pGroupList result = new WifiP2pGroupList();
              for (WifiP2pGroup group : groupList.getGroupList()) {
                  result.add(maybeEraseOwnDeviceAddress(group, uid));
              }
              return result;
          }
  
          /**
           * 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 (mVerboseLoggingEnabled) logd("target ssid is " + ssid + " join:" + join);
  
              if (join && dev.isGroupLimit()) {
                  if (mVerboseLoggingEnabled) 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 (mVerboseLoggingEnabled) {
                      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 (mVerboseLoggingEnabled) logd("Remove unknown network");
                  mGroups.remove(netId);
                  mWifiP2pMetrics.updatePersistentGroup(mGroups);
                  return true;
              }
  
              if (!isClientRemoved) {
                  // specified p2p client is not found. already removed.
                  return false;
              }
  
              if (mVerboseLoggingEnabled) 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 = mFrameworkFacade.getStringSetting(mContext,
                      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 = mFrameworkFacade.getSecureStringSetting(mContext,
                          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);
  
              mFrameworkFacade.setStringSetting(mContext,
                      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 (mVerboseLoggingEnabled) logd("DeviceAddress: " + mThisDevice.deviceAddress);
              mWifiNative.p2pFlush();
              mWifiNative.p2pServiceFlush();
              mServiceTransactionId = 0;
              mServiceDiscReqId = null;
  
              updatePersistentNetworks(RELOAD);
              enableVerboseLogging(mFrameworkFacade.getIntegerSetting(mContext,
                      Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED, 0));
          }
  
          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 (mVerboseLoggingEnabled) logd("stop IpClient");
                  stopIpClient();
                  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, false);
              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 (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 (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, false);
  
              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);
          }
  
          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();
          }
  
          private void clearClientInfo(Messenger m) {
              // update wpa_supplicant service info
              clearLocalServices(m);
              clearServiceRequests(m);
              // remove client from client list
              ClientInfo clientInfo = mClientInfoList.remove(m);
              if (clientInfo != null) {
                  logd("Client:" + clientInfo.mPackageName + " is removed");
              }
          }
  
          /**
           * 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 (mVerboseLoggingEnabled) 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 (mVerboseLoggingEnabled) 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 (mVerboseLoggingEnabled) 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(String pkgName, int uid) {
              // getPeers() is guaranteed to be invoked after Wifi Service is up
              // This ensures getInstance() will return a non-null object now
              if (mWifiPermissionsUtil.checkCanAccessWifiDirect(pkgName, uid, true)) {
                  return new WifiP2pDeviceList(mPeers);
              } else {
                  return new WifiP2pDeviceList();
              }
          }
  
          private void setPendingFactoryReset(boolean pending) {
              mFrameworkFacade.setIntegerSetting(mContext,
                      Settings.Global.WIFI_P2P_PENDING_FACTORY_RESET,
                      pending ? 1 : 0);
          }
  
          private boolean isPendingFactoryReset() {
              int val = mFrameworkFacade.getIntegerSetting(mContext,
                      Settings.Global.WIFI_P2P_PENDING_FACTORY_RESET,
                      0);
              return (val != 0);
          }
  
          /**
           * Enforces permissions on the caller who is requesting factory reset.
           * @param pkg Bundle containing the calling package string.
           * @param uid The caller uid.
           */
          private boolean factoryReset(int uid) {
              String pkgName = mContext.getPackageManager().getNameForUid(uid);
              UserManager userManager = mWifiInjector.getUserManager();
  
              if (!mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)) return false;
  
              if (userManager.hasUserRestriction(UserManager.DISALLOW_NETWORK_RESET)) return false;
  
              if (userManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI)) return false;
  
              Log.i(TAG, "factoryReset uid=" + uid + " pkg=" + pkgName);
  
              if (mNetworkInfo.isAvailable()) {
                  if (mWifiNative.p2pListNetworks(mGroups)) {
                      for (WifiP2pGroup group : mGroups.getGroupList()) {
                          mWifiNative.removeP2pNetwork(group.getNetworkId());
                      }
                  }
                  // reload will save native config and broadcast changed event.
                  updatePersistentNetworks(true);
                  setPendingFactoryReset(false);
              } else {
                  setPendingFactoryReset(true);
              }
              return true;
          }
  
          /**
          * Get calling package string from Client HashMap
          *
          * @param uid The uid of the caller package
          * @param replyMessenger AsyncChannel handler in caller
          */
          private String getCallingPkgName(int uid, Messenger replyMessenger) {
              ClientInfo clientInfo = mClientInfoList.get(replyMessenger);
              if (clientInfo != null) {
                  return clientInfo.mPackageName;
              }
              if (uid == Process.SYSTEM_UID) return mContext.getOpPackageName();
              return null;
  
          }
  
          /**
           * Clear all of p2p local service request/response for all p2p clients
           */
          private void clearServicesForAllClients() {
              for (ClientInfo c : mClientInfoList.values()) {
                  clearLocalServices(c.mMessenger);
                  clearServiceRequests(c.mMessenger);
              }
          }
      }
  
      /**
       * 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;
          private String mPackageName;
  
  
          // A service discovery request list.
          private SparseArray<WifiP2pServiceRequest> mReqList;
  
          // A local service information list.
          private List<WifiP2pServiceInfo> mServList;
  
          private ClientInfo(Messenger m) {
              mMessenger = m;
              mPackageName = null;
              mReqList = new SparseArray();
              mServList = new ArrayList<WifiP2pServiceInfo>();
          }
      }
  }
  