/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
*
* 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 net.java.sip.communicator.impl.sysactivity;
import java.util.*;
import net.java.sip.communicator.service.sysactivity.*;
import net.java.sip.communicator.service.sysactivity.event.*;
import net.java.sip.communicator.util.Logger;
import org.jitsi.util.*;
/**
* Service implementation listens for computer changes as sleeping, network
* change, inactivity.
*
* @author Damian Minkov
*/
public class SystemActivityNotificationsServiceImpl
implements SystemActivityNotifications.NotificationsDelegate,
SystemActivityNotificationsService,
Runnable
{
/**
* The Logger used by this
* SystemActivityNotificationsServiceImpl for logging output.
*/
private final Logger logger
= Logger.getLogger(SystemActivityNotificationsServiceImpl.class);
/**
* The thread dispatcher of network change events.
*/
private final SystemActivityEventDispatcher eventDispatcher
= new SystemActivityEventDispatcher();
/**
* A list of listeners registered for idle events.
*/
private final Map idleChangeListeners
= new HashMap();
/**
* Listeners which are fired for idle state and which will be fired
* with idle end when needed.
*/
private final List listenersInIdleState
= new ArrayList();
/**
* The interval between checks when not idle.
*/
private static final int CHECK_FOR_IDLE_DEFAULT = 30 * 1000;
/**
* The interval between checks when idle. The interval is shorter
* so we can react almost immediately when we are active again.
*/
private static final int CHECK_FOR_IDLE_WHEN_IDLE = 1000;
/**
* The time in milliseconds between two checks for system idle.
*/
private static int idleStateCheckDelay = CHECK_FOR_IDLE_DEFAULT;
/**
* Whether current service is started or stopped.
*/
private boolean running = false;
/**
* The time when we received latest network change event.
*/
private long lastNetworkChange = -1;
/**
* Sometimes (on windows) we got several network change events
* this is the time after which latest event we will skip next events.
*/
private static final long NETWORK_EVENT_SILENT_TIME = 10*1000;
/**
* Whether network is currently connected.
*/
private Boolean networkIsConnected = null;
/**
* The linux impl class name.
*/
private static final String SYSTEM_ACTIVITY_MANAGER_LINUX_CLASS
= "net.java.sip.communicator.impl.sysactivity.NetworkManagerListenerImpl";
/**
* The android impl class name.
*/
private static final String SYSTEM_ACTIVITY_MANAGER_ANDROID_CLASS
= "net.java.sip.communicator.impl.sysactivity.ConnectivityManagerListenerImpl";
/**
* The currently instantiated and working manager.
*/
private SystemActivityManager currentRunningManager = null;
/**
* Init and start notifications.
*/
public void start()
{
running = true;
// set the delegate and start notification in new thread
// make sure we don't block startup process
Thread notifystartThread
= new Thread(
new Runnable()
{
public void run()
{
SystemActivityNotifications.setDelegate(
SystemActivityNotificationsServiceImpl.this);
SystemActivityNotifications.start();
}
},
"SystemActivityNotificationsServiceImpl");
notifystartThread.setDaemon(true);
notifystartThread.start();
if(isSupported(SystemActivityEvent.EVENT_SYSTEM_IDLE))
{
// a thread periodically checks system idle state and if it pass the
// idle time for a particular listener, will inform it.
Thread idleNotifyThread = new Thread(
this,
"SystemActivityNotificationsServiceImpl.IdleNotifyThread");
idleNotifyThread.setDaemon(true);
idleNotifyThread.start();
}
if (getCurrentRunningManager() != null)
getCurrentRunningManager().start();
}
/**
* Stop notifications.
*/
public void stop()
{
SystemActivityNotifications.stop();
if (getCurrentRunningManager() != null)
getCurrentRunningManager().stop();
eventDispatcher.stop();
running = false;
synchronized(this)
{
this.notifyAll();
}
}
/**
* Registers a listener that would be notified of changes that have occurred
* in the underlying system.
*
* @param listener the listener that we'd like to register for changes in
* the underlying system.
*/
public void addSystemActivityChangeListener(
SystemActivityChangeListener listener)
{
eventDispatcher.addSystemActivityChangeListener(listener);
}
/**
* Remove the specified listener so that it won't receive further
* notifications of changes that occur in the underlying system
*
* @param listener the listener to remove.
*/
public void removeSystemActivityChangeListener(
SystemActivityChangeListener listener)
{
eventDispatcher.removeSystemActivityChangeListener(listener);
}
/**
* Registers a listener that would be notified for idle of the system
* for idleTime.
*
* @param idleTime the time in milliseconds after which we will consider
* system to be idle. This doesn't count when system seems idle as
* monitor is off or screensaver is on, or desktop is locked.
* @param listener the listener that we'd like to register for changes in
* the underlying system.
*/
public void addIdleSystemChangeListener(
long idleTime,
SystemActivityChangeListener listener)
{
synchronized (idleChangeListeners)
{
if (idleTime > 0
&& !idleChangeListeners.containsKey(listener))
idleChangeListeners.put(listener, idleTime);
}
}
/**
* Remove the specified listener so that it won't receive further
* notifications for idle system.
*
* @param listener the listener to remove.
*/
public void removeIdleSystemChangeListener(
SystemActivityChangeListener listener)
{
synchronized (idleChangeListeners)
{
idleChangeListeners.remove(listener);
}
}
/**
* The time since last user input. The time the system has been idle.
* @return time the system has been idle.
*/
public long getTimeSinceLastInput()
{
if(SystemActivityNotifications.isLoaded())
return SystemActivityNotifications.getLastInput();
else
return -1;
}
/**
* Callback method when receiving notifications.
*
* @param type type of the notification.
*/
public void notify(int type)
{
SystemActivityEvent evt = null;
switch(type)
{
case SystemActivityNotifications.NOTIFY_SLEEP :
evt = new SystemActivityEvent(this,
SystemActivityEvent.EVENT_SLEEP);
break;
case SystemActivityNotifications.NOTIFY_WAKE :
evt = new SystemActivityEvent(this,
SystemActivityEvent.EVENT_WAKE);
break;
case SystemActivityNotifications.NOTIFY_DISPLAY_SLEEP :
evt = new SystemActivityEvent(this,
SystemActivityEvent.EVENT_DISPLAY_SLEEP);
break;
case SystemActivityNotifications.NOTIFY_DISPLAY_WAKE :
evt = new SystemActivityEvent(this,
SystemActivityEvent.EVENT_DISPLAY_WAKE);
break;
case SystemActivityNotifications.NOTIFY_SCREENSAVER_START :
evt = new SystemActivityEvent(this,
SystemActivityEvent.EVENT_SCREENSAVER_START);
break;
case SystemActivityNotifications.NOTIFY_SCREENSAVER_WILL_STOP :
evt = new SystemActivityEvent(this,
SystemActivityEvent.EVENT_SCREENSAVER_WILL_STOP);
break;
case SystemActivityNotifications.NOTIFY_SCREENSAVER_STOP :
evt = new SystemActivityEvent(this,
SystemActivityEvent.EVENT_SCREENSAVER_STOP);
break;
case SystemActivityNotifications.NOTIFY_SCREEN_LOCKED :
evt = new SystemActivityEvent(this,
SystemActivityEvent.EVENT_SCREEN_LOCKED);
break;
case SystemActivityNotifications.NOTIFY_SCREEN_UNLOCKED :
evt = new SystemActivityEvent(this,
SystemActivityEvent.EVENT_SCREEN_UNLOCKED);
break;
case SystemActivityNotifications.NOTIFY_NETWORK_CHANGE :
{
evt = new SystemActivityEvent(this,
SystemActivityEvent.EVENT_NETWORK_CHANGE);
break;
}
case SystemActivityNotifications.NOTIFY_DNS_CHANGE :
{
evt = new SystemActivityEvent(this,
SystemActivityEvent.EVENT_DNS_CHANGE);
break;
}
case SystemActivityNotifications.NOTIFY_QUERY_ENDSESSION :
{
// both events QUERY_ENDSESSION and ENDSESSION
// depend on the result one after another
// we don't put them in new thread in order to give control
// in the bundles using this events.
evt = new SystemActivityEvent(this,
SystemActivityEvent.EVENT_QUERY_ENDSESSION);
eventDispatcher.fireSystemActivityEventCurrentThread(evt);
return;
}
case SystemActivityNotifications.NOTIFY_ENDSESSION :
{
// both events QUERY_ENDSESSION and ENDSESSION
// depend on the result one after another
// we don't put them in new thread in order to give control
// in the bundles using this events.
evt = new SystemActivityEvent(this,
SystemActivityEvent.EVENT_ENDSESSION);
eventDispatcher.fireSystemActivityEventCurrentThread(evt);
return;
}
}
if (evt != null)
fireSystemActivityEvent(evt);
}
/**
* Callback method when receiving special network notifications.
*
* @param family family of network change (ipv6, ipv4)
* AF_UNSPEC = 0 (The address family is unspecified.)
* AF_INET = 2 (The Internet Protocol version 4 (IPv4) address family)
* AF_INET6 = 23 (The Internet Protocol version 6 (IPv6) address family)
* @param luidIndex unique index of interface
* @param name name of the interface
* @param type of the interface
* Possible values for the interface type are listed in the Ipifcons.h file.
* common values:
* IF_TYPE_OTHER = 1 (Some other type of network interface.)
* IF_TYPE_ETHERNET_CSMACD = 6 (An Ethernet network interface.)
* IF_TYPE_ISO88025_TOKENRING = 9 (A token ring network interface.)
* IF_TYPE_PPP = 23 (A PPP network interface.)
* IF_TYPE_SOFTWARE_LOOPBACK = 24 (A software loopback network interface.)
* IF_TYPE_IEEE80211 = 71 (An IEEE 802.11 wireless network interface.)
* IF_TYPE_TUNNEL = 131 (A tunnel type encapsulation network interface.)
* IF_TYPE_IEEE1394 = 144 (An IEEE 1394 (Firewire) high performance
* serial bus network interface.)
* @param connected whether interface is connected or not.
*/
public void notifyNetworkChange(
int family,
long luidIndex,
String name,
long type,
boolean connected)
{
long current = System.currentTimeMillis();
if(current - lastNetworkChange <= NETWORK_EVENT_SILENT_TIME
&& (networkIsConnected != null && networkIsConnected.equals(connected)))
{
networkIsConnected = connected;
return;
}
lastNetworkChange = current;
networkIsConnected = connected;
SystemActivityEvent evt = new SystemActivityEvent(this,
SystemActivityEvent.EVENT_NETWORK_CHANGE);
fireSystemActivityEvent(evt);
}
/**
* The thread run method that handles idle notifies.
*
* @see Thread#run()
*/
public void run()
{
while(running)
{
try
{
long idleTime = 0;
if(idleChangeListeners.size() > 0)
{
// check
idleTime = SystemActivityNotifications.getLastInput();
if((idleTime < idleStateCheckDelay)
&& (listenersInIdleState.size() > 0))
{
for(SystemActivityChangeListener l
: listenersInIdleState)
{
fireSystemIdleEndEvent(l);
}
listenersInIdleState.clear();
}
for(Map.Entry entry
: idleChangeListeners.entrySet())
{
SystemActivityChangeListener listener =
entry.getKey();
if(!listenersInIdleState.contains(listener)
&& (entry.getValue() <= idleTime))
{
fireSystemIdleEvent(listener);
listenersInIdleState.add(listener);
}
}
}
// if the minimum check for idle is X minutes
// we will wait before checking (X - Y + 1sec)
// where Y is the last idle time returned by OS
if(listenersInIdleState.size() > 0)
{
idleStateCheckDelay = CHECK_FOR_IDLE_WHEN_IDLE;
}
else if(idleTime != 0)
{
long minIdleSetting = CHECK_FOR_IDLE_DEFAULT;
if(!idleChangeListeners.isEmpty())
minIdleSetting =
Collections.min(idleChangeListeners.values());
int newSetting = (int)(minIdleSetting - idleTime) + 1000;
if(newSetting > 0)
idleStateCheckDelay = newSetting;
else
idleStateCheckDelay = CHECK_FOR_IDLE_DEFAULT;
}
else
{
idleStateCheckDelay = CHECK_FOR_IDLE_DEFAULT;
}
// wait for the specified time
synchronized(this)
{
this.wait(idleStateCheckDelay);
}
}
catch(UnsatisfiedLinkError t)
{
logger.error("Missing native impl", t);
return;
}
catch(Throwable t)
{
logger.error("Error checking for idle", t);
}
}
}
/**
* Delivers the specified event to all registered listeners.
*
* @param evt the SystemActivityEvent that we'd like delivered to
* all registered message listeners.
*/
protected void fireSystemActivityEvent(SystemActivityEvent evt)
{
int eventID = evt.getEventID();
// Add network activity info to track wake up problems.
if (logger.isInfoEnabled()
&& ((eventID == SystemActivityEvent.EVENT_NETWORK_CHANGE)
|| (eventID == SystemActivityEvent.EVENT_DNS_CHANGE)))
{
logger.info("Received system activity event: " + evt);
}
if (eventID == SystemActivityEvent.EVENT_NETWORK_CHANGE)
{
// Give time to Java to dispatch same event and populate its network
// interfaces.
eventDispatcher.fireSystemActivityEvent(evt, 500);
}
else
eventDispatcher.fireSystemActivityEvent(evt);
}
/**
* Delivers the specified event to all registered listeners.
*
* @param listener listener to inform
*/
protected void fireSystemIdleEvent(SystemActivityChangeListener listener)
{
SystemActivityEvent evt
= new SystemActivityEvent(
this,
SystemActivityEvent.EVENT_SYSTEM_IDLE);
if (logger.isDebugEnabled())
logger.debug("Dispatching SystemActivityEvent evt=" + evt);
try
{
listener.activityChanged(evt);
}
catch (Throwable t)
{
if (t instanceof ThreadDeath)
throw (ThreadDeath) t;
else
logger.error("Error delivering event", t);
}
}
/**
* Delivers the specified event to listener.
*
* @param listener listener to inform
*/
protected void fireSystemIdleEndEvent(
SystemActivityChangeListener listener)
{
SystemActivityEvent evt
= new SystemActivityEvent(
this,
SystemActivityEvent.EVENT_SYSTEM_IDLE_END);
if (logger.isDebugEnabled())
logger.debug("Dispatching SystemActivityEvent evt=" + evt);
try
{
listener.activityChanged(evt);
}
catch (Throwable t)
{
if (t instanceof ThreadDeath)
throw (ThreadDeath) t;
else
logger.error("Error delivering event", t);
}
}
/**
* Can check whether an event id is supported on
* current operation system.
* Simple return what is implemented in native, and checks
* are made when possible, for example linux cannot connect
* to NM through dbus.
* @param eventID the event to check.
* @return whether the supplied event id is supported.
*/
public boolean isSupported(int eventID)
{
if(OSUtils.IS_WINDOWS)
{
switch(eventID)
{
case SystemActivityEvent.EVENT_SLEEP:
case SystemActivityEvent.EVENT_WAKE:
case SystemActivityEvent.EVENT_NETWORK_CHANGE:
case SystemActivityEvent.EVENT_SYSTEM_IDLE:
case SystemActivityEvent.EVENT_SYSTEM_IDLE_END:
return SystemActivityNotifications.isLoaded();
default:
return false;
}
}
else if(OSUtils.IS_MAC)
{
return SystemActivityNotifications.isLoaded();
}
else if(OSUtils.IS_LINUX)
{
switch(eventID)
{
case SystemActivityEvent.EVENT_SLEEP:
case SystemActivityEvent.EVENT_NETWORK_CHANGE:
{
SystemActivityManager currentRunningManager
= getCurrentRunningManager();
return
(currentRunningManager == null)
? false
: currentRunningManager.isConnected();
}
case SystemActivityEvent.EVENT_SYSTEM_IDLE:
case SystemActivityEvent.EVENT_SYSTEM_IDLE_END:
return SystemActivityNotifications.isLoaded();
default:
return false;
}
}
else if(OSUtils.IS_ANDROID)
{
return (eventID == SystemActivityEvent.EVENT_NETWORK_CHANGE);
}
else
{
return false;
}
}
/**
* Returns or instantiate the manager.
* @return
*/
private SystemActivityManager getCurrentRunningManager()
{
if(currentRunningManager == null)
{
try
{
String className = null;
if(OSUtils.IS_LINUX)
{
className = SYSTEM_ACTIVITY_MANAGER_LINUX_CLASS;
}
else if(OSUtils.IS_ANDROID)
{
className = SYSTEM_ACTIVITY_MANAGER_ANDROID_CLASS;
}
if(className != null)
currentRunningManager = (SystemActivityManager)
Class.forName(className).newInstance();
}
catch(Throwable t)
{
logger.error("Error creating manager", t);
}
}
return currentRunningManager;
}
}