RhodesService.java Source code

Java tutorial

Introduction

Here is the source code for RhodesService.java

Source

/*------------------------------------------------------------------------
* (The MIT License)
* 
* Copyright (c) 2008-2011 Rhomobile, Inc.
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* 
* http://rhomobile.com
*------------------------------------------------------------------------*/

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.BufferedReader;
import java.io.FileReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Calendar;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.UUID;
import java.util.Vector;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;

import com.rhomobile.rhodes.alert.Alert;
import com.rhomobile.rhodes.alert.StatusNotification;
import com.rhomobile.rhodes.event.EventStore;
import com.rhomobile.rhodes.extmanager.RhoExtManager;
import com.rhomobile.rhodes.file.RhoFileApi;
import com.rhomobile.rhodes.geolocation.GeoLocation;
import com.rhomobile.rhodes.mainview.MainView;
import com.rhomobile.rhodes.mainview.SplashScreen;
import com.rhomobile.rhodes.osfunctionality.AndroidFunctionalityManager;
import com.rhomobile.rhodes.ui.AboutDialog;
import com.rhomobile.rhodes.ui.LogOptionsDialog;
import com.rhomobile.rhodes.ui.LogViewDialog;
import com.rhomobile.rhodes.uri.ExternalHttpHandler;
import com.rhomobile.rhodes.uri.LocalFileHandler;
import com.rhomobile.rhodes.uri.MailUriHandler;
import com.rhomobile.rhodes.uri.SmsUriHandler;
import com.rhomobile.rhodes.uri.TelUriHandler;
import com.rhomobile.rhodes.uri.UriHandler;
import com.rhomobile.rhodes.uri.VideoUriHandler;
import com.rhomobile.rhodes.util.ContextFactory;
import com.rhomobile.rhodes.util.JSONGenerator;
import com.rhomobile.rhodes.util.PerformOnUiThread;
import com.rhomobile.rhodes.util.PhoneId;
import com.rhomobile.rhodes.util.Utils;

import android.app.AlertDialog;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.PowerManager;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.view.WindowManager;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.RemoteViews;

public class RhodesService extends Service {

    private static final String TAG = RhodesService.class.getSimpleName();

    private static final boolean DEBUG = false;

    public static final String INTENT_EXTRA_PREFIX = "com.rhomobile.rhodes";

    public static final String INTENT_SOURCE = INTENT_EXTRA_PREFIX + ".intent_source";

    public static int WINDOW_FLAGS = WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN;
    public static int WINDOW_MASK = WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN;
    public static boolean ANDROID_TITLE = true;

    private static final int DOWNLOAD_PACKAGE_ID = 1;

    private static final String ACTION_ASK_CANCEL_DOWNLOAD = "com.rhomobile.rhodes.DownloadManager.ACTION_ASK_CANCEL_DOWNLOAD";
    private static final String ACTION_CANCEL_DOWNLOAD = "com.rhomobile.rhodes.DownloadManager.ACTION_CANCEL_DOWNLOAD";

    private static final String NOTIFICATION_NONE = "none";
    private static final String NOTIFICATION_BACKGROUND = "background";
    private static final String NOTIFICATION_ALWAYS = "always";

    private static RhodesService sInstance = null;

    private final IBinder mBinder = new LocalBinder();

    private BroadcastReceiver mConnectionChangeReceiver;

    @SuppressWarnings("rawtypes")
    private static final Class[] mStartForegroundSignature = new Class[] { int.class, Notification.class };
    @SuppressWarnings("rawtypes")
    private static final Class[] mStopForegroundSignature = new Class[] { boolean.class };
    @SuppressWarnings("rawtypes")
    private static final Class[] mSetForegroundSignature = new Class[] { boolean.class };

    private Method mStartForeground;
    private Method mStopForeground;
    private Method mSetForeground;

    private NotificationManager mNM;

    private boolean mNeedGeoLocationRestart = false;

    class PowerWakeLock {
        private PowerManager.WakeLock wakeLockObject = null;
        private boolean wakeLockEnabled = false;

        synchronized boolean isHeld() {
            return (wakeLockObject != null) && wakeLockObject.isHeld();
        }

        synchronized boolean acquire(boolean enable) {
            if (enable) {
                Logger.I(TAG, "Enable WakeLock");
                wakeLockEnabled = true;
            }
            if (wakeLockEnabled) {
                if (wakeLockObject == null) {
                    PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
                    if (pm != null) {
                        Logger.I(TAG, "Acquire WakeLock");
                        wakeLockObject = pm
                                .newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, TAG);
                        wakeLockObject.setReferenceCounted(false);
                        wakeLockObject.acquire();
                    } else {
                        Logger.E(TAG, "Can not get PowerManager to acquire WakeLock!!!");
                    }
                    return false;
                }
                //return wakeLockObject.isHeld();
                return true;
            }
            return false;
        }

        synchronized boolean release() {
            if (wakeLockObject != null) {
                Logger.I(TAG, "Release WakeLock");
                wakeLockObject.release();
                wakeLockObject = null;
                return true;
            }
            return false;
        }

