package net.kismetwireless.android.smarterwifimanager; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.database.sqlite.SQLiteException; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiManager; import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.PowerManager; import android.preference.PreferenceManager; import android.provider.Settings; import android.support.v4.app.NotificationCompat; import android.telephony.CellInfo; import android.telephony.CellLocation; import android.telephony.PhoneStateListener; import android.telephony.TelephonyManager; import android.util.Log; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Set; public class SmarterWifiService extends Service { // Unique combinations: // WIFISTATE IGNORE + CONTROL RANGE - no valid towers no other conditions public enum ControlType { CONTROL_DISABLED, CONTROL_USER, CONTROL_TOWER, CONTROL_TOWERID, CONTROL_GEOFENCE, CONTROL_BLUETOOTH, CONTROL_TIME, CONTROL_SSIDBLACKLIST, CONTROL_AIRPLANE, CONTROL_TETHER, CONTROL_SLEEPPOLICY, CONTROL_NEVERRUN } public enum WifiState { // Hard blocked, on, off, idle, ignore WIFI_BLOCKED, WIFI_ON, WIFI_OFF, WIFI_IDLE, WIFI_IGNORE } public enum BluetoothState { BLUETOOTH_BLOCKED, BLUETOOTH_ON, BLUETOOTH_OFF, BLUETOOTH_IDLE, BLUETOOTH_IGNORE } public enum TowerType { TOWER_UNKNOWN, TOWER_BLOCK, TOWER_ENABLE, TOWER_INVALID } private boolean everBeenRun = false; private boolean shutdown = false; SharedPreferences preferences; TelephonyManager telephonyManager; SmarterPhoneListener phoneListener; WifiManager wifiManager; BluetoothAdapter btAdapter; ConnectivityManager connectivityManager; private NotificationManager notificationManager; private boolean proctorWifi = true; private boolean learnWifi = true; private int enableWaitSeconds = 1; private int disableWaitSeconds = 30; private boolean showNotification = true; private boolean performTowerPurges = false; private int purgeTowerHours = 168; private WifiState userOverrideState = WifiState.WIFI_IGNORE; private WifiState curState = WifiState.WIFI_IGNORE; private WifiState targetState = WifiState.WIFI_IGNORE; private CellLocationCommon currentCellLocation; private TowerType currentTowerType = TowerType.TOWER_UNKNOWN; private ControlType lastControlReason = ControlType.CONTROL_TOWERID; private Handler timerHandler = new Handler(); private SmarterDBSource dbSource; private NotificationCompat.Builder notificationBuilder; private long lastTowerMap = 0; private boolean bluetoothEnabled = false; private boolean bluetoothBlocking = false; private boolean initialBluetoothState = false; private HashMap<String, SmarterBluetooth> bluetoothBlockingDevices = new HashMap<String, SmarterBluetooth>(); private HashMap<String, SmarterBluetooth> bluetoothConnectedDevices = new HashMap<String, SmarterBluetooth>(); private SmarterTimeRange currentTimeRange, nextTimeRange; private AlarmReceiver alarmReceiver; private boolean pendingWifiShutdown = false, pendingBluetoothShutdown = false; public static abstract class SmarterServiceCallback { protected ControlType controlType; protected WifiState wifiState, controlState; protected SmarterSSID lastSsid; protected TowerType towerType; protected BluetoothState lastBtState; public void wifiStateChanged(final SmarterSSID ssid, final WifiState state, final WifiState controlstate, final ControlType type) { lastSsid = ssid; wifiState = state; controlType = type; controlState = controlstate; return; } public void towerStateChanged(final long towerid, final TowerType type) { towerType = type; return; } public void bluetoothStateChanged(final BluetoothState state) { lastBtState = state; return; } public void preferencesChanged() { return; } } ArrayList<SmarterServiceCallback> callbackList = new ArrayList<SmarterServiceCallback>(); // Minimal in the extreme binder which returns the service for direct calling public class ServiceBinder extends Binder { SmarterWifiService getService() { return SmarterWifiService.this; } } private ServiceBinder serviceBinder = new ServiceBinder(); private Context context; @Override public void onCreate() { super.onCreate(); context = this.getApplicationContext(); preferences = PreferenceManager.getDefaultSharedPreferences(context); wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); try { dbSource = new SmarterDBSource(context); } catch (SQLiteException e) { LogAlias.d("smarter", "failed to open database: " + e); } // Default network state connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); phoneListener = new SmarterPhoneListener(); telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); btAdapter = BluetoothAdapter.getDefaultAdapter(); notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); notificationBuilder = new NotificationCompat.Builder(context); alarmReceiver = new AlarmReceiver(); alarmReceiver.setAlarm(context, System.currentTimeMillis() + (20 * 1000)); // Make the notification notificationBuilder.setSmallIcon(R.drawable.ic_launcher_notification_idle); notificationBuilder.setPriority(NotificationCompat.PRIORITY_MIN); notificationBuilder.setOnlyAlertOnce(true); notificationBuilder.setOngoing(true); //Intent intent = new Intent(this, MainActivity.class); Intent intent = new Intent(this, ActivityQuickconfig.class); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); // intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); PendingIntent pIntent = PendingIntent.getActivity(this, 0, intent, 0); notificationBuilder.setContentIntent(pIntent); // Get the initial BT enable state initialBluetoothState = true; updatePreferences(); // telephonyManager.listen(phoneListener, PhoneStateListener.LISTEN_CELL_LOCATION); telephonyManager.listen(phoneListener, PhoneStateListener.LISTEN_CELL_INFO | PhoneStateListener.LISTEN_CELL_LOCATION); // Kick an update // configureWifiState(); // Update the time range database which also fires BT and Wifi configurations configureTimerangeState(); if (showNotification) notificationManager.notify(0, notificationBuilder.build()); addCallback(new SmarterServiceCallback() { WifiState lastState = WifiState.WIFI_IDLE; ControlType lastControl = ControlType.CONTROL_DISABLED; @Override public void wifiStateChanged(SmarterSSID ssid, WifiState state, WifiState controlstate, ControlType type) { super.wifiStateChanged(ssid, state, controlstate, type); if (state == lastState && type == lastControl) return; lastState = state; lastControl = type; int wifiIconId = R.drawable.ic_launcher_notification_ignore; int wifiTextResource = -1; int reasonTextResource = -1; wifiTextResource = wifiStateToTextResource(state); reasonTextResource = controlTypeToTextResource(type, state); if (state == WifiState.WIFI_IDLE) { wifiIconId = R.drawable.ic_launcher_notification_idle; } else if (state == WifiState.WIFI_BLOCKED) { wifiIconId = R.drawable.ic_launcher_notification_disabled; } else if (state == WifiState.WIFI_IGNORE) { wifiIconId = R.drawable.ic_launcher_notification_idle; } else if (state == WifiState.WIFI_OFF) { if (type == ControlType.CONTROL_BLUETOOTH) wifiIconId = R.drawable.ic_launcher_notification_bluetooth; else if (type == ControlType.CONTROL_TIME) wifiIconId = R.drawable.ic_launcher_notification_clock; else if (type == ControlType.CONTROL_TOWER) wifiIconId = R.drawable.ic_launcher_notification_cell; else wifiIconId = R.drawable.ic_launcher_notification_disabled; } else if (state == WifiState.WIFI_ON) { if (type == ControlType.CONTROL_BLUETOOTH) wifiIconId = R.drawable.ic_launcher_notification_bluetooth; else if (type == ControlType.CONTROL_TIME) wifiIconId = R.drawable.ic_launcher_notification_clock; else if (type == ControlType.CONTROL_TOWER) wifiIconId = R.drawable.ic_launcher_notification_cell; else wifiIconId = R.drawable.ic_launcher_notification_ignore; } notificationBuilder.setSmallIcon(wifiIconId); if (wifiTextResource > 0) { notificationBuilder.setContentTitle(getString(wifiTextResource)); } else { notificationBuilder.setContentTitle(""); } if (reasonTextResource > 0) { notificationBuilder.setContentText(getString(reasonTextResource)); } else { notificationBuilder.setContentText(""); } // notificationBuilder.setContentTitle(wifiText); // notificationBuilder.setContentText(reasonText); if (showNotification) notificationManager.notify(0, notificationBuilder.build()); } }); towerCleanupTask.run(); } @Override public IBinder onBind(Intent intent) { return serviceBinder; } @Override public void onDestroy() { super.onDestroy(); shutdown = true; } @Override public int onStartCommand(Intent intent, int flags, int startId) { return START_STICKY; } public void updateTimeRanges() { ArrayList<SmarterTimeRange> ranges = getTimeRangeList(); currentTimeRange = null; nextTimeRange = null; if (ranges == null) { LogAlias.d("smarter", "updateTimeRanges, no ranges"); return; } // Are we in a time range right now? If so, figure out which one has the // shortest duration that we're part of. Once we fall out of this time range // we'll redo this calculation, which will grab the outer time range if one // exists. for (SmarterTimeRange t : ranges) { if (!t.getEnabled()) continue; if (!t.isInRangeNow()) continue; if (currentTimeRange == null) { currentTimeRange = new SmarterTimeRange(t); continue; } if (t.getDurationMinutes() < currentTimeRange.getDurationMinutes()) { currentTimeRange = t; } } long now = System.currentTimeMillis(); long timeUtilStart = 0; // Figure out the next time range we're going to be in for (SmarterTimeRange t : ranges) { if (!t.getEnabled()) continue; long nextT = t.getNextStartMillis(); // Shouldn't be possible since next will always find based on now, but can't hurt if (nextT < now) continue; if (timeUtilStart == 0 || nextT - now < timeUtilStart) { nextTimeRange = t; } } if (currentTimeRange == null && nextTimeRange == null) { LogAlias.d("smarter", "Not in any time ranges and none coming up"); return; } if (currentTimeRange != null) { LogAlias.d("smarter", "currently in a time range"); // Is the next alarm for the end of this time range, or for an overlapping range? if (nextTimeRange == null || (nextTimeRange != null && currentTimeRange.getNextEndMillis() < nextTimeRange.getNextStartMillis())) { LogAlias.d("smarter", "next alarm for end of this time range"); alarmReceiver.setAlarm(this, currentTimeRange.getNextEndMillis() + 1); } else { LogAlias.d("smarter", "next alarm for start of overlapping time range"); alarmReceiver.setAlarm(this, nextTimeRange.getNextStartMillis() + 1); } if (currentTimeRange.getBluetoothControlled() && btAdapter != null) { if (currentTimeRange.getBluetoothEnabled()) { btAdapter.enable(); } else { btAdapter.disable(); } } } else if (nextTimeRange != null) { LogAlias.d("smarter", "upcoming time range, setting alarm for start of it"); alarmReceiver.setAlarm(this, nextTimeRange.getNextStartMillis() + 1); } } public void updatePreferences() { everBeenRun = preferences.getBoolean("everbeenrun", false); learnWifi = preferences.getBoolean(getString(R.string.pref_learn), true); proctorWifi = preferences.getBoolean(getString(R.string.pref_enable), true); disableWaitSeconds = Integer.parseInt(preferences.getString(getString(R.string.prefs_item_shutdowntime), "30")); if (disableWaitSeconds < 30) disableWaitSeconds = 30; showNotification = preferences.getBoolean(getString(R.string.prefs_item_notification), true); if (!showNotification) notificationManager.cancel(0); else notificationManager.notify(0, notificationBuilder.build()); performTowerPurges = preferences.getBoolean(getString(R.string.prefs_item_towermaintenance), false); configureWifiState(); triggerCallbackPrefsChanged(); } public void shutdownService() { shutdown = true; timerHandler.removeCallbacks(towerCleanupTask); timerHandler.removeCallbacks(wifiEnableTask); timerHandler.removeCallbacks(wifiDisableTask); telephonyManager.listen(phoneListener, 0); } private void startBluetoothEnable() { if (btAdapter != null) { LogAlias.d("smarter", "Turning on bluetooth"); btAdapter.enable(); } } private void startBluetoothShutdown() { if (btAdapter != null) { LogAlias.d("smarter", "Turning off bluetooth"); btAdapter.disable(); } } private void startWifiEnable() { pendingWifiShutdown = false; timerHandler.removeCallbacks(wifiEnableTask); PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); if (!pm.isScreenOn()) { LogAlias.d("smarter", "turning on wifi immediately because screen is off"); wifiManager.setWifiEnabled(true); return; } timerHandler.postDelayed(wifiEnableTask, enableWaitSeconds * 1000); LogAlias.d("smarter", "Starting countdown of " + enableWaitSeconds + " to enable wifi"); } private void startWifiShutdown() { // If we're asked to turn off wifi and the screen is off, just do it. PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); if (!pm.isScreenOn()) { LogAlias.d("smarter", "shutting down wifi immediately because screen is off"); pendingWifiShutdown = false; timerHandler.removeCallbacks(wifiDisableTask); wifiManager.setWifiEnabled(false); return; } if (pendingWifiShutdown) { LogAlias.d("smarter", "wifi countdown in progress, not shutting down"); return; } pendingWifiShutdown = true; // Restart the countdown incase we got called while a countdown was already running timerHandler.removeCallbacks(wifiDisableTask); // Set a timer - if we haven't connected to a network by the time this // expires, shut down wifi again timerHandler.postDelayed(wifiDisableTask, disableWaitSeconds * 1000); LogAlias.d("smarter", "Starting countdown of " + disableWaitSeconds + " to shut down wifi"); } private Runnable wifiEnableTask = new Runnable() { public void run() { if (shutdown) return; if (!proctorWifi) return; LogAlias.d("smarter", "enabling wifi"); wifiManager.setWifiEnabled(true); } }; private Runnable wifiDisableTask = new Runnable() { public void run() { LogAlias.d("smarter", "wifi disable task triggered"); if (shutdown) { LogAlias.d("smarter", "Wifi disable task triggered, but we're about to shut down the service"); pendingWifiShutdown = false; return; } // If we're not proctoring wifi... if (!proctorWifi) { LogAlias.d("smarter", "We were going to shut down wifi, but we're no longer controlling wi-fi"); pendingWifiShutdown = false; return; } if (getWifiState() == WifiState.WIFI_ON) { LogAlias.d("smarter", "We were going to shut down wifi, but it's connected now"); pendingWifiShutdown = false; return; } LogAlias.d("smarter", "Shutting down wi-fi, we haven't gotten a link"); wifiManager.setWifiEnabled(false); pendingWifiShutdown = false; } }; // Set current tower or fetch current tower private void handleCellLocation(CellLocation location) { if (location == null) { location = telephonyManager.getCellLocation(); } LogAlias.d("smarter", "Handling cell location, going to kick curtower and wifistate"); setCurrentTower(new CellLocationCommon(location)); // configureWifiState(); } private class SmarterPhoneListener extends PhoneStateListener { @Override public void onCellLocationChanged(CellLocation location) { LogAlias.d("smarter", "celllocation changeded, calling directly"); handleCellLocation(location); } @Override public void onCellInfoChanged(List<CellInfo> infos) { LogAlias.d("smarter", "cellinfo changed, spoofing via getcellocation"); handleCellLocation(telephonyManager.getCellLocation()); } } // Set the current tower and figure out what our tower state is private void setCurrentTower(CellLocationCommon curloc) { if (curloc == null) { curloc = new CellLocationCommon(telephonyManager.getCellLocation()); } /* CellLocationCommon oldLoc = currentCellLocation; if (oldLoc != null && oldLoc.getTowerId() == curloc.getTowerId()) { WifiState idlestate = getWifiState(); if (targetState == WifiState.WIFI_BLOCKED || targetState == WifiState.WIFI_OFF) { startWifiShutdown(); } if (idlestate == targetState) { // Nothing to do, get out LogAlias.d("smarter", "still in " + curloc.getTowerId() + " so nothing has changed"); return; } else if (targetState != WifiState.WIFI_IGNORE) { LogAlias.d("smarter", "tower changed but state " + idlestate + " doesn't match target " + targetState + " so trigger update"); } } */ currentCellLocation = curloc; if (curloc.getTowerId() < 0) currentTowerType = TowerType.TOWER_INVALID; else currentTowerType = TowerType.TOWER_UNKNOWN; if (curloc.getTowerId() > 0 && learnWifi) { SmarterSSID ssid = getCurrentSsid(); // If we know this tower already, set type to enable if (dbSource.queryTowerMapped(curloc.getTowerId())) { LogAlias.d("smarter", "Entered range of known tower: " + curloc.getTowerId() + " towertype = enable"); // map it and update the last seen time dbSource.mapTower(getCurrentSsid(), curloc.getTowerId()); currentTowerType = TowerType.TOWER_ENABLE; } // If we're associated to a wifi, map the tower. // Don't map towers while we're tethered. if (getWifiState() == WifiState.WIFI_ON && !getWifiTethered() && currentTowerType != TowerType.TOWER_ENABLE) { if (ssid != null && ssid.isBlacklisted()) { // We don't learn anything based on this ssid } else { LogAlias.d("smarter", "New tower " + curloc.getTowerId() + ", Wi-Fi connected, learning tower"); dbSource.mapTower(getCurrentSsid(), curloc.getTowerId()); lastTowerMap = System.currentTimeMillis(); currentTowerType = TowerType.TOWER_ENABLE; } } triggerCallbackTowerChanged(); configureWifiState(); return; } triggerCallbackTowerChanged(); } public void addCallback(SmarterServiceCallback cb) { try { if (cb == null) { Log.e("smarter", "Got a null callback?"); return; } synchronized (callbackList) { callbackList.add(cb); } // Call our CBs immediately for setup cb.towerStateChanged(currentCellLocation.getTowerId(), currentTowerType); cb.wifiStateChanged(getCurrentSsid(), getWifiState(), getShouldWifiBeEnabled(), lastControlReason); cb.bluetoothStateChanged(getBluetoothState()); } catch (NullPointerException npe) { Log.e("smarter", "Got NPE in addcallback, caught, but not sure what happened"); } } public void removeCallback(SmarterServiceCallback cb) { if (cb == null) { Log.e("smarter", "Got a null callback?"); return; } synchronized (callbackList) { callbackList.remove(cb); } } public void triggerCallbackPrefsChanged() { synchronized (callbackList) { for (SmarterServiceCallback cb : callbackList) { cb.preferencesChanged(); } } } public void triggerCallbackTowerChanged() { synchronized (callbackList) { for (SmarterServiceCallback cb : callbackList) { cb.towerStateChanged(currentCellLocation.getTowerId(), currentTowerType); } } } public void triggerCallbackWifiChanged() { synchronized (callbackList) { for (SmarterServiceCallback cb: callbackList) { cb.wifiStateChanged(getCurrentSsid(), getWifiState(), getShouldWifiBeEnabled(), lastControlReason); } } } public void triggerCallbackBluetoothChanged() { synchronized (callbackList) { for (SmarterServiceCallback cb: callbackList) { cb.bluetoothStateChanged(getBluetoothState()); } } } public void configureWifiState() { curState = getWifiState(); targetState = getShouldWifiBeEnabled(); LogAlias.d("smarter", "configureWifiState current " + curState + " target " + targetState); if (curState == WifiState.WIFI_IGNORE) { triggerCallbackWifiChanged(); return; } if (curState == WifiState.WIFI_ON || curState == WifiState.WIFI_IDLE) { // If we're on or idle then we only need to turn off LogAlias.d("smarter", "configureWifiState " + curState); if (targetState == WifiState.WIFI_BLOCKED) { LogAlias.d("smarter", "Target state: Blocked, shutting down wifi now, " + controlTypeToText(lastControlReason)); timerHandler.removeCallbacks(wifiEnableTask); timerHandler.removeCallbacks(wifiDisableTask); pendingWifiShutdown = false; wifiManager.setWifiEnabled(false); } else if (targetState == WifiState.WIFI_OFF) { LogAlias.d("smarter", "Target state: Off, scheduling shutdown, " + controlTypeToText(lastControlReason)); // Kill any enable pending timerHandler.removeCallbacks(wifiEnableTask); // Start the timered kill startWifiShutdown(); } } else { if (targetState == WifiState.WIFI_ON) { LogAlias.d("smarter", "Target state: On, scheduling bringup, " + controlTypeToText(lastControlReason)); startWifiEnable(); } } triggerCallbackWifiChanged(); configureBluetoothState(); } public void configureBluetoothState() { int btstate = btAdapter.getState(); BluetoothState targetstate = getShouldBluetoothBeEnabled(); // Learn time range if null //if (currentTimeRange == null) { // initialBluetoothState = (btstate != BluetoothAdapter.STATE_OFF); // LogAlias.d("smarter", "learned default bt state: " + initialBluetoothState); //} if (btstate == BluetoothAdapter.STATE_OFF) { bluetoothBlocking = false; bluetoothEnabled = false; bluetoothBlockingDevices.clear(); bluetoothConnectedDevices.clear(); if (targetstate == BluetoothState.BLUETOOTH_ON) { startBluetoothEnable(); } } else if (btstate == BluetoothAdapter.STATE_ON) { bluetoothEnabled = true; if (targetstate == BluetoothState.BLUETOOTH_BLOCKED || targetstate == BluetoothState.BLUETOOTH_OFF) { startBluetoothShutdown(); } } triggerCallbackBluetoothChanged(); // We can't get a list of connected devices, only watch } public void configureTimerangeState() { boolean wasInRange = false; // Are we in a state when we started? If not, update our bluetooth state if (currentTimeRange == null) { initialBluetoothState = getBluetoothState() != BluetoothState.BLUETOOTH_OFF; LogAlias.d("smarter", "not in a range, learned default bt state: " + initialBluetoothState); } else { wasInRange = true; } LogAlias.d("smarter", "updating time ranges"); // Figure out if we should be in one and set future alarms updateTimeRanges(); // We've transitioned out of a time range so set BT back to whatever it used to be if (currentTimeRange == null && wasInRange && btAdapter != null) { LogAlias.d("smarter", "transitioning out of time range, restoring bluetooth to previous state of: " + initialBluetoothState); if (initialBluetoothState) { btAdapter.enable(); } else { btAdapter.disable(); } } // Configure wifi and bluetooth configureWifiState(); configureBluetoothState(); } public void handleWifiP2PState(int state) { LogAlias.d("smarter", "wifi p2p state changed: " + state); } public void handleBluetoothDeviceState(BluetoothDevice d, int state) { if (state == BluetoothAdapter.STATE_CONNECTED) { SmarterBluetooth sbd = dbSource.getBluetoothBlacklisted(d); bluetoothConnectedDevices.put(d.getAddress(), sbd); if (sbd.isBlacklisted()) { LogAlias.d("smarter", "blocking bt on device " + d.getAddress() + " " + d.getName()); bluetoothBlockingDevices.put(d.getAddress(), sbd); if (!bluetoothBlocking) { bluetoothBlocking = true; configureWifiState(); } } } else { bluetoothConnectedDevices.remove(d.getAddress()); bluetoothBlockingDevices.remove(d.getAddress()); if (bluetoothBlockingDevices.size() <= 0) { bluetoothBlocking = false; configureWifiState(); } } } // Based on everything we know, should bluetooth be enabled? public BluetoothState getShouldBluetoothBeEnabled() { // We're not looking at all if (proctorWifi == false) { lastControlReason = ControlType.CONTROL_DISABLED; return BluetoothState.BLUETOOTH_IGNORE; } // Enable Bluetooth only if WiFi is disabled. // Disable it otherwise. if (getWifiState() == WifiState.WIFI_ON) { return BluetoothState.BLUETOOTH_BLOCKED; } else { return BluetoothState.BLUETOOTH_ON; } } // Based on everything we know, should wifi be enabled? // WIFI_ON - Turn it on // WIFI_OFF - Start a shutdown timer // WIFI_BLOCKED - Kill it immediately // WIFI_IDLE - Do nothing public WifiState getShouldWifiBeEnabled() { WifiState curstate = getWifiState(); SmarterSSID ssid = getCurrentSsid(); boolean tethered = getWifiTethered(); // We've never been run if (everBeenRun == false) { lastControlReason = ControlType.CONTROL_NEVERRUN; return WifiState.WIFI_IGNORE; } // We're not looking at all if (proctorWifi == false) { lastControlReason = ControlType.CONTROL_DISABLED; return WifiState.WIFI_IGNORE; } // Tethering overrides almost everything if (tethered) { LogAlias.d("smarter", "Tethering detected, ignoring wifi state"); lastControlReason = ControlType.CONTROL_TETHER; return WifiState.WIFI_IGNORE; } // Airplane mode causes us to ignore the wifi entirely, do whatever the user sets it as if (getAirplaneMode()) { LogAlias.d("smarter", "Airplane mode detected, ignoring wifi state"); lastControlReason = ControlType.CONTROL_AIRPLANE; return WifiState.WIFI_IGNORE; } // If the user wants spefically to turn it on or off via the SWM UI, do so if (userOverrideState == WifiState.WIFI_OFF) { LogAlias.d("smarter", "User-controled wifi, user wants wifi off"); lastControlReason = ControlType.CONTROL_USER; return WifiState.WIFI_BLOCKED; } if (userOverrideState == WifiState.WIFI_ON) { LogAlias.d("smarter", "User-controlled wifi, user wants wifi on"); lastControlReason = ControlType.CONTROL_USER; return WifiState.WIFI_ON; } // If we're in a time range... if (currentTimeRange != null) { // And we control wifi... if (currentTimeRange.getWifiControlled()) { // and we're supposed to shut it down if (!currentTimeRange.getWifiEnabled()) { // Always aggressively block when in a time range LogAlias.d("smarter", "Time range, aggressively disabling wifi"); lastControlReason = ControlType.CONTROL_TIME; return WifiState.WIFI_BLOCKED; /* // Harsh or gentle? if (currentTimeRange.getAggressiveManagement()) return WifiState.WIFI_BLOCKED; else return WifiState.WIFI_OFF; */ } else { // We want it on.. LogAlias.d("smarter", "Time range, enabling wifi"); lastControlReason = ControlType.CONTROL_TIME; return WifiState.WIFI_ON; } } // Otherwise we're not managing wifi in this time range so we keep going } // Bluetooth blocks learning if (bluetoothBlocking) { LogAlias.d("smarter", "Connected to bluetooth device, blocking wifi"); lastControlReason = ControlType.CONTROL_BLUETOOTH; return WifiState.WIFI_BLOCKED; } if (curstate == WifiState.WIFI_ON && (ssid != null && ssid.isBlacklisted())) { LogAlias.d("smarter", "Connected to blacklisted SSID, ignoring wifi"); lastControlReason = ControlType.CONTROL_SSIDBLACKLIST; return WifiState.WIFI_IGNORE; } if (currentTowerType == TowerType.TOWER_INVALID) { LogAlias.d("smarter", "Connected to invalid tower, ignoring wifi state"); lastControlReason = ControlType.CONTROL_TOWER; return WifiState.WIFI_IGNORE; } if (currentTowerType == TowerType.TOWER_BLOCK) { LogAlias.d("smarter", "Connected to blocked tower, turning off wifi"); lastControlReason = ControlType.CONTROL_TOWERID; return WifiState.WIFI_BLOCKED; } if (currentTowerType == TowerType.TOWER_ENABLE) { LogAlias.d("smarter", "Connected to enable tower, turning on wifi"); lastControlReason = ControlType.CONTROL_TOWER; return WifiState.WIFI_ON; } if (currentTowerType == TowerType.TOWER_UNKNOWN && curstate == WifiState.WIFI_ON) { LogAlias.d("smarter", "Connected to unknown tower, wifi is enabled, keep wifi on"); lastControlReason = ControlType.CONTROL_TOWER; return WifiState.WIFI_ON; } if (currentTowerType == TowerType.TOWER_UNKNOWN && curstate == WifiState.WIFI_IDLE) { LogAlias.d("smarter", "Connected to unknown tower, wifi is idle, we should turn it off."); lastControlReason = ControlType.CONTROL_TOWER; return WifiState.WIFI_OFF; } lastControlReason = ControlType.CONTROL_TOWER; return WifiState.WIFI_OFF; } public BluetoothState getBluetoothState() { if (btAdapter == null) return BluetoothState.BLUETOOTH_OFF; int s = btAdapter.getState(); if (s == BluetoothAdapter.STATE_ON) return BluetoothState.BLUETOOTH_ON; return BluetoothState.BLUETOOTH_OFF; } public WifiState getWifiState() { if (!proctorWifi) { lastControlReason = ControlType.CONTROL_DISABLED; return WifiState.WIFI_IGNORE; } int rawstate = wifiManager.getWifiState(); boolean rawwifienabled = false; if (rawstate == WifiManager.WIFI_STATE_ENABLED || rawstate == WifiManager.WIFI_STATE_ENABLING) rawwifienabled = true; NetworkInfo rawni = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI); boolean rawnetenabled = false; if (rawni != null && rawni.isConnected()) rawnetenabled = true; // LogAlias.d("smarter", "getwifistate wifi radio enable: " + rawwifienabled + " isConnected " + rawnetenabled); if (rawwifienabled && rawnetenabled) { return WifiState.WIFI_ON; } if (rawwifienabled && !rawnetenabled) { return WifiState.WIFI_IDLE; } return WifiState.WIFI_OFF; } public SmarterSSID getCurrentSsid() { SmarterSSID curssid = null; if (getWifiState() == WifiState.WIFI_ON) curssid = dbSource.getSsidBlacklisted(wifiManager.getConnectionInfo().getSSID()); return curssid; } public Long getCurrentTower() { return currentCellLocation.getTowerId(); } public boolean getAirplaneMode() { return Settings.System.getInt(context.getContentResolver(), Settings.System.AIRPLANE_MODE_ON, 0) != 0; } public int getSleepPolicy() { return Settings.System.getInt(context.getContentResolver(), Settings.System.WIFI_SLEEP_POLICY, Settings.System.WIFI_SLEEP_POLICY_NEVER); } public boolean getWifiStateEnabled(WifiState state) { if (state == WifiState.WIFI_ON || state == WifiState.WIFI_IDLE) return true; return false; } static public int wifiStateToTextResource(WifiState s) { switch (s) { case WIFI_BLOCKED: return R.string.wifistate_blocked; case WIFI_IDLE: return R.string.wifistate_idle; case WIFI_IGNORE: return R.string.wifistate_ignore; case WIFI_ON: return R.string.wifistate_on; case WIFI_OFF: return R.string.wifistate_off; } return R.string.wifistate_ignore; } static public int controlTypeToTextResource(ControlType t, WifiState s) { switch (t) { case CONTROL_DISABLED: return R.string.explanation_wifi_management_disabled; case CONTROL_BLUETOOTH: // BT always indicates off (for now) return R.string.explanation_wifi_disabled_bluetooth; case CONTROL_TIME: if (s == WifiState.WIFI_OFF) return R.string.explanation_wifi_time_exclude; return R.string.explanation_wifi_time_include; case CONTROL_GEOFENCE: if (s == WifiState.WIFI_OFF) return R.string.explanation_wifi_geofence_exclude; return R.string.explanation_wifi_geofence_include; case CONTROL_TOWER: if (s == WifiState.WIFI_OFF) return R.string.explanation_wifi_disabled_cell; if (s == WifiState.WIFI_IDLE) return R.string.explanation_wifi_idle_disable; return R.string.explanation_wifi_enabled_cell; case CONTROL_TOWERID: return R.string.explanation_wifi_disabled_towerid; case CONTROL_USER: if (s == WifiState.WIFI_OFF || s == WifiState.WIFI_BLOCKED) return R.string.explanation_wifi_forced_user_disabled; return R.string.explanation_wifi_forced_user_enabled; case CONTROL_SSIDBLACKLIST: return R.string.explanation_wifi_ignore_ssidblacklist; case CONTROL_AIRPLANE: return R.string.explanation_wifi_ignore_airplane; case CONTROL_TETHER: return R.string.explanation_wifi_ignore_tethered; case CONTROL_NEVERRUN: return R.string.explanation_wifi_neverrun; } return R.string.explanation_unknown; } static public String controlTypeToText(ControlType t) { switch (t) { case CONTROL_DISABLED: return "Wi-Fi management disabled"; case CONTROL_BLUETOOTH: return "Bluetooth"; case CONTROL_GEOFENCE: return "Geofence"; case CONTROL_TOWER: return "Auto-learned location"; case CONTROL_TIME: return "Time range"; case CONTROL_TOWERID: return "Tower ID"; case CONTROL_USER: return "User override"; case CONTROL_SSIDBLACKLIST: return "SSID blacklisted"; case CONTROL_AIRPLANE: return "Airplane mode"; case CONTROL_TETHER: return "Tethering"; } return "Unknown"; } public ArrayList<SmarterBluetooth> getBluetoothBlacklist() { ArrayList<SmarterBluetooth> btlist = new ArrayList<SmarterBluetooth>(); if (btAdapter == null) return btlist; Set<BluetoothDevice> btset = btAdapter.getBondedDevices(); for (BluetoothDevice d : btset) { SmarterBluetooth sbt = dbSource.getBluetoothBlacklisted(d); btlist.add(sbt); } return btlist; } public void setBluetoothBlacklist(SmarterBluetooth device, boolean blacklist, boolean enable) { dbSource.setBluetoothBlacklisted(device, blacklist, enable); if (blacklist) { if (!bluetoothBlockingDevices.containsKey(device.getBtmac())) { if (bluetoothConnectedDevices.containsKey(device.getBtmac())) { bluetoothBlockingDevices.put(device.getBtmac(), device); bluetoothBlocking = true; LogAlias.d("smarter", "after adding " + device.getBtName() + " blocking bluetooth"); configureWifiState(); } } } else { if (bluetoothBlockingDevices.containsKey(device.getBtmac())) { bluetoothBlockingDevices.remove(device.getBtmac()); if (bluetoothBlockingDevices.size() <= 0) { LogAlias.d("smarter", "after removing " + device.getBtName() + " nothing blocking in bluetooth"); bluetoothBlocking = false; configureWifiState(); } } } } public ArrayList<SmarterSSID> getSsidBlacklist() { ArrayList<SmarterSSID> blist = new ArrayList<SmarterSSID>(); List<WifiConfiguration> wic = wifiManager.getConfiguredNetworks(); if (wic == null) return blist; for (WifiConfiguration w : wic) { blist.add(dbSource.getSsidBlacklisted(w.SSID)); } return blist; } public void setSsidBlacklist(SmarterSSID ssid, boolean blacklisted) { LogAlias.d("smarter", "service backend setting ssid " + ssid.getSsid() + " blacklist " + blacklisted); dbSource.setSsidBlacklisted(ssid, blacklisted); handleCellLocation(null); configureWifiState(); } public ArrayList<SmarterSSID> getSsidTowerlist() { return dbSource.getMappedSSIDList(); } public void deleteSsidTowerMap(SmarterSSID ssid) { dbSource.deleteSsidTowerMap(ssid); handleCellLocation(null); } public void deleteCurrentTower() { if (currentCellLocation == null) return; if (currentCellLocation.getTowerId() < 0) return; dbSource.deleteSsidTowerInstance(currentCellLocation.getTowerId()); handleCellLocation(null); } public long getLastTowerMap() { return lastTowerMap; } public boolean getWifiTethered() { boolean ret = false; Method[] wmMethods = wifiManager.getClass().getDeclaredMethods(); for (Method method: wmMethods){ if (method.getName().equals("isWifiApEnabled")) { try { ret = (Boolean) method.invoke(wifiManager); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } // LogAlias.d("smarter", "tethering: " + ret); return ret; } public boolean getWifiAlwaysScanning() { boolean ret = false; Method[] wmMethods = wifiManager.getClass().getDeclaredMethods(); for (Method method: wmMethods){ if (method.getName().equals("isScanAlwaysAvailable")) { try { ret = (Boolean) method.invoke(wifiManager); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } // LogAlias.d("smarter", "tethering: " + ret); return ret; } public ArrayList<SmarterTimeRange> getTimeRangeList() { return dbSource.getTimeRangeList(); } public void deleteTimeRange(SmarterTimeRange r) { dbSource.deleteTimeRange(r); configureTimerangeState(); } public long updateTimeRange(SmarterTimeRange r) { long ud = dbSource.updateTimeRange(r); LogAlias.d("smarter", "saved time range to " + ud); configureTimerangeState(); return ud; } public long updateTimeRangeEnabled(SmarterTimeRange r) { long ud = dbSource.updateTimeRangeEnabled(r); configureTimerangeState(); return ud; } private Runnable towerCleanupTask = new Runnable() { @Override public void run() { if (performTowerPurges && dbSource != null && getWifiState() == WifiState.WIFI_ON) { SmarterSSID ssid = getCurrentSsid(); LogAlias.d("smarter", "looking to see if we should purge old towers..."); dbSource.deleteSsidTowerLastTime(ssid, purgeTowerHours * 60 * 60); } // every 10 minutes timerHandler.postDelayed(this, 1000 * 60 * 10); } }; }