• 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.IntDef;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.app.ActivityManager;
26 import android.app.AlertDialog;
27 import android.app.AppOpsManager;
28 import android.app.Notification;
29 import android.app.NotificationManager;
30 import android.app.PendingIntent;
31 import android.content.BroadcastReceiver;
32 import android.content.Context;
33 import android.content.Intent;
34 import android.content.IntentFilter;
35 import android.content.pm.ApplicationInfo;
36 import android.content.pm.PackageManager;
37 import android.content.res.Resources;
38 import android.graphics.drawable.Icon;
39 import android.net.MacAddress;
40 import android.net.NetworkScoreManager;
41 import android.net.wifi.ISuggestionConnectionStatusListener;
42 import android.net.wifi.ScanResult;
43 import android.net.wifi.WifiConfiguration;
44 import android.net.wifi.WifiManager;
45 import android.net.wifi.WifiNetworkSuggestion;
46 import android.net.wifi.WifiScanner;
47 import android.net.wifi.hotspot2.PasspointConfiguration;
48 import android.os.Handler;
49 import android.os.IBinder;
50 import android.os.Process;
51 import android.os.RemoteException;
52 import android.os.UserHandle;
53 import android.telephony.TelephonyManager;
54 import android.text.TextUtils;
55 import android.util.Log;
56 import android.util.Pair;
57 import android.view.WindowManager;
58 
59 import com.android.internal.annotations.VisibleForTesting;
60 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
61 import com.android.server.wifi.util.ExternalCallbackTracker;
62 import com.android.server.wifi.util.LruConnectionTracker;
63 import com.android.server.wifi.util.WifiPermissionsUtil;
64 import com.android.wifi.resources.R;
65 
66 import java.io.FileDescriptor;
67 import java.io.PrintWriter;
68 import java.lang.annotation.Retention;
69 import java.lang.annotation.RetentionPolicy;
70 import java.util.ArrayList;
71 import java.util.Collection;
72 import java.util.Collections;
73 import java.util.HashMap;
74 import java.util.HashSet;
75 import java.util.Iterator;
76 import java.util.List;
77 import java.util.Map;
78 import java.util.Objects;
79 import java.util.Set;
80 import java.util.stream.Collectors;
81 
82 import javax.annotation.concurrent.NotThreadSafe;
83 
84 /**
85  * Network Suggestions Manager.
86  * NOTE: This class should always be invoked from the main wifi service thread.
87  */
88 @NotThreadSafe
89 public class WifiNetworkSuggestionsManager {
90     private static final String TAG = "WifiNetworkSuggestionsManager";
91 
92     /** Intent when user tapped action button to allow the app. */
93     @VisibleForTesting
94     public static final String NOTIFICATION_USER_ALLOWED_APP_INTENT_ACTION =
95             "com.android.server.wifi.action.NetworkSuggestion.USER_ALLOWED_APP";
96     /** Intent when user tapped action button to disallow the app. */
97     @VisibleForTesting
98     public static final String NOTIFICATION_USER_DISALLOWED_APP_INTENT_ACTION =
99             "com.android.server.wifi.action.NetworkSuggestion.USER_DISALLOWED_APP";
100     /** Intent when user dismissed the notification. */
101     @VisibleForTesting
102     public static final String NOTIFICATION_USER_DISMISSED_INTENT_ACTION =
103             "com.android.server.wifi.action.NetworkSuggestion.USER_DISMISSED";
104     @VisibleForTesting
105     public static final String EXTRA_PACKAGE_NAME =
106             "com.android.server.wifi.extra.NetworkSuggestion.PACKAGE_NAME";
107     @VisibleForTesting
108     public static final String EXTRA_UID =
109             "com.android.server.wifi.extra.NetworkSuggestion.UID";
110 
111     public static final int APP_TYPE_CARRIER_PRIVILEGED = 1;
112     public static final int APP_TYPE_NETWORK_PROVISIONING = 2;
113     public static final int APP_TYPE_NON_PRIVILEGED = 3;
114 
115     public static final int ACTION_USER_ALLOWED_APP = 1;
116     public static final int ACTION_USER_DISALLOWED_APP = 2;
117     public static final int ACTION_USER_DISMISS = 3;
118 
119     @IntDef(prefix = { "ACTION_USER_" }, value = {
120             ACTION_USER_ALLOWED_APP,
121             ACTION_USER_DISALLOWED_APP,
122             ACTION_USER_DISMISS
123     })
124     @Retention(RetentionPolicy.SOURCE)
125     public @interface UserActionCode { }
126 
127     /**
128      * Limit number of hidden networks attach to scan
129      */
130     private static final int NUMBER_OF_HIDDEN_NETWORK_FOR_ONE_SCAN = 100;
131 
132     private final WifiContext mContext;
133     private final Resources mResources;
134     private final Handler mHandler;
135     private final AppOpsManager mAppOps;
136     private final ActivityManager mActivityManager;
137     private final NotificationManager mNotificationManager;
138     private final NetworkScoreManager mNetworkScoreManager;
139     private final PackageManager mPackageManager;
140     private final WifiPermissionsUtil mWifiPermissionsUtil;
141     private final WifiConfigManager mWifiConfigManager;
142     private final WifiMetrics mWifiMetrics;
143     private final WifiInjector mWifiInjector;
144     private final FrameworkFacade mFrameworkFacade;
145     private final WifiCarrierInfoManager mWifiCarrierInfoManager;
146     private final WifiKeyStore mWifiKeyStore;
147     // Keep order of network connection.
148     private final LruConnectionTracker mLruConnectionTracker;
149 
150     /**
151      * Per app meta data to store network suggestions, status, etc for each app providing network
152      * suggestions on the device.
153      */
154     public static class PerAppInfo {
155         /**
156          * UID of the app.
157          */
158         public int uid;
159         /**
160          * Package Name of the app.
161          */
162         public final String packageName;
163         /**
164          * First Feature in the package that registered the suggestion
165          */
166         public final String featureId;
167         /**
168          * Set of active network suggestions provided by the app.
169          */
170         public final Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions = new HashSet<>();
171         /**
172          * Whether we have shown the user a notification for this app.
173          */
174         public boolean hasUserApproved = false;
175         /**
176          * Carrier Id of SIM which give app carrier privileges.
177          */
178         public int carrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
179 
180         /** Stores the max size of the {@link #extNetworkSuggestions} list ever for this app */
181         public int maxSize = 0;
182 
PerAppInfo(int uid, @NonNull String packageName, @Nullable String featureId)183         public PerAppInfo(int uid, @NonNull String packageName, @Nullable String featureId) {
184             this.uid = uid;
185             this.packageName = packageName;
186             this.featureId = featureId;
187         }
188 
189         /**
190          * Needed for migration of config store data.
191          */
setUid(int uid)192         public void setUid(int uid) {
193             if (this.uid == Process.INVALID_UID) {
194                 this.uid = uid;
195             }
196             // else ignored.
197         }
198 
199         /**
200          * Returns true if this app has the necessary approvals to place network suggestions.
201          */
isApproved(@ullable String activeScorerPkg)202         private boolean isApproved(@Nullable String activeScorerPkg) {
203             return hasUserApproved || isExemptFromUserApproval(activeScorerPkg);
204         }
205 
206         /**
207          * Returns true if this app can suggest networks without user approval.
208          */
isExemptFromUserApproval(@ullable String activeScorerPkg)209         private boolean isExemptFromUserApproval(@Nullable String activeScorerPkg) {
210             final boolean isCarrierPrivileged = carrierId != TelephonyManager.UNKNOWN_CARRIER_ID;
211             if (isCarrierPrivileged) {
212                 return true;
213             }
214             return packageName.equals(activeScorerPkg);
215         }
216 
217         // This is only needed for comparison in unit tests.
218         @Override
equals(Object other)219         public boolean equals(Object other) {
220             if (other == null) return false;
221             if (!(other instanceof PerAppInfo)) return false;
222             PerAppInfo otherPerAppInfo = (PerAppInfo) other;
223             return uid == otherPerAppInfo.uid
224                     && TextUtils.equals(packageName, otherPerAppInfo.packageName)
225                     && Objects.equals(extNetworkSuggestions, otherPerAppInfo.extNetworkSuggestions)
226                     && hasUserApproved == otherPerAppInfo.hasUserApproved;
227         }
228 
229         // This is only needed for comparison in unit tests.
230         @Override
hashCode()231         public int hashCode() {
232             return Objects.hash(uid, packageName, extNetworkSuggestions, hasUserApproved);
233         }
234 
235         @Override
toString()236         public String toString() {
237             return new StringBuilder("PerAppInfo[ ")
238                     .append("uid=").append(uid)
239                     .append(", packageName=").append(packageName)
240                     .append(", hasUserApproved=").append(hasUserApproved)
241                     .append(", suggestions=").append(extNetworkSuggestions)
242                     .append(" ]")
243                     .toString();
244         }
245     }
246 
247     /**
248      * Internal container class which holds a network suggestion and a pointer to the
249      * {@link PerAppInfo} entry from {@link #mActiveNetworkSuggestionsPerApp} corresponding to the
250      * app that made the suggestion.
251      */
252     public static class ExtendedWifiNetworkSuggestion {
253         public final WifiNetworkSuggestion wns;
254         // Store the pointer to the corresponding app's meta data.
255         public final PerAppInfo perAppInfo;
256         public boolean isAutojoinEnabled;
257 
ExtendedWifiNetworkSuggestion(@onNull WifiNetworkSuggestion wns, @NonNull PerAppInfo perAppInfo, boolean isAutoJoinEnabled)258         public ExtendedWifiNetworkSuggestion(@NonNull WifiNetworkSuggestion wns,
259                                              @NonNull PerAppInfo perAppInfo,
260                                              boolean isAutoJoinEnabled) {
261             this.wns = wns;
262             this.perAppInfo = perAppInfo;
263             this.isAutojoinEnabled = isAutoJoinEnabled;
264             this.wns.wifiConfiguration.fromWifiNetworkSuggestion = true;
265             this.wns.wifiConfiguration.ephemeral = true;
266             this.wns.wifiConfiguration.creatorName = perAppInfo.packageName;
267             this.wns.wifiConfiguration.creatorUid = perAppInfo.uid;
268         }
269 
270         @Override
hashCode()271         public int hashCode() {
272             return Objects.hash(wns, perAppInfo.uid, perAppInfo.packageName);
273         }
274 
275         @Override
equals(Object obj)276         public boolean equals(Object obj) {
277             if (this == obj) {
278                 return true;
279             }
280             if (!(obj instanceof ExtendedWifiNetworkSuggestion)) {
281                 return false;
282             }
283             ExtendedWifiNetworkSuggestion other = (ExtendedWifiNetworkSuggestion) obj;
284             return wns.equals(other.wns)
285                     && perAppInfo.uid == other.perAppInfo.uid
286                     && TextUtils.equals(perAppInfo.packageName, other.perAppInfo.packageName);
287         }
288 
289         /**
290          * Helper method to set the carrier Id.
291          */
setCarrierId(int carrierId)292         public void setCarrierId(int carrierId) {
293             if (wns.passpointConfiguration == null) {
294                 wns.wifiConfiguration.carrierId = carrierId;
295             } else {
296                 wns.passpointConfiguration.setCarrierId(carrierId);
297             }
298         }
299 
300         @Override
toString()301         public String toString() {
302             return new StringBuilder(wns.toString())
303                     .append(", isAutoJoinEnabled=").append(isAutojoinEnabled)
304                     .toString();
305         }
306 
307         /**
308          * Convert from {@link WifiNetworkSuggestion} to a new instance of
309          * {@link ExtendedWifiNetworkSuggestion}.
310          */
fromWns(@onNull WifiNetworkSuggestion wns, @NonNull PerAppInfo perAppInfo, boolean isAutoJoinEnabled)311         public static ExtendedWifiNetworkSuggestion fromWns(@NonNull WifiNetworkSuggestion wns,
312                 @NonNull PerAppInfo perAppInfo, boolean isAutoJoinEnabled) {
313             return new ExtendedWifiNetworkSuggestion(wns, perAppInfo, isAutoJoinEnabled);
314         }
315 
316         /**
317          * Create a {@link WifiConfiguration} from suggestion for framework internal use.
318          */
createInternalWifiConfiguration()319         public WifiConfiguration createInternalWifiConfiguration() {
320             WifiConfiguration config = new WifiConfiguration(wns.getWifiConfiguration());
321             config.allowAutojoin = isAutojoinEnabled;
322             return config;
323         }
324     }
325 
326     /**
327      * Map of package name of an app to the set of active network suggestions provided by the app.
328      */
329     private final Map<String, PerAppInfo> mActiveNetworkSuggestionsPerApp = new HashMap<>();
330     /**
331      * Map of package name of an app to the app ops changed listener for the app.
332      */
333     private final Map<String, AppOpsChangedListener> mAppOpsChangedListenerPerApp = new HashMap<>();
334     /**
335      * Map maintained to help lookup all the network suggestions (with no bssid) that match a
336      * provided scan result.
337      * Note:
338      * <li>There could be multiple suggestions (provided by different apps) that match a single
339      * scan result.</li>
340      * <li>Adding/Removing to this set for scan result lookup is expensive. But, we expect scan
341      * result lookup to happen much more often than apps modifying network suggestions.</li>
342      */
343     private final Map<ScanResultMatchInfo, Set<ExtendedWifiNetworkSuggestion>>
344             mActiveScanResultMatchInfoWithNoBssid = new HashMap<>();
345     /**
346      * Map maintained to help lookup all the network suggestions (with bssid) that match a provided
347      * scan result.
348      * Note:
349      * <li>There could be multiple suggestions (provided by different apps) that match a single
350      * scan result.</li>
351      * <li>Adding/Removing to this set for scan result lookup is expensive. But, we expect scan
352      * result lookup to happen much more often than apps modifying network suggestions.</li>
353      */
354     private final Map<Pair<ScanResultMatchInfo, MacAddress>, Set<ExtendedWifiNetworkSuggestion>>
355             mActiveScanResultMatchInfoWithBssid = new HashMap<>();
356     /**
357      * List of {@link WifiNetworkSuggestion} matching the current connected network.
358      */
359     private Set<ExtendedWifiNetworkSuggestion> mActiveNetworkSuggestionsMatchingConnection;
360 
361     private final Map<String, Set<ExtendedWifiNetworkSuggestion>>
362             mPasspointInfo = new HashMap<>();
363 
364     private final HashMap<String, ExternalCallbackTracker<ISuggestionConnectionStatusListener>>
365             mSuggestionStatusListenerPerApp = new HashMap<>();
366 
367     /**
368      * Store the suggestion update listeners.
369      */
370     private final List<OnSuggestionUpdateListener> mListeners = new ArrayList<>();
371 
372     /**
373      * Intent filter for processing notification actions.
374      */
375     private final IntentFilter mIntentFilter;
376 
377     /**
378      * Verbose logging flag.
379      */
380     private boolean mVerboseLoggingEnabled = false;
381     /**
382      * Indicates that we have new data to serialize.
383      */
384     private boolean mHasNewDataToSerialize = false;
385     /**
386      * Indicates if the user approval notification is active.
387      */
388     private boolean mUserApprovalUiActive = false;
389 
390     private boolean mIsLastUserApprovalUiDialog = false;
391 
392     private boolean mUserDataLoaded = false;
393 
394     /**
395      * Listener for app-ops changes for active suggestor apps.
396      */
397     private final class AppOpsChangedListener implements AppOpsManager.OnOpChangedListener {
398         private final String mPackageName;
399         private final int mUid;
400 
AppOpsChangedListener(@onNull String packageName, int uid)401         AppOpsChangedListener(@NonNull String packageName, int uid) {
402             mPackageName = packageName;
403             mUid = uid;
404         }
405 
406         @Override
onOpChanged(String op, String packageName)407         public void onOpChanged(String op, String packageName) {
408             mHandler.post(() -> {
409                 if (!mPackageName.equals(packageName)) return;
410                 if (!OPSTR_CHANGE_WIFI_STATE.equals(op)) return;
411 
412                 // Ensure the uid to package mapping is still correct.
413                 try {
414                     mAppOps.checkPackage(mUid, mPackageName);
415                 } catch (SecurityException e) {
416                     Log.wtf(TAG, "Invalid uid/package" + packageName);
417                     return;
418                 }
419 
420                 if (mAppOps.unsafeCheckOpNoThrow(OPSTR_CHANGE_WIFI_STATE, mUid, mPackageName)
421                         == AppOpsManager.MODE_IGNORED) {
422                     Log.i(TAG, "User disallowed change wifi state for " + packageName);
423                     // User disabled the app, remove app from database. We want the notification
424                     // again if the user enabled the app-op back.
425                     removeApp(mPackageName);
426                     mWifiMetrics.incrementNetworkSuggestionUserRevokePermission();
427                 }
428             });
429         }
430     };
431 
432     /**
433      * Module to interact with the wifi config store.
434      */
435     private class NetworkSuggestionDataSource implements NetworkSuggestionStoreData.DataSource {
436         @Override
toSerialize()437         public Map<String, PerAppInfo> toSerialize() {
438             for (Map.Entry<String, PerAppInfo> entry : mActiveNetworkSuggestionsPerApp.entrySet()) {
439                 Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions =
440                         entry.getValue().extNetworkSuggestions;
441                 for (ExtendedWifiNetworkSuggestion ewns : extNetworkSuggestions) {
442                     if (ewns.wns.passpointConfiguration != null) {
443                         continue;
444                     }
445                     ewns.wns.wifiConfiguration.isMostRecentlyConnected = mLruConnectionTracker
446                             .isMostRecentlyConnected(ewns.createInternalWifiConfiguration());
447                 }
448             }
449             // Clear the flag after writing to disk.
450             // TODO(b/115504887): Don't reset the flag on write failure.
451             mHasNewDataToSerialize = false;
452             return mActiveNetworkSuggestionsPerApp;
453         }
454 
455         @Override
fromDeserialized(Map<String, PerAppInfo> networkSuggestionsMap)456         public void fromDeserialized(Map<String, PerAppInfo> networkSuggestionsMap) {
457             mActiveNetworkSuggestionsPerApp.clear();
458             mActiveNetworkSuggestionsPerApp.putAll(networkSuggestionsMap);
459             // Build the scan cache.
460             for (Map.Entry<String, PerAppInfo> entry : networkSuggestionsMap.entrySet()) {
461                 String packageName = entry.getKey();
462                 Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions =
463                         entry.getValue().extNetworkSuggestions;
464                 if (!extNetworkSuggestions.isEmpty()) {
465                     // Start tracking app-op changes from the app if they have active suggestions.
466                     startTrackingAppOpsChange(packageName,
467                             extNetworkSuggestions.iterator().next().perAppInfo.uid);
468                 }
469                 for (ExtendedWifiNetworkSuggestion ewns : extNetworkSuggestions) {
470                     if (ewns.wns.passpointConfiguration != null) {
471                         addToPasspointInfoMap(ewns);
472                     } else {
473                         if (ewns.wns.wifiConfiguration.isMostRecentlyConnected) {
474                             mLruConnectionTracker
475                                     .addNetwork(ewns.createInternalWifiConfiguration());
476                         }
477                         addToScanResultMatchInfoMap(ewns);
478                     }
479                 }
480             }
481             mUserDataLoaded = true;
482         }
483 
484         @Override
reset()485         public void reset() {
486             mUserDataLoaded = false;
487             mActiveNetworkSuggestionsPerApp.clear();
488             mActiveScanResultMatchInfoWithBssid.clear();
489             mActiveScanResultMatchInfoWithNoBssid.clear();
490             mPasspointInfo.clear();
491         }
492 
493         @Override
hasNewDataToSerialize()494         public boolean hasNewDataToSerialize() {
495             return mHasNewDataToSerialize;
496         }
497     }
498 
handleUserAllowAction(int uid, String packageName)499     private void handleUserAllowAction(int uid, String packageName) {
500         Log.i(TAG, "User clicked to allow app");
501         // Set the user approved flag.
502         setHasUserApprovedForApp(true, packageName);
503         mUserApprovalUiActive = false;
504         mWifiMetrics.addUserApprovalSuggestionAppUiReaction(
505                 ACTION_USER_ALLOWED_APP,
506                 mIsLastUserApprovalUiDialog);
507     }
508 
handleUserDisallowAction(int uid, String packageName)509     private void handleUserDisallowAction(int uid, String packageName) {
510         Log.i(TAG, "User clicked to disallow app");
511         // Set the user approved flag.
512         setHasUserApprovedForApp(false, packageName);
513         // Take away CHANGE_WIFI_STATE app-ops from the app.
514         mAppOps.setMode(AppOpsManager.OPSTR_CHANGE_WIFI_STATE, uid, packageName,
515                 MODE_IGNORED);
516         mUserApprovalUiActive = false;
517         mWifiMetrics.addUserApprovalSuggestionAppUiReaction(
518                 ACTION_USER_DISALLOWED_APP,
519                 mIsLastUserApprovalUiDialog);
520     }
521 
handleUserDismissAction()522     private void handleUserDismissAction() {
523         Log.i(TAG, "User dismissed the notification");
524         mUserApprovalUiActive = false;
525         mWifiMetrics.addUserApprovalSuggestionAppUiReaction(
526                 ACTION_USER_DISMISS,
527                 mIsLastUserApprovalUiDialog);
528     }
529 
530     private final BroadcastReceiver mBroadcastReceiver =
531             new BroadcastReceiver() {
532                 @Override
533                 public void onReceive(Context context, Intent intent) {
534                     String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
535                     int uid = intent.getIntExtra(EXTRA_UID, -1);
536                     if (packageName == null || uid == -1) {
537                         Log.e(TAG, "No package name or uid found in intent");
538                         return;
539                     }
540                     switch (intent.getAction()) {
541                         case NOTIFICATION_USER_ALLOWED_APP_INTENT_ACTION:
542                             handleUserAllowAction(uid, packageName);
543                             break;
544                         case NOTIFICATION_USER_DISALLOWED_APP_INTENT_ACTION:
545                             handleUserDisallowAction(uid, packageName);
546                             break;
547                         case NOTIFICATION_USER_DISMISSED_INTENT_ACTION:
548                             handleUserDismissAction();
549                             return; // no need to cancel a dismissed notification, return.
550                         default:
551                             Log.e(TAG, "Unknown action " + intent.getAction());
552                             return;
553                     }
554                     // Clear notification once the user interacts with it.
555                     mNotificationManager.cancel(SystemMessage.NOTE_NETWORK_SUGGESTION_AVAILABLE);
556                 }
557             };
558 
559     /**
560      * Interface for other modules to listen to the suggestion updated events.
561      */
562     public interface OnSuggestionUpdateListener {
563         /**
564          * Invoked on suggestion being added or updated.
565          */
onSuggestionsAddedOrUpdated(@onNull List<WifiNetworkSuggestion> addedSuggestions)566         void onSuggestionsAddedOrUpdated(@NonNull List<WifiNetworkSuggestion> addedSuggestions);
567         /**
568          * Invoked on suggestion being removed.
569          */
onSuggestionsRemoved(@onNull List<WifiNetworkSuggestion> removedSuggestions)570         void onSuggestionsRemoved(@NonNull List<WifiNetworkSuggestion> removedSuggestions);
571     }
572 
573     private final class UserApproveCarrierListener implements
574             WifiCarrierInfoManager.OnUserApproveCarrierListener {
575 
576         @Override
onUserAllowed(int carrierId)577         public void onUserAllowed(int carrierId) {
578             restoreInitialAutojoinForCarrierId(carrierId);
579         }
580     }
581 
WifiNetworkSuggestionsManager(WifiContext context, Handler handler, WifiInjector wifiInjector, WifiPermissionsUtil wifiPermissionsUtil, WifiConfigManager wifiConfigManager, WifiConfigStore wifiConfigStore, WifiMetrics wifiMetrics, WifiCarrierInfoManager wifiCarrierInfoManager, WifiKeyStore keyStore, LruConnectionTracker lruConnectionTracker)582     public WifiNetworkSuggestionsManager(WifiContext context, Handler handler,
583             WifiInjector wifiInjector, WifiPermissionsUtil wifiPermissionsUtil,
584             WifiConfigManager wifiConfigManager, WifiConfigStore wifiConfigStore,
585             WifiMetrics wifiMetrics, WifiCarrierInfoManager wifiCarrierInfoManager,
586             WifiKeyStore keyStore, LruConnectionTracker lruConnectionTracker) {
587         mContext = context;
588         mResources = context.getResources();
589         mHandler = handler;
590         mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
591         mActivityManager = context.getSystemService(ActivityManager.class);
592         mNotificationManager =
593                 (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
594         mNetworkScoreManager = context.getSystemService(NetworkScoreManager.class);
595         mPackageManager = context.getPackageManager();
596         mWifiInjector = wifiInjector;
597         mFrameworkFacade = mWifiInjector.getFrameworkFacade();
598         mWifiPermissionsUtil = wifiPermissionsUtil;
599         mWifiConfigManager = wifiConfigManager;
600         mWifiMetrics = wifiMetrics;
601         mWifiCarrierInfoManager = wifiCarrierInfoManager;
602         mWifiKeyStore = keyStore;
603 
604         // register the data store for serializing/deserializing data.
605         wifiConfigStore.registerStoreData(
606                 wifiInjector.makeNetworkSuggestionStoreData(new NetworkSuggestionDataSource()));
607 
608         mWifiCarrierInfoManager.addImsiExemptionUserApprovalListener(
609                 new UserApproveCarrierListener());
610 
611         // Register broadcast receiver for UI interactions.
612         mIntentFilter = new IntentFilter();
613         mIntentFilter.addAction(NOTIFICATION_USER_ALLOWED_APP_INTENT_ACTION);
614         mIntentFilter.addAction(NOTIFICATION_USER_DISALLOWED_APP_INTENT_ACTION);
615         mIntentFilter.addAction(NOTIFICATION_USER_DISMISSED_INTENT_ACTION);
616 
617         mContext.registerReceiver(mBroadcastReceiver, mIntentFilter, null, handler);
618         mLruConnectionTracker = lruConnectionTracker;
619     }
620 
621     /**
622      * Enable verbose logging.
623      */
enableVerboseLogging(int verbose)624     public void enableVerboseLogging(int verbose) {
625         mVerboseLoggingEnabled = verbose > 0;
626     }
627 
saveToStore()628     private void saveToStore() {
629         // Set the flag to let WifiConfigStore that we have new data to write.
630         mHasNewDataToSerialize = true;
631         if (!mWifiConfigManager.saveToStore(true)) {
632             Log.w(TAG, "Failed to save to store");
633         }
634     }
635 
addToScanResultMatchInfoMap( @onNull ExtendedWifiNetworkSuggestion extNetworkSuggestion)636     private void addToScanResultMatchInfoMap(
637             @NonNull ExtendedWifiNetworkSuggestion extNetworkSuggestion) {
638         ScanResultMatchInfo scanResultMatchInfo =
639                 ScanResultMatchInfo.fromWifiConfiguration(
640                         extNetworkSuggestion.wns.wifiConfiguration);
641         Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestionsForScanResultMatchInfo;
642         if (!TextUtils.isEmpty(extNetworkSuggestion.wns.wifiConfiguration.BSSID)) {
643             Pair<ScanResultMatchInfo, MacAddress> lookupPair =
644                     Pair.create(scanResultMatchInfo,
645                             MacAddress.fromString(
646                                     extNetworkSuggestion.wns.wifiConfiguration.BSSID));
647             extNetworkSuggestionsForScanResultMatchInfo =
648                     mActiveScanResultMatchInfoWithBssid.get(lookupPair);
649             if (extNetworkSuggestionsForScanResultMatchInfo == null) {
650                 extNetworkSuggestionsForScanResultMatchInfo = new HashSet<>();
651                 mActiveScanResultMatchInfoWithBssid.put(
652                         lookupPair, extNetworkSuggestionsForScanResultMatchInfo);
653             }
654         } else {
655             extNetworkSuggestionsForScanResultMatchInfo =
656                     mActiveScanResultMatchInfoWithNoBssid.get(scanResultMatchInfo);
657             if (extNetworkSuggestionsForScanResultMatchInfo == null) {
658                 extNetworkSuggestionsForScanResultMatchInfo = new HashSet<>();
659                 mActiveScanResultMatchInfoWithNoBssid.put(
660                         scanResultMatchInfo, extNetworkSuggestionsForScanResultMatchInfo);
661             }
662         }
663         extNetworkSuggestionsForScanResultMatchInfo.remove(extNetworkSuggestion);
664         extNetworkSuggestionsForScanResultMatchInfo.add(extNetworkSuggestion);
665     }
666 
removeFromScanResultMatchInfoMapAndRemoveRelatedScoreCard( @onNull ExtendedWifiNetworkSuggestion extNetworkSuggestion)667     private void removeFromScanResultMatchInfoMapAndRemoveRelatedScoreCard(
668             @NonNull ExtendedWifiNetworkSuggestion extNetworkSuggestion) {
669         ScanResultMatchInfo scanResultMatchInfo =
670                 ScanResultMatchInfo.fromWifiConfiguration(
671                         extNetworkSuggestion.wns.wifiConfiguration);
672         Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestionsForScanResultMatchInfo;
673         if (!TextUtils.isEmpty(extNetworkSuggestion.wns.wifiConfiguration.BSSID)) {
674             Pair<ScanResultMatchInfo, MacAddress> lookupPair =
675                     Pair.create(scanResultMatchInfo,
676                             MacAddress.fromString(
677                                     extNetworkSuggestion.wns.wifiConfiguration.BSSID));
678             extNetworkSuggestionsForScanResultMatchInfo =
679                     mActiveScanResultMatchInfoWithBssid.get(lookupPair);
680             // This should never happen because we should have done necessary error checks in
681             // the parent method.
682             if (extNetworkSuggestionsForScanResultMatchInfo == null) {
683                 Log.wtf(TAG, "No scan result match info found.");
684                 return;
685             }
686             extNetworkSuggestionsForScanResultMatchInfo.remove(extNetworkSuggestion);
687             // Remove the set from map if empty.
688             if (extNetworkSuggestionsForScanResultMatchInfo.isEmpty()) {
689                 mActiveScanResultMatchInfoWithBssid.remove(lookupPair);
690                 if (!mActiveScanResultMatchInfoWithNoBssid.containsKey(scanResultMatchInfo)) {
691                     removeNetworkFromScoreCard(extNetworkSuggestion.wns.wifiConfiguration);
692                     mLruConnectionTracker.removeNetwork(
693                             extNetworkSuggestion.wns.wifiConfiguration);
694                 }
695             }
696         } else {
697             extNetworkSuggestionsForScanResultMatchInfo =
698                     mActiveScanResultMatchInfoWithNoBssid.get(scanResultMatchInfo);
699             // This should never happen because we should have done necessary error checks in
700             // the parent method.
701             if (extNetworkSuggestionsForScanResultMatchInfo == null) {
702                 Log.wtf(TAG, "No scan result match info found.");
703                 return;
704             }
705             extNetworkSuggestionsForScanResultMatchInfo.remove(extNetworkSuggestion);
706             // Remove the set from map if empty.
707             if (extNetworkSuggestionsForScanResultMatchInfo.isEmpty()) {
708                 mActiveScanResultMatchInfoWithNoBssid.remove(scanResultMatchInfo);
709                 removeNetworkFromScoreCard(extNetworkSuggestion.wns.wifiConfiguration);
710                 mLruConnectionTracker.removeNetwork(
711                         extNetworkSuggestion.wns.wifiConfiguration);
712             }
713         }
714     }
715 
removeNetworkFromScoreCard(WifiConfiguration wifiConfiguration)716     private void removeNetworkFromScoreCard(WifiConfiguration wifiConfiguration) {
717         WifiConfiguration existing =
718                 mWifiConfigManager.getConfiguredNetwork(wifiConfiguration.getKey());
719         // If there is a saved network, do not remove from the score card.
720         if (existing != null && !existing.fromWifiNetworkSuggestion) {
721             return;
722         }
723         mWifiInjector.getWifiScoreCard().removeNetwork(wifiConfiguration.SSID);
724     }
725 
addToPasspointInfoMap(ExtendedWifiNetworkSuggestion ewns)726     private void addToPasspointInfoMap(ExtendedWifiNetworkSuggestion ewns) {
727         Set<ExtendedWifiNetworkSuggestion> extendedWifiNetworkSuggestions =
728                 mPasspointInfo.get(ewns.wns.wifiConfiguration.FQDN);
729         if (extendedWifiNetworkSuggestions == null) {
730             extendedWifiNetworkSuggestions = new HashSet<>();
731         }
732         extendedWifiNetworkSuggestions.add(ewns);
733         mPasspointInfo.put(ewns.wns.wifiConfiguration.FQDN, extendedWifiNetworkSuggestions);
734     }
735 
removeFromPassPointInfoMap(ExtendedWifiNetworkSuggestion ewns)736     private void removeFromPassPointInfoMap(ExtendedWifiNetworkSuggestion ewns) {
737         Set<ExtendedWifiNetworkSuggestion> extendedWifiNetworkSuggestions =
738                 mPasspointInfo.get(ewns.wns.wifiConfiguration.FQDN);
739         if (extendedWifiNetworkSuggestions == null
740                 || !extendedWifiNetworkSuggestions.contains(ewns)) {
741             Log.wtf(TAG, "No Passpoint info found.");
742             return;
743         }
744         extendedWifiNetworkSuggestions.remove(ewns);
745         if (extendedWifiNetworkSuggestions.isEmpty()) {
746             mPasspointInfo.remove(ewns.wns.wifiConfiguration.FQDN);
747         }
748     }
749 
750 
751     // Issues a disconnect if the only serving network suggestion is removed.
removeFromConfigManagerIfServingNetworkSuggestionRemoved( Collection<ExtendedWifiNetworkSuggestion> extNetworkSuggestionsRemoved)752     private void removeFromConfigManagerIfServingNetworkSuggestionRemoved(
753             Collection<ExtendedWifiNetworkSuggestion> extNetworkSuggestionsRemoved) {
754         if (mActiveNetworkSuggestionsMatchingConnection == null
755                 || mActiveNetworkSuggestionsMatchingConnection.isEmpty()) {
756             return;
757         }
758         WifiConfiguration activeWifiConfiguration =
759                 mActiveNetworkSuggestionsMatchingConnection.iterator().next().wns.wifiConfiguration;
760         if (mActiveNetworkSuggestionsMatchingConnection.removeAll(extNetworkSuggestionsRemoved)) {
761             if (mActiveNetworkSuggestionsMatchingConnection.isEmpty()) {
762                 Log.i(TAG, "Only network suggestion matching the connected network removed. "
763                         + "Removing from config manager...");
764                 // will trigger a disconnect.
765                 mWifiConfigManager.removeSuggestionConfiguredNetwork(
766                         activeWifiConfiguration.getKey());
767             }
768         }
769     }
770 
startTrackingAppOpsChange(@onNull String packageName, int uid)771     private void startTrackingAppOpsChange(@NonNull String packageName, int uid) {
772         AppOpsChangedListener appOpsChangedListener =
773                 new AppOpsChangedListener(packageName, uid);
774         mAppOps.startWatchingMode(OPSTR_CHANGE_WIFI_STATE, packageName, appOpsChangedListener);
775         mAppOpsChangedListenerPerApp.put(packageName, appOpsChangedListener);
776     }
777 
778     /**
779      * Helper method to convert the incoming collection of public {@link WifiNetworkSuggestion}
780      * objects to a set of corresponding internal wrapper
781      * {@link ExtendedWifiNetworkSuggestion} objects.
782      */
convertToExtendedWnsSet( final Collection<WifiNetworkSuggestion> networkSuggestions, final PerAppInfo perAppInfo)783     private Set<ExtendedWifiNetworkSuggestion> convertToExtendedWnsSet(
784             final Collection<WifiNetworkSuggestion> networkSuggestions,
785             final PerAppInfo perAppInfo) {
786         return networkSuggestions
787                 .stream()
788                 .collect(Collectors.mapping(
789                         n -> ExtendedWifiNetworkSuggestion.fromWns(n, perAppInfo,
790                                 n.isInitialAutoJoinEnabled),
791                         Collectors.toSet()));
792     }
793 
794     /**
795      * Helper method to convert the incoming collection of internal wrapper
796      * {@link ExtendedWifiNetworkSuggestion} objects to a set of corresponding public
797      * {@link WifiNetworkSuggestion} objects.
798      */
convertToWnsSet( final Collection<ExtendedWifiNetworkSuggestion> extNetworkSuggestions)799     private Set<WifiNetworkSuggestion> convertToWnsSet(
800             final Collection<ExtendedWifiNetworkSuggestion> extNetworkSuggestions) {
801         return extNetworkSuggestions
802                 .stream()
803                 .collect(Collectors.mapping(
804                         n -> n.wns,
805                         Collectors.toSet()));
806     }
807 
updateWifiConfigInWcmIfPresent( WifiConfiguration newConfig, int uid, String packageName)808     private void updateWifiConfigInWcmIfPresent(
809             WifiConfiguration newConfig, int uid, String packageName) {
810         WifiConfiguration configInWcm =
811                 mWifiConfigManager.getConfiguredNetwork(newConfig.getKey());
812         if (configInWcm == null) return;
813         // !suggestion
814         if (!configInWcm.fromWifiNetworkSuggestion) return;
815         // is suggestion from same app.
816         if (configInWcm.creatorUid != uid
817                 || !TextUtils.equals(configInWcm.creatorName, packageName)) {
818             return;
819         }
820         NetworkUpdateResult result = mWifiConfigManager.addOrUpdateNetwork(
821                 newConfig, uid, packageName);
822         if (!result.isSuccess()) {
823             Log.e(TAG, "Failed to update config in WifiConfigManager");
824         } else {
825             if (mVerboseLoggingEnabled) {
826                 Log.v(TAG, "Updated config in WifiConfigManager");
827             }
828         }
829     }
830 
831     /**
832      * Add the provided list of network suggestions from the corresponding app's active list.
833      */
add( List<WifiNetworkSuggestion> networkSuggestions, int uid, String packageName, @Nullable String featureId)834     public @WifiManager.NetworkSuggestionsStatusCode int add(
835             List<WifiNetworkSuggestion> networkSuggestions, int uid, String packageName,
836             @Nullable String featureId) {
837         if (!mWifiPermissionsUtil.doesUidBelongToCurrentUser(uid)) {
838             Log.e(TAG, "UID " + uid + " not visible to the current user");
839             return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_INTERNAL;
840         }
841         if (!mUserDataLoaded) {
842             Log.e(TAG, "Add Network suggestion before boot complete is not allowed.");
843             return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_INTERNAL;
844         }
845         if (networkSuggestions == null || networkSuggestions.isEmpty()) {
846             Log.w(TAG, "Empty list of network suggestions for " + packageName + ". Ignoring");
847             return WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS;
848         }
849         if (mVerboseLoggingEnabled) {
850             Log.v(TAG, "Adding " + networkSuggestions.size() + " networks from " + packageName);
851         }
852         if (!validateNetworkSuggestions(networkSuggestions)) {
853             Log.e(TAG, "Invalid suggestion add from app: " + packageName);
854             return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_INVALID;
855         }
856         if (!validateCarrierNetworkSuggestions(networkSuggestions, uid, packageName)) {
857             Log.e(TAG, "bad wifi suggestion from app: " + packageName);
858             return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_NOT_ALLOWED;
859         }
860 
861         int carrierId = mWifiCarrierInfoManager
862                 .getCarrierIdForPackageWithCarrierPrivileges(packageName);
863         final String activeScorerPackage = mNetworkScoreManager.getActiveScorerPackage();
864         PerAppInfo perAppInfo = mActiveNetworkSuggestionsPerApp.get(packageName);
865         if (perAppInfo == null) {
866             perAppInfo = new PerAppInfo(uid, packageName, featureId);
867             mActiveNetworkSuggestionsPerApp.put(packageName, perAppInfo);
868             if (mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(uid)) {
869                 Log.i(TAG, "Setting the carrier provisioning app approved");
870                 perAppInfo.hasUserApproved = true;
871                 mWifiMetrics.incrementNetworkSuggestionApiUsageNumOfAppInType(
872                         APP_TYPE_NETWORK_PROVISIONING);
873             } else if (carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) {
874                 Log.i(TAG, "Setting the carrier privileged app approved");
875                 perAppInfo.carrierId = carrierId;
876                 mWifiMetrics.incrementNetworkSuggestionApiUsageNumOfAppInType(
877                         APP_TYPE_CARRIER_PRIVILEGED);
878             } else if (perAppInfo.packageName.equals(activeScorerPackage)) {
879                 Log.i(TAG, "Exempting the active scorer app");
880                 // nothing more to do, user approval related checks are done at network selection
881                 // time (which also takes care of any dynamic changes in active scorer).
882                 mWifiMetrics.incrementNetworkSuggestionApiUsageNumOfAppInType(
883                         APP_TYPE_NON_PRIVILEGED);
884             } else {
885                 if (isSuggestionFromForegroundApp(packageName)) {
886                     sendUserApprovalDialog(packageName, uid);
887                 } else {
888                     sendUserApprovalNotificationIfNotApproved(packageName, uid);
889                 }
890                 mWifiMetrics.incrementNetworkSuggestionApiUsageNumOfAppInType(
891                         APP_TYPE_NON_PRIVILEGED);
892             }
893         }
894         // If PerAppInfo is upgrade from pre-R, uid may not be set.
895         perAppInfo.setUid(uid);
896         Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions =
897                 convertToExtendedWnsSet(networkSuggestions, perAppInfo);
898         boolean isLowRamDevice = mActivityManager.isLowRamDevice();
899         int networkSuggestionsMaxPerApp =
900                 WifiManager.getMaxNumberOfNetworkSuggestionsPerApp(isLowRamDevice);
901         if (perAppInfo.extNetworkSuggestions.size() + extNetworkSuggestions.size()
902                 > networkSuggestionsMaxPerApp) {
903             Set<ExtendedWifiNetworkSuggestion> savedNetworkSuggestions =
904                     new HashSet<>(perAppInfo.extNetworkSuggestions);
905             savedNetworkSuggestions.addAll(extNetworkSuggestions);
906             if (savedNetworkSuggestions.size() > networkSuggestionsMaxPerApp) {
907                 Log.e(TAG, "Failed to add network suggestions for " + packageName
908                         + ". Exceeds max per app, current list size: "
909                         + perAppInfo.extNetworkSuggestions.size()
910                         + ", new list size: "
911                         + extNetworkSuggestions.size());
912                 return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_EXCEEDS_MAX_PER_APP;
913             }
914         }
915         if (perAppInfo.extNetworkSuggestions.isEmpty()) {
916             // Start tracking app-op changes from the app if they have active suggestions.
917             startTrackingAppOpsChange(packageName, uid);
918         }
919 
920         for (ExtendedWifiNetworkSuggestion ewns: extNetworkSuggestions) {
921             if (carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) {
922                 ewns.setCarrierId(carrierId);
923             }
924             // If network has no IMSI protection and user didn't approve exemption, make it initial
925             // auto join disabled
926             if (isSimBasedSuggestion(ewns)) {
927                 int subId = mWifiCarrierInfoManager
928                         .getMatchingSubId(getCarrierIdFromSuggestion(ewns));
929                 if (!(mWifiCarrierInfoManager.requiresImsiEncryption(subId)
930                         || mWifiCarrierInfoManager.hasUserApprovedImsiPrivacyExemptionForCarrier(
931                         getCarrierIdFromSuggestion(ewns)))) {
932                     ewns.isAutojoinEnabled = false;
933                 }
934             }
935             if (ewns.wns.passpointConfiguration == null) {
936                 if (ewns.wns.wifiConfiguration.isEnterprise()) {
937                     if (!mWifiKeyStore.updateNetworkKeys(ewns.wns.wifiConfiguration, null)) {
938                         Log.e(TAG, "Enterprise network install failure for SSID: "
939                                 + ewns.wns.wifiConfiguration.SSID);
940                         continue;
941                     }
942                 }
943                 // If we have a config in WifiConfigManager for this suggestion, update
944                 // WifiConfigManager with the latest WifiConfig.
945                 // Note: Similar logic is present in PasspointManager for passpoint networks.
946                 updateWifiConfigInWcmIfPresent(
947                         ewns.createInternalWifiConfiguration(), uid, packageName);
948                 addToScanResultMatchInfoMap(ewns);
949             } else {
950                 ewns.wns.passpointConfiguration.setAutojoinEnabled(ewns.isAutojoinEnabled);
951                 // Install Passpoint config, if failure, ignore that suggestion
952                 if (!mWifiInjector.getPasspointManager().addOrUpdateProvider(
953                         ewns.wns.passpointConfiguration, uid,
954                         packageName, true, !ewns.wns.isUntrusted())) {
955                     Log.e(TAG, "Passpoint profile install failure for FQDN: "
956                             + ewns.wns.wifiConfiguration.FQDN);
957                     continue;
958                 }
959                 addToPasspointInfoMap(ewns);
960             }
961             perAppInfo.extNetworkSuggestions.remove(ewns);
962             perAppInfo.extNetworkSuggestions.add(ewns);
963         }
964         for (OnSuggestionUpdateListener listener : mListeners) {
965             listener.onSuggestionsAddedOrUpdated(networkSuggestions);
966         }
967         // Update the max size for this app.
968         perAppInfo.maxSize = Math.max(perAppInfo.extNetworkSuggestions.size(), perAppInfo.maxSize);
969         saveToStore();
970         mWifiMetrics.incrementNetworkSuggestionApiNumModification();
971         mWifiMetrics.noteNetworkSuggestionApiListSizeHistogram(getAllMaxSizes());
972         return WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS;
973     }
974 
getCarrierIdFromSuggestion(ExtendedWifiNetworkSuggestion ewns)975     private int getCarrierIdFromSuggestion(ExtendedWifiNetworkSuggestion ewns) {
976         if (ewns.wns.passpointConfiguration == null) {
977             return ewns.wns.wifiConfiguration.carrierId;
978         }
979         return ewns.wns.passpointConfiguration.getCarrierId();
980     }
981 
isSimBasedSuggestion(ExtendedWifiNetworkSuggestion ewns)982     private boolean isSimBasedSuggestion(ExtendedWifiNetworkSuggestion ewns) {
983         if (ewns.wns.passpointConfiguration == null) {
984             return ewns.wns.wifiConfiguration.enterpriseConfig != null
985                     && ewns.wns.wifiConfiguration.enterpriseConfig.isAuthenticationSimBased();
986         } else {
987             return ewns.wns.passpointConfiguration.getCredential().getSimCredential() != null;
988         }
989     }
990 
validateNetworkSuggestions(List<WifiNetworkSuggestion> networkSuggestions)991     private boolean validateNetworkSuggestions(List<WifiNetworkSuggestion> networkSuggestions) {
992         for (WifiNetworkSuggestion wns : networkSuggestions) {
993             if (wns == null || wns.wifiConfiguration == null) {
994                 return false;
995             }
996             if (wns.passpointConfiguration == null) {
997                 if (!WifiConfigurationUtil.validate(wns.wifiConfiguration,
998                         WifiConfigurationUtil.VALIDATE_FOR_ADD)) {
999                     return false;
1000                 }
1001                 if (wns.wifiConfiguration.isEnterprise()
1002                         && wns.wifiConfiguration.enterpriseConfig.isInsecure()) {
1003                     Log.e(TAG, "Insecure enterprise suggestion is invalid.");
1004                     return false;
1005                 }
1006 
1007             } else {
1008                 if (!wns.passpointConfiguration.validate()) {
1009                     return false;
1010                 }
1011             }
1012         }
1013         return true;
1014     }
1015 
validateCarrierNetworkSuggestions( List<WifiNetworkSuggestion> networkSuggestions, int uid, String packageName)1016     private boolean validateCarrierNetworkSuggestions(
1017             List<WifiNetworkSuggestion> networkSuggestions, int uid, String packageName) {
1018         if (mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(uid)
1019                 || mWifiCarrierInfoManager.getCarrierIdForPackageWithCarrierPrivileges(packageName)
1020                 != TelephonyManager.UNKNOWN_CARRIER_ID) {
1021             return true;
1022         }
1023         // If an app doesn't have carrier privileges or carrier provisioning permission, suggests
1024         // SIM-based network and sets CarrierId are illegal.
1025         for (WifiNetworkSuggestion suggestion : networkSuggestions) {
1026             WifiConfiguration wifiConfiguration = suggestion.wifiConfiguration;
1027             PasspointConfiguration passpointConfiguration = suggestion.passpointConfiguration;
1028             if (passpointConfiguration == null) {
1029                 if (wifiConfiguration.carrierId != TelephonyManager.UNKNOWN_CARRIER_ID) {
1030                     return false;
1031                 }
1032                 if (wifiConfiguration.enterpriseConfig != null
1033                         && wifiConfiguration.enterpriseConfig.isAuthenticationSimBased()) {
1034                     return false;
1035                 }
1036             } else {
1037                 if (passpointConfiguration.getCarrierId() != TelephonyManager.UNKNOWN_CARRIER_ID) {
1038                     return false;
1039                 }
1040                 if (passpointConfiguration.getCredential() != null
1041                         && passpointConfiguration.getCredential().getSimCredential() != null) {
1042                     return false;
1043                 }
1044             }
1045         }
1046         return true;
1047     }
1048 
stopTrackingAppOpsChange(@onNull String packageName)1049     private void stopTrackingAppOpsChange(@NonNull String packageName) {
1050         AppOpsChangedListener appOpsChangedListener =
1051                 mAppOpsChangedListenerPerApp.remove(packageName);
1052         if (appOpsChangedListener == null) {
1053             Log.wtf(TAG, "No app ops listener found for " + packageName);
1054             return;
1055         }
1056         mAppOps.stopWatchingMode(appOpsChangedListener);
1057     }
1058 
1059     /**
1060      * Remove provided list from that App active list. If provided list is empty, will remove all.
1061      * Will disconnect network if current connected network is in the remove list.
1062      */
removeInternal( @onNull Collection<ExtendedWifiNetworkSuggestion> extNetworkSuggestions, @NonNull String packageName, @NonNull PerAppInfo perAppInfo)1063     private void removeInternal(
1064             @NonNull Collection<ExtendedWifiNetworkSuggestion> extNetworkSuggestions,
1065             @NonNull String packageName,
1066             @NonNull PerAppInfo perAppInfo) {
1067         // Get internal suggestions
1068         Set<ExtendedWifiNetworkSuggestion> removingExtSuggestions =
1069                 new HashSet<>(perAppInfo.extNetworkSuggestions);
1070         if (!extNetworkSuggestions.isEmpty()) {
1071             // Keep the internal suggestions need to remove.
1072             removingExtSuggestions.retainAll(extNetworkSuggestions);
1073             perAppInfo.extNetworkSuggestions.removeAll(extNetworkSuggestions);
1074         } else {
1075             // empty list is used to clear everything for the app. Store a copy for use below.
1076             perAppInfo.extNetworkSuggestions.clear();
1077         }
1078         if (perAppInfo.extNetworkSuggestions.isEmpty()) {
1079             // Note: We don't remove the app entry even if there is no active suggestions because
1080             // we want to keep the notification state for all apps that have ever provided
1081             // suggestions.
1082             if (mVerboseLoggingEnabled) Log.v(TAG, "No active suggestions for " + packageName);
1083             // Stop tracking app-op changes from the app if they don't have active suggestions.
1084             stopTrackingAppOpsChange(packageName);
1085         }
1086         // Clear the cache.
1087         List<WifiNetworkSuggestion> removingSuggestions = new ArrayList<>();
1088         for (ExtendedWifiNetworkSuggestion ewns : removingExtSuggestions) {
1089             if (ewns.wns.passpointConfiguration != null) {
1090                 // Clear the Passpoint config.
1091                 mWifiInjector.getPasspointManager().removeProvider(
1092                         ewns.perAppInfo.uid,
1093                         false,
1094                         ewns.wns.passpointConfiguration.getUniqueId(), null);
1095                 removeFromPassPointInfoMap(ewns);
1096             } else {
1097                 if (ewns.wns.wifiConfiguration.isEnterprise()) {
1098                     mWifiKeyStore.removeKeys(ewns.wns.wifiConfiguration.enterpriseConfig);
1099                 }
1100                 removeFromScanResultMatchInfoMapAndRemoveRelatedScoreCard(ewns);
1101             }
1102             removingSuggestions.add(ewns.wns);
1103         }
1104         for (OnSuggestionUpdateListener listener : mListeners) {
1105             listener.onSuggestionsRemoved(removingSuggestions);
1106         }
1107         // Disconnect suggested network if connected
1108         removeFromConfigManagerIfServingNetworkSuggestionRemoved(removingExtSuggestions);
1109     }
1110 
1111     /**
1112      * Remove the provided list of network suggestions from the corresponding app's active list.
1113      */
remove( List<WifiNetworkSuggestion> networkSuggestions, int uid, String packageName)1114     public @WifiManager.NetworkSuggestionsStatusCode int remove(
1115             List<WifiNetworkSuggestion> networkSuggestions, int uid, String packageName) {
1116         if (!mWifiPermissionsUtil.doesUidBelongToCurrentUser(uid)) {
1117             Log.e(TAG, "UID " + uid + " not visible to the current user");
1118             return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_INTERNAL;
1119         }
1120         if (!mUserDataLoaded) {
1121             Log.e(TAG, "Remove Network suggestion before boot complete is not allowed.");
1122             return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_INTERNAL;
1123         }
1124         if (networkSuggestions == null) {
1125             Log.w(TAG, "Null list of network suggestions for " + packageName + ". Ignoring");
1126             return WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS;
1127         }
1128         if (mVerboseLoggingEnabled) {
1129             Log.v(TAG, "Removing " + networkSuggestions.size() + " networks from " + packageName);
1130         }
1131 
1132         if (!validateNetworkSuggestions(networkSuggestions)) {
1133             Log.e(TAG, "Invalid suggestion remove from app: " + packageName);
1134             return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_REMOVE_INVALID;
1135         }
1136         PerAppInfo perAppInfo = mActiveNetworkSuggestionsPerApp.get(packageName);
1137         if (perAppInfo == null) {
1138             Log.e(TAG, "Failed to remove network suggestions for " + packageName
1139                     + ". No network suggestions found");
1140             return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_REMOVE_INVALID;
1141         }
1142         Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions =
1143                 convertToExtendedWnsSet(networkSuggestions, perAppInfo);
1144         // check if all the request network suggestions are present in the active list.
1145         if (!extNetworkSuggestions.isEmpty()
1146                 && !perAppInfo.extNetworkSuggestions.containsAll(extNetworkSuggestions)) {
1147             Log.e(TAG, "Failed to remove network suggestions for " + packageName
1148                     + ". Network suggestions not found in active network suggestions");
1149             return WifiManager.STATUS_NETWORK_SUGGESTIONS_ERROR_REMOVE_INVALID;
1150         }
1151         removeInternal(extNetworkSuggestions, packageName, perAppInfo);
1152         saveToStore();
1153         mWifiMetrics.incrementNetworkSuggestionApiNumModification();
1154         mWifiMetrics.noteNetworkSuggestionApiListSizeHistogram(getAllMaxSizes());
1155         return WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS;
1156     }
1157 
1158     /**
1159      * Remove all tracking of the app that has been uninstalled.
1160      */
removeApp(@onNull String packageName)1161     public void removeApp(@NonNull String packageName) {
1162         PerAppInfo perAppInfo = mActiveNetworkSuggestionsPerApp.get(packageName);
1163         if (perAppInfo == null) return;
1164         removeInternal(Collections.EMPTY_LIST, packageName, perAppInfo);
1165         // Remove the package fully from the internal database.
1166         mActiveNetworkSuggestionsPerApp.remove(packageName);
1167         ExternalCallbackTracker<ISuggestionConnectionStatusListener> listenerTracker =
1168                 mSuggestionStatusListenerPerApp.remove(packageName);
1169         if (listenerTracker != null) listenerTracker.clear();
1170         saveToStore();
1171         Log.i(TAG, "Removed " + packageName);
1172     }
1173 
1174     /**
1175      * Get all network suggestion for target App
1176      * @return List of WifiNetworkSuggestions
1177      */
get(@onNull String packageName, int uid)1178     public @NonNull List<WifiNetworkSuggestion> get(@NonNull String packageName, int uid) {
1179         List<WifiNetworkSuggestion> networkSuggestionList = new ArrayList<>();
1180         if (!mWifiPermissionsUtil.doesUidBelongToCurrentUser(uid)) {
1181             Log.e(TAG, "UID " + uid + " not visible to the current user");
1182             return networkSuggestionList;
1183         }
1184         if (!mUserDataLoaded) {
1185             Log.e(TAG, "Get Network suggestion before boot complete is not allowed.");
1186             return networkSuggestionList;
1187         }
1188         PerAppInfo perAppInfo = mActiveNetworkSuggestionsPerApp.get(packageName);
1189         // if App never suggested return empty list.
1190         if (perAppInfo == null) return networkSuggestionList;
1191         for (ExtendedWifiNetworkSuggestion extendedSuggestion : perAppInfo.extNetworkSuggestions) {
1192             networkSuggestionList.add(extendedSuggestion.wns);
1193         }
1194         return networkSuggestionList;
1195     }
1196 
1197     /**
1198      * Clear all internal state (for network settings reset).
1199      */
clear()1200     public void clear() {
1201         Iterator<Map.Entry<String, PerAppInfo>> iter =
1202                 mActiveNetworkSuggestionsPerApp.entrySet().iterator();
1203         while (iter.hasNext()) {
1204             Map.Entry<String, PerAppInfo> entry = iter.next();
1205             removeInternal(Collections.EMPTY_LIST, entry.getKey(), entry.getValue());
1206             iter.remove();
1207         }
1208         mSuggestionStatusListenerPerApp.clear();
1209         saveToStore();
1210         Log.i(TAG, "Cleared all internal state");
1211     }
1212 
1213     /**
1214      * Check if network suggestions are enabled or disabled for the app.
1215      */
hasUserApprovedForApp(String packageName)1216     public boolean hasUserApprovedForApp(String packageName) {
1217         PerAppInfo perAppInfo = mActiveNetworkSuggestionsPerApp.get(packageName);
1218         if (perAppInfo == null) return false;
1219 
1220         return perAppInfo.hasUserApproved;
1221     }
1222 
1223     /**
1224      * Enable or Disable network suggestions for the app.
1225      */
setHasUserApprovedForApp(boolean approved, String packageName)1226     public void setHasUserApprovedForApp(boolean approved, String packageName) {
1227         PerAppInfo perAppInfo = mActiveNetworkSuggestionsPerApp.get(packageName);
1228         if (perAppInfo == null) return;
1229 
1230         if (mVerboseLoggingEnabled) {
1231             Log.v(TAG, "Setting the app " + packageName
1232                     + (approved ? " approved" : " not approved"));
1233         }
1234         perAppInfo.hasUserApproved = approved;
1235         saveToStore();
1236     }
1237 
1238     /**
1239      * When user approve the IMSI protection exemption for carrier, restore the initial auto join
1240      * configure. If user already change it to enabled, keep that choice.
1241      */
restoreInitialAutojoinForCarrierId(int carrierId)1242     private void restoreInitialAutojoinForCarrierId(int carrierId) {
1243         for (PerAppInfo appInfo : mActiveNetworkSuggestionsPerApp.values()) {
1244             for (ExtendedWifiNetworkSuggestion ewns : appInfo.extNetworkSuggestions) {
1245                 if (!(isSimBasedSuggestion(ewns)
1246                         && getCarrierIdFromSuggestion(ewns) == carrierId)) {
1247                     continue;
1248                 }
1249                 if (mVerboseLoggingEnabled) {
1250                     Log.v(TAG, "Restore auto-join for suggestion: " + ewns);
1251                 }
1252                 ewns.isAutojoinEnabled |= ewns.wns.isInitialAutoJoinEnabled;
1253                 // Restore passpoint provider auto join.
1254                 if (ewns.wns.passpointConfiguration != null) {
1255                     mWifiInjector.getPasspointManager()
1256                             .enableAutojoin(ewns.wns.passpointConfiguration.getUniqueId(),
1257                                     null, ewns.isAutojoinEnabled);
1258                 }
1259             }
1260         }
1261     }
1262 
1263     /**
1264      * Returns a set of all network suggestions across all apps.
1265      */
1266     @VisibleForTesting
getAllNetworkSuggestions()1267     public Set<WifiNetworkSuggestion> getAllNetworkSuggestions() {
1268         return mActiveNetworkSuggestionsPerApp.values()
1269                 .stream()
1270                 .flatMap(e -> convertToWnsSet(e.extNetworkSuggestions)
1271                         .stream())
1272                 .collect(Collectors.toSet());
1273     }
1274 
1275     /**
1276      * Returns a set of all network suggestions across all apps that have been approved by user.
1277      */
getAllApprovedNetworkSuggestions()1278     public Set<WifiNetworkSuggestion> getAllApprovedNetworkSuggestions() {
1279         final String activeScorerPackage = mNetworkScoreManager.getActiveScorerPackage();
1280         return mActiveNetworkSuggestionsPerApp.values()
1281                 .stream()
1282                 .filter(e -> e.isApproved(activeScorerPackage))
1283                 .flatMap(e -> convertToWnsSet(e.extNetworkSuggestions)
1284                         .stream())
1285                 .collect(Collectors.toSet());
1286     }
1287 
1288     /**
1289      * Get all user approved, non-passpoint networks from suggestion.
1290      */
getAllScanOptimizationSuggestionNetworks()1291     public List<WifiConfiguration> getAllScanOptimizationSuggestionNetworks() {
1292         List<WifiConfiguration> networks = new ArrayList<>();
1293         final String activeScorerPackage = mNetworkScoreManager.getActiveScorerPackage();
1294         for (PerAppInfo info : mActiveNetworkSuggestionsPerApp.values()) {
1295             if (!info.isApproved(activeScorerPackage)) {
1296                 continue;
1297             }
1298             for (ExtendedWifiNetworkSuggestion ewns : info.extNetworkSuggestions) {
1299                 if (ewns.wns.getPasspointConfig() != null) {
1300                     continue;
1301                 }
1302                 WifiConfiguration network = mWifiConfigManager
1303                         .getConfiguredNetwork(ewns.wns.getWifiConfiguration().getKey());
1304                 if (network == null) {
1305                     network = ewns.createInternalWifiConfiguration();
1306                 }
1307                 networks.add(network);
1308             }
1309         }
1310         return networks;
1311     }
1312 
getAllMaxSizes()1313     private List<Integer> getAllMaxSizes() {
1314         return mActiveNetworkSuggestionsPerApp.values()
1315                 .stream()
1316                 .map(e -> e.maxSize)
1317                 .collect(Collectors.toList());
1318     }
1319 
getPrivateBroadcast(@onNull String action, @NonNull Pair<String, String> extra1, @NonNull Pair<String, Integer> extra2)1320     private PendingIntent getPrivateBroadcast(@NonNull String action,
1321             @NonNull Pair<String, String> extra1, @NonNull Pair<String, Integer> extra2) {
1322         Intent intent = new Intent(action)
1323                 .setPackage(mWifiInjector.getWifiStackPackageName())
1324                 .putExtra(extra1.first, extra1.second)
1325                 .putExtra(extra2.first, extra2.second);
1326         return mFrameworkFacade.getBroadcast(mContext, 0, intent,
1327                 PendingIntent.FLAG_UPDATE_CURRENT);
1328     }
1329 
getAppName(@onNull String packageName, int uid)1330     private @NonNull CharSequence getAppName(@NonNull String packageName, int uid) {
1331         ApplicationInfo applicationInfo = null;
1332         try {
1333             applicationInfo = mContext.getPackageManager().getApplicationInfoAsUser(
1334                 packageName, 0, UserHandle.getUserHandleForUid(uid));
1335         } catch (PackageManager.NameNotFoundException e) {
1336             Log.e(TAG, "Failed to find app name for " + packageName);
1337             return "";
1338         }
1339         CharSequence appName = mPackageManager.getApplicationLabel(applicationInfo);
1340         return (appName != null) ? appName : "";
1341     }
1342 
1343     /**
1344      * Check if the request came from foreground app.
1345      */
isSuggestionFromForegroundApp(@onNull String packageName)1346     private boolean isSuggestionFromForegroundApp(@NonNull String packageName) {
1347         try {
1348             return mActivityManager.getPackageImportance(packageName)
1349                     <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
1350         } catch (SecurityException e) {
1351             Log.e(TAG, "Failed to check the app state", e);
1352             return false;
1353         }
1354     }
1355 
sendUserApprovalDialog(@onNull String packageName, int uid)1356     private void sendUserApprovalDialog(@NonNull String packageName, int uid) {
1357         CharSequence appName = getAppName(packageName, uid);
1358         AlertDialog dialog = mFrameworkFacade.makeAlertDialogBuilder(mContext)
1359                 .setTitle(mResources.getString(R.string.wifi_suggestion_title))
1360                 .setMessage(mResources.getString(R.string.wifi_suggestion_content, appName))
1361                 .setPositiveButton(
1362                         mResources.getText(R.string.wifi_suggestion_action_allow_app),
1363                         (d, which) -> mHandler.post(
1364                                 () -> handleUserAllowAction(uid, packageName)))
1365                 .setNegativeButton(
1366                         mResources.getText(R.string.wifi_suggestion_action_disallow_app),
1367                         (d, which) -> mHandler.post(
1368                                 () -> handleUserDisallowAction(uid, packageName)))
1369                 .setOnDismissListener(
1370                         (d) -> mHandler.post(() -> handleUserDismissAction()))
1371                 .setOnCancelListener(
1372                         (d) -> mHandler.post(() -> handleUserDismissAction()))
1373                 .create();
1374         dialog.setCanceledOnTouchOutside(false);
1375         dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
1376         dialog.getWindow().addSystemFlags(
1377                 WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS);
1378         dialog.show();
1379         mUserApprovalUiActive = true;
1380         mIsLastUserApprovalUiDialog = true;
1381     }
1382 
sendUserApprovalNotification(@onNull String packageName, int uid)1383     private void sendUserApprovalNotification(@NonNull String packageName, int uid) {
1384         Notification.Action userAllowAppNotificationAction =
1385                 new Notification.Action.Builder(null,
1386                         mResources.getText(R.string.wifi_suggestion_action_allow_app),
1387                         getPrivateBroadcast(NOTIFICATION_USER_ALLOWED_APP_INTENT_ACTION,
1388                                 Pair.create(EXTRA_PACKAGE_NAME, packageName),
1389                                 Pair.create(EXTRA_UID, uid)))
1390                         .build();
1391         Notification.Action userDisallowAppNotificationAction =
1392                 new Notification.Action.Builder(null,
1393                         mResources.getText(R.string.wifi_suggestion_action_disallow_app),
1394                         getPrivateBroadcast(NOTIFICATION_USER_DISALLOWED_APP_INTENT_ACTION,
1395                                 Pair.create(EXTRA_PACKAGE_NAME, packageName),
1396                                 Pair.create(EXTRA_UID, uid)))
1397                         .build();
1398 
1399         CharSequence appName = getAppName(packageName, uid);
1400         Notification notification = mFrameworkFacade.makeNotificationBuilder(
1401                 mContext, WifiService.NOTIFICATION_NETWORK_STATUS)
1402                 .setSmallIcon(Icon.createWithResource(mContext.getWifiOverlayApkPkgName(),
1403                         com.android.wifi.resources.R.drawable.stat_notify_wifi_in_range))
1404                 .setTicker(mResources.getString(R.string.wifi_suggestion_title))
1405                 .setContentTitle(mResources.getString(R.string.wifi_suggestion_title))
1406                 .setStyle(new Notification.BigTextStyle()
1407                         .bigText(mResources.getString(R.string.wifi_suggestion_content, appName)))
1408                 .setDeleteIntent(getPrivateBroadcast(NOTIFICATION_USER_DISMISSED_INTENT_ACTION,
1409                         Pair.create(EXTRA_PACKAGE_NAME, packageName), Pair.create(EXTRA_UID, uid)))
1410                 .setShowWhen(false)
1411                 .setLocalOnly(true)
1412                 .setColor(mResources.getColor(android.R.color.system_notification_accent_color,
1413                         mContext.getTheme()))
1414                 .addAction(userAllowAppNotificationAction)
1415                 .addAction(userDisallowAppNotificationAction)
1416                 .build();
1417 
1418         // Post the notification.
1419         mNotificationManager.notify(
1420                 SystemMessage.NOTE_NETWORK_SUGGESTION_AVAILABLE, notification);
1421         mUserApprovalUiActive = true;
1422         mIsLastUserApprovalUiDialog = false;
1423     }
1424 
1425     /**
1426      * Send user approval notification if the app is not approved
1427      * @param packageName app package name
1428      * @param uid app UID
1429      * @return true if app is not approved and send notification.
1430      */
sendUserApprovalNotificationIfNotApproved( @onNull String packageName, @NonNull int uid)1431     private boolean sendUserApprovalNotificationIfNotApproved(
1432             @NonNull String packageName, @NonNull int uid) {
1433         if (!mActiveNetworkSuggestionsPerApp.containsKey(packageName)) {
1434             Log.wtf(TAG, "AppInfo is missing for " + packageName);
1435             return false;
1436         }
1437         if (mActiveNetworkSuggestionsPerApp.get(packageName).hasUserApproved) {
1438             return false; // already approved.
1439         }
1440 
1441         if (mUserApprovalUiActive) {
1442             return false; // has active notification.
1443         }
1444         Log.i(TAG, "Sending user approval notification for " + packageName);
1445         sendUserApprovalNotification(packageName, uid);
1446         return true;
1447     }
1448 
1449     private @Nullable Set<ExtendedWifiNetworkSuggestion>
getNetworkSuggestionsForScanResultMatchInfo( @onNull ScanResultMatchInfo scanResultMatchInfo, @Nullable MacAddress bssid)1450             getNetworkSuggestionsForScanResultMatchInfo(
1451             @NonNull ScanResultMatchInfo scanResultMatchInfo, @Nullable MacAddress bssid) {
1452         Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions = new HashSet<>();
1453         if (bssid != null) {
1454             Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestionsWithBssid =
1455                     mActiveScanResultMatchInfoWithBssid.get(
1456                             Pair.create(scanResultMatchInfo, bssid));
1457             if (matchingExtNetworkSuggestionsWithBssid != null) {
1458                 extNetworkSuggestions.addAll(matchingExtNetworkSuggestionsWithBssid);
1459             }
1460         }
1461         Set<ExtendedWifiNetworkSuggestion> matchingNetworkSuggestionsWithNoBssid =
1462                 mActiveScanResultMatchInfoWithNoBssid.get(scanResultMatchInfo);
1463         if (matchingNetworkSuggestionsWithNoBssid != null) {
1464             extNetworkSuggestions.addAll(matchingNetworkSuggestionsWithNoBssid);
1465         }
1466         if (extNetworkSuggestions.isEmpty()) {
1467             return null;
1468         }
1469         return extNetworkSuggestions;
1470     }
1471 
getNetworkSuggestionsForFqdnMatch( @ullable String fqdn)1472     private @Nullable Set<ExtendedWifiNetworkSuggestion> getNetworkSuggestionsForFqdnMatch(
1473             @Nullable String fqdn) {
1474         if (TextUtils.isEmpty(fqdn)) {
1475             return null;
1476         }
1477         return mPasspointInfo.get(fqdn);
1478     }
1479 
1480     /**
1481      * Returns a set of all network suggestions matching the provided FQDN.
1482      */
getNetworkSuggestionsForFqdn(String fqdn)1483     public @Nullable Set<ExtendedWifiNetworkSuggestion> getNetworkSuggestionsForFqdn(String fqdn) {
1484         Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions =
1485                 getNetworkSuggestionsForFqdnMatch(fqdn);
1486         if (extNetworkSuggestions == null) {
1487             return null;
1488         }
1489         final String activeScorerPackage = mNetworkScoreManager.getActiveScorerPackage();
1490         Set<ExtendedWifiNetworkSuggestion> approvedExtNetworkSuggestions = new HashSet<>();
1491         for (ExtendedWifiNetworkSuggestion ewns : extNetworkSuggestions) {
1492             if (!ewns.perAppInfo.isApproved(activeScorerPackage)) {
1493                 sendUserApprovalNotificationIfNotApproved(ewns.perAppInfo.packageName,
1494                         ewns.perAppInfo.uid);
1495                 continue;
1496             }
1497             if (isSimBasedSuggestion(ewns)) {
1498                 mWifiCarrierInfoManager.sendImsiProtectionExemptionNotificationIfRequired(
1499                         getCarrierIdFromSuggestion(ewns));
1500             }
1501             approvedExtNetworkSuggestions.add(ewns);
1502         }
1503 
1504         if (approvedExtNetworkSuggestions.isEmpty()) {
1505             return null;
1506         }
1507         if (mVerboseLoggingEnabled) {
1508             Log.v(TAG, "getNetworkSuggestionsForFqdn Found "
1509                     + approvedExtNetworkSuggestions + " for " + fqdn);
1510         }
1511         return approvedExtNetworkSuggestions;
1512     }
1513 
1514     /**
1515      * Returns a set of all network suggestions matching the provided scan detail.
1516      */
getNetworkSuggestionsForScanDetail( @onNull ScanDetail scanDetail)1517     public @Nullable Set<ExtendedWifiNetworkSuggestion> getNetworkSuggestionsForScanDetail(
1518             @NonNull ScanDetail scanDetail) {
1519         ScanResult scanResult = scanDetail.getScanResult();
1520         if (scanResult == null) {
1521             Log.e(TAG, "No scan result found in scan detail");
1522             return null;
1523         }
1524         Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions = null;
1525         try {
1526             ScanResultMatchInfo scanResultMatchInfo =
1527                     ScanResultMatchInfo.fromScanResult(scanResult);
1528             extNetworkSuggestions = getNetworkSuggestionsForScanResultMatchInfo(
1529                     scanResultMatchInfo,  MacAddress.fromString(scanResult.BSSID));
1530         } catch (IllegalArgumentException e) {
1531             Log.e(TAG, "Failed to lookup network from scan result match info map", e);
1532         }
1533         if (extNetworkSuggestions == null) {
1534             return null;
1535         }
1536         final String activeScorerPackage = mNetworkScoreManager.getActiveScorerPackage();
1537         Set<ExtendedWifiNetworkSuggestion> approvedExtNetworkSuggestions = new HashSet<>();
1538         for (ExtendedWifiNetworkSuggestion ewns : extNetworkSuggestions) {
1539             if (!ewns.perAppInfo.isApproved(activeScorerPackage)) {
1540                 sendUserApprovalNotificationIfNotApproved(ewns.perAppInfo.packageName,
1541                         ewns.perAppInfo.uid);
1542                 continue;
1543             }
1544             if (isSimBasedSuggestion(ewns)) {
1545                 mWifiCarrierInfoManager.sendImsiProtectionExemptionNotificationIfRequired(
1546                         getCarrierIdFromSuggestion(ewns));
1547             }
1548             approvedExtNetworkSuggestions.add(ewns);
1549         }
1550 
1551         if (approvedExtNetworkSuggestions.isEmpty()) {
1552             return null;
1553         }
1554         if (mVerboseLoggingEnabled) {
1555             Log.v(TAG, "getNetworkSuggestionsForScanDetail Found "
1556                     + approvedExtNetworkSuggestions + " for " + scanResult.SSID
1557                     + "[" + scanResult.capabilities + "]");
1558         }
1559         return approvedExtNetworkSuggestions;
1560     }
1561 
1562     /**
1563      * Returns a set of all network suggestions matching the provided the WifiConfiguration.
1564      */
getNetworkSuggestionsForWifiConfiguration( @onNull WifiConfiguration wifiConfiguration, @Nullable String bssid)1565     public @Nullable Set<ExtendedWifiNetworkSuggestion> getNetworkSuggestionsForWifiConfiguration(
1566             @NonNull WifiConfiguration wifiConfiguration, @Nullable String bssid) {
1567         Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions = null;
1568         if (wifiConfiguration.isPasspoint()) {
1569             extNetworkSuggestions = getNetworkSuggestionsForFqdnMatch(wifiConfiguration.FQDN);
1570         } else {
1571             try {
1572                 ScanResultMatchInfo scanResultMatchInfo =
1573                         ScanResultMatchInfo.fromWifiConfiguration(wifiConfiguration);
1574                 extNetworkSuggestions = getNetworkSuggestionsForScanResultMatchInfo(
1575                         scanResultMatchInfo, bssid == null ? null : MacAddress.fromString(bssid));
1576             } catch (IllegalArgumentException e) {
1577                 Log.e(TAG, "Failed to lookup network from scan result match info map", e);
1578             }
1579         }
1580         if (extNetworkSuggestions == null || extNetworkSuggestions.isEmpty()) {
1581             return null;
1582         }
1583         final String activeScorerPackage = mNetworkScoreManager.getActiveScorerPackage();
1584         Set<ExtendedWifiNetworkSuggestion> approvedExtNetworkSuggestions =
1585                 extNetworkSuggestions
1586                         .stream()
1587                         .filter(n -> n.perAppInfo.isApproved(activeScorerPackage))
1588                         .collect(Collectors.toSet());
1589         if (approvedExtNetworkSuggestions.isEmpty()) {
1590             return null;
1591         }
1592         if (mVerboseLoggingEnabled) {
1593             Log.v(TAG, "getNetworkSuggestionsForWifiConfiguration Found "
1594                     + approvedExtNetworkSuggestions + " for " + wifiConfiguration.SSID
1595                     + wifiConfiguration.FQDN + "[" + wifiConfiguration.allowedKeyManagement + "]");
1596         }
1597         return approvedExtNetworkSuggestions;
1598     }
1599 
1600     /**
1601      * Retrieve the WifiConfigurations for all matched suggestions which allow user manually connect
1602      * and user already approved for non-open networks.
1603      */
getWifiConfigForMatchedNetworkSuggestionsSharedWithUser( List<ScanResult> scanResults)1604     public @NonNull List<WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(
1605             List<ScanResult> scanResults) {
1606         // Create a HashSet to avoid return multiple result for duplicate ScanResult.
1607         Set<String> networkKeys = new HashSet<>();
1608         List<WifiConfiguration> sharedWifiConfigs = new ArrayList<>();
1609         for (ScanResult scanResult : scanResults) {
1610             ScanResultMatchInfo scanResultMatchInfo =
1611                     ScanResultMatchInfo.fromScanResult(scanResult);
1612             if (scanResultMatchInfo.networkType == WifiConfiguration.SECURITY_TYPE_OPEN) {
1613                 continue;
1614             }
1615             Set<ExtendedWifiNetworkSuggestion> extNetworkSuggestions =
1616                     getNetworkSuggestionsForScanResultMatchInfo(
1617                             scanResultMatchInfo,  MacAddress.fromString(scanResult.BSSID));
1618             if (extNetworkSuggestions == null || extNetworkSuggestions.isEmpty()) {
1619                 continue;
1620             }
1621             Set<ExtendedWifiNetworkSuggestion> sharedNetworkSuggestions = extNetworkSuggestions
1622                     .stream()
1623                     .filter(ewns -> ewns.perAppInfo.hasUserApproved
1624                             && ewns.wns.isUserAllowedToManuallyConnect)
1625                     .collect(Collectors.toSet());
1626             if (sharedNetworkSuggestions.isEmpty()) {
1627                 continue;
1628             }
1629             ExtendedWifiNetworkSuggestion ewns =
1630                     sharedNetworkSuggestions.stream().findFirst().get();
1631             if (mVerboseLoggingEnabled) {
1632                 Log.v(TAG, "getWifiConfigForMatchedNetworkSuggestionsSharedWithUser Found "
1633                         + ewns + " for " + scanResult.SSID + "[" + scanResult.capabilities + "]");
1634             }
1635             WifiConfiguration config = ewns.wns.wifiConfiguration;
1636             WifiConfiguration existingConfig = mWifiConfigManager
1637                     .getConfiguredNetwork(config.getKey());
1638             if (existingConfig == null || !existingConfig.fromWifiNetworkSuggestion) {
1639                 continue;
1640             }
1641             if (networkKeys.add(existingConfig.getKey())) {
1642                 sharedWifiConfigs.add(existingConfig);
1643             }
1644         }
1645         return sharedWifiConfigs;
1646     }
1647 
1648     /**
1649      * Check if the given passpoint suggestion has user approval and allow user manually connect.
1650      */
isPasspointSuggestionSharedWithUser(WifiConfiguration config)1651     public boolean isPasspointSuggestionSharedWithUser(WifiConfiguration config) {
1652         if (WifiConfiguration.isMetered(config, null)
1653                 && mWifiCarrierInfoManager.isCarrierNetworkFromNonDefaultDataSim(config)) {
1654             return false;
1655         }
1656         Set<ExtendedWifiNetworkSuggestion> extendedWifiNetworkSuggestions =
1657                 getNetworkSuggestionsForFqdnMatch(config.FQDN);
1658         Set<ExtendedWifiNetworkSuggestion> matchedSuggestions =
1659                 extendedWifiNetworkSuggestions == null ? null : extendedWifiNetworkSuggestions
1660                 .stream().filter(ewns -> ewns.perAppInfo.uid == config.creatorUid)
1661                 .collect(Collectors.toSet());
1662         if (matchedSuggestions == null || matchedSuggestions.isEmpty()) {
1663             Log.e(TAG, "Matched network suggestion is missing for FQDN:" + config.FQDN);
1664             return false;
1665         }
1666         ExtendedWifiNetworkSuggestion suggestion = matchedSuggestions
1667                 .stream().findAny().get();
1668         return suggestion.wns.isUserAllowedToManuallyConnect
1669                 && suggestion.perAppInfo.hasUserApproved;
1670     }
1671 
1672     /**
1673      * Get hidden network from active network suggestions.
1674      * Todo(): Now limit by a fixed number, maybe we can try rotation?
1675      * @return set of WifiConfigurations
1676      */
retrieveHiddenNetworkList()1677     public List<WifiScanner.ScanSettings.HiddenNetwork> retrieveHiddenNetworkList() {
1678         List<WifiScanner.ScanSettings.HiddenNetwork> hiddenNetworks = new ArrayList<>();
1679         for (PerAppInfo appInfo : mActiveNetworkSuggestionsPerApp.values()) {
1680             if (!appInfo.hasUserApproved) continue;
1681             for (ExtendedWifiNetworkSuggestion ewns : appInfo.extNetworkSuggestions) {
1682                 if (!ewns.wns.wifiConfiguration.hiddenSSID) continue;
1683                 hiddenNetworks.add(
1684                         new WifiScanner.ScanSettings.HiddenNetwork(
1685                                 ewns.wns.wifiConfiguration.SSID));
1686                 if (hiddenNetworks.size() >= NUMBER_OF_HIDDEN_NETWORK_FOR_ONE_SCAN) {
1687                     return hiddenNetworks;
1688                 }
1689             }
1690         }
1691         return hiddenNetworks;
1692     }
1693 
1694     /**
1695      * Helper method to send the post connection broadcast to specified package.
1696      */
sendPostConnectionBroadcast( ExtendedWifiNetworkSuggestion extSuggestion)1697     private void sendPostConnectionBroadcast(
1698             ExtendedWifiNetworkSuggestion extSuggestion) {
1699         Intent intent = new Intent(WifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION);
1700         intent.putExtra(WifiManager.EXTRA_NETWORK_SUGGESTION, extSuggestion.wns);
1701         // Intended to wakeup the receiving app so set the specific package name.
1702         intent.setPackage(extSuggestion.perAppInfo.packageName);
1703         mContext.sendBroadcastAsUser(
1704                 intent, UserHandle.getUserHandleForUid(extSuggestion.perAppInfo.uid));
1705     }
1706 
1707     /**
1708      * Helper method to send the post connection broadcast to specified package.
1709      */
sendPostConnectionBroadcastIfAllowed( ExtendedWifiNetworkSuggestion matchingExtSuggestion, @NonNull String message)1710     private void sendPostConnectionBroadcastIfAllowed(
1711             ExtendedWifiNetworkSuggestion matchingExtSuggestion, @NonNull String message) {
1712         try {
1713             mWifiPermissionsUtil.enforceCanAccessScanResults(
1714                     matchingExtSuggestion.perAppInfo.packageName,
1715                     matchingExtSuggestion.perAppInfo.featureId,
1716                     matchingExtSuggestion.perAppInfo.uid, message);
1717         } catch (SecurityException se) {
1718             Log.w(TAG, "Permission denied for sending post connection broadcast to "
1719                     + matchingExtSuggestion.perAppInfo.packageName);
1720             return;
1721         }
1722         if (mVerboseLoggingEnabled) {
1723             Log.v(TAG, "Sending post connection broadcast to "
1724                     + matchingExtSuggestion.perAppInfo.packageName);
1725         }
1726         sendPostConnectionBroadcast(matchingExtSuggestion);
1727     }
1728 
1729     /**
1730      * Send out the {@link WifiManager#ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION} to the
1731      * network suggestion that provided credential for the current connection network.
1732      * If current connection network is open user saved network, broadcast will be only sent out to
1733      * one of the carrier apps that suggested matched network suggestions.
1734      *
1735      * @param connectedNetwork {@link WifiConfiguration} representing the network connected to.
1736      * @param connectedBssid BSSID of the network connected to.
1737      */
handleConnectionSuccess( @onNull WifiConfiguration connectedNetwork, @NonNull String connectedBssid)1738     private void handleConnectionSuccess(
1739             @NonNull WifiConfiguration connectedNetwork, @NonNull String connectedBssid) {
1740         if (!(connectedNetwork.fromWifiNetworkSuggestion || connectedNetwork.isOpenNetwork())) {
1741             return;
1742         }
1743         Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestions =
1744                     getNetworkSuggestionsForWifiConfiguration(connectedNetwork, connectedBssid);
1745 
1746         if (mVerboseLoggingEnabled) {
1747             Log.v(TAG, "Network suggestions matching the connection "
1748                     + matchingExtNetworkSuggestions);
1749         }
1750         if (matchingExtNetworkSuggestions == null
1751                 || matchingExtNetworkSuggestions.isEmpty()) return;
1752 
1753         if (connectedNetwork.fromWifiNetworkSuggestion) {
1754             // Find subset of network suggestions from app suggested the connected network.
1755             matchingExtNetworkSuggestions =
1756                     matchingExtNetworkSuggestions.stream()
1757                             .filter(x -> x.perAppInfo.uid == connectedNetwork.creatorUid)
1758                             .collect(Collectors.toSet());
1759             if (matchingExtNetworkSuggestions.isEmpty()) {
1760                 Log.wtf(TAG, "Current connected network suggestion is missing!");
1761                 return;
1762             }
1763             // Store the set of matching network suggestions.
1764             mActiveNetworkSuggestionsMatchingConnection =
1765                     new HashSet<>(matchingExtNetworkSuggestions);
1766         } else {
1767             if (connectedNetwork.isOpenNetwork()) {
1768                 // For saved open network, found the matching suggestion from carrier privileged
1769                 // apps. As we only expect one suggestor app to take action on post connection, if
1770                 // multiple apps suggested matched suggestions, framework will randomly pick one.
1771                 matchingExtNetworkSuggestions = matchingExtNetworkSuggestions.stream()
1772                         .filter(x -> x.perAppInfo.carrierId != TelephonyManager.UNKNOWN_CARRIER_ID
1773                                 || mWifiPermissionsUtil
1774                                 .checkNetworkCarrierProvisioningPermission(x.perAppInfo.uid))
1775                         .limit(1).collect(Collectors.toSet());
1776                 if (matchingExtNetworkSuggestions.isEmpty()) {
1777                     if (mVerboseLoggingEnabled) {
1778                         Log.v(TAG, "No suggestion matched connected user saved open network.");
1779                     }
1780                     return;
1781                 }
1782             }
1783         }
1784 
1785         mWifiMetrics.incrementNetworkSuggestionApiNumConnectSuccess();
1786         // Find subset of network suggestions have set |isAppInteractionRequired|.
1787         Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestionsWithReqAppInteraction =
1788                 matchingExtNetworkSuggestions.stream()
1789                         .filter(x -> x.wns.isAppInteractionRequired)
1790                         .collect(Collectors.toSet());
1791         if (matchingExtNetworkSuggestionsWithReqAppInteraction.isEmpty()) return;
1792 
1793         // Iterate over the matching network suggestions list:
1794         // a) Ensure that these apps have the necessary location permissions.
1795         // b) Send directed broadcast to the app with their corresponding network suggestion.
1796         for (ExtendedWifiNetworkSuggestion matchingExtNetworkSuggestion
1797                 : matchingExtNetworkSuggestionsWithReqAppInteraction) {
1798             sendPostConnectionBroadcastIfAllowed(
1799                     matchingExtNetworkSuggestion,
1800                     "Connected to " + matchingExtNetworkSuggestion.wns.wifiConfiguration.SSID
1801                             + ". featureId is first feature of the app using network suggestions");
1802         }
1803     }
1804 
1805     /**
1806      * Handle connection failure.
1807      *
1808      * @param network {@link WifiConfiguration} representing the network that connection failed to.
1809      * @param bssid BSSID of the network connection failed to if known, else null.
1810      * @param failureCode failure reason code.
1811      */
handleConnectionFailure(@onNull WifiConfiguration network, @Nullable String bssid, int failureCode)1812     private void handleConnectionFailure(@NonNull WifiConfiguration network,
1813                                          @Nullable String bssid, int failureCode) {
1814         if (!network.fromWifiNetworkSuggestion) {
1815             return;
1816         }
1817         Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestions =
1818                 getNetworkSuggestionsForWifiConfiguration(network, bssid);
1819         if (mVerboseLoggingEnabled) {
1820             Log.v(TAG, "Network suggestions matching the connection failure "
1821                     + matchingExtNetworkSuggestions);
1822         }
1823         if (matchingExtNetworkSuggestions == null
1824                 || matchingExtNetworkSuggestions.isEmpty()) return;
1825 
1826         mWifiMetrics.incrementNetworkSuggestionApiNumConnectFailure();
1827         // TODO (b/115504887, b/112196799): Blacklist the corresponding network suggestion if
1828         // the connection failed.
1829 
1830         // Find subset of network suggestions which suggested the connection failure network.
1831         Set<ExtendedWifiNetworkSuggestion> matchingExtNetworkSuggestionsFromTargetApp =
1832                 matchingExtNetworkSuggestions.stream()
1833                         .filter(x -> x.perAppInfo.uid == network.creatorUid)
1834                         .collect(Collectors.toSet());
1835         if (matchingExtNetworkSuggestionsFromTargetApp.isEmpty()) {
1836             Log.wtf(TAG, "Current connection failure network suggestion is missing!");
1837             return;
1838         }
1839 
1840         for (ExtendedWifiNetworkSuggestion matchingExtNetworkSuggestion
1841                 : matchingExtNetworkSuggestionsFromTargetApp) {
1842             sendConnectionFailureIfAllowed(matchingExtNetworkSuggestion.perAppInfo.packageName,
1843                     matchingExtNetworkSuggestion.perAppInfo.featureId,
1844                     matchingExtNetworkSuggestion.perAppInfo.uid,
1845                     matchingExtNetworkSuggestion.wns, failureCode);
1846         }
1847     }
1848 
resetConnectionState()1849     private void resetConnectionState() {
1850         mActiveNetworkSuggestionsMatchingConnection = null;
1851     }
1852 
1853     /**
1854      * Invoked by {@link ClientModeImpl} on end of connection attempt to a network.
1855      *
1856      * @param failureCode Failure codes representing {@link WifiMetrics.ConnectionEvent} codes.
1857      * @param network WifiConfiguration corresponding to the current network.
1858      * @param bssid BSSID of the current network.
1859      */
handleConnectionAttemptEnded( int failureCode, @NonNull WifiConfiguration network, @Nullable String bssid)1860     public void handleConnectionAttemptEnded(
1861             int failureCode, @NonNull WifiConfiguration network, @Nullable String bssid) {
1862         if (mVerboseLoggingEnabled) {
1863             Log.v(TAG, "handleConnectionAttemptEnded " + failureCode + ", " + network);
1864         }
1865         resetConnectionState();
1866         if (failureCode == WifiMetrics.ConnectionEvent.FAILURE_NONE) {
1867             handleConnectionSuccess(network, bssid);
1868         } else {
1869             handleConnectionFailure(network, bssid, failureCode);
1870         }
1871     }
1872 
1873     /**
1874      * Invoked by {@link ClientModeImpl} on disconnect from network.
1875      */
handleDisconnect(@onNull WifiConfiguration network, @NonNull String bssid)1876     public void handleDisconnect(@NonNull WifiConfiguration network, @NonNull String bssid) {
1877         if (mVerboseLoggingEnabled) {
1878             Log.v(TAG, "handleDisconnect " + network);
1879         }
1880         resetConnectionState();
1881     }
1882 
1883     /**
1884      * Send network connection failure event to app when an connection attempt failure.
1885      * @param packageName package name to send event
1886      * @param featureId The feature in the package
1887      * @param uid uid of the app.
1888      * @param matchingSuggestion suggestion on this connection failure
1889      * @param connectionEvent connection failure code
1890      */
sendConnectionFailureIfAllowed(String packageName, @Nullable String featureId, int uid, @NonNull WifiNetworkSuggestion matchingSuggestion, int connectionEvent)1891     private void sendConnectionFailureIfAllowed(String packageName, @Nullable String featureId,
1892             int uid, @NonNull WifiNetworkSuggestion matchingSuggestion, int connectionEvent) {
1893         ExternalCallbackTracker<ISuggestionConnectionStatusListener> listenersTracker =
1894                 mSuggestionStatusListenerPerApp.get(packageName);
1895         if (listenersTracker == null || listenersTracker.getNumCallbacks() == 0) {
1896             return;
1897         }
1898         try {
1899             mWifiPermissionsUtil.enforceCanAccessScanResults(
1900                     packageName, featureId, uid, "Connection failure");
1901         } catch (SecurityException se) {
1902             Log.w(TAG, "Permission denied for sending connection failure event to " + packageName);
1903             return;
1904         }
1905         if (mVerboseLoggingEnabled) {
1906             Log.v(TAG, "Sending connection failure event to " + packageName);
1907         }
1908         for (ISuggestionConnectionStatusListener listener : listenersTracker.getCallbacks()) {
1909             try {
1910                 listener.onConnectionStatus(matchingSuggestion,
1911                         internalConnectionEventToSuggestionFailureCode(connectionEvent));
1912             } catch (RemoteException e) {
1913                 Log.e(TAG, "sendNetworkCallback: remote exception -- " + e);
1914             }
1915         }
1916     }
1917 
1918     private @WifiManager.SuggestionConnectionStatusCode
internalConnectionEventToSuggestionFailureCode(int connectionEvent)1919             int internalConnectionEventToSuggestionFailureCode(int connectionEvent) {
1920         switch (connectionEvent) {
1921             case WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_REJECTION:
1922             case WifiMetrics.ConnectionEvent.FAILURE_ASSOCIATION_TIMED_OUT:
1923                 return WifiManager.STATUS_SUGGESTION_CONNECTION_FAILURE_ASSOCIATION;
1924             case WifiMetrics.ConnectionEvent.FAILURE_SSID_TEMP_DISABLED:
1925             case WifiMetrics.ConnectionEvent.FAILURE_AUTHENTICATION_FAILURE:
1926                 return WifiManager.STATUS_SUGGESTION_CONNECTION_FAILURE_AUTHENTICATION;
1927             case WifiMetrics.ConnectionEvent.FAILURE_DHCP:
1928                 return WifiManager.STATUS_SUGGESTION_CONNECTION_FAILURE_IP_PROVISIONING;
1929             default:
1930                 return WifiManager.STATUS_SUGGESTION_CONNECTION_FAILURE_UNKNOWN;
1931         }
1932     }
1933 
1934     /**
1935      * Register a SuggestionConnectionStatusListener on network connection failure.
1936      * @param binder IBinder instance to allow cleanup if the app dies.
1937      * @param listener ISuggestionNetworkCallback instance to add.
1938      * @param listenerIdentifier identifier of the listener, should be hash code of listener.
1939      * @param uid uid of the app.
1940      * @return true if succeed otherwise false.
1941      */
registerSuggestionConnectionStatusListener(@onNull IBinder binder, @NonNull ISuggestionConnectionStatusListener listener, int listenerIdentifier, String packageName, int uid)1942     public boolean registerSuggestionConnectionStatusListener(@NonNull IBinder binder,
1943             @NonNull ISuggestionConnectionStatusListener listener,
1944             int listenerIdentifier, String packageName, int uid) {
1945         if (!mWifiPermissionsUtil.doesUidBelongToCurrentUser(uid)) {
1946             Log.e(TAG, "UID " + uid + " not visible to the current user");
1947             return false;
1948         }
1949         ExternalCallbackTracker<ISuggestionConnectionStatusListener> listenersTracker =
1950                 mSuggestionStatusListenerPerApp.get(packageName);
1951         if (listenersTracker == null) {
1952             listenersTracker =
1953                     new ExternalCallbackTracker<>(mHandler);
1954         }
1955         listenersTracker.add(binder, listener, listenerIdentifier);
1956         mSuggestionStatusListenerPerApp.put(packageName, listenersTracker);
1957         return true;
1958     }
1959 
1960     /**
1961      * Unregister a listener on network connection failure.
1962      * @param listenerIdentifier identifier of the listener, should be hash code of listener.
1963      * @param uid uid of the app.
1964      */
unregisterSuggestionConnectionStatusListener(int listenerIdentifier, String packageName, int uid)1965     public void unregisterSuggestionConnectionStatusListener(int listenerIdentifier,
1966             String packageName, int uid) {
1967         if (!mWifiPermissionsUtil.doesUidBelongToCurrentUser(uid)) {
1968             Log.e(TAG, "UID " + uid + " not visible to the current user");
1969             return;
1970         }
1971         ExternalCallbackTracker<ISuggestionConnectionStatusListener> listenersTracker =
1972                 mSuggestionStatusListenerPerApp.get(packageName);
1973         if (listenersTracker == null || listenersTracker.remove(listenerIdentifier) == null) {
1974             Log.w(TAG, "unregisterSuggestionConnectionStatusListener: Listener["
1975                     + listenerIdentifier + "] from " + packageName + " already unregister.");
1976         }
1977         if (listenersTracker != null && listenersTracker.getNumCallbacks() == 0) {
1978             mSuggestionStatusListenerPerApp.remove(packageName);
1979         }
1980     }
1981 
1982     /**
1983      * When SIM state changes, check if carrier privileges changes for app.
1984      * If app changes from privileged to not privileged, remove all suggestions and reset state.
1985      * If app changes from not privileges to privileged, set target carrier id for all suggestions.
1986      */
resetCarrierPrivilegedApps()1987     public void resetCarrierPrivilegedApps() {
1988         Log.w(TAG, "SIM state is changed!");
1989         for (PerAppInfo appInfo : mActiveNetworkSuggestionsPerApp.values()) {
1990             int carrierId = mWifiCarrierInfoManager
1991                     .getCarrierIdForPackageWithCarrierPrivileges(appInfo.packageName);
1992             if (carrierId == appInfo.carrierId) {
1993                 continue;
1994             }
1995             if (carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) {
1996                 Log.i(TAG, "Carrier privilege revoked for " + appInfo.packageName);
1997                 removeInternal(Collections.EMPTY_LIST, appInfo.packageName, appInfo);
1998                 mActiveNetworkSuggestionsPerApp.remove(appInfo.packageName);
1999                 continue;
2000             }
2001             Log.i(TAG, "Carrier privilege granted for " + appInfo.packageName);
2002             appInfo.carrierId = carrierId;
2003             for (ExtendedWifiNetworkSuggestion ewns : appInfo.extNetworkSuggestions) {
2004                 ewns.wns.wifiConfiguration.carrierId = carrierId;
2005             }
2006         }
2007         saveToStore();
2008     }
2009 
2010     /**
2011      * Set auto-join enable/disable for suggestion network
2012      * @param config WifiConfiguration which is to change.
2013      * @param choice true to enable auto-join, false to disable.
2014      * @return true on success, false otherwise (e.g. if no match suggestion exists).
2015      */
allowNetworkSuggestionAutojoin(WifiConfiguration config, boolean choice)2016     public boolean allowNetworkSuggestionAutojoin(WifiConfiguration config, boolean choice) {
2017         if (!config.fromWifiNetworkSuggestion) {
2018             Log.e(TAG, "allowNetworkSuggestionAutojoin: on non-suggestion network: "
2019                     + config);
2020             return false;
2021         }
2022 
2023         Set<ExtendedWifiNetworkSuggestion> matchingExtendedWifiNetworkSuggestions =
2024                 getNetworkSuggestionsForWifiConfiguration(config, config.BSSID);
2025         if (config.isPasspoint()) {
2026             if (!mWifiInjector.getPasspointManager().enableAutojoin(config.getKey(),
2027                     null, choice)) {
2028                 return false;
2029             }
2030         }
2031         for (ExtendedWifiNetworkSuggestion ewns : matchingExtendedWifiNetworkSuggestions) {
2032             ewns.isAutojoinEnabled = choice;
2033         }
2034         saveToStore();
2035         return true;
2036     }
2037 
2038     /**
2039      * Get the filtered ScanResults which may be authenticated by the suggested configurations.
2040      * @param wifiNetworkSuggestions The list of {@link WifiNetworkSuggestion}
2041      * @param scanResults The list of {@link ScanResult}
2042      * @return The filtered ScanResults
2043      */
2044     @NonNull
getMatchingScanResults( @onNull List<WifiNetworkSuggestion> wifiNetworkSuggestions, @NonNull List<ScanResult> scanResults)2045     public Map<WifiNetworkSuggestion, List<ScanResult>> getMatchingScanResults(
2046             @NonNull List<WifiNetworkSuggestion> wifiNetworkSuggestions,
2047             @NonNull List<ScanResult> scanResults) {
2048         Map<WifiNetworkSuggestion, List<ScanResult>> filteredScanResults = new HashMap<>();
2049         if (wifiNetworkSuggestions == null || wifiNetworkSuggestions.isEmpty()
2050                 || scanResults == null || scanResults.isEmpty()) {
2051             return filteredScanResults;
2052         }
2053         for (WifiNetworkSuggestion suggestion : wifiNetworkSuggestions) {
2054             if (suggestion == null || suggestion.wifiConfiguration == null) {
2055                 continue;
2056             }
2057             filteredScanResults.put(suggestion,
2058                     getMatchingScanResultsForSuggestion(suggestion, scanResults));
2059         }
2060 
2061         return filteredScanResults;
2062     }
2063 
getMatchingScanResultsForSuggestion(WifiNetworkSuggestion suggestion, List<ScanResult> scanResults)2064     private List<ScanResult> getMatchingScanResultsForSuggestion(WifiNetworkSuggestion suggestion,
2065             List<ScanResult> scanResults) {
2066         if (suggestion.passpointConfiguration != null) {
2067             return mWifiInjector.getPasspointManager().getMatchingScanResults(
2068                     suggestion.passpointConfiguration, scanResults);
2069         } else {
2070             return getMatchingScanResults(suggestion.wifiConfiguration, scanResults);
2071         }
2072     }
2073 
2074     /**
2075      * Get the filtered ScanResults which may be authenticated by the {@link WifiConfiguration}.
2076      * @param wifiConfiguration The instance of {@link WifiConfiguration}
2077      * @param scanResults The list of {@link ScanResult}
2078      * @return The filtered ScanResults
2079      */
2080     @NonNull
getMatchingScanResults( @onNull WifiConfiguration wifiConfiguration, @NonNull List<ScanResult> scanResults)2081     private List<ScanResult> getMatchingScanResults(
2082             @NonNull WifiConfiguration wifiConfiguration,
2083             @NonNull List<ScanResult> scanResults) {
2084         ScanResultMatchInfo matchInfoFromConfigration =
2085                 ScanResultMatchInfo.fromWifiConfiguration(wifiConfiguration);
2086         if (matchInfoFromConfigration == null) {
2087             return new ArrayList<>();
2088         }
2089         List<ScanResult> filteredScanResult = new ArrayList<>();
2090         for (ScanResult scanResult : scanResults) {
2091             if (matchInfoFromConfigration.equals(ScanResultMatchInfo.fromScanResult(scanResult))) {
2092                 filteredScanResult.add(scanResult);
2093             }
2094         }
2095 
2096         return filteredScanResult;
2097     }
2098 
2099     /**
2100      * Add the suggestion update event listener
2101      */
addOnSuggestionUpdateListener(OnSuggestionUpdateListener listener)2102     public void addOnSuggestionUpdateListener(OnSuggestionUpdateListener listener) {
2103         mListeners.add(listener);
2104     }
2105 
2106     /**
2107      * When a saved open network has a same network suggestion which is from app has
2108      * NETWORK_CARRIER_PROVISIONING permission, also that app suggested secure network suggestion
2109      * for same carrier with higher or equal priority and Auto-Join enabled, also that secure
2110      * network is in the range. The saved open network will be ignored during the network selection.
2111      * TODO (b/142035508): revert all these changes once we build infra needed to solve this.
2112      * @param configuration Saved open network to check if it should be ignored.
2113      * @param scanDetails Available ScanDetail nearby.
2114      * @return True if the open network should be ignored, false otherwise.
2115      */
shouldBeIgnoredBySecureSuggestionFromSameCarrier( @onNull WifiConfiguration configuration, List<ScanDetail> scanDetails)2116     public boolean shouldBeIgnoredBySecureSuggestionFromSameCarrier(
2117             @NonNull WifiConfiguration configuration, List<ScanDetail> scanDetails) {
2118         if (!mResources.getBoolean(
2119                 R.bool.config_wifiIgnoreOpenSavedNetworkWhenSecureSuggestionAvailable)) {
2120             return false;
2121         }
2122         if (configuration == null || scanDetails == null || !configuration.isOpenNetwork()) {
2123             return false;
2124         }
2125         Set<ExtendedWifiNetworkSuggestion> matchedExtSuggestions =
2126                 getNetworkSuggestionsForWifiConfiguration(configuration, null);
2127         if (matchedExtSuggestions == null || matchedExtSuggestions.isEmpty()) {
2128             return false;
2129         }
2130         matchedExtSuggestions = matchedExtSuggestions.stream().filter(ewns ->
2131                 mWifiPermissionsUtil.checkNetworkCarrierProvisioningPermission(ewns.perAppInfo.uid))
2132                 .collect(Collectors.toSet());
2133         if (matchedExtSuggestions.isEmpty()) {
2134             return false;
2135         }
2136         for (ExtendedWifiNetworkSuggestion ewns : matchedExtSuggestions) {
2137             if (hasSecureSuggestionFromSameCarrierAvailable(ewns, scanDetails)) {
2138                 return true;
2139             }
2140         }
2141         return false;
2142     }
2143 
hasSecureSuggestionFromSameCarrierAvailable( ExtendedWifiNetworkSuggestion extendedWifiNetworkSuggestion, List<ScanDetail> scanDetails)2144     private boolean hasSecureSuggestionFromSameCarrierAvailable(
2145             ExtendedWifiNetworkSuggestion extendedWifiNetworkSuggestion,
2146             List<ScanDetail> scanDetails) {
2147         boolean isOpenSuggestionMetered = WifiConfiguration.isMetered(
2148                 extendedWifiNetworkSuggestion.wns.wifiConfiguration, null);
2149         Set<ExtendedWifiNetworkSuggestion> secureExtSuggestions = new HashSet<>();
2150         for (ExtendedWifiNetworkSuggestion ewns : extendedWifiNetworkSuggestion.perAppInfo
2151                 .extNetworkSuggestions) {
2152             // Open network and auto-join disable suggestion, ignore.
2153             if (isOpenSuggestion(ewns) || !ewns.isAutojoinEnabled) {
2154                 continue;
2155             }
2156             // From different carrier as open suggestion, ignore.
2157             if (getCarrierIdFromSuggestion(ewns)
2158                     != getCarrierIdFromSuggestion(extendedWifiNetworkSuggestion)) {
2159                 continue;
2160             }
2161             // Secure and open has different meterness, ignore
2162             if (WifiConfiguration.isMetered(ewns.wns.wifiConfiguration, null)
2163                     != isOpenSuggestionMetered) {
2164                 continue;
2165             }
2166             // Low priority than open suggestion, ignore.
2167             if (ewns.wns.wifiConfiguration.priority
2168                     < extendedWifiNetworkSuggestion.wns.wifiConfiguration.priority) {
2169                 continue;
2170             }
2171             WifiConfiguration wcmConfig = mWifiConfigManager
2172                     .getConfiguredNetwork(ewns.wns.wifiConfiguration.getKey());
2173             // Network selection is disabled, ignore.
2174             if (wcmConfig != null && !wcmConfig.getNetworkSelectionStatus().isNetworkEnabled()) {
2175                 continue;
2176             }
2177             secureExtSuggestions.add(ewns);
2178         }
2179 
2180         if (secureExtSuggestions.isEmpty()) {
2181             return false;
2182         }
2183         List<ScanResult> scanResults = scanDetails.stream().map(ScanDetail::getScanResult)
2184                 .collect(Collectors.toList());
2185         // Check if the secure suggestion is in the range.
2186         for (ExtendedWifiNetworkSuggestion ewns : secureExtSuggestions) {
2187             if (!getMatchingScanResultsForSuggestion(ewns.wns, scanResults).isEmpty()) {
2188                 return true;
2189             }
2190         }
2191         return false;
2192     }
2193 
isOpenSuggestion(ExtendedWifiNetworkSuggestion extendedWifiNetworkSuggestion)2194     private boolean isOpenSuggestion(ExtendedWifiNetworkSuggestion extendedWifiNetworkSuggestion) {
2195         if (extendedWifiNetworkSuggestion.wns.passpointConfiguration != null) {
2196             return false;
2197         }
2198         return extendedWifiNetworkSuggestion.wns.wifiConfiguration.isOpenNetwork();
2199     }
2200 
2201     /**
2202      * Dump of {@link WifiNetworkSuggestionsManager}.
2203      */
dump(FileDescriptor fd, PrintWriter pw, String[] args)2204     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
2205         pw.println("Dump of WifiNetworkSuggestionsManager");
2206         pw.println("WifiNetworkSuggestionsManager - Networks Begin ----");
2207         final String activeScorerPackage = mNetworkScoreManager.getActiveScorerPackage();
2208         for (Map.Entry<String, PerAppInfo> networkSuggestionsEntry
2209                 : mActiveNetworkSuggestionsPerApp.entrySet()) {
2210             pw.println("Package Name: " + networkSuggestionsEntry.getKey());
2211             PerAppInfo appInfo = networkSuggestionsEntry.getValue();
2212             pw.println("Has user approved: " + appInfo.hasUserApproved);
2213             pw.println("Has carrier privileges: "
2214                     + (appInfo.carrierId != TelephonyManager.UNKNOWN_CARRIER_ID));
2215             pw.println("Is active scorer: " + appInfo.packageName.equals(activeScorerPackage));
2216             for (ExtendedWifiNetworkSuggestion extNetworkSuggestion
2217                     : appInfo.extNetworkSuggestions) {
2218                 pw.println("Network: " + extNetworkSuggestion);
2219             }
2220         }
2221         pw.println("WifiNetworkSuggestionsManager - Networks End ----");
2222         pw.println("WifiNetworkSuggestionsManager - Network Suggestions matching connection: "
2223                 + mActiveNetworkSuggestionsMatchingConnection);
2224     }
2225 }
2226