        synchronized boolean reset() {
            if (wakeLockObject != null) {
                Logger.I(TAG, "Reset WakeLock");
                wakeLockObject.release();
                wakeLockObject = null;
                wakeLockEnabled = false;
                return true;
            }
            return false;
        }
    }

    private PowerWakeLock wakeLock = new PowerWakeLock();

    private Vector<UriHandler> mUriHandlers = new Vector<UriHandler>();

    public boolean handleUrlLoading(String url) {
        Enumeration<UriHandler> e = mUriHandlers.elements();
        while (e.hasMoreElements()) {
            UriHandler handler = e.nextElement();
            try {
                if (handler.handle(url))
                    return true;
            } catch (Exception ex) {
                Logger.E(TAG, ex.getMessage());
                continue;
            }
        }

        return false;
    }

    Handler mHandler = null;

    public native void doSyncAllSources(boolean v);

    public native void doSyncSource(String source);

    public native String normalizeUrl(String url);

    public static native void doRequest(String url);

    public static native void doRequestAsync(String url);

    public static native void doRequestEx(String url, String body, String data, boolean waitForResponse);

    public static native void doRequestJson(String url, String body, String data, boolean waitForResponse);

    public static native void loadUrl(String url);

    public static native String currentLocation(int tab);

    public static native void navigateBack();

    public static native void onScreenOrientationChanged(int width, int height, int angle);

    public static native void callUiCreatedCallback();

    public static native void callUiDestroyedCallback();

    public static native void callActivationCallback(boolean active);

    public static native String getBuildConfig(String key);

    public static native String getInvalidSecurityTokenMessage();

    public static native void resetHttpLogging(String http_log_url);

    public static native void resetFileLogging(String log_path);

    public static native boolean isMotorolaLicencePassed(String license, String company, String appName);

    public native void notifyNetworkStatusChanged(int status);

    public static RhodesService getInstance() {
        return sInstance;
    }

    public static native boolean isTitleEnabled();

    private static final String CONF_PHONE_ID = "phone_id";

    private PhoneId getPhoneId() {
        String strPhoneId = RhoConf.getString(CONF_PHONE_ID);
        PhoneId phoneId = PhoneId.getId(this, strPhoneId);
        if (strPhoneId == null || strPhoneId.length() == 0)
            RhoConf.setString(CONF_PHONE_ID, phoneId.toString());

        return phoneId;
    }

    public class LocalBinder extends Binder {
        RhodesService getService() {
            return RhodesService.this;
        }
    };

    @Override
    public IBinder onBind(Intent intent) {
        Logger.D(TAG, "onBind");
        return mBinder;
    }

    @Override
    public void onCreate() {
        Logger.D(TAG, "onCreate");

        sInstance = this;

        Context context = this;

        mNM = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

        LocalFileProvider.revokeUriPermissions(this);

        Logger.I("Rhodes", "Loading...");
        RhodesApplication.create();

        RhodesActivity ra = RhodesActivity.getInstance();
        if (ra != null) {
            // Show splash screen only if we have active activity
            SplashScreen splashScreen = ra.getSplashScreen();
            splashScreen.start();

            // Increase WebView rendering priority
            WebView w = new WebView(context);
            WebSettings webSettings = w.getSettings();
            webSettings.setRenderPriority(WebSettings.RenderPriority.HIGH);
        }

        initForegroundServiceApi();

        // Register custom uri handlers here
        mUriHandlers.addElement(new ExternalHttpHandler(context));
        mUriHandlers.addElement(new LocalFileHandler(context));
        mUriHandlers.addElement(new MailUriHandler(context));
        mUriHandlers.addElement(new TelUriHandler(context));
        mUriHandlers.addElement(new SmsUriHandler(context));
        mUriHandlers.addElement(new VideoUriHandler(context));

        mConnectionChangeReceiver = new ConnectionChangeReceiver();
        IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
        registerReceiver(mConnectionChangeReceiver, filter);

        RhodesApplication.start();

        if (BaseActivity.getActivitiesCount() > 0)
            handleAppActivation();
    }

    public static void handleAppStarted() {
        RhodesApplication.handleAppStarted();
    }

    private void initForegroundServiceApi() {
        try {
            mStartForeground = getClass().getMethod("startForeground", mStartForegroundSignature);
            mStopForeground = getClass().getMethod("stopForeground", mStopForegroundSignature);
            mSetForeground = getClass().getMethod("setForeground", mSetForegroundSignature);
        } catch (NoSuchMethodException e) {
            mStartForeground = null;
            mStopForeground = null;
            mSetForeground = null;
        }
    }

    @Override
    public void onDestroy() {

        if (DEBUG)
            Log.d(TAG, "+++ onDestroy");
        sInstance = null;
        RhodesApplication.stop();
    }

    @Override
    public void onStart(Intent intent, int startId) {
        Log.d(TAG, "onStart");
        try {
            handleCommand(intent, startId);
        } catch (Exception e) {
            Logger.E(TAG, "Can't handle service command");
            Logger.E(TAG, e);
        }
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Logger.D(TAG, "onStartCommand");
        try {
            handleCommand(intent, startId);
        } catch (Exception e) {
            Logger.E(TAG, "Can't handle service command");
            Logger.E(TAG, e);
        }
        return Service.START_STICKY;
    }

    private void handleCommand(Intent intent, int startId) {
        if (intent == null) {
            return;
        }
        String source = intent.getStringExtra(INTENT_SOURCE);
        Logger.I(TAG, "handleCommand: startId=" + startId + ", source=" + source);
        if (source == null)
            throw new IllegalArgumentException("Service command received from empty source");

        if (source.equals(BaseActivity.INTENT_SOURCE)) {
            Logger.D(TAG, "New activity was created");
        } else if (source.equals(PushContract.INTENT_SOURCE)) {
            int type = intent.getIntExtra(PushContract.INTENT_TYPE, PushContract.INTENT_TYPE_UNKNOWN);
            switch (type) {
            case PushContract.INTENT_TYPE_REGISTRATION_ID: {
                String id = intent.getStringExtra(PushContract.INTENT_REGISTRATION_ID);
                String pushType = intent.getStringExtra(PushContract.INTENT_PUSH_CLIENT);
                if (id == null)
                    throw new IllegalArgumentException("Empty registration id received in service command");
                Logger.I(TAG, "Received PUSH registration id: " + id);
                setPushRegistrationId(pushType, id);
            }
                break;
            case PushContract.INTENT_TYPE_MESSAGE:
                if (intent.hasExtra(PushContract.INTENT_MESSAGE_EXTRAS)) {
                    final String pushType = intent.getStringExtra(PushContract.INTENT_PUSH_CLIENT);
                    final Bundle extras = intent.getBundleExtra(PushContract.INTENT_MESSAGE_EXTRAS);
                    Logger.D(TAG, "Received PUSH message: " + extras);
                    RhodesApplication.runWhen(RhodesApplication.AppState.AppStarted,
                            new RhodesApplication.StateHandler(true) {
                                @Override
                                public void run() {
                                    handlePushMessage(pushType, extras);
                                }
                            });
                    break;
                } else if (intent.hasExtra(PushContract.INTENT_MESSAGE_JSON)) {
                    final String pushType = intent.getStringExtra(PushContract.INTENT_PUSH_CLIENT);
                    final String json = intent.getStringExtra(PushContract.INTENT_MESSAGE_JSON);
                    if (json != null) {
                        Logger.D(TAG, "Received PUSH message (JSON): " + json);
                        RhodesApplication.runWhen(RhodesApplication.AppState.AppStarted,
                                new RhodesApplication.StateHandler(true) {
                                    @Override
                                    public void run() {
                                        handlePushMessage(pushType, json);
                                    }
                                });
                    }
                    break;
                }
            default:
                Logger.W(TAG, "Unknown command type received from " + source + ": " + type);
            }
        }
    }

    public void startServiceForeground(int id, Notification notification) {
        if (mStartForeground != null) {
            try {
                mStartForeground.invoke(this, new Object[] { Integer.valueOf(id), notification });
            } catch (InvocationTargetException e) {
                Log.e(TAG, "Unable to invoke startForeground", e);
            } catch (IllegalAccessException e) {
                Log.e(TAG, "Unable to invoke startForeground", e);
            }
            return;
        }

        if (mSetForeground != null) {
            try {
                mSetForeground.invoke(this, new Object[] { Boolean.valueOf(true) });
            } catch (InvocationTargetException e) {
                Log.e(TAG, "Unable to invoke setForeground", e);
            } catch (IllegalAccessException e) {
                Log.e(TAG, "Unable to invoke setForeground", e);
            }
        }
        mNM.notify(id, notification);
    }

    public void stopServiceForeground(int id) {
        if (mStopForeground != null) {
            try {
                mStopForeground.invoke(this, new Object[] { Integer.valueOf(id) });
            } catch (InvocationTargetException e) {
                Log.e(TAG, "Unable to invoke stopForeground", e);
            } catch (IllegalAccessException e) {
                Log.e(TAG, "Unable to invoke stopForeground", e);
            }
            return;
        }

        mNM.cancel(id);
        if (mSetForeground != null) {
            try {
                mSetForeground.invoke(this, new Object[] { Boolean.valueOf(false) });
            } catch (InvocationTargetException e) {
                Log.e(TAG, "Unable to invoke setForeground", e);
            } catch (IllegalAccessException e) {
                Log.e(TAG, "Unable to invoke setForeground", e);
            }
        }
    }

    public void setMainView(MainView v) throws NullPointerException {
        RhodesActivity.safeGetInstance().setMainView(v);
    }

    public MainView getMainView() {
        RhodesActivity ra = RhodesActivity.getInstance();
        if (ra == null)
            return null;
        return ra.getMainView();
    }

    public static void exit() {
        PerformOnUiThread.exec(new Runnable() {
            @Override
            public void run() {
                Logger.I(TAG, "Exit application");
                try {
                    // Do this fake state change in order to make processing before server is stopped
                    RhodesApplication.stateChanged(RhodesApplication.UiState.MainActivityPaused);

                    RhodesService service = RhodesService.getInstance();
                    if (service != null) {
                        Logger.T(TAG, "stop RhodesService");
                        service.wakeLock.reset();
                        service.stopSelf();
                    }

                    Logger.T(TAG, "stop RhodesApplication");
                    RhodesApplication.stop();
                } catch (Exception e) {
                    Logger.E(TAG, e);
                }
            }
        });
    }

    public static void showAboutDialog() {
        PerformOnUiThread.exec(new Runnable() {
            public void run() {
                final AboutDialog aboutDialog = new AboutDialog(ContextFactory.getUiContext());
                aboutDialog.setTitle("About");
                aboutDialog.setCanceledOnTouchOutside(true);
                aboutDialog.setCancelable(true);
                aboutDialog.show();
            }
        });
    }

    public static void showLogView() {
        PerformOnUiThread.exec(new Runnable() {
            public void run() {
                final LogViewDialog logViewDialog = new LogViewDialog(ContextFactory.getUiContext());
                logViewDialog.setTitle("Log View");
                logViewDialog.setCancelable(true);
                logViewDialog.show();
            }
        });
    }

    public static void showLogOptions() {
        PerformOnUiThread.exec(new Runnable() {
            public void run() {
                final LogOptionsDialog logOptionsDialog = new LogOptionsDialog(ContextFactory.getUiContext());
                logOptionsDialog.setTitle("Logging Options");
                logOptionsDialog.setCancelable(true);
                logOptionsDialog.show();
            }
        });
    }

    // Called from native code
    public static void deleteFilesInFolder(String folder) {
        try {
            String[] children = new File(folder).list();
            for (int i = 0; i != children.length; ++i)
                Utils.deleteRecursively(new File(folder, children[i]));
        } catch (Exception e) {
            Logger.E(TAG, e);
        }
    }

    public static boolean pingHost(String host) {
        HttpURLConnection conn = null;
        boolean hostExists = false;
        try {
            URL url = new URL(host);
            HttpURLConnection.setFollowRedirects(false);
            conn = (HttpURLConnection) url.openConnection();

            conn.setRequestMethod("HEAD");
            conn.setAllowUserInteraction(false);
            conn.setDoInput(true);
            conn.setDoOutput(true);
            conn.setUseCaches(false);
            conn.setConnectTimeout(10000);
            conn.setReadTimeout(10000);

            hostExists = (conn.getContentLength() > 0);
            if (hostExists)
                Logger.I(TAG, "PING network SUCCEEDED.");
            else
                Logger.E(TAG, "PING network FAILED.");
        } catch (Exception e) {
            Logger.E(TAG, e);
        } finally {
            if (conn != null) {
                try {
                    conn.disconnect();
                } catch (Exception e) {
                    Logger.E(TAG, e);
                }
            }
        }

        return hostExists;
    }

    private static boolean hasNetworkEx(boolean checkCell, boolean checkWifi, boolean checkEthernet,
            boolean checkWimax, boolean checkBluetooth, boolean checkAny) {
        if (!Capabilities.NETWORK_STATE_ENABLED) {
            Logger.E(TAG, "HAS_NETWORK: Capability NETWORK_STATE disabled");
            return false;
        }

        Context ctx = RhodesService.getContext();
        ConnectivityManager conn = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE);
        if (conn == null) {
            Logger.E(TAG, "HAS_NETWORK: cannot create ConnectivityManager");
            return false;
        }

        NetworkInfo[] info = conn.getAllNetworkInfo();
        if (info == null) {
            Logger.E(TAG, "HAS_NETWORK: cannot issue getAllNetworkInfo");
            return false;
        }

        //{
        //   Utils.platformLog("NETWORK", "$$$$$$$$$$$$$$$$$$$   Networks ; $$$$$$$$$$$$$$$$$$$$$$");
        //   for (int i = 0, lim = info.length; i < lim; ++i) 
        //   {
        //      int type = info[i].getType();
        //      String name = info[i].getTypeName();
        //      boolean is_connected = info[i].getState() == NetworkInfo.State.CONNECTED;
        //      Utils.platformLog("NETWORK", "        - Name ["+name+"],  type ["+String.valueOf(type)+"], connected ["+String.valueOf(is_connected)+"]");
        //   }
        //}

        for (int i = 0, lim = info.length; i < lim; ++i) {
            boolean is_connected = info[i].getState() == NetworkInfo.State.CONNECTED;
            int type = info[i].getType();
            if (is_connected) {
                if ((type == ConnectivityManager.TYPE_MOBILE) && (checkCell))
                    return true;
                if ((type == ConnectivityManager.TYPE_WIFI) && (checkWifi))
                    return true;
                if ((type == 6) && (checkWimax))
                    return true;
                if ((type == 9) && (checkEthernet))
                    return true;
                if ((type == 7) && (checkBluetooth))
                    return true;
                if (checkAny)
                    return true;
            }
        }

        Logger.I(TAG, "HAS_NETWORK: all networks are disconnected");

        return false;
    }

    private static boolean hasWiFiNetwork() {
        return hasNetworkEx(false, true, true, true, false, false);
    }

    private static boolean hasCellNetwork() {
        return hasNetworkEx(true, false, false, false, false, false);
    }

    private static boolean hasNetwork() {
        return hasNetworkEx(true, true, true, true, false, true);
    }

    private static String getCurrentLocale() {
        String locale = Locale.getDefault().getLanguage();
        if (locale.length() == 0)
            locale = "en";
        return locale;
    }

    private static String getCurrentCountry() {
        String cl = Locale.getDefault().getCountry();
        return cl;
    }

    public static int getScreenWidth() {
        if (BaseActivity.getScreenProperties() != null)
            return BaseActivity.getScreenProperties().getWidth();
        else
            return 0;
    }

    public static int getScreenHeight() {
        if (BaseActivity.getScreenProperties() != null)
            return BaseActivity.getScreenProperties().getHeight();
        else
            return 0;
    }

    public static float getScreenPpiX() {
        if (BaseActivity.getScreenProperties() != null)
            return BaseActivity.getScreenProperties().getPpiX();
        else
            return 0;
    }

    public static float getScreenPpiY() {
        if (BaseActivity.getScreenProperties() != null)
            return BaseActivity.getScreenProperties().getPpiY();
        else
            return 0;
    }

    public static int getScreenOrientation() {
        if (BaseActivity.getScreenProperties() != null)
            return BaseActivity.getScreenProperties().getOrientation();
        else
            return Configuration.ORIENTATION_UNDEFINED;
    }

    public static Object getProperty(String name) {
        try {
            if (name.equalsIgnoreCase("platform"))
                return "ANDROID";
            else if (name.equalsIgnoreCase("locale"))
                return getCurrentLocale();
            else if (name.equalsIgnoreCase("country"))
                return getCurrentCountry();
            else if (name.equalsIgnoreCase("screen_width"))
                return Integer.valueOf(getScreenWidth());
            else if (name.equalsIgnoreCase("screen_height"))
                return Integer.valueOf(getScreenHeight());
            else if (name.equalsIgnoreCase("screen_orientation")) {
                int orientation = getScreenOrientation();
                if ((orientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
                        || (orientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE))
                    return "landscape";
                else
                    return "portrait";
            } else if (name.equalsIgnoreCase("has_network"))
                return Boolean.valueOf(hasNetwork());
            else if (name.equalsIgnoreCase("has_wifi_network"))
                return Boolean.valueOf(hasWiFiNetwork());
            else if (name.equalsIgnoreCase("has_cell_network"))
                return Boolean.valueOf(hasCellNetwork());
            else if (name.equalsIgnoreCase("ppi_x"))
                return Float.valueOf(getScreenPpiX());
            else if (name.equalsIgnoreCase("ppi_y"))
                return Float.valueOf(getScreenPpiY());
            else if (name.equalsIgnoreCase("phone_number")) {
                Context context = ContextFactory.getContext();
                String number = "";
                if (context != null) {
                    TelephonyManager manager = (TelephonyManager) context
                            .getSystemService(Context.TELEPHONY_SERVICE);
                    number = manager.getLine1Number();
                    Logger.I(TAG, "Phone number: " + number + "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
                }
                return number;
            } else if (name.equalsIgnoreCase("device_owner_name")) {
                return AndroidFunctionalityManager.getAndroidFunctionality()
                        .AccessOwnerInfo_getUsername(getContext());
            } else if (name.equalsIgnoreCase("device_owner_email")) {
                return AndroidFunctionalityManager.getAndroidFunctionality().AccessOwnerInfo_getEmail(getContext());
            } else if (name.equalsIgnoreCase("device_name")) {
                return Build.MANUFACTURER + " " + Build.DEVICE;
            } else if (name.equalsIgnoreCase("is_emulator")) {
                String strDevice = Build.DEVICE;
                return Boolean.valueOf(strDevice != null && strDevice.equalsIgnoreCase("generic"));
            } else if (name.equalsIgnoreCase("os_version")) {
                return Build.VERSION.RELEASE;
            } else if (name.equalsIgnoreCase("has_calendar")) {
                return Boolean.valueOf(EventStore.hasCalendar());
            } else if (name.equalsIgnoreCase("phone_id")) {
                RhodesService service = RhodesService.getInstance();
                if (service != null) {
                    PhoneId phoneId = service.getPhoneId();
                    return phoneId.toString();
                } else {
                    return "";
                }
            } else if (name.equalsIgnoreCase("webview_framework")) {
                return RhodesActivity.safeGetInstance().getMainView().getWebView(-1).getEngineId();
            } else if (name.equalsIgnoreCase("is_motorola_device")) {
                return isMotorolaDevice();
            } else if (name.equalsIgnoreCase("oem_info")) {
                return Build.PRODUCT;
            } else if (name.equalsIgnoreCase("uuid")) {
                return fetchUUID();
            } else if (name.equalsIgnoreCase("has_camera")) {
                return Boolean.TRUE;
            } else {
                return RhoExtManager.getImplementationInstance().getProperty(name);
            }
        } catch (Exception e) {
            Logger.E(TAG, "Can't get property \"" + name + "\": " + e);
        }

        return null;
    }

    public static Boolean isMotorolaDevice() {
        Boolean res = false;
        try {
            Class<?> commonClass = Class.forName("com.motorolasolutions.rhoelements.Common");
            Method isEmdkDeviceMethod = commonClass.getDeclaredMethod("isEmdkDevice");
            res = (Boolean) isEmdkDeviceMethod.invoke(null);
        } catch (Throwable e) {
        }
        return Boolean.valueOf(Capabilities.MOTOROLA_ENABLED && res);
    }

    public static String getTimezoneStr() {
        Calendar cal = Calendar.getInstance();
        TimeZone tz = cal.getTimeZone();
        return tz.getDisplayName();
    }

    public static void runApplication(String appName, Object params) {
        try {
            Context ctx = RhodesService.getContext();
            PackageManager mgr = ctx.getPackageManager();
            PackageInfo info = mgr.getPackageInfo(appName, PackageManager.GET_ACTIVITIES);
            if (info.activities.length == 0) {
                Logger.E(TAG, "No activities found for application " + appName);
                return;
            }
            ActivityInfo ainfo = info.activities[0];
            String className = ainfo.name;
            if (className.startsWith("."))
                className = ainfo.packageName + className;

            Intent intent = new Intent();
            intent.setClassName(appName, className);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            if (params != null) {
                Bundle startParams = new Bundle();
                if (params instanceof String) {
                    if (((String) params).length() != 0) {
                        String[] paramStrings = ((String) params).split("&");
                        for (int i = 0; i < paramStrings.length; ++i) {
                            String key = paramStrings[i];
                            String value = "";
                            int splitIdx = key.indexOf('=');
                            if (splitIdx != -1) {
                                value = key.substring(splitIdx + 1);
                                key = key.substring(0, splitIdx);
                            }
                            startParams.putString(key, value);
                        }
                    }
                } else
                    throw new IllegalArgumentException("Unknown type of incoming parameter");

                intent.putExtras(startParams);
            }
            ctx.startActivity(intent);
        } catch (Exception e) {
            Logger.E(TAG, "Can't run application " + appName + ": " + e.getMessage());
        }
    }

    public static boolean isAppInstalled(String appName) {
        try {
            RhodesService.getContext().getPackageManager().getPackageInfo(appName, 0);
            return true;
        } catch (NameNotFoundException ne) {
            return false;
        } catch (Exception e) {
            Logger.E(TAG, "Can't check is app " + appName + " installed: " + e.getMessage());
            return false;
        }
    }

    private void updateDownloadNotification(String url, int totalBytes, int currentBytes) {
        Context context = RhodesActivity.getContext();

        Notification n = new Notification();
        n.icon = android.R.drawable.stat_sys_download;
        n.flags |= Notification.FLAG_ONGOING_EVENT;

        RemoteViews expandedView = new RemoteViews(context.getPackageName(),
                R.layout.status_bar_ongoing_event_progress_bar);

        StringBuilder newUrl = new StringBuilder();
        if (url.length() < 17)
            newUrl.append(url);
        else {
            newUrl.append(url.substring(0, 7));
            newUrl.append("...");
            newUrl.append(url.substring(url.length() - 7, url.length()));
        }
        expandedView.setTextViewText(R.id.title, newUrl.toString());

        StringBuffer downloadingText = new StringBuffer();
        if (totalBytes > 0) {
            long progress = currentBytes * 100 / totalBytes;
            downloadingText.append(progress);
            downloadingText.append('%');
        }
        expandedView.setTextViewText(R.id.progress_text, downloadingText.toString());
        expandedView.setProgressBar(R.id.progress_bar, totalBytes < 0 ? 100 : totalBytes, currentBytes,
                totalBytes < 0);
        n.contentView = expandedView;

        Intent intent = new Intent(ACTION_ASK_CANCEL_DOWNLOAD);
        n.contentIntent = PendingIntent.getBroadcast(context, 0, intent, 0);
        intent = new Intent(ACTION_CANCEL_DOWNLOAD);
        n.deleteIntent = PendingIntent.getBroadcast(context, 0, intent, 0);

        mNM.notify(DOWNLOAD_PACKAGE_ID, n);
    }

    private File downloadPackage(String url) throws IOException {
        final Context ctx = RhodesActivity.getContext();

        final Thread thisThread = Thread.currentThread();

        final Runnable cancelAction = new Runnable() {
            public void run() {
                thisThread.interrupt();
            }
        };

        BroadcastReceiver downloadReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                String action = intent.getAction();
                if (action.equals(ACTION_ASK_CANCEL_DOWNLOAD)) {
                    AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
                    builder.setMessage("Cancel download?");
                    AlertDialog dialog = builder.create();
                    dialog.setButton(AlertDialog.BUTTON_POSITIVE, ctx.getText(android.R.string.yes),
                            new DialogInterface.OnClickListener() {
                                public void onClick(DialogInterface dialog, int which) {
                                    cancelAction.run();
                                }
                            });
                    dialog.setButton(AlertDialog.BUTTON_NEGATIVE, ctx.getText(android.R.string.no),
                            new DialogInterface.OnClickListener() {
                                public void onClick(DialogInterface dialog, int which) {
                                    // Nothing
                                }
                            });
                    dialog.show();
                } else if (action.equals(ACTION_CANCEL_DOWNLOAD)) {
                    cancelAction.run();
                }
            }
        };
        IntentFilter filter = new IntentFilter();
        filter.addAction(ACTION_ASK_CANCEL_DOWNLOAD);
        filter.addAction(ACTION_CANCEL_DOWNLOAD);
        ctx.registerReceiver(downloadReceiver, filter);

        File tmpFile = null;
        InputStream is = null;
        OutputStream os = null;
        try {
            updateDownloadNotification(url, -1, 0);

            /*
            List<File> folders = new ArrayList<File>();
            folders.add(Environment.getDownloadCacheDirectory());
            folders.add(Environment.getDataDirectory());
            folders.add(ctx.getCacheDir());
            folders.add(ctx.getFilesDir());
            try {
               folders.add(new File(ctx.getPackageManager().getApplicationInfo(ctx.getPackageName(), 0).dataDir));
            } catch (NameNotFoundException e1) {
               // Ignore
            }
            folders.add(Environment.getExternalStorageDirectory());
                
            for (File folder : folders) {
               File tmpRootFolder = new File(folder, "rhodownload");
               File tmpFolder = new File(tmpRootFolder, ctx.getPackageName());
               if (tmpFolder.exists())
                  deleteFilesInFolder(tmpFolder.getAbsolutePath());
               else
                  tmpFolder.mkdirs();
                   
               File of = new File(tmpFolder, UUID.randomUUID().toString() + ".apk");
               Logger.D(TAG, "Check path " + of.getAbsolutePath() + "...");
               try {
                  os = new FileOutputStream(of);
               }
               catch (FileNotFoundException e) {
                  Logger.D(TAG, "Can't open file " + of.getAbsolutePath() + ", check next path");
                  continue;
               }
               Logger.D(TAG, "File " + of.getAbsolutePath() + " succesfully opened for write, start download app");
                   
               tmpFile = of;
               break;
            }
            */

            tmpFile = ctx.getFileStreamPath(UUID.randomUUID().toString() + ".apk");
            os = ctx.openFileOutput(tmpFile.getName(), Context.MODE_WORLD_READABLE);

            Logger.D(TAG, "Download " + url + " to " + tmpFile.getAbsolutePath() + "...");

            URL u = new URL(url);
            URLConnection conn = u.openConnection();
            int totalBytes = -1;
            if (conn instanceof HttpURLConnection) {
                HttpURLConnection httpConn = (HttpURLConnection) conn;
                totalBytes = httpConn.getContentLength();
            }
            is = conn.getInputStream();

            int downloaded = 0;
            updateDownloadNotification(url, totalBytes, downloaded);

            long prevProgress = 0;
            byte[] buf = new byte[65536];
            for (;;) {
                if (thisThread.isInterrupted()) {
                    tmpFile.delete();
                    Logger.D(TAG, "Download of " + url + " was canceled");
                    return null;
                }
                int nread = is.read(buf);
                if (nread == -1)
                    break;

                //Logger.D(TAG, "Downloading " + url + ": got " + nread + " bytes...");
                os.write(buf, 0, nread);

                downloaded += nread;
                if (totalBytes > 0) {
                    // Update progress view only if current progress is greater than
                    // previous by more than 10%. Otherwise, if update it very frequently,
                    // user will no have chance to click on notification view and cancel if need
                    long progress = downloaded * 10 / totalBytes;
                    if (progress > prevProgress) {
                        updateDownloadNotification(url, totalBytes, downloaded);
                        prevProgress = progress;
                    }
                }
            }

            Logger.D(TAG, "File stored to " + tmpFile.getAbsolutePath());

            return tmpFile;
        } catch (IOException e) {
            if (tmpFile != null)
                tmpFile.delete();
            throw e;
        } finally {
            try {
                if (is != null)
                    is.close();
            } catch (IOException e) {
            }
            try {
                if (os != null)
                    os.close();
            } catch (IOException e) {
            }

            mNM.cancel(DOWNLOAD_PACKAGE_ID);
            ctx.unregisterReceiver(downloadReceiver);
        }
    }

    public static void installApplication(final String url) {
        Thread bgThread = new Thread(new Runnable() {
            public void run() {
                try {
                    final RhodesService r = RhodesService.getInstance();
                    final File tmpFile = r.downloadPackage(url);
                    if (tmpFile != null) {
                        PerformOnUiThread.exec(new Runnable() {
                            public void run() {
                                try {
                                    Logger.D(TAG, "Install package " + tmpFile.getAbsolutePath());
                                    Uri uri = Uri.fromFile(tmpFile);
                                    Intent intent = new Intent(Intent.ACTION_VIEW);
                                    intent.setDataAndType(uri, "application/vnd.android.package-archive");
                                    r.startActivity(intent);
                                } catch (Exception e) {
                                    Log.e(TAG, "Can't install file from " + tmpFile.getAbsolutePath(), e);
                                    Logger.E(TAG, "Can't install file from " + tmpFile.getAbsolutePath() + ": "
                                            + e.getMessage());
                                }
                            }
                        });
                    }
                } catch (IOException e) {
                    Log.e(TAG, "Can't download package from " + url, e);
                    Logger.E(TAG, "Can't download package from " + url + ": " + e.getMessage());
                }
            }
        });
        bgThread.setPriority(Thread.MIN_PRIORITY);
        bgThread.start();
    }

    public static void uninstallApplication(String appName) {
        try {
            Uri packageUri = Uri.parse("package:" + appName);
            Intent intent = new Intent(Intent.ACTION_DELETE, packageUri);
            RhodesService.getContext().startActivity(intent);
        } catch (Exception e) {
            Logger.E(TAG, "Can't uninstall application " + appName + ": " + e.getMessage());
        }
    }

    /** Opens remote or local URL
     * @throws URISyntaxException, ActivityNotFoundException */
    public static void openExternalUrl(String url) throws URISyntaxException, ActivityNotFoundException {
        //        try
        //        {
        if (url.charAt(0) == '/')
            url = "file://" + RhoFileApi.absolutePath(url);

        //FIXME: Use common URI handling
        Context ctx = RhodesService.getContext();
        LocalFileHandler fileHandler = new LocalFileHandler(ctx);
        if (!fileHandler.handle(url)) {
            Logger.D(TAG, "Handling URI: " + url);

            Intent intent = Intent.parseUri(url, 0);
            ctx.startActivity(Intent.createChooser(intent, "Open in..."));
        }
        //        }
        //        catch (Exception e) {
        //            Logger.E(TAG, "Can't open url :'" + url + "': " + e.getMessage());
        //        }
    }

    public native void setPushRegistrationId(String type, String id);

    private native boolean callPushCallback(String type, String json);

    private void handlePushMessage(String pushType, Bundle extras) {
        Logger.D(TAG, "Handle PUSH message");

        if (extras == null) {
            Logger.W(TAG, "Empty PUSH message received");
            return;
        }

        String phoneId = extras.getString("phone_id");
        if (phoneId != null && phoneId.length() > 0 && !phoneId.equals(this.getPhoneId().toString())) {
            Logger.W(TAG, "Push message for another phone_id: " + phoneId);
            Logger.W(TAG, "Current phone_id: " + this.getPhoneId().toString());
            return;
        }

        String json = new JSONGenerator(extras).toString();

        Logger.D(TAG, "Received PUSH message: " + json);
        if (callPushCallback(pushType, json)) {
            Logger.T(TAG, "Push message completely handled in callback");
            return;
        }

        final String alert = extras.getString("alert");

        boolean statusNotification = false;
        if (Push.PUSH_NOTIFICATIONS.equals(NOTIFICATION_ALWAYS))
            statusNotification = true;
        else if (Push.PUSH_NOTIFICATIONS.equals(NOTIFICATION_BACKGROUND))
            statusNotification = !RhodesApplication.canHandleNow(RhodesApplication.AppState.AppActivated);

        if (statusNotification) {
            Intent intent = new Intent(getContext(), RhodesActivity.class);
            StatusNotification.simpleNotification(TAG, 0, getContext(), intent, getBuildConfig("name"), alert);
        }

        if (alert != null) {
            Logger.D(TAG, "PUSH: Alert: " + alert);
            Alert.showPopup(alert);
        }
        final String sound = extras.getString("sound");
        if (sound != null) {
            Logger.D(TAG, "PUSH: Sound file name: " + sound);
            Alert.playFile("/public/alerts/" + sound, null);
        }
        String vibrate = extras.getString("vibrate");
        if (vibrate != null) {
            Logger.D(TAG, "PUSH: Vibrate: " + vibrate);
            int duration;
            try {
                duration = Integer.parseInt(vibrate);
            } catch (NumberFormatException e) {
                duration = 5;
            }
            final int arg_duration = duration;
            Logger.D(TAG, "Vibrate " + duration + " seconds");
            Alert.vibrate(arg_duration);
        }

        String syncSources = extras.getString("do_sync");
        if ((syncSources != null) && (syncSources.length() > 0)) {
            Logger.D(TAG, "PUSH: Sync:");
            boolean syncAll = false;
            for (String source : syncSources.split(",")) {
                Logger.D(TAG, "url = " + source);
                if (source.equalsIgnoreCase("all")) {
                    syncAll = true;
                    break;
                } else {
                    final String arg_source = source.trim();
                    doSyncSource(arg_source);
                }
            }

            if (syncAll) {
                doSyncAllSources(true);
            }
        }
    }

    private void handlePushMessage(String type, String json) {
        Logger.T(TAG, "Handle push message");

        Logger.D(TAG, "Push message JSON: " + json);

        if (callPushCallback(type, json)) {
            Logger.T(TAG, "Push message completely handled in callback");
            return;
        }
        if (json != null) {
            JSONObject jsonObject;
            try {
                jsonObject = (JSONObject) new JSONTokener(json).nextValue();

                final String alert = jsonObject.optString("alert");

                boolean statusNotification = false;
                if (Push.PUSH_NOTIFICATIONS.equals(NOTIFICATION_ALWAYS)) {
                    Logger.D(TAG, "Show push notification always");
                    statusNotification = true;
                } else if (Push.PUSH_NOTIFICATIONS.equals(NOTIFICATION_BACKGROUND)) {
                    Logger.D(TAG, "Show push notification from background");
                    statusNotification = !RhodesApplication.canHandleNow(RhodesApplication.AppState.AppActivated);
                }

                if (statusNotification) {
                    Logger.D(TAG, "Showing status push notification");
                    Intent intent = new Intent(getContext(), RhodesActivity.class);
                    StatusNotification.simpleNotification(TAG, 0, getContext(), intent, getBuildConfig("name"),
                            alert);
                }

                if (alert.length() > 0) {
                    Logger.D(TAG, "PUSH: Alert: " + alert);
                    Alert.showPopup(alert);
                }
                final String sound = jsonObject.optString("sound");
                if (sound.length() > 0) {
                    Logger.D(TAG, "PUSH: Sound file name: " + sound);
                    Alert.playFile("/public/alerts/" + sound, null);
                }
                int vibrate = jsonObject.optInt("vibrate");
                if (vibrate > 0) {
                    Logger.D(TAG, "PUSH: Vibrate: " + vibrate);
                    Logger.D(TAG, "Vibrate " + vibrate + " seconds");
                    Alert.vibrate(vibrate);
                }
                JSONArray syncSources = jsonObject.optJSONArray("do_sync");
                if ((syncSources != null) && (syncSources.length() > 0)) {
                    Logger.D(TAG, "PUSH: Sync:");
                    boolean syncAll = false;
                    for (int i = 0; i < syncSources.length(); ++i) {
                        String source = syncSources.optString(i);
                        Logger.D(TAG, "source = " + source);
                        if (source.equalsIgnoreCase("all")) {
                            syncAll = true;
                            break;
                        } else {
                            doSyncSource(source);
                        }
                    }

                    if (syncAll) {
                        doSyncAllSources(true);
                    }
                }
            } catch (JSONException e) {
                Logger.E(TAG, "Error parsing JSON payload in push message: " + e.getMessage());
            }
        }
    }

    private void restartGeoLocationIfNeeded() {
        if (mNeedGeoLocationRestart) {
            //GeoLocation.restart();
            mNeedGeoLocationRestart = false;
        }
    }

    private void stopGeoLocation() {
        mNeedGeoLocationRestart = GeoLocation.isAvailable();
        //GeoLocation.stop();
    }

    private void restoreWakeLockIfNeeded() {
        wakeLock.acquire(false);
    }

    private void stopWakeLock() {
        Logger.I(TAG, "activityStopped() temporary release wakeLock object");
        wakeLock.release();
    }

    public static int rho_sys_set_sleeping(int enable) {
        Logger.I(TAG, "rho_sys_set_sleeping(" + enable + ")");
        RhodesService rs = RhodesService.getInstance();
        int wasEnabled = rs.wakeLock.isHeld() ? 1 : 0;
        if (rs != null) {

            if (enable != 0) {
                // disable lock device
                wasEnabled = rs.wakeLock.reset() ? 1 : 0;
            } else {
                // lock device from sleep
                PerformOnUiThread.exec(new Runnable() {
                    public void run() {
                        RhodesService rs = RhodesService.getInstance();
                        if (rs != null)
                            rs.wakeLock.acquire(true);
                        else
                            Logger.E(TAG, "rho_sys_set_sleeping() - No RhodesService has initialized !!!");
                    }
                });
            }
        }
        return wasEnabled;
    }

    private static String fetchUUID() {
        String uuid = "";
        // Get serial number from UUID file built into image
        try {
            if (isMotorolaDevice()) {
                BufferedReader reader = new BufferedReader(new FileReader("/sys/hardware_id/uuid"));
                uuid = reader.readLine();
                Logger.E(TAG, "uuid: " + uuid);
            } else {
                uuid = computeUUID();
                Logger.E(TAG, "uuid: " + uuid);
            }
        } catch (Exception e) {
            Logger.E(TAG, "Cannot determine device UUID");
        } finally {
            return uuid;
        }
    }

    /**
     * This method is used only for non-Motorola devices as the UUID needs to be computed by other parameters.
     * @return 32-byte long UUID
     */
    private static String computeUUID() {
        RhodesService srv = RhodesService.getInstance();
        if (srv == null)
            throw new IllegalStateException("No rhodes service instance at this moment");
        String res = "";
        WifiManager wifi = (WifiManager) srv.getSystemService(Context.WIFI_SERVICE);
        // Get WiFi status
        WifiInfo wifiInfo = wifi.getConnectionInfo();
        String macAddress = wifiInfo.getMacAddress();
        macAddress = macAddress.replaceAll(":", "");
        UUID localUuid = UUID.nameUUIDFromBytes(macAddress.getBytes());
        res = localUuid.toString().replaceAll("-", "");
        return res.toUpperCase();
    }

    void handleAppActivation() {
        if (DEBUG)
            Log.d(TAG, "handle app activation");
        restartGeoLocationIfNeeded();
        restoreWakeLockIfNeeded();
        callActivationCallback(true);
        RhodesApplication.stateChanged(RhodesApplication.AppState.AppActivated);
    }

    void handleAppDeactivation() {
        if (DEBUG)
            Log.d(TAG, "handle app deactivation");
        RhodesApplication.stateChanged(RhodesApplication.AppState.AppDeactivated);
        stopWakeLock();
        stopGeoLocation();
        callActivationCallback(false);
    }

    @Override
    public void startActivity(Intent intent) {

        RhodesActivity ra = RhodesActivity.getInstance();
        if (intent.getComponent() != null
                && intent.getComponent().compareTo(new ComponentName(this, RhodesActivity.class.getName())) == 0) {
            Logger.T(TAG, "Start or bring main activity: " + RhodesActivity.class.getName() + ".");
            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
            if (ra == null) {
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                super.startActivity(intent);
                return;
            }
        }

        if (ra != null) {
            Logger.T(TAG, "Starting new activity on top.");
            if (DEBUG) {
                Bundle extras = intent.getExtras();
                if (extras != null) {
                    for (String key : extras.keySet()) {
                        Object val = extras.get(key);
                        if (val != null)
                            Log.d(TAG, key + ": " + val.toString());
                        else
                            Log.d(TAG, key + ": <empty>");
                    }
                }
            }
            ra.startActivity(intent);
        } else {
            throw new IllegalStateException(
                    "Trying to start activity, but there is no main activity instance (we are in background, no UI active)");
        }
    }

    public static void setFullscreen(boolean enable) {
        BaseActivity.setFullScreenMode(enable);
    }

    public static boolean getFullscreen() {
        return BaseActivity.getFullScreenMode();
    }

    public static void setScreenAutoRotate(boolean enable) {
        BaseActivity.setScreenAutoRotateMode(enable);
    }

    public static boolean getScreenAutoRotate() {
        return BaseActivity.getScreenAutoRotateMode();
    }

    public static Context getContext() {
        RhodesService r = RhodesService.getInstance();
        if (r == null)
            throw new IllegalStateException("No rhodes service instance at this moment");
        return r;
    }

    public static boolean isJQTouch_mode() {
        return RhoConf.getBool("jqtouch_mode");
    }

    public static void bringToFront() {
        if (RhodesApplication.isRhodesActivityStarted()) {
            Logger.T(TAG, "Main activity is already at front, do nothing");
            return;
        }

        RhodesService srv = RhodesService.getInstance();
        if (srv == null)
            throw new IllegalStateException("No rhodes service instance at this moment");

        Logger.T(TAG, "Bring main activity to front");

        Intent intent = new Intent(srv, RhodesActivity.class);
        srv.startActivity(intent);
    }

    public static String getNativeMenu() {
        List<Object> menuItems = RhodesActivity.safeGetInstance().getMenu().getMenuDescription();
        String menuJson = new JSONGenerator(menuItems).toString();

        Logger.T(TAG, "Menu: " + menuJson);

        return menuJson;
    }

    public static void setNativeMenu(List<String> jsonMenu) {
        List<Map<String, String>> nativeMenu = new ArrayList<Map<String, String>>();

        Iterator<String> iter = jsonMenu.iterator();
        while (iter.hasNext()) {
            try {
                String jsonItem = iter.next();
                Map<String, String> menuItem = new HashMap<String, String>();
                JSONObject json = new JSONObject(jsonItem);
                Iterator<String> itemIter = json.keys();
                while (itemIter.hasNext()) {
                    Logger.T(TAG, "New menu item");
                    String itemKey = itemIter.next();
                    String itemDescr = json.getString(itemKey);

                    Logger.T(TAG, itemKey + "->" + itemDescr);

                    menuItem.put(itemKey, itemDescr);
                }
                nativeMenu.add(menuItem);
            } catch (JSONException e) {
                Logger.E(TAG, e);
            }
        }
        RhodesActivity.safeGetInstance().getMenu().setMenu(nativeMenu);
    }
}