/* This file is part of Project MAXS. MAXS and its modules 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. MAXS 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 MAXS. If not, see <http://www.gnu.org/licenses/>. */ package org.projectmaxs.transport.xmpp.xmppservice; import java.util.concurrent.atomic.AtomicInteger; import org.jivesoftware.smack.tcp.BundleAndDefer; import org.jivesoftware.smack.tcp.BundleAndDeferCallback; import org.jivesoftware.smack.tcp.XMPPTCPConnection; import org.projectmaxs.shared.global.util.Log; import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.ConnectivityManager; import android.net.ConnectivityManager.OnNetworkActiveListener; import android.os.BatteryManager; import android.os.Build; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; /** * Bundle And Defer means that Smack will invoke the * {@link BundleAndDeferCallback#getBundleAndDeferMillis(BundleAndDefer)} callback once it is about * to send a stanza that could be deferred. If it is deferred, all following stanzas will get * bundled. The return value of the callback is the time in milliseconds the stanza, and all * following, will get deferred. * <p> * Together with he callback, Smack hands out an reference to a {@link BundleAndDefer} instance, * which allows us to abort the current deferring and send all bundled stanzas right away. We do * this once Android reports that the network become active. * </p> * */ public class XMPPBundleAndDefer { /** * How long Smack defers outgoing stanzas if the current network is in high power (active) * state. */ private static final int ACTIVE_STATE_DEFER_MILLIS = 150; /** * How long Smack defers outgoing stanzas if the current network is not in high power (inactive) * state. */ private static final int INACTIVE_STATE_DEFER_MILLIS = 23 * 1000; private static final Log LOG = Log.getLog(); /** * Integer value indication when not to BAD (BundleAndDefer). If its value is greater zero, then * bundle and * defer while not take place. */ private static final AtomicInteger sDoNotBadInt = new AtomicInteger(); /** * The current BundleAndDefer instance, which can be used to stop the current bundle and defer * process by Smack. Once it's stopped, the bundled stanzas so far will be send immediately. */ private static BundleAndDefer currentBundleAndDefer; @TargetApi(21) public static void initialize(final Context context) { final ConnectivityManager connectivityManager = (ConnectivityManager) context .getSystemService(Context.CONNECTIVITY_SERVICE); BundleAndDeferCallback bundleAndDeferCallback = new BundleAndDeferCallback() { @Override public int getBundleAndDeferMillis(BundleAndDefer bundleAndDefer) { if (sDoNotBadInt.get() > 0) { return 0; } XMPPBundleAndDefer.currentBundleAndDefer = bundleAndDefer; String networkState = "unknown (needs Android >= 5.0)"; boolean networkActive = false; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (connectivityManager.isDefaultNetworkActive()) { networkState = "active"; networkActive = true; } else { networkState = "incative"; } } boolean isPlugged = isPlugged(context); final int deferMillis; if (isPlugged || networkActive) { deferMillis = ACTIVE_STATE_DEFER_MILLIS; } else { deferMillis = INACTIVE_STATE_DEFER_MILLIS; } if (LOG.isDebugLogEnabled()) { LOG.d("Returning " + deferMillis + "ms in getBundleAndDeferMillis(). Network is " + networkState + ", batteryPlugged: " + isPlugged); } return deferMillis; } }; XMPPTCPConnection.setDefaultBundleAndDeferCallback(bundleAndDeferCallback); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { connectivityManager.addDefaultNetworkActiveListener(new OnNetworkActiveListener() { @Override public void onNetworkActive() { stopCurrentBundleAndDefer(); } }); } } public static void stopCurrentBundleAndDefer() { final BundleAndDefer localCurrentbundleAndDefer = currentBundleAndDefer; if (localCurrentbundleAndDefer == null) { return; } LOG.d("stopCurrentBundleAndDefer() invoked and currentbundleAndDefer not null, calling stopCurrentBundleAndDefer()"); localCurrentbundleAndDefer.stopCurrentBundleAndDefer(); } /** * Disables bundle and defer until {@link #enableBundleAndDefer()} is called. */ public static void disableBundleAndDefer() { sDoNotBadInt.incrementAndGet(); stopCurrentBundleAndDefer(); } /** * Re-enables bundle and defer. {@link #disableBundleAndDefer()} must be called prior calling * this. */ public static void enableBundleAndDefer() { sDoNotBadInt.decrementAndGet(); } private static final IntentFilter BATTERY_CHANGED_INTENT_FILTER = new IntentFilter( Intent.ACTION_BATTERY_CHANGED); @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) private static boolean isPlugged(Context context) { // BATTERY_CHANGED_INTENT is a sticky broadcast intent final Intent intent = context.registerReceiver(null, BATTERY_CHANGED_INTENT_FILTER); final int plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1); boolean isPlugged; switch (plugged) { case BatteryManager.BATTERY_PLUGGED_AC: case BatteryManager.BATTERY_PLUGGED_USB: isPlugged = true; break; default: isPlugged = false; break; } if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) { isPlugged |= plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS; } return isPlugged; } }