1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.snippet.wifi.direct; 18 19 import android.content.BroadcastReceiver; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.IntentFilter; 23 import android.net.NetworkInfo; 24 import android.net.wifi.WifiManager; 25 import android.net.wifi.p2p.WifiP2pConfig; 26 import android.net.wifi.p2p.WifiP2pDevice; 27 import android.net.wifi.p2p.WifiP2pGroup; 28 import android.net.wifi.p2p.WifiP2pInfo; 29 import android.net.wifi.p2p.WifiP2pManager; 30 import android.os.Build; 31 import android.util.Log; 32 33 import androidx.test.platform.app.InstrumentationRegistry; 34 35 import com.android.compatibility.common.util.ApiLevelUtil; 36 import com.android.compatibility.common.util.PollingCheck; 37 import com.android.compatibility.common.util.ShellIdentityUtils; 38 39 import com.google.android.mobly.snippet.Snippet; 40 import com.google.android.mobly.snippet.rpc.Rpc; 41 42 import org.json.JSONException; 43 44 import java.util.concurrent.CountDownLatch; 45 import java.util.concurrent.TimeUnit; 46 47 public class WifiDirectSnippet implements Snippet { 48 49 private static final String TAG = "WifiDirectSnippet"; 50 private final Context mContext; 51 52 private final WifiP2pManager mP2pManager; 53 private WifiP2pInfo mP2pInfo; 54 private WifiP2pGroup mP2pGroup; 55 private WifiP2pManager.Channel mChannel; 56 private WifiManager mWifiManager; 57 58 private final Object mLock = new Object(); 59 60 private final WifiP2pManager.ChannelListener mChannelListener = 61 new WifiP2pManager.ChannelListener() { 62 public void onChannelDisconnected() { 63 Log.e(TAG, "P2P channel disconnected"); 64 } 65 }; 66 67 private BroadcastReceiver mP2pStateChangedReceiver = 68 new BroadcastReceiver() { 69 @Override 70 public void onReceive(Context c, Intent intent) { 71 String action = intent.getAction(); 72 if (action.equals(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION)) { 73 Log.d(TAG, "Wifi P2p State Changed."); 74 int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, 0); 75 if (state == WifiP2pManager.WIFI_P2P_STATE_DISABLED) { 76 Log.d(TAG, "Disabled"); 77 } else if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) { 78 Log.d(TAG, "Enabled"); 79 } 80 } else if (action.equals(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION)) { 81 Log.d(TAG, "Wifi P2p Peers Changed"); 82 } else if (action.equals(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)) { 83 Log.d(TAG, "Wifi P2p Connection Changed."); 84 synchronized (mLock) { 85 NetworkInfo networkInfo = intent 86 .getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO); 87 if (networkInfo.isConnected()) { 88 Log.d(TAG, "Wifi P2p Connected."); 89 mP2pInfo = intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO); 90 mP2pGroup = intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP); 91 mLock.notify(); 92 } else if (networkInfo.isConnectedOrConnecting()) { 93 Log.d(TAG, "Wifi P2p is in connecting state"); 94 } else { 95 Log.d(TAG, "Wifi P2p is in disconnected state"); 96 } 97 } 98 } else if (action.equals(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION)) { 99 Log.d(TAG, "Wifi P2p This Device Changed."); 100 WifiP2pDevice device = intent 101 .getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_DEVICE); 102 } else if (action.equals(WifiP2pManager.WIFI_P2P_DISCOVERY_CHANGED_ACTION)) { 103 Log.d(TAG, "Wifi P2p Discovery Changed."); 104 int state = intent.getIntExtra(WifiP2pManager.EXTRA_DISCOVERY_STATE, 0); 105 if (state == WifiP2pManager.WIFI_P2P_DISCOVERY_STARTED) { 106 Log.d(TAG, "discovery started."); 107 } else if (state == WifiP2pManager.WIFI_P2P_DISCOVERY_STOPPED) { 108 Log.d(TAG, "discovery stopped."); 109 } 110 } 111 } 112 }; 113 114 class WifiP2pActionListener implements WifiP2pManager.ActionListener { 115 private final String mOperation; 116 private final CountDownLatch mCountDownLatch; WifiP2pActionListener(String action)117 WifiP2pActionListener(String action) { 118 mCountDownLatch = new CountDownLatch(1); 119 mOperation = action; 120 } 121 await()122 public void await() throws InterruptedException { 123 mCountDownLatch.await(1000, TimeUnit.MILLISECONDS); 124 } 125 126 @Override onSuccess()127 public void onSuccess() { 128 Log.d(TAG, mOperation + " OnSuccess"); 129 mCountDownLatch.countDown(); 130 } 131 @Override onFailure(int reason)132 public void onFailure(int reason) { 133 Log.d(TAG, mOperation + " onFailure - reason: " + reason); 134 mCountDownLatch.countDown(); 135 } 136 } 137 buildWifiP2pConfig(String networkName, String passphrase, int frequency)138 private WifiP2pConfig buildWifiP2pConfig(String networkName, String passphrase, 139 int frequency) { 140 WifiP2pConfig.Builder builder = new WifiP2pConfig.Builder(); 141 builder.setNetworkName(networkName); 142 builder.setPassphrase(passphrase); 143 if (frequency != 0) { 144 builder.setGroupOperatingFrequency(frequency); 145 } 146 return builder.build(); 147 } 148 WifiDirectSnippet()149 public WifiDirectSnippet() { 150 mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 151 mP2pManager = mContext.getSystemService(WifiP2pManager.class); 152 mWifiManager = mContext.getSystemService(WifiManager.class); 153 154 Log.d(TAG, "WifiDirectSnippet - init"); 155 } 156 157 @Rpc(description = "Check if Aware pairing supported") isChannelConstrainedDiscoverySupported()158 public boolean isChannelConstrainedDiscoverySupported() 159 throws InterruptedException { 160 if (!ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) { 161 return false; 162 } 163 return mP2pManager.isChannelConstrainedDiscoverySupported(); 164 } 165 166 @Rpc(description = "Execute p2p initialize") initializeP2p()167 public void initializeP2p() throws Exception { 168 ShellIdentityUtils.invokeWithShellPermissions( 169 () -> mWifiManager.allowAutojoinGlobal(false)); 170 ShellIdentityUtils.invokeWithShellPermissions(() -> mWifiManager.disconnect()); 171 PollingCheck.check( 172 "Wifi not disconnected", 173 20000, 174 () -> mWifiManager.getConnectionInfo().getNetworkId() == -1); 175 mChannel = mP2pManager.initialize(mContext, mContext.getMainLooper(), mChannelListener); 176 IntentFilter intentFilter = new IntentFilter(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION); 177 intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION); 178 intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); 179 intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION); 180 intentFilter.setPriority(999); 181 mContext.registerReceiver(mP2pStateChangedReceiver, intentFilter); 182 Log.d(TAG, "Wifi Direct initialization completed"); 183 } 184 185 @Rpc(description = "Close P2P channel") closeP2p()186 public void closeP2p() throws JSONException { 187 if (mChannel != null) { 188 try { 189 WifiP2pActionListener actionListener = new WifiP2pActionListener("RemoveGroup"); 190 mP2pManager.removeGroup(mChannel, actionListener); 191 actionListener.await(); 192 } catch (InterruptedException e) { 193 Log.d(TAG, "RemoveGroup request failed"); 194 } 195 mChannel.close(); 196 mChannel = null; 197 Log.d(TAG, "Wifi Direct close called"); 198 } 199 ShellIdentityUtils.invokeWithShellPermissions(() -> mWifiManager.allowAutojoinGlobal(true)); 200 Log.d(TAG, "Wifi Direct close completed"); 201 } 202 203 @Rpc(description = "Create a P2P group owner with config") p2pCreateGroup(String networkName, String passphrase, int frequency)204 public boolean p2pCreateGroup(String networkName, String passphrase, 205 int frequency) throws JSONException { 206 if (mChannel == null) { 207 Log.d(TAG, "p2pCreateGroup failed -should call initializeP2p first "); 208 return false; 209 } 210 WifiP2pActionListener actionListener = new WifiP2pActionListener("CreateGroup"); 211 WifiP2pConfig wifiP2pConfig = buildWifiP2pConfig(networkName, passphrase, frequency); 212 try { 213 mP2pManager.createGroup(mChannel, wifiP2pConfig, actionListener); 214 actionListener.await(); 215 } catch (InterruptedException e) { 216 Log.d(TAG, "p2pCreateGroup request failed"); 217 return false; 218 } 219 Log.d(TAG, "p2pCreateGroup succeeded"); 220 return true; 221 } 222 223 @Rpc(description = "Connect to a P2P group owner with config") p2pConnect(String networkName, String passphrase, int frequency)224 public long p2pConnect(String networkName, String passphrase, 225 int frequency) throws JSONException, InterruptedException { 226 if (mChannel == null) { 227 Log.d(TAG, "p2pConnect failed- should call initializeP2p first"); 228 return -1; 229 } 230 WifiP2pActionListener actionListener = new WifiP2pActionListener("Connect"); 231 WifiP2pConfig wifiP2pConfig = buildWifiP2pConfig(networkName, passphrase, frequency); 232 long startTime = System.currentTimeMillis(); 233 try { 234 mP2pManager.connect(mChannel, wifiP2pConfig, actionListener); 235 actionListener.await(); 236 } catch (InterruptedException e) { 237 Log.d(TAG, "p2pConnect request failed"); 238 return -1; 239 } 240 241 synchronized (mLock) { 242 mLock.wait(5000); 243 } 244 245 if (mP2pInfo == null || !mP2pInfo.groupFormed || mP2pGroup == null) { 246 Log.d(TAG, "p2pConnect failure"); 247 248 return -1; 249 } 250 251 Log.d(TAG, "p2pConnect succeeded - group not null"); 252 return System.currentTimeMillis() - startTime; 253 } 254 255 @Override shutdown()256 public void shutdown() { 257 Log.d(TAG, "Wifi Direct shutdown called"); 258 } 259 } 260