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