Home | History | Annotate | Download | only in wifi
      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 com.android.server.wifi.ConnectToNetworkNotificationBuilder.ACTION_CONNECT_TO_NETWORK;
     20 import static com.android.server.wifi.ConnectToNetworkNotificationBuilder.ACTION_PICK_WIFI_NETWORK;
     21 import static com.android.server.wifi.ConnectToNetworkNotificationBuilder.ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE;
     22 import static com.android.server.wifi.ConnectToNetworkNotificationBuilder.ACTION_USER_DISMISSED_NOTIFICATION;
     23 import static com.android.server.wifi.ConnectToNetworkNotificationBuilder.AVAILABLE_NETWORK_NOTIFIER_TAG;
     24 
     25 import android.annotation.IntDef;
     26 import android.annotation.NonNull;
     27 import android.app.Notification;
     28 import android.app.NotificationManager;
     29 import android.content.BroadcastReceiver;
     30 import android.content.Context;
     31 import android.content.Intent;
     32 import android.content.IntentFilter;
     33 import android.database.ContentObserver;
     34 import android.net.wifi.ScanResult;
     35 import android.net.wifi.WifiConfiguration;
     36 import android.net.wifi.WifiManager;
     37 import android.os.Handler;
     38 import android.os.Looper;
     39 import android.os.Message;
     40 import android.os.Messenger;
     41 import android.os.UserHandle;
     42 import android.os.UserManager;
     43 import android.provider.Settings;
     44 import android.text.TextUtils;
     45 import android.util.ArraySet;
     46 import android.util.Log;
     47 
     48 import com.android.internal.annotations.VisibleForTesting;
     49 import com.android.server.wifi.nano.WifiMetricsProto.ConnectToNetworkNotificationAndActionCount;
     50 import com.android.server.wifi.util.ScanResultUtil;
     51 
     52 import java.io.FileDescriptor;
     53 import java.io.PrintWriter;
     54 import java.lang.annotation.Retention;
     55 import java.lang.annotation.RetentionPolicy;
     56 import java.util.List;
     57 import java.util.Set;
     58 
     59 /**
     60  * Base class for all network notifiers (e.g. OpenNetworkNotifier, CarrierNetworkNotifier).
     61  *
     62  * NOTE: These API's are not thread safe and should only be used from WifiStateMachine thread.
     63  */
     64 public class AvailableNetworkNotifier {
     65 
     66     /** Time in milliseconds to display the Connecting notification. */
     67     private static final int TIME_TO_SHOW_CONNECTING_MILLIS = 10000;
     68 
     69     /** Time in milliseconds to display the Connected notification. */
     70     private static final int TIME_TO_SHOW_CONNECTED_MILLIS = 5000;
     71 
     72     /** Time in milliseconds to display the Failed To Connect notification. */
     73     private static final int TIME_TO_SHOW_FAILED_MILLIS = 5000;
     74 
     75     /** The state of the notification */
     76     @IntDef({
     77             STATE_NO_NOTIFICATION,
     78             STATE_SHOWING_RECOMMENDATION_NOTIFICATION,
     79             STATE_CONNECTING_IN_NOTIFICATION,
     80             STATE_CONNECTED_NOTIFICATION,
     81             STATE_CONNECT_FAILED_NOTIFICATION
     82     })
     83     @Retention(RetentionPolicy.SOURCE)
     84     private @interface State {}
     85 
     86     /** No recommendation is made and no notifications are shown. */
     87     private static final int STATE_NO_NOTIFICATION = 0;
     88     /** The initial notification recommending a network to connect to is shown. */
     89     private static final int STATE_SHOWING_RECOMMENDATION_NOTIFICATION = 1;
     90     /** The notification of status of connecting to the recommended network is shown. */
     91     private static final int STATE_CONNECTING_IN_NOTIFICATION = 2;
     92     /** The notification that the connection to the recommended network was successful is shown. */
     93     private static final int STATE_CONNECTED_NOTIFICATION = 3;
     94     /** The notification to show that connection to the recommended network failed is shown. */
     95     private static final int STATE_CONNECT_FAILED_NOTIFICATION = 4;
     96 
     97     /** Current state of the notification. */
     98     @State private int mState = STATE_NO_NOTIFICATION;
     99 
    100     /**
    101      * The {@link Clock#getWallClockMillis()} must be at least this value for us
    102      * to show the notification again.
    103      */
    104     private long mNotificationRepeatTime;
    105     /**
    106      * When a notification is shown, we wait this amount before possibly showing it again.
    107      */
    108     private final long mNotificationRepeatDelay;
    109     /** Default repeat delay in seconds. */
    110     @VisibleForTesting
    111     static final int DEFAULT_REPEAT_DELAY_SEC = 900;
    112 
    113     /** Whether the user has set the setting to show the 'available networks' notification. */
    114     private boolean mSettingEnabled;
    115     /** Whether the screen is on or not. */
    116     private boolean mScreenOn;
    117 
    118     /** List of SSIDs blacklisted from recommendation. */
    119     private final Set<String> mBlacklistedSsids;
    120 
    121     private final Context mContext;
    122     private final Handler mHandler;
    123     private final FrameworkFacade mFrameworkFacade;
    124     private final WifiMetrics mWifiMetrics;
    125     private final Clock mClock;
    126     private final WifiConfigManager mConfigManager;
    127     private final WifiStateMachine mWifiStateMachine;
    128     private final Messenger mSrcMessenger;
    129     private final ConnectToNetworkNotificationBuilder mNotificationBuilder;
    130 
    131     private ScanResult mRecommendedNetwork;
    132 
    133     /** Tag used for logs and metrics */
    134     private final String mTag;
    135     /** Identifier of the {@link SsidSetStoreData}. */
    136     private final String mStoreDataIdentifier;
    137     /** Identifier for the settings toggle, used for registering ContentObserver */
    138     private final String mToggleSettingsName;
    139 
    140     /** System wide identifier for notification in Notification Manager */
    141     private final int mSystemMessageNotificationId;
    142 
    143     public AvailableNetworkNotifier(
    144             String tag,
    145             String storeDataIdentifier,
    146             String toggleSettingsName,
    147             int notificationIdentifier,
    148             Context context,
    149             Looper looper,
    150             FrameworkFacade framework,
    151             Clock clock,
    152             WifiMetrics wifiMetrics,
    153             WifiConfigManager wifiConfigManager,
    154             WifiConfigStore wifiConfigStore,
    155             WifiStateMachine wifiStateMachine,
    156             ConnectToNetworkNotificationBuilder connectToNetworkNotificationBuilder) {
    157         mTag = tag;
    158         mStoreDataIdentifier = storeDataIdentifier;
    159         mToggleSettingsName = toggleSettingsName;
    160         mSystemMessageNotificationId = notificationIdentifier;
    161         mContext = context;
    162         mHandler = new Handler(looper);
    163         mFrameworkFacade = framework;
    164         mWifiMetrics = wifiMetrics;
    165         mClock = clock;
    166         mConfigManager = wifiConfigManager;
    167         mWifiStateMachine = wifiStateMachine;
    168         mNotificationBuilder = connectToNetworkNotificationBuilder;
    169         mScreenOn = false;
    170         mSrcMessenger = new Messenger(new Handler(looper, mConnectionStateCallback));
    171 
    172         mBlacklistedSsids = new ArraySet<>();
    173         wifiConfigStore.registerStoreData(new SsidSetStoreData(mStoreDataIdentifier,
    174                 new AvailableNetworkNotifierStoreData()));
    175 
    176         // Setting is in seconds
    177         mNotificationRepeatDelay = mFrameworkFacade.getIntegerSetting(context,
    178                 Settings.Global.WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY,
    179                 DEFAULT_REPEAT_DELAY_SEC) * 1000L;
    180         NotificationEnabledSettingObserver settingObserver = new NotificationEnabledSettingObserver(
    181                 mHandler);
    182         settingObserver.register();
    183 
    184         IntentFilter filter = new IntentFilter();
    185         filter.addAction(ACTION_USER_DISMISSED_NOTIFICATION);
    186         filter.addAction(ACTION_CONNECT_TO_NETWORK);
    187         filter.addAction(ACTION_PICK_WIFI_NETWORK);
    188         filter.addAction(ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE);
    189         mContext.registerReceiver(
    190                 mBroadcastReceiver, filter, null /* broadcastPermission */, mHandler);
    191     }
    192 
    193     private final BroadcastReceiver mBroadcastReceiver =
    194             new BroadcastReceiver() {
    195                 @Override
    196                 public void onReceive(Context context, Intent intent) {
    197                     if (!mTag.equals(intent.getExtra(AVAILABLE_NETWORK_NOTIFIER_TAG))) {
    198                         return;
    199                     }
    200                     switch (intent.getAction()) {
    201                         case ACTION_USER_DISMISSED_NOTIFICATION:
    202                             handleUserDismissedAction();
    203                             break;
    204                         case ACTION_CONNECT_TO_NETWORK:
    205                             handleConnectToNetworkAction();
    206                             break;
    207                         case ACTION_PICK_WIFI_NETWORK:
    208                             handleSeeAllNetworksAction();
    209                             break;
    210                         case ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE:
    211                             handlePickWifiNetworkAfterConnectFailure();
    212                             break;
    213                         default:
    214                             Log.e(mTag, "Unknown action " + intent.getAction());
    215                     }
    216                 }
    217             };
    218 
    219     private final Handler.Callback mConnectionStateCallback = (Message msg) -> {
    220         switch (msg.what) {
    221             // Success here means that an attempt to connect to the network has been initiated.
    222             // Successful connection updates are received via the
    223             // WifiConnectivityManager#handleConnectionStateChanged() callback.
    224             case WifiManager.CONNECT_NETWORK_SUCCEEDED:
    225                 break;
    226             case WifiManager.CONNECT_NETWORK_FAILED:
    227                 handleConnectionAttemptFailedToSend();
    228                 break;
    229             default:
    230                 Log.e("AvailableNetworkNotifier", "Unknown message " + msg.what);
    231         }
    232         return true;
    233     };
    234 
    235     /**
    236      * Clears the pending notification. This is called by {@link WifiConnectivityManager} on stop.
    237      *
    238      * @param resetRepeatTime resets the time delay for repeated notification if true.
    239      */
    240     public void clearPendingNotification(boolean resetRepeatTime) {
    241         if (resetRepeatTime) {
    242             mNotificationRepeatTime = 0;
    243         }
    244 
    245         if (mState != STATE_NO_NOTIFICATION) {
    246             getNotificationManager().cancel(mSystemMessageNotificationId);
    247 
    248             if (mRecommendedNetwork != null) {
    249                 Log.d(mTag, "Notification with state="
    250                         + mState
    251                         + " was cleared for recommended network: "
    252                         + mRecommendedNetwork.SSID);
    253             }
    254             mState = STATE_NO_NOTIFICATION;
    255             mRecommendedNetwork = null;
    256         }
    257     }
    258 
    259     private boolean isControllerEnabled() {
    260         return mSettingEnabled && !UserManager.get(mContext)
    261                 .hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, UserHandle.CURRENT);
    262     }
    263 
    264     /**
    265      * If there are available networks, attempt to post a network notification.
    266      *
    267      * @param availableNetworks Available networks to choose from and possibly show notification
    268      */
    269     public void handleScanResults(@NonNull List<ScanDetail> availableNetworks) {
    270         if (!isControllerEnabled()) {
    271             clearPendingNotification(true /* resetRepeatTime */);
    272             return;
    273         }
    274         if (availableNetworks.isEmpty() && mState == STATE_SHOWING_RECOMMENDATION_NOTIFICATION) {
    275             clearPendingNotification(false /* resetRepeatTime */);
    276             return;
    277         }
    278 
    279         // Not enough time has passed to show a recommendation notification again
    280         if (mState == STATE_NO_NOTIFICATION
    281                 && mClock.getWallClockMillis() < mNotificationRepeatTime) {
    282             return;
    283         }
    284 
    285         // Do nothing when the screen is off and no notification is showing.
    286         if (mState == STATE_NO_NOTIFICATION && !mScreenOn) {
    287             return;
    288         }
    289 
    290         // Only show a new or update an existing recommendation notification.
    291         if (mState == STATE_NO_NOTIFICATION
    292                 || mState == STATE_SHOWING_RECOMMENDATION_NOTIFICATION) {
    293             ScanResult recommendation =
    294                     recommendNetwork(availableNetworks, new ArraySet<>(mBlacklistedSsids));
    295 
    296             if (recommendation != null) {
    297                 postInitialNotification(recommendation);
    298             } else {
    299                 clearPendingNotification(false /* resetRepeatTime */);
    300             }
    301         }
    302     }
    303 
    304     /**
    305      * Recommends a network to connect to from a list of available networks, while ignoring the
    306      * SSIDs in the blacklist.
    307      */
    308     public ScanResult recommendNetwork(@NonNull List<ScanDetail> networks,
    309             @NonNull Set<String> blacklistedSsids) {
    310         ScanResult result = null;
    311         int highestRssi = Integer.MIN_VALUE;
    312         for (ScanDetail scanDetail : networks) {
    313             ScanResult scanResult = scanDetail.getScanResult();
    314 
    315             if (scanResult.level > highestRssi) {
    316                 result = scanResult;
    317                 highestRssi = scanResult.level;
    318             }
    319         }
    320 
    321         if (result != null && blacklistedSsids.contains(result.SSID)) {
    322             result = null;
    323         }
    324         return result;
    325     }
    326 
    327     /** Handles screen state changes. */
    328     public void handleScreenStateChanged(boolean screenOn) {
    329         mScreenOn = screenOn;
    330     }
    331 
    332     /**
    333      * Called by {@link WifiConnectivityManager} when Wi-Fi is connected. If the notification
    334      * was in the connecting state, update the notification to show that it has connected to the
    335      * recommended network.
    336      */
    337     public void handleWifiConnected() {
    338         if (mState != STATE_CONNECTING_IN_NOTIFICATION) {
    339             clearPendingNotification(true /* resetRepeatTime */);
    340             return;
    341         }
    342 
    343         postNotification(mNotificationBuilder.createNetworkConnectedNotification(mTag,
    344                 mRecommendedNetwork));
    345 
    346         Log.d(mTag, "User connected to recommended network: " + mRecommendedNetwork.SSID);
    347         mWifiMetrics.incrementConnectToNetworkNotification(mTag,
    348                 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_CONNECTED_TO_NETWORK);
    349         mState = STATE_CONNECTED_NOTIFICATION;
    350         mHandler.postDelayed(
    351                 () -> {
    352                     if (mState == STATE_CONNECTED_NOTIFICATION) {
    353                         clearPendingNotification(true /* resetRepeatTime */);
    354                     }
    355                 },
    356                 TIME_TO_SHOW_CONNECTED_MILLIS);
    357     }
    358 
    359     /**
    360      * Handles when a Wi-Fi connection attempt failed.
    361      */
    362     public void handleConnectionFailure() {
    363         if (mState != STATE_CONNECTING_IN_NOTIFICATION) {
    364             return;
    365         }
    366         postNotification(mNotificationBuilder.createNetworkFailedNotification(mTag));
    367 
    368         Log.d(mTag, "User failed to connect to recommended network: " + mRecommendedNetwork.SSID);
    369         mWifiMetrics.incrementConnectToNetworkNotification(mTag,
    370                 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_FAILED_TO_CONNECT);
    371         mState = STATE_CONNECT_FAILED_NOTIFICATION;
    372         mHandler.postDelayed(
    373                 () -> {
    374                     if (mState == STATE_CONNECT_FAILED_NOTIFICATION) {
    375                         clearPendingNotification(false /* resetRepeatTime */);
    376                     }
    377                 },
    378                 TIME_TO_SHOW_FAILED_MILLIS);
    379     }
    380 
    381     private NotificationManager getNotificationManager() {
    382         return (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
    383     }
    384 
    385     private void postInitialNotification(ScanResult recommendedNetwork) {
    386         if (mRecommendedNetwork != null
    387                 && TextUtils.equals(mRecommendedNetwork.SSID, recommendedNetwork.SSID)) {
    388             return;
    389         }
    390 
    391         postNotification(mNotificationBuilder.createConnectToAvailableNetworkNotification(mTag,
    392                 recommendedNetwork));
    393 
    394         if (mState == STATE_NO_NOTIFICATION) {
    395             mWifiMetrics.incrementConnectToNetworkNotification(mTag,
    396                     ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
    397         } else {
    398             mWifiMetrics.incrementNumNetworkRecommendationUpdates(mTag);
    399         }
    400         mState = STATE_SHOWING_RECOMMENDATION_NOTIFICATION;
    401         mRecommendedNetwork = recommendedNetwork;
    402         mNotificationRepeatTime = mClock.getWallClockMillis() + mNotificationRepeatDelay;
    403     }
    404 
    405     private void postNotification(Notification notification) {
    406         getNotificationManager().notify(mSystemMessageNotificationId, notification);
    407     }
    408 
    409     private void handleConnectToNetworkAction() {
    410         mWifiMetrics.incrementConnectToNetworkNotificationAction(mTag, mState,
    411                 ConnectToNetworkNotificationAndActionCount.ACTION_CONNECT_TO_NETWORK);
    412         if (mState != STATE_SHOWING_RECOMMENDATION_NOTIFICATION) {
    413             return;
    414         }
    415         postNotification(mNotificationBuilder.createNetworkConnectingNotification(mTag,
    416                 mRecommendedNetwork));
    417         mWifiMetrics.incrementConnectToNetworkNotification(mTag,
    418                 ConnectToNetworkNotificationAndActionCount.NOTIFICATION_CONNECTING_TO_NETWORK);
    419 
    420         Log.d(mTag,
    421                 "User initiated connection to recommended network: " + mRecommendedNetwork.SSID);
    422         WifiConfiguration network = createRecommendedNetworkConfig(mRecommendedNetwork);
    423         Message msg = Message.obtain();
    424         msg.what = WifiManager.CONNECT_NETWORK;
    425         msg.arg1 = WifiConfiguration.INVALID_NETWORK_ID;
    426         msg.obj = network;
    427         msg.replyTo = mSrcMessenger;
    428         mWifiStateMachine.sendMessage(msg);
    429 
    430         mState = STATE_CONNECTING_IN_NOTIFICATION;
    431         mHandler.postDelayed(
    432                 () -> {
    433                     if (mState == STATE_CONNECTING_IN_NOTIFICATION) {
    434                         handleConnectionFailure();
    435                     }
    436                 },
    437                 TIME_TO_SHOW_CONNECTING_MILLIS);
    438     }
    439 
    440     WifiConfiguration createRecommendedNetworkConfig(ScanResult recommendedNetwork) {
    441         return ScanResultUtil.createNetworkFromScanResult(recommendedNetwork);
    442     }
    443 
    444     private void handleSeeAllNetworksAction() {
    445         mWifiMetrics.incrementConnectToNetworkNotificationAction(mTag, mState,
    446                 ConnectToNetworkNotificationAndActionCount.ACTION_PICK_WIFI_NETWORK);
    447         startWifiSettings();
    448     }
    449 
    450     private void startWifiSettings() {
    451         // Close notification drawer before opening the picker.
    452         mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
    453         mContext.startActivity(
    454                 new Intent(Settings.ACTION_WIFI_SETTINGS)
    455                         .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
    456         clearPendingNotification(false /* resetRepeatTime */);
    457     }
    458 
    459     private void handleConnectionAttemptFailedToSend() {
    460         handleConnectionFailure();
    461         mWifiMetrics.incrementNumNetworkConnectMessageFailedToSend(mTag);
    462     }
    463 
    464     private void handlePickWifiNetworkAfterConnectFailure() {
    465         mWifiMetrics.incrementConnectToNetworkNotificationAction(mTag, mState,
    466                 ConnectToNetworkNotificationAndActionCount
    467                         .ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE);
    468         startWifiSettings();
    469     }
    470 
    471     private void handleUserDismissedAction() {
    472         Log.d(mTag, "User dismissed notification with state=" + mState);
    473         mWifiMetrics.incrementConnectToNetworkNotificationAction(mTag, mState,
    474                 ConnectToNetworkNotificationAndActionCount.ACTION_USER_DISMISSED_NOTIFICATION);
    475         if (mState == STATE_SHOWING_RECOMMENDATION_NOTIFICATION) {
    476             // blacklist dismissed network
    477             mBlacklistedSsids.add(mRecommendedNetwork.SSID);
    478             mWifiMetrics.setNetworkRecommenderBlacklistSize(mTag, mBlacklistedSsids.size());
    479             mConfigManager.saveToStore(false /* forceWrite */);
    480             Log.d(mTag, "Network is added to the network notification blacklist: "
    481                     + mRecommendedNetwork.SSID);
    482         }
    483         resetStateAndDelayNotification();
    484     }
    485 
    486     private void resetStateAndDelayNotification() {
    487         mState = STATE_NO_NOTIFICATION;
    488         mNotificationRepeatTime = System.currentTimeMillis() + mNotificationRepeatDelay;
    489         mRecommendedNetwork = null;
    490     }
    491 
    492     /** Dump this network notifier's state. */
    493     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    494         pw.println(mTag + ": ");
    495         pw.println("mSettingEnabled " + mSettingEnabled);
    496         pw.println("currentTime: " + mClock.getWallClockMillis());
    497         pw.println("mNotificationRepeatTime: " + mNotificationRepeatTime);
    498         pw.println("mState: " + mState);
    499         pw.println("mBlacklistedSsids: " + mBlacklistedSsids.toString());
    500     }
    501 
    502     private class AvailableNetworkNotifierStoreData implements SsidSetStoreData.DataSource {
    503         @Override
    504         public Set<String> getSsids() {
    505             return new ArraySet<>(mBlacklistedSsids);
    506         }
    507 
    508         @Override
    509         public void setSsids(Set<String> ssidList) {
    510             mBlacklistedSsids.addAll(ssidList);
    511             mWifiMetrics.setNetworkRecommenderBlacklistSize(mTag, mBlacklistedSsids.size());
    512         }
    513     }
    514 
    515     private class NotificationEnabledSettingObserver extends ContentObserver {
    516         NotificationEnabledSettingObserver(Handler handler) {
    517             super(handler);
    518         }
    519 
    520         public void register() {
    521             mFrameworkFacade.registerContentObserver(mContext,
    522                     Settings.Global.getUriFor(mToggleSettingsName), true, this);
    523             mSettingEnabled = getValue();
    524         }
    525 
    526         @Override
    527         public void onChange(boolean selfChange) {
    528             super.onChange(selfChange);
    529             mSettingEnabled = getValue();
    530             clearPendingNotification(true /* resetRepeatTime */);
    531         }
    532 
    533         private boolean getValue() {
    534             boolean enabled =
    535                     mFrameworkFacade.getIntegerSetting(mContext, mToggleSettingsName, 1) == 1;
    536             mWifiMetrics.setIsWifiNetworksAvailableNotificationEnabled(mTag, enabled);
    537             Log.d(mTag, "Settings toggle enabled=" + enabled);
    538             return enabled;
    539         }
    540     }
    541 }
    542