1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.wifitrackerlib;
18 
19 import static android.net.wifi.WifiInfo.DEFAULT_MAC_ADDRESS;
20 import static android.os.Build.VERSION_CODES;
21 
22 import android.annotation.SuppressLint;
23 import android.annotation.TargetApi;
24 import android.content.Context;
25 import android.icu.text.MessageFormat;
26 import android.net.wifi.WifiInfo;
27 import android.net.wifi.WifiManager;
28 import android.net.wifi.sharedconnectivity.app.HotspotNetwork;
29 import android.net.wifi.sharedconnectivity.app.HotspotNetworkConnectionStatus;
30 import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo;
31 import android.net.wifi.sharedconnectivity.app.SharedConnectivityManager;
32 import android.os.Handler;
33 import android.text.BidiFormatter;
34 import android.text.TextUtils;
35 import android.util.Log;
36 
37 import androidx.annotation.IntDef;
38 import androidx.annotation.IntRange;
39 import androidx.annotation.NonNull;
40 import androidx.annotation.Nullable;
41 import androidx.annotation.WorkerThread;
42 import androidx.core.os.BuildCompat;
43 
44 import org.json.JSONException;
45 import org.json.JSONObject;
46 
47 import java.lang.annotation.Retention;
48 import java.lang.annotation.RetentionPolicy;
49 import java.util.ArrayList;
50 import java.util.Collections;
51 import java.util.HashMap;
52 import java.util.Map;
53 import java.util.Objects;
54 
55 /**
56  * WifiEntry representation of a Hotspot Network provided via {@link SharedConnectivityManager}.
57  */
58 @TargetApi(VERSION_CODES.UPSIDE_DOWN_CAKE)
59 public class HotspotNetworkEntry extends WifiEntry {
60     static final String TAG = "HotspotNetworkEntry";
61     public static final String KEY_PREFIX = "HotspotNetworkEntry:";
62     public static final String EXTRA_KEY_IS_BATTERY_CHARGING = "is_battery_charging";
63 
64     private static final String DEVICE_TYPE_KEY = "DEVICE_TYPE";
65     private static final String NETWORK_NAME_KEY = "NETWORK_NAME";
66 
67     @NonNull private final WifiTrackerInjector mInjector;
68     @NonNull private final Context mContext;
69     @Nullable private final SharedConnectivityManager mSharedConnectivityManager;
70 
71     @Nullable private HotspotNetwork mHotspotNetworkData;
72     @NonNull private HotspotNetworkEntryKey mKey;
73     @ConnectionStatus
74     private int mLastStatus = HotspotNetworkConnectionStatus.CONNECTION_STATUS_UNKNOWN;
75     private boolean mConnectionError = false;
76 
77     /**
78      * If editing this IntDef also edit the definition in:
79      * {@link android.net.wifi.sharedconnectivity.app.HotspotNetwork}
80      *
81      * @hide
82      */
83     @Retention(RetentionPolicy.SOURCE)
84     @IntDef({
85             HotspotNetwork.NETWORK_TYPE_UNKNOWN,
86             HotspotNetwork.NETWORK_TYPE_CELLULAR,
87             HotspotNetwork.NETWORK_TYPE_WIFI,
88             HotspotNetwork.NETWORK_TYPE_ETHERNET
89     })
90     public @interface NetworkType {} // TODO(b/271868642): Add IfThisThanThat lint
91 
92     /**
93      * If editing this IntDef also edit the definition in:
94      * {@link android.net.wifi.sharedconnectivity.app.NetworkProviderInfo}
95      *
96      * @hide
97      */
98     @Retention(RetentionPolicy.SOURCE)
99     @IntDef({
100             NetworkProviderInfo.DEVICE_TYPE_UNKNOWN,
101             NetworkProviderInfo.DEVICE_TYPE_PHONE,
102             NetworkProviderInfo.DEVICE_TYPE_TABLET,
103             NetworkProviderInfo.DEVICE_TYPE_LAPTOP,
104             NetworkProviderInfo.DEVICE_TYPE_WATCH,
105             NetworkProviderInfo.DEVICE_TYPE_AUTO
106     })
107     public @interface DeviceType {} // TODO(b/271868642): Add IfThisThanThat lint
108 
109     public static final int CONNECTION_STATUS_CONNECTED = 10;
110 
111     /**
112      * If editing this IntDef also edit the definition in:
113      * {@link android.net.wifi.sharedconnectivity.app.HotspotNetworkConnectionStatus}
114      *
115      * @hide
116      */
117     @Retention(RetentionPolicy.SOURCE)
118     @IntDef({
119             HotspotNetworkConnectionStatus.CONNECTION_STATUS_UNKNOWN,
120             HotspotNetworkConnectionStatus.CONNECTION_STATUS_ENABLING_HOTSPOT,
121             HotspotNetworkConnectionStatus.CONNECTION_STATUS_UNKNOWN_ERROR,
122             HotspotNetworkConnectionStatus.CONNECTION_STATUS_PROVISIONING_FAILED,
123             HotspotNetworkConnectionStatus.CONNECTION_STATUS_TETHERING_TIMEOUT,
124             HotspotNetworkConnectionStatus.CONNECTION_STATUS_TETHERING_UNSUPPORTED,
125             HotspotNetworkConnectionStatus.CONNECTION_STATUS_NO_CELL_DATA,
126             HotspotNetworkConnectionStatus.CONNECTION_STATUS_ENABLING_HOTSPOT_FAILED,
127             HotspotNetworkConnectionStatus.CONNECTION_STATUS_ENABLING_HOTSPOT_TIMEOUT,
128             HotspotNetworkConnectionStatus.CONNECTION_STATUS_CONNECT_TO_HOTSPOT_FAILED,
129             CONNECTION_STATUS_CONNECTED,
130     })
131     public @interface ConnectionStatus {} // TODO(b/271868642): Add IfThisThanThat lint
132 
133     /**
134      * Create a HotspotNetworkEntry from HotspotNetwork data.
135      */
HotspotNetworkEntry( @onNull WifiTrackerInjector injector, @NonNull Context context, @NonNull Handler callbackHandler, @NonNull WifiManager wifiManager, @Nullable SharedConnectivityManager sharedConnectivityManager, @NonNull HotspotNetwork hotspotNetworkData)136     HotspotNetworkEntry(
137             @NonNull WifiTrackerInjector injector,
138             @NonNull Context context, @NonNull Handler callbackHandler,
139             @NonNull WifiManager wifiManager,
140             @Nullable SharedConnectivityManager sharedConnectivityManager,
141             @NonNull HotspotNetwork hotspotNetworkData) {
142         super(injector, callbackHandler, wifiManager, false /*forSavedNetworksPage*/);
143         mInjector = injector;
144         mContext = context;
145         mSharedConnectivityManager = sharedConnectivityManager;
146         mHotspotNetworkData = hotspotNetworkData;
147         mKey = new HotspotNetworkEntryKey(hotspotNetworkData);
148     }
149 
150     /**
151      * Create a HotspotNetworkEntry from HotspotNetworkEntryKey.
152      */
HotspotNetworkEntry( @onNull WifiTrackerInjector injector, @NonNull Context context, @NonNull Handler callbackHandler, @NonNull WifiManager wifiManager, @Nullable SharedConnectivityManager sharedConnectivityManager, @NonNull HotspotNetworkEntryKey key)153     HotspotNetworkEntry(
154             @NonNull WifiTrackerInjector injector,
155             @NonNull Context context, @NonNull Handler callbackHandler,
156             @NonNull WifiManager wifiManager,
157             @Nullable SharedConnectivityManager sharedConnectivityManager,
158             @NonNull HotspotNetworkEntryKey key) {
159         super(injector, callbackHandler, wifiManager, false /*forSavedNetworksPage*/);
160         mInjector = injector;
161         mContext = context;
162         mSharedConnectivityManager = sharedConnectivityManager;
163         mHotspotNetworkData = null;
164         mKey = key;
165     }
166 
167     @Override
getKey()168     public String getKey() {
169         return mKey.toString();
170     }
171 
getHotspotNetworkEntryKey()172     public HotspotNetworkEntryKey getHotspotNetworkEntryKey() {
173         return mKey;
174     }
175 
176     /**
177      * Updates the hotspot data for this entry. Creates a new key when called.
178      *
179      * @param hotspotNetworkData An updated data set from SharedConnectivityService.
180      */
181     @WorkerThread
updateHotspotNetworkData( @onNull HotspotNetwork hotspotNetworkData)182     protected synchronized void updateHotspotNetworkData(
183             @NonNull HotspotNetwork hotspotNetworkData) {
184         mHotspotNetworkData = hotspotNetworkData;
185         mKey = new HotspotNetworkEntryKey(hotspotNetworkData);
186         notifyOnUpdated();
187     }
188 
189     @WorkerThread
connectionInfoMatches(@onNull WifiInfo wifiInfo)190     protected synchronized boolean connectionInfoMatches(@NonNull WifiInfo wifiInfo) {
191         if (mKey.isVirtualEntry()) {
192             return false;
193         }
194         return Objects.equals(mKey.getScanResultKey(),
195                 new StandardWifiEntry.ScanResultKey(WifiInfo.sanitizeSsid(wifiInfo.getSSID()),
196                         Collections.singletonList(wifiInfo.getCurrentSecurityType())));
197     }
198 
199     @Override
getLevel()200     public int getLevel() {
201         if (getConnectedState() == CONNECTED_STATE_DISCONNECTED) {
202             return WIFI_LEVEL_MAX;
203         }
204         return super.getLevel();
205     }
206 
207     @Override
getTitle()208     public synchronized String getTitle() {
209         if (mHotspotNetworkData == null) {
210             return "";
211         }
212         return mHotspotNetworkData.getNetworkProviderInfo().getDeviceName();
213     }
214 
215     @Override
getSummary(boolean concise)216     public synchronized String getSummary(boolean concise) {
217         if (mHotspotNetworkData == null) {
218             return "";
219         }
220         if (mCalledConnect) {
221             return mContext.getString(R.string.wifitrackerlib_hotspot_network_connecting);
222         }
223         if (mConnectionError) {
224             switch (mLastStatus) {
225                 case HotspotNetworkConnectionStatus.CONNECTION_STATUS_PROVISIONING_FAILED:
226                 case HotspotNetworkConnectionStatus.CONNECTION_STATUS_TETHERING_TIMEOUT:
227                     return mContext.getString(
228                         R.string.wifitrackerlib_hotspot_network_summary_error_carrier_incomplete,
229                         BidiFormatter.getInstance().unicodeWrap(
230                                mHotspotNetworkData.getNetworkName()));
231                 case HotspotNetworkConnectionStatus.CONNECTION_STATUS_TETHERING_UNSUPPORTED:
232                     return mContext.getString(
233                             R.string.wifitrackerlib_hotspot_network_summary_error_carrier_block,
234                             BidiFormatter.getInstance().unicodeWrap(
235                                     mHotspotNetworkData.getNetworkName()));
236                 case HotspotNetworkConnectionStatus.CONNECTION_STATUS_NO_CELL_DATA:
237                 case HotspotNetworkConnectionStatus.CONNECTION_STATUS_ENABLING_HOTSPOT_FAILED:
238                 case HotspotNetworkConnectionStatus.CONNECTION_STATUS_ENABLING_HOTSPOT_TIMEOUT:
239                 case HotspotNetworkConnectionStatus.CONNECTION_STATUS_CONNECT_TO_HOTSPOT_FAILED:
240                     MessageFormat msg = new MessageFormat(mContext.getString(
241                             R.string.wifitrackerlib_hotspot_network_summary_error_settings));
242                     Map<String, Object> args = new HashMap<>();
243                     args.put(DEVICE_TYPE_KEY, getDeviceTypeId(
244                             mHotspotNetworkData.getNetworkProviderInfo().getDeviceType()));
245                     return msg.format(args);
246                 case HotspotNetworkConnectionStatus.CONNECTION_STATUS_UNKNOWN_ERROR:
247                 default:
248                     return mContext.getString(
249                             R.string.wifitrackerlib_hotspot_network_summary_error_generic);
250             }
251         }
252         MessageFormat msg = new MessageFormat(
253                 mContext.getString(R.string.wifitrackerlib_hotspot_network_summary_new));
254         Map<String, Object> args = new HashMap<>();
255         args.put(DEVICE_TYPE_KEY,
256                 getDeviceTypeId(mHotspotNetworkData.getNetworkProviderInfo().getDeviceType()));
257         args.put(NETWORK_NAME_KEY, mHotspotNetworkData.getNetworkName());
258         return msg.format(args);
259     }
260 
261     /**
262      * Alternate summary string to be used on Network & internet page.
263      *
264      * @return Display string.
265      */
getAlternateSummary()266     public synchronized String getAlternateSummary() {
267         if (mHotspotNetworkData == null) {
268             return "";
269         }
270         return mContext.getString(R.string.wifitrackerlib_hotspot_network_alternate,
271                 BidiFormatter.getInstance().unicodeWrap(mHotspotNetworkData.getNetworkName()),
272                 BidiFormatter.getInstance().unicodeWrap(
273                         mHotspotNetworkData.getNetworkProviderInfo().getDeviceName()));
274     }
275 
276     @Override
getSsid()277     public synchronized String getSsid() {
278         StandardWifiEntry.ScanResultKey scanResultKey = mKey.getScanResultKey();
279         if (scanResultKey == null) {
280             return null;
281         }
282         return scanResultKey.getSsid();
283     }
284 
285     @Override
286     @Nullable
287     @SuppressLint("HardwareIds")
getMacAddress()288     public synchronized String getMacAddress() {
289         if (mWifiInfo == null) {
290             return null;
291         }
292         final String wifiInfoMac = mWifiInfo.getMacAddress();
293         if (!TextUtils.isEmpty(wifiInfoMac)
294                 && !TextUtils.equals(wifiInfoMac, DEFAULT_MAC_ADDRESS)) {
295             return wifiInfoMac;
296         }
297         if (getPrivacy() != PRIVACY_RANDOMIZED_MAC) {
298             final String[] factoryMacs = mWifiManager.getFactoryMacAddresses();
299             if (factoryMacs.length > 0) {
300                 return factoryMacs[0];
301             }
302         }
303         return null;
304     }
305 
306     @Override
307     @Privacy
getPrivacy()308     public int getPrivacy() {
309         return PRIVACY_RANDOMIZED_MAC;
310     }
311 
312     @Override
getSecurityString(boolean concise)313     public synchronized String getSecurityString(boolean concise) {
314         if (mHotspotNetworkData == null) {
315             return "";
316         }
317         return Utils.getSecurityString(mContext,
318                 new ArrayList<>(mHotspotNetworkData.getHotspotSecurityTypes()), concise);
319     }
320 
321     @Override
getStandardString()322     public synchronized String getStandardString() {
323         if (mWifiInfo == null) {
324             return "";
325         }
326         return Utils.getStandardString(mContext, mWifiInfo.getWifiStandard());
327     }
328 
329     @Override
getBandString()330     public synchronized String getBandString() {
331         if (mWifiInfo == null) {
332             return "";
333         }
334         return Utils.wifiInfoToBandString(mContext, mWifiInfo);
335     }
336 
337     /**
338      * Connection strength between the host device and the internet.
339      *
340      * @return Displayed connection strength in the range 0 to 4.
341      */
342     @IntRange(from = 0, to = 4)
getUpstreamConnectionStrength()343     public synchronized int getUpstreamConnectionStrength() {
344         if (mHotspotNetworkData == null) {
345             return 0;
346         }
347         return mHotspotNetworkData.getNetworkProviderInfo().getConnectionStrength();
348     }
349 
350     /**
351      * Network type used by the host device to connect to the internet.
352      *
353      * @return NetworkType enum.
354      */
355     @NetworkType
getNetworkType()356     public synchronized int getNetworkType() {
357         if (mHotspotNetworkData == null) {
358             return HotspotNetwork.NETWORK_TYPE_UNKNOWN;
359         }
360         return mHotspotNetworkData.getHostNetworkType();
361     }
362 
363     /**
364      * Device type of the host device.
365      *
366      * @return DeviceType enum.
367      */
368     @DeviceType
getDeviceType()369     public synchronized int getDeviceType() {
370         if (mHotspotNetworkData == null) {
371             return NetworkProviderInfo.DEVICE_TYPE_UNKNOWN;
372         }
373         return mHotspotNetworkData.getNetworkProviderInfo().getDeviceType();
374     }
375 
376     /**
377      * The battery percentage of the host device.
378      */
379     @IntRange(from = 0, to = 100)
getBatteryPercentage()380     public synchronized int getBatteryPercentage() {
381         if (mHotspotNetworkData == null) {
382             return 0;
383         }
384         return mHotspotNetworkData.getNetworkProviderInfo().getBatteryPercentage();
385     }
386 
387     /**
388      * If the host device is currently charging its battery.
389      */
isBatteryCharging()390     public synchronized boolean isBatteryCharging() {
391         if (mHotspotNetworkData == null) {
392             return false;
393         }
394         if (BuildCompat.isAtLeastV()
395                 && NonSdkApiWrapper.isNetworkProviderBatteryChargingStatusEnabled()
396                 && mHotspotNetworkData.getNetworkProviderInfo().isBatteryCharging()) {
397             return true;
398         }
399         // With API flag on we still support either the API or the bundle for compatibility.
400         return mHotspotNetworkData.getNetworkProviderInfo().getExtras().getBoolean(
401                 EXTRA_KEY_IS_BATTERY_CHARGING, false);
402     }
403 
404     @Override
canConnect()405     public synchronized boolean canConnect() {
406         return getConnectedState() == CONNECTED_STATE_DISCONNECTED;
407     }
408 
409     @Override
connect(@ullable ConnectCallback callback)410     public synchronized void connect(@Nullable ConnectCallback callback) {
411         mConnectCallback = callback;
412         if (mSharedConnectivityManager == null) {
413             if (callback != null) {
414                 mCallbackHandler.post(() -> callback.onConnectResult(
415                         ConnectCallback.CONNECT_STATUS_FAILURE_UNKNOWN));
416             }
417             return;
418         }
419         mSharedConnectivityManager.connectHotspotNetwork(mHotspotNetworkData);
420     }
421 
422     @Override
canDisconnect()423     public synchronized boolean canDisconnect() {
424         return getConnectedState() != CONNECTED_STATE_DISCONNECTED;
425     }
426 
427     @Override
disconnect(@ullable DisconnectCallback callback)428     public synchronized void disconnect(@Nullable DisconnectCallback callback) {
429         mCalledDisconnect = true;
430         mDisconnectCallback = callback;
431         if (mSharedConnectivityManager == null) {
432             if (callback != null) {
433                 mCallbackHandler.post(() -> callback.onDisconnectResult(
434                         DisconnectCallback.DISCONNECT_STATUS_FAILURE_UNKNOWN));
435             }
436             return;
437         }
438         mSharedConnectivityManager.disconnectHotspotNetwork(mHotspotNetworkData);
439     }
440 
441     @Nullable
getHotspotNetworkData()442     public HotspotNetwork getHotspotNetworkData() {
443         return mHotspotNetworkData;
444     }
445 
446     /**
447      * Trigger ConnectCallback with data from SharedConnectivityService.
448      * @param status HotspotNetworkConnectionStatus#ConnectionStatus enum.
449      */
onConnectionStatusChanged(@onnectionStatus int status)450     public void onConnectionStatusChanged(@ConnectionStatus int status) {
451         mLastStatus = status;
452         switch (status) {
453             case HotspotNetworkConnectionStatus.CONNECTION_STATUS_ENABLING_HOTSPOT:
454                 mCalledConnect = true;
455                 mConnectionError = false;
456                 notifyOnUpdated();
457                 break;
458             case HotspotNetworkConnectionStatus.CONNECTION_STATUS_UNKNOWN_ERROR:
459             case HotspotNetworkConnectionStatus.CONNECTION_STATUS_PROVISIONING_FAILED:
460             case HotspotNetworkConnectionStatus.CONNECTION_STATUS_TETHERING_TIMEOUT:
461             case HotspotNetworkConnectionStatus.CONNECTION_STATUS_TETHERING_UNSUPPORTED:
462             case HotspotNetworkConnectionStatus.CONNECTION_STATUS_NO_CELL_DATA:
463             case HotspotNetworkConnectionStatus.CONNECTION_STATUS_ENABLING_HOTSPOT_FAILED:
464             case HotspotNetworkConnectionStatus.CONNECTION_STATUS_ENABLING_HOTSPOT_TIMEOUT:
465             case HotspotNetworkConnectionStatus.CONNECTION_STATUS_CONNECT_TO_HOTSPOT_FAILED:
466                 mCallbackHandler.post(() -> {
467                     final ConnectCallback connectCallback = mConnectCallback;
468                     if (connectCallback != null) {
469                         connectCallback.onConnectResult(
470                                 ConnectCallback.CONNECT_STATUS_FAILURE_UNKNOWN);
471                     }
472                 });
473                 mCalledConnect = false;
474                 mConnectionError = true;
475                 notifyOnUpdated();
476                 break;
477             case CONNECTION_STATUS_CONNECTED:
478                 mCallbackHandler.post(() -> {
479                     final ConnectCallback connectCallback = mConnectCallback;
480                     if (connectCallback != null) {
481                         connectCallback.onConnectResult(
482                                 ConnectCallback.CONNECT_STATUS_SUCCESS);
483                     }
484                 });
485                 mCalledConnect = false;
486                 mConnectionError = false;
487                 notifyOnUpdated();
488                 break;
489             default:
490                 // Do nothing
491         }
492     }
493 
494     static class HotspotNetworkEntryKey {
495         private static final String KEY_IS_VIRTUAL_ENTRY_KEY = "IS_VIRTUAL_ENTRY_KEY";
496         private static final String KEY_DEVICE_ID_KEY = "DEVICE_ID_KEY";
497         private static final String KEY_SCAN_RESULT_KEY = "SCAN_RESULT_KEY";
498 
499         private boolean mIsVirtualEntry;
500         private long mDeviceId;
501         @Nullable
502         private StandardWifiEntry.ScanResultKey mScanResultKey;
503 
504         /**
505          * Creates a HotspotNetworkEntryKey based on a {@link HotspotNetwork} parcelable object.
506          *
507          * @param hotspotNetworkData A {@link HotspotNetwork} object from SharedConnectivityService.
508          */
HotspotNetworkEntryKey(@onNull HotspotNetwork hotspotNetworkData)509         HotspotNetworkEntryKey(@NonNull HotspotNetwork hotspotNetworkData) {
510             mDeviceId = hotspotNetworkData.getDeviceId();
511             if (hotspotNetworkData.getHotspotSsid() == null || (
512                     hotspotNetworkData.getHotspotSecurityTypes() == null)) {
513                 mIsVirtualEntry = true;
514                 mScanResultKey = null;
515             } else {
516                 mIsVirtualEntry = false;
517                 mScanResultKey = new StandardWifiEntry.ScanResultKey(
518                         hotspotNetworkData.getHotspotSsid(),
519                         new ArrayList<>(hotspotNetworkData.getHotspotSecurityTypes()));
520             }
521         }
522 
523         /**
524          * Creates a HotspotNetworkEntryKey from its String representation.
525          */
HotspotNetworkEntryKey(@onNull String string)526         HotspotNetworkEntryKey(@NonNull String string) {
527             mScanResultKey = null;
528             if (!string.startsWith(KEY_PREFIX)) {
529                 Log.e(TAG, "String key does not start with key prefix!");
530                 return;
531             }
532             try {
533                 final JSONObject keyJson = new JSONObject(
534                         string.substring(KEY_PREFIX.length()));
535                 if (keyJson.has(KEY_IS_VIRTUAL_ENTRY_KEY)) {
536                     mIsVirtualEntry = keyJson.getBoolean(KEY_IS_VIRTUAL_ENTRY_KEY);
537                 }
538                 if (keyJson.has(KEY_DEVICE_ID_KEY)) {
539                     mDeviceId = keyJson.getLong(KEY_DEVICE_ID_KEY);
540                 }
541                 if (keyJson.has(KEY_SCAN_RESULT_KEY)) {
542                     mScanResultKey = new StandardWifiEntry.ScanResultKey(keyJson.getString(
543                             KEY_SCAN_RESULT_KEY));
544                 }
545             } catch (JSONException e) {
546                 Log.e(TAG, "JSONException while converting HotspotNetworkEntryKey to string: " + e);
547             }
548         }
549 
550         /**
551          * Returns the JSON String representation of this HotspotNetworkEntryKey.
552          */
553         @Override
toString()554         public String toString() {
555             final JSONObject keyJson = new JSONObject();
556             try {
557                 keyJson.put(KEY_IS_VIRTUAL_ENTRY_KEY, mIsVirtualEntry);
558                 keyJson.put(KEY_DEVICE_ID_KEY, mDeviceId);
559                 if (mScanResultKey != null) {
560                     keyJson.put(KEY_SCAN_RESULT_KEY, mScanResultKey.toString());
561                 }
562             } catch (JSONException e) {
563                 Log.wtf(TAG,
564                         "JSONException while converting HotspotNetworkEntryKey to string: " + e);
565             }
566             return KEY_PREFIX + keyJson.toString();
567         }
568 
isVirtualEntry()569         public boolean isVirtualEntry() {
570             return mIsVirtualEntry;
571         }
572 
getDeviceId()573         public long getDeviceId() {
574             return mDeviceId;
575         }
576 
577         /**
578          * Returns the ScanResultKey of this HotspotNetworkEntryKey to match against ScanResults
579          */
580         @Nullable
getScanResultKey()581         StandardWifiEntry.ScanResultKey getScanResultKey() {
582             return mScanResultKey;
583         }
584     }
585 
getDeviceTypeId(@eviceType int deviceType)586     private static String getDeviceTypeId(@DeviceType int deviceType) {
587         switch (deviceType) {
588             case NetworkProviderInfo.DEVICE_TYPE_PHONE:
589                 return "PHONE";
590             case NetworkProviderInfo.DEVICE_TYPE_TABLET:
591                 return "TABLET";
592             case NetworkProviderInfo.DEVICE_TYPE_LAPTOP:
593                 return "COMPUTER";
594             case NetworkProviderInfo.DEVICE_TYPE_WATCH:
595                 return "WATCH";
596             case NetworkProviderInfo.DEVICE_TYPE_AUTO:
597                 return "VEHICLE";
598             default:
599                 return "UNKNOWN";
600         }
601     }
602 }
603