1 /*
2  * Copyright (C) 2019 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.settings.wifi.slice;
18 
19 import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
20 import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY;
21 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
22 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
23 
24 import static com.android.settings.wifi.slice.WifiSlice.DEFAULT_EXPANDED_ROW_COUNT;
25 
26 import android.content.Context;
27 import android.content.Intent;
28 import android.net.ConnectivityManager;
29 import android.net.ConnectivityManager.NetworkCallback;
30 import android.net.Network;
31 import android.net.NetworkCapabilities;
32 import android.net.NetworkRequest;
33 import android.net.Uri;
34 import android.net.wifi.WifiInfo;
35 import android.os.Bundle;
36 import android.os.Handler;
37 import android.os.Looper;
38 import android.os.UserHandle;
39 import android.text.TextUtils;
40 import android.util.Log;
41 
42 import androidx.annotation.VisibleForTesting;
43 
44 import com.android.internal.util.Preconditions;
45 import com.android.settings.slices.SliceBackgroundWorker;
46 import com.android.settingslib.wifi.AccessPoint;
47 import com.android.settingslib.wifi.WifiTracker;
48 
49 import java.util.ArrayList;
50 import java.util.List;
51 
52 /**
53  * {@link SliceBackgroundWorker} for Wi-Fi, used by {@link WifiSlice}.
54  */
55 public class WifiScanWorker extends SliceBackgroundWorker<AccessPoint> implements
56         WifiTracker.WifiListener {
57 
58     private static final String TAG = "WifiScanWorker";
59 
60     @VisibleForTesting
61     WifiNetworkCallback mNetworkCallback;
62 
63     private final Context mContext;
64     private final ConnectivityManager mConnectivityManager;
65     private final WifiTracker mWifiTracker;
66 
67     private static String sClickedWifiSsid;
68 
WifiScanWorker(Context context, Uri uri)69     public WifiScanWorker(Context context, Uri uri) {
70         super(context, uri);
71         mContext = context;
72         mConnectivityManager = context.getSystemService(ConnectivityManager.class);
73         mWifiTracker = new WifiTracker(mContext, this /* wifiListener */,
74                 true /* includeSaved */, true /* includeScans */);
75     }
76 
77     @Override
onSlicePinned()78     protected void onSlicePinned() {
79         mWifiTracker.onStart();
80         onAccessPointsChanged();
81     }
82 
83     @Override
onSliceUnpinned()84     protected void onSliceUnpinned() {
85         mWifiTracker.onStop();
86         unregisterNetworkCallback();
87         clearClickedWifiOnSliceUnpinned();
88     }
89 
90     @Override
close()91     public void close() {
92         mWifiTracker.onDestroy();
93     }
94 
95     @Override
onWifiStateChanged(int state)96     public void onWifiStateChanged(int state) {
97         notifySliceChange();
98     }
99 
100     @Override
onConnectedChanged()101     public void onConnectedChanged() {
102     }
103 
104     @Override
onAccessPointsChanged()105     public void onAccessPointsChanged() {
106         // in case state has changed
107         if (!mWifiTracker.getManager().isWifiEnabled()) {
108             updateResults(null);
109             return;
110         }
111         // AccessPoints are sorted by the WifiTracker
112         final List<AccessPoint> accessPoints = mWifiTracker.getAccessPoints();
113         final List<AccessPoint> resultList = new ArrayList<>();
114         for (AccessPoint ap : accessPoints) {
115             if (ap.isReachable()) {
116                 resultList.add(clone(ap));
117                 if (resultList.size() >= DEFAULT_EXPANDED_ROW_COUNT) {
118                     break;
119                 }
120             }
121         }
122         updateResults(resultList);
123     }
124 
clone(AccessPoint accessPoint)125     private AccessPoint clone(AccessPoint accessPoint) {
126         final Bundle savedState = new Bundle();
127         accessPoint.saveWifiState(savedState);
128         return new AccessPoint(mContext, savedState);
129     }
130 
131     @Override
areListsTheSame(List<AccessPoint> a, List<AccessPoint> b)132     protected boolean areListsTheSame(List<AccessPoint> a, List<AccessPoint> b) {
133         if (!a.equals(b)) {
134             return false;
135         }
136 
137         // compare access point states one by one
138         final int listSize = a.size();
139         for (int i = 0; i < listSize; i++) {
140             if (a.get(i).getDetailedState() != b.get(i).getDetailedState()) {
141                 return false;
142             }
143         }
144         return true;
145     }
146 
saveClickedWifi(AccessPoint accessPoint)147     static void saveClickedWifi(AccessPoint accessPoint) {
148         sClickedWifiSsid = accessPoint.getSsidStr();
149     }
150 
clearClickedWifi()151     static void clearClickedWifi() {
152         sClickedWifiSsid = null;
153     }
154 
isWifiClicked(WifiInfo info)155     static boolean isWifiClicked(WifiInfo info) {
156         final String ssid = WifiInfo.removeDoubleQuotes(info.getSSID());
157         return !TextUtils.isEmpty(ssid) && TextUtils.equals(ssid, sClickedWifiSsid);
158     }
159 
clearClickedWifiOnSliceUnpinned()160     protected void clearClickedWifiOnSliceUnpinned() {
161         clearClickedWifi();
162     }
163 
isSessionValid()164     protected boolean isSessionValid() {
165         return true;
166     }
167 
registerNetworkCallback(Network wifiNetwork)168     public void registerNetworkCallback(Network wifiNetwork) {
169         if (wifiNetwork == null) {
170             return;
171         }
172 
173         if (mNetworkCallback != null && mNetworkCallback.isSameNetwork(wifiNetwork)) {
174             return;
175         }
176 
177         unregisterNetworkCallback();
178 
179         mNetworkCallback = new WifiNetworkCallback(wifiNetwork);
180         mConnectivityManager.registerNetworkCallback(
181                 new NetworkRequest.Builder()
182                         .clearCapabilities()
183                         .addTransportType(TRANSPORT_WIFI)
184                         .build(),
185                 mNetworkCallback,
186                 new Handler(Looper.getMainLooper()));
187     }
188 
unregisterNetworkCallback()189     public void unregisterNetworkCallback() {
190         if (mNetworkCallback != null) {
191             try {
192                 mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
193             } catch (RuntimeException e) {
194                 Log.e(TAG, "Unregistering CaptivePortalNetworkCallback failed.", e);
195             }
196             mNetworkCallback = null;
197         }
198     }
199 
200     class WifiNetworkCallback extends NetworkCallback {
201 
202         private final Network mNetwork;
203         private boolean mIsCaptivePortal;
204         private boolean mHasPartialConnectivity;
205         private boolean mIsValidated;
206 
WifiNetworkCallback(Network network)207         WifiNetworkCallback(Network network) {
208             mNetwork = Preconditions.checkNotNull(network);
209         }
210 
211         @Override
onCapabilitiesChanged(Network network, NetworkCapabilities nc)212         public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
213             if (!isSameNetwork(network)) {
214                 return;
215             }
216 
217             final boolean prevIsCaptivePortal = mIsCaptivePortal;
218             final boolean prevHasPartialConnectivity = mHasPartialConnectivity;
219             final boolean prevIsValidated = mIsValidated;
220 
221             mIsCaptivePortal = nc.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL);
222             mHasPartialConnectivity = nc.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY);
223             mIsValidated = nc.hasCapability(NET_CAPABILITY_VALIDATED);
224 
225             if (prevIsCaptivePortal == mIsCaptivePortal
226                     && prevHasPartialConnectivity == mHasPartialConnectivity
227                     && prevIsValidated == mIsValidated) {
228                 return;
229             }
230 
231             notifySliceChange();
232 
233             // Automatically start captive portal
234             if (!prevIsCaptivePortal && mIsCaptivePortal
235                     && isWifiClicked(mWifiTracker.getManager().getConnectionInfo())
236                     && isSessionValid()) {
237                 final Intent intent = new Intent(mContext, ConnectToWifiHandler.class)
238                         .putExtra(ConnectivityManager.EXTRA_NETWORK, network)
239                         .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
240                 // Sending a broadcast in the system process needs to specify a user
241                 mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
242             }
243         }
244 
245         /**
246          * Returns true if the supplied network is not null and is the same as the originally
247          * supplied value.
248          */
isSameNetwork(Network network)249         public boolean isSameNetwork(Network network) {
250             return mNetwork.equals(network);
251         }
252     }
253 }
254