• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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;
18 
19 import static android.net.wifi.DeauthenticationReasonCode.REASON_UNKNOWN;
20 
21 import android.content.Context;
22 import android.net.wifi.SoftApConfiguration;
23 import android.net.wifi.SoftApInfo;
24 import android.net.wifi.WifiClient;
25 import android.net.wifi.WifiConfiguration;
26 import android.net.wifi.WifiManager;
27 import android.os.Handler;
28 import android.os.HandlerThread;
29 import android.util.Log;
30 
31 import androidx.annotation.NonNull;
32 import androidx.test.platform.app.InstrumentationRegistry;
33 
34 import com.android.compatibility.common.util.PollingCheck;
35 import com.android.wifi.flags.Flags;
36 
37 import com.google.android.mobly.snippet.Snippet;
38 import com.google.android.mobly.snippet.event.EventCache;
39 import com.google.android.mobly.snippet.event.SnippetEvent;
40 import com.google.android.mobly.snippet.rpc.AsyncRpc;
41 import com.google.android.mobly.snippet.rpc.Rpc;
42 
43 import org.json.JSONException;
44 import org.json.JSONObject;
45 
46 import java.util.List;
47 import java.util.concurrent.TimeUnit;
48 
49 /** Snippet class for WifiManager. */
50 public class WifiManagerSnippet implements Snippet {
51 
52     private static final String TAG = "WifiManagerSnippet";
53     private static final long POLLING_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10);
54 
55     private final WifiManager mWifiManager;
56     private final Handler mHandler;
57     private final Object mLock = new Object();
58 
59     private WifiManagerSnippet.SnippetSoftApCallback mSoftApCallback;
60     private WifiManager.LocalOnlyHotspotReservation mLocalOnlyHotspotReservation;
61 
62     /** Callback to listen in and verify events to SoftAp. */
63     private static class SnippetSoftApCallback implements WifiManager.SoftApCallback {
64         private final String mCallbackId;
65 
SnippetSoftApCallback(String callbackId)66         SnippetSoftApCallback(String callbackId) {
67             mCallbackId = callbackId;
68         }
69 
70         @Override
onConnectedClientsChanged(@onNull SoftApInfo info, @NonNull List<WifiClient> clients)71         public void onConnectedClientsChanged(@NonNull SoftApInfo info,
72                 @NonNull List<WifiClient> clients) {
73             Log.d(TAG, "onConnectedClientsChanged, info=" + info + ", clients=" + clients);
74             SnippetEvent event = new SnippetEvent(mCallbackId, "onConnectedClientsChanged");
75             event.getData().putInt("connectedClientsCount", clients.size());
76             String macAddress = null;
77             if (!clients.isEmpty()) {
78                 // In our Mobly test cases, there is only ever one other device.
79                 WifiClient client = clients.getFirst();
80                 macAddress = client.getMacAddress().toString();
81             }
82             event.getData().putString("clientMacAddress", macAddress);
83             EventCache.getInstance().postEvent(event);
84         }
85 
86         @Override
onClientsDisconnected(@onNull SoftApInfo info, @NonNull List<WifiClient> clients)87         public void onClientsDisconnected(@NonNull SoftApInfo info,
88                 @NonNull List<WifiClient> clients) {
89             Log.d(TAG, "onClientsDisconnected, info=" + info + ", clients=" + clients);
90             SnippetEvent event = new SnippetEvent(mCallbackId, "onClientsDisconnected");
91             event.getData().putInt("disconnectedClientsCount", clients.size());
92             String macAddress = null;
93             int disconnectReason = REASON_UNKNOWN;
94             if (!clients.isEmpty()) {
95                 // In our Mobly test cases, there is only ever one other device.
96                 WifiClient client = clients.getFirst();
97                 macAddress = client.getMacAddress().toString();
98                 disconnectReason = client.getDisconnectReason();
99             }
100             event.getData().putString("clientMacAddress", macAddress);
101             event.getData().putInt("clientDisconnectReason", disconnectReason);
102             EventCache.getInstance().postEvent(event);
103         }
104     }
105 
106     /** Callback class to get the results of local hotspot start. */
107     private class SnippetLocalOnlyHotspotCallback extends WifiManager.LocalOnlyHotspotCallback {
108         private final String mCallbackId;
109 
SnippetLocalOnlyHotspotCallback(String callbackId)110         SnippetLocalOnlyHotspotCallback(String callbackId) {
111             mCallbackId = callbackId;
112         }
113 
114         @Override
onStarted(WifiManager.LocalOnlyHotspotReservation reservation)115         public void onStarted(WifiManager.LocalOnlyHotspotReservation reservation) {
116             Log.d(TAG, "Local-only hotspot onStarted");
117             synchronized (mLock) {
118                 mLocalOnlyHotspotReservation = reservation;
119             }
120             SoftApConfiguration currentConfiguration = reservation.getSoftApConfiguration();
121             SnippetEvent event = new SnippetEvent(mCallbackId, "onStarted");
122             event.getData().putString("ssid",
123                     WifiJsonConverter.trimQuotationMarks(
124                             currentConfiguration.getWifiSsid().toString()));
125             event.getData()
126                     .putString(
127                             "passphrase",
128                             currentConfiguration.getPassphrase());
129             EventCache.getInstance().postEvent(event);
130         }
131     }
132 
WifiManagerSnippet()133     public WifiManagerSnippet() {
134         Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
135         mWifiManager = context.getSystemService(WifiManager.class);
136         HandlerThread handlerThread = new HandlerThread(getClass().getSimpleName());
137         handlerThread.start();
138         mHandler = new Handler(handlerThread.getLooper());
139     }
140 
141     /**
142      * Starts local-only hotspot.
143      *
144      * @param callbackId A unique identifier assigned automatically by Mobly.
145      */
146     @AsyncRpc(description = "Call to start local-only hotspot.")
wifiStartLocalOnlyHotspot(String callbackId)147     public void wifiStartLocalOnlyHotspot(String callbackId) {
148         mWifiManager.startLocalOnlyHotspot(new SnippetLocalOnlyHotspotCallback(callbackId),
149                 mHandler);
150     }
151 
152     /**
153      * Stop local-only hotspot.
154      */
155     @Rpc(description = "Call to stop local-only hotspot.")
wifiStopLocalOnlyHotspot()156     public void wifiStopLocalOnlyHotspot() {
157         synchronized (mLock) {
158             if (mLocalOnlyHotspotReservation == null) {
159                 Log.w(TAG, "Requested to stop local-only hotspot which was already stopped.");
160                 return;
161             }
162 
163             mLocalOnlyHotspotReservation.close();
164             mLocalOnlyHotspotReservation = null;
165         }
166     }
167 
168     /**
169      * Registers a callback for Soft AP.
170      *
171      * @param callbackId A unique identifier assigned automatically by Mobly.
172      */
173     @AsyncRpc(description = "Call to register SoftApCallback.")
wifiRegisterSoftApCallback(String callbackId)174     public void wifiRegisterSoftApCallback(String callbackId) {
175         if (mSoftApCallback == null) {
176             mSoftApCallback = new SnippetSoftApCallback(callbackId);
177             mWifiManager.registerSoftApCallback(mHandler::post, mSoftApCallback);
178         }
179     }
180 
181 
182     /**
183      * Registers a callback for local-only hotspot.
184      *
185      * @param callbackId A unique identifier assigned automatically by Mobly.
186      */
187     @AsyncRpc(description = "Call to register SoftApCallback for local-only hotspot.")
wifiRegisterLocalOnlyHotspotSoftApCallback(String callbackId)188     public void wifiRegisterLocalOnlyHotspotSoftApCallback(String callbackId) {
189         if (mSoftApCallback == null) {
190             mSoftApCallback = new SnippetSoftApCallback(callbackId);
191             mWifiManager.registerLocalOnlyHotspotSoftApCallback(mHandler::post,
192                     mSoftApCallback);
193         }
194     }
195 
196     /**
197      * Checks if the device supports portable hotspot.
198      *
199      * @return {@code true} if the device supports portable hotspot, {@code false} otherwise.
200      */
201     @Rpc(description = "Check if the device supports portable hotspot.")
wifiIsPortableHotspotSupported()202     public boolean wifiIsPortableHotspotSupported() {
203         return mWifiManager.isPortableHotspotSupported();
204     }
205 
206     /**
207      * Unregisters soft AP callback function.
208      */
209     @Rpc(description = "Unregister soft AP callback function.")
wifiUnregisterSoftApCallback()210     public void wifiUnregisterSoftApCallback() {
211         if (mSoftApCallback == null) {
212             return;
213         }
214 
215         mWifiManager.unregisterSoftApCallback(mSoftApCallback);
216         mSoftApCallback = null;
217     }
218 
219     /**
220      * Unregisters soft AP callback function.
221      */
222     @Rpc(description = "Unregister soft AP callback function.")
wifiUnregisterLocalOnlyHotspotSoftApCallback()223     public void wifiUnregisterLocalOnlyHotspotSoftApCallback() {
224         if (mSoftApCallback == null) {
225             return;
226         }
227 
228         mWifiManager.unregisterLocalOnlyHotspotSoftApCallback(mSoftApCallback);
229         mSoftApCallback = null;
230     }
231 
232     /**
233      * Enables all saved networks.
234      */
235     @Rpc(description = "Enable all saved networks.")
wifiEnableAllSavedNetworks()236     public void wifiEnableAllSavedNetworks() {
237         for (WifiConfiguration savedNetwork : mWifiManager.getConfiguredNetworks()) {
238             mWifiManager.enableNetwork(savedNetwork.networkId, false);
239         }
240     }
241 
242     /**
243      * Disables all saved networks.
244      */
245     @Rpc(description = "Disable all saved networks.")
wifiDisableAllSavedNetworks()246     public void wifiDisableAllSavedNetworks() {
247         for (WifiConfiguration savedNetwork : mWifiManager.getConfiguredNetworks()) {
248             mWifiManager.disableNetwork(savedNetwork.networkId);
249         }
250     }
251 
252     /**
253      * Checks the softap_disconnect_reason flag.
254      *
255      * @return {@code true} if the softap_disconnect_reason flag is enabled, {@code false}
256      * otherwise.
257      */
258     @Rpc(description = "Checks SoftApDisconnectReason flag.")
wifiCheckSoftApDisconnectReasonFlag()259     public boolean wifiCheckSoftApDisconnectReasonFlag() {
260         return Flags.softapDisconnectReason();
261     }
262 
263     /**
264      * Gets the Wi-Fi tethered AP Configuration.
265      *
266      * @return AP details in {@link SoftApConfiguration} as JSON format.
267      */
268     @Rpc(description = "Get current SoftApConfiguration.")
wifiGetSoftApConfiguration()269     public JSONObject wifiGetSoftApConfiguration() throws JSONException {
270         return WifiJsonConverter.serialize(mWifiManager.getSoftApConfiguration());
271     }
272 
273     /**
274      * Waits for tethering to be disabled.
275      *
276      * @return {@code true} if tethering is disabled within the timeout, {@code false} otherwise.
277      */
278     @Rpc(description = "Call to wait for tethering to be disabled.")
wifiWaitForTetheringDisabled()279     public boolean wifiWaitForTetheringDisabled() {
280         try {
281             PollingCheck.check("Tethering NOT disabled", POLLING_TIMEOUT_MS,
282                     () -> !mWifiManager.isWifiApEnabled());
283         } catch (Exception e) {
284             return false;
285         }
286         return true;
287     }
288 }
289