1 /*
2  * Copyright (C) 2017 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.googlecode.android_scripting.facade.wifi;
18 
19 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
20 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
21 import static android.net.wifi.WifiScanner.WIFI_BAND_24_GHZ;
22 import static android.net.wifi.WifiScanner.WIFI_BAND_5_GHZ;
23 
24 import static com.googlecode.android_scripting.jsonrpc.JsonBuilder.build;
25 
26 import android.annotation.NonNull;
27 import android.annotation.Nullable;
28 import android.app.Service;
29 import android.content.BroadcastReceiver;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.content.IntentFilter;
33 import android.net.ConnectivityManager;
34 import android.net.ConnectivityManager.NetworkCallback;
35 import android.net.DhcpInfo;
36 import android.net.MacAddress;
37 import android.net.Network;
38 import android.net.NetworkCapabilities;
39 import android.net.NetworkInfo;
40 import android.net.NetworkInfo.DetailedState;
41 import android.net.NetworkRequest;
42 import android.net.NetworkSpecifier;
43 import android.net.Uri;
44 import android.net.wifi.CoexUnsafeChannel;
45 import android.net.wifi.EasyConnectStatusCallback;
46 import android.net.wifi.ScanResult;
47 import android.net.wifi.SoftApCapability;
48 import android.net.wifi.SoftApConfiguration;
49 import android.net.wifi.SoftApInfo;
50 import android.net.wifi.WifiClient;
51 import android.net.wifi.WifiConfiguration;
52 import android.net.wifi.WifiConfiguration.AuthAlgorithm;
53 import android.net.wifi.WifiConfiguration.KeyMgmt;
54 import android.net.wifi.WifiEnterpriseConfig;
55 import android.net.wifi.WifiInfo;
56 import android.net.wifi.WifiManager;
57 import android.net.wifi.WifiManager.NetworkRequestMatchCallback;
58 import android.net.wifi.WifiManager.NetworkRequestUserSelectionCallback;
59 import android.net.wifi.WifiManager.SubsystemRestartTrackingCallback;
60 import android.net.wifi.WifiManager.WifiLock;
61 import android.net.wifi.WifiNetworkSpecifier;
62 import android.net.wifi.WifiNetworkSuggestion;
63 import android.net.wifi.WpsInfo;
64 import android.net.wifi.hotspot2.ConfigParser;
65 import android.net.wifi.hotspot2.OsuProvider;
66 import android.net.wifi.hotspot2.PasspointConfiguration;
67 import android.net.wifi.hotspot2.ProvisioningCallback;
68 import android.os.Bundle;
69 import android.os.Handler;
70 import android.os.HandlerExecutor;
71 import android.os.HandlerThread;
72 import android.os.PatternMatcher;
73 import android.os.connectivity.WifiActivityEnergyInfo;
74 import android.provider.Settings.SettingNotFoundException;
75 import android.text.TextUtils;
76 import android.util.Base64;
77 import android.util.SparseArray;
78 import android.util.SparseIntArray;
79 
80 
81 import com.android.internal.annotations.GuardedBy;
82 import com.android.modules.utils.build.SdkLevel;
83 
84 import com.googlecode.android_scripting.Log;
85 import com.googlecode.android_scripting.facade.EventFacade;
86 import com.googlecode.android_scripting.facade.FacadeManager;
87 import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
88 import com.googlecode.android_scripting.rpc.Rpc;
89 import com.googlecode.android_scripting.rpc.RpcOptional;
90 import com.googlecode.android_scripting.rpc.RpcParameter;
91 
92 import org.json.JSONArray;
93 import org.json.JSONException;
94 import org.json.JSONObject;
95 
96 import java.io.ByteArrayInputStream;
97 import java.io.ByteArrayOutputStream;
98 import java.io.IOException;
99 import java.io.InputStream;
100 import java.io.ObjectOutput;
101 import java.io.ObjectOutputStream;
102 import java.security.GeneralSecurityException;
103 import java.security.KeyFactory;
104 import java.security.NoSuchAlgorithmException;
105 import java.security.PrivateKey;
106 import java.security.PublicKey;
107 import java.security.cert.CertificateException;
108 import java.security.cert.CertificateFactory;
109 import java.security.cert.X509Certificate;
110 import java.security.spec.InvalidKeySpecException;
111 import java.security.spec.PKCS8EncodedKeySpec;
112 import java.security.spec.X509EncodedKeySpec;
113 import java.util.ArrayList;
114 import java.util.Arrays;
115 import java.util.Collections;
116 import java.util.HashMap;
117 import java.util.List;
118 import java.util.Map;
119 import java.util.concurrent.CountDownLatch;
120 import java.util.concurrent.Executor;
121 import java.util.concurrent.TimeUnit;
122 
123 /**
124  * WifiManager functions.
125  */
126 // TODO: make methods handle various wifi states properly
127 // e.g. wifi connection result will be null when flight mode is on
128 public class WifiManagerFacade extends RpcReceiver {
129     private static final String mEventType = "WifiManager";
130     // MIME type for passpoint config.
131     private static final String TYPE_WIFICONFIG = "application/x-wifi-config";
132     private static final int TIMEOUT_MILLIS = 5000;
133 
134     private final Service mService;
135     private final WifiManager mWifi;
136     private final ConnectivityManager mCm;
137     private final EventFacade mEventFacade;
138 
139     private final IntentFilter mScanFilter;
140     private final IntentFilter mStateChangeFilter;
141     private final IntentFilter mTetherFilter;
142     private final IntentFilter mNetworkSuggestionStateChangeFilter;
143     private final WifiScanReceiver mScanResultsAvailableReceiver;
144     private final WifiScanResultsReceiver mWifiScanResultsReceiver;
145     private final WifiStateChangeReceiver mStateChangeReceiver;
146     private final WifiNetworkSuggestionStateChangeReceiver mNetworkSuggestionStateChangeReceiver;
147     private SubsystemRestartTrackingCallbackFacade mSubsystemRestartTrackingCallback = null;
148     private final HandlerThread mCallbackHandlerThread;
149     private final Object mCallbackLock = new Object();
150     private boolean mTrackingWifiStateChange;
151     private boolean mTrackingTetherStateChange;
152     private boolean mTrackingNetworkSuggestionStateChange;
153     @GuardedBy("mCallbackLock")
154     private NetworkRequestUserSelectionCallback mNetworkRequestUserSelectionCallback;
155     private final SparseArray<SoftApCallbackImp> mSoftapCallbacks;
156     // This is null if SdkLevel is not at least S
157     @Nullable private WifiManager.CoexCallback mCoexCallback;
158 
159     private final BroadcastReceiver mTetherStateReceiver = new BroadcastReceiver() {
160         @Override
161         public void onReceive(Context context, Intent intent) {
162             String action = intent.getAction();
163             if (WifiManager.WIFI_AP_STATE_CHANGED_ACTION.equals(action)) {
164                 Log.d("Wifi AP state changed.");
165                 int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_AP_STATE,
166                         WifiManager.WIFI_AP_STATE_FAILED);
167                 if (state == WifiManager.WIFI_AP_STATE_ENABLED) {
168                     mEventFacade.postEvent("WifiManagerApEnabled", null);
169                 } else if (state == WifiManager.WIFI_AP_STATE_DISABLED) {
170                     mEventFacade.postEvent("WifiManagerApDisabled", null);
171                 }
172             } else if (ConnectivityManager.ACTION_TETHER_STATE_CHANGED.equals(action)) {
173                 Log.d("Tether state changed.");
174                 ArrayList<String> available = intent.getStringArrayListExtra(
175                         ConnectivityManager.EXTRA_AVAILABLE_TETHER);
176                 ArrayList<String> active = intent.getStringArrayListExtra(
177                         ConnectivityManager.EXTRA_ACTIVE_TETHER);
178                 ArrayList<String> errored = intent.getStringArrayListExtra(
179                         ConnectivityManager.EXTRA_ERRORED_TETHER);
180                 Bundle msg = new Bundle();
181                 msg.putStringArrayList("AVAILABLE_TETHER", available);
182                 msg.putStringArrayList("ACTIVE_TETHER", active);
183                 msg.putStringArrayList("ERRORED_TETHER", errored);
184                 mEventFacade.postEvent("TetherStateChanged", msg);
185             }
186         }
187     };
188 
189     private final NetworkRequestMatchCallback mNetworkRequestMatchCallback =
190             new NetworkRequestMatchCallback() {
191                 private static final String EVENT_TAG = mEventType + "NetworkRequestMatchCallback";
192 
193                 @Override
194                 public void onUserSelectionCallbackRegistration(
195                         NetworkRequestUserSelectionCallback userSelectionCallback) {
196                     synchronized (mCallbackLock) {
197                         mNetworkRequestUserSelectionCallback = userSelectionCallback;
198                     }
199                 }
200 
201                 @Override
202                 public void onAbort() {
203                     mEventFacade.postEvent(EVENT_TAG + "OnAbort", null);
204                 }
205 
206                 @Override
207                 public void onMatch(List<ScanResult> scanResults) {
208                     mEventFacade.postEvent(EVENT_TAG + "OnMatch", scanResults);
209                 }
210 
211                 @Override
212                 public void onUserSelectionConnectSuccess(WifiConfiguration wifiConfiguration) {
213                     mEventFacade.postEvent(EVENT_TAG + "OnUserSelectionConnectSuccess",
214                             wifiConfiguration);
215                 }
216 
217                 @Override
218                 public void onUserSelectionConnectFailure(WifiConfiguration wifiConfiguration) {
219                     mEventFacade.postEvent(EVENT_TAG + "OnUserSelectionConnectFailure",
220                             wifiConfiguration);
221                 }
222             };
223 
224     private static class SoftApCallbackImp implements WifiManager.SoftApCallback {
225         // A monotonic increasing counter for softap callback ids.
226         private static int sCount = 0;
227 
228         private final int mId;
229         private final EventFacade mEventFacade;
230         private final String mEventStr;
231 
SoftApCallbackImp(EventFacade eventFacade)232         SoftApCallbackImp(EventFacade eventFacade) {
233             sCount++;
234             mId = sCount;
235             mEventFacade = eventFacade;
236             mEventStr = mEventType + "SoftApCallback-" + mId + "-";
237         }
238 
239         @Override
onStateChanged(int state, int failureReason)240         public void onStateChanged(int state, int failureReason) {
241             Bundle msg = new Bundle();
242             msg.putInt("State", state);
243             msg.putInt("FailureReason", failureReason);
244             mEventFacade.postEvent(mEventStr + "OnStateChanged", msg);
245         }
246 
247         @Override
onConnectedClientsChanged(List<WifiClient> clients)248         public void onConnectedClientsChanged(List<WifiClient> clients) {
249             ArrayList<MacAddress> macAddresses = new ArrayList<>();
250             clients.forEach(x -> macAddresses.add(x.getMacAddress()));
251             Bundle msg = new Bundle();
252             msg.putInt("NumClients", clients.size());
253             msg.putParcelableArrayList("MacAddresses", macAddresses);
254             mEventFacade.postEvent(mEventStr + "OnNumClientsChanged", msg);
255             mEventFacade.postEvent(mEventStr + "OnConnectedClientsChanged", clients);
256         }
257 
258         @Override
onConnectedClientsChanged(SoftApInfo info, List<WifiClient> clients)259         public void onConnectedClientsChanged(SoftApInfo info, List<WifiClient> clients) {
260             ArrayList<MacAddress> macAddresses = new ArrayList<>();
261             clients.forEach(x -> macAddresses.add(x.getMacAddress()));
262             Bundle msg = new Bundle();
263             msg.putParcelable("Info", info);
264             msg.putParcelableArrayList("ClientsMacAddress", macAddresses);
265             mEventFacade.postEvent(mEventStr + "OnConnectedClientsChangedWithInfo", msg);
266         }
267 
268         @Override
onInfoChanged(SoftApInfo softApInfo)269         public void onInfoChanged(SoftApInfo softApInfo) {
270             mEventFacade.postEvent(mEventStr + "OnInfoChanged", softApInfo);
271         }
272 
273         @Override
onInfoChanged(List<SoftApInfo> infos)274         public void onInfoChanged(List<SoftApInfo> infos) {
275             mEventFacade.postEvent(mEventStr + "OnInfoListChanged", infos);
276         }
277 
278         @Override
onCapabilityChanged(SoftApCapability softApCapability)279         public void onCapabilityChanged(SoftApCapability softApCapability) {
280             mEventFacade.postEvent(mEventStr + "OnCapabilityChanged", softApCapability);
281         }
282 
283         @Override
onBlockedClientConnecting(WifiClient client, int blockedReason)284         public void onBlockedClientConnecting(WifiClient client, int blockedReason) {
285             Bundle msg = new Bundle();
286             msg.putString("WifiClient", client.getMacAddress().toString());
287             msg.putInt("BlockedReason", blockedReason);
288             mEventFacade.postEvent(mEventStr + "OnBlockedClientConnecting", msg);
289         }
290     };
291 
292     private static class CoexCallbackImpl extends WifiManager.CoexCallback {
293         private final EventFacade mEventFacade;
294         private final String mEventStr;
295 
CoexCallbackImpl(EventFacade eventFacade)296         CoexCallbackImpl(EventFacade eventFacade) {
297             mEventFacade = eventFacade;
298             mEventStr = mEventType + "CoexCallback";
299         }
300 
301         @Override
onCoexUnsafeChannelsChanged( @onNull List<CoexUnsafeChannel> unsafeChannels, int restrictions)302         public void onCoexUnsafeChannelsChanged(
303                 @NonNull List<CoexUnsafeChannel> unsafeChannels, int restrictions) {
304             Bundle event = new Bundle();
305             try {
306                 event.putString("KEY_COEX_UNSAFE_CHANNELS",
307                         coexUnsafeChannelsToJson(unsafeChannels).toString());
308                 event.putString("KEY_COEX_RESTRICTIONS",
309                         coexRestrictionsToJson(restrictions).toString());
310                 mEventFacade.postEvent(mEventStr + "#onCoexUnsafeChannelsChanged", event);
311             } catch (JSONException e) {
312                 Log.e("Failed to post event for onCoexUnsafeChannelsChanged: " + e);
313             }
314         }
315     };
316 
317     private WifiLock mLock = null;
318     private boolean mIsConnected = false;
319 
WifiManagerFacade(FacadeManager manager)320     public WifiManagerFacade(FacadeManager manager) {
321         super(manager);
322         mService = manager.getService();
323         mWifi = (WifiManager) mService.getSystemService(Context.WIFI_SERVICE);
324         mCm = (ConnectivityManager) mService.getSystemService(Context.CONNECTIVITY_SERVICE);
325         mEventFacade = manager.getReceiver(EventFacade.class);
326         mCallbackHandlerThread = new HandlerThread("WifiManagerFacade");
327 
328         mScanFilter = new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
329         mStateChangeFilter = new IntentFilter(WifiManager.NETWORK_STATE_CHANGED_ACTION);
330         mStateChangeFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
331         mStateChangeFilter.addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION);
332         mStateChangeFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
333         mStateChangeFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY - 1);
334 
335         mTetherFilter = new IntentFilter(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
336         mTetherFilter.addAction(ConnectivityManager.ACTION_TETHER_STATE_CHANGED);
337 
338         mNetworkSuggestionStateChangeFilter = new IntentFilter(
339                 WifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION);
340 
341         mScanResultsAvailableReceiver = new WifiScanReceiver(mEventFacade);
342         mWifiScanResultsReceiver = new WifiScanResultsReceiver(mEventFacade);
343         mStateChangeReceiver = new WifiStateChangeReceiver();
344         mNetworkSuggestionStateChangeReceiver = new WifiNetworkSuggestionStateChangeReceiver();
345         mTrackingWifiStateChange = false;
346         mTrackingTetherStateChange = false;
347         mTrackingNetworkSuggestionStateChange = false;
348         mCallbackHandlerThread.start();
349         mSoftapCallbacks = new SparseArray<>();
350         if (SdkLevel.isAtLeastS()) {
351             mCoexCallback = new CoexCallbackImpl(mEventFacade);
352         }
353     }
354 
makeLock(int wifiMode)355     private void makeLock(int wifiMode) {
356         if (mLock == null) {
357             mLock = mWifi.createWifiLock(wifiMode, "sl4a");
358             mLock.acquire();
359         }
360     }
361 
362     /**
363      * Handle Broadcast receiver for Scan Result
364      *
365      * @parm eventFacade Object of EventFacade
366      */
367     class WifiScanReceiver extends BroadcastReceiver {
368         private final EventFacade mEventFacade;
369 
WifiScanReceiver(EventFacade eventFacade)370         WifiScanReceiver(EventFacade eventFacade) {
371             mEventFacade = eventFacade;
372         }
373 
374         @Override
onReceive(Context c, Intent intent)375         public void onReceive(Context c, Intent intent) {
376             String action = intent.getAction();
377             if (action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
378                 if (!intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false)) {
379                     Log.w("Wifi connection scan failed, ignoring.");
380                     mEventFacade.postEvent(mEventType + "ScanFailure", null);
381                 } else {
382                     Bundle mResults = new Bundle();
383                     Log.d("Wifi connection scan finished, results available.");
384                     mResults.putLong("Timestamp", System.currentTimeMillis() / 1000);
385                     mEventFacade.postEvent(mEventType + "ScanResultsAvailable", mResults);
386                 }
387                 mService.unregisterReceiver(mScanResultsAvailableReceiver);
388             }
389         }
390     }
391 
392     class WifiScanResultsReceiver extends WifiManager.ScanResultsCallback {
393         private final EventFacade mEventFacade;
394 
WifiScanResultsReceiver(EventFacade eventFacade)395         WifiScanResultsReceiver(EventFacade eventFacade) {
396             mEventFacade = eventFacade;
397         }
398         @Override
onScanResultsAvailable()399         public void onScanResultsAvailable() {
400             Bundle mResults = new Bundle();
401             Log.d("Wifi connection scan finished, results available.");
402             mResults.putLong("Timestamp", System.currentTimeMillis() / 1000);
403             mEventFacade.postEvent(mEventType + "ScanResultsCallbackOnSuccess", mResults);
404             mWifi.unregisterScanResultsCallback(mWifiScanResultsReceiver);
405         }
406     }
407 
408     class WifiActionListener implements WifiManager.ActionListener {
409         private final EventFacade mEventFacade;
410         private final String TAG;
411 
WifiActionListener(EventFacade eventFacade, String tag)412         public WifiActionListener(EventFacade eventFacade, String tag) {
413             mEventFacade = eventFacade;
414             this.TAG = tag;
415         }
416 
417         @Override
onSuccess()418         public void onSuccess() {
419             Log.d("WifiActionListener  onSuccess called for " + mEventType + TAG + "OnSuccess");
420             mEventFacade.postEvent(mEventType + TAG + "OnSuccess", null);
421         }
422 
423         @Override
onFailure(int reason)424         public void onFailure(int reason) {
425             Log.d("WifiActionListener  onFailure called for" + mEventType);
426             Bundle msg = new Bundle();
427             msg.putInt("reason", reason);
428             mEventFacade.postEvent(mEventType + TAG + "OnFailure", msg);
429         }
430     }
431 
432     public class WifiStateChangeReceiver extends BroadcastReceiver {
433         String mCachedWifiInfo = "";
434 
435         /**
436          * When a peer to peer request is active, WifiManager.getConnectionInfo() returns
437          * the peer to peer connection details. Hence use networking API's to retrieve the
438          * internet connection details.
439          *
440          * But on Android R, we will need to fallback to the legacy getConnectionInfo() API since
441          * WifiInfo doesn't implement TransportInfo.
442          */
getInternetConnectivityWifiInfo()443         private WifiInfo getInternetConnectivityWifiInfo() {
444             if (!SdkLevel.isAtLeastS()) {
445                 return mWifi.getConnectionInfo();
446             }
447             // TODO (b/156867433): We need a location sensitive synchronous API proposed
448             // in aosp/1629501.
449             final CountDownLatch waitForNetwork = new CountDownLatch(1);
450             final class AnswerBox {
451                 public WifiInfo wifiInfo;
452             }
453             final AnswerBox answerBox = new AnswerBox();
454             final NetworkCallback networkCallback =
455                     new NetworkCallback(NetworkCallback.FLAG_INCLUDE_LOCATION_INFO) {
456                 @Override
457                 public void onCapabilitiesChanged(@NonNull Network network,
458                         @NonNull NetworkCapabilities networkCapabilities) {
459                     answerBox.wifiInfo = (WifiInfo) networkCapabilities.getTransportInfo();
460                     waitForNetwork.countDown();
461                 }
462             };
463             mCm.registerNetworkCallback(
464                     new NetworkRequest.Builder()
465                             .addTransportType(TRANSPORT_WIFI)
466                             .addCapability(NET_CAPABILITY_INTERNET)
467                             .build(), networkCallback);
468             try {
469                 if (!waitForNetwork.await(5, TimeUnit.SECONDS)) {
470                     Log.e("Timed out waiting for network to connect");
471                     return null;
472                 }
473                 return answerBox.wifiInfo;
474             } catch (InterruptedException e) {
475                 Log.e("Waiting for onAvailable failed", e);
476                 return null;
477             } finally {
478                 mCm.unregisterNetworkCallback(networkCallback);
479             }
480         }
481 
482         @Override
onReceive(Context context, Intent intent)483         public void onReceive(Context context, Intent intent) {
484             String action = intent.getAction();
485             if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
486                 Log.d("Wifi network state changed.");
487                 NetworkInfo nInfo = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
488                 Log.d("NetworkInfo " + nInfo);
489                 // If network info is of type wifi, send wifi events.
490                 if (nInfo.getType() == ConnectivityManager.TYPE_WIFI) {
491                     if (nInfo.getDetailedState().equals(DetailedState.CONNECTED)) {
492                         WifiInfo wInfo = getInternetConnectivityWifiInfo();
493                         if (wInfo == null) {
494                             Log.e("Failed to get WifiInfo for internet connection. "
495                                     + "Not sending wifi network connection event");
496                             return;
497                         }
498                         String bssid = wInfo.getBSSID();
499                         if (bssid != null && !mCachedWifiInfo.equals(wInfo.toString())) {
500                             Log.d("WifiNetworkConnected");
501                             mEventFacade.postEvent("WifiNetworkConnected", wInfo);
502                         }
503                         mCachedWifiInfo = wInfo.toString();
504                     } else {
505                         if (nInfo.getDetailedState().equals(DetailedState.DISCONNECTED)) {
506                             if (!mCachedWifiInfo.equals("")) {
507                                 mCachedWifiInfo = "";
508                                 mEventFacade.postEvent("WifiNetworkDisconnected", null);
509                             }
510                         }
511                     }
512                 }
513             } else if (action.equals(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION)) {
514                 Log.d("Supplicant connection state changed.");
515                 mIsConnected = intent
516                         .getBooleanExtra(WifiManager.EXTRA_SUPPLICANT_CONNECTED, false);
517                 Bundle msg = new Bundle();
518                 msg.putBoolean("Connected", mIsConnected);
519                 mEventFacade.postEvent("SupplicantConnectionChanged", msg);
520             } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
521                 int state = intent.getIntExtra(
522                         WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_DISABLED);
523                 Log.d("Wifi state changed to " + state);
524                 boolean enabled;
525                 if (state == WifiManager.WIFI_STATE_DISABLED) {
526                     enabled = false;
527                 } else if (state == WifiManager.WIFI_STATE_ENABLED) {
528                     enabled = true;
529                 } else {
530                     // we only care about enabled/disabled.
531                     Log.v("Ignoring intermediate wifi state change event...");
532                     return;
533                 }
534                 Bundle msg = new Bundle();
535                 msg.putBoolean("enabled", enabled);
536                 mEventFacade.postEvent("WifiStateChanged", msg);
537             }
538         }
539     }
540 
541     public class WifiNetworkSuggestionStateChangeReceiver extends BroadcastReceiver {
542         @Override
onReceive(Context context, Intent intent)543         public void onReceive(Context context, Intent intent) {
544             String action = intent.getAction();
545             if (action.equals(WifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION)) {
546                 WifiNetworkSuggestion networkSuggestion =
547                         intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_SUGGESTION);
548                 mEventFacade.postEvent(
549                         "WifiNetworkSuggestionPostConnection",
550                         networkSuggestion.wifiConfiguration.SSID);
551             }
552         }
553     }
554 
555     public class WifiWpsCallback extends WifiManager.WpsCallback {
556         private static final String tag = "WifiWps";
557 
558         @Override
onStarted(String pin)559         public void onStarted(String pin) {
560             Bundle msg = new Bundle();
561             msg.putString("pin", pin);
562             mEventFacade.postEvent(tag + "OnStarted", msg);
563         }
564 
565         @Override
onSucceeded()566         public void onSucceeded() {
567             Log.d("Wps op succeeded");
568             mEventFacade.postEvent(tag + "OnSucceeded", null);
569         }
570 
571         @Override
onFailed(int reason)572         public void onFailed(int reason) {
573             Bundle msg = new Bundle();
574             msg.putInt("reason", reason);
575             mEventFacade.postEvent(tag + "OnFailed", msg);
576         }
577     }
578 
applyingkeyMgmt(WifiConfiguration config, ScanResult result)579     private void applyingkeyMgmt(WifiConfiguration config, ScanResult result) {
580         if (result.capabilities.contains("WEP")) {
581             config.allowedKeyManagement.set(KeyMgmt.NONE);
582             config.allowedAuthAlgorithms.set(AuthAlgorithm.OPEN);
583             config.allowedAuthAlgorithms.set(AuthAlgorithm.SHARED);
584         } else if (result.capabilities.contains("PSK")) {
585             config.allowedKeyManagement.set(KeyMgmt.WPA_PSK);
586         } else if (result.capabilities.contains("EAP")) {
587             // this is probably wrong, as we don't have a way to enter the enterprise config
588             config.allowedKeyManagement.set(KeyMgmt.WPA_EAP);
589             config.allowedKeyManagement.set(KeyMgmt.IEEE8021X);
590         } else {
591             config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
592         }
593     }
594 
genWifiConfig(JSONObject j)595     private WifiConfiguration genWifiConfig(JSONObject j) throws JSONException {
596         if (j == null) {
597             return null;
598         }
599         WifiConfiguration config = new WifiConfiguration();
600         if (j.has("SSID")) {
601             config.SSID = "\"" + j.getString("SSID") + "\"";
602         } else if (j.has("ssid")) {
603             config.SSID = "\"" + j.getString("ssid") + "\"";
604         }
605         if (j.has("password")) {
606             String security;
607 
608             // Check if new security type SAE (WPA3) is present. Default to PSK
609             if (j.has("security")) {
610                 if (TextUtils.equals(j.getString("security"), "SAE")) {
611                     config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE);
612                 } else {
613                     config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
614                 }
615             } else {
616                 config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
617             }
618             config.preSharedKey = "\"" + j.getString("password") + "\"";
619         } else if (j.has("preSharedKey")) {
620             config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK);
621             config.preSharedKey = j.getString("preSharedKey");
622         } else {
623             if (j.has("security")) {
624                 if (TextUtils.equals(j.getString("security"), "OWE")) {
625                     config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OWE);
626                 } else {
627                     config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN);
628                 }
629             } else {
630                 config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN);
631             }
632         }
633         if (j.has("BSSID")) {
634             config.BSSID = j.getString("BSSID");
635         }
636         if (j.has("hiddenSSID")) {
637             config.hiddenSSID = j.getBoolean("hiddenSSID");
638         }
639         if (j.has("priority")) {
640             config.priority = j.getInt("priority");
641         }
642         if (j.has("apBand")) {
643             config.apBand = j.getInt("apBand");
644         }
645         if (j.has("wepKeys")) {
646             // Looks like we only support static WEP.
647             config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_WEP);
648             JSONArray keys = j.getJSONArray("wepKeys");
649             String[] wepKeys = new String[keys.length()];
650             for (int i = 0; i < keys.length(); i++) {
651                 wepKeys[i] = keys.getString(i);
652             }
653             config.wepKeys = wepKeys;
654         }
655         if (j.has("wepTxKeyIndex")) {
656             config.wepTxKeyIndex = j.getInt("wepTxKeyIndex");
657         }
658         if (j.has("meteredOverride")) {
659             config.meteredOverride = j.getInt("meteredOverride");
660         }
661         if (j.has("macRand")) {
662             config.macRandomizationSetting = j.getInt("macRand");
663         }
664         if (j.has("carrierId")) {
665             config.carrierId = j.getInt("carrierId");
666         }
667         return config;
668     }
669 
genWifiEnterpriseConfig(JSONObject j)670     private static WifiEnterpriseConfig genWifiEnterpriseConfig(JSONObject j) throws JSONException,
671             GeneralSecurityException {
672         WifiEnterpriseConfig eConfig = new WifiEnterpriseConfig();
673         if (j.has(WifiEnterpriseConfig.EAP_KEY)) {
674             int eap = j.getInt(WifiEnterpriseConfig.EAP_KEY);
675             eConfig.setEapMethod(eap);
676         }
677         if (j.has(WifiEnterpriseConfig.PHASE2_KEY)) {
678             int p2Method = j.getInt(WifiEnterpriseConfig.PHASE2_KEY);
679             eConfig.setPhase2Method(p2Method);
680         }
681         if (j.has(WifiEnterpriseConfig.CA_CERT_KEY)) {
682             String certStr = j.getString(WifiEnterpriseConfig.CA_CERT_KEY);
683             Log.v("CA Cert String is " + certStr);
684             eConfig.setCaCertificate(strToX509Cert(certStr));
685         }
686         if (j.has(WifiEnterpriseConfig.CLIENT_CERT_KEY)
687                 && j.has(WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY)) {
688             String certStr = j.getString(WifiEnterpriseConfig.CLIENT_CERT_KEY);
689             String keyStr = j.getString(WifiEnterpriseConfig.PRIVATE_KEY_ID_KEY);
690             Log.v("Client Cert String is " + certStr);
691             Log.v("Client Key String is " + keyStr);
692             X509Certificate cert = strToX509Cert(certStr);
693             String certAlgo = "RSA";
694             if (j.has("cert_algo")) {
695                 certAlgo = j.getString("cert_algo");
696             }
697             PrivateKey privKey = strToPrivateKey(keyStr, certAlgo);
698             Log.v("Cert is " + cert);
699             Log.v("Private Key is " + privKey);
700             eConfig.setClientKeyEntry(privKey, cert);
701         }
702         if (j.has(WifiEnterpriseConfig.IDENTITY_KEY)) {
703             String identity = j.getString(WifiEnterpriseConfig.IDENTITY_KEY);
704             Log.v("Setting identity to " + identity);
705             eConfig.setIdentity(identity);
706         }
707         if (j.has(WifiEnterpriseConfig.PASSWORD_KEY)) {
708             String pwd = j.getString(WifiEnterpriseConfig.PASSWORD_KEY);
709             Log.v("Setting password to " + pwd);
710             eConfig.setPassword(pwd);
711         }
712         if (j.has(WifiEnterpriseConfig.ALTSUBJECT_MATCH_KEY)) {
713             String altSub = j.getString(WifiEnterpriseConfig.ALTSUBJECT_MATCH_KEY);
714             Log.v("Setting Alt Subject to " + altSub);
715             eConfig.setAltSubjectMatch(altSub);
716         }
717         if (j.has(WifiEnterpriseConfig.DOM_SUFFIX_MATCH_KEY)) {
718             String domSuffix = j.getString(WifiEnterpriseConfig.DOM_SUFFIX_MATCH_KEY);
719             Log.v("Setting Domain Suffix Match to " + domSuffix);
720             eConfig.setDomainSuffixMatch(domSuffix);
721         }
722         if (j.has(WifiEnterpriseConfig.REALM_KEY)) {
723             String realm = j.getString(WifiEnterpriseConfig.REALM_KEY);
724             Log.v("Setting Domain Suffix Match to " + realm);
725             eConfig.setRealm(realm);
726         }
727         if (j.has(WifiEnterpriseConfig.OCSP)) {
728             int ocsp = j.getInt(WifiEnterpriseConfig.OCSP);
729             Log.v("Setting OCSP to " + ocsp);
730             eConfig.setOcsp(ocsp);
731         }
732         return eConfig;
733     }
734 
genWifiConfigWithEnterpriseConfig(JSONObject j)735     private WifiConfiguration genWifiConfigWithEnterpriseConfig(JSONObject j) throws JSONException,
736             GeneralSecurityException {
737         if (j == null) {
738             return null;
739         }
740         WifiConfiguration config = new WifiConfiguration();
741         config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP);
742 
743         if (j.has("security")) {
744             if (TextUtils.equals(j.getString("security"), "SUITE_B_192")) {
745                 config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B);
746             }
747         }
748 
749         if (j.has("SSID")) {
750             config.SSID = "\"" + j.getString("SSID") + "\"";
751         } else if (j.has("ssid")) {
752             config.SSID = "\"" + j.getString("ssid") + "\"";
753         }
754         if (j.has("FQDN")) {
755             config.FQDN = j.getString("FQDN");
756         }
757         if (j.has("providerFriendlyName")) {
758             config.providerFriendlyName = j.getString("providerFriendlyName");
759         }
760         if (j.has("roamingConsortiumIds")) {
761             JSONArray ids = j.getJSONArray("roamingConsortiumIds");
762             long[] rIds = new long[ids.length()];
763             for (int i = 0; i < ids.length(); i++) {
764                 rIds[i] = ids.getLong(i);
765             }
766             config.roamingConsortiumIds = rIds;
767         }
768         if (j.has("carrierId")) {
769             config.carrierId = j.getInt("carrierId");
770         }
771         config.enterpriseConfig = genWifiEnterpriseConfig(j);
772         return config;
773     }
774 
775     /**
776      * Generate {@link WifiNetworkSpecifier} from the specified json.
777      */
genWifiNetworkSpecifier(JSONObject j)778     public static NetworkSpecifier genWifiNetworkSpecifier(JSONObject j) throws JSONException,
779             GeneralSecurityException {
780         if (j == null) {
781             return null;
782         }
783         WifiNetworkSpecifier.Builder builder = new WifiNetworkSpecifier.Builder();
784         if (j.has("SSID")) {
785             builder = builder.setSsid(j.getString("SSID"));
786         } else if (j.has("ssidPattern")) {
787             builder = builder.setSsidPattern(
788                     new PatternMatcher(j.getString("ssidPattern"),
789                             PatternMatcher.PATTERN_ADVANCED_GLOB));
790         }
791         if (j.has("BSSID")) {
792             builder = builder.setBssid(MacAddress.fromString(j.getString("BSSID")));
793         } else if (j.has("bssidPattern")) {
794             builder = builder.setBssidPattern(
795                     MacAddress.fromString(j.getJSONArray("bssidPattern").getString(0)),
796                     MacAddress.fromString(j.getJSONArray("bssidPattern").getString(1)));
797         }
798         if (j.has("hiddenSSID")) {
799             builder = builder.setIsHiddenSsid(j.getBoolean("hiddenSSID"));
800         }
801         if (j.has("isEnhancedOpen")) {
802             builder = builder.setIsEnhancedOpen(j.getBoolean("isEnhancedOpen"));
803         }
804         boolean isWpa3 = false;
805         if (j.has("isWpa3") && j.getBoolean("isWpa3")) {
806             isWpa3 = true;
807         }
808         if (j.has("password") && !j.has(WifiEnterpriseConfig.EAP_KEY)) {
809             if (!isWpa3) {
810                 builder = builder.setWpa2Passphrase(j.getString("password"));
811             } else {
812                 builder = builder.setWpa3Passphrase(j.getString("password"));
813             }
814         }
815         if (j.has(WifiEnterpriseConfig.EAP_KEY)) {
816             if (!isWpa3) {
817                 builder = builder.setWpa2EnterpriseConfig(genWifiEnterpriseConfig(j));
818             } else {
819                 builder = builder.setWpa3EnterpriseConfig(genWifiEnterpriseConfig(j));
820             }
821         }
822         return builder.build();
823     }
824 
genWifiNetworkSuggestion(JSONObject j)825     private WifiNetworkSuggestion genWifiNetworkSuggestion(JSONObject j) throws JSONException,
826             GeneralSecurityException, IOException {
827         if (j == null) {
828             return null;
829         }
830         WifiNetworkSuggestion.Builder builder = new WifiNetworkSuggestion.Builder();
831         if (j.has("isAppInteractionRequired")) {
832             builder = builder.setIsAppInteractionRequired(j.getBoolean("isAppInteractionRequired"));
833         }
834         if (j.has("isUserInteractionRequired")) {
835             builder = builder.setIsUserInteractionRequired(
836                     j.getBoolean("isUserInteractionRequired"));
837         }
838         if (j.has("isMetered")) {
839             builder = builder.setIsMetered(j.getBoolean("isMetered"));
840         }
841         if (j.has("priority")) {
842             builder = builder.setPriority(j.getInt("priority"));
843         }
844         if (j.has("carrierId")) {
845             builder.setCarrierId(j.getInt("carrierId"));
846         }
847         if (j.has("enableAutojoin")) {
848             builder.setIsInitialAutojoinEnabled(j.getBoolean("enableAutojoin"));
849         }
850         if (j.has("untrusted")) {
851             builder.setUntrusted(j.getBoolean("untrusted"));
852         }
853         if (j.has("profile")) {
854             builder = builder.setPasspointConfig(genWifiPasspointConfig(j));
855         } else {
856             if (j.has("SSID")) {
857                 builder = builder.setSsid(j.getString("SSID"));
858             }
859             if (j.has("BSSID")) {
860                 builder = builder.setBssid(MacAddress.fromString(j.getString("BSSID")));
861             }
862             if (j.has("hiddenSSID")) {
863                 builder = builder.setIsHiddenSsid(j.getBoolean("hiddenSSID"));
864             }
865             if (j.has("isEnhancedOpen")) {
866                 builder = builder.setIsEnhancedOpen(j.getBoolean("isEnhancedOpen"));
867             }
868             boolean isWpa3 = false;
869             if (j.has("isWpa3") && j.getBoolean("isWpa3")) {
870                 isWpa3 = true;
871             }
872             if (j.has("password") && !j.has(WifiEnterpriseConfig.EAP_KEY)) {
873                 if (!isWpa3) {
874                     builder = builder.setWpa2Passphrase(j.getString("password"));
875                 } else {
876                     builder = builder.setWpa3Passphrase(j.getString("password"));
877                 }
878             }
879             if (j.has(WifiEnterpriseConfig.EAP_KEY)) {
880                 if (!isWpa3) {
881                     builder = builder.setWpa2EnterpriseConfig(genWifiEnterpriseConfig(j));
882                 } else {
883                     builder = builder.setWpa3EnterpriseConfig(genWifiEnterpriseConfig(j));
884                 }
885             }
886         }
887         if (j.has("enhancedMacRandomizationEnabled")
888                 && j.getBoolean("enhancedMacRandomizationEnabled")) {
889             builder = builder.setMacRandomizationSetting(
890                 WifiNetworkSuggestion.RANDOMIZATION_NON_PERSISTENT);
891         }
892 
893         return builder.build();
894     }
895 
genWifiNetworkSuggestions( JSONArray jsonNetworkSuggestionsArray)896     private List<WifiNetworkSuggestion> genWifiNetworkSuggestions(
897             JSONArray jsonNetworkSuggestionsArray) throws JSONException, GeneralSecurityException,
898             IOException {
899         if (jsonNetworkSuggestionsArray == null) {
900             return null;
901         }
902         List<WifiNetworkSuggestion> networkSuggestions = new ArrayList<>();
903         for (int i = 0; i < jsonNetworkSuggestionsArray.length(); i++) {
904             networkSuggestions.add(
905                     genWifiNetworkSuggestion(jsonNetworkSuggestionsArray.getJSONObject(i)));
906         }
907         return networkSuggestions;
908     }
909 
matchScanResult(ScanResult result, String id)910     private boolean matchScanResult(ScanResult result, String id) {
911         if (result.BSSID.equals(id) || result.SSID.equals(id)) {
912             return true;
913         }
914         return false;
915     }
916 
parseWpsInfo(String infoStr)917     private WpsInfo parseWpsInfo(String infoStr) throws JSONException {
918         if (infoStr == null) {
919             return null;
920         }
921         JSONObject j = new JSONObject(infoStr);
922         WpsInfo info = new WpsInfo();
923         if (j.has("setup")) {
924             info.setup = j.getInt("setup");
925         }
926         if (j.has("BSSID")) {
927             info.BSSID = j.getString("BSSID");
928         }
929         if (j.has("pin")) {
930             info.pin = j.getString("pin");
931         }
932         return info;
933     }
934 
base64StrToBytes(String input)935     private static byte[] base64StrToBytes(String input) {
936         return Base64.decode(input, Base64.DEFAULT);
937     }
938 
strToX509Cert(String certStr)939     private static X509Certificate strToX509Cert(String certStr) throws CertificateException {
940         byte[] certBytes = base64StrToBytes(certStr);
941         InputStream certStream = new ByteArrayInputStream(certBytes);
942         CertificateFactory cf = CertificateFactory.getInstance("X509");
943         return (X509Certificate) cf.generateCertificate(certStream);
944     }
945 
strToPrivateKey(String key, String algo)946     private static PrivateKey strToPrivateKey(String key, String algo)
947             throws NoSuchAlgorithmException, InvalidKeySpecException {
948         byte[] keyBytes = base64StrToBytes(key);
949         PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
950         KeyFactory fact = KeyFactory.getInstance(algo);
951         PrivateKey priv = fact.generatePrivate(keySpec);
952         return priv;
953     }
954 
strToPublicKey(String key)955     private PublicKey strToPublicKey(String key) throws NoSuchAlgorithmException,
956             InvalidKeySpecException {
957         byte[] keyBytes = base64StrToBytes(key);
958         X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
959         KeyFactory fact = KeyFactory.getInstance("RSA");
960         PublicKey pub = fact.generatePublic(keySpec);
961         return pub;
962     }
963 
wifiConfigurationFromScanResult(ScanResult result)964     private WifiConfiguration wifiConfigurationFromScanResult(ScanResult result) {
965         if (result == null)
966             return null;
967         WifiConfiguration config = new WifiConfiguration();
968         config.SSID = "\"" + result.SSID + "\"";
969         applyingkeyMgmt(config, result);
970         config.BSSID = result.BSSID;
971         return config;
972     }
973 
974     @Rpc(description = "test.")
wifiTest( @pcParametername = "certString") String certString)975     public String wifiTest(
976             @RpcParameter(name = "certString") String certString) throws CertificateException, IOException {
977         // TODO(angli): Make this work. Convert a X509Certificate back to a string.
978         X509Certificate caCert = strToX509Cert(certString);
979         caCert.getEncoded();
980         ByteArrayOutputStream bos = new ByteArrayOutputStream();
981         ObjectOutput out = new ObjectOutputStream(bos);
982         out.writeObject(caCert);
983         byte[] data = bos.toByteArray();
984         bos.close();
985         return Base64.encodeToString(data, Base64.DEFAULT);
986     }
987 
988     @Rpc(description = "Add a network.")
989     @Deprecated
wifiAddNetwork(@pcParametername = "wifiConfig") JSONObject wifiConfig)990     public Integer wifiAddNetwork(@RpcParameter(name = "wifiConfig") JSONObject wifiConfig)
991             throws JSONException {
992         return wifiaddOrUpdateNetwork(wifiConfig);
993     }
994 
995     @Rpc(description = "Add or update a network.")
wifiaddOrUpdateNetwork(@pcParametername = "wifiConfig") JSONObject wifiConfig)996     public Integer wifiaddOrUpdateNetwork(@RpcParameter(name = "wifiConfig") JSONObject wifiConfig)
997             throws JSONException {
998         return mWifi.addNetwork(genWifiConfig(wifiConfig));
999     }
1000 
1001     @Rpc(description = "Cancel Wi-fi Protected Setup.")
wifiCancelWps()1002     public void wifiCancelWps() throws JSONException {
1003         WifiWpsCallback listener = new WifiWpsCallback();
1004         mWifi.cancelWps(listener);
1005     }
1006 
1007     @Rpc(description = "Checks Wifi state.", returns = "True if Wifi is enabled.")
wifiCheckState()1008     public Boolean wifiCheckState() {
1009         return mWifi.getWifiState() == WifiManager.WIFI_STATE_ENABLED;
1010     }
1011 
1012     /**
1013     * @deprecated Use {@link #wifiConnectByConfig(config)} instead.
1014     */
1015     @Rpc(description = "Connects to the network with the given configuration")
1016     @Deprecated
wifiConnect(@pcParametername = "config") JSONObject config)1017     public Boolean wifiConnect(@RpcParameter(name = "config") JSONObject config)
1018             throws JSONException {
1019         try {
1020             wifiConnectByConfig(config);
1021         } catch (GeneralSecurityException e) {
1022             String msg = "Caught GeneralSecurityException with the provided"
1023                          + "configuration";
1024             throw new RuntimeException(msg);
1025         }
1026         return true;
1027     }
1028 
1029     /**
1030     * @deprecated Use {@link #wifiConnectByConfig(config)} instead.
1031     */
1032     @Rpc(description = "Connects to the network with the given configuration")
1033     @Deprecated
wifiEnterpriseConnect(@pcParametername = "config") JSONObject config)1034     public Boolean wifiEnterpriseConnect(@RpcParameter(name = "config")
1035             JSONObject config) throws JSONException, GeneralSecurityException {
1036         try {
1037             wifiConnectByConfig(config);
1038         } catch (GeneralSecurityException e) {
1039             throw e;
1040         }
1041         return true;
1042     }
1043 
1044     /**
1045      * Connects to a wifi network using configuration.
1046      * @param config JSONObject Dictionary of wifi connection parameters
1047      * @throws JSONException
1048      * @throws GeneralSecurityException
1049      */
1050     @Rpc(description = "Connects to the network with the given configuration")
wifiConnectByConfig(@pcParametername = "config") JSONObject config)1051     public void wifiConnectByConfig(@RpcParameter(name = "config") JSONObject config)
1052             throws JSONException, GeneralSecurityException {
1053         WifiConfiguration wifiConfig;
1054         WifiActionListener listener;
1055         // Check if this is 802.1x or 802.11x config.
1056         if (config.has(WifiEnterpriseConfig.EAP_KEY)) {
1057             wifiConfig = genWifiConfigWithEnterpriseConfig(config);
1058         } else {
1059             wifiConfig = genWifiConfig(config);
1060         }
1061         listener = new WifiActionListener(mEventFacade,
1062                 WifiConstants.WIFI_CONNECT_BY_CONFIG_CALLBACK);
1063         mWifi.connect(wifiConfig, listener);
1064     }
1065 
1066     /**
1067     * Gets the Wi-Fi factory MAC addresses.
1068     * @return An array of String represnting Wi-Fi MAC addresses,
1069     *         Or an empty Srting if failed.
1070     */
1071     @Rpc(description = "Gets the Wi-Fi factory MAC addresses", returns = "An array of String, representing the MAC address")
wifigetFactorymacAddresses()1072     public String[] wifigetFactorymacAddresses(){
1073         return mWifi.getFactoryMacAddresses();
1074     }
1075 
1076     @Rpc(description = "Gets the randomized MAC address", returns = "A MAC address or null")
wifigetRandomizedMacAddress(@pcParametername = "config") JSONObject config)1077     public MacAddress wifigetRandomizedMacAddress(@RpcParameter(name = "config") JSONObject config)
1078             throws JSONException{
1079         List<WifiConfiguration> configList = mWifi.getConfiguredNetworks();
1080         for(WifiConfiguration WifiConfig : configList){
1081             String ssid = WifiConfig.SSID;
1082             ssid = ssid.replace("\"", "");
1083             if (ssid.equals(config.getString("SSID"))){
1084                 return WifiConfig.getRandomizedMacAddress();
1085             }
1086         }
1087         Log.d("Did not find a matching object in wifiManager.");
1088         return null;
1089     }
1090     /**
1091      * Generate a Passpoint configuration from JSON config.
1092      * @param config JSON config containing base64 encoded Passpoint profile
1093      */
1094     @Rpc(description = "Generate Passpoint configuration", returns = "PasspointConfiguration object")
genWifiPasspointConfig(@pcParameter name = "config") JSONObject config)1095     public PasspointConfiguration genWifiPasspointConfig(@RpcParameter(
1096             name = "config") JSONObject config)
1097             throws JSONException,CertificateException, IOException {
1098         String profileStr = "";
1099         if (config == null) {
1100             return null;
1101         }
1102         if (config.has("profile")) {
1103             profileStr =  config.getString("profile");
1104         }
1105         return ConfigParser.parsePasspointConfig(TYPE_WIFICONFIG,
1106                 profileStr.getBytes());
1107     }
1108 
1109     /**
1110      * Add or update a Passpoint configuration.
1111      * @param config base64 encoded message containing Passpoint profile
1112      * @throws JSONException
1113      */
1114     @Rpc(description = "Add or update a Passpoint configuration")
addUpdatePasspointConfig(@pcParameter name = "config") JSONObject config)1115     public void addUpdatePasspointConfig(@RpcParameter(
1116             name = "config") JSONObject config)
1117             throws JSONException,CertificateException, IOException {
1118         PasspointConfiguration passpointConfig = genWifiPasspointConfig(config);
1119         mWifi.addOrUpdatePasspointConfiguration(passpointConfig);
1120     }
1121 
1122     /**
1123      * Remove a Passpoint configuration.
1124      * @param fqdn The FQDN of the passpoint configuration to be removed
1125      * @return true on success; false otherwise
1126      */
1127     @Rpc(description = "Remove a Passpoint configuration")
removePasspointConfig( @pcParametername = "fqdn") String fqdn)1128     public void removePasspointConfig(
1129             @RpcParameter(name = "fqdn") String fqdn) {
1130         mWifi.removePasspointConfiguration(fqdn);
1131     }
1132 
1133     /**
1134      * Get list of Passpoint configurations.
1135      * @return A list of FQDNs of the Passpoint configurations
1136      */
1137     @Rpc(description = "Return the list of installed Passpoint configurations", returns = "A list of Passpoint configurations")
getPasspointConfigs()1138     public List<String> getPasspointConfigs() {
1139         List<String> fqdnList = new ArrayList<String>();
1140         for(PasspointConfiguration passpoint :
1141             mWifi.getPasspointConfigurations()) {
1142             fqdnList.add(passpoint.getHomeSp().getFqdn());
1143         }
1144         return fqdnList;
1145     }
1146 
1147     private class ProvisioningCallbackFacade extends ProvisioningCallback {
1148         private final EventFacade mEventFacade;
1149 
ProvisioningCallbackFacade(EventFacade eventFacade)1150         ProvisioningCallbackFacade(EventFacade eventFacade) {
1151             mEventFacade = eventFacade;
1152         }
1153 
1154         @Override
onProvisioningFailure(int status)1155         public void onProvisioningFailure(int status) {
1156             Log.v("Provisioning Failure " + status);
1157             Bundle msg = new Bundle();
1158             msg.putString("tag", "failure");
1159             msg.putInt("reason", status);
1160             mEventFacade.postEvent("onProvisioningCallback", msg);
1161         }
1162 
1163         @Override
onProvisioningStatus(int status)1164         public void onProvisioningStatus(int status) {
1165             Log.v("Provisioning status " + status);
1166             Bundle msg = new Bundle();
1167             msg.putString("tag", "status");
1168             msg.putInt("status", status);
1169             mEventFacade.postEvent("onProvisioningCallback", msg);
1170         }
1171 
1172         @Override
onProvisioningComplete()1173         public void onProvisioningComplete() {
1174             Log.v("Provisioning Complete");
1175             Bundle msg = new Bundle();
1176             msg.putString("tag", "success");
1177             mEventFacade.postEvent("onProvisioningCallback", msg);
1178         }
1179     }
1180 
1181     private class SubsystemRestartTrackingCallbackFacade extends SubsystemRestartTrackingCallback {
1182         private final EventFacade mEventFacade;
1183 
SubsystemRestartTrackingCallbackFacade(EventFacade eventFacade)1184         SubsystemRestartTrackingCallbackFacade(EventFacade eventFacade) {
1185             super();
1186             mEventFacade = eventFacade;
1187         }
1188 
1189         @Override
onSubsystemRestarting()1190         public void onSubsystemRestarting() {
1191             Log.v("onSubsystemRestarting");
1192             mEventFacade.postEvent("WifiSubsystemRestarting", null);
1193         }
1194 
1195         @Override
onSubsystemRestarted()1196         public void onSubsystemRestarted() {
1197             Log.v("onSubsystemRestarted");
1198             mEventFacade.postEvent("WifiSubsystemRestarted", null);
1199         }
1200     }
1201 
buildTestOsuProvider(JSONObject config)1202     private OsuProvider buildTestOsuProvider(JSONObject config) {
1203         String osuServiceDescription = "Google Passpoint Test Service";
1204         List<Integer> osuMethodList =
1205                 Arrays.asList(OsuProvider.METHOD_SOAP_XML_SPP);
1206 
1207         try {
1208             if (!config.has("osuSSID")) {
1209                 Log.e("missing osuSSID from the config");
1210                 return null;
1211             }
1212             String osuSsid = config.getString("osuSSID");
1213 
1214             if (!config.has("osuUri")) {
1215                 Log.e("missing osuUri from the config");
1216                 return null;
1217             }
1218             Uri osuServerUri = Uri.parse(config.getString("osuUri"));
1219 
1220             Log.v("OSU Server URI " + osuServerUri.toString());
1221             if (!config.has("osuFriendlyName")) {
1222                 Log.e("missing osuFriendlyName from the config");
1223                 return null;
1224             }
1225             String osuFriendlyName = config.getString("osuFriendlyName");
1226 
1227             if (config.has("description")) {
1228                 osuServiceDescription = config.getString("description");
1229             }
1230             Map<String, String> osuFriendlyNames = new HashMap<>();
1231             osuFriendlyNames.put("eng", osuFriendlyName);
1232             return new OsuProvider(osuSsid, osuFriendlyNames, osuServiceDescription,
1233                     osuServerUri, null, osuMethodList);
1234         } catch (JSONException e) {
1235             Log.e("JSON Parsing error: " + e);
1236             return null;
1237         }
1238     }
1239 
1240     /**
1241      * Start subscription provisioning
1242      */
1243     @Rpc(description = "Starts subscription provisioning flow")
startSubscriptionProvisioning( @pcParametername = "configJson") JSONObject configJson)1244     public void startSubscriptionProvisioning(
1245             @RpcParameter(name = "configJson") JSONObject configJson) {
1246         ProvisioningCallback callback = new ProvisioningCallbackFacade(mEventFacade);
1247         mWifi.startSubscriptionProvisioning(buildTestOsuProvider(configJson),
1248                 mService.getMainExecutor(), callback);
1249     }
1250 
1251     /**
1252      * Connects to a wifi network using networkId.
1253      * @param networkId the network identity for the network in the supplicant
1254      */
1255     @Rpc(description = "Connects to the network with the given networkId")
wifiConnectByNetworkId( @pcParametername = "networkId") Integer networkId)1256     public void wifiConnectByNetworkId(
1257             @RpcParameter(name = "networkId") Integer networkId) {
1258         WifiActionListener listener;
1259         listener = new WifiActionListener(mEventFacade,
1260                 WifiConstants.WIFI_CONNECT_BY_NETID_CALLBACK);
1261         mWifi.connect(networkId, listener);
1262     }
1263 
1264     @Rpc(description = "Disconnects from the currently active access point.", returns = "True if the operation succeeded.")
wifiDisconnect()1265     public Boolean wifiDisconnect() {
1266         return mWifi.disconnect();
1267     }
1268 
1269     @Rpc(description = "Enable/disable autojoin scan and switch network when connected.")
wifiSetEnableAutoJoinWhenAssociated(@pcParametername = "enable") Boolean enable)1270     public Boolean wifiSetEnableAutoJoinWhenAssociated(@RpcParameter(name = "enable") Boolean enable) {
1271         return mWifi.setEnableAutoJoinWhenAssociated(enable);
1272     }
1273 
1274     @Rpc(description = "Enable a configured network. Initiate a connection if disableOthers is true", returns = "True if the operation succeeded.")
wifiEnableNetwork(@pcParametername = "netId") Integer netId, @RpcParameter(name = "disableOthers") Boolean disableOthers)1275     public Boolean wifiEnableNetwork(@RpcParameter(name = "netId") Integer netId,
1276             @RpcParameter(name = "disableOthers") Boolean disableOthers) {
1277         return mWifi.enableNetwork(netId, disableOthers);
1278     }
1279 
1280     @Rpc(description = "Enable WiFi verbose logging.")
wifiEnableVerboseLogging(@pcParametername = "level") Integer level)1281     public void wifiEnableVerboseLogging(@RpcParameter(name = "level") Integer level) {
1282         mWifi.setVerboseLoggingEnabled(level > 0);
1283     }
1284 
1285     @Rpc(description = "Resets all WifiManager settings.")
wifiFactoryReset()1286     public void wifiFactoryReset() {
1287         mWifi.factoryReset();
1288     }
1289 
1290     /**
1291      * Forget a wifi network by networkId.
1292      *
1293      * @param networkId Id of wifi network
1294      */
1295     @Rpc(description = "Forget a wifi network by networkId")
wifiForgetNetwork(@pcParametername = "wifiSSID") Integer networkId)1296     public void wifiForgetNetwork(@RpcParameter(name = "wifiSSID") Integer networkId) {
1297         WifiActionListener listener = new WifiActionListener(mEventFacade,
1298                 WifiConstants.WIFI_FORGET_NETWORK_CALLBACK);
1299         mWifi.forget(networkId, listener);
1300     }
1301 
1302     /**
1303      * User disconnect network.
1304      *
1305      * @param ssid SSID of wifi network
1306      */
1307     @Rpc(description = "Disconnect a wifi network by SSID")
wifiUserDisconnectNetwork(@pcParametername = "ssid") String ssid)1308     public void wifiUserDisconnectNetwork(@RpcParameter(name = "ssid") String ssid) {
1309         mWifi.disableEphemeralNetwork("\"" + ssid + "\"");
1310         mWifi.disconnect();
1311     }
1312 
1313     /**
1314      * User disconnect passpoint network.
1315      *
1316      * @param fqdn FQDN of the passpoint network
1317      */
1318     @Rpc(description = "Disconnect a wifi network by FQDN")
wifiUserDisconnectPasspointNetwork(@pcParametername = "fqdn") String fqdn)1319     public void wifiUserDisconnectPasspointNetwork(@RpcParameter(name = "fqdn") String fqdn) {
1320         mWifi.disableEphemeralNetwork(fqdn);
1321         mWifi.disconnect();
1322     }
1323 
1324     /**
1325      * Get SoftAp Configuration with SoftApConfiguration.
1326      */
1327     @Rpc(description = "Gets the Wi-Fi AP Configuration.")
wifiGetApConfiguration()1328     public SoftApConfiguration wifiGetApConfiguration() {
1329         return mWifi.getSoftApConfiguration();
1330     }
1331 
1332     /**
1333      * Get SoftAp Configuration with WifiConfiguration.
1334      *
1335      * Used to test deprecated API to check backward compatible
1336      */
1337     @Rpc(description = "Gets the Wi-Fi AP Configuration with WifiConfiguration.")
wifiGetApConfigurationWithWifiConfiguration()1338     public WifiConfiguration wifiGetApConfigurationWithWifiConfiguration() {
1339         return mWifi.getWifiApConfiguration();
1340     }
1341 
1342     @Rpc(description = "Return a list of all the configured wifi networks.")
wifiGetConfiguredNetworks()1343     public List<WifiConfiguration> wifiGetConfiguredNetworks() {
1344         return mWifi.getConfiguredNetworks();
1345     }
1346 
1347     @Rpc(description = "Returns information about the currently active access point.")
wifiGetConnectionInfo()1348     public WifiInfo wifiGetConnectionInfo() {
1349         return mWifi.getConnectionInfo();
1350     }
1351 
1352     /**
1353      * Check if wifi network is temporary disabled.
1354      * @param config JSONObject Dictionary of wifi connection parameters.
1355      * @return True if network is disabled temporarily, False if not.
1356      */
1357     @Rpc(description = "Check if network is temporary disabled")
wifiIsNetworkTemporaryDisabledForNetwork( @pcParametername = "config") JSONObject config)1358     public boolean wifiIsNetworkTemporaryDisabledForNetwork(
1359             @RpcParameter(name = "config") JSONObject config)
1360                 throws JSONException, GeneralSecurityException {
1361         WifiConfiguration wifiConfig;
1362         if (config.has(WifiEnterpriseConfig.EAP_KEY)) {
1363             wifiConfig = genWifiConfigWithEnterpriseConfig(config);
1364         } else {
1365             wifiConfig = genWifiConfig(config);
1366         }
1367         List<WifiConfiguration> wifiConfigList = wifiGetConfiguredNetworks();
1368         for (WifiConfiguration conf : wifiConfigList) {
1369             if (conf.getSsidAndSecurityTypeString().equals(
1370                     wifiConfig.getSsidAndSecurityTypeString())) {
1371                 Log.d("Found matching config in the configured networks.");
1372                 return conf.getNetworkSelectionStatus().isNetworkTemporaryDisabled();
1373             }
1374         }
1375         Log.d("Wifi config is not in list of configured wifi networks.");
1376         return false;
1377     }
1378 
1379     /**
1380      * Get wifi standard for wifi connection.
1381      */
1382     @Rpc(description = "Return connection WiFi standard")
wifiGetConnectionStandard()1383     public Integer wifiGetConnectionStandard() {
1384         return mWifi.getConnectionInfo().getWifiStandard();
1385     }
1386 
1387     @Rpc(description = "Returns wifi activity and energy usage info.")
wifiGetControllerActivityEnergyInfo()1388     public WifiActivityEnergyInfo wifiGetControllerActivityEnergyInfo() {
1389         WifiActivityEnergyInfo[] mutable = {null};
1390         CountDownLatch latch = new CountDownLatch(1);
1391         mWifi.getWifiActivityEnergyInfoAsync(new Executor() {
1392             @Override
1393             public void execute(Runnable runnable) {
1394                 runnable.run();
1395             }
1396         }, info -> {
1397             mutable[0] = info;
1398             latch.countDown();
1399         });
1400         boolean completedSuccessfully = false;
1401         try {
1402             completedSuccessfully = latch.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
1403         } catch (InterruptedException e) {
1404             Log.w("Interrupted while awaiting for WifiManager.getWifiActivityEnergyInfoAsync()");
1405         }
1406         if (completedSuccessfully) {
1407             return mutable[0];
1408         } else {
1409             Log.w("WifiManager.getWifiActivityEnergyInfoAsync() timed out after "
1410                     + TIMEOUT_MILLIS + " milliseconds");
1411             return null;
1412         }
1413     }
1414 
1415     @Rpc(description = "Get the country code used by WiFi.")
wifiGetCountryCode()1416     public String wifiGetCountryCode() {
1417         return mWifi.getCountryCode();
1418     }
1419 
1420     @Rpc(description = "Get the current network.")
wifiGetCurrentNetwork()1421     public Network wifiGetCurrentNetwork() {
1422         return mWifi.getCurrentNetwork();
1423     }
1424 
1425     @Rpc(description = "Get the info from last successful DHCP request.")
wifiGetDhcpInfo()1426     public DhcpInfo wifiGetDhcpInfo() {
1427         return mWifi.getDhcpInfo();
1428     }
1429 
1430     @Rpc(description = "Get setting for Framework layer autojoin enable status.")
wifiGetEnableAutoJoinWhenAssociated()1431     public Boolean wifiGetEnableAutoJoinWhenAssociated() {
1432         return mWifi.getEnableAutoJoinWhenAssociated();
1433     }
1434 
1435     @Rpc(description = "Get privileged configured networks.")
wifiGetPrivilegedConfiguredNetworks()1436     public List<WifiConfiguration> wifiGetPrivilegedConfiguredNetworks() {
1437         return mWifi.getPrivilegedConfiguredNetworks();
1438     }
1439 
1440     @Rpc(description = "Returns the list of access points found during the most recent Wifi scan.")
wifiGetScanResults()1441     public List<ScanResult> wifiGetScanResults() {
1442         return mWifi.getScanResults();
1443     }
1444 
1445     @Rpc(description = "Get the current level of WiFi verbose logging.")
wifiGetVerboseLoggingLevel()1446     public Integer wifiGetVerboseLoggingLevel() {
1447         return mWifi.isVerboseLoggingEnabled() ? 1 : 0;
1448     }
1449 
1450     /**
1451      * Query whether or not the device supports concurrency of Station (STA) + multiple access
1452      * points (AP) (where the APs bridged together).
1453      *
1454      * @return true if this device supports concurrency of STA + multiple APs which are bridged
1455      *         together, false otherwise.
1456      */
1457     @Rpc(description = "true if this adapter supports STA + bridged Soft AP concurrency.")
wifiIsStaBridgedApConcurrencySupported()1458     public Boolean wifiIsStaBridgedApConcurrencySupported() {
1459         return mWifi.isStaBridgedApConcurrencySupported();
1460     }
1461 
1462     /**
1463      * Query whether or not the device supports multiple Access point (AP) which are bridged
1464      * together.
1465      *
1466      * @return true if this device supports concurrency of multiple AP which bridged together,
1467      *         false otherwise.
1468      */
1469     @Rpc(description = "true if this adapter supports bridged Soft AP concurrency.")
wifiIsBridgedApConcurrencySupported()1470     public Boolean wifiIsBridgedApConcurrencySupported() {
1471         return mWifi.isBridgedApConcurrencySupported();
1472     }
1473 
1474     @Rpc(description = "true if this adapter supports 5 GHz band.")
wifiIs5GHzBandSupported()1475     public Boolean wifiIs5GHzBandSupported() {
1476         return mWifi.is5GHzBandSupported();
1477     }
1478 
1479     @Rpc(description = "true if this adapter supports multiple simultaneous connections for"
1480             + "local only use-case.")
wifiIsStaConcurrencyForLocalOnlyConnectionsSupported()1481     public Boolean wifiIsStaConcurrencyForLocalOnlyConnectionsSupported() {
1482         return mWifi.isStaConcurrencyForLocalOnlyConnectionsSupported();
1483     }
1484 
1485     @Rpc(description = "true if this adapter supports multiple simultaneous connections for mbb "
1486             + "wifi to wifi switching.")
wifiIsMakeBeforeBreakWifiSwitchingSupported()1487     public Boolean wifiIsMakeBeforeBreakWifiSwitchingSupported() {
1488         return mWifi.isMakeBeforeBreakWifiSwitchingSupported();
1489     }
1490 
1491     @Rpc(description = "true if this adapter supports multiple simultaneous connections for "
1492             + "restricted connection use-case.")
wifiIsStaConcurrencyForRestrictedConnectionsSupported()1493     public Boolean wifiIsStaConcurrencyForRestrictedConnectionsSupported() {
1494         return mWifi.isStaConcurrencyForRestrictedConnectionsSupported();
1495     }
1496 
1497     @Rpc(description = "Return true if WiFi is enabled.")
wifiGetisWifiEnabled()1498     public Boolean wifiGetisWifiEnabled() {
1499         return mWifi.isWifiEnabled();
1500     }
1501 
1502     @Rpc(description = "Return whether Wi-Fi AP is enabled or disabled.")
wifiIsApEnabled()1503     public Boolean wifiIsApEnabled() {
1504         return mWifi.isWifiApEnabled();
1505     }
1506 
1507     @Rpc(description = "Check if Device-to-AP RTT is supported.")
wifiIsDeviceToApRttSupported()1508     public Boolean wifiIsDeviceToApRttSupported() {
1509         return mWifi.isDeviceToApRttSupported();
1510     }
1511 
1512     @Rpc(description = "Check if Device-to-device RTT is supported.")
wifiIsDeviceToDeviceRttSupported()1513     public Boolean wifiIsDeviceToDeviceRttSupported() {
1514         return mWifi.isDeviceToDeviceRttSupported();
1515     }
1516 
1517     /**
1518      * @return true if chipset supports 5GHz band and false otherwise.
1519      */
1520     @Rpc(description = "Check if the chipset supports 5GHz frequency band.")
is5GhzBandSupported()1521     public Boolean is5GhzBandSupported() {
1522         return mWifi.is5GHzBandSupported();
1523     }
1524 
1525     /**
1526      * @return true if chipset supports 6GHz band and false otherwise.
1527      */
1528     @Rpc(description = "Check if the chipset supports 6GHz frequency band.")
is6GhzBandSupported()1529     public Boolean is6GhzBandSupported() {
1530         return mWifi.is6GHzBandSupported();
1531     }
1532 
1533     @Rpc(description = "Check if this adapter supports advanced power/performance counters.")
wifiIsEnhancedPowerReportingSupported()1534     public Boolean wifiIsEnhancedPowerReportingSupported() {
1535         return mWifi.isEnhancedPowerReportingSupported();
1536     }
1537 
1538     @Rpc(description = "Check if multicast is enabled.")
wifiIsMulticastEnabled()1539     public Boolean wifiIsMulticastEnabled() {
1540         return mWifi.isMulticastEnabled();
1541     }
1542 
1543     @Rpc(description = "true if this adapter supports Wi-Fi Aware APIs.")
wifiIsAwareSupported()1544     public Boolean wifiIsAwareSupported() {
1545         return mWifi.isWifiAwareSupported();
1546     }
1547 
1548     @Rpc(description = "true if this adapter supports Off Channel Tunnel Directed Link Setup.")
wifiIsOffChannelTdlsSupported()1549     public Boolean wifiIsOffChannelTdlsSupported() {
1550         return mWifi.isOffChannelTdlsSupported();
1551     }
1552 
1553     @Rpc(description = "true if this adapter supports WifiP2pManager (Wi-Fi Direct).")
wifiIsP2pSupported()1554     public Boolean wifiIsP2pSupported() {
1555         return mWifi.isP2pSupported();
1556     }
1557 
1558     @Rpc(description = "true if this adapter supports passpoint.")
wifiIsPasspointSupported()1559     public Boolean wifiIsPasspointSupported() {
1560         return mWifi.isPasspointSupported();
1561     }
1562 
1563     @Rpc(description = "true if this adapter supports portable Wi-Fi hotspot.")
wifiIsPortableHotspotSupported()1564     public Boolean wifiIsPortableHotspotSupported() {
1565         return mWifi.isPortableHotspotSupported();
1566     }
1567 
1568     @Rpc(description = "true if this adapter supports offloaded connectivity scan.")
wifiIsPreferredNetworkOffloadSupported()1569     public Boolean wifiIsPreferredNetworkOffloadSupported() {
1570         return mWifi.isPreferredNetworkOffloadSupported();
1571     }
1572 
1573     @Rpc(description = "Check if wifi scanner is supported on this device.")
wifiIsScannerSupported()1574     public Boolean wifiIsScannerSupported() {
1575         return mWifi.isWifiScannerSupported();
1576     }
1577 
1578     @Rpc(description = "Check if tdls is supported on this device.")
wifiIsTdlsSupported()1579     public Boolean wifiIsTdlsSupported() {
1580         return mWifi.isTdlsSupported();
1581     }
1582 
1583     /**
1584      * @return true if this device supports WPA3-Personal SAE
1585      */
1586     @Rpc(description = "Check if WPA3-Personal SAE is supported on this device.")
wifiIsWpa3SaeSupported()1587     public Boolean wifiIsWpa3SaeSupported() {
1588         return mWifi.isWpa3SaeSupported();
1589     }
1590     /**
1591      * @return true if this device supports WPA3-Enterprise Suite-B-192
1592      */
1593     @Rpc(description = "Check if WPA3-Enterprise Suite-B-192 is supported on this device.")
wifiIsWpa3SuiteBSupported()1594     public Boolean wifiIsWpa3SuiteBSupported() {
1595         return mWifi.isWpa3SuiteBSupported();
1596     }
1597     /**
1598      * @return true if this device supports Wi-Fi Enhanced Open (OWE)
1599      */
1600     @Rpc(description = "Check if Enhanced Open (OWE) is supported on this device.")
wifiIsEnhancedOpenSupported()1601     public Boolean wifiIsEnhancedOpenSupported() {
1602         return mWifi.isEnhancedOpenSupported();
1603     }
1604 
1605     /**
1606      * @return true if this device supports Wi-Fi Device Provisioning Protocol (Easy-connect)
1607      * Enrollee Responder mode
1608      */
1609     @Rpc(description = "Check if Easy Connect (DPP) Enrollee responder mode is supported "
1610             + "on this device.")
wifiIsEasyConnectEnrolleeResponderModeSupported()1611     public Boolean wifiIsEasyConnectEnrolleeResponderModeSupported() {
1612         return mWifi.isEasyConnectEnrolleeResponderModeSupported();
1613     }
1614 
1615     /**
1616      * @return true if this device supports Wi-Fi Device Provisioning Protocol (Easy-connect)
1617      */
1618     @Rpc(description = "Check if Easy Connect (DPP) is supported on this device.")
wifiIsEasyConnectSupported()1619     public Boolean wifiIsEasyConnectSupported() {
1620         return mWifi.isEasyConnectSupported();
1621     }
1622 
1623     @Rpc(description = "Acquires a full Wifi lock.")
wifiLockAcquireFull()1624     public void wifiLockAcquireFull() {
1625         makeLock(WifiManager.WIFI_MODE_FULL);
1626     }
1627 
1628     @Rpc(description = "Acquires a scan only Wifi lock.")
wifiLockAcquireScanOnly()1629     public void wifiLockAcquireScanOnly() {
1630         makeLock(WifiManager.WIFI_MODE_SCAN_ONLY);
1631     }
1632 
1633     @Rpc(description = "Acquires a high performance Wifi lock.")
wifiLockAcquireFullHighPerf()1634     public void wifiLockAcquireFullHighPerf() {
1635         makeLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF);
1636     }
1637 
1638     @Rpc(description = "Acquires a low latency Wifi lock.")
wifiLockAcquireFullLowLatency()1639     public void wifiLockAcquireFullLowLatency() {
1640         makeLock(WifiManager.WIFI_MODE_FULL_LOW_LATENCY);
1641     }
1642 
1643     @Rpc(description = "Releases a previously acquired Wifi lock.")
wifiLockRelease()1644     public void wifiLockRelease() {
1645         if (mLock != null) {
1646             mLock.release();
1647             mLock = null;
1648         }
1649     }
1650 
1651     @Rpc(description = "Reassociates with the currently active access point.", returns = "True if the operation succeeded.")
wifiReassociate()1652     public Boolean wifiReassociate() {
1653         return mWifi.reassociate();
1654     }
1655 
1656     @Rpc(description = "Reconnects to the currently active access point.", returns = "True if the operation succeeded.")
wifiReconnect()1657     public Boolean wifiReconnect() {
1658         return mWifi.reconnect();
1659     }
1660 
1661     @Rpc(description = "Remove a configured network.", returns = "True if the operation succeeded.")
wifiRemoveNetwork(@pcParametername = "netId") Integer netId)1662     public Boolean wifiRemoveNetwork(@RpcParameter(name = "netId") Integer netId) {
1663         return mWifi.removeNetwork(netId);
1664     }
1665 
getApBandFromChannelFrequency(int freq)1666     private int getApBandFromChannelFrequency(int freq) {
1667         if (ScanResult.is24GHz(freq)) {
1668             return SoftApConfiguration.BAND_2GHZ;
1669         } else if (ScanResult.is5GHz(freq)) {
1670             return SoftApConfiguration.BAND_5GHZ;
1671         } else if (ScanResult.is6GHz(freq)) {
1672             return SoftApConfiguration.BAND_6GHZ;
1673         } else if (ScanResult.is60GHz(freq)) {
1674             return SoftApConfiguration.BAND_60GHZ;
1675         }
1676         return -1;
1677     }
1678 
convertJSONArrayToIntArray(JSONArray jArray)1679     private int[] convertJSONArrayToIntArray(JSONArray jArray) throws JSONException {
1680         if (jArray == null) {
1681             return null;
1682         }
1683         int[] iArray = new int[jArray.length()];
1684         for (int i = 0; i < jArray.length(); i++) {
1685             iArray[i] = jArray.getInt(i);
1686         }
1687         return iArray;
1688     }
1689 
createSoftApConfiguration(JSONObject configJson)1690     private SoftApConfiguration createSoftApConfiguration(JSONObject configJson)
1691             throws JSONException {
1692         if (configJson == null) {
1693             return null;
1694         }
1695         SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder();
1696         if (configJson.has("SSID")) {
1697             configBuilder.setSsid(configJson.getString("SSID"));
1698         }
1699         if (configJson.has("password")) {
1700             String pwd = configJson.getString("password");
1701             // Check if new security type SAE (WPA3) is present. Default to PSK
1702             if (configJson.has("security")) {
1703                 String securityType = configJson.getString("security");
1704                 if (TextUtils.equals(securityType, "WPA2_PSK")) {
1705                     configBuilder.setPassphrase(pwd, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
1706                 } else if (TextUtils.equals(securityType, "WPA3_SAE_TRANSITION")) {
1707                     configBuilder.setPassphrase(pwd,
1708                             SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION);
1709                 } else if (TextUtils.equals(securityType, "WPA3_SAE")) {
1710                     configBuilder.setPassphrase(pwd, SoftApConfiguration.SECURITY_TYPE_WPA3_SAE);
1711                 }
1712             } else {
1713                 configBuilder.setPassphrase(pwd, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
1714             }
1715         }
1716         if (configJson.has("BSSID")) {
1717             configBuilder.setBssid(MacAddress.fromString(configJson.getString("BSSID")));
1718         }
1719         if (configJson.has("hiddenSSID")) {
1720             configBuilder.setHiddenSsid(configJson.getBoolean("hiddenSSID"));
1721         }
1722         if (configJson.has("apBand")) {
1723             configBuilder.setBand(configJson.getInt("apBand"));
1724         }
1725         if (configJson.has("apChannel") && configJson.has("apBand")) {
1726             configBuilder.setChannel(configJson.getInt("apChannel"), configJson.getInt("apBand"));
1727         }
1728 
1729         if (configJson.has("MaxNumberOfClients")) {
1730             configBuilder.setMaxNumberOfClients(configJson.getInt("MaxNumberOfClients"));
1731         }
1732 
1733         if (configJson.has("ShutdownTimeoutMillis")) {
1734             configBuilder.setShutdownTimeoutMillis(configJson.getLong("ShutdownTimeoutMillis"));
1735         }
1736 
1737         if (configJson.has("AutoShutdownEnabled")) {
1738             configBuilder.setAutoShutdownEnabled(configJson.getBoolean("AutoShutdownEnabled"));
1739         }
1740 
1741         if (configJson.has("ClientControlByUserEnabled")) {
1742             configBuilder.setClientControlByUserEnabled(
1743                     configJson.getBoolean("ClientControlByUserEnabled"));
1744         }
1745 
1746         List allowedClientList = new ArrayList<>();
1747         if (configJson.has("AllowedClientList")) {
1748             JSONArray allowedList = configJson.getJSONArray("AllowedClientList");
1749             for (int i = 0; i < allowedList.length(); i++) {
1750                 allowedClientList.add(MacAddress.fromString(allowedList.getString(i)));
1751             }
1752         }
1753 
1754         List blockedClientList = new ArrayList<>();
1755         if (configJson.has("BlockedClientList")) {
1756             JSONArray blockedList = configJson.getJSONArray("BlockedClientList");
1757             for (int j = 0; j < blockedList.length(); j++) {
1758                 blockedClientList.add(MacAddress.fromString(blockedList.getString(j)));
1759             }
1760         }
1761 
1762         configBuilder.setAllowedClientList(allowedClientList);
1763         configBuilder.setBlockedClientList(blockedClientList);
1764 
1765         if (SdkLevel.isAtLeastS()) {
1766             if (configJson.has("apBands")) {
1767                 JSONArray jBands = configJson.getJSONArray("apBands");
1768                 int[] bands = convertJSONArrayToIntArray(jBands);
1769                 configBuilder.setBands(bands);
1770             }
1771 
1772             if (configJson.has("apChannelFrequencies")) {
1773                 JSONArray jChannelFrequencys = configJson.getJSONArray("apChannelFrequencies");
1774                 int[] channelFrequencies = convertJSONArrayToIntArray(jChannelFrequencys);
1775                 SparseIntArray channels = new SparseIntArray();
1776                 for (int channelFrequency : channelFrequencies) {
1777                     if (channelFrequency != 0) {
1778                         channels.put(getApBandFromChannelFrequency(channelFrequency),
1779                                 ScanResult.convertFrequencyMhzToChannelIfSupported(
1780                                         channelFrequency));
1781                     }
1782                 }
1783                 if (channels.size() != 0) {
1784                     configBuilder.setChannels(channels);
1785                 }
1786             }
1787 
1788             if (configJson.has("MacRandomizationSetting")) {
1789                 configBuilder.setMacRandomizationSetting(
1790                         configJson.getInt("MacRandomizationSetting"));
1791             }
1792 
1793             if (configJson.has("BridgedModeOpportunisticShutdownEnabled")) {
1794                 configBuilder.setBridgedModeOpportunisticShutdownEnabled(
1795                         configJson.getBoolean("BridgedModeOpportunisticShutdownEnabled"));
1796             }
1797 
1798             if (configJson.has("Ieee80211axEnabled")) {
1799                 configBuilder.setIeee80211axEnabled(configJson.getBoolean("Ieee80211axEnabled"));
1800             }
1801         }
1802         return configBuilder.build();
1803     }
1804 
createSoftApWifiConfiguration(JSONObject configJson)1805     private WifiConfiguration createSoftApWifiConfiguration(JSONObject configJson)
1806             throws JSONException {
1807         WifiConfiguration config = genWifiConfig(configJson);
1808         // Need to strip of extra quotation marks for SSID and password.
1809         String ssid = config.SSID;
1810         if (ssid != null) {
1811             config.SSID = ssid.substring(1, ssid.length() - 1);
1812         }
1813 
1814         config.allowedKeyManagement.clear();
1815         String pwd = config.preSharedKey;
1816         if (pwd != null) {
1817             config.allowedKeyManagement.set(KeyMgmt.WPA2_PSK);
1818             config.preSharedKey = pwd.substring(1, pwd.length() - 1);
1819         } else {
1820             config.allowedKeyManagement.set(KeyMgmt.NONE);
1821         }
1822         return config;
1823     }
1824 
1825     /**
1826      * Set SoftAp Configuration with SoftApConfiguration.
1827      */
1828     @Rpc(description = "Set configuration for soft AP.")
wifiSetWifiApConfiguration( @pcParametername = "configJson") JSONObject configJson)1829     public Boolean wifiSetWifiApConfiguration(
1830             @RpcParameter(name = "configJson") JSONObject configJson) throws JSONException {
1831         return mWifi.setSoftApConfiguration(createSoftApConfiguration(configJson));
1832     }
1833 
1834     /**
1835      * Set SoftAp Configuration with WifiConfiguration.
1836      *
1837      * Used to test deprecated API to check backward compatible.
1838      */
1839     @Rpc(description = "Set configuration for soft AP with WifiConfig.")
wifiSetWifiApConfigurationWithWifiConfiguration( @pcParametername = "configJson") JSONObject configJson)1840     public Boolean wifiSetWifiApConfigurationWithWifiConfiguration(
1841             @RpcParameter(name = "configJson") JSONObject configJson) throws JSONException {
1842         return mWifi.setWifiApConfiguration(createSoftApWifiConfiguration(configJson));
1843     }
1844 
1845     /**
1846      * Register softap callback.
1847      *
1848      * @return the id associated with the {@link SoftApCallbackImp}
1849      * used for registering callback.
1850      */
1851     @Rpc(description = "Register softap callback function.",
1852             returns = "Id of the callback associated with registering.")
registerSoftApCallback()1853     public Integer registerSoftApCallback() {
1854         SoftApCallbackImp softApCallback = new SoftApCallbackImp(mEventFacade);
1855         mSoftapCallbacks.put(softApCallback.mId, softApCallback);
1856         mWifi.registerSoftApCallback(
1857                 new HandlerExecutor(new Handler(mCallbackHandlerThread.getLooper())),
1858                 softApCallback);
1859         return softApCallback.mId;
1860     }
1861 
1862     /**
1863      * Unregister softap callback role for the {@link SoftApCallbackImp} identified by the given
1864      * {@code callbackId}.
1865      *
1866      * @param callbackId the id associated with the {@link SoftApCallbackImp}
1867      * used for registering callback.
1868      *
1869      */
1870     @Rpc(description = "Unregister softap callback function.")
unregisterSoftApCallback(@pcParametername = "callbackId") Integer callbackId)1871     public void unregisterSoftApCallback(@RpcParameter(name = "callbackId") Integer callbackId) {
1872         mWifi.unregisterSoftApCallback(mSoftapCallbacks.get(callbackId));
1873         mSoftapCallbacks.delete(callbackId);
1874     }
1875 
1876     @Rpc(description = "Enable/disable tdls with a mac address.")
wifiSetTdlsEnabledWithMacAddress( @pcParametername = "remoteMacAddress") String remoteMacAddress, @RpcParameter(name = "enable") Boolean enable)1877     public void wifiSetTdlsEnabledWithMacAddress(
1878             @RpcParameter(name = "remoteMacAddress") String remoteMacAddress,
1879             @RpcParameter(name = "enable") Boolean enable) {
1880         mWifi.setTdlsEnabledWithMacAddress(remoteMacAddress, enable);
1881     }
1882 
1883     @Rpc(description = "Starts a scan for Wifi access points.", returns = "True if the scan was initiated successfully.")
wifiStartScan()1884     public Boolean wifiStartScan() {
1885         mService.registerReceiver(mScanResultsAvailableReceiver, mScanFilter);
1886         return mWifi.startScan();
1887     }
1888 
1889     @Rpc(description = "Starts a scan for Wifi access points with scanResultCallback.",
1890             returns = "True if the scan was initiated successfully.")
wifiStartScanWithListener()1891     public Boolean wifiStartScanWithListener() {
1892         mWifi.registerScanResultsCallback(mService.getMainExecutor(), mWifiScanResultsReceiver);
1893         return mWifi.startScan();
1894     }
1895 
1896     @Rpc(description = "Start Wi-fi Protected Setup.")
wifiStartWps( @pcParametername = "config", description = "A json string with fields \\"setup\\", \\"BSSID\\", and \\"pin\\"") String config)1897     public void wifiStartWps(
1898             @RpcParameter(name = "config", description = "A json string with fields \"setup\", \"BSSID\", and \"pin\"") String config)
1899                     throws JSONException {
1900         WpsInfo info = parseWpsInfo(config);
1901         WifiWpsCallback listener = new WifiWpsCallback();
1902         Log.d("Starting wps with: " + info);
1903         mWifi.startWps(info, listener);
1904     }
1905 
1906     @Rpc(description = "Start listening for wifi state change related broadcasts.")
wifiStartTrackingStateChange()1907     public void wifiStartTrackingStateChange() {
1908         mService.registerReceiver(mStateChangeReceiver, mStateChangeFilter);
1909         mTrackingWifiStateChange = true;
1910     }
1911 
1912     @Rpc(description = "Stop listening for wifi state change related broadcasts.")
wifiStopTrackingStateChange()1913     public void wifiStopTrackingStateChange() {
1914         if (mTrackingWifiStateChange == true) {
1915             mService.unregisterReceiver(mStateChangeReceiver);
1916             mTrackingWifiStateChange = false;
1917         }
1918     }
1919 
1920     @Rpc(description = "Start listening for tether state change related broadcasts.")
wifiStartTrackingTetherStateChange()1921     public void wifiStartTrackingTetherStateChange() {
1922         mService.registerReceiver(mTetherStateReceiver, mTetherFilter);
1923         mTrackingTetherStateChange = true;
1924     }
1925 
1926     @Rpc(description = "Stop listening for wifi state change related broadcasts.")
wifiStopTrackingTetherStateChange()1927     public void wifiStopTrackingTetherStateChange() {
1928         if (mTrackingTetherStateChange == true) {
1929             mService.unregisterReceiver(mTetherStateReceiver);
1930             mTrackingTetherStateChange = false;
1931         }
1932     }
1933 
1934     @Rpc(description = "Start listening for network suggestion change related broadcasts.")
wifiStartTrackingNetworkSuggestionStateChange()1935     public void wifiStartTrackingNetworkSuggestionStateChange() {
1936         mService.registerReceiver(
1937                 mNetworkSuggestionStateChangeReceiver, mNetworkSuggestionStateChangeFilter);
1938         mTrackingNetworkSuggestionStateChange = true;
1939     }
1940 
1941     @Rpc(description = "Stop listening for network suggestion change related broadcasts.")
wifiStopTrackingNetworkSuggestionStateChange()1942     public void wifiStopTrackingNetworkSuggestionStateChange() {
1943         if (mTrackingNetworkSuggestionStateChange) {
1944             mService.unregisterReceiver(mNetworkSuggestionStateChangeReceiver);
1945             mTrackingNetworkSuggestionStateChange = false;
1946         }
1947     }
1948 
1949     @Rpc(description = "Toggle Wifi on and off.", returns = "True if Wifi is enabled.")
wifiToggleState(@pcParametername = "enabled") @pcOptional Boolean enabled)1950     public Boolean wifiToggleState(@RpcParameter(name = "enabled") @RpcOptional Boolean enabled) {
1951         if (enabled == null) {
1952             enabled = !wifiCheckState();
1953         }
1954         mWifi.setWifiEnabled(enabled);
1955         return enabled;
1956     }
1957 
1958     @Rpc(description = "Restart the WiFi subsystem.")
restartWifiSubsystem()1959     public void restartWifiSubsystem() {
1960         if (mSubsystemRestartTrackingCallback == null) {
1961             // one-time registration if needed
1962             mSubsystemRestartTrackingCallback = new SubsystemRestartTrackingCallbackFacade(
1963                     mEventFacade);
1964         }
1965         mWifi.restartWifiSubsystem();
1966     }
1967 
1968     @Rpc(description = "Toggle Wifi scan always available on and off.", returns = "True if Wifi scan is always available.")
wifiToggleScanAlwaysAvailable( @pcParametername = "enabled") @pcOptional Boolean enabled)1969     public Boolean wifiToggleScanAlwaysAvailable(
1970             @RpcParameter(name = "enabled") @RpcOptional Boolean enabled)
1971                     throws SettingNotFoundException {
1972         boolean isSet = (enabled == null) ? !mWifi.isScanAlwaysAvailable() : enabled;
1973         mWifi.setScanAlwaysAvailable(isSet);
1974         return isSet;
1975     }
1976 
1977     @Rpc(description = "Enable/disable WifiConnectivityManager.")
wifiEnableWifiConnectivityManager( @pcParametername = "enable") Boolean enable)1978     public void wifiEnableWifiConnectivityManager(
1979             @RpcParameter(name = "enable") Boolean enable) {
1980         mWifi.allowAutojoinGlobal(enable);
1981     }
1982 
1983     /**
1984      * Register network request match callback to simulate the UI flow.
1985      *
1986      * @throws JSONException
1987      * @throws GeneralSecurityException
1988      */
1989     @Rpc(description = "Register network request match callback")
wifiRegisterNetworkRequestMatchCallback()1990     public void wifiRegisterNetworkRequestMatchCallback()
1991             throws JSONException, GeneralSecurityException {
1992         // Listen for UI interaction callbacks
1993         mWifi.registerNetworkRequestMatchCallback(
1994                 new HandlerExecutor(new Handler(mCallbackHandlerThread.getLooper())),
1995                 mNetworkRequestMatchCallback);
1996     }
1997 
1998     /**
1999      * Triggers connect to a specific wifi network.
2000      *
2001      * @param jsonConfig JSONObject Dictionary of wifi connection parameters
2002      * @throws JSONException
2003      * @throws GeneralSecurityException
2004      */
2005     @Rpc(description = "Connects to the specified network for the ongoing network request")
wifiSendUserSelectionForNetworkRequestMatch( @pcParametername = "jsonConfig") JSONObject jsonConfig)2006     public void wifiSendUserSelectionForNetworkRequestMatch(
2007             @RpcParameter(name = "jsonConfig") JSONObject jsonConfig)
2008             throws JSONException, GeneralSecurityException {
2009         synchronized (mCallbackLock) {
2010             if (mNetworkRequestUserSelectionCallback == null) {
2011                 throw new IllegalStateException("user callback is null");
2012             }
2013             // Copy the SSID for user selection.
2014             WifiConfiguration config = new WifiConfiguration();
2015             if (jsonConfig.has("SSID")) {
2016                 config.SSID = "\"" + jsonConfig.getString("SSID") + "\"";
2017             }
2018             mNetworkRequestUserSelectionCallback.select(config);
2019         }
2020     }
2021 
2022     /**
2023      * Rejects network request.
2024      *
2025      * @throws JSONException
2026      * @throws GeneralSecurityException
2027      */
2028     @Rpc(description = "Rejects ongoing network request")
wifiSendUserRejectionForNetworkRequestMatch()2029     public void wifiSendUserRejectionForNetworkRequestMatch()
2030             throws JSONException, GeneralSecurityException {
2031         synchronized (mCallbackLock) {
2032             if (mNetworkRequestUserSelectionCallback == null) {
2033                 throw new IllegalStateException("user callback is null");
2034             }
2035             mNetworkRequestUserSelectionCallback.reject();
2036         }
2037     }
2038 
2039     /**
2040      * Add network suggestions.
2041 
2042      * @param wifiNetworkSuggestions Array of JSONObject Dictionary of wifi network suggestion
2043      *                               parameters
2044      * @throws JSONException
2045      * @throws GeneralSecurityException
2046      */
2047     @Rpc(description = "Add network suggestions to the platform")
wifiAddNetworkSuggestions( @pcParametername = "wifiNetworkSuggestions") JSONArray wifiNetworkSuggestions)2048     public boolean wifiAddNetworkSuggestions(
2049             @RpcParameter(name = "wifiNetworkSuggestions") JSONArray wifiNetworkSuggestions)
2050             throws JSONException, GeneralSecurityException, IOException {
2051         return mWifi.addNetworkSuggestions(genWifiNetworkSuggestions(wifiNetworkSuggestions))
2052                 == WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS;
2053     }
2054 
2055     /**
2056      * Remove network suggestions.
2057 
2058      * @param wifiNetworkSuggestions Array of JSONObject Dictionary of wifi network suggestion
2059      *                               parameters
2060      * @throws JSONException
2061      * @throws GeneralSecurityException
2062      */
2063     @Rpc(description = "Remove network suggestions from the platform")
wifiRemoveNetworkSuggestions( @pcParametername = "wifiNetworkSuggestions") JSONArray wifiNetworkSuggestions)2064     public boolean wifiRemoveNetworkSuggestions(
2065             @RpcParameter(name = "wifiNetworkSuggestions") JSONArray wifiNetworkSuggestions)
2066             throws JSONException, GeneralSecurityException, IOException {
2067         return mWifi.removeNetworkSuggestions(genWifiNetworkSuggestions(wifiNetworkSuggestions))
2068                 == WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS;
2069     }
2070 
2071     @Override
shutdown()2072     public void shutdown() {
2073         wifiLockRelease();
2074         if (mTrackingWifiStateChange == true) {
2075             wifiStopTrackingStateChange();
2076         }
2077         if (mTrackingTetherStateChange == true) {
2078             wifiStopTrackingTetherStateChange();
2079         }
2080     }
2081 
2082     private class EasyConnectCallback extends EasyConnectStatusCallback {
2083         private static final String EASY_CONNECT_CALLBACK_TAG = "onDppCallback";
2084 
2085         @Override
onEnrolleeSuccess(int newWifiConfigurationId)2086         public void onEnrolleeSuccess(int newWifiConfigurationId) {
2087             Bundle msg = new Bundle();
2088             msg.putString("Type", "onEnrolleeSuccess");
2089             msg.putInt("NetworkId", newWifiConfigurationId);
2090             Log.d("Posting event: onEnrolleeSuccess");
2091             mEventFacade.postEvent(EASY_CONNECT_CALLBACK_TAG, msg);
2092         }
2093 
2094         @Override
onConfiguratorSuccess(int code)2095         public void onConfiguratorSuccess(int code) {
2096             Bundle msg = new Bundle();
2097             msg.putString("Type", "onConfiguratorSuccess");
2098             msg.putInt("Status", code);
2099             Log.d("Posting event: onConfiguratorSuccess");
2100             mEventFacade.postEvent(EASY_CONNECT_CALLBACK_TAG, msg);
2101         }
2102 
2103         @Override
onFailure(int code, String ssid, SparseArray<int[]> channelList, int[] bandList)2104         public void onFailure(int code, String ssid, SparseArray<int[]> channelList,
2105                 int[] bandList) {
2106             Bundle msg = new Bundle();
2107             msg.putString("Type", "onFailure");
2108             msg.putInt("Status", code);
2109             Log.d("Posting event: onFailure");
2110             if (ssid != null) {
2111                 Log.d("onFailure SSID: " + ssid);
2112                 msg.putString("onFailureSsid", ssid);
2113             } else {
2114                 msg.putString("onFailureSsid", "");
2115             }
2116             if (channelList != null) {
2117                 Log.d("onFailure list of tried channels: " + channelList);
2118                 int key;
2119                 int index = 0;
2120                 JSONObject formattedChannelList = new JSONObject();
2121 
2122                 // Build a JSON array of classes, with an array of channels for each class.
2123                 do {
2124                     try {
2125                         key = channelList.keyAt(index);
2126                     } catch (java.lang.ArrayIndexOutOfBoundsException e) {
2127                         break;
2128                     }
2129                     try {
2130                         JSONArray channelsInClassArray = new JSONArray();
2131 
2132                         int[] output = channelList.get(key);
2133                         for (int i = 0; i < output.length; i++) {
2134                             channelsInClassArray.put(output[i]);
2135                         }
2136                         formattedChannelList.put(Integer.toString(key),
2137                                 build(channelsInClassArray));
2138                     } catch (org.json.JSONException e) {
2139                         msg.putString("onFailureChannelList", "");
2140                         break;
2141                     }
2142                     index++;
2143                 } while (true);
2144 
2145                 msg.putString("onFailureChannelList", formattedChannelList.toString());
2146             } else {
2147                 msg.putString("onFailureChannelList", "");
2148             }
2149 
2150             if (bandList != null) {
2151                 // Build a JSON array of bands represented as operating classes
2152                 Log.d("onFailure list of supported bands: " + bandList);
2153                 JSONArray formattedBandList = new JSONArray();
2154                 for (int i = 0; i < bandList.length; i++) {
2155                     formattedBandList.put(bandList[i]);
2156                 }
2157                 msg.putString("onFailureBandList", formattedBandList.toString());
2158             } else {
2159                 msg.putString("onFailureBandList", "");
2160             }
2161             mEventFacade.postEvent(EASY_CONNECT_CALLBACK_TAG, msg);
2162         }
2163 
2164         @Override
onProgress(int code)2165         public void onProgress(int code) {
2166             Bundle msg = new Bundle();
2167             msg.putString("Type", "onProgress");
2168             msg.putInt("Status", code);
2169             Log.d("Posting event: onProgress");
2170             mEventFacade.postEvent(EASY_CONNECT_CALLBACK_TAG, msg);
2171         }
2172 
2173         @Override
onBootstrapUriGenerated(@onNull Uri dppUri)2174         public void onBootstrapUriGenerated(@NonNull Uri dppUri) {
2175             Bundle msg = new Bundle();
2176             msg.putString("Type", "onBootstrapUriGenerated");
2177             Log.d("onBootstrapUriGenerated uri: " + dppUri.toString());
2178             msg.putString("generatedUri", dppUri.toString());
2179             Log.d("Posting event: onBootstrapUriGenerated");
2180             mEventFacade.postEvent(EASY_CONNECT_CALLBACK_TAG, msg);
2181         }
2182     }
2183 
2184     private static @WifiManager.EasyConnectCryptographyCurve
getEasyConnectCryptographyCurve(String curve)2185             int getEasyConnectCryptographyCurve(String curve) {
2186 
2187         switch (curve) {
2188             case "secp384r1":
2189                 return WifiManager.EASY_CONNECT_CRYPTOGRAPHY_CURVE_SECP384R1;
2190             case "secp521r1":
2191                 return WifiManager.EASY_CONNECT_CRYPTOGRAPHY_CURVE_SECP521R1;
2192             case "brainpoolP256r1":
2193                 return WifiManager.EASY_CONNECT_CRYPTOGRAPHY_CURVE_BRAINPOOLP256R1;
2194             case "brainpoolP384r1":
2195                 return WifiManager.EASY_CONNECT_CRYPTOGRAPHY_CURVE_BRAINPOOLP384R1;
2196             case "brainpoolP512r1":
2197                 return WifiManager.EASY_CONNECT_CRYPTOGRAPHY_CURVE_BRAINPOOLP512R1;
2198             case "prime256v1":
2199             default:
2200                 return WifiManager.EASY_CONNECT_CRYPTOGRAPHY_CURVE_PRIME256V1;
2201         }
2202     }
2203 
2204     /**
2205      * Start Easy Connect (DPP) in Initiator-Configurator role: Send Wi-Fi configuration to a peer
2206      *
2207      * @param enrolleeUri Peer URI
2208      * @param selectedNetworkId Wi-Fi configuration ID
2209      */
2210     @Rpc(description = "Easy Connect Initiator-Configurator: Send Wi-Fi configuration to peer")
startEasyConnectAsConfiguratorInitiator(@pcParametername = "enrolleeUri") String enrolleeUri, @RpcParameter(name = "selectedNetworkId") Integer selectedNetworkId, @RpcParameter(name = "netRole") String netRole)2211     public void startEasyConnectAsConfiguratorInitiator(@RpcParameter(name = "enrolleeUri") String
2212             enrolleeUri, @RpcParameter(name = "selectedNetworkId") Integer selectedNetworkId,
2213             @RpcParameter(name = "netRole") String netRole)
2214             throws JSONException {
2215         EasyConnectCallback dppStatusCallback = new EasyConnectCallback();
2216         int netRoleInternal;
2217 
2218         if (netRole.equals("ap")) {
2219             netRoleInternal = WifiManager.EASY_CONNECT_NETWORK_ROLE_AP;
2220         } else {
2221             netRoleInternal = WifiManager.EASY_CONNECT_NETWORK_ROLE_STA;
2222         }
2223 
2224         // Start Easy Connect
2225         mWifi.startEasyConnectAsConfiguratorInitiator(enrolleeUri, selectedNetworkId,
2226                 netRoleInternal, mService.getMainExecutor(), dppStatusCallback);
2227     }
2228 
2229     /**
2230      * Start Easy Connect (DPP) in Initiator-Enrollee role: Receive Wi-Fi configuration from a peer
2231      *
2232      * @param configuratorUri
2233      */
2234     @Rpc(description = "Easy Connect Initiator-Enrollee: Receive Wi-Fi configuration from peer")
startEasyConnectAsEnrolleeInitiator(@pcParametername = "configuratorUri") String configuratorUri)2235     public void startEasyConnectAsEnrolleeInitiator(@RpcParameter(name = "configuratorUri") String
2236             configuratorUri) {
2237         EasyConnectCallback dppStatusCallback = new EasyConnectCallback();
2238 
2239         // Start Easy Connect
2240         mWifi.startEasyConnectAsEnrolleeInitiator(configuratorUri, mService.getMainExecutor(),
2241                 dppStatusCallback);
2242     }
2243 
2244     /**
2245      * Start Easy Connect (DPP) in Responder-Enrollee role: Receive Wi-Fi configuration from a peer
2246      *
2247      * @param deviceInfo The device specific info to attach in the generated URI
2248      * @param cryptographyCurve Elliptic curve cryptography used to generate DPP
2249      *        public/private key pair
2250      */
2251     @Rpc(description = "Easy Connect Responder-Enrollee: Receive Wi-Fi configuration from peer")
startEasyConnectAsEnrolleeResponder(@pcParametername = "deviceInfo") String deviceInfo, @RpcParameter(name = "cryptographyCurve") String cryptographyCurve)2252     public void startEasyConnectAsEnrolleeResponder(@RpcParameter(name = "deviceInfo") String
2253             deviceInfo, @RpcParameter(name = "cryptographyCurve") String cryptographyCurve) {
2254         EasyConnectCallback dppStatusCallback = new EasyConnectCallback();
2255 
2256         // Start Easy Connect
2257         mWifi.startEasyConnectAsEnrolleeResponder(deviceInfo,
2258                 getEasyConnectCryptographyCurve(cryptographyCurve), mService.getMainExecutor(),
2259                 dppStatusCallback);
2260     }
2261 
2262     /**
2263      * Stop Easy Connect (DPP) session
2264      *
2265      */
2266     @Rpc(description = "Stop Easy Connect session")
stopEasyConnectSession()2267     public void stopEasyConnectSession() {
2268         // Stop Easy Connect
2269         mWifi.stopEasyConnectSession();
2270     }
2271 
2272     /**
2273      * Enable/Disable auto join for target network
2274      */
2275     @Rpc(description = "Set network auto join enable/disable")
wifiEnableAutojoin(@pcParametername = "netId") Integer netId, @RpcParameter(name = "enableAutojoin") Boolean enableAutojoin)2276     public void wifiEnableAutojoin(@RpcParameter(name = "netId") Integer netId,
2277             @RpcParameter(name = "enableAutojoin") Boolean enableAutojoin) {
2278         mWifi.allowAutojoin(netId, enableAutojoin);
2279     }
2280 
2281     /**
2282      * Enable/Disable auto join for target Passpoint network
2283      */
2284     @Rpc(description = "Set passpoint network auto join enable/disable")
wifiEnableAutojoinPasspoint(@pcParametername = "FQDN") String fqdn, @RpcParameter(name = "enableAutojoin") Boolean enableAutojoin)2285     public void wifiEnableAutojoinPasspoint(@RpcParameter(name = "FQDN") String fqdn,
2286             @RpcParameter(name = "enableAutojoin") Boolean enableAutojoin) {
2287         mWifi.allowAutojoinPasspoint(fqdn, enableAutojoin);
2288     }
2289 
genCoexUnsafeChannel(JSONObject j)2290     private static CoexUnsafeChannel genCoexUnsafeChannel(JSONObject j) throws JSONException {
2291         if (j == null || !j.has("band") || !j.has("channel")) {
2292             return null;
2293         }
2294 
2295         final int band;
2296         final String jsonBand = j.getString("band");
2297         if (TextUtils.equals(jsonBand, "24_GHZ")) {
2298             band = WIFI_BAND_24_GHZ;
2299         } else if (TextUtils.equals(jsonBand, "5_GHZ")) {
2300             band = WIFI_BAND_5_GHZ;
2301         } else {
2302             return null;
2303         }
2304         if (j.has("powerCapDbm")) {
2305             return new CoexUnsafeChannel(band, j.getInt("channel"), j.getInt("powerCapDbm"));
2306         }
2307         return new CoexUnsafeChannel(band, j.getInt("channel"));
2308     }
2309 
genCoexUnsafeChannels( JSONArray jsonCoexUnsafeChannelsArray)2310     private static List<CoexUnsafeChannel> genCoexUnsafeChannels(
2311             JSONArray jsonCoexUnsafeChannelsArray) throws JSONException {
2312         if (jsonCoexUnsafeChannelsArray == null) {
2313             return Collections.emptyList();
2314         }
2315         List<CoexUnsafeChannel> unsafeChannels = new ArrayList<>();
2316         for (int i = 0; i < jsonCoexUnsafeChannelsArray.length(); i++) {
2317             unsafeChannels.add(
2318                     genCoexUnsafeChannel(jsonCoexUnsafeChannelsArray.getJSONObject(i)));
2319         }
2320         return unsafeChannels;
2321     }
2322 
genCoexRestrictions(JSONArray jsonCoexRestrictionArray)2323     private static int genCoexRestrictions(JSONArray jsonCoexRestrictionArray)
2324             throws JSONException {
2325         if (jsonCoexRestrictionArray == null) {
2326             return 0;
2327         }
2328         int coexRestrictions = 0;
2329         for (int i = 0; i < jsonCoexRestrictionArray.length(); i++) {
2330             final String jsonRestriction = jsonCoexRestrictionArray.getString(i);
2331             if (TextUtils.equals(jsonRestriction, "WIFI_DIRECT")) {
2332                 coexRestrictions |= WifiManager.COEX_RESTRICTION_WIFI_DIRECT;
2333             }
2334             if (TextUtils.equals(jsonRestriction, "SOFTAP")) {
2335                 coexRestrictions |= WifiManager.COEX_RESTRICTION_SOFTAP;
2336             }
2337             if (TextUtils.equals(jsonRestriction, "WIFI_AWARE")) {
2338                 coexRestrictions |= WifiManager.COEX_RESTRICTION_WIFI_AWARE;
2339             }
2340         }
2341         return coexRestrictions;
2342     }
2343 
2344     /**
2345      * Converts a set of {@link CoexUnsafeChannel} to a {@link JSONArray} of {@link JSONObject} of
2346      * format:
2347      *     {
2348      *         "band": <"24_GHZ" or "5_GHZ">
2349      *         "channel" : <Channel Number>
2350      *         (Optional) "powerCapDbm" : <Power Cap in Dbm>
2351      *     }
2352      */
coexUnsafeChannelsToJson(List<CoexUnsafeChannel> unsafeChannels)2353     private static JSONArray coexUnsafeChannelsToJson(List<CoexUnsafeChannel> unsafeChannels)
2354             throws JSONException {
2355         final JSONArray jsonCoexUnsafeChannelArray = new JSONArray();
2356         for (CoexUnsafeChannel unsafeChannel : unsafeChannels) {
2357             final String jsonBand;
2358             if (unsafeChannel.getBand() == WIFI_BAND_24_GHZ) {
2359                 jsonBand = "24_GHZ";
2360             } else if (unsafeChannel.getBand() == WIFI_BAND_5_GHZ) {
2361                 jsonBand = "5_GHZ";
2362             } else {
2363                 continue;
2364             }
2365             final JSONObject jsonUnsafeChannel = new JSONObject();
2366             jsonUnsafeChannel.put("band", jsonBand);
2367             jsonUnsafeChannel.put("channel", unsafeChannel.getChannel());
2368             final int powerCapDbm = unsafeChannel.getPowerCapDbm();
2369             if (powerCapDbm != CoexUnsafeChannel.POWER_CAP_NONE) {
2370                 jsonUnsafeChannel.put("powerCapDbm", powerCapDbm);
2371             }
2372             jsonCoexUnsafeChannelArray.put(jsonUnsafeChannel);
2373         }
2374         return jsonCoexUnsafeChannelArray;
2375     }
2376 
2377     /**
2378      * Converts a coex restriction bitmask {@link WifiManager#getCoexRestrictions()} to a JSON array
2379      * of possible values "WIFI_DIRECT", "SOFTAP", "WIFI_AWARE".
2380      */
coexRestrictionsToJson(int coexRestrictions)2381     private static JSONArray coexRestrictionsToJson(int coexRestrictions) {
2382         final JSONArray jsonCoexRestrictionArray = new JSONArray();
2383         if ((coexRestrictions & WifiManager.COEX_RESTRICTION_WIFI_DIRECT) != 0) {
2384             jsonCoexRestrictionArray.put("WIFI_DIRECT");
2385         }
2386         if ((coexRestrictions & WifiManager.COEX_RESTRICTION_SOFTAP) != 0) {
2387             jsonCoexRestrictionArray.put("SOFTAP");
2388         }
2389         if ((coexRestrictions & WifiManager.COEX_RESTRICTION_WIFI_AWARE) != 0) {
2390             jsonCoexRestrictionArray.put("WIFI_AWARE");
2391         }
2392         return jsonCoexRestrictionArray;
2393     }
2394 
2395     /**
2396      * Returns whether the default coex algorithm is enabled or not
2397      *
2398      * @return {@code true} if the default coex algorithm is enabled, {@code false} otherwise.
2399      */
2400     @Rpc(description = "Returns whether the default coex algorithm is enabled or not")
wifiIsDefaultCoexAlgorithmEnabled()2401     public boolean wifiIsDefaultCoexAlgorithmEnabled() {
2402         if (!SdkLevel.isAtLeastS()) {
2403             return false;
2404         }
2405         return mWifi.isDefaultCoexAlgorithmEnabled();
2406     }
2407 
2408     /**
2409      * Sets the active list of unsafe channels to avoid for coex and the restricted Wifi interfaces.
2410      *
2411      * @param unsafeChannels JSONArray representation of {@link CoexUnsafeChannel}.
2412      *                       See {@link #coexUnsafeChannelsToJson(List)}.
2413      * @param restrictions JSONArray representation of coex restrictions.
2414      *                     See {@link #coexRestrictionsToJson(int)}.
2415      * @throws JSONException
2416      */
2417     @Rpc(description = "Set the unsafe channels to avoid for coex")
wifiSetCoexUnsafeChannels( @pcParametername = "unsafeChannels") JSONArray unsafeChannels, @RpcParameter(name = "restrictions") JSONArray restrictions)2418     public void wifiSetCoexUnsafeChannels(
2419             @RpcParameter(name = "unsafeChannels") JSONArray unsafeChannels,
2420             @RpcParameter(name = "restrictions") JSONArray restrictions) throws JSONException {
2421         if (!SdkLevel.isAtLeastS()) {
2422             return;
2423         }
2424         mWifi.setCoexUnsafeChannels(
2425                 genCoexUnsafeChannels(unsafeChannels), genCoexRestrictions(restrictions));
2426     }
2427 
2428     /**
2429      * Registers a coex callback to start receiving coex update events.
2430      */
2431     @Rpc(description = "Registers a coex callback to start receiving coex update events")
wifiRegisterCoexCallback()2432     public void wifiRegisterCoexCallback() {
2433         if (!SdkLevel.isAtLeastS()) {
2434             return;
2435         }
2436         mWifi.registerCoexCallback(
2437                 new HandlerExecutor(mCallbackHandlerThread.getThreadHandler()), mCoexCallback);
2438     }
2439 
2440     /**
2441      * Unregisters the coex callback to stop receiving coex update events.
2442      */
2443     @Rpc(description = "Unregisters the coex callback to stop receiving coex update events")
wifiUnregisterCoexCallback()2444     public void wifiUnregisterCoexCallback() {
2445         if (!SdkLevel.isAtLeastS()) {
2446             return;
2447         }
2448         mWifi.unregisterCoexCallback(mCoexCallback);
2449     }
2450 }
2451