/* * 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 static com.android.server.wifi.WifiSettingsConfigStore.WIFI_P2P_DEVICE_NAME; import static com.android.server.wifi.WifiSettingsConfigStore.WIFI_P2P_PENDING_FACTORY_RESET; import static com.android.server.wifi.WifiSettingsConfigStore.WIFI_VERBOSE_LOGGING_ENABLED; import android.annotation.Nullable; 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.PackageInfo; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; import android.location.LocationManager; import android.net.ConnectivityManager; import android.net.DhcpResultsParcelable; import android.net.InetAddresses; import android.net.LinkProperties; import android.net.NetworkInfo; import android.net.NetworkStack; import android.net.TetheringManager; import android.net.ip.IIpClient; import android.net.ip.IpClientCallbacks; import android.net.ip.IpClientUtil; import android.net.shared.ProvisioningConfiguration; import android.net.wifi.CoexUnsafeChannel; import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; 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.Looper; import android.os.Message; import android.os.Messenger; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; import android.os.WorkSource; import android.provider.Settings; import android.text.TextUtils; import android.util.Log; 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.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.internal.util.WakeupMessage; import com.android.modules.utils.build.SdkLevel; import com.android.server.wifi.FrameworkFacade; import com.android.server.wifi.WifiGlobals; import com.android.server.wifi.WifiInjector; import com.android.server.wifi.WifiLog; import com.android.server.wifi.WifiSettingsConfigStore; import com.android.server.wifi.coex.CoexManager; import com.android.server.wifi.proto.nano.WifiMetricsProto.P2pConnectionEvent; import com.android.server.wifi.util.NetdWrapper; import com.android.server.wifi.util.StringUtil; 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 com.android.wifi.resources.R; import java.io.FileDescriptor; import java.io.PrintWriter; import java.net.Inet4Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; /** * 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"; @VisibleForTesting public static final String P2P_IDLE_SHUTDOWN_MESSAGE_TIMEOUT_TAG = TAG + " Idle Shutdown Message Timeout"; private boolean mVerboseLoggingEnabled = false; private static final String NETWORKTYPE = "WIFI_P2P"; @VisibleForTesting static final String DEFAULT_DEVICE_NAME_PREFIX = "Android_"; // The maxinum length of the device name is 32 bytes, see // Section 4.1.15 in Wi-Fi Direct Specification v1 and // Section 12 in Wi-Fi Protected Setup Specification v2. @VisibleForTesting static final int DEVICE_NAME_LENGTH_MAX = 32; @VisibleForTesting static final int DEVICE_NAME_POSTFIX_LENGTH_MIN = 4; @VisibleForTesting static final int DEVICE_NAME_PREFIX_LENGTH_MAX = DEVICE_NAME_LENGTH_MAX - DEVICE_NAME_POSTFIX_LENGTH_MIN; @VisibleForTesting static final int DEFAULT_GROUP_OWNER_INTENT = 6; @VisibleForTesting // It requires to over "DISCOVER_TIMEOUT_S(120)" or "GROUP_CREATING_WAIT_TIME_MS(120)". // Otherwise it will cause interface down before function timeout. static final long P2P_INTERFACE_IDLE_SHUTDOWN_TIMEOUT_MS = 150_000; private Context mContext; NetdWrapper mNetdWrapper; private IIpClient mIpClient; private int mIpClientStartIndex = 0; private DhcpResultsParcelable mDhcpResultsParcelable; 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 WifiSettingsConfigStore mSettingsConfigStore; private WifiP2pMetrics mWifiP2pMetrics; // This will only be null if SdkLevel is not at least S @Nullable private CoexManager mCoexManager; private WifiGlobals mWifiGlobals; 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 }; private static final String[] RECEIVER_PERMISSIONS_FOR_BROADCAST_LOCATION_OFF = { android.Manifest.permission.NETWORK_SETTINGS, 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; // idle shutdown message public static final int CMD_P2P_IDLE_SHUTDOWN = BASE + 19; // 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; private static final int GROUP_OWNER_TETHER_READY = BASE + 35; private static final int UPDATE_P2P_DISALLOWED_CHANNELS = BASE + 36; public static final int ENABLED = 1; public static final int DISABLED = 0; private static final int P2P_CONNECT_TRIGGER_GROUP_NEG_REQ = 1; private static final int P2P_CONNECT_TRIGGER_INVITATION_REQ = 2; private static final int P2P_CONNECT_TRIGGER_OTHER = 3; private final boolean mP2pSupported; private final 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.DetailedState mDetailedState; private boolean mTemporarilyDisconnectedWifi = false; // The transaction Id of service discovery request private int mServiceTransactionId = 0; // Service discovery request ID of wpa_supplicant. // null means it's not set yet. private String mServiceDiscReqId; // clients(application) information list private HashMap mClientInfoList = new HashMap(); // clients(application) channel list private Map mClientChannelList = new HashMap(); // 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"; // Idle shut down @VisibleForTesting public WakeupMessage mP2pIdleShutdownMessage; /** * 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: Log.d(TAG, "ClientHandler.handleMessage ignoring msg=" + msg); break; } } } private ClientHandler mClientHandler; private NetworkInfo makeNetworkInfo() { final NetworkInfo info = new NetworkInfo(ConnectivityManager.TYPE_WIFI_P2P, 0, NETWORKTYPE, ""); if (mDetailedState != NetworkInfo.DetailedState.IDLE) { info.setDetailedState(mDetailedState, null, null); } return info; } /** * 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, WorkSource ws) { mDeathRecipient = dr; mMessenger = m; mWorkSource = ws; } @Override public String toString() { return "deathRecipient=" + mDeathRecipient + ", messenger=" + mMessenger + ", worksource=" + mWorkSource; } final DeathRecipient mDeathRecipient; final Messenger mMessenger; final WorkSource mWorkSource; } private Object mLock = new Object(); private final Map mDeathDataByBinder = new ConcurrentHashMap<>(); public WifiP2pServiceImpl(Context context, WifiInjector wifiInjector) { mContext = context; mWifiInjector = wifiInjector; mWifiPermissionsUtil = mWifiInjector.getWifiPermissionsUtil(); mFrameworkFacade = mWifiInjector.getFrameworkFacade(); mSettingsConfigStore = mWifiInjector.getSettingsConfigStore(); mWifiP2pMetrics = mWifiInjector.getWifiP2pMetrics(); mCoexManager = mWifiInjector.getCoexManager(); mWifiGlobals = mWifiInjector.getWifiGlobals(); mDetailedState = NetworkInfo.DetailedState.IDLE; mP2pSupported = mContext.getPackageManager().hasSystemFeature( PackageManager.FEATURE_WIFI_DIRECT); 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() { mNetdWrapper = mWifiInjector.makeNetdWrapper(); } 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 boolean checkAnyPermissionOf(String... permissions) { for (String permission : permissions) { if (mContext.checkCallingOrSelfPermission(permission) == PackageManager.PERMISSION_GRANTED) { return true; } } return false; } private void enforceAnyPermissionOf(String... permissions) { if (!checkAnyPermissionOf(permissions)) { throw new SecurityException("Requires one of the following permissions: " + String.join(", ", permissions) + "."); } } private void enforceNetworkStackOrLocationHardwarePermission() { enforceAnyPermissionOf( android.Manifest.permission.LOCATION_HARDWARE, android.Manifest.permission.NETWORK_STACK, NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK); } private void stopIpClient() { // Invalidate all previous start requests mIpClientStartIndex++; if (mIpClient != null) { try { mIpClient.shutdown(); } catch (RemoteException e) { e.rethrowFromSystemServer(); } mIpClient = null; } mDhcpResultsParcelable = 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(DhcpResultsParcelable 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, final String packageName) { 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); }; WorkSource ws = packageName != null ? new WorkSource(Binder.getCallingUid(), packageName) : new WorkSource(Binder.getCallingUid()); try { binder.linkToDeath(dr, 0); mDeathDataByBinder.put(binder, new DeathHandlerData(dr, messenger, ws)); } catch (RemoteException e) { Log.e(TAG, "Error on linkToDeath: e=" + e); // fall-through here - won't clean up } // If p2p is already on, send ENABLE_P2P to merge the new worksource. // If p2p is off, the first one activates P2P will merge all worksources. if (!mP2pStateMachine.isP2pDisabled()) { 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() { enforceNetworkStackOrLocationHardwarePermission(); 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) { 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("mDetailedState " + mDetailedState); 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; private List mCoexUnsafeChannels = new ArrayList<>(); private int mUserListenChannel = 0; private int mUserOperatingChannel = 0; // 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 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) { // Init p2p idle shutdown message mP2pIdleShutdownMessage = new WakeupMessage(mContext, this.getHandler(), P2P_IDLE_SHUTDOWN_MESSAGE_TIMEOUT_TAG, CMD_P2P_IDLE_SHUTDOWN); // 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; } 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 tethering state mContext.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (mGroup == null) return; if (!mGroup.isGroupOwner()) return; if (TextUtils.isEmpty(mGroup.getInterface())) return; final ArrayList interfaces = intent.getStringArrayListExtra( TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY); if (interfaces.contains(mGroup.getInterface())) { sendMessage(GROUP_OWNER_TETHER_READY); } } }, new IntentFilter(TetheringManager.ACTION_TETHER_STATE_CHANGED)); mSettingsConfigStore.registerChangeListener( WIFI_VERBOSE_LOGGING_ENABLED, (key, newValue) -> enableVerboseLogging(newValue), getHandler()); if (SdkLevel.isAtLeastS()) { mCoexManager.registerCoexListener(new CoexManager.CoexListener() { @Override public void onCoexUnsafeChannelsChanged() { checkCoexUnsafeChannels(); } }); } } } boolean isP2pDisabled() { return getCurrentState() == mP2pDisabledState; } void scheduleIdleShutdown() { if (mP2pIdleShutdownMessage != null) { mP2pIdleShutdownMessage.cancel(); mP2pIdleShutdownMessage.schedule(SystemClock.elapsedRealtime() + P2P_INTERFACE_IDLE_SHUTDOWN_TIMEOUT_MS); if (mVerboseLoggingEnabled) { Log.d(TAG, "IdleShutDown message (re)scheduled in " + (P2P_INTERFACE_IDLE_SHUTDOWN_TIMEOUT_MS / 1000) + "s"); } } mP2pStateMachine.getHandler().removeMessages(CMD_P2P_IDLE_SHUTDOWN); } void cancelIdleShutdown() { if (mP2pIdleShutdownMessage != null) { mP2pIdleShutdownMessage.cancel(); if (mVerboseLoggingEnabled) { Log.d(TAG, "IdleShutDown message canceled"); } } mP2pStateMachine.getHandler().removeMessages(CMD_P2P_IDLE_SHUTDOWN); } void checkCoexUnsafeChannels() { List unsafeChannels = null; // If WIFI DIRECT bit is not set, pass null to clear unsafe channels. if (SdkLevel.isAtLeastS() && (mCoexManager.getCoexRestrictions() & WifiManager.COEX_RESTRICTION_WIFI_DIRECT) != 0) { unsafeChannels = mCoexManager.getCoexUnsafeChannels(); Log.d(TAG, "UnsafeChannels: " + unsafeChannels.stream() .map(Object::toString) .collect(Collectors.joining(","))); } sendMessage(UPDATE_P2P_DISALLOWED_CHANNELS, unsafeChannels); } /** * Enable verbose logging for all sub modules. */ private void enableVerboseLogging(boolean verbose) { mVerboseLoggingEnabled = verbose; mWifiNative.enableVerboseLogging(verbose ? 1 : 0); mWifiMonitor.enableVerboseLogging(verbose ? 1 : 0); } 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); } private WorkSource createMergedRequestorWs() { WorkSource requestorWs = new WorkSource(); for (DeathHandlerData deathHandlerData : mDeathDataByBinder.values()) { requestorWs.add(deathHandlerData.mWorkSource); } return requestorWs; } 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 && mWifiChannel != null) { mWifiChannel.replyToMessage(message, message.arg2); } 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), getCallingFeatureId(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), getCallingFeatureId(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: if (!checkNetworkSettingsOrNetworkStackOrReadWifiCredentialPermission( message.sendingUid)) { loge("Permission violation - none of NETWORK_SETTING, NETWORK_STACK," + " or READ_WIFI_CREDENTIAL permission, uid = " + message.sendingUid); replyToMessage(message, WifiP2pManager.RESPONSE_PERSISTENT_GROUP_INFO, new WifiP2pGroupList()); break; } 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 ? 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, makeNetworkInfo()); 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 GROUP_OWNER_TETHER_READY: case UPDATE_P2P_DISALLOWED_CHANNELS: 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); String featureId = bundle.getString(WifiP2pManager.CALLING_FEATURE_ID); 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; clientInfo.mFeatureId = featureId; } break; case WifiP2pManager.REQUEST_DEVICE_INFO: if (!mWifiPermissionsUtil.checkCanAccessWifiDirect( getCallingPkgName(message.sendingUid, message.replyTo), getCallingFeatureId(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()); mInterfaceName = null; // reset iface name on disable. } 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); } } private boolean setupInterface() { if (!mIsWifiEnabled) { Log.e(TAG, "Ignore P2P enable since wifi is " + mIsWifiEnabled); return false; } WorkSource requestorWs = createMergedRequestorWs(); mInterfaceName = mWifiNative.setupInterface((String ifaceName) -> { sendMessage(DISABLE_P2P); checkAndSendP2pStateChangedBroadcast(); }, getHandler(), requestorWs); if (mInterfaceName == null) { Log.e(TAG, "Failed to setup interface for P2P"); return false; } setupInterfaceFeatures(mInterfaceName); try { mNetdWrapper.setInterfaceUp(mInterfaceName); } catch (IllegalStateException ie) { loge("Unable to change interface settings: " + ie); } registerForWifiMonitorEvents(); return true; } @Override public boolean processMessage(Message message) { if (mVerboseLoggingEnabled) logd(getName() + message.toString()); switch (message.what) { case ENABLE_P2P: if (setupInterface()) { 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: // only handle commands from clients and only commands // which require P2P to be active. if (message.what < Protocol.BASE_WIFI_P2P_MANAGER || Protocol.BASE_WIFI_P2P_SERVICE <= message.what || message.what == WifiP2pManager.UPDATE_CHANNEL_INFO) { return NOT_HANDLED; } // If P2P is not ready, it might be disabled due // to another interface, ex. turn on softap from // the quicksettings. // As the new priority scheme, the foreground app // might be able to use P2P, so just try to enable // it. // Check & re-enable P2P if needed. // P2P interface will be created if all of the below are true: // a) Wifi is enabled. // b) There is at least 1 client app which invoked initialize(). if (mVerboseLoggingEnabled) { Log.d(TAG, "Wifi enabled=" + mIsWifiEnabled + ", Number of clients=" + mDeathDataByBinder.size()); } if (!mIsWifiEnabled) return NOT_HANDLED; if (mDeathDataByBinder.isEmpty()) return NOT_HANDLED; if (!setupInterface()) return NOT_HANDLED; deferMessage(message); transitionTo(mInactiveState); break; } return HANDLED; } } class P2pEnabledState extends State { @Override public void enter() { if (mVerboseLoggingEnabled) logd(getName()); if (isPendingFactoryReset()) { factoryReset(Process.SYSTEM_UID); } checkCoexUnsafeChannels(); 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: if (!mWifiNative.replaceRequestorWs(createMergedRequestorWs())) { Log.e(TAG, "Failed to replace requestorWs"); } 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); if (!mWifiNative.replaceRequestorWs(createMergedRequestorWs())) { Log.e(TAG, "Failed to replace requestorWs"); } break; case WifiP2pManager.SET_DEVICE_NAME: { if (!checkNetworkSettingsOrNetworkStackOrOverrideWifiConfigPermission( message.sendingUid)) { loge("Permission violation - none of NETWORK_SETTING, NETWORK_STACK," + " or OVERRIDE_WIFI_CONFIG permission, uid = " + message.sendingUid); replyToMessage(message, WifiP2pManager.SET_DEVICE_NAME_FAILED, WifiP2pManager.ERROR); break; } 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; if (mWifiNative.p2pFind(DISCOVER_TIMEOUT_S)) { sendP2pDiscoveryChangedBroadcast(true); } } if (blocked && mWifiChannel != null) { mWifiChannel.replyToMessage(message, message.arg2); } break; case WifiP2pManager.DISCOVER_PEERS: if (!mWifiPermissionsUtil.checkCanAccessWifiDirect( getCallingPkgName(message.sendingUid, message.replyTo), getCallingFeatureId(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), getCallingFeatureId(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)) { sendP2pDiscoveryChangedBroadcast(true); 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), getCallingFeatureId(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 sdRespList = (List) 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 (!checkNetworkSettingsOrNetworkStackOrOverrideWifiConfigPermission( message.sendingUid)) { loge("Permission violation - none of NETWORK_SETTING, NETWORK_STACK," + " or OVERRIDE_WIFI_CONFIG permission, uid = " + message.sendingUid); replyToMessage(message, WifiP2pManager.DELETE_PERSISTENT_GROUP_FAILED, WifiP2pManager.ERROR); break; } 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: if (!checkNetworkSettingsOrNetworkStackOrOverrideWifiConfigPermission( message.sendingUid)) { loge("Permission violation - none of NETWORK_SETTING, NETWORK_STACK," + " or OVERRIDE_WIFI_CONFIG permission, uid = " + message.sendingUid); replyToMessage(message, WifiP2pManager.SET_CHANNEL_FAILED, WifiP2pManager.ERROR); break; } if (message.obj == null) { Log.e(TAG, "Illegal arguments(s)"); break; } Bundle p2pChannels = (Bundle) message.obj; mUserListenChannel = p2pChannels.getInt("lc", 0); mUserOperatingChannel = p2pChannels.getInt("oc", 0); if (updateP2pChannels()) { 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; case UPDATE_P2P_DISALLOWED_CHANNELS: mCoexUnsafeChannels.clear(); if (null != message.obj) { mCoexUnsafeChannels.addAll((List) message.obj); } updateP2pChannels(); break; default: return NOT_HANDLED; } return HANDLED; } @Override public void exit() { sendP2pDiscoveryChangedBroadcast(false); mUserListenChannel = 0; mUserOperatingChannel = 0; mCoexUnsafeChannels.clear(); } } class InactiveState extends State { @Override public void enter() { if (mVerboseLoggingEnabled) logd(getName()); mSavedPeerConfig.invalidate(); mDetailedState = NetworkInfo.DetailedState.IDLE; scheduleIdleShutdown(); } @Override public void exit() { cancelIdleShutdown(); } @Override public boolean processMessage(Message message) { if (mVerboseLoggingEnabled) logd(getName() + message.toString()); // Re-schedule the shutdown timer since we got the new operation. // only handle commands from clients. if (message.what > Protocol.BASE_WIFI_P2P_MANAGER && message.what < Protocol.BASE_WIFI_P2P_SERVICE) { scheduleIdleShutdown(); } switch (message.what) { case WifiP2pManager.CONNECT: if (!mWifiPermissionsUtil.checkCanAccessWifiDirect( getCallingPkgName(message.sendingUid, message.replyTo), getCallingFeatureId(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, false)) { 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 CMD_P2P_IDLE_SHUTDOWN: Log.d(TAG, "IdleShutDown message received"); sendMessage(DISABLE_P2P); 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), getCallingFeatureId(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.NETWORK_ID_PERSISTENT) { // 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.NETWORK_ID_PERSISTENT) { 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 (!checkNetworkSettingsOrNetworkStackOrOverrideWifiConfigPermission( message.sendingUid)) { loge("Permission violation - none of NETWORK_SETTING, NETWORK_STACK," + " or OVERRIDE_WIFI_CONFIG permission, uid = " + message.sendingUid); replyToMessage(message, WifiP2pManager.SET_CHANNEL_FAILED, WifiP2pManager.ERROR); break; } if (message.obj == null) { Log.e(TAG, "Illegal arguments(s)"); break; } Bundle p2pChannels = (Bundle) message.obj; mUserListenChannel = p2pChannels.getInt("lc", 0); mUserOperatingChannel = p2pChannels.getInt("oc", 0); if (updateP2pChannels()) { 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, P2P_CONNECT_TRIGGER_GROUP_NEG_REQ); 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; mSavedPeerConfig.groupOwnerIntent = selectGroupOwnerIntentIfNecessary(mSavedPeerConfig); 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, true)) { // Do negotiation when persistence fails p2pConnectWithPinDisplay(mSavedPeerConfig, P2P_CONNECT_TRIGGER_INVITATION_REQ); } 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, P2P_CONNECT_TRIGGER_OTHER); 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, P2P_CONNECT_TRIGGER_OTHER); 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, P2P_CONNECT_TRIGGER_OTHER); 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.NETWORK_ID_PERSISTENT) { // 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); } // {@link com.android.server.connectivity.Tethering} listens to // {@link WifiP2pManager#WIFI_P2P_CONNECTION_CHANGED_ACTION} // events and takes over the DHCP server management automatically. // Because tethering service introduces random IP range, P2P could not // hard-coded group owner IP and needs to wait for tethering completion. // As a result, P2P sends a unicast intent to tether service to trigger // the whole flow before entering GroupCreatedState. setWifiP2pInfoOnGroupFormation(null); if (!sendP2pTetherRequestBroadcast()) { loge("Cannot start tethering, remove " + mGroup); mWifiNative.p2pGroupRemove(mGroup.getInterface()); } break; } 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 GROUP_OWNER_TETHER_READY: if (mGroup != null && mGroup.isGroupOwner()) { Log.d(TAG, "tether " + mGroup.getInterface() + " ready"); 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.NETWORK_ID_PERSISTENT; p2pConnectWithPinDisplay(mSavedPeerConfig, P2P_CONNECT_TRIGGER_OTHER); } 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.NETWORK_ID_PERSISTENT; p2pConnectWithPinDisplay(mSavedPeerConfig, P2P_CONNECT_TRIGGER_OTHER); } else if (status == P2pStatus.NO_COMMON_CHANNEL) { transitionTo(mFrequencyConflictState); } else { mWifiP2pMetrics.endConnectionEvent( P2pConnectionEvent.CLF_INVITATION_FAIL); handleGroupCreationFailure(); transitionTo(mInactiveState); } break; case WifiP2pMonitor.AP_STA_CONNECTED_EVENT: case WifiP2pMonitor.AP_STA_DISCONNECTED_EVENT: // Group owner needs to wait for tethering completion before // moving to GroupCreatedState. If native layer reports STA event // earlier, defer it. if (mGroup != null && mGroup.isGroupOwner()) { deferMessage(message); 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 = mContext.getResources(); AlertDialog dialog = mFrameworkFacade.makeAlertDialogBuilder(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); dialog.getWindow().addSystemFlags( WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS); 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(); mDetailedState = NetworkInfo.DetailedState.CONNECTED; updateThisDevice(WifiP2pDevice.CONNECTED); // DHCP server has already been started if I am a group owner if (mGroup.isGroupOwner()) { Inet4Address addr = getInterfaceAddress(mGroup.getInterface()); if (addr != null) { setWifiP2pInfoOnGroupFormation(addr.getHostAddress()); Log.d(TAG, "Group owner address: " + addr.getHostAddress() + " at " + mGroup.getInterface()); } else { mWifiNative.p2pGroupRemove(mGroup.getInterface()); } } // 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: mDhcpResultsParcelable = (DhcpResultsParcelable) message.obj; if (mDhcpResultsParcelable == null) { break; } if (mVerboseLoggingEnabled) { logd("mDhcpResultsParcelable: " + mDhcpResultsParcelable); } setWifiP2pInfoOnGroupFormation(mDhcpResultsParcelable.serverAddress); try { final String ifname = mGroup.getInterface(); if (mDhcpResultsParcelable != null) { mNetdWrapper.addInterfaceToLocalNetwork( ifname, mDhcpResultsParcelable.baseConfiguration.getRoutes(ifname)); } } catch (Exception e) { loge("Failed to add iface to local network " + e); } sendP2pConnectionChangedBroadcast(); break; case IPC_PROVISIONING_SUCCESS: 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), getCallingFeatureId(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; case WifiP2pManager.CANCEL_CONNECT: mWifiNative.p2pCancelConnect(); mWifiP2pMetrics.endConnectionEvent( P2pConnectionEvent.CLF_CANCEL); ArrayList invitingPeers = new ArrayList<>(); mPeers.getDeviceList().forEach(dev -> { if (dev.status == WifiP2pDevice.INVITED) { invitingPeers.add(dev); } }); if (mPeers.remove(new WifiP2pDeviceList(invitingPeers))) { sendPeersChangedBroadcast(); } replyToMessage(message, WifiP2pManager.CANCEL_CONNECT_SUCCEEDED); break; default: return NOT_HANDLED; } return HANDLED; } public void exit() { // The group is still there and handling incoming request, // no need to update P2P connection information. if (mGroup != null) return; mWifiP2pMetrics.endGroupEvent(); updateThisDevice(WifiP2pDevice.AVAILABLE); resetWifiP2pInfo(); mDetailedState = NetworkInfo.DetailedState.DISCONNECTED; sendP2pConnectionChangedBroadcast(); // When location mode is off, tethering service cannot receive regular // p2p connection changed event to stop tethering, send a // dedicated update to stop it. if (!mWifiPermissionsUtil.isLocationModeEnabled()) { sendP2pTetherRequestBroadcast(); } } } 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(); } private void checkAndSendP2pStateChangedBroadcast() { Log.d(TAG, "Wifi enabled=" + mIsWifiEnabled); sendP2pStateChangedBroadcast(mIsWifiEnabled); } 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 sendBroadcastMultiplePermissions(Intent intent) { Context context = mContext.createContextAsUser(UserHandle.ALL, 0); String[] permissions = RECEIVER_PERMISSIONS_FOR_BROADCAST; if (!mWifiPermissionsUtil.isLocationModeEnabled()) { permissions = RECEIVER_PERMISSIONS_FOR_BROADCAST_LOCATION_OFF; } context.sendBroadcastWithMultiplePermissions( intent, permissions); } 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)); sendBroadcastMultiplePermissions(intent); } 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); sendBroadcastMultiplePermissions(intent); } 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, makeNetworkInfo()); intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP, eraseOwnDeviceAddress(mGroup)); sendBroadcastMultiplePermissions(intent); if (mWifiChannel != null) { mWifiChannel.sendMessage(WifiP2pServiceImpl.P2P_CONNECTION_CHANGED, makeNetworkInfo()); } else { loge("sendP2pConnectionChangedBroadcast(): WifiChannel is null"); } } private boolean isPackageExisted(String pkgName) { PackageManager pm = mContext.getPackageManager(); try { PackageInfo info = pm.getPackageInfo(pkgName, PackageManager.GET_META_DATA); } catch (PackageManager.NameNotFoundException e) { return false; } return true; } private String findTetheringServicePackage() { ArrayList possiblePackageNames = new ArrayList<>(); // AOSP possiblePackageNames.add("com.android.networkstack.tethering"); // mainline release possiblePackageNames.add("com.google.android.networkstack.tethering"); // Android Go possiblePackageNames.add("com.android.networkstack.tethering.inprocess"); for (String pkgName: possiblePackageNames) { if (isPackageExisted(pkgName)) { Log.d(TAG, "Tethering service package: " + pkgName); return pkgName; } } Log.w(TAG, "Cannot find tethering service package!"); return null; } private boolean sendP2pTetherRequestBroadcast() { String tetheringServicePackage = findTetheringServicePackage(); if (TextUtils.isEmpty(tetheringServicePackage)) return false; Log.i(TAG, "sending p2p tether request broadcast to " + tetheringServicePackage); Intent intent = new Intent(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); intent.setPackage(tetheringServicePackage); 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, makeNetworkInfo()); intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP, eraseOwnDeviceAddress(mGroup)); Context context = mContext.createContextAsUser(UserHandle.ALL, 0); context.sendBroadcastWithMultiplePermissions( intent, RECEIVER_PERMISSIONS_FOR_BROADCAST); return true; } private void sendP2pPersistentGroupsChangedBroadcast() { if (mVerboseLoggingEnabled) logd("sending p2p persistent groups changed broadcast"); Intent intent = new Intent(WifiP2pManager.ACTION_WIFI_P2P_PERSISTENT_GROUPS_CHANGED); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); } private void addRowToDialog(ViewGroup group, int stringId, String value) { Resources r = mContext.getResources(); View row = LayoutInflater.from(mContext).cloneInContext(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 = mContext.getResources(); final View textEntryView = LayoutInflater.from(mContext).cloneInContext(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 = mFrameworkFacade.makeAlertDialogBuilder(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); dialog.getWindow().addSystemFlags( WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS); dialog.show(); } private void notifyP2pProvDiscShowPinRequest(String pin, String peerAddress) { Resources r = mContext.getResources(); final View textEntryView = LayoutInflater.from(mContext).cloneInContext(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 = mFrameworkFacade.makeAlertDialogBuilder(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); dialog.getWindow().addSystemFlags( WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS); dialog.show(); } private void notifyInvitationReceived() { Resources r = mContext.getResources(); final WpsInfo wps = mSavedPeerConfig.wps; final View textEntryView = LayoutInflater.from(mContext).cloneInContext(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 = mFrameworkFacade.makeAlertDialogBuilder(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); dialog.getWindow().addSystemFlags( WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS); dialog.show(); } /** * This method unifies the persisent group list, cleans up unused * networks and if required, updates corresponding broadcast receivers * @param reload 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 (group.getOwner() == null) { Log.d(TAG, "group.getOwner() null"); continue; } if (Objects.equals(mThisDevice.deviceAddress, 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, int triggerType) { if (config == null) { Log.e(TAG, "Illegal argument(s)"); return; } WifiP2pDevice dev = fetchCurrentDeviceDetails(config); if (dev == null) { Log.e(TAG, "Invalid device"); return; } config.groupOwnerIntent = selectGroupOwnerIntentIfNecessary(config); boolean action; if (triggerType == P2P_CONNECT_TRIGGER_GROUP_NEG_REQ) { // If this is called from the GO negotiation path, the sender initiated // a group negotiation. action = FORM_GROUP; } else if (triggerType == P2P_CONNECT_TRIGGER_INVITATION_REQ) { // The group owner won't report it is a Group Owner always. // If this is called from the invitation path, the sender should be in // a group, and the target should be a group owner. action = JOIN_GROUP; } else { action = dev.isGroupOwner() ? JOIN_GROUP : FORM_GROUP; } String pin = mWifiNative.p2pConnect(config, action); 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, boolean isInvited) { 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; } // The group owner won't report it is a Group Owner always. // If this is called from the invitation path, the sender should be in // a group, and the target should be a group owner. boolean join = dev.isGroupOwner() || isInvited; 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 (isInvited && netId < 0) { netId = mGroups.getNetworkId(dev.deviceAddress); } 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.NETWORK_ID_PERSISTENT; 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 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 Inet4Address getInterfaceAddress(String interfaceName) { NetworkInterface iface; try { iface = NetworkInterface.getByName(interfaceName); } catch (SocketException ex) { Log.w(TAG, "Could not obtain address of network interface " + interfaceName, ex); return null; } Enumeration addrs = iface.getInetAddresses(); while (addrs.hasMoreElements()) { InetAddress addr = addrs.nextElement(); if (addr instanceof Inet4Address) { return (Inet4Address) addr; } } Log.w(TAG, "Could not obtain address of network interface " + interfaceName + " because it had no IPv4 addresses."); return null; } private void setWifiP2pInfoOnGroupFormation(String serverAddress) { InetAddress serverInetAddress = serverAddress == null ? null : InetAddresses.parseNumericAddress(serverAddress); 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 = mSettingsConfigStore.get(WIFI_P2P_DEVICE_NAME); if (null != deviceName) return deviceName; String prefix = mWifiGlobals.getWifiP2pDeviceNamePrefix(); if (DEVICE_NAME_PREFIX_LENGTH_MAX < prefix.getBytes(StandardCharsets.UTF_8).length || 0 == prefix.getBytes(StandardCharsets.UTF_8).length) { logw("The length of default device name prefix is invalid" + ", fallback to default name."); prefix = DEFAULT_DEVICE_NAME_PREFIX; } // The length of remaining bytes is at least {@link #DEVICE_NAME_POSTFIX_LENGTH_MIN}. int remainingBytes = DEVICE_NAME_LENGTH_MAX - prefix.getBytes(StandardCharsets.UTF_8).length; int numDigits = mWifiGlobals.getWifiP2pDeviceNamePostfixNumDigits(); if (numDigits > remainingBytes) { logw("The postfix length exceeds the remaining byte number" + ", use the smaller one."); numDigits = remainingBytes; } String postfix; if (numDigits >= DEVICE_NAME_POSTFIX_LENGTH_MIN) { postfix = StringUtil.generateRandomNumberString(numDigits); } else { // 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); postfix = id.substring(0, 4); } logd("the default device name: " + prefix + postfix); return prefix + postfix; } 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); mSettingsConfigStore.put(WIFI_P2P_DEVICE_NAME, devName); sendThisDeviceChangedBroadcast(); return true; } private boolean setWfdInfo(WifiP2pWfdInfo wfdInfo) { final boolean enabled = wfdInfo.isEnabled(); boolean success; if (!mWifiNative.setWfdEnable(enabled)) { loge("Failed to set wfd enable: " + enabled); return false; } if (enabled) { if (!mWifiNative.setWfdDeviceInfo(wfdInfo.getDeviceInfoHex())) { loge("Failed to set wfd properties"); return false; } if (!setWfdR2InfoIfNecessary(wfdInfo)) { loge("Failed to set wfd r2 properties"); return false; } } mThisDevice.wfdInfo = wfdInfo; sendThisDeviceChangedBroadcast(); return true; } private boolean setWfdR2InfoIfNecessary(WifiP2pWfdInfo wfdInfo) { if (!SdkLevel.isAtLeastS()) return true; if (!wfdInfo.isR2Supported()) return true; return mWifiNative.setWfdR2DeviceInfo(wfdInfo.getR2DeviceInfoHex()); } private void initializeP2pSettings() { mThisDevice.deviceName = getPersistedDeviceName(); mThisDevice.primaryDeviceType = mContext.getResources().getString( R.string.config_wifi_p2p_device_type); 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(mSettingsConfigStore.get(WIFI_VERBOSE_LOGGING_ENABLED)); } private void updateThisDevice(int status) { mThisDevice.status = status; sendThisDeviceChangedBroadcast(); } private void handleGroupCreationFailure() { // A group is formed, but the tethering request is not proceed. if (null != mGroup) { // 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); mWifiNative.p2pGroupRemove(mGroup.getInterface()); mGroup = null; } resetWifiP2pInfo(); mDetailedState = NetworkInfo.DetailedState.FAILED; 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()) { // {@link com.android.server.connectivity.Tethering} listens to // {@link WifiP2pManager#WIFI_P2P_CONNECTION_CHANGED_ACTION} // events and takes over the DHCP server management automatically. } else { if (mVerboseLoggingEnabled) logd("stop IpClient"); stopIpClient(); try { mNetdWrapper.removeInterfaceFromLocalNetwork(mGroup.getInterface()); } catch (IllegalStateException e) { loge("Failed to remove iface from local network " + e); } } try { mNetdWrapper.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) { Log.d(TAG, s); } @Override protected void loge(String s) { Log.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 1 byte and non-zero. if (mServiceTransactionId == 256) mServiceTransactionId = 1; 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 deadClients = new ArrayList(); for (ClientInfo c : mClientInfoList.values()) { Message msg = Message.obtain(); msg.what = WifiP2pManager.PING; msg.arg1 = 0; msg.arg2 = 0; msg.obj = null; 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 pkgName Package name of the caller * @param featureId Feature in the package of the caller * @param uid of the caller * @return WifiP2pDeviceList the peer list */ private WifiP2pDeviceList getPeers(String pkgName, @Nullable String featureId, 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, featureId, uid, true)) { return new WifiP2pDeviceList(mPeers); } else { return new WifiP2pDeviceList(); } } private void setPendingFactoryReset(boolean pending) { mSettingsConfigStore.put(WIFI_P2P_PENDING_FACTORY_RESET, pending); } private boolean isPendingFactoryReset() { return mSettingsConfigStore.get(WIFI_P2P_PENDING_FACTORY_RESET); } /** * 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.hasUserRestrictionForUser( UserManager.DISALLOW_NETWORK_RESET, UserHandle.getUserHandleForUid(uid)) || userManager.hasUserRestrictionForUser( UserManager.DISALLOW_CONFIG_WIFI, UserHandle.getUserHandleForUid(uid))) { return false; } Log.i(TAG, "factoryReset uid=" + uid + " pkg=" + pkgName); if (mInterfaceName != null) { 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; } /** * Get calling feature id from Client HashMap * * @param uid The uid of the caller * @param replyMessenger AsyncChannel handler in caller */ private String getCallingFeatureId(int uid, Messenger replyMessenger) { ClientInfo clientInfo = mClientInfoList.get(replyMessenger); if (clientInfo != null) { return clientInfo.mFeatureId; } if (uid == Process.SYSTEM_UID) return mContext.getAttributionTag(); 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); } } private int selectGroupOwnerIntentIfNecessary(WifiP2pConfig config) { int intent = config.groupOwnerIntent; // return the legacy default value for invalid values. if (intent != WifiP2pConfig.GROUP_OWNER_INTENT_AUTO) { if (intent < WifiP2pConfig.GROUP_OWNER_INTENT_MIN || intent > WifiP2pConfig.GROUP_OWNER_INTENT_MAX) { intent = DEFAULT_GROUP_OWNER_INTENT; } return intent; } WifiManager wifiManager = mContext.getSystemService(WifiManager.class); WifiInfo wifiInfo = wifiManager.getConnectionInfo(); Log.d(TAG, "WifiInfo: " + wifiInfo); int freq = wifiInfo.getFrequency(); if (wifiInfo.getNetworkId() == WifiConfiguration.INVALID_NETWORK_ID) { intent = DEFAULT_GROUP_OWNER_INTENT + 1; } else if (ScanResult.is24GHz(freq)) { intent = WifiP2pConfig.GROUP_OWNER_INTENT_MIN; } else if (ScanResult.is5GHz(freq)) { // If both sides use the maximum, the negotiation would fail. intent = WifiP2pConfig.GROUP_OWNER_INTENT_MAX - 1; } else { intent = DEFAULT_GROUP_OWNER_INTENT; } Log.i(TAG, "change GO intent value from " + config.groupOwnerIntent + " to " + intent); return intent; } private boolean updateP2pChannels() { Log.d(TAG, "Set P2P listen channel to " + mUserListenChannel); if (!mWifiNative.p2pSetListenChannel(mUserListenChannel)) { Log.e(TAG, "Cannot set listen channel."); return false; } Log.d(TAG, "Set P2P operating channel to " + mUserOperatingChannel + ", unsafe channels: " + mCoexUnsafeChannels.stream() .map(Object::toString).collect(Collectors.joining(","))); if (!mWifiNative.p2pSetOperatingChannel(mUserOperatingChannel, mCoexUnsafeChannels)) { Log.e(TAG, "Cannot set operate channel."); return false; } return true; } } /** * 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; private @Nullable String mFeatureId; // A service discovery request list. private SparseArray mReqList; // A local service information list. private List mServList; private ClientInfo(Messenger m) { mMessenger = m; mPackageName = null; mFeatureId = null; mReqList = new SparseArray(); mServList = new ArrayList(); } } /** * Check that the UID has one of the following permissions: * {@link android.Manifest.permission.NETWORK_SETTINGS} * {@link android.Manifest.permission.NETWORK_STACK} * {@link android.Manifest.permission.OVERRIDE_WIFI_CONFIG} * * @param uid the UID to check * @return whether the UID has any of the above permissions */ private boolean checkNetworkSettingsOrNetworkStackOrOverrideWifiConfigPermission(int uid) { return mWifiPermissionsUtil.checkNetworkSettingsPermission(uid) || mWifiPermissionsUtil.checkNetworkStackPermission(uid) || mWifiPermissionsUtil.checkConfigOverridePermission(uid); } /** * Check that the UID has one of the following permissions: * {@link android.Manifest.permission.NETWORK_SETTINGS} * {@link android.Manifest.permission.NETWORK_STACK} * {@link android.Manifest.permission.READ_WIFI_CREDENTIAL} * * @param uid the UID to check * @return whether the UID has any of the above permissions */ private boolean checkNetworkSettingsOrNetworkStackOrReadWifiCredentialPermission(int uid) { return mWifiPermissionsUtil.checkNetworkSettingsPermission(uid) || mWifiPermissionsUtil.checkNetworkStackPermission(uid) || mWifiPermissionsUtil.checkReadWifiCredentialPermission(uid); } }