/*
* Copyright 2013 Thomas Hoffmann
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.j4velin.wifiAutoOff;
import android.annotation.SuppressLint;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.NetworkInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.p2p.WifiP2pInfo;
import android.net.wifi.p2p.WifiP2pManager;
import android.os.Build;
import android.os.PowerManager;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.widget.Toast;
/**
* Class for receiving various events and react on them.
*/
public class Receiver extends BroadcastReceiver {
public final static String LOCATION_ENTERED_ACTION = "LOCATION_ENTERED";
public final static String EXTRA_LOCATION_NAME = "name";
private static NetworkInfo.State previousState = null;
private static final int TIMER_SCREEN_OFF = 1;
private static final int TIMER_NO_NETWORK = 2;
static final int TIMER_ON_AT = 3;
static final int TIMER_OFF_AT = 4;
static final int TIMER_ON_EVERY = 5;
static final int TIMEOUT_NO_NETWORK = 5;
static final int TIMEOUT_SCREEN_OFF = 10;
static final int ON_EVERY_TIME_MIN = 120;
static final String ON_AT_TIME = "8:00";
static final String OFF_AT_TIME = "22:00";
/**
* Starts one of the timers to turn WiFi off
*
* @param context the context
* @param id TIMER_SCREEN_OFF or TIMER_NO_NETWORK
* @param time in min
*/
private void startTimer(final Context context, int id, int time) {
String action = (id == TIMER_SCREEN_OFF) ? "SCREEN_OFF_TIMER" : "NO_NETWORK_TIMER";
Intent timerIntent =
new Intent(context, Receiver.class).putExtra("timer", id).setAction(action);
if (PendingIntent.getBroadcast(context, id, timerIntent, PendingIntent.FLAG_NO_CREATE) ==
null) {
if (Build.VERSION.SDK_INT >= 19) {
APILevel19Wrapper.setExactTimer(context, AlarmManager.RTC_WAKEUP,
System.currentTimeMillis() + 60000 * time, PendingIntent
.getBroadcast(context, id, timerIntent,
PendingIntent.FLAG_UPDATE_CURRENT));
} else {
((AlarmManager) context.getSystemService(Context.ALARM_SERVICE))
.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + 60000 * time,
PendingIntent.getBroadcast(context, id, timerIntent,
PendingIntent.FLAG_UPDATE_CURRENT));
}
Log.insert(context, context.getString(
id == TIMER_SCREEN_OFF ? R.string.event_screen_off_timer :
R.string.event_no_network_timer, time), Log.Type.TIMER);
} else if (BuildConfig.DEBUG) {
Logger.log("timer for action " + action + " already set");
}
}
/**
* Stops the timer
*
* @param context the context
* @param id TIMER_SCREEN_OFF or TIMER_NO_NETWORK
* @return true, if timer was actually active
*/
private boolean stopTimer(final Context context, int id) {
Intent timerIntent = new Intent(context, Receiver.class).putExtra("timer", id)
.setAction(id == TIMER_SCREEN_OFF ? "SCREEN_OFF_TIMER" : "NO_NETWORK_TIMER");
PendingIntent pendingIntent =
PendingIntent.getBroadcast(context, id, timerIntent, PendingIntent.FLAG_NO_CREATE);
if (pendingIntent != null) {
((AlarmManager) context.getSystemService(Context.ALARM_SERVICE)).cancel(pendingIntent);
pendingIntent.cancel();
Log.insert(context, (id == TIMER_SCREEN_OFF) ? R.string.event_screen_off_canceled :
R.string.event_no_network_canceled, Log.Type.TIMER);
}
return pendingIntent != null;
}
/**
* Get default shared preferences
*
* @param context the context
* @return default SharedPreferences for given context
*/
@SuppressLint("InlinedApi")
private static SharedPreferences getSharedPreferences(final Context context) {
String prefFileName = context.getPackageName() + "_preferences";
return context.getSharedPreferences(prefFileName, Context.MODE_MULTI_PROCESS);
}
/**
* Changes the WiFi state
*
* @param context the context
* @param on true to turn WiFi on, false to turn it off
*/
@SuppressWarnings("deprecation")
private static void changeWiFi(final Context context, boolean on) {
SharedPreferences prefs = getSharedPreferences(context);
if (on && prefs.getBoolean("airplane", true)) {
// check for airplane mode
if (BuildConfig.DEBUG) Logger.log("checking for airplane mode");
try {
if (android.os.Build.VERSION.SDK_INT >=
android.os.Build.VERSION_CODES.JELLY_BEAN_MR1 ?
APILevel17Wrapper.isAirplaneModeOn(context) : Settings.System
.getInt(context.getContentResolver(), Settings.System.AIRPLANE_MODE_ON) ==
1) {
Log.insert(context, R.string.event_airplane, Log.Type.AIRPLANE_MODE);
return;
}
} catch (final SettingNotFoundException e) {
// not airplane setting found? Handle like not in airplane mode
// then
if (BuildConfig.DEBUG) Logger.log(e);
e.printStackTrace();
}
}
try {
WifiManager wm = ((WifiManager) context.getSystemService(Context.WIFI_SERVICE));
// do we need to change at all?
if (wm.isWifiEnabled() != on) {
Log.insert(context, on ? R.string.event_turn_on : R.string.event_turn_off,
on ? Log.Type.WIFI_ON : Log.Type.WIFI_OFF);
wm.setWifiEnabled(on);
}
} catch (Exception e) {
Toast.makeText(context, "Can not change WiFi state: " + e.getClass().getName(),
Toast.LENGTH_LONG).show();
}
}
@SuppressLint("InlinedApi")
@Override
public void onReceive(final Context context, final Intent intent) {
final String action = intent.getAction();
if (BuildConfig.DEBUG) Logger.log("received: " + action);
SharedPreferences prefs = getSharedPreferences(context);
if (intent.hasExtra("timer")) {
// one of the timers expired -> turn wifi off
changeWiFi(context, false);
stopTimer(context, intent.getIntExtra("timer", 0));
} else if (intent.hasExtra("changeWiFi")) {
// for "ON AT" or "OFF AT" options
changeWiFi(context, intent.getBooleanExtra("changeWiFi", false));
Start.createTimers(context);
} else {
switch (action) {
case LOCATION_ENTERED_ACTION:
if (!((WifiManager) context.getSystemService(Context.WIFI_SERVICE))
.isWifiEnabled()) {
Log.insert(context, context.getString(R.string.event_location,
intent.getStringExtra(EXTRA_LOCATION_NAME)),
Log.Type.LOCATION_ENTERED);
if (prefs.getBoolean("off_no_network", true)) {
// start the timer before actually turning on the WiFi to set the NO_NETWORK
// timer to at least 10 min. The set timer in the following WIFI_STATE_CHANGED_ACTION
// will then have no effect, as the timer is already set
startTimer(context, TIMER_NO_NETWORK, Math.max(10,
prefs.getInt("no_network_timeout", TIMEOUT_NO_NETWORK)));
}
changeWiFi(context, true);
} // else: WiFi is already enabled, do nothing
break;
case ScreenChangeDetector.SCREEN_OFF_ACTION:
if (prefs.getBoolean("off_screen_off", true)) {
if (!prefs.getBoolean("ignore_screen_off", false)) {
// screen went off -> start TIMER_SCREEN_OFF
startTimer(context, TIMER_SCREEN_OFF,
prefs.getInt("screen_off_timeout", TIMEOUT_SCREEN_OFF));
} else {
Log.insert(context, R.string.event_display_off_ignored_ac,
Log.Type.SCREEN_OFF);
}
}
break;
case UnlockReceiver.USER_PRESENT_ACTION:
case ScreenChangeDetector.SCREEN_ON_ACTION:
Log.insert(context, R.string.event_unlocked, Log.Type.UNLOCKED);
// user unlocked the device -> stop TIMER_SCREEN_OFF, might turn on
// WiFi
stopTimer(context, TIMER_SCREEN_OFF);
if (prefs.getBoolean("on_unlock", true)) {
boolean noNetTimer = stopTimer(context, TIMER_NO_NETWORK);
if (((WifiManager) context.getSystemService(Context.WIFI_SERVICE))
.isWifiEnabled()) {
if (noNetTimer && prefs.getBoolean("off_no_network", true)) {
// if WiFi is already turned on, just restart the NO_NETWORK timer
startTimer(context, TIMER_NO_NETWORK,
prefs.getInt("no_network_timeout", TIMEOUT_NO_NETWORK));
}
} else {
changeWiFi(context, true);
}
}
break;
case WifiManager.NETWORK_STATE_CHANGED_ACTION:
final NetworkInfo nwi =
intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
if (nwi == null) return;
if (nwi.isConnected()) {
if (!nwi.getState().equals(previousState)) {
Log.insert(context, R.string.event_connected, Log.Type.WIFI_CONNECTED);
}
stopTimer(context, TIMER_NO_NETWORK);
} else if (nwi.getState().equals(NetworkInfo.State.DISCONNECTED)) {
if (!nwi.getState().equals(previousState)) {
Log.insert(context, R.string.event_disconnected,
Log.Type.WIFI_DISCONNECTED);
}
if (prefs.getBoolean("off_no_network", true)) {
startTimer(context, TIMER_NO_NETWORK,
prefs.getInt("no_network_timeout", TIMEOUT_NO_NETWORK));
}
}
previousState = nwi.getState();
break;
case WifiManager.WIFI_STATE_CHANGED_ACTION:
if (intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
WifiManager.WIFI_STATE_UNKNOWN) == WifiManager.WIFI_STATE_ENABLED) {
Log.insert(context, R.string.event_enabled, Log.Type.WIFI_ON);
if (prefs.getBoolean("off_no_network", true)) {
startTimer(context, TIMER_NO_NETWORK,
prefs.getInt("no_network_timeout", TIMEOUT_NO_NETWORK));
}
if (prefs.getBoolean("off_screen_off", true) &&
((Build.VERSION.SDK_INT < 20 && !((PowerManager) context
.getSystemService(Context.POWER_SERVICE)).isScreenOn()) ||
(Build.VERSION.SDK_INT >= 20 &&
!APILevel20Wrapper.isScreenOn(context)))) {
startTimer(context, TIMER_SCREEN_OFF,
prefs.getInt("screen_off_timeout", TIMEOUT_SCREEN_OFF));
}
} else if (intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
WifiManager.WIFI_STATE_UNKNOWN) == WifiManager.WIFI_STATE_DISABLED) {
Log.insert(context, R.string.event_disabled, Log.Type.WIFI_OFF);
stopTimer(context, TIMER_SCREEN_OFF);
stopTimer(context, TIMER_NO_NETWORK);
}
break;
case WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION:
// wifi direct connection changed
NetworkInfo nwi2 = intent.getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO);
WifiP2pInfo winfo =
intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO);
if (BuildConfig.DEBUG) Logger.log("new P2P network state: " + nwi2.getState());
if (BuildConfig.DEBUG && Build.VERSION.SDK_INT >= 14)
Logger.log("P2P group formed: " + APILevel14Wrapper.groupFormed(winfo));
if (nwi2.isConnected() ||
(Build.VERSION.SDK_INT >= 14 && APILevel14Wrapper.groupFormed(winfo))) {
if (!nwi2.getState().equals(previousState)) {
Log.insert(context, R.string.event_connected, Log.Type.WIFI_CONNECTED);
}
stopTimer(context, TIMER_NO_NETWORK);
} else if (nwi2.getState().equals(NetworkInfo.State.DISCONNECTED)) {
if (!nwi2.getState().equals(previousState)) {
Log.insert(context, R.string.event_disconnected,
Log.Type.WIFI_DISCONNECTED);
}
if (prefs.getBoolean("off_no_network", true)) {
startTimer(context, TIMER_NO_NETWORK,
prefs.getInt("no_network_timeout", TIMEOUT_NO_NETWORK));
}
}
previousState = nwi2.getState();
break;
case Intent.ACTION_POWER_CONNECTED:
// connected to external power supply
if (prefs.getBoolean("power_connected", false)) {
Log.insert(context, R.string.event_ac, Log.Type.AC_CONNECTED);
changeWiFi(context, true);
if (prefs.getBoolean("off_screen_off",
true)) { // ignore display off events while charging
stopTimer(context, TIMER_SCREEN_OFF);
prefs.edit().putBoolean("ignore_screen_off", true).apply();
if (BuildConfig.DEBUG) Logger.log("ignore screen off events");
}
}
break;
case Intent.ACTION_POWER_DISCONNECTED:
// disconnected from external power supply
if (prefs.getBoolean("power_connected", false)) {
Log.insert(context, R.string.event_ac_disconnected,
Log.Type.AC_DISCONNECTED);
// do we need to start the screen off timer?
if (prefs.getBoolean("off_screen_off", true)) {
prefs.edit().putBoolean("ignore_screen_off", false).apply();
if (BuildConfig.DEBUG)
Logger.log("dont ignore screen off event any more");
if ((Build.VERSION.SDK_INT < 20 && !((PowerManager) context
.getSystemService(Context.POWER_SERVICE)).isScreenOn()) ||
(Build.VERSION.SDK_INT >= 20 &&
!APILevel20Wrapper.isScreenOn(context))) {
if (BuildConfig.DEBUG) Logger.log("screen is off -> start timer");
startTimer(context, TIMER_SCREEN_OFF,
prefs.getInt("screen_off_timeout", TIMEOUT_SCREEN_OFF));
}
}
}
break;
}
}
}
}