/* * Copyright 1990-2009 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 only, as published by the Free Software Foundation. * * This program 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 version 2 for more details (a copy is * included at /legal/license.txt). * * You should have received a copy of the GNU General Public License * version 2 along with this work; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa * Clara, CA 95054 or visit www.sun.com if you need additional * information or have any questions. */ package com.sun.javame.sensor; import java.util.Enumeration; import java.util.Hashtable; import java.util.Vector; import javax.microedition.sensor.SensorInfo; import javax.microedition.sensor.SensorListener; import com.sun.javame.sensor.helper.IdentityWrapper; /** * This class is responsible for callbacks to SensorListener registered with * SensorInfo. */ class AvailabilityPoller implements AvailabilityListener { /** * Holds Vector of SensorListener-s for each SensorInfo. * <i>@GuardedBy("infoListenerMap")</i> */ private final Hashtable infoListenerMap = new Hashtable(); /** * Stores the last SensorInfo availability state. The value updated during * each notification call. * <i>@GuardedBy("infoListenerMap")</i> */ private final Hashtable lastAvailable = new Hashtable(); /** * Stores the current SensorInfo availability state which is used if there * are more SensorListener-s for one SensorInfo. The value is also updated * by the {@link #notifyAvailability(SensorInfo, boolean)} called from * {@link AvailabilityNotifier}. The content is erased on each poller loop. * <i>@GuardedBy("infoListenerMap")</i> */ private final Hashtable cachedAvailability = new Hashtable(); /** Lock used to sleep in each poller loop. */ private final Object sleepLock = new Object(); /** * The polling worker thread. * <i>@GuardedBy("infoListenerMap")</i> */ private PollerWorker pollingThread; /** Executor used to fire SensorListener notifications. */ private final GuardedExecutor executor = new GuardedExecutor(); /** Time in milliseconds the poller sleeps between runs. */ private final int pollerSleep; /** * Create availability poller with specific delay. * * @param pollerSleep time in milliseconds the poller will sleep between * runs */ AvailabilityPoller(int pollerSleep) { this.pollerSleep = pollerSleep; } /** * Set SensorInfo cached availability and notify the sleep lock. */ public void notifyAvailability(SensorInfo info, boolean available) { synchronized (infoListenerMap) { IdentityWrapper infoWrap = new IdentityWrapper(info); cachedAvailability.put(infoWrap, available ? Boolean.TRUE : Boolean.FALSE); } synchronized (sleepLock) { sleepLock.notify(); } } /** * If not present add listener and call appropriate callback method. This * will also call {@link AvailabilityNotifier#startMonitoringAvailability()} * if the {@link SensorInfo} implements it and the first SensorListener is * registered. * * @param listener SensorListener which is registered * @param info SensorInfo which changes the listener listens to */ void addListener(SensorListener listener, SensorInfo info) { IdentityWrapper infoWrap = new IdentityWrapper(info); IdentityWrapper listenWrap = new IdentityWrapper(listener); synchronized (infoListenerMap) { // Get or create listeners container Vector listeners = (Vector) infoListenerMap.get(infoWrap); if (listeners == null) { listeners = new Vector(); infoListenerMap.put(infoWrap, listeners); } if (listeners.size() == 0 && info instanceof AvailabilityNotifier) { // If the device supports notifications enable them ((AvailabilityNotifier) info).startMonitoringAvailability(this); listeners.addElement(listenWrap); } else if (!listeners.contains(listenWrap)) { listeners.addElement(listenWrap); } else { listener = null; } // Must be in synchronized block because poller can call us // immediately and that will result in double call if (listener != null) { final Boolean available = info.isAvailable() ? Boolean.TRUE : Boolean.FALSE; lastAvailable.put(infoWrap, available); if (available.booleanValue()) { listener.sensorAvailable(info); } else { listener.sensorUnavailable(info); } ensureStarted(); infoListenerMap.notify(); } } } /** * Remove the SensorListener from every SensorInfo to which it is * registered. This will also call * {@link AvailabilityNotifier#stopMonitoringAvailability()}, if the * {@link SensorInfo} implements it and the last SensorListener is * unregistered. * * @param listener SensorListener which is unregistered */ void removeListener(SensorListener listener) { synchronized (infoListenerMap) { IdentityWrapper listenWrap = new IdentityWrapper(listener); Enumeration enumeration = infoListenerMap.keys(); Vector infosToRemove = new Vector(); while (enumeration.hasMoreElements()) { IdentityWrapper key = (IdentityWrapper) enumeration .nextElement(); Vector listeners = (Vector) infoListenerMap.get(key); if (listeners.contains(listenWrap)) { listeners.removeElement(listenWrap); if (listeners.isEmpty()) { infosToRemove.addElement(key); SensorInfo info = (SensorInfo) key.getWrapped(); if (info instanceof AvailabilityNotifier) { ((AvailabilityNotifier) info) .stopMonitoringAvailability(this); } } } } for (int i = 0; i < infosToRemove.size(); i++) { infoListenerMap.remove(infosToRemove.elementAt(i)); } } } /** * Ensure that the worker thread is running. If it does not exist (null) or * died for some reason (isAlive == false), then we create new and start it. */ private void ensureStarted() { if (pollingThread == null || !pollingThread.isAlive()) { pollingThread = new PollerWorker(); pollingThread.start(); } } /** * Executor class which shields the current thread from unchecked exceptions * that can shut it down. */ private static final class GuardedExecutor { public void execute(Runnable runnable) { try { runnable.run(); } catch (Throwable ignore) { // We have to ignore exceptions, otherwise the will kill the // current thread. } } } /** * Class holding the state informations about the upcoming listener * callback. * <i>@Immutable</i> */ private static final class ToCall implements Runnable { private final SensorInfo info; private final SensorListener listener; private final boolean available; public ToCall(SensorInfo info, SensorListener listener, boolean available) { this.info = info; this.listener = listener; this.available = available; } public void run() { if (available) { listener.sensorAvailable(info); } else { listener.sensorUnavailable(info); } } } /** * The poller worker thread which in a infinite loop fetches SensorInfo * availability and calls appropriate listeners. */ private class PollerWorker extends Thread { public void run() { while (true) { synchronized (infoListenerMap) { while (infoListenerMap.isEmpty()) { try { infoListenerMap.wait(); } catch (InterruptedException ignore) { // Avoid interrupt, we control this thread } } } // To avoid delay if isAvailable is time consuming long startTimeMillis = System.currentTimeMillis(); Vector toCall = new Vector(); synchronized (infoListenerMap) { Enumeration enumeration = infoListenerMap.keys(); while (enumeration.hasMoreElements()) { IdentityWrapper key = (IdentityWrapper) enumeration .nextElement(); SensorInfo info = (SensorInfo) key.getWrapped(); Boolean available = (Boolean) cachedAvailability .get(key); if (available == null) { available = info.isAvailable() ? Boolean.TRUE : Boolean.FALSE; cachedAvailability.put(key, available); } Boolean oldAvailable = (Boolean) lastAvailable.put(key, available); if (!available.equals(oldAvailable)) { Vector listeners = (Vector) infoListenerMap .get(key); for (int i = 0; i < listeners.size(); i++) { SensorListener listener = (SensorListener) ((IdentityWrapper) listeners.elementAt(i)) .getWrapped(); toCall.addElement(new ToCall(info, listener, available.booleanValue())); } } } cachedAvailability.clear(); } for (int i = 0; i < toCall.size(); i++) { Runnable runnable = (Runnable) toCall.elementAt(i); executor.execute(runnable); } long millisToWait = pollerSleep - (System.currentTimeMillis() - startTimeMillis); if (millisToWait > 0) { synchronized (sleepLock) { try { sleepLock.wait(millisToWait); } catch (InterruptedException ignore) { // Avoid interrupt, we control this thread } } } } } } }