• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.server.wifi;
18 
19 import static android.app.AppOpsManager.MODE_IGNORED;
20 import static android.app.AppOpsManager.OPSTR_CHANGE_WIFI_STATE;
21 
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.app.AppOpsManager;
25 import android.app.Notification;
26 import android.app.NotificationManager;
27 import android.app.PendingIntent;
28 import android.content.BroadcastReceiver;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.content.IntentFilter;
32 import android.content.pm.ApplicationInfo;
33 import android.content.pm.PackageManager;
34 import android.content.res.Resources;
35 import android.net.MacAddress;
36 import android.net.wifi.ScanResult;
37 import android.net.wifi.WifiConfiguration;
38 import android.net.wifi.WifiManager;
39 import android.net.wifi.WifiNetworkSuggestion;
40 import android.os.Handler;
41 import android.os.UserHandle;
42 import android.text.TextUtils;
43 import android.util.Log;
44 import android.util.Pair;
45 
46 import com.android.internal.R;
47 import com.android.internal.annotations.VisibleForTesting;
48 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
49 import com.android.internal.notification.SystemNotificationChannels;
50 import com.android.server.wifi.util.WifiPermissionsUtil;
51 
52 import java.io.FileDescriptor;
53 import java.io.PrintWriter;
54 import java.util.Collection;
55 import java.util.Collections;
56 import java.util.HashMap;
57 import java.util.HashSet;
58 import java.util.Iterator;
59 import java.util.List;
60 import java.util.Map;
61 import java.util.Objects;
62 import java.util.Set;
63 import java.util.stream.Collectors;
64 
65 import javax.annotation.concurrent.NotThreadSafe;
66 
67 /**
68  * Network Suggestions Manager.
69  * NOTE: This class should always be invoked from the main wifi service thread.
70  */
71 @NotThreadSafe
72 public class WifiNetworkSuggestionsManager {
73     private static final String TAG = "WifiNetworkSuggestionsManager";
74 
75     /** Intent when user tapped action button to allow the app. */
76     @VisibleForTesting
77     public static final String NOTIFICATION_USER_ALLOWED_APP_INTENT_ACTION =
78             "com.android.server.wifi.action.NetworkSuggestion.USER_ALLOWED_APP";
79     /** Intent when user tapped action button to disallow the app. */
80     @VisibleForTesting
81     public static final String NOTIFICATION_USER_DISALLOWED_APP_INTENT_ACTION =
82             "com.android.server.wifi.action.NetworkSuggestion.USER_DISALLOWED_APP";
83     /** Intent when user dismissed the notification. */
84     @VisibleForTesting
85     public static final String NOTIFICATION_USER_DISMISSED_INTENT_ACTION =
86             "com.android.server.wifi.action.NetworkSuggestion.USER_DISMISSED";
87     @VisibleForTesting
88     public static final String EXTRA_PACKAGE_NAME =
89             "com.android.server.wifi.extra.NetworkSuggestion.PACKAGE_NAME";
90     @VisibleForTesting
91     public static final String EXTRA_UID =
92             "com.android.server.wifi.extra.NetworkSuggestion.UID";
93 
94     private final Context mContext;
95     private final Resources mResources;
96     private final Handler mHandler;
97     private final AppOpsManager mAppOps;
98     private final NotificationManager mNotificationManager;
99     private final PackageManager mPackageManager;
100     private final WifiPermissionsUtil mWifiPermissionsUtil;
101     private final WifiConfigManager mWifiConfigManager;
102     private final WifiMetrics mWifiMetrics;
103     private final WifiInjector mWifiInjector;
104     private final FrameworkFacade mFrameworkFacade;
105     private final WifiKeyStore mWifiKeyStore;
106 
107     /**
108      * Per app meta data to store network suggestions, status, etc for each app providing network
109      * suggestions on the device.
110      */
111     public static class PerAppInfo {
112         /**
113          * Package Name of the app.
114          */
115         public final String packageName;
116         /**
117          * Set of active network suggestions provided by the app.
118          */
119         public final Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions = new HashSet<>();
120         /**
121          * Whether we have shown the user a notification for this app.
122          */
123         public boolean hasUserApproved = false;
124 
125         /** Stores the max size of the {@link #extNetworkSuggestions} list ever for this app */
126         public int maxSize = 0;
127 
PerAppInfo(@onNull String packageName)128         public PerAppInfo(@NonNull String packageName) {
129             this.packageName = packageName;
130         }
131 
132         // This is only needed for comparison in unit tests.
133         @Override
equals(Object other)134         public boolean equals(Object other) {
135             if (other == null) return false;
136             if (!(other instanceof PerAppInfo)) return false;
137             PerAppInfo otherPerAppInfo = (PerAppInfo) other;
138             return TextUtils.equals(packageName, otherPerAppInfo.packageName)
139                     && Objects.equals(extNetworkSuggestions, otherPerAppInfo.extNetworkSuggestions)
140                     && hasUserApproved == otherPerAppInfo.hasUserApproved;
141         }
142 
143         // This is only needed for comparison in unit tests.
144         @Override
hashCode()145         public int hashCode() {
146             return Objects.hash(packageName, extNetworkSuggestions, hasUserApproved);
147         }
148     }
149 
150     /**
151      * Internal container class which holds a network suggestion and a pointer to the
152      * {@link PerAppInfo} entry from {@link #mActiveNetworkSuggestionsPerApp} corresponding to the
153      * app that made the suggestion.
154      */
155     public static class ExtendedWifiNetworkSuggestion {
156         public final WifiNetworkSuggestion wns;
157         // Store the pointer to the corresponding app's meta data.
158         public final PerAppInfo perAppInfo;
159 
ExtendedWifiNetworkSuggestion(@onNull WifiNetworkSuggestion wns, @NonNull PerAppInfo perAppInfo)160         public ExtendedWifiNetworkSuggestion(@NonNull WifiNetworkSuggestion wns,
161                                              @NonNull PerAppInfo perAppInfo) {
162             this.wns = wns;
163             this.perAppInfo = perAppInfo;
164             this.wns.wifiConfiguration.fromWifiNetworkSuggestion = true;
165             this.wns.wifiConfiguration.ephemeral = true;
166             this.wns.wifiConfiguration.creatorName = perAppInfo.packageName;
167             this.wns.wifiConfiguration.creatorUid = wns.suggestorUid;
168         }
169 
170         @Override
hashCode()171         public int hashCode() {
172             return Objects.hash(wns); // perAppInfo not used for equals.
173         }
174 
175         @Override
equals(Object obj)176         public boolean equals(Object obj) {
177             if (this == obj) {
178                 return true;
179             }
180             if (!(obj instanceof ExtendedWifiNetworkSuggestion)) {
181                 return false;
182             }
183             ExtendedWifiNetworkSuggestion other = (ExtendedWifiNetworkSuggestion) obj;
184             return wns.equals(other.wns); // perAppInfo not used for equals.
185         }
186 
187         @Override
toString()188         public String toString() {
189             return "Extended" + wns.toString();
190         }
191 
192         /**
193          * Convert from {@link WifiNetworkSuggestion} to a new instance of
194          * {@link ExtendedWifiNetworkSuggestion}.
195          */
fromWns( @onNull WifiNetworkSuggestion wns, @NonNull PerAppInfo perAppInfo)196         public static ExtendedWifiNetworkSuggestion fromWns(
197                 @NonNull WifiNetworkSuggestion wns, @NonNull PerAppInfo perAppInfo) {
198             return new ExtendedWifiNetworkSuggestion(wns, perAppInfo);
199         }
200     }
201 
202     /**
203      * Map of package name of an app to the set of active network suggestions provided by the app.
204      */
205     private final Map<String, PerAppInfo> mActiveNetworkSuggestionsPerApp = new HashMap<>();
206     /**
207      * Map of package name of an app to the app ops changed listener for the app.
208      */
209     private final Map<String, AppOpsChangedListener> mAppOpsChangedListenerPerApp = new HashMap<>();
210     /**
211      * Map maintained to help lookup all the network suggestions (with no bssid) that match a
212      * provided scan result.
213      * Note:
214      * <li>There could be multiple suggestions (provided by different apps) that match a single
215      * scan result.</li>
216      * <li>Adding/Removing to this set for scan result lookup is expensive. But, we expect scan
217      * result lookup to happen much more often than apps modifying network suggestions.</li>
218      */
219     private final Map<ScanResultMatchInfo, Set<ExtendedWifiNetworkSuggestion>>
220             mActiveScanResultMatchInfoWithNoBssid = new HashMap<>();
221     /**
222      * Map maintained to help lookup all the network suggestions (with bssid) that match a provided
223      * scan result.
224      * Note:
225      * <li>There could be multiple suggestions (provided by different apps) that match a single
226      * scan result.</li>
227      * <li>Adding/Removing to this set for scan result lookup is expensive. But, we expect scan
228      * result lookup to happen much more often than apps modifying network suggestions.</li>
229      */
230     private final Map<Pair<ScanResultMatchInfo, MacAddress>, Set<ExtendedWifiNetworkSuggestion>>
231             mActiveScanResultMatchInfoWithBssid = new HashMap<>();
232     /**
233      * List of {@link WifiNetworkSuggestion} matching the current connected network.
234      */
235     private Set<ExtendedWifiNetworkSuggestion> mActiveNetworkSuggestionsMatchingConnection;
236 
237     /**
238      * Intent filter for processing notification actions.
239      */
240     private final IntentFilter mIntentFilter;
241 
242     /**
243      * Verbose logging flag.
244      */
245     private boolean mVerboseLoggingEnabled = false;
246     /**
247      * Indicates that we have new data to serialize.
248      */
249     private boolean mHasNewDataToSerialize = false;
250     /**
251      * Indicates if the user approval notification is active.
252      */
253     private boolean mUserApprovalNotificationActive = false;
254     /**
255      * Stores the name of the user approval notification that is active.
256      */
257     private String mUserApprovalNotificationPackageName;
258 
259     /**
260      * Listener for app-ops changes for active suggestor apps.
261      */
262     private final class AppOpsChangedListener implements AppOpsManager.OnOpChangedListener {
263         private final String mPackageName;
264         private final int mUid;
265 
AppOpsChangedListener(@onNull String packageName, int uid)266         AppOpsChangedListener(@NonNull String packageName, int uid) {
267             mPackageName = packageName;
268             mUid = uid;
269         }
270 
271         @Override
onOpChanged(String op, String packageName)272         public void onOpChanged(String op, String packageName) {
273             mHandler.post(() -> {
274                 if (!mPackageName.equals(packageName)) return;
275                 if (!OPSTR_CHANGE_WIFI_STATE.equals(op)) return;
276 
277                 // Ensure the uid to package mapping is still correct.
278                 try {
279                     mAppOps.checkPackage(mUid, mPackageName);
280                 } catch (SecurityException e) {
281                     Log.wtf(TAG, "Invalid uid/package" + packageName);
282                     return;
283                 }
284 
285                 if (mAppOps.unsafeCheckOpNoThrow(OPSTR_CHANGE_WIFI_STATE, mUid, mPackageName)
286                         == AppOpsManager.MODE_IGNORED) {
287                     Log.i(TAG, "User disallowed change wifi state for " + packageName);
288                     // User disabled the app, remove app from database. We want the notification
289                     // again if the user enabled the app-op back.
290                     removeApp(mPackageName);
291                 }
292             });
293         }
294     };
295 
296     /**
297      * Module to interact with the wifi config store.
298      */
299     private class NetworkSuggestionDataSource implements NetworkSuggestionStoreData.DataSource {
300         @Override
toSerialize()301         public Map<String, PerAppInfo> toSerialize() {
302             // Clear the flag after writing to disk.
303             // TODO(b/115504887): Don't reset the flag on write failure.
304             mHasNewDataToSerialize = false;
305             return mActiveNetworkSuggestionsPerApp;
306         }
307 
308         @Override
309 
fromDeserialized(Map<String, PerAppInfo> networkSuggestionsMap)310         public void fromDeserialized(Map<String, PerAppInfo> networkSuggestionsMap) {
311             mActiveNetworkSuggestionsPerApp.putAll(networkSuggestionsMap);
312             // Build the scan cache.
313             for (Map.Entry<String, PerAppInfo> entry : networkSuggestionsMap.entrySet()) {
314                 String packageName = entry.getKey();
315                 Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions =
316                         entry.getValue().extNetworkSuggestions;
317                 if (!extNetworkSuggestions.isEmpty()) {
318                     // Start tracking app-op changes from the app if they have active suggestions.
319                     startTrackingAppOpsChange(packageName,
320                             extNetworkSuggestions.iterator().next().wns.suggestorUid);
321                 }
322                 addToScanResultMatchInfoMap(extNetworkSuggestions);
323             }
324         }
325 
326         @Override
reset()327         public void reset() {
328             mActiveNetworkSuggestionsPerApp.clear();
329             mActiveScanResultMatchInfoWithBssid.clear();
330             mActiveScanResultMatchInfoWithNoBssid.clear();
331         }
332 
333         @Override
hasNewDataToSerialize()334         public boolean hasNewDataToSerialize() {
335             return mHasNewDataToSerialize;
336         }
337     }
338 
339     private final BroadcastReceiver mBroadcastReceiver =
340             new BroadcastReceiver() {
341                 @Override
342                 public void onReceive(Context context, Intent intent) {
343                     String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
344                     if (packageName == null) {
345                         Log.e(TAG, "No package name found in intent");
346                         return;
347                     }
348                     int uid = intent.getIntExtra(EXTRA_UID, -1);
349                     if (uid == -1) {
350                         Log.e(TAG, "No uid found in intent");
351                         return;
352                     }
353                     switch (intent.getAction()) {
354                         case NOTIFICATION_USER_ALLOWED_APP_INTENT_ACTION:
355                             Log.i(TAG, "User clicked to allow app");
356                             // Set the user approved flag.
357                             setHasUserApprovedForApp(true, packageName);
358                             break;
359                         case NOTIFICATION_USER_DISALLOWED_APP_INTENT_ACTION:
360                             Log.i(TAG, "User clicked to disallow app");
361                             // Set the user approved flag.
362                             setHasUserApprovedForApp(false, packageName);
363                             // Take away CHANGE_WIFI_STATE app-ops from the app.
364                             mAppOps.setMode(AppOpsManager.OP_CHANGE_WIFI_STATE, uid, packageName,
365                                     MODE_IGNORED);
366                             break;
367                         case NOTIFICATION_USER_DISMISSED_INTENT_ACTION:
368                             Log.i(TAG, "User dismissed the notification");
369                             mUserApprovalNotificationActive = false;
370                             return; // no need to cancel a dismissed notification, return.
371                         default:
372                             Log.e(TAG, "Unknown action " + intent.getAction());
373                             return;
374                     }
375                     // Clear notification once the user interacts with it.
376                     mUserApprovalNotificationActive = false;
377                     mNotificationManager.cancel(SystemMessage.NOTE_NETWORK_SUGGESTION_AVAILABLE);
378                 }
379             };
380 
WifiNetworkSuggestionsManager(Context context, Handler handler, WifiInjector wifiInjector, WifiPermissionsUtil wifiPermissionsUtil, WifiConfigManager wifiConfigManager, WifiConfigStore wifiConfigStore, WifiMetrics wifiMetrics, WifiKeyStore keyStore)381     public WifiNetworkSuggestionsManager(Context context, Handler handler,
382                                          WifiInjector wifiInjector,
383                                          WifiPermissionsUtil wifiPermissionsUtil,
384                                          WifiConfigManager wifiConfigManager,
385                                          WifiConfigStore wifiConfigStore,
386                                          WifiMetrics wifiMetrics,
387                                          WifiKeyStore keyStore) {
388         mContext = context;
389         mResources = context.getResources();
390         mHandler = handler;
391         mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
392         mNotificationManager =
393                 (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
394         mPackageManager = context.getPackageManager();
395         mWifiInjector = wifiInjector;
396         mFrameworkFacade = mWifiInjector.getFrameworkFacade();
397         mWifiPermissionsUtil = wifiPermissionsUtil;
398         mWifiConfigManager = wifiConfigManager;
399         mWifiMetrics = wifiMetrics;
400         mWifiKeyStore = keyStore;
401 
402         // register the data store for serializing/deserializing data.
403         wifiConfigStore.registerStoreData(
404                 wifiInjector.makeNetworkSuggestionStoreData(new NetworkSuggestionDataSource()));
405 
406         // Register broadcast receiver for UI interactions.
407         mIntentFilter = new IntentFilter();
408         mIntentFilter.addAction(NOTIFICATION_USER_ALLOWED_APP_INTENT_ACTION);
409         mIntentFilter.addAction(NOTIFICATION_USER_DISALLOWED_APP_INTENT_ACTION);
410         mIntentFilter.addAction(NOTIFICATION_USER_DISMISSED_INTENT_ACTION);
411         mContext.registerReceiver(mBroadcastReceiver, mIntentFilter);
412     }
413 
414     /**
415      * Enable verbose logging.
416      */
enableVerboseLogging(int verbose)417     public void enableVerboseLogging(int verbose) {
418         mVerboseLoggingEnabled = verbose > 0;
419     }
420 
saveToStore()421     private void saveToStore() {
422         // Set the flag to let WifiConfigStore that we have new data to write.
423         mHasNewDataToSerialize = true;
424         if (!mWifiConfigManager.saveToStore(true)) {
425             Log.w(TAG, "Failed to save to store");
426         }
427     }
428 
addToScanResultMatchInfoMap( @onNull Collection<ExtendedWifiNetworkSuggestion> extNetworkSuggestions)429     private void addToScanResultMatchInfoMap(
430             @NonNull Collection<ExtendedWifiNetworkSuggestion> extNetworkSuggestions) {
431         for (ExtendedWifiNetworkSuggestion extNetworkSuggestion : extNetworkSuggestions) {
432             ScanResultMatchInfo scanResultMatchInfo =
433                     ScanResultMatchInfo.fromWifiConfiguration(
434                             extNetworkSuggestion.wns.wifiConfiguration);
435             Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestionsForScanResultMatchInfo;
436             if (!TextUtils.isEmpty(extNetworkSuggestion.wns.wifiConfiguration.BSSID)) {
437                 Pair<ScanResultMatchInfo, MacAddress> lookupPair =
438                         Pair.create(scanResultMatchInfo,
439                                 MacAddress.fromString(
440                                         extNetworkSuggestion.wns.wifiConfiguration.BSSID));
441                 extNetworkSuggestionsForScanResultMatchInfo =
442                         mActiveScanResultMatchInfoWithBssid.get(lookupPair);
443                 if (extNetworkSuggestionsForScanResultMatchInfo == null) {
444                     extNetworkSuggestionsForScanResultMatchInfo = new HashSet<>();
445                     mActiveScanResultMatchInfoWithBssid.put(
446                             lookupPair, extNetworkSuggestionsForScanResultMatchInfo);
447                 }
448             } else {
449                 extNetworkSuggestionsForScanResultMatchInfo =
450                         mActiveScanResultMatchInfoWithNoBssid.get(scanResultMatchInfo);
451                 if (extNetworkSuggestionsForScanResultMatchInfo == null) {
452                     extNetworkSuggestionsForScanResultMatchInfo = new HashSet<>();
453                     mActiveScanResultMatchInfoWithNoBssid.put(
454                             scanResultMatchInfo, extNetworkSuggestionsForScanResultMatchInfo);
455                 }
456             }
457             extNetworkSuggestionsForScanResultMatchInfo.add(extNetworkSuggestion);
458         }
459     }
460 
removeFromScanResultMatchInfoMap( @onNull Collection<ExtendedWifiNetworkSuggestion> extNetworkSuggestions)461     private void removeFromScanResultMatchInfoMap(
462             @NonNull Collection<ExtendedWifiNetworkSuggestion> extNetworkSuggestions) {
463         for (ExtendedWifiNetworkSuggestion extNetworkSuggestion : extNetworkSuggestions) {
464             ScanResultMatchInfo scanResultMatchInfo =
465                     ScanResultMatchInfo.fromWifiConfiguration(
466                             extNetworkSuggestion.wns.wifiConfiguration);
467             Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestionsForScanResultMatchInfo;
468             if (!TextUtils.isEmpty(extNetworkSuggestion.wns.wifiConfiguration.BSSID)) {
469                 Pair<ScanResultMatchInfo, MacAddress> lookupPair =
470                         Pair.create(scanResultMatchInfo,
471                                 MacAddress.fromString(
472                                         extNetworkSuggestion.wns.wifiConfiguration.BSSID));
473                 extNetworkSuggestionsForScanResultMatchInfo =
474                         mActiveScanResultMatchInfoWithBssid.get(lookupPair);
475                 // This should never happen because we should have done necessary error checks in
476                 // the parent method.
477                 if (extNetworkSuggestionsForScanResultMatchInfo == null) {
478                     Log.wtf(TAG, "No scan result match info found.");
479                 }
480                 extNetworkSuggestionsForScanResultMatchInfo.remove(extNetworkSuggestion);
481                 // Remove the set from map if empty.
482                 if (extNetworkSuggestionsForScanResultMatchInfo.isEmpty()) {
483                     mActiveScanResultMatchInfoWithBssid.remove(lookupPair);
484                 }
485             } else {
486                 extNetworkSuggestionsForScanResultMatchInfo =
487                         mActiveScanResultMatchInfoWithNoBssid.get(scanResultMatchInfo);
488                 // This should never happen because we should have done necessary error checks in
489                 // the parent method.
490                 if (extNetworkSuggestionsForScanResultMatchInfo == null) {
491                     Log.wtf(TAG, "No scan result match info found.");
492                 }
493                 extNetworkSuggestionsForScanResultMatchInfo.remove(extNetworkSuggestion);
494                 // Remove the set from map if empty.
495                 if (extNetworkSuggestionsForScanResultMatchInfo.isEmpty()) {
496                     mActiveScanResultMatchInfoWithNoBssid.remove(scanResultMatchInfo);
497                 }
498             }
499         }
500     }
501 
502     // Issues a disconnect if the only serving network suggestion is removed.
503     // TODO (b/115504887): What if there is also a saved network with the same credentials?
triggerDisconnectIfServingNetworkSuggestionRemoved( Collection<ExtendedWifiNetworkSuggestion> extNetworkSuggestionsRemoved)504     private void triggerDisconnectIfServingNetworkSuggestionRemoved(
505             Collection<ExtendedWifiNetworkSuggestion> extNetworkSuggestionsRemoved) {
506         if (mActiveNetworkSuggestionsMatchingConnection == null
507                 || mActiveNetworkSuggestionsMatchingConnection.isEmpty()) {
508             return;
509         }
510         if (mActiveNetworkSuggestionsMatchingConnection.removeAll(extNetworkSuggestionsRemoved)) {
511             if (mActiveNetworkSuggestionsMatchingConnection.isEmpty()) {
512                 Log.i(TAG, "Only network suggestion matching the connected network removed. "
513                         + "Disconnecting...");
514                 mWifiInjector.getClientModeImpl().disconnectCommand();
515             }
516         }
517     }
518 
startTrackingAppOpsChange(@onNull String packageName, int uid)519     private void startTrackingAppOpsChange(@NonNull String packageName, int uid) {
520         AppOpsChangedListener appOpsChangedListener =
521                 new AppOpsChangedListener(packageName, uid);
522         mAppOps.startWatchingMode(OPSTR_CHANGE_WIFI_STATE, packageName, appOpsChangedListener);
523         mAppOpsChangedListenerPerApp.put(packageName, appOpsChangedListener);
524     }
525 
526     /**
527      * Helper method to convert the incoming collection of public {@link WifiNetworkSuggestion}
528      * objects to a set of corresponding internal wrapper
529      * {@link ExtendedWifiNetworkSuggestion} objects.
530      */
convertToExtendedWnsSet( final Collection<WifiNetworkSuggestion> networkSuggestions, final PerAppInfo perAppInfo)531     private Set<ExtendedWifiNetworkSuggestion> convertToExtendedWnsSet(
532             final Collection<WifiNetworkSuggestion> networkSuggestions,
533             final PerAppInfo perAppInfo) {
534         return networkSuggestions
535                 .stream()
536                 .collect(Collectors.mapping(
537                         n -> ExtendedWifiNetworkSuggestion.fromWns(n, perAppInfo),
538                         Collectors.toSet()));
539     }
540 
541     /**
542      * Helper method to convert the incoming collection of internal wrapper
543      * {@link ExtendedWifiNetworkSuggestion} objects to a set of corresponding public
544      * {@link WifiNetworkSuggestion} objects.
545      */
convertToWnsSet( final Collection<ExtendedWifiNetworkSuggestion> extNetworkSuggestions)546     private Set<WifiNetworkSuggestion> convertToWnsSet(
547             final Collection<ExtendedWifiNetworkSuggestion> extNetworkSuggestions) {
548         return extNetworkSuggestions
549                 .stream()
550                 .collect(Collectors.mapping(
551                         n -> n.wns,
552                         Collectors.toSet()));
553     }
554 
555     /**
556      * Add the provided list of network suggestions from the corresponding app's active list.
557      */
add( List<WifiNetworkSuggestion> networkSuggestions, int uid, String packageName)558     public @WifiManager.NetworkSuggestionsStatusCode int add(
559             List<WifiNetworkSuggestion> networkSuggestions, int uid, String packageName) {
560         if (mVerboseLoggingEnabled) {
561             Log.v(TAG, "Adding " + networkSuggestions.size() + " networks from " + packageName);
562         }
563         if (networkSuggestions.isEmpty()) {
564             Log.w(TAG, "Empty list of network suggestions for " + packageName + ". Ignoring");
565             return WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS;
566         }
567         PerAppInfo perAppInfo = mActiveNetworkSuggestionsPerApp.get(packageName);
568         if (perAppInfo == null) {
569             perAppInfo = new PerAppInfo(packageName);
570             mActiveNetworkSuggestionsPerApp.put(packageName, perAppInfo);
571             if (mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(uid)) {
572                 Log.i(TAG, "Setting the carrier provisioning app approved");
573                 perAppInfo.hasUserApproved = true;
574             }
575         }
576         Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions =
577                 convertToExtendedWnsSet(networkSuggestions, perAppInfo);
578         // check if the app is trying to in-place modify network suggestions.
579         if (!Collections.disjoint(perAppInfo.extNetworkSuggestions, extNetworkSuggestions)) {
580             Log.e(TAG, "Failed to add network suggestions for " + packageName
581                     + ". Modification of active network suggestions disallowed");
582             return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_DUPLICATE;
583         }
584         if (perAppInfo.extNetworkSuggestions.size() + extNetworkSuggestions.size()
585                 > WifiManager.NETWORK_SUGGESTIONS_MAX_PER_APP) {
586             Log.e(TAG, "Failed to add network suggestions for " + packageName
587                     + ". Exceeds max per app, current list size: "
588                     + perAppInfo.extNetworkSuggestions.size()
589                     + ", new list size: "
590                     + extNetworkSuggestions.size());
591             return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_EXCEEDS_MAX_PER_APP;
592         }
593         if (perAppInfo.extNetworkSuggestions.isEmpty()) {
594             // Start tracking app-op changes from the app if they have active suggestions.
595             startTrackingAppOpsChange(packageName, uid);
596         }
597         Iterator<ExtendedWifiNetworkSuggestion> iterator = extNetworkSuggestions.iterator();
598         // Install enterprise network suggestion catificate.
599         while (iterator.hasNext()) {
600             WifiConfiguration config = iterator.next().wns.wifiConfiguration;
601             if (!config.isEnterprise()) {
602                 continue;
603             }
604             if (!mWifiKeyStore.updateNetworkKeys(config, null)) {
605                 Log.e(TAG, "Enterprise network install failure for SSID: "
606                         + config.SSID);
607                 iterator.remove();
608             }
609         }
610         perAppInfo.extNetworkSuggestions.addAll(extNetworkSuggestions);
611         // Update the max size for this app.
612         perAppInfo.maxSize = Math.max(perAppInfo.extNetworkSuggestions.size(), perAppInfo.maxSize);
613         addToScanResultMatchInfoMap(extNetworkSuggestions);
614         saveToStore();
615         mWifiMetrics.incrementNetworkSuggestionApiNumModification();
616         mWifiMetrics.noteNetworkSuggestionApiListSizeHistogram(getAllMaxSizes());
617         return WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS;
618     }
619 
stopTrackingAppOpsChange(@onNull String packageName)620     private void stopTrackingAppOpsChange(@NonNull String packageName) {
621         AppOpsChangedListener appOpsChangedListener =
622                 mAppOpsChangedListenerPerApp.remove(packageName);
623         if (appOpsChangedListener == null) {
624             Log.wtf(TAG, "No app ops listener found for " + packageName);
625             return;
626         }
627         mAppOps.stopWatchingMode(appOpsChangedListener);
628     }
629 
removeInternal( @onNull Collection<ExtendedWifiNetworkSuggestion> extNetworkSuggestions, @NonNull String packageName, @NonNull PerAppInfo perAppInfo)630     private void removeInternal(
631             @NonNull Collection<ExtendedWifiNetworkSuggestion> extNetworkSuggestions,
632             @NonNull String packageName,
633             @NonNull PerAppInfo perAppInfo) {
634         // Get internal suggestions
635         Set<ExtendedWifiNetworkSuggestion> removingSuggestions =
636                 new HashSet<>(perAppInfo.extNetworkSuggestions);
637         if (!extNetworkSuggestions.isEmpty()) {
638             // Keep the internal suggestions need to remove.
639             removingSuggestions.retainAll(extNetworkSuggestions);
640             perAppInfo.extNetworkSuggestions.removeAll(extNetworkSuggestions);
641         } else {
642             // empty list is used to clear everything for the app. Store a copy for use below.
643             extNetworkSuggestions = new HashSet<>(perAppInfo.extNetworkSuggestions);
644             perAppInfo.extNetworkSuggestions.clear();
645         }
646         if (perAppInfo.extNetworkSuggestions.isEmpty()) {
647             // Note: We don't remove the app entry even if there is no active suggestions because
648             // we want to keep the notification state for all apps that have ever provided
649             // suggestions.
650             if (mVerboseLoggingEnabled) Log.v(TAG, "No active suggestions for " + packageName);
651             // Stop tracking app-op changes from the app if they don't have active suggestions.
652             stopTrackingAppOpsChange(packageName);
653         }
654         // Clean the enterprise certifiacte.
655         for (ExtendedWifiNetworkSuggestion ewns : removingSuggestions) {
656             WifiConfiguration config = ewns.wns.wifiConfiguration;
657             if (!config.isEnterprise()) {
658                 continue;
659             }
660             mWifiKeyStore.removeKeys(config.enterpriseConfig);
661         }
662         // Clear the scan cache.
663         removeFromScanResultMatchInfoMap(removingSuggestions);
664     }
665 
666     /**
667      * Remove the provided list of network suggestions from the corresponding app's active list.
668      */
remove( List<WifiNetworkSuggestion> networkSuggestions, String packageName)669     public @WifiManager.NetworkSuggestionsStatusCode int remove(
670             List<WifiNetworkSuggestion> networkSuggestions, String packageName) {
671         if (mVerboseLoggingEnabled) {
672             Log.v(TAG, "Removing " + networkSuggestions.size() + " networks from " + packageName);
673         }
674         PerAppInfo perAppInfo = mActiveNetworkSuggestionsPerApp.get(packageName);
675         if (perAppInfo == null) {
676             Log.e(TAG, "Failed to remove network suggestions for " + packageName
677                     + ". No network suggestions found");
678             return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_REMOVE_INVALID;
679         }
680         Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions =
681                 convertToExtendedWnsSet(networkSuggestions, perAppInfo);
682         // check if all the request network suggestions are present in the active list.
683         if (!extNetworkSuggestions.isEmpty()
684                 && !perAppInfo.extNetworkSuggestions.containsAll(extNetworkSuggestions)) {
685             Log.e(TAG, "Failed to remove network suggestions for " + packageName
686                     + ". Network suggestions not found in active network suggestions");
687             return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_REMOVE_INVALID;
688         }
689         removeInternal(extNetworkSuggestions, packageName, perAppInfo);
690         saveToStore();
691         mWifiMetrics.incrementNetworkSuggestionApiNumModification();
692         mWifiMetrics.noteNetworkSuggestionApiListSizeHistogram(getAllMaxSizes());
693         return WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS;
694     }
695 
696     /**
697      * Remove all tracking of the app that has been uninstalled.
698      */
removeApp(@onNull String packageName)699     public void removeApp(@NonNull String packageName) {
700         PerAppInfo perAppInfo = mActiveNetworkSuggestionsPerApp.get(packageName);
701         if (perAppInfo == null) return;
702         // Disconnect from the current network, if the only suggestion for it was removed.
703         triggerDisconnectIfServingNetworkSuggestionRemoved(perAppInfo.extNetworkSuggestions);
704         removeInternal(Collections.EMPTY_LIST, packageName, perAppInfo);
705         // Remove the package fully from the internal database.
706         mActiveNetworkSuggestionsPerApp.remove(packageName);
707         saveToStore();
708         Log.i(TAG, "Removed " + packageName);
709     }
710 
711     /**
712      * Clear all internal state (for network settings reset).
713      */
clear()714     public void clear() {
715         Iterator<Map.Entry<String, PerAppInfo>> iter =
716                 mActiveNetworkSuggestionsPerApp.entrySet().iterator();
717         // Disconnect if we're connected to one of the suggestions.
718         triggerDisconnectIfServingNetworkSuggestionRemoved(
719                 mActiveNetworkSuggestionsMatchingConnection);
720         while (iter.hasNext()) {
721             Map.Entry<String, PerAppInfo> entry = iter.next();
722             removeInternal(Collections.EMPTY_LIST, entry.getKey(), entry.getValue());
723             iter.remove();
724         }
725         saveToStore();
726         Log.i(TAG, "Cleared all internal state");
727     }
728 
729     /**
730      * Check if network suggestions are enabled or disabled for the app.
731      */
hasUserApprovedForApp(String packageName)732     public boolean hasUserApprovedForApp(String packageName) {
733         PerAppInfo perAppInfo = mActiveNetworkSuggestionsPerApp.get(packageName);
734         if (perAppInfo == null) return false;
735 
736         return perAppInfo.hasUserApproved;
737     }
738 
739     /**
740      * Enable or Disable network suggestions for the app.
741      */
setHasUserApprovedForApp(boolean approved, String packageName)742     public void setHasUserApprovedForApp(boolean approved, String packageName) {
743         PerAppInfo perAppInfo = mActiveNetworkSuggestionsPerApp.get(packageName);
744         if (perAppInfo == null) return;
745 
746         if (mVerboseLoggingEnabled) {
747             Log.v(TAG, "Setting the app " + (approved ? "approved" : "not approved"));
748         }
749         perAppInfo.hasUserApproved = approved;
750         saveToStore();
751     }
752 
753     /**
754      * Returns a set of all network suggestions across all apps.
755      */
756     @VisibleForTesting
getAllNetworkSuggestions()757     public Set<WifiNetworkSuggestion> getAllNetworkSuggestions() {
758         return mActiveNetworkSuggestionsPerApp.values()
759                 .stream()
760                 .flatMap(e -> convertToWnsSet(e.extNetworkSuggestions)
761                         .stream())
762                 .collect(Collectors.toSet());
763     }
764 
getAllMaxSizes()765     private List<Integer> getAllMaxSizes() {
766         return mActiveNetworkSuggestionsPerApp.values()
767                 .stream()
768                 .map(e -> e.maxSize)
769                 .collect(Collectors.toList());
770     }
771 
getPrivateBroadcast(@onNull String action, @NonNull String packageName, int uid)772     private PendingIntent getPrivateBroadcast(@NonNull String action, @NonNull String packageName,
773                                               int uid) {
774         Intent intent = new Intent(action)
775                 .setPackage("android")
776                 .putExtra(EXTRA_PACKAGE_NAME, packageName)
777                 .putExtra(EXTRA_UID, uid);
778         return mFrameworkFacade.getBroadcast(mContext, 0, intent,
779                 PendingIntent.FLAG_UPDATE_CURRENT);
780     }
781 
getAppName(@onNull String packageName)782     private @NonNull CharSequence getAppName(@NonNull String packageName) {
783         ApplicationInfo applicationInfo = null;
784         try {
785             applicationInfo = mPackageManager.getApplicationInfo(packageName, 0);
786         } catch (PackageManager.NameNotFoundException e) {
787             Log.e(TAG, "Failed to find app name for " + packageName);
788             return "";
789         }
790         CharSequence appName = mPackageManager.getApplicationLabel(applicationInfo);
791         return (appName != null) ? appName : "";
792     }
793 
sendUserApprovalNotification(@onNull String packageName, int uid)794     private void sendUserApprovalNotification(@NonNull String packageName, int uid) {
795         Notification.Action userAllowAppNotificationAction =
796                 new Notification.Action.Builder(null,
797                         mResources.getText(R.string.wifi_suggestion_action_allow_app),
798                         getPrivateBroadcast(NOTIFICATION_USER_ALLOWED_APP_INTENT_ACTION,
799                                 packageName, uid))
800                         .build();
801         Notification.Action userDisallowAppNotificationAction =
802                 new Notification.Action.Builder(null,
803                         mResources.getText(R.string.wifi_suggestion_action_disallow_app),
804                         getPrivateBroadcast(NOTIFICATION_USER_DISALLOWED_APP_INTENT_ACTION,
805                                 packageName, uid))
806                         .build();
807 
808         CharSequence appName = getAppName(packageName);
809         Notification notification = new Notification.Builder(
810                 mContext, SystemNotificationChannels.NETWORK_STATUS)
811                 .setSmallIcon(R.drawable.stat_notify_wifi_in_range)
812                 .setTicker(mResources.getString(R.string.wifi_suggestion_title))
813                 .setContentTitle(mResources.getString(R.string.wifi_suggestion_title))
814                 .setContentText(mResources.getString(R.string.wifi_suggestion_content, appName))
815                 .setDeleteIntent(getPrivateBroadcast(NOTIFICATION_USER_DISMISSED_INTENT_ACTION,
816                         packageName, uid))
817                 .setShowWhen(false)
818                 .setLocalOnly(true)
819                 .setColor(mResources.getColor(R.color.system_notification_accent_color,
820                         mContext.getTheme()))
821                 .addAction(userAllowAppNotificationAction)
822                 .addAction(userDisallowAppNotificationAction)
823                 .build();
824 
825         // Post the notification.
826         mNotificationManager.notify(
827                 SystemMessage.NOTE_NETWORK_SUGGESTION_AVAILABLE, notification);
828         mUserApprovalNotificationActive = true;
829         mUserApprovalNotificationPackageName = packageName;
830     }
831 
sendUserApprovalNotificationIfNotApproved( @onNull PerAppInfo perAppInfo, @NonNull WifiNetworkSuggestion matchingSuggestion)832     private boolean sendUserApprovalNotificationIfNotApproved(
833             @NonNull PerAppInfo perAppInfo,
834             @NonNull WifiNetworkSuggestion matchingSuggestion) {
835         if (perAppInfo.hasUserApproved) {
836             return false; // already approved.
837         }
838 
839         Log.i(TAG, "Sending user approval notification for " + perAppInfo.packageName);
840         sendUserApprovalNotification(perAppInfo.packageName, matchingSuggestion.suggestorUid);
841         return true;
842     }
843 
844     private @Nullable Set<ExtendedWifiNetworkSuggestion>
getNetworkSuggestionsForScanResultMatchInfo( @onNull ScanResultMatchInfo scanResultMatchInfo, @Nullable MacAddress bssid)845             getNetworkSuggestionsForScanResultMatchInfo(
846             @NonNull ScanResultMatchInfo scanResultMatchInfo, @Nullable MacAddress bssid) {
847         Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions = new HashSet<>();
848         if (bssid != null) {
849             Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestionsWithBssid =
850                     mActiveScanResultMatchInfoWithBssid.get(
851                             Pair.create(scanResultMatchInfo, bssid));
852             if (matchingExtNetworkSuggestionsWithBssid != null) {
853                 extNetworkSuggestions.addAll(matchingExtNetworkSuggestionsWithBssid);
854             }
855         }
856         Set<ExtendedWifiNetworkSuggestion> matchingNetworkSuggestionsWithNoBssid =
857                 mActiveScanResultMatchInfoWithNoBssid.get(scanResultMatchInfo);
858         if (matchingNetworkSuggestionsWithNoBssid != null) {
859             extNetworkSuggestions.addAll(matchingNetworkSuggestionsWithNoBssid);
860         }
861         if (extNetworkSuggestions.isEmpty()) {
862             return null;
863         }
864         return extNetworkSuggestions;
865     }
866 
867     /**
868      * Returns a set of all network suggestions matching the provided scan detail.
869      */
getNetworkSuggestionsForScanDetail( @onNull ScanDetail scanDetail)870     public @Nullable Set<WifiNetworkSuggestion> getNetworkSuggestionsForScanDetail(
871             @NonNull ScanDetail scanDetail) {
872         ScanResult scanResult = scanDetail.getScanResult();
873         if (scanResult == null) {
874             Log.e(TAG, "No scan result found in scan detail");
875             return null;
876         }
877         Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions = null;
878         try {
879             ScanResultMatchInfo scanResultMatchInfo =
880                     ScanResultMatchInfo.fromScanResult(scanResult);
881             extNetworkSuggestions = getNetworkSuggestionsForScanResultMatchInfo(
882                     scanResultMatchInfo,  MacAddress.fromString(scanResult.BSSID));
883         } catch (IllegalArgumentException e) {
884             Log.e(TAG, "Failed to lookup network from scan result match info map", e);
885         }
886         if (extNetworkSuggestions == null) {
887             return null;
888         }
889         Set<ExtendedWifiNetworkSuggestion> approvedExtNetworkSuggestions =
890                 extNetworkSuggestions
891                         .stream()
892                         .filter(n -> n.perAppInfo.hasUserApproved)
893                         .collect(Collectors.toSet());
894         // If there is no active notification, check if we need to get approval for any of the apps
895         // & send a notification for one of them. If there are multiple packages awaiting approval,
896         // we end up picking the first one. The others will be reconsidered in the next iteration.
897         if (!mUserApprovalNotificationActive
898                 && approvedExtNetworkSuggestions.size() != extNetworkSuggestions.size()) {
899             for (ExtendedWifiNetworkSuggestion extNetworkSuggestion : extNetworkSuggestions) {
900                 if (sendUserApprovalNotificationIfNotApproved(
901                         extNetworkSuggestion.perAppInfo, extNetworkSuggestion.wns)) {
902                     break;
903                 }
904             }
905         }
906         if (approvedExtNetworkSuggestions.isEmpty()) {
907             return null;
908         }
909         if (mVerboseLoggingEnabled) {
910             Log.v(TAG, "getNetworkSuggestionsForScanDetail Found "
911                     + approvedExtNetworkSuggestions + " for " + scanResult.SSID
912                     + "[" + scanResult.capabilities + "]");
913         }
914         return convertToWnsSet(approvedExtNetworkSuggestions);
915     }
916 
917     /**
918      * Returns a set of all network suggestions matching the provided the WifiConfiguration.
919      */
getNetworkSuggestionsForWifiConfiguration( @onNull WifiConfiguration wifiConfiguration, @Nullable String bssid)920     private @Nullable Set<ExtendedWifiNetworkSuggestion> getNetworkSuggestionsForWifiConfiguration(
921             @NonNull WifiConfiguration wifiConfiguration, @Nullable String bssid) {
922         Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions = null;
923         try {
924             ScanResultMatchInfo scanResultMatchInfo =
925                     ScanResultMatchInfo.fromWifiConfiguration(wifiConfiguration);
926             extNetworkSuggestions = getNetworkSuggestionsForScanResultMatchInfo(
927                     scanResultMatchInfo,  bssid == null ? null : MacAddress.fromString(bssid));
928         } catch (IllegalArgumentException e) {
929             Log.e(TAG, "Failed to lookup network from scan result match info map", e);
930         }
931         if (extNetworkSuggestions == null) {
932             return null;
933         }
934         Set<ExtendedWifiNetworkSuggestion> approvedExtNetworkSuggestions =
935                 extNetworkSuggestions
936                         .stream()
937                         .filter(n -> n.perAppInfo.hasUserApproved)
938                         .collect(Collectors.toSet());
939         if (approvedExtNetworkSuggestions.isEmpty()) {
940             return null;
941         }
942         if (mVerboseLoggingEnabled) {
943             Log.v(TAG, "getNetworkSuggestionsFoWifiConfiguration Found "
944                     + approvedExtNetworkSuggestions + " for " + wifiConfiguration.SSID
945                     + "[" + wifiConfiguration.allowedKeyManagement + "]");
946         }
947         return approvedExtNetworkSuggestions;
948     }
949 
950     /**
951      * Helper method to send the post connection broadcast to specified package.
952      */
sendPostConnectionBroadcast( String packageName, WifiNetworkSuggestion networkSuggestion)953     private void sendPostConnectionBroadcast(
954             String packageName, WifiNetworkSuggestion networkSuggestion) {
955         Intent intent = new Intent(WifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION);
956         intent.putExtra(WifiManager.EXTRA_NETWORK_SUGGESTION, networkSuggestion);
957         // Intended to wakeup the receiving app so set the specific package name.
958         intent.setPackage(packageName);
959         mContext.sendBroadcastAsUser(
960                 intent, UserHandle.getUserHandleForUid(networkSuggestion.suggestorUid));
961     }
962 
963     /**
964      * Helper method to send the post connection broadcast to specified package.
965      */
sendPostConnectionBroadcastIfAllowed( String packageName, WifiNetworkSuggestion matchingSuggestion)966     private void sendPostConnectionBroadcastIfAllowed(
967             String packageName, WifiNetworkSuggestion matchingSuggestion) {
968         try {
969             mWifiPermissionsUtil.enforceCanAccessScanResults(
970                     packageName, matchingSuggestion.suggestorUid);
971         } catch (SecurityException se) {
972             return;
973         }
974         if (mVerboseLoggingEnabled) {
975             Log.v(TAG, "Sending post connection broadcast to " + packageName);
976         }
977         sendPostConnectionBroadcast(packageName, matchingSuggestion);
978     }
979 
980     /**
981      * Send out the {@link WifiManager#ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION} to all the
982      * network suggestion credentials that match the current connection network.
983      *
984      * @param connectedNetwork {@link WifiConfiguration} representing the network connected to.
985      * @param connectedBssid BSSID of the network connected to.
986      */
handleConnectionSuccess( @onNull WifiConfiguration connectedNetwork, @NonNull String connectedBssid)987     private void handleConnectionSuccess(
988             @NonNull WifiConfiguration connectedNetwork, @NonNull String connectedBssid) {
989         Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestions =
990                 getNetworkSuggestionsForWifiConfiguration(connectedNetwork, connectedBssid);
991         if (mVerboseLoggingEnabled) {
992             Log.v(TAG, "Network suggestions matching the connection "
993                     + matchingExtNetworkSuggestions);
994         }
995         if (matchingExtNetworkSuggestions == null
996                 || matchingExtNetworkSuggestions.isEmpty()) return;
997 
998         mWifiMetrics.incrementNetworkSuggestionApiNumConnectSuccess();
999 
1000         // Store the set of matching network suggestions.
1001         mActiveNetworkSuggestionsMatchingConnection = new HashSet<>(matchingExtNetworkSuggestions);
1002 
1003         // Find subset of network suggestions which have set |isAppInteractionRequired|.
1004         Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestionsWithReqAppInteraction =
1005                 matchingExtNetworkSuggestions.stream()
1006                         .filter(x -> x.wns.isAppInteractionRequired)
1007                         .collect(Collectors.toSet());
1008         if (matchingExtNetworkSuggestionsWithReqAppInteraction.size() == 0) return;
1009 
1010         // Iterate over the matching network suggestions list:
1011         // a) Ensure that these apps have the necessary location permissions.
1012         // b) Send directed broadcast to the app with their corresponding network suggestion.
1013         for (ExtendedWifiNetworkSuggestion matchingExtNetworkSuggestion
1014                 : matchingExtNetworkSuggestionsWithReqAppInteraction) {
1015             sendPostConnectionBroadcastIfAllowed(
1016                     matchingExtNetworkSuggestion.perAppInfo.packageName,
1017                     matchingExtNetworkSuggestion.wns);
1018         }
1019     }
1020 
1021     /**
1022      * Handle connection failure.
1023      *
1024      * @param network {@link WifiConfiguration} representing the network that connection failed to.
1025      * @param bssid BSSID of the network connection failed to if known, else null.
1026      */
handleConnectionFailure(@onNull WifiConfiguration network, @Nullable String bssid)1027     private void handleConnectionFailure(@NonNull WifiConfiguration network,
1028                                          @Nullable String bssid) {
1029         Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestions =
1030                 getNetworkSuggestionsForWifiConfiguration(network, bssid);
1031         if (mVerboseLoggingEnabled) {
1032             Log.v(TAG, "Network suggestions matching the connection failure "
1033                     + matchingExtNetworkSuggestions);
1034         }
1035         if (matchingExtNetworkSuggestions == null
1036                 || matchingExtNetworkSuggestions.isEmpty()) return;
1037 
1038         mWifiMetrics.incrementNetworkSuggestionApiNumConnectFailure();
1039         // TODO (b/115504887, b/112196799): Blacklist the corresponding network suggestion if
1040         // the connection failed.
1041     }
1042 
resetConnectionState()1043     private void resetConnectionState() {
1044         mActiveNetworkSuggestionsMatchingConnection = null;
1045     }
1046 
1047     /**
1048      * Invoked by {@link ClientModeImpl} on end of connection attempt to a network.
1049      *
1050      * @param failureCode Failure codes representing {@link WifiMetrics.ConnectionEvent} codes.
1051      * @param network WifiConfiguration corresponding to the current network.
1052      * @param bssid BSSID of the current network.
1053      */
handleConnectionAttemptEnded( int failureCode, @NonNull WifiConfiguration network, @Nullable String bssid)1054     public void handleConnectionAttemptEnded(
1055             int failureCode, @NonNull WifiConfiguration network, @Nullable String bssid) {
1056         if (mVerboseLoggingEnabled) {
1057             Log.v(TAG, "handleConnectionAttemptEnded " + failureCode + ", " + network);
1058         }
1059         resetConnectionState();
1060         if (failureCode == WifiMetrics.ConnectionEvent.FAILURE_NONE) {
1061             handleConnectionSuccess(network, bssid);
1062         } else {
1063             handleConnectionFailure(network, bssid);
1064         }
1065     }
1066 
1067     /**
1068      * Invoked by {@link ClientModeImpl} on disconnect from network.
1069      */
handleDisconnect(@onNull WifiConfiguration network, @NonNull String bssid)1070     public void handleDisconnect(@NonNull WifiConfiguration network, @NonNull String bssid) {
1071         if (mVerboseLoggingEnabled) {
1072             Log.v(TAG, "handleDisconnect " + network);
1073         }
1074         resetConnectionState();
1075     }
1076 
1077     /**
1078      * Dump of {@link WifiNetworkSuggestionsManager}.
1079      */
dump(FileDescriptor fd, PrintWriter pw, String[] args)1080     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1081         pw.println("Dump of WifiNetworkSuggestionsManager");
1082         pw.println("WifiNetworkSuggestionsManager - Networks Begin ----");
1083         for (Map.Entry<String, PerAppInfo> networkSuggestionsEntry
1084                 : mActiveNetworkSuggestionsPerApp.entrySet()) {
1085             pw.println("Package Name: " + networkSuggestionsEntry.getKey());
1086             PerAppInfo appInfo = networkSuggestionsEntry.getValue();
1087             pw.println("Has user approved: " + appInfo.hasUserApproved);
1088             for (ExtendedWifiNetworkSuggestion extNetworkSuggestion
1089                     : appInfo.extNetworkSuggestions) {
1090                 pw.println("Network: " + extNetworkSuggestion);
1091             }
1092         }
1093         pw.println("WifiNetworkSuggestionsManager - Networks End ----");
1094         pw.println("WifiNetworkSuggestionsManager - Network Suggestions matching connection: "
1095                 + mActiveNetworkSuggestionsMatchingConnection);
1096     }
1097 }
1098 
1099