/* * Copyright 2010-2011 Research In Motion Limited. * * 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 blackberry.push; import java.util.Enumeration; import java.util.Vector; import net.rim.blackberry.api.push.PushApplicationDescriptor; import net.rim.blackberry.api.push.PushApplicationRegistry; import net.rim.blackberry.api.push.PushApplicationStatus; import net.rim.device.api.script.ScriptableFunction; import net.rim.device.api.system.ApplicationDescriptor; import net.rim.device.api.system.EventLogger; import net.rim.device.api.util.IntHashtable; import blackberry.common.push.PushDaemon; import blackberry.common.push.PushPersistentStore; import blackberry.core.ApplicationEventHandler; import blackberry.core.EventService; import blackberry.core.WidgetProperties; /** * Service that handles all the active push listeners in the application. */ public class PushService { public static final int BES_PUSH = 0; public static final int BIS_PUSH = 1; private IntHashtable _pushListeners; private PushListener2 _pushListener2; private OnExitHandler _onExitHandler; public static final long PUSHSERVICE_GUID = Long.parseLong( PushService.class.getName().hashCode() + "", 16 ); private PushDaemon.DaemonStore _daemonStore; /** * Retrieves the singleton instance of this object. * * @return Singleton instance of the PushService object. */ public synchronized static PushService getInstance() { return InstanceHolder.INSTANCE; } private static class InstanceHolder { private static final PushService INSTANCE = new PushService(); } /* * private static class InstanceHolderPush */ private PushService() { // store the application descriptor and push in RuntimeStore so that it can be accessed from daemon. _daemonStore = PushDaemon.DaemonStore.loadFromStore(); // persistent the application descriptor PushPersistentStore.setAppDescArgs( ApplicationDescriptor.currentApplicationDescriptor().getArgs() ); _pushListeners = new IntHashtable(); _onExitHandler = new OnExitHandler(); } /** * Creates a new push listener to listen on a specified port. * * @param port * the port to listen to for push messages. * @param callback * scriptable function to invoke when a push message is received. * @param maxQueueCount * the maximum number of push messages to queue before oldest messages are discarded. */ public void openPushChannel( int port, ScriptableFunction callback, int maxQueueCount ) throws Exception { PushListener listener = (PushListener) _pushListeners.get( port ); // new listener if( listener == null ) { listener = new PushListener( port, callback, maxQueueCount ); _pushListeners.put( port, listener ); // new listener - create onExitHandler if( _pushListeners.size() == 1 ) { setOnExitListener( true ); } listener.start(); } // update existing listener else { listener.updateCallback( callback ); } } /** * Register PushApplication on the PPG server and create new push listener to listen on specific port * * @param port * The port on the device to listen for pushes on. * @param appId * The id provided to you for your push application after signing up to use the BlackBerry Push Service. * @param serverUrl * The URL for the PPG. Examples of this URL include: http://pushapi.eval.blackberry.com if using the eval * environment of the BlackBerry Push Service or http://pushapi.na.blackberry.com if using the production * environment of the BlackBerry Push Service. * @param entryPage * The page that will be shown when application closes and a push data arrives. * @param maxQueueCap * The maximum number of pushes to queue before the oldest pushes are discarded. * @param onData * The ScriptableFunction that is invoked when a new push has been received. * @param onRegister * The ScriptableFunction that is invoked when the result of the registration performed during the opening of the * push listener is received. * @param onSimChange * The ScriptableFunction that is invoked on a SIM card change (since it has implications on the receiving of * pushes). */ public void openBISPushChannel( int port, String appId, String serverUrl, String entryPage, int maxQueueCap, ScriptableFunction onData, ScriptableFunction onRegister, ScriptableFunction onSimChange ) throws Exception { // check to see if a push is opened on this port using old push API PushListener listener = (PushListener) _pushListeners.get( port ); if( listener != null ) { throw new IllegalArgumentException( "A push is opened on this port, please close it and try again." ); } // check to see if a push is open on this port using new push API int currentType = PushPersistentStore.getLastKnownType(); int currentPort = PushPersistentStore.getLastKnownPort(); // cannot run BES and BIS at the same time if( currentType != -1 && currentType == BES_PUSH ) { throw new IllegalArgumentException( "A BES push is opened, please close it first." ); } // only one port can be opened at a time if( currentPort != -1 && port != currentPort ) { throw new IllegalArgumentException( "A different port (" + currentPort + ") is already opened, only one port can be opened at a time." ); } EventLogger.logEvent( WidgetProperties.getInstance().getGuid(), "open BIS push listener".getBytes() ); ApplicationDescriptor ad = new ApplicationDescriptor( ApplicationDescriptor.currentApplicationDescriptor(), new String[] { "PushDaemon", entryPage, "" + maxQueueCap } ); PushApplicationDescriptor pad = new PushApplicationDescriptor( appId, port, serverUrl, PushApplicationDescriptor.SERVER_TYPE_BPAS, ad ); int status = PushApplicationRegistry.getStatus( pad ).getStatus(); if( status != PushApplicationStatus.STATUS_ACTIVE ) { EventLogger.logEvent( WidgetProperties.getInstance().getGuid(), "Register push application".getBytes() ); PushApplicationRegistry.registerApplication( pad ); } // new listener if( _pushListener2 == null ) { _pushListener2 = new PushListener2( port, _daemonStore.getMessageQueue(), onData, onRegister, onSimChange ); setOnExitListener( true ); _pushListener2.start(); } else { _pushListener2.updateCallback( onData, onRegister, onSimChange ); } } /** * Creates a new push listener to listen on a specified port. * * @param port * The port on the device to listen for pushes on. * @param entryPage * The page that will be shown when application closes and a push data arrives. * @param maxQueueCap * The maximum number of pushes to queue before the oldest pushes are discarded. * @param onData * The ScriptableFunction that is invoked when a new push has been received. * @param onSimChange * The ScriptableFunction that is invoked on a SIM card change (since it has implications on the receiving of * pushes). */ public void openBESPushChannel( int port, String entryPage, int maxQueueCap, ScriptableFunction onData, ScriptableFunction onSimChange ) throws Exception { // check to see if a push is opened on this port using old push API PushListener listener = (PushListener) _pushListeners.get( port ); if( listener != null ) { throw new IllegalArgumentException( "A push is opened on this port, please close it and try again." ); } // check to see if a push is open on this port using new push API int currentType = PushPersistentStore.getLastKnownType(); int currentPort = PushPersistentStore.getLastKnownPort(); // cannot run BES and BIS at the same time if( currentType != -1 && currentType == BIS_PUSH ) { throw new IllegalArgumentException( "A BIS push is opened, please close it and try again." ); } // only one port can be opened at a time if( currentPort != -1 && port != currentPort ) { throw new IllegalArgumentException( "A different port (" + currentPort + ") is already opened, only one port can be opened at a time." ); } ApplicationDescriptor ad = new ApplicationDescriptor( ApplicationDescriptor.currentApplicationDescriptor(), new String[] { "PushDaemon", entryPage, "" + maxQueueCap } ); PushApplicationDescriptor pad = new PushApplicationDescriptor( port, ad ); int status = PushApplicationRegistry.getStatus( pad ).getStatus(); if( status != PushApplicationStatus.STATUS_ACTIVE ) { PushApplicationRegistry.registerApplication( pad ); } // new listener if( _pushListener2 == null ) { _pushListener2 = new PushListener2( port, _daemonStore.getMessageQueue(), onData, onSimChange ); setOnExitListener( true ); _pushListener2.start(); } else { _pushListener2.updateCallback( onData, null, onSimChange ); } } /** * Closes a push channel. * * @param port * the port the push listener was opened on. */ public void closePushChannel( int port ) { PushListener listener = (PushListener) _pushListeners.get( port ); if( listener != null ) { listener.stop(); _pushListeners.remove( port ); } else { if( PushPersistentStore.getLastKnownPort() == port ) { stopDaemon(); if( _pushListener2 != null ) { _pushListener2.stop(); _pushListener2 = null; } EventLogger.logEvent( WidgetProperties.getInstance().getGuid(), "Unregister push application".getBytes() ); PushApplicationRegistry.unregisterApplication(); PushPersistentStore.setLastKnownType( -1 ); PushPersistentStore.setLastKnownPort( -1 ); } } if( ( _pushListeners.size() == 0 ) && ( _pushListener2 == null ) ) { setOnExitListener( false ); } } /** * Closes all push channels. */ public void closePushChannel() { for( Enumeration e = _pushListeners.elements(); e.hasMoreElements(); ) { PushListener listener = (PushListener) e.nextElement(); if( listener != null ) { listener.stop(); } } _pushListeners.clear(); if( _pushListener2 != null ) { stopDaemon(); _pushListener2.stop(); _pushListener2 = null; EventLogger.logEvent( WidgetProperties.getInstance().getGuid(), "Unregister push application".getBytes() ); PushApplicationRegistry.unregisterApplication(); setOnExitListener( false ); } else if( PushPersistentStore.getLastKnownPort() != -1 ) { stopDaemon(); EventLogger.logEvent( WidgetProperties.getInstance().getGuid(), "Unregister push application".getBytes() ); PushApplicationRegistry.unregisterApplication(); } PushPersistentStore.setLastKnownType( -1 ); PushPersistentStore.setLastKnownPort( -1 ); } private void stopDaemon() { if( _daemonStore != null ) { Vector commandQueue = _daemonStore.getCommandQueue(); synchronized( commandQueue ) { commandQueue.addElement( new Object() ); commandQueue.notify(); } _daemonStore.getMessageQueue().removeAllElements(); } } private void setOnExitListener( boolean activate ) { if( activate ) { EventService.getInstance().addHandler( ApplicationEventHandler.EVT_APP_EXIT, _onExitHandler ); } else { EventService.getInstance().removeHandler( ApplicationEventHandler.EVT_APP_EXIT, _onExitHandler ); } } /** * Validates the port is not reserved or in use. * * @param port * the port to validate. * @return <code>true</code> if the port is valid; <code>false</code> otherwise. */ public static boolean isValidPort( int port ) { switch( port ) { case 80: case 443: case 7874: case 8080: return false; default: return true; } } final class OnExitHandler implements ApplicationEventHandler { /** * @see ApplicationEventHandler#handleEvent(int, Object[]) */ public void handleEvent( int eventID, Object[] args ) { // close all push ports Enumeration listenerEnum = _pushListeners.elements(); PushListener listener; while( listenerEnum.hasMoreElements() ) { listener = (PushListener) listenerEnum.nextElement(); listener.stop(); } _pushListeners.clear(); if( _pushListener2 != null ) { _pushListener2.stop(); _pushListener2 = null; } setOnExitListener( false ); } /** * @see ApplicationEventHandler#handlePreEvent(int, Object[]) */ public boolean handlePreEvent( int eventID, Object[] args ) { // do nothing return false; } } public void resetListeners() { if( _pushListener2 != null ) { _pushListener2.updateCallback( null, null, null ); } } }