/**
Copyright 2015 Tim Engler, Rareventure LLC
This file is part of Tiny Travel Tracker.
Tiny Travel Tracker is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Tiny Travel Tracker is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Tiny Travel Tracker. If not, see <http://www.gnu.org/licenses/>.
*/
package com.rareventure.android;
import java.util.List;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiManager;
import android.util.Log;
import com.rareventure.android.AndroidPreferenceSet.AndroidPreferences;
import com.rareventure.gps2.GTG;
/**
* Finds wireless connections. Don't let the bastards keep you down!
*/
public class WirelessAdapter {
private WifiManager wfm;
private Context ctx;
private IntentFilter filter;
private Mode mode = Mode.NOOP;
private Listener listener;
private BroadcastReceiver wifiBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
/* ttt_installer:remove_line */Log.d(GTG.TAG,"Received intent "+intent);
if(intent.getAction().equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION))
{
//any scan that happens, regardless if we did it, we record.. we might as well
//it's possible for this method to return null, so we have to be careful
List<ScanResult> l = wfm.getScanResults();
if(l != null)
listener.notifyWifiScan(System.currentTimeMillis(), l);
synchronized(WirelessAdapter.this)
{
goToSleep();
}
return;
}
//otherwise we got a wifi change, so we update our state and do whatever
updateState();
}
};
private Preferences prefs = new Preferences();
//PERF: maybe we should not use it's own thread for this. Don't want to use a HandlerTimer since that
//requires a UI thread and we may not be called by one
private Runnable timeoutRunnable = new Runnable() {
@Override
public void run() {
while(true)
{
//after the timeout period, regardless of where we are in the cycle, we
//shutdown and noop
synchronized(WirelessAdapter.this)
{
if(isRunning)
{
try {
if(timeoutMs != 0)
{
WirelessAdapter.this.wait(timeoutMs);
}
else WirelessAdapter.this.wait();
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
}
if(timeoutMs != 0 && System.currentTimeMillis() > timeoutMs || !isRunning)
{
/* ttt_installer:remove_line */Log.d(GTG.TAG,"Wireless Timeout");
goToSleep();
}
if(!isRunning)
break;
}
}
}
};
private boolean weEnabledWifi;
private long timeoutMs;
private Thread thread;
private boolean isRunning = true;
public WirelessAdapter(Context ctx, Listener listener)
{
this.ctx = ctx;
this.listener = listener;
wfm = (WifiManager) ctx.getSystemService(Context.WIFI_SERVICE);
filter = new IntentFilter();
filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
ctx.registerReceiver(wifiBroadcastReceiver, filter);
thread =new Thread(timeoutRunnable, "Wireless Adapter Timeout Thread");
thread.setDaemon(true);
thread.start();
}
public synchronized void shutdown()
{
isRunning = false;
this.notify();
ctx.unregisterReceiver(wifiBroadcastReceiver);
}
private synchronized void goToSleep() {
//if we enabled wifi, we shut it off
if(weEnabledWifi)
{
wfm.setWifiEnabled(false);
/* ttt_installer:remove_line */Log.d(GTG.TAG,"Shutdown WIFI");
weEnabledWifi = false;
}
mode = Mode.NOOP;
timeoutMs = 0;
/* ttt_installer:remove_line */Log.d(GTG.TAG,"Go To sleep");
}
protected synchronized void updateState() {
if(mode == Mode.NOOP) //if we are not doing anything
return; //then quit immediately
int wifiState = wfm.getWifiState();
if(wifiState == WifiManager.WIFI_STATE_DISABLING ||
wifiState == WifiManager.WIFI_STATE_UNKNOWN ||
wifiState == WifiManager.WIFI_STATE_ENABLING)
{
//if the wifi is in a changing state, lets wait until it is in a final
//state before doing anything (we will be called back when the state changes)
return;
}
else if(wifiState != WifiManager.WIFI_STATE_ENABLED &&
wifiState != WifiManager.WIFI_STATE_DISABLED)
{
Log.e(GTG.TAG, "Got a weird, unknown state, "+wifiState); //TODO 3: what to do for errors
goToSleep();//give up
return;
}
if(mode == Mode.START)
{
//wifi is shutdown, so we enable it
if(wifiState != WifiManager.WIFI_STATE_ENABLED)
{
mode = Mode.ENABLING_WIFI;
if(!wfm.setWifiEnabled(true)) //if we can't enable wifi
{
Log.e(GTG.TAG, "Can't enable wifi!"); //TODO 3: What to do about errors
goToSleep();//give up
return;
}
//note that we enabled it. We will only disable it if we enabled it ourselves
//of course this is not perfect because someone else could enable at about the
//same time we did (not this code but some other code in some app, probably far far away),
//but that is the best we can do with the API we have
weEnabledWifi = true;
//wait until it is enabled (we will be called back when this happens)
return;
}
//the state is already enabled, so we can go ahead and scan
mode = Mode.ENABLING_WIFI;
}
if(mode == Mode.ENABLING_WIFI)
{
//if it is enabled, lets go ahead and scan
if(wifiState == WifiManager.WIFI_STATE_ENABLED)
{
mode = Mode.BEFORE_SCAN_START;
}
else //it must be disabled, let's leave it alone and let it sort itself out
//if necessary, the timer will change us back to a noop
{
return;
}
}
if(mode == Mode.BEFORE_SCAN_START)
{
if(wifiState == WifiManager.WIFI_STATE_DISABLED)
{
//something strange happened... we should be ready to scan. so let's give up
//the timer will shut us off
return;
}
//now try to scan
mode = Mode.AFTER_SCAN_START;
if(!wfm.startScan())
{
Log.e(GTG.TAG, "Can't start a scan!"); //TODO 3: What to do about errors
//we can't start a scan, so lets give up. The timer will shut us off
return;
}
//wait for the scan results to be retrieved... BroadCastReceiver.onReceive will handle this
return;
}
if(mode == Mode.AFTER_SCAN_START)
{
//noop... we just wait until the scan is complete (which is handled by BroadCastReciever.onReceive)
}
}
public synchronized void doScan()
{
// Log.d(GTG.TAG,"Do scan");
timeoutMs = System.currentTimeMillis() + prefs.wirelessDoScanTimeoutMs;
notify(); // start the timeout timer;
if(mode == Mode.NOOP)
mode = Mode.START;
updateState();
}
public static interface Listener
{
void notifyWifiScan(long timeMs, List<ScanResult> scanResults);
}
public enum Mode {
START, NOOP, AFTER_SCAN_START, ENABLING_WIFI, BEFORE_SCAN_START}
public static class Preferences implements AndroidPreferences
{
/**
* Amount of time before we give up scanning and realize that android isn't going
* to call us back.
*/
public long wirelessDoScanTimeoutMs = 30000;
}
}