• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.TargetApi;
23 import android.content.Context;
24 import android.net.wifi.WifiInfo;
25 import android.net.wifi.WifiManager;
26 import android.net.wifi.sharedconnectivity.app.HotspotNetwork;
27 import android.net.wifi.sharedconnectivity.app.HotspotNetworkConnectionStatus;
28 import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo;
29 import android.net.wifi.sharedconnectivity.app.SharedConnectivityManager;
30 import android.os.Handler;
31 import android.text.BidiFormatter;
32 import android.text.TextUtils;
33 import android.util.Log;
34 
35 import androidx.annotation.IntDef;
36 import androidx.annotation.IntRange;
37 import androidx.annotation.NonNull;
38 import androidx.annotation.Nullable;
39 import androidx.annotation.WorkerThread;
40 
41 import org.json.JSONException;
42 import org.json.JSONObject;
43 
44 import java.lang.annotation.Retention;
45 import java.lang.annotation.RetentionPolicy;
46 import java.util.ArrayList;
47 import java.util.Collections;
48 import java.util.Objects;
49 
50 /**
51  * WifiEntry representation of a Hotspot Network provided via {@link SharedConnectivityManager}.
52  */
53 @TargetApi(VERSION_CODES.UPSIDE_DOWN_CAKE)
54 public class HotspotNetworkEntry extends WifiEntry {
55     static final String TAG = "HotspotNetworkEntry";
56     public static final String KEY_PREFIX = "HotspotNetworkEntry:";
57 
58     @NonNull private final WifiTrackerInjector mInjector;
59     @NonNull private final Context mContext;
60     @Nullable private final SharedConnectivityManager mSharedConnectivityManager;
61 
62     @Nullable private HotspotNetwork mHotspotNetworkData;
63     @NonNull private HotspotNetworkEntryKey mKey;
64 
65     /**
66      * If editing this IntDef also edit the definition in:
67      * {@link android.net.wifi.sharedconnectivity.app.HotspotNetwork}
68      *
69      * @hide
70      */
71     @Retention(RetentionPolicy.SOURCE)
72     @IntDef({
73             HotspotNetwork.NETWORK_TYPE_UNKNOWN,
74             HotspotNetwork.NETWORK_TYPE_CELLULAR,
75             HotspotNetwork.NETWORK_TYPE_WIFI,
76             HotspotNetwork.NETWORK_TYPE_ETHERNET
77     })
78     public @interface NetworkType {} // TODO(b/271868642): Add IfThisThanThat lint
79 
80     /**
81      * If editing this IntDef also edit the definition in:
82      * {@link android.net.wifi.sharedconnectivity.app.NetworkProviderInfo}
83      *
84      * @hide
85      */
86     @Retention(RetentionPolicy.SOURCE)
87     @IntDef({
88             NetworkProviderInfo.DEVICE_TYPE_UNKNOWN,
89             NetworkProviderInfo.DEVICE_TYPE_PHONE,
90             NetworkProviderInfo.DEVICE_TYPE_TABLET,
91             NetworkProviderInfo.DEVICE_TYPE_LAPTOP,
92             NetworkProviderInfo.DEVICE_TYPE_WATCH,
93             NetworkProviderInfo.DEVICE_TYPE_AUTO
94     })
95     public @interface DeviceType {} // TODO(b/271868642): Add IfThisThanThat lint
96 
97     /**
98      * If editing this IntDef also edit the definition in:
99      * {@link android.net.wifi.sharedconnectivity.app.HotspotNetworkConnectionStatus}
100      *
101      * @hide
102      */
103     @Retention(RetentionPolicy.SOURCE)
104     @IntDef({
105             HotspotNetworkConnectionStatus.CONNECTION_STATUS_UNKNOWN,
106             HotspotNetworkConnectionStatus.CONNECTION_STATUS_ENABLING_HOTSPOT,
107             HotspotNetworkConnectionStatus.CONNECTION_STATUS_UNKNOWN_ERROR,
108             HotspotNetworkConnectionStatus.CONNECTION_STATUS_PROVISIONING_FAILED,
109             HotspotNetworkConnectionStatus.CONNECTION_STATUS_TETHERING_TIMEOUT,
110             HotspotNetworkConnectionStatus.CONNECTION_STATUS_TETHERING_UNSUPPORTED,
111             HotspotNetworkConnectionStatus.CONNECTION_STATUS_NO_CELL_DATA,
112             HotspotNetworkConnectionStatus.CONNECTION_STATUS_ENABLING_HOTSPOT_FAILED,
113             HotspotNetworkConnectionStatus.CONNECTION_STATUS_ENABLING_HOTSPOT_TIMEOUT,
114             HotspotNetworkConnectionStatus.CONNECTION_STATUS_CONNECT_TO_HOTSPOT_FAILED,
115     })
116     public @interface ConnectionStatus {} // TODO(b/271868642): Add IfThisThanThat lint
117 
118     /**
119      * Create a HotspotNetworkEntry from HotspotNetwork data.
120      */
HotspotNetworkEntry( @onNull WifiTrackerInjector injector, @NonNull Context context, @NonNull Handler callbackHandler, @NonNull WifiManager wifiManager, @Nullable SharedConnectivityManager sharedConnectivityManager, @NonNull HotspotNetwork hotspotNetworkData)121     HotspotNetworkEntry(
122             @NonNull WifiTrackerInjector injector,
123             @NonNull Context context, @NonNull Handler callbackHandler,
124             @NonNull WifiManager wifiManager,
125             @Nullable SharedConnectivityManager sharedConnectivityManager,
126             @NonNull HotspotNetwork hotspotNetworkData) {
127         super(injector, callbackHandler, wifiManager, false /*forSavedNetworksPage*/);
128         mInjector = injector;
129         mContext = context;
130         mSharedConnectivityManager = sharedConnectivityManager;
131         mHotspotNetworkData = hotspotNetworkData;
132         mKey = new HotspotNetworkEntryKey(hotspotNetworkData);
133     }
134 
135     /**
136      * Create a HotspotNetworkEntry from HotspotNetworkEntryKey.
137      */
HotspotNetworkEntry( @onNull WifiTrackerInjector injector, @NonNull Context context, @NonNull Handler callbackHandler, @NonNull WifiManager wifiManager, @Nullable SharedConnectivityManager sharedConnectivityManager, @NonNull HotspotNetworkEntryKey key)138     HotspotNetworkEntry(
139             @NonNull WifiTrackerInjector injector,
140             @NonNull Context context, @NonNull Handler callbackHandler,
141             @NonNull WifiManager wifiManager,
142             @Nullable SharedConnectivityManager sharedConnectivityManager,
143             @NonNull HotspotNetworkEntryKey key) {
144         super(injector, callbackHandler, wifiManager, false /*forSavedNetworksPage*/);
145         mInjector = injector;
146         mContext = context;
147         mSharedConnectivityManager = sharedConnectivityManager;
148         mHotspotNetworkData = null;
149         mKey = key;
150     }
151 
152     @Override
getKey()153     public String getKey() {
154         return mKey.toString();
155     }
156 
getHotspotNetworkEntryKey()157     public HotspotNetworkEntryKey getHotspotNetworkEntryKey() {
158         return mKey;
159     }
160 
161     /**
162      * Updates the hotspot data for this entry. Creates a new key when called.
163      *
164      * @param hotspotNetworkData An updated data set from SharedConnectivityService.
165      */
166     @WorkerThread
updateHotspotNetworkData( @onNull HotspotNetwork hotspotNetworkData)167     protected synchronized void updateHotspotNetworkData(
168             @NonNull HotspotNetwork hotspotNetworkData) {
169         mHotspotNetworkData = hotspotNetworkData;
170         mKey = new HotspotNetworkEntryKey(hotspotNetworkData);
171         notifyOnUpdated();
172     }
173 
174     @WorkerThread
connectionInfoMatches(@onNull WifiInfo wifiInfo)175     protected synchronized boolean connectionInfoMatches(@NonNull WifiInfo wifiInfo) {
176         if (mKey.isVirtualEntry()) {
177             return false;
178         }
179         return Objects.equals(mKey.getScanResultKey(),
180                 new StandardWifiEntry.ScanResultKey(WifiInfo.sanitizeSsid(wifiInfo.getSSID()),
181                         Collections.singletonList(wifiInfo.getCurrentSecurityType())));
182     }
183 
184     @Override
getLevel()185     public int getLevel() {
186         if (getConnectedState() == CONNECTED_STATE_DISCONNECTED) {
187             return WIFI_LEVEL_MAX;
188         }
189         return super.getLevel();
190     }
191 
192     @Override
getTitle()193     public synchronized String getTitle() {
194         if (mHotspotNetworkData == null) {
195             return "";
196         }
197         return mHotspotNetworkData.getNetworkProviderInfo().getDeviceName();
198     }
199 
200     @Override
getSummary(boolean concise)201     public synchronized String getSummary(boolean concise) {
202         if (mHotspotNetworkData == null) {
203             return "";
204         }
205         if (mCalledConnect) {
206             return mContext.getString(R.string.wifitrackerlib_hotspot_network_connecting);
207         }
208         return mContext.getString(R.string.wifitrackerlib_hotspot_network_summary,
209                 BidiFormatter.getInstance().unicodeWrap(mHotspotNetworkData.getNetworkName()),
210                 BidiFormatter.getInstance().unicodeWrap(
211                         mHotspotNetworkData.getNetworkProviderInfo().getModelName()));
212     }
213 
214     /**
215      * Alternate summary string to be used on Network & internet page.
216      *
217      * @return Display string.
218      */
getAlternateSummary()219     public synchronized String getAlternateSummary() {
220         if (mHotspotNetworkData == null) {
221             return "";
222         }
223         return mContext.getString(R.string.wifitrackerlib_hotspot_network_alternate,
224                 BidiFormatter.getInstance().unicodeWrap(mHotspotNetworkData.getNetworkName()),
225                 BidiFormatter.getInstance().unicodeWrap(
226                         mHotspotNetworkData.getNetworkProviderInfo().getDeviceName()));
227     }
228 
229     @Override
getSsid()230     public synchronized String getSsid() {
231         StandardWifiEntry.ScanResultKey scanResultKey = mKey.getScanResultKey();
232         if (scanResultKey == null) {
233             return null;
234         }
235         return scanResultKey.getSsid();
236     }
237 
238     @Override
239     @Nullable
getMacAddress()240     public synchronized String getMacAddress() {
241         if (mWifiInfo == null) {
242             return null;
243         }
244         final String wifiInfoMac = mWifiInfo.getMacAddress();
245         if (!TextUtils.isEmpty(wifiInfoMac)
246                 && !TextUtils.equals(wifiInfoMac, DEFAULT_MAC_ADDRESS)) {
247             return wifiInfoMac;
248         }
249         if (getPrivacy() != PRIVACY_RANDOMIZED_MAC) {
250             final String[] factoryMacs = mWifiManager.getFactoryMacAddresses();
251             if (factoryMacs.length > 0) {
252                 return factoryMacs[0];
253             }
254         }
255         return null;
256     }
257 
258     @Override
259     @Privacy
getPrivacy()260     public int getPrivacy() {
261         return PRIVACY_RANDOMIZED_MAC;
262     }
263 
264     @Override
getSecurityString(boolean concise)265     public synchronized String getSecurityString(boolean concise) {
266         if (mHotspotNetworkData == null) {
267             return "";
268         }
269         return Utils.getSecurityString(mContext,
270                 new ArrayList<>(mHotspotNetworkData.getHotspotSecurityTypes()), concise);
271     }
272 
273     @Override
getStandardString()274     public synchronized String getStandardString() {
275         if (mWifiInfo == null) {
276             return "";
277         }
278         return Utils.getStandardString(mContext, mWifiInfo.getWifiStandard());
279     }
280 
281     @Override
getBandString()282     public synchronized String getBandString() {
283         if (mWifiInfo == null) {
284             return "";
285         }
286         return Utils.wifiInfoToBandString(mContext, mWifiInfo);
287     }
288 
289     /**
290      * Connection strength between the host device and the internet.
291      *
292      * @return Displayed connection strength in the range 0 to 4.
293      */
294     @IntRange(from = 0, to = 4)
getUpstreamConnectionStrength()295     public synchronized int getUpstreamConnectionStrength() {
296         if (mHotspotNetworkData == null) {
297             return 0;
298         }
299         return mHotspotNetworkData.getNetworkProviderInfo().getConnectionStrength();
300     }
301 
302     /**
303      * Network type used by the host device to connect to the internet.
304      *
305      * @return NetworkType enum.
306      */
307     @NetworkType
getNetworkType()308     public synchronized int getNetworkType() {
309         if (mHotspotNetworkData == null) {
310             return HotspotNetwork.NETWORK_TYPE_UNKNOWN;
311         }
312         return mHotspotNetworkData.getHostNetworkType();
313     }
314 
315     /**
316      * Device type of the host device.
317      *
318      * @return DeviceType enum.
319      */
320     @DeviceType
getDeviceType()321     public synchronized int getDeviceType() {
322         if (mHotspotNetworkData == null) {
323             return NetworkProviderInfo.DEVICE_TYPE_UNKNOWN;
324         }
325         return mHotspotNetworkData.getNetworkProviderInfo().getDeviceType();
326     }
327 
328     /**
329      * The battery percentage of the host device.
330      */
331     @IntRange(from = 0, to = 100)
getBatteryPercentage()332     public synchronized int getBatteryPercentage() {
333         if (mHotspotNetworkData == null) {
334             return 0;
335         }
336         return mHotspotNetworkData.getNetworkProviderInfo().getBatteryPercentage();
337     }
338 
339     /**
340      * If the host device is currently charging its battery.
341      */
isBatteryCharging()342     public synchronized boolean isBatteryCharging() {
343         if (mHotspotNetworkData == null) {
344             return false;
345         }
346         return mHotspotNetworkData.getExtras().getBoolean("is_battery_charging", false);
347     }
348 
349     @Override
canConnect()350     public synchronized boolean canConnect() {
351         return getConnectedState() == CONNECTED_STATE_DISCONNECTED;
352     }
353 
354     @Override
connect(@ullable ConnectCallback callback)355     public synchronized void connect(@Nullable ConnectCallback callback) {
356         mConnectCallback = callback;
357         if (mSharedConnectivityManager == null) {
358             if (callback != null) {
359                 mCallbackHandler.post(() -> callback.onConnectResult(
360                         ConnectCallback.CONNECT_STATUS_FAILURE_UNKNOWN));
361             }
362             return;
363         }
364         mSharedConnectivityManager.connectHotspotNetwork(mHotspotNetworkData);
365     }
366 
367     @Override
canDisconnect()368     public synchronized boolean canDisconnect() {
369         return getConnectedState() != CONNECTED_STATE_DISCONNECTED;
370     }
371 
372     @Override
disconnect(@ullable DisconnectCallback callback)373     public synchronized void disconnect(@Nullable DisconnectCallback callback) {
374         mDisconnectCallback = callback;
375         if (mSharedConnectivityManager == null) {
376             if (callback != null) {
377                 mCallbackHandler.post(() -> callback.onDisconnectResult(
378                         DisconnectCallback.DISCONNECT_STATUS_FAILURE_UNKNOWN));
379             }
380             return;
381         }
382         mSharedConnectivityManager.disconnectHotspotNetwork(mHotspotNetworkData);
383     }
384 
385     @Nullable
getHotspotNetworkData()386     public HotspotNetwork getHotspotNetworkData() {
387         return mHotspotNetworkData;
388     }
389 
390     /**
391      * Trigger ConnectCallback with data from SharedConnectivityService.
392      * @param status HotspotNetworkConnectionStatus#ConnectionStatus enum.
393      */
onConnectionStatusChanged(@onnectionStatus int status)394     public void onConnectionStatusChanged(@ConnectionStatus int status) {
395         if (mConnectCallback == null) return;
396         switch (status) {
397             case HotspotNetworkConnectionStatus.CONNECTION_STATUS_ENABLING_HOTSPOT:
398                 mCalledConnect = true;
399                 notifyOnUpdated();
400                 break;
401             case HotspotNetworkConnectionStatus.CONNECTION_STATUS_UNKNOWN_ERROR:
402             case HotspotNetworkConnectionStatus.CONNECTION_STATUS_PROVISIONING_FAILED:
403             case HotspotNetworkConnectionStatus.CONNECTION_STATUS_TETHERING_TIMEOUT:
404             case HotspotNetworkConnectionStatus.CONNECTION_STATUS_TETHERING_UNSUPPORTED:
405             case HotspotNetworkConnectionStatus.CONNECTION_STATUS_NO_CELL_DATA:
406             case HotspotNetworkConnectionStatus.CONNECTION_STATUS_ENABLING_HOTSPOT_FAILED:
407             case HotspotNetworkConnectionStatus.CONNECTION_STATUS_ENABLING_HOTSPOT_TIMEOUT:
408             case HotspotNetworkConnectionStatus.CONNECTION_STATUS_CONNECT_TO_HOTSPOT_FAILED:
409                 mCallbackHandler.post(() -> mConnectCallback.onConnectResult(
410                         ConnectCallback.CONNECT_STATUS_FAILURE_UNKNOWN));
411                 mCalledConnect = false;
412                 notifyOnUpdated();
413                 break;
414             default:
415                 // Do nothing
416         }
417     }
418 
419     static class HotspotNetworkEntryKey {
420         private static final String KEY_IS_VIRTUAL_ENTRY_KEY = "IS_VIRTUAL_ENTRY_KEY";
421         private static final String KEY_DEVICE_ID_KEY = "DEVICE_ID_KEY";
422         private static final String KEY_SCAN_RESULT_KEY = "SCAN_RESULT_KEY";
423 
424         private boolean mIsVirtualEntry;
425         private long mDeviceId;
426         @Nullable
427         private StandardWifiEntry.ScanResultKey mScanResultKey;
428 
429         /**
430          * Creates a HotspotNetworkEntryKey based on a {@link HotspotNetwork} parcelable object.
431          *
432          * @param hotspotNetworkData A {@link HotspotNetwork} object from SharedConnectivityService.
433          */
HotspotNetworkEntryKey(@onNull HotspotNetwork hotspotNetworkData)434         HotspotNetworkEntryKey(@NonNull HotspotNetwork hotspotNetworkData) {
435             mDeviceId = hotspotNetworkData.getDeviceId();
436             if (hotspotNetworkData.getHotspotSsid() == null || (
437                     hotspotNetworkData.getHotspotSecurityTypes() == null)) {
438                 mIsVirtualEntry = true;
439                 mScanResultKey = null;
440             } else {
441                 mIsVirtualEntry = false;
442                 mScanResultKey = new StandardWifiEntry.ScanResultKey(
443                         hotspotNetworkData.getHotspotSsid(),
444                         new ArrayList<>(hotspotNetworkData.getHotspotSecurityTypes()));
445             }
446         }
447 
448         /**
449          * Creates a HotspotNetworkEntryKey from its String representation.
450          */
HotspotNetworkEntryKey(@onNull String string)451         HotspotNetworkEntryKey(@NonNull String string) {
452             mScanResultKey = null;
453             if (!string.startsWith(KEY_PREFIX)) {
454                 Log.e(TAG, "String key does not start with key prefix!");
455                 return;
456             }
457             try {
458                 final JSONObject keyJson = new JSONObject(
459                         string.substring(KEY_PREFIX.length()));
460                 if (keyJson.has(KEY_IS_VIRTUAL_ENTRY_KEY)) {
461                     mIsVirtualEntry = keyJson.getBoolean(KEY_IS_VIRTUAL_ENTRY_KEY);
462                 }
463                 if (keyJson.has(KEY_DEVICE_ID_KEY)) {
464                     mDeviceId = keyJson.getLong(KEY_DEVICE_ID_KEY);
465                 }
466                 if (keyJson.has(KEY_SCAN_RESULT_KEY)) {
467                     mScanResultKey = new StandardWifiEntry.ScanResultKey(keyJson.getString(
468                             KEY_SCAN_RESULT_KEY));
469                 }
470             } catch (JSONException e) {
471                 Log.e(TAG, "JSONException while converting HotspotNetworkEntryKey to string: " + e);
472             }
473         }
474 
475         /**
476          * Returns the JSON String representation of this HotspotNetworkEntryKey.
477          */
478         @Override
toString()479         public String toString() {
480             final JSONObject keyJson = new JSONObject();
481             try {
482                 keyJson.put(KEY_IS_VIRTUAL_ENTRY_KEY, mIsVirtualEntry);
483                 keyJson.put(KEY_DEVICE_ID_KEY, mDeviceId);
484                 if (mScanResultKey != null) {
485                     keyJson.put(KEY_SCAN_RESULT_KEY, mScanResultKey.toString());
486                 }
487             } catch (JSONException e) {
488                 Log.wtf(TAG,
489                         "JSONException while converting HotspotNetworkEntryKey to string: " + e);
490             }
491             return KEY_PREFIX + keyJson.toString();
492         }
493 
isVirtualEntry()494         public boolean isVirtualEntry() {
495             return mIsVirtualEntry;
496         }
497 
getDeviceId()498         public long getDeviceId() {
499             return mDeviceId;
500         }
501 
502         /**
503          * Returns the ScanResultKey of this HotspotNetworkEntryKey to match against ScanResults
504          */
505         @Nullable
getScanResultKey()506         StandardWifiEntry.ScanResultKey getScanResultKey() {
507             return mScanResultKey;
508         }
509     }
510 }
511