All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jivesoftware.smackx.ping.android.ServerPingWithAlarmManager Maven / Gradle / Ivy

The newest version!
/**
 *
 * Copyright © 2014-2024 Florian Schmaus
 *
 * 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 org.jivesoftware.smackx.ping.android;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.logging.Logger;

import org.jivesoftware.smack.ConnectionCreationListener;
import org.jivesoftware.smack.Manager;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPConnectionRegistry;
import org.jivesoftware.smack.util.Async;
import org.jivesoftware.smackx.ping.PingManager;

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.os.Build;
import android.os.SystemClock;

/**
 * Send automatic server pings with the help of {@link AlarmManager}.
 * 

* Smack's {@link PingManager} uses a ScheduledThreadPoolExecutor to schedule the * automatic server pings, but on Android, those scheduled pings are not reliable. This is because * the Android device may go into deep sleep where the system will not continue to run this causes *

    *
  • the system time to not move forward, which means that the time spent in deep sleep is not * counted towards the scheduled delay time
  • *
  • the scheduled Runnable is not run while the system is in deep sleep.
  • *
*

* That is the reason Android comes with an API to schedule those tasks: AlarmManager. Which this * class uses to determine every 30 minutes if a server ping is necessary. The interval of 30 * minutes is the ideal trade-off between reliability and low resource (battery) consumption. *

*

* In order to use this class you need to call {@link #onCreate(Context)} once, for example * in the onCreate() method of your Service holding the XMPPConnection. And to avoid * leaking any resources, you should call {@link #onDestroy()} when you no longer need any of its * functionality. *

*/ public final class ServerPingWithAlarmManager extends Manager { private static final Logger LOGGER = Logger.getLogger(ServerPingWithAlarmManager.class .getName()); private static final String PING_ALARM_ACTION = "org.igniterealtime.smackx.ping.ACTION"; private static final Map INSTANCES = new WeakHashMap(); static { XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() { @Override public void connectionCreated(XMPPConnection connection) { getInstanceFor(connection); } }); } /** * Get the instance of this manager for the given connection. * * @param connection the connection. * @return the instance of this manager for the given connection. */ public static synchronized ServerPingWithAlarmManager getInstanceFor(XMPPConnection connection) { ServerPingWithAlarmManager serverPingWithAlarmManager = INSTANCES.get(connection); if (serverPingWithAlarmManager == null) { serverPingWithAlarmManager = new ServerPingWithAlarmManager(connection); INSTANCES.put(connection, serverPingWithAlarmManager); } return serverPingWithAlarmManager; } private boolean mEnabled = true; private ServerPingWithAlarmManager(XMPPConnection connection) { super(connection); } /** * If enabled, ServerPingWithAlarmManager will call {@link PingManager#pingServerIfNecessary()} * for the connection of this instance every half hour. * * @param enabled whether or not this manager is should be enabled or not. */ public void setEnabled(boolean enabled) { mEnabled = enabled; } /** * Check if this manager is enabled. * * @return true if this manager is enabled, false otherwise. */ public boolean isEnabled() { return mEnabled; } private static final BroadcastReceiver ALARM_BROADCAST_RECEIVER = new BroadcastReceiver() { @Override @SuppressWarnings("LockOnNonEnclosingClassLiteral") public void onReceive(Context context, Intent intent) { LOGGER.fine("Ping Alarm broadcast received"); Set> managers; synchronized (ServerPingWithAlarmManager.class) { // Make a copy to avoid ConcurrentModificationException when // iterating directly over INSTANCES and the Set is modified // concurrently by creating a new ServerPingWithAlarmManager. managers = new HashSet<>(INSTANCES.entrySet()); } for (Map.Entry entry : managers) { XMPPConnection connection = entry.getKey(); if (entry.getValue().isEnabled()) { LOGGER.fine("Calling pingServerIfNecessary for connection " + connection); final PingManager pingManager = PingManager.getInstanceFor(connection); // Android BroadcastReceivers have a timeout of 60 seconds. // The connections reply timeout may be higher, which causes // timeouts of the broadcast receiver and a subsequent ANR // of the App of the broadcast receiver. We therefore need // to call pingServerIfNecessary() in a new thread to avoid // this. It could happen that the device gets back to sleep // until the Thread runs, but that's a risk we are willing // to take into account as it's unlikely. Async.go(new Runnable() { @Override public void run() { pingManager.pingServerIfNecessary(); } }, "PingServerIfNecessary (" + connection.getConnectionCounter() + ')'); } else { LOGGER.fine("NOT calling pingServerIfNecessary (disabled) on connection " + connection.getConnectionCounter()); } } } }; private static Context sContext; private static PendingIntent sPendingIntent; private static AlarmManager sAlarmManager; /** * Register a pending intent with the AlarmManager to be broadcast every half hour and * register the alarm broadcast receiver to receive this intent. The receiver will check all * known questions if a ping is Necessary when invoked by the alarm intent. * * @param context an Android context. */ public static void onCreate(Context context) { sContext = context; int receiverFlags = 0; if (Build.VERSION.SDK_INT >= 34) { receiverFlags |= 4; // RECEIVER_NOT_EXPORTED } context.registerReceiver(ALARM_BROADCAST_RECEIVER, new IntentFilter(PING_ALARM_ACTION), receiverFlags); sAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); int pendingIntentFlags = 0; if (Build.VERSION.SDK_INT >= 23) { pendingIntentFlags |= PendingIntent.FLAG_IMMUTABLE; } sPendingIntent = PendingIntent.getBroadcast(context, 0, new Intent(PING_ALARM_ACTION), pendingIntentFlags); sAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + AlarmManager.INTERVAL_HALF_HOUR, AlarmManager.INTERVAL_HALF_HOUR, sPendingIntent); } /** * Unregister the alarm broadcast receiver and cancel the alarm. */ public static void onDestroy() { sContext.unregisterReceiver(ALARM_BROADCAST_RECEIVER); sAlarmManager.cancel(sPendingIntent); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy