Home | History | Annotate | Download | only in wifi
      1 /*
      2  * Copyright (C) 2011 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 android.net.wifi;
     18 
     19 import android.app.Notification;
     20 import android.app.NotificationManager;
     21 import android.app.PendingIntent;
     22 import android.content.BroadcastReceiver;
     23 import android.content.ContentResolver;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.content.IntentFilter;
     27 import android.content.res.Resources;
     28 import android.database.ContentObserver;
     29 import android.net.ConnectivityManager;
     30 import android.net.DnsPinger;
     31 import android.net.NetworkInfo;
     32 import android.net.Uri;
     33 import android.os.Message;
     34 import android.os.SystemClock;
     35 import android.os.SystemProperties;
     36 import android.provider.Settings;
     37 import android.provider.Settings.Secure;
     38 import android.util.Log;
     39 
     40 import com.android.internal.R;
     41 import com.android.internal.util.Protocol;
     42 import com.android.internal.util.State;
     43 import com.android.internal.util.StateMachine;
     44 
     45 import java.io.IOException;
     46 import java.io.PrintWriter;
     47 import java.net.HttpURLConnection;
     48 import java.net.InetAddress;
     49 import java.net.URL;
     50 import java.util.HashMap;
     51 import java.util.HashSet;
     52 import java.util.List;
     53 
     54 /**
     55  * {@link WifiWatchdogStateMachine} monitors the initial connection to a Wi-Fi
     56  * network with multiple access points. After the framework successfully
     57  * connects to an access point, the watchdog verifies connectivity by 'pinging'
     58  * the configured DNS server using {@link DnsPinger}.
     59  * <p>
     60  * On DNS check failure, the BSSID is blacklisted if it is reasonably likely
     61  * that another AP might have internet access; otherwise the SSID is disabled.
     62  * <p>
     63  * On DNS success, the WatchdogService initiates a walled garden check via an
     64  * http get. A browser window is activated if a walled garden is detected.
     65  *
     66  * @hide
     67  */
     68 public class WifiWatchdogStateMachine extends StateMachine {
     69 
     70     private static final boolean DBG = false;
     71     private static final String TAG = "WifiWatchdogStateMachine";
     72     private static final String DISABLED_NETWORK_NOTIFICATION_ID = "WifiWatchdog.networkdisabled";
     73     private static final String WALLED_GARDEN_NOTIFICATION_ID = "WifiWatchdog.walledgarden";
     74 
     75     private static final int WIFI_SIGNAL_LEVELS = 4;
     76     /**
     77      * Low signal is defined as less than or equal to cut off
     78      */
     79     private static final int LOW_SIGNAL_CUTOFF = 0;
     80 
     81     private static final long DEFAULT_DNS_CHECK_SHORT_INTERVAL_MS = 2 * 60 * 1000;
     82     private static final long DEFAULT_DNS_CHECK_LONG_INTERVAL_MS = 60 * 60 * 1000;
     83     private static final long DEFAULT_WALLED_GARDEN_INTERVAL_MS = 30 * 60 * 1000;
     84 
     85     private static final int DEFAULT_MAX_SSID_BLACKLISTS = 7;
     86     private static final int DEFAULT_NUM_DNS_PINGS = 5; // Multiple pings to detect setup issues
     87     private static final int DEFAULT_MIN_DNS_RESPONSES = 1;
     88 
     89     private static final int DEFAULT_DNS_PING_TIMEOUT_MS = 2000;
     90 
     91     private static final long DEFAULT_BLACKLIST_FOLLOWUP_INTERVAL_MS = 15 * 1000;
     92 
     93     // See http://go/clientsdns for usage approval
     94     private static final String DEFAULT_WALLED_GARDEN_URL =
     95             "http://clients3.google.com/generate_204";
     96     private static final int WALLED_GARDEN_SOCKET_TIMEOUT_MS = 10000;
     97 
     98     /* Some carrier apps might have support captive portal handling. Add some delay to allow
     99         app authentication to be done before our test.
    100        TODO: This should go away once we provide an API to apps to disable walled garden test
    101        for certain SSIDs
    102      */
    103     private static final int WALLED_GARDEN_START_DELAY_MS = 3000;
    104 
    105     private static final int DNS_INTRATEST_PING_INTERVAL_MS = 200;
    106     /* With some router setups, it takes a few hunder milli-seconds before connection is active */
    107     private static final int DNS_START_DELAY_MS = 1000;
    108 
    109     private static final int BASE = Protocol.BASE_WIFI_WATCHDOG;
    110 
    111     /**
    112      * Indicates the enable setting of WWS may have changed
    113      */
    114     private static final int EVENT_WATCHDOG_TOGGLED                 = BASE + 1;
    115 
    116     /**
    117      * Indicates the wifi network state has changed. Passed w/ original intent
    118      * which has a non-null networkInfo object
    119      */
    120     private static final int EVENT_NETWORK_STATE_CHANGE             = BASE + 2;
    121     /**
    122      * Indicates the signal has changed. Passed with arg1
    123      * {@link #mNetEventCounter} and arg2 [raw signal strength]
    124      */
    125     private static final int EVENT_RSSI_CHANGE                      = BASE + 3;
    126     private static final int EVENT_SCAN_RESULTS_AVAILABLE           = BASE + 4;
    127     private static final int EVENT_WIFI_RADIO_STATE_CHANGE          = BASE + 5;
    128     private static final int EVENT_WATCHDOG_SETTINGS_CHANGE         = BASE + 6;
    129 
    130     private static final int MESSAGE_HANDLE_WALLED_GARDEN           = BASE + 100;
    131     private static final int MESSAGE_HANDLE_BAD_AP                  = BASE + 101;
    132     /**
    133      * arg1 == mOnlineWatchState.checkCount
    134      */
    135     private static final int MESSAGE_SINGLE_DNS_CHECK               = BASE + 102;
    136     private static final int MESSAGE_NETWORK_FOLLOWUP               = BASE + 103;
    137     private static final int MESSAGE_DELAYED_WALLED_GARDEN_CHECK    = BASE + 104;
    138 
    139     private Context mContext;
    140     private ContentResolver mContentResolver;
    141     private WifiManager mWifiManager;
    142     private DnsPinger mDnsPinger;
    143     private IntentFilter mIntentFilter;
    144     private BroadcastReceiver mBroadcastReceiver;
    145 
    146     private DefaultState mDefaultState = new DefaultState();
    147     private WatchdogDisabledState mWatchdogDisabledState = new WatchdogDisabledState();
    148     private WatchdogEnabledState mWatchdogEnabledState = new WatchdogEnabledState();
    149     private NotConnectedState mNotConnectedState = new NotConnectedState();
    150     private ConnectedState mConnectedState = new ConnectedState();
    151     private DnsCheckingState mDnsCheckingState = new DnsCheckingState();
    152     private OnlineWatchState mOnlineWatchState = new OnlineWatchState();
    153     private OnlineState mOnlineState = new OnlineState();
    154     private DnsCheckFailureState mDnsCheckFailureState = new DnsCheckFailureState();
    155     private DelayWalledGardenState mDelayWalledGardenState = new DelayWalledGardenState();
    156     private WalledGardenState mWalledGardenState = new WalledGardenState();
    157     private BlacklistedApState mBlacklistedApState = new BlacklistedApState();
    158 
    159     private long mDnsCheckShortIntervalMs;
    160     private long mDnsCheckLongIntervalMs;
    161     private long mWalledGardenIntervalMs;
    162     private int mMaxSsidBlacklists;
    163     private int mNumDnsPings;
    164     private int mMinDnsResponses;
    165     private int mDnsPingTimeoutMs;
    166     private long mBlacklistFollowupIntervalMs;
    167     private boolean mPoorNetworkDetectionEnabled;
    168     private boolean mWalledGardenTestEnabled;
    169     private String mWalledGardenUrl;
    170 
    171     private boolean mShowDisabledNotification;
    172     /**
    173      * The {@link WifiInfo} object passed to WWSM on network broadcasts
    174      */
    175     private WifiInfo mConnectionInfo;
    176     private int mNetEventCounter = 0;
    177 
    178     /**
    179      * Currently maintained but not used, TODO
    180      */
    181     private HashSet<String> mBssids = new HashSet<String>();
    182     private int mNumCheckFailures = 0;
    183 
    184     private Long mLastWalledGardenCheckTime = null;
    185 
    186     /**
    187      * This is set by the blacklisted state and reset when connected to a new AP.
    188      * It triggers a disableNetwork call if a DNS check fails.
    189      */
    190     public boolean mDisableAPNextFailure = false;
    191     private static boolean sWifiOnly = false;
    192     private boolean mDisabledNotificationShown;
    193     private boolean mWalledGardenNotificationShown;
    194     public boolean mHasConnectedWifiManager = false;
    195 
    196     /**
    197      * STATE MAP
    198      *          Default
    199      *         /       \
    200      * Disabled     Enabled
    201      *             /       \
    202      * NotConnected      Connected
    203      *                  /---------\
    204      *               (all other states)
    205      */
    206     private WifiWatchdogStateMachine(Context context) {
    207         super(TAG);
    208         mContext = context;
    209         mContentResolver = context.getContentResolver();
    210         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
    211         mDnsPinger = new DnsPinger(mContext, "WifiWatchdogStateMachine.DnsPinger",
    212                                 this.getHandler().getLooper(), this.getHandler(),
    213                                 ConnectivityManager.TYPE_WIFI);
    214 
    215         setupNetworkReceiver();
    216 
    217         // The content observer to listen needs a handler
    218         registerForSettingsChanges();
    219         registerForWatchdogToggle();
    220         addState(mDefaultState);
    221             addState(mWatchdogDisabledState, mDefaultState);
    222             addState(mWatchdogEnabledState, mDefaultState);
    223                 addState(mNotConnectedState, mWatchdogEnabledState);
    224                 addState(mConnectedState, mWatchdogEnabledState);
    225                     addState(mDnsCheckingState, mConnectedState);
    226                     addState(mDnsCheckFailureState, mConnectedState);
    227                     addState(mDelayWalledGardenState, mConnectedState);
    228                     addState(mWalledGardenState, mConnectedState);
    229                     addState(mBlacklistedApState, mConnectedState);
    230                     addState(mOnlineWatchState, mConnectedState);
    231                     addState(mOnlineState, mConnectedState);
    232 
    233         setInitialState(mWatchdogDisabledState);
    234         updateSettings();
    235     }
    236 
    237     public static WifiWatchdogStateMachine makeWifiWatchdogStateMachine(Context context) {
    238         ContentResolver contentResolver = context.getContentResolver();
    239 
    240         ConnectivityManager cm = (ConnectivityManager) context.getSystemService(
    241                 Context.CONNECTIVITY_SERVICE);
    242         sWifiOnly = (cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) == false);
    243 
    244         // Disable for wifi only devices.
    245         if (Settings.Secure.getString(contentResolver, Settings.Secure.WIFI_WATCHDOG_ON) == null &&
    246                 sWifiOnly) {
    247             putSettingsBoolean(contentResolver, Settings.Secure.WIFI_WATCHDOG_ON, false);
    248         }
    249         WifiWatchdogStateMachine wwsm = new WifiWatchdogStateMachine(context);
    250         wwsm.start();
    251         wwsm.sendMessage(EVENT_WATCHDOG_TOGGLED);
    252         return wwsm;
    253     }
    254 
    255     /**
    256    *
    257    */
    258     private void setupNetworkReceiver() {
    259         mBroadcastReceiver = new BroadcastReceiver() {
    260             @Override
    261             public void onReceive(Context context, Intent intent) {
    262                 String action = intent.getAction();
    263                 if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
    264                     sendMessage(EVENT_NETWORK_STATE_CHANGE, intent);
    265                 } else if (action.equals(WifiManager.RSSI_CHANGED_ACTION)) {
    266                     obtainMessage(EVENT_RSSI_CHANGE, mNetEventCounter,
    267                             intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -200)).sendToTarget();
    268                 } else if (action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
    269                     sendMessage(EVENT_SCAN_RESULTS_AVAILABLE);
    270                 } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
    271                     sendMessage(EVENT_WIFI_RADIO_STATE_CHANGE,
    272                             intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
    273                                     WifiManager.WIFI_STATE_UNKNOWN));
    274                 }
    275             }
    276         };
    277 
    278         mIntentFilter = new IntentFilter();
    279         mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
    280         mIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
    281         mIntentFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
    282         mIntentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
    283     }
    284 
    285     /**
    286      * Observes the watchdog on/off setting, and takes action when changed.
    287      */
    288     private void registerForWatchdogToggle() {
    289         ContentObserver contentObserver = new ContentObserver(this.getHandler()) {
    290             @Override
    291             public void onChange(boolean selfChange) {
    292                 sendMessage(EVENT_WATCHDOG_TOGGLED);
    293             }
    294         };
    295 
    296         mContext.getContentResolver().registerContentObserver(
    297                 Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_ON),
    298                 false, contentObserver);
    299     }
    300 
    301     /**
    302      * Observes watchdogs secure setting changes.
    303      */
    304     private void registerForSettingsChanges() {
    305         ContentObserver contentObserver = new ContentObserver(this.getHandler()) {
    306             @Override
    307             public void onChange(boolean selfChange) {
    308                 sendMessage(EVENT_WATCHDOG_SETTINGS_CHANGE);
    309             }
    310         };
    311 
    312         mContext.getContentResolver().registerContentObserver(
    313                 Settings.Secure.getUriFor(
    314                         Settings.Secure.WIFI_WATCHDOG_DNS_CHECK_SHORT_INTERVAL_MS),
    315                         false, contentObserver);
    316         mContext.getContentResolver().registerContentObserver(
    317                 Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_DNS_CHECK_LONG_INTERVAL_MS),
    318                 false, contentObserver);
    319         mContext.getContentResolver().registerContentObserver(
    320                 Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_INTERVAL_MS),
    321                 false, contentObserver);
    322         mContext.getContentResolver().registerContentObserver(
    323                 Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_MAX_SSID_BLACKLISTS),
    324                 false, contentObserver);
    325         mContext.getContentResolver().registerContentObserver(
    326                 Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_NUM_DNS_PINGS),
    327                 false, contentObserver);
    328         mContext.getContentResolver().registerContentObserver(
    329                 Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_MIN_DNS_RESPONSES),
    330                 false, contentObserver);
    331         mContext.getContentResolver().registerContentObserver(
    332                 Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_DNS_PING_TIMEOUT_MS),
    333                 false, contentObserver);
    334         mContext.getContentResolver().registerContentObserver(
    335                 Settings.Secure.getUriFor(
    336                         Settings.Secure.WIFI_WATCHDOG_BLACKLIST_FOLLOWUP_INTERVAL_MS),
    337                         false, contentObserver);
    338         mContext.getContentResolver().registerContentObserver(
    339                 Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED),
    340                 false, contentObserver);
    341         mContext.getContentResolver().registerContentObserver(
    342                 Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_URL),
    343                 false, contentObserver);
    344         mContext.getContentResolver().registerContentObserver(
    345                 Settings.Secure.getUriFor(Settings.Secure.WIFI_WATCHDOG_SHOW_DISABLED_NETWORK_POPUP)
    346                 , false, contentObserver);
    347     }
    348 
    349     /**
    350      * DNS based detection techniques do not work at all hotspots. The one sure
    351      * way to check a walled garden is to see if a URL fetch on a known address
    352      * fetches the data we expect
    353      */
    354     private boolean isWalledGardenConnection() {
    355         HttpURLConnection urlConnection = null;
    356         try {
    357             URL url = new URL(mWalledGardenUrl);
    358             urlConnection = (HttpURLConnection) url.openConnection();
    359             urlConnection.setInstanceFollowRedirects(false);
    360             urlConnection.setConnectTimeout(WALLED_GARDEN_SOCKET_TIMEOUT_MS);
    361             urlConnection.setReadTimeout(WALLED_GARDEN_SOCKET_TIMEOUT_MS);
    362             urlConnection.setUseCaches(false);
    363             urlConnection.getInputStream();
    364             // We got a valid response, but not from the real google
    365             return urlConnection.getResponseCode() != 204;
    366         } catch (IOException e) {
    367             if (DBG) {
    368                 log("Walled garden check - probably not a portal: exception " + e);
    369             }
    370             return false;
    371         } finally {
    372             if (urlConnection != null) {
    373                 urlConnection.disconnect();
    374             }
    375         }
    376     }
    377 
    378     private boolean rssiStrengthAboveCutoff(int rssi) {
    379         return WifiManager.calculateSignalLevel(rssi, WIFI_SIGNAL_LEVELS) > LOW_SIGNAL_CUTOFF;
    380     }
    381 
    382     public void dump(PrintWriter pw) {
    383         pw.print("WatchdogStatus: ");
    384         pw.print("State " + getCurrentState());
    385         pw.println(", network [" + mConnectionInfo + "]");
    386         pw.print("checkFailures   " + mNumCheckFailures);
    387         pw.println(", bssids: " + mBssids);
    388         pw.println("lastSingleCheck: " + mOnlineWatchState.lastCheckTime);
    389     }
    390 
    391     private boolean isWatchdogEnabled() {
    392         return getSettingsBoolean(mContentResolver, Settings.Secure.WIFI_WATCHDOG_ON, true);
    393     }
    394 
    395     private void updateSettings() {
    396         mDnsCheckShortIntervalMs = Secure.getLong(mContentResolver,
    397                 Secure.WIFI_WATCHDOG_DNS_CHECK_SHORT_INTERVAL_MS,
    398                 DEFAULT_DNS_CHECK_SHORT_INTERVAL_MS);
    399         mDnsCheckLongIntervalMs = Secure.getLong(mContentResolver,
    400                 Secure.WIFI_WATCHDOG_DNS_CHECK_LONG_INTERVAL_MS,
    401                 DEFAULT_DNS_CHECK_LONG_INTERVAL_MS);
    402         mMaxSsidBlacklists = Secure.getInt(mContentResolver,
    403                 Secure.WIFI_WATCHDOG_MAX_SSID_BLACKLISTS,
    404                 DEFAULT_MAX_SSID_BLACKLISTS);
    405         mNumDnsPings = Secure.getInt(mContentResolver,
    406                 Secure.WIFI_WATCHDOG_NUM_DNS_PINGS,
    407                 DEFAULT_NUM_DNS_PINGS);
    408         mMinDnsResponses = Secure.getInt(mContentResolver,
    409                 Secure.WIFI_WATCHDOG_MIN_DNS_RESPONSES,
    410                 DEFAULT_MIN_DNS_RESPONSES);
    411         mDnsPingTimeoutMs = Secure.getInt(mContentResolver,
    412                 Secure.WIFI_WATCHDOG_DNS_PING_TIMEOUT_MS,
    413                 DEFAULT_DNS_PING_TIMEOUT_MS);
    414         mBlacklistFollowupIntervalMs = Secure.getLong(mContentResolver,
    415                 Settings.Secure.WIFI_WATCHDOG_BLACKLIST_FOLLOWUP_INTERVAL_MS,
    416                 DEFAULT_BLACKLIST_FOLLOWUP_INTERVAL_MS);
    417         //TODO: enable this by default after changing watchdog behavior
    418         //Also, update settings description
    419         mPoorNetworkDetectionEnabled = getSettingsBoolean(mContentResolver,
    420                 Settings.Secure.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED, false);
    421         mWalledGardenTestEnabled = getSettingsBoolean(mContentResolver,
    422                 Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_TEST_ENABLED, true);
    423         mWalledGardenUrl = getSettingsStr(mContentResolver,
    424                 Settings.Secure.WIFI_WATCHDOG_WALLED_GARDEN_URL,
    425                 DEFAULT_WALLED_GARDEN_URL);
    426         mWalledGardenIntervalMs = Secure.getLong(mContentResolver,
    427                 Secure.WIFI_WATCHDOG_WALLED_GARDEN_INTERVAL_MS,
    428                 DEFAULT_WALLED_GARDEN_INTERVAL_MS);
    429         mShowDisabledNotification = getSettingsBoolean(mContentResolver,
    430                 Settings.Secure.WIFI_WATCHDOG_SHOW_DISABLED_NETWORK_POPUP, true);
    431     }
    432 
    433     /**
    434      * Helper to return wait time left given a min interval and last run
    435      *
    436      * @param interval minimum wait interval
    437      * @param lastTime last time action was performed in
    438      *            SystemClock.elapsedRealtime(). Null if never.
    439      * @return non negative time to wait
    440      */
    441     private static long waitTime(long interval, Long lastTime) {
    442         if (lastTime == null)
    443             return 0;
    444         long wait = interval + lastTime - SystemClock.elapsedRealtime();
    445         return wait > 0 ? wait : 0;
    446     }
    447 
    448     private static String wifiInfoToStr(WifiInfo wifiInfo) {
    449         if (wifiInfo == null)
    450             return "null";
    451         return "(" + wifiInfo.getSSID() + ", " + wifiInfo.getBSSID() + ")";
    452     }
    453 
    454     /**
    455      * Uses {@link #mConnectionInfo}.
    456      */
    457     private void updateBssids() {
    458         String curSsid = mConnectionInfo.getSSID();
    459         List<ScanResult> results = mWifiManager.getScanResults();
    460         int oldNumBssids = mBssids.size();
    461 
    462         if (results == null) {
    463             if (DBG) {
    464                 log("updateBssids: Got null scan results!");
    465             }
    466             return;
    467         }
    468 
    469         for (ScanResult result : results) {
    470             if (result == null || result.SSID == null) {
    471                 if (DBG) {
    472                     log("Received invalid scan result: " + result);
    473                 }
    474                 continue;
    475             }
    476             if (curSsid.equals(result.SSID))
    477                 mBssids.add(result.BSSID);
    478         }
    479     }
    480 
    481     private void resetWatchdogState() {
    482         if (DBG) {
    483             log("Resetting watchdog state...");
    484         }
    485         mConnectionInfo = null;
    486         mDisableAPNextFailure = false;
    487         mLastWalledGardenCheckTime = null;
    488         mNumCheckFailures = 0;
    489         mBssids.clear();
    490         setDisabledNetworkNotificationVisible(false);
    491         setWalledGardenNotificationVisible(false);
    492     }
    493 
    494     private void setWalledGardenNotificationVisible(boolean visible) {
    495         // If it should be hidden and it is already hidden, then noop
    496         if (!visible && !mWalledGardenNotificationShown) {
    497             return;
    498         }
    499 
    500         Resources r = Resources.getSystem();
    501         NotificationManager notificationManager = (NotificationManager) mContext
    502             .getSystemService(Context.NOTIFICATION_SERVICE);
    503 
    504         if (visible) {
    505             Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(mWalledGardenUrl));
    506             intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);
    507 
    508             CharSequence title = r.getString(R.string.wifi_available_sign_in, 0);
    509             CharSequence details = r.getString(R.string.wifi_available_sign_in_detailed,
    510                     mConnectionInfo.getSSID());
    511 
    512             Notification notification = new Notification();
    513             notification.when = 0;
    514             notification.icon = com.android.internal.R.drawable.stat_notify_wifi_in_range;
    515             notification.flags = Notification.FLAG_AUTO_CANCEL;
    516             notification.contentIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
    517             notification.tickerText = title;
    518             notification.setLatestEventInfo(mContext, title, details, notification.contentIntent);
    519 
    520             notificationManager.notify(WALLED_GARDEN_NOTIFICATION_ID, 1, notification);
    521         } else {
    522             notificationManager.cancel(WALLED_GARDEN_NOTIFICATION_ID, 1);
    523         }
    524         mWalledGardenNotificationShown = visible;
    525     }
    526 
    527     private void setDisabledNetworkNotificationVisible(boolean visible) {
    528         // If it should be hidden and it is already hidden, then noop
    529         if (!visible && !mDisabledNotificationShown) {
    530             return;
    531         }
    532 
    533         Resources r = Resources.getSystem();
    534         NotificationManager notificationManager = (NotificationManager) mContext
    535             .getSystemService(Context.NOTIFICATION_SERVICE);
    536 
    537         if (visible) {
    538             CharSequence title = r.getText(R.string.wifi_watchdog_network_disabled);
    539             String msg = mConnectionInfo.getSSID() +
    540                 r.getText(R.string.wifi_watchdog_network_disabled_detailed);
    541 
    542             Notification wifiDisabledWarning = new Notification.Builder(mContext)
    543                 .setSmallIcon(R.drawable.stat_sys_warning)
    544                 .setDefaults(Notification.DEFAULT_ALL)
    545                 .setTicker(title)
    546                 .setContentTitle(title)
    547                 .setContentText(msg)
    548                 .setContentIntent(PendingIntent.getActivity(mContext, 0,
    549                             new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK)
    550                             .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK), 0))
    551                 .setWhen(System.currentTimeMillis())
    552                 .setAutoCancel(true)
    553                 .getNotification();
    554 
    555             notificationManager.notify(DISABLED_NETWORK_NOTIFICATION_ID, 1, wifiDisabledWarning);
    556         } else {
    557             notificationManager.cancel(DISABLED_NETWORK_NOTIFICATION_ID, 1);
    558         }
    559         mDisabledNotificationShown = visible;
    560     }
    561 
    562     class DefaultState extends State {
    563         @Override
    564         public boolean processMessage(Message msg) {
    565             switch (msg.what) {
    566                 case EVENT_WATCHDOG_SETTINGS_CHANGE:
    567                     updateSettings();
    568                     if (DBG) {
    569                         log("Updating wifi-watchdog secure settings");
    570                     }
    571                     return HANDLED;
    572             }
    573             if (DBG) {
    574                 log("Caught message " + msg.what + " in state " +
    575                         getCurrentState().getName());
    576             }
    577             return HANDLED;
    578         }
    579     }
    580 
    581     class WatchdogDisabledState extends State {
    582         @Override
    583         public boolean processMessage(Message msg) {
    584             switch (msg.what) {
    585                 case EVENT_WATCHDOG_TOGGLED:
    586                     if (isWatchdogEnabled())
    587                         transitionTo(mNotConnectedState);
    588                     return HANDLED;
    589             }
    590             return NOT_HANDLED;
    591         }
    592     }
    593 
    594     class WatchdogEnabledState extends State {
    595         @Override
    596         public void enter() {
    597             resetWatchdogState();
    598             mContext.registerReceiver(mBroadcastReceiver, mIntentFilter);
    599             if (DBG) log("WifiWatchdogService enabled");
    600         }
    601 
    602         @Override
    603         public boolean processMessage(Message msg) {
    604             switch (msg.what) {
    605                 case EVENT_WATCHDOG_TOGGLED:
    606                     if (!isWatchdogEnabled())
    607                         transitionTo(mWatchdogDisabledState);
    608                     return HANDLED;
    609                 case EVENT_NETWORK_STATE_CHANGE:
    610                     Intent stateChangeIntent = (Intent) msg.obj;
    611                     NetworkInfo networkInfo = (NetworkInfo)
    612                             stateChangeIntent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
    613 
    614                     setDisabledNetworkNotificationVisible(false);
    615                     setWalledGardenNotificationVisible(false);
    616                     switch (networkInfo.getState()) {
    617                         case CONNECTED:
    618                             WifiInfo wifiInfo = (WifiInfo)
    619                                 stateChangeIntent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO);
    620                             if (wifiInfo == null) {
    621                                 loge("Connected --> WifiInfo object null!");
    622                                 return HANDLED;
    623                             }
    624 
    625                             if (wifiInfo.getSSID() == null || wifiInfo.getBSSID() == null) {
    626                                 loge("Received wifiInfo object with null elts: "
    627                                         + wifiInfoToStr(wifiInfo));
    628                                 return HANDLED;
    629                             }
    630 
    631                             initConnection(wifiInfo);
    632                             mConnectionInfo = wifiInfo;
    633                             mNetEventCounter++;
    634                             if (mPoorNetworkDetectionEnabled) {
    635                                 updateBssids();
    636                                 transitionTo(mDnsCheckingState);
    637                             } else {
    638                                 transitionTo(mDelayWalledGardenState);
    639                             }
    640                             break;
    641                         default:
    642                             mNetEventCounter++;
    643                             transitionTo(mNotConnectedState);
    644                             break;
    645                     }
    646                     return HANDLED;
    647                 case EVENT_WIFI_RADIO_STATE_CHANGE:
    648                     if ((Integer) msg.obj == WifiManager.WIFI_STATE_DISABLING) {
    649                         if (DBG) log("WifiStateDisabling -- Resetting WatchdogState");
    650                         resetWatchdogState();
    651                         mNetEventCounter++;
    652                         transitionTo(mNotConnectedState);
    653                     }
    654                     return HANDLED;
    655             }
    656 
    657             return NOT_HANDLED;
    658         }
    659 
    660         /**
    661          * @param wifiInfo Info object with non-null ssid and bssid
    662          */
    663         private void initConnection(WifiInfo wifiInfo) {
    664             if (DBG) {
    665                 log("Connected:: old " + wifiInfoToStr(mConnectionInfo) +
    666                         " ==> new " + wifiInfoToStr(wifiInfo));
    667             }
    668 
    669             if (mConnectionInfo == null || !wifiInfo.getSSID().equals(mConnectionInfo.getSSID())) {
    670                 resetWatchdogState();
    671             } else if (!wifiInfo.getBSSID().equals(mConnectionInfo.getBSSID())) {
    672                 mDisableAPNextFailure = false;
    673             }
    674         }
    675 
    676         @Override
    677         public void exit() {
    678             mContext.unregisterReceiver(mBroadcastReceiver);
    679             if (DBG) log("WifiWatchdogService disabled");
    680         }
    681     }
    682 
    683     class NotConnectedState extends State {
    684     }
    685 
    686     class ConnectedState extends State {
    687         @Override
    688         public boolean processMessage(Message msg) {
    689             switch (msg.what) {
    690                 case EVENT_SCAN_RESULTS_AVAILABLE:
    691                     if (mPoorNetworkDetectionEnabled) {
    692                         updateBssids();
    693                     }
    694                     return HANDLED;
    695                 case EVENT_WATCHDOG_SETTINGS_CHANGE:
    696                     updateSettings();
    697                     if (mPoorNetworkDetectionEnabled) {
    698                         transitionTo(mOnlineWatchState);
    699                     } else {
    700                         transitionTo(mOnlineState);
    701                     }
    702                     return HANDLED;
    703             }
    704             return NOT_HANDLED;
    705         }
    706     }
    707 
    708     class DnsCheckingState extends State {
    709         List<InetAddress> mDnsList;
    710         int[] dnsCheckSuccesses;
    711         String dnsCheckLogStr;
    712         String[] dnsResponseStrs;
    713         /** Keeps track of active dns pings.  Map is from pingID to index in mDnsList */
    714         HashMap<Integer, Integer> idDnsMap = new HashMap<Integer, Integer>();
    715 
    716         @Override
    717         public void enter() {
    718             mDnsList = mDnsPinger.getDnsList();
    719             int numDnses = mDnsList.size();
    720             dnsCheckSuccesses = new int[numDnses];
    721             dnsResponseStrs = new String[numDnses];
    722             for (int i = 0; i < numDnses; i++)
    723                 dnsResponseStrs[i] = "";
    724 
    725             if (DBG) {
    726                 dnsCheckLogStr = String.format("Pinging %s on ssid [%s]: ",
    727                         mDnsList, mConnectionInfo.getSSID());
    728                 log(dnsCheckLogStr);
    729             }
    730 
    731             idDnsMap.clear();
    732             for (int i=0; i < mNumDnsPings; i++) {
    733                 for (int j = 0; j < numDnses; j++) {
    734                     idDnsMap.put(mDnsPinger.pingDnsAsync(mDnsList.get(j), mDnsPingTimeoutMs,
    735                             DNS_START_DELAY_MS + DNS_INTRATEST_PING_INTERVAL_MS * i), j);
    736                 }
    737             }
    738         }
    739 
    740         @Override
    741         public boolean processMessage(Message msg) {
    742             if (msg.what != DnsPinger.DNS_PING_RESULT) {
    743                 return NOT_HANDLED;
    744             }
    745 
    746             int pingID = msg.arg1;
    747             int pingResponseTime = msg.arg2;
    748 
    749             Integer dnsServerId = idDnsMap.get(pingID);
    750             if (dnsServerId == null) {
    751                 loge("Received a Dns response with unknown ID!");
    752                 return HANDLED;
    753             }
    754 
    755             idDnsMap.remove(pingID);
    756             if (pingResponseTime >= 0)
    757                 dnsCheckSuccesses[dnsServerId]++;
    758 
    759             if (DBG) {
    760                 if (pingResponseTime >= 0) {
    761                     dnsResponseStrs[dnsServerId] += "|" + pingResponseTime;
    762                 } else {
    763                     dnsResponseStrs[dnsServerId] += "|x";
    764                 }
    765             }
    766 
    767             /**
    768              * After a full ping count, if we have more responses than this
    769              * cutoff, the outcome is success; else it is 'failure'.
    770              */
    771 
    772             /**
    773              * Our final success count will be at least this big, so we're
    774              * guaranteed to succeed.
    775              */
    776             if (dnsCheckSuccesses[dnsServerId] >= mMinDnsResponses) {
    777                 // DNS CHECKS OK, NOW WALLED GARDEN
    778                 if (DBG) {
    779                     log(makeLogString() + "  SUCCESS");
    780                 }
    781 
    782                 if (!shouldCheckWalledGarden()) {
    783                     transitionTo(mOnlineWatchState);
    784                     return HANDLED;
    785                 }
    786 
    787                 transitionTo(mDelayWalledGardenState);
    788                 return HANDLED;
    789             }
    790 
    791             if (idDnsMap.isEmpty()) {
    792                 if (DBG) {
    793                     log(makeLogString() + "  FAILURE");
    794                 }
    795                 transitionTo(mDnsCheckFailureState);
    796                 return HANDLED;
    797             }
    798 
    799             return HANDLED;
    800         }
    801 
    802         private String makeLogString() {
    803             String logStr = dnsCheckLogStr;
    804             for (String respStr : dnsResponseStrs)
    805                 logStr += " [" + respStr + "]";
    806             return logStr;
    807         }
    808 
    809         @Override
    810         public void exit() {
    811             mDnsPinger.cancelPings();
    812         }
    813 
    814         private boolean shouldCheckWalledGarden() {
    815             if (!mWalledGardenTestEnabled) {
    816                 if (DBG)
    817                     log("Skipping walled garden check - disabled");
    818                 return false;
    819             }
    820             long waitTime = waitTime(mWalledGardenIntervalMs,
    821                     mLastWalledGardenCheckTime);
    822             if (waitTime > 0) {
    823                 if (DBG) {
    824                     log("Skipping walled garden check - wait " +
    825                             waitTime + " ms.");
    826                 }
    827                 return false;
    828             }
    829             return true;
    830         }
    831     }
    832 
    833     class DelayWalledGardenState extends State {
    834         @Override
    835         public void enter() {
    836             sendMessageDelayed(MESSAGE_DELAYED_WALLED_GARDEN_CHECK, WALLED_GARDEN_START_DELAY_MS);
    837         }
    838 
    839         @Override
    840         public boolean processMessage(Message msg) {
    841             switch (msg.what) {
    842                 case MESSAGE_DELAYED_WALLED_GARDEN_CHECK:
    843                     mLastWalledGardenCheckTime = SystemClock.elapsedRealtime();
    844                     if (isWalledGardenConnection()) {
    845                         if (DBG) log("Walled garden test complete - walled garden detected");
    846                         transitionTo(mWalledGardenState);
    847                     } else {
    848                         if (DBG) log("Walled garden test complete - online");
    849                         if (mPoorNetworkDetectionEnabled) {
    850                             transitionTo(mOnlineWatchState);
    851                         } else {
    852                             transitionTo(mOnlineState);
    853                         }
    854                     }
    855                     return HANDLED;
    856                 default:
    857                     return NOT_HANDLED;
    858             }
    859         }
    860     }
    861 
    862     class OnlineWatchState extends State {
    863         /**
    864          * Signals a short-wait message is enqueued for the current 'guard' counter
    865          */
    866         boolean unstableSignalChecks = false;
    867 
    868         /**
    869          * The signal is unstable.  We should enqueue a short-wait check, if one is enqueued
    870          * already
    871          */
    872         boolean signalUnstable = false;
    873 
    874         /**
    875          * A monotonic counter to ensure that at most one check message will be processed from any
    876          * set of check messages currently enqueued.  Avoids duplicate checks when a low-signal
    877          * event is observed.
    878          */
    879         int checkGuard = 0;
    880         Long lastCheckTime = null;
    881 
    882         /** Keeps track of dns pings.  Map is from pingID to InetAddress used for ping */
    883         HashMap<Integer, InetAddress> pingInfoMap = new HashMap<Integer, InetAddress>();
    884 
    885         @Override
    886         public void enter() {
    887             lastCheckTime = SystemClock.elapsedRealtime();
    888             signalUnstable = false;
    889             checkGuard++;
    890             unstableSignalChecks = false;
    891             pingInfoMap.clear();
    892             triggerSingleDnsCheck();
    893         }
    894 
    895         @Override
    896         public boolean processMessage(Message msg) {
    897             switch (msg.what) {
    898                 case EVENT_RSSI_CHANGE:
    899                     if (msg.arg1 != mNetEventCounter) {
    900                         if (DBG) {
    901                             log("Rssi change message out of sync, ignoring");
    902                         }
    903                         return HANDLED;
    904                     }
    905                     int newRssi = msg.arg2;
    906                     signalUnstable = !rssiStrengthAboveCutoff(newRssi);
    907                     if (DBG) {
    908                         log("OnlineWatchState:: new rssi " + newRssi + " --> level " +
    909                                 WifiManager.calculateSignalLevel(newRssi, WIFI_SIGNAL_LEVELS));
    910                     }
    911 
    912                     if (signalUnstable && !unstableSignalChecks) {
    913                         if (DBG) {
    914                             log("Sending triggered check msg");
    915                         }
    916                         triggerSingleDnsCheck();
    917                     }
    918                     return HANDLED;
    919                 case MESSAGE_SINGLE_DNS_CHECK:
    920                     if (msg.arg1 != checkGuard) {
    921                         if (DBG) {
    922                             log("Single check msg out of sync, ignoring.");
    923                         }
    924                         return HANDLED;
    925                     }
    926                     lastCheckTime = SystemClock.elapsedRealtime();
    927                     pingInfoMap.clear();
    928                     for (InetAddress curDns: mDnsPinger.getDnsList()) {
    929                         pingInfoMap.put(mDnsPinger.pingDnsAsync(curDns, mDnsPingTimeoutMs, 0),
    930                                 curDns);
    931                     }
    932                     return HANDLED;
    933                 case DnsPinger.DNS_PING_RESULT:
    934                     InetAddress curDnsServer = pingInfoMap.get(msg.arg1);
    935                     if (curDnsServer == null) {
    936                         return HANDLED;
    937                     }
    938                     pingInfoMap.remove(msg.arg1);
    939                     int responseTime = msg.arg2;
    940                     if (responseTime >= 0) {
    941                         if (DBG) {
    942                             log("Single DNS ping OK. Response time: "
    943                                     + responseTime + " from DNS " + curDnsServer);
    944                         }
    945                         pingInfoMap.clear();
    946 
    947                         checkGuard++;
    948                         unstableSignalChecks = false;
    949                         triggerSingleDnsCheck();
    950                     } else {
    951                         if (pingInfoMap.isEmpty()) {
    952                             if (DBG) {
    953                                 log("Single dns ping failure. All dns servers failed, "
    954                                         + "starting full checks.");
    955                             }
    956                             transitionTo(mDnsCheckingState);
    957                         }
    958                     }
    959                     return HANDLED;
    960             }
    961             return NOT_HANDLED;
    962         }
    963 
    964         @Override
    965         public void exit() {
    966             mDnsPinger.cancelPings();
    967         }
    968 
    969         /**
    970          * Times a dns check with an interval based on {@link #signalUnstable}
    971          */
    972         private void triggerSingleDnsCheck() {
    973             long waitInterval;
    974             if (signalUnstable) {
    975                 waitInterval = mDnsCheckShortIntervalMs;
    976                 unstableSignalChecks = true;
    977             } else {
    978                 waitInterval = mDnsCheckLongIntervalMs;
    979             }
    980             sendMessageDelayed(obtainMessage(MESSAGE_SINGLE_DNS_CHECK, checkGuard, 0),
    981                     waitTime(waitInterval, lastCheckTime));
    982         }
    983     }
    984 
    985 
    986     /* Child state of ConnectedState indicating that we are online
    987      * and there is nothing to do
    988      */
    989     class OnlineState extends State {
    990     }
    991 
    992     class DnsCheckFailureState extends State {
    993 
    994         @Override
    995         public void enter() {
    996             mNumCheckFailures++;
    997             obtainMessage(MESSAGE_HANDLE_BAD_AP, mNetEventCounter, 0).sendToTarget();
    998         }
    999 
   1000         @Override
   1001         public boolean processMessage(Message msg) {
   1002             if (msg.what != MESSAGE_HANDLE_BAD_AP) {
   1003                 return NOT_HANDLED;
   1004             }
   1005 
   1006             if (msg.arg1 != mNetEventCounter) {
   1007                 if (DBG) {
   1008                     log("Msg out of sync, ignoring...");
   1009                 }
   1010                 return HANDLED;
   1011             }
   1012 
   1013             if (mDisableAPNextFailure || mNumCheckFailures >= mBssids.size()
   1014                     || mNumCheckFailures >= mMaxSsidBlacklists) {
   1015                 if (sWifiOnly) {
   1016                     log("Would disable bad network, but device has no mobile data!" +
   1017                             "  Going idle...");
   1018                     // This state should be called idle -- will be changing flow.
   1019                     transitionTo(mNotConnectedState);
   1020                     return HANDLED;
   1021                 }
   1022 
   1023                 // TODO : Unban networks if they had low signal ?
   1024                 log("Disabling current SSID " + wifiInfoToStr(mConnectionInfo)
   1025                         + ".  " + "numCheckFailures " + mNumCheckFailures
   1026                         + ", numAPs " + mBssids.size());
   1027                 int networkId = mConnectionInfo.getNetworkId();
   1028                 if (!mHasConnectedWifiManager) {
   1029                     mWifiManager.asyncConnect(mContext, getHandler());
   1030                     mHasConnectedWifiManager = true;
   1031                 }
   1032                 mWifiManager.disableNetwork(networkId, WifiConfiguration.DISABLED_DNS_FAILURE);
   1033                 if (mShowDisabledNotification && mConnectionInfo.isExplicitConnect()) {
   1034                     setDisabledNetworkNotificationVisible(true);
   1035                 }
   1036                 transitionTo(mNotConnectedState);
   1037             } else {
   1038                 log("Blacklisting current BSSID.  " + wifiInfoToStr(mConnectionInfo)
   1039                        + "numCheckFailures " + mNumCheckFailures + ", numAPs " + mBssids.size());
   1040 
   1041                 mWifiManager.addToBlacklist(mConnectionInfo.getBSSID());
   1042                 mWifiManager.reassociate();
   1043                 transitionTo(mBlacklistedApState);
   1044             }
   1045             return HANDLED;
   1046         }
   1047     }
   1048 
   1049     class WalledGardenState extends State {
   1050         @Override
   1051         public void enter() {
   1052             obtainMessage(MESSAGE_HANDLE_WALLED_GARDEN, mNetEventCounter, 0).sendToTarget();
   1053         }
   1054 
   1055         @Override
   1056         public boolean processMessage(Message msg) {
   1057             if (msg.what != MESSAGE_HANDLE_WALLED_GARDEN) {
   1058                 return NOT_HANDLED;
   1059             }
   1060 
   1061             if (msg.arg1 != mNetEventCounter) {
   1062                 if (DBG) {
   1063                     log("WalledGardenState::Msg out of sync, ignoring...");
   1064                 }
   1065                 return HANDLED;
   1066             }
   1067             setWalledGardenNotificationVisible(true);
   1068             if (mPoorNetworkDetectionEnabled) {
   1069                 transitionTo(mOnlineWatchState);
   1070             } else {
   1071                 transitionTo(mOnlineState);
   1072             }
   1073             return HANDLED;
   1074         }
   1075     }
   1076 
   1077     class BlacklistedApState extends State {
   1078         @Override
   1079         public void enter() {
   1080             mDisableAPNextFailure = true;
   1081             sendMessageDelayed(obtainMessage(MESSAGE_NETWORK_FOLLOWUP, mNetEventCounter, 0),
   1082                     mBlacklistFollowupIntervalMs);
   1083         }
   1084 
   1085         @Override
   1086         public boolean processMessage(Message msg) {
   1087             if (msg.what != MESSAGE_NETWORK_FOLLOWUP) {
   1088                 return NOT_HANDLED;
   1089             }
   1090 
   1091             if (msg.arg1 != mNetEventCounter) {
   1092                 if (DBG) {
   1093                     log("BlacklistedApState::Msg out of sync, ignoring...");
   1094                 }
   1095                 return HANDLED;
   1096             }
   1097 
   1098             transitionTo(mDnsCheckingState);
   1099             return HANDLED;
   1100         }
   1101     }
   1102 
   1103 
   1104     /**
   1105      * Convenience function for retrieving a single secure settings value
   1106      * as a string with a default value.
   1107      *
   1108      * @param cr The ContentResolver to access.
   1109      * @param name The name of the setting to retrieve.
   1110      * @param def Value to return if the setting is not defined.
   1111      *
   1112      * @return The setting's current value, or 'def' if it is not defined
   1113      */
   1114     private static String getSettingsStr(ContentResolver cr, String name, String def) {
   1115         String v = Settings.Secure.getString(cr, name);
   1116         return v != null ? v : def;
   1117     }
   1118 
   1119     /**
   1120      * Convenience function for retrieving a single secure settings value
   1121      * as a boolean.  Note that internally setting values are always
   1122      * stored as strings; this function converts the string to a boolean
   1123      * for you.  The default value will be returned if the setting is
   1124      * not defined or not a valid boolean.
   1125      *
   1126      * @param cr The ContentResolver to access.
   1127      * @param name The name of the setting to retrieve.
   1128      * @param def Value to return if the setting is not defined.
   1129      *
   1130      * @return The setting's current value, or 'def' if it is not defined
   1131      * or not a valid boolean.
   1132      */
   1133     private static boolean getSettingsBoolean(ContentResolver cr, String name, boolean def) {
   1134         return Settings.Secure.getInt(cr, name, def ? 1 : 0) == 1;
   1135     }
   1136 
   1137     /**
   1138      * Convenience function for updating a single settings value as an
   1139      * integer. This will either create a new entry in the table if the
   1140      * given name does not exist, or modify the value of the existing row
   1141      * with that name.  Note that internally setting values are always
   1142      * stored as strings, so this function converts the given value to a
   1143      * string before storing it.
   1144      *
   1145      * @param cr The ContentResolver to access.
   1146      * @param name The name of the setting to modify.
   1147      * @param value The new value for the setting.
   1148      * @return true if the value was set, false on database errors
   1149      */
   1150     private static boolean putSettingsBoolean(ContentResolver cr, String name, boolean value) {
   1151         return Settings.Secure.putInt(cr, name, value ? 1 : 0);
   1152     }
   1153 
   1154     private void log(String s) {
   1155         Log.d(TAG, s);
   1156     }
   1157 
   1158     private void loge(String s) {
   1159         Log.e(TAG, s);
   1160     }
   1161 }
   1162