package com.artfulbits.utils;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.WifiManager;
import android.os.SystemClock;
import android.util.Log;
import java.util.Iterator;
import java.util.WeakHashMap;
import java.util.logging.Logger;
/**
* Class implements periodic check for Internet connectivity on device. It is possible to use it as
* broadcast receiver on CONNECTIVITY_CHANGE and WIFI_STATE_CHANGED.<br/>
* For proper class use please do configuration steps:<br/>
* <br/>
* STEP #1 (CRITICAL) - set permissions in AndroidManifest.xml:
* <p/>
* <pre>
* <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
* </pre>
* <p/>
* STEP #2 (OPTIONAL) - configure broadcast receiver in AndroidManifest.xml:
* <p/>
* <pre>
* <receiver android:name="com.artfulbits.utils.InternetUtils" >
* <intent-filter>
* <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
* <action android:name="android.net.wifi.WIFI_STATE_CHANGED" />
* </intent-filter>
* </receiver>
* </pre>
* <p/>
* STEP #3 (CRITICAL) - place one line of code:
* <p/>
* <pre>
* InternetUtils.install( context, period, listener );
* </pre>
* <p/>
* Best place for call is {@link android.app.Application#onCreate()} class or inside main Activity.
*/
public class InternetUtils
extends BroadcastReceiver {
/* [ CONSTANTS ] ======================================================================================================================================= */
/** Our specific action for broadcasting. */
public static final String ACTION_PERIODIC_CHECK_BROADCAST = "com.artfulbits.utils.InternetUtils.PERIODIC_CHECK";
/** Send this constant if you want to disable periodic checks. */
public static final long NO_PERIODIC_CHECK = -1;
/** Major instance that do processing of all broadcasts */
private static final InternetUtils Instance = new InternetUtils();
/** Our own class Logger instance. */
private final static Logger _log = LogEx.getLogger(InternetUtils.class);
/** unique broadcast identifier. */
private static final int UniqueRequestCode = -100;
/** Maximum allowed delay in periodic checks in millis. */
private static final int MaxSchedulePeriod = 24 * 60 * 60 * 1000;
/** Collection of attached listeners. */
private static final WeakHashMap<IConnectionListener, IConnectionListener> sListeners = new WeakHashMap<InternetUtils.IConnectionListener,
InternetUtils.IConnectionListener>();
/** Known connection status. */
public enum InternetStatus {
/** No Internet connection. */
NO_CONNECTION,
/** We are in process of status check. */
CHECKING_CONNECTION,
/** Internet connected. */
CONNECTED,
/** Class not properly attached to the project. */
BAD_CONFIG
}
/* [ STATIC MEMBERS ] ================================================================================================================================== */
private static boolean sIsInstalled;
private static boolean sHasPeriodicCheck;
private static boolean sInstalledReceiver;
private static InternetStatus sCurrentState = InternetStatus.NO_CONNECTION;
/* [ STATIC METHODS ] ================================================================================================================================== */
/**
* Install Internet connection checker.
*
* @param context application context.
*/
public static void install(final Context context) {
install(context, NO_PERIODIC_CHECK, null);
}
/**
* Install Internet connection checker with specified period.
*
* @param context application context
* @param period period of checks in millis, use NO_PERIODIC_CHECK if no needs in periodic checks.
*/
public static void install(final Context context, final long period) {
install(context, period, null);
}
/**
* Install Internet connection checker with specified listener. No periodic checks.
*
* @param context application context
* @param listener listener instance.
*/
public static void install(final Context context, final IConnectionListener listener) {
install(context, NO_PERIODIC_CHECK, listener);
}
/**
* Install Internet connection checker with specified period and listener.
*
* @param context application context
* @param period period of checks in millis, use NO_PERIODIC_CHECK if no needs in periodic checks.
* @param listener listener instance.
*/
public synchronized static void install(final Context context, final long period, final IConnectionListener listener) {
ValidUtils.isNull(context, "Context instance required.");
ValidUtils.isRange((int) period, (int) NO_PERIODIC_CHECK, MaxSchedulePeriod,
"period can not be larger 24 hours or be less NO_PERIOD_CHECK");
if (!sIsInstalled) {
// get current status
forceCheck(context);
// periodic check
cancelPeriodicCheck(context);
setPeriodicCheck(context, period);
// register receiver
registerReceiver(context);
// attach listener
register(listener);
sIsInstalled = !(sCurrentState == InternetStatus.BAD_CONFIG && period == NO_PERIODIC_CHECK);
_log.info(sIsInstalled ? "Connect: installed internet connection checker." : "Connect: installation failed.");
}
}
/**
* Un-install utility class. Free system resources.
*
* @param context application context.
*/
public synchronized static void uninstall(final Context context) {
ValidUtils.isNull(context, "Context instance required.");
if (sIsInstalled) {
cancelPeriodicCheck(context);
unregisterReceiver(context);
sIsInstalled = false;
}
}
/**
* Force Internet connection check.
*
* @param context application context.
*/
public static void forceCheck(final Context context) {
ValidUtils.isNull(context, "Context instance required.");
InternetStatus status = InternetStatus.BAD_CONFIG;
final int security = context.checkCallingOrSelfPermission(android.Manifest.permission.ACCESS_NETWORK_STATE);
if (PackageManager.PERMISSION_GRANTED == security) {
final ConnectivityManager cm = Use.service(context, Context.CONNECTIVITY_SERVICE);
// grab status from WiFi, mobile and active connection
final NetworkInfo niWiFi = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
final NetworkInfo niMobile = cm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
final NetworkInfo niActive = cm.getActiveNetworkInfo();
status = (null != niWiFi && niWiFi.isConnected()) ? InternetStatus.CONNECTED : InternetStatus.NO_CONNECTION;
_log.info("Connect: WiFi status - " + status);
if (InternetStatus.NO_CONNECTION == status) {
status = (null != niMobile && niMobile.isConnected()) ? InternetStatus.CONNECTED
: InternetStatus.NO_CONNECTION;
_log.info("Connect: Mobile status - " + status);
}
if (InternetStatus.NO_CONNECTION == status) {
status = (null != niActive && niActive.isConnected()) ? InternetStatus.CONNECTED
: InternetStatus.NO_CONNECTION;
_log.info("Connect: Active status - " + status);
}
}
updateStatus(status);
}
/**
* Attach listener instance. We keep only weak references on listeners, so it is no actual need to
* call 'unregister'.
*
* @param listener listener instance.
*/
public static void register(final IConnectionListener listener) {
if (null != listener) {
sListeners.put(listener, listener);
}
}
/**
* Detach listener instance.
*
* @param listener listener instance.
*/
public static void unregister(final IConnectionListener listener) {
if (null != listener) {
sListeners.remove(listener);
}
}
/**
* Get current Internet connection state.
*
* @return last known connection state.
*/
public static InternetStatus getCurrentState() {
return sCurrentState;
}
/**
* Is Internet connection checker installed.
*
* @return True - installed, otherwise False.
*/
public static boolean isInstalled() {
return sIsInstalled;
}
/**
* Is Internet connection checker installed with periodic checks.
*
* @return True - periodic check set, otherwise false.
*/
public static boolean hasPeriodicCheck() {
return sHasPeriodicCheck;
}
/* [ IMPLEMENTATION & HELPERS ] ======================================================================================================================== */
/** {@inheritDoc} */
@Override
public void onReceive(final Context context, final Intent intent) {
// redirect calls from another instances to our main one
if (!Instance.equals(this)) {
Instance.onReceive(context, intent);
return;
}
final String action = intent.getAction();
_log.info("Connect: Received broadcast Action: " + action);
if (ACTION_PERIODIC_CHECK_BROADCAST.equals(action) ||
ConnectivityManager.CONNECTIVITY_ACTION.equals(action) ||
WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
updateStatus(InternetStatus.CHECKING_CONNECTION);
forceCheck(context);
}
}
/**
* Update Internet connection status and notify all listeners about changes.
*
* @param status
*/
private static void updateStatus(final InternetStatus status) {
final InternetStatus old = sCurrentState;
sCurrentState = status;
// do not do any callbacks if new status repeats previous status
if (old == status) {
return;
}
// report into LOG only if states are different
_log.info("Connect: current " + status + ", old " + old);
final Iterator<IConnectionListener> iterator = sListeners.keySet().iterator();
while (iterator.hasNext()) {
final IConnectionListener listener = iterator.next();
try {
listener.onStateChanged(old, status);
} catch (Throwable ignored) {
_log.warning("Connect: user listener raise exception. Exception: " + ignored.getMessage());
_log.warning("Connect: exception stack: " + Log.getStackTraceString(ignored));
}
}
}
/**
* Schedule periodic check.
*
* @param context application context.
* @param period defined by user period.
*/
private static void setPeriodicCheck(final Context context, long period) {
// no periodic checks
if (NO_PERIODIC_CHECK == period) {
_log.warning("Connect: no periodic checks.");
return;
}
final AlarmManager am = Use.service(context, Context.ALARM_SERVICE);
final long current = SystemClock.elapsedRealtime();
final Intent intent = new Intent(ACTION_PERIODIC_CHECK_BROADCAST);
final PendingIntent pi = PendingIntent.getBroadcast(context, UniqueRequestCode, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
am.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, current + period, period, pi);
sHasPeriodicCheck = true;
_log.info("Connect: check internet connection with period: " + period);
}
/**
* Register broadcast receiver in system.
*
* @param context application context.
*/
private synchronized static void registerReceiver(final Context context) {
if (!sInstalledReceiver) {
final IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_PERIODIC_CHECK_BROADCAST);
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
context.registerReceiver(Instance, filter);
sInstalledReceiver = true;
}
}
/**
* Unregister broadcast receiver in system.
*
* @param context application context.
*/
private synchronized static void unregisterReceiver(final Context context) {
if (sInstalledReceiver) {
context.unregisterReceiver(Instance);
sInstalledReceiver = false;
}
}
/**
* Cancel scheduling of periodic check.
*
* @param context application context.
*/
private static void cancelPeriodicCheck(final Context context) {
final AlarmManager am = Use.service(context, Context.ALARM_SERVICE);
final Intent intent = new Intent(ACTION_PERIODIC_CHECK_BROADCAST);
final PendingIntent pi = PendingIntent.getBroadcast(context, UniqueRequestCode, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
am.cancel(pi);
sHasPeriodicCheck = false;
}
/* [ NESTED DECLARATIONS ] ============================================================================================================================= */
/** Callback interface for everyone who wants to listen internet connection state changes. */
public interface IConnectionListener {
/**
* Internet connection state changed.
*
* @param old previous state
* @param new$ new state
*/
void onStateChanged(final InternetStatus old, final InternetStatus new$);
}
}