/* * Copyright (C) 2020, University of the Basque Country (UPV/EHU) * Contact for licensing options: <licensing-mcpttclient(at)mcopenplatform(dot)com> * * The original file was part of Open Source IMSDROID * Copyright (C) 2010-2011, Mamadou Diop. * Copyright (C) 2011, Doubango Telecom. * * * Contact: Mamadou Diop <diopmamadou(at)doubango(dot)org> * * This file is part of Open Source Doubango Framework. * * This is free software: you can redistribute it and/or modify it under the terms of * the GNU General Public License as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * This 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 for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package org.doubango.ngn; import android.app.ActivityManager; import android.app.Application; import android.app.KeyguardManager; import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.hardware.SensorManager; import android.media.AudioManager; import android.net.ConnectivityManager; import android.os.Build; import android.os.PowerManager; import android.telephony.TelephonyManager; import android.util.Log; import android.view.Display; import android.view.WindowManager; import org.doubango.ngn.utils.NgnStringUtils; import org.doubango.utils.AndroidUtils; import org.doubango.utils.CpuFeatures_t; import org.doubango.utils.Utils; import java.lang.reflect.Field; import java.util.Arrays; /** @mainpage Foreword * * <h1>Foreword</h1> * <b>android-ngn-stack</b> is a <a href="http://en.wikipedia.org/wiki/Next_generation_network">NGN</a> (Next Generation Network) stack for Android 2.x (or later) devices. <br /> * The Stack is based on <a href="http://doubango.org">doubango</a> framework. <a href="http://doubango.org">doubango</a> is the world's most advanced open source * 3GPP IMS/RCS framework for both embedded and desktop systems. <br /> * The main purpose is to provide an open source stack for the developers to build their own VoIP applications. <br /> * This framework offers a unique set of features ranging from audio/video calls, content sharing, messaging, conferencing, enhanced address book to social presence. * All these features are implemented in accordance with the standards: GSMA RCS, 3GPP IMS or VoLTE.<br /> * * @page Introduction * This document has been written by us (Doubango Telecom) to help developers to quickly create innovative multimedia applications * for the Android OS. If you are a developer and is looking for the best way to develop a NGN (VoIP, Messaging, Video Conferencing, ...) or rich application for Android * then your are at the right place. <br /> * If you want to get help or have some feedbacks then please visit our website: <a href="http://code.google.com/p/imsdroid/">http://code.google.com/p/imsdroid/</a> * * <h2>Doubango Solution</h2> * <b>android-ngn-stack</b> is part of Doubango Solution which include many components such as: * * <h3>Client-side components</h3> * - <a href="http://code.google.com/p/boghe/">Boghe</a>: IMS/RCS Client for Windows * - <a href="http://code.google.com/p/imsdroid/">IMSDroid</a>: IMS/RCS Client for Android using <b>android-ngn-stack</b> * - <a href="http://code.google.com/p/idoubs/">iDoubs</a>: IMS/RCS Client for iOS (iPhone, iPad and iPod Touch) * * <h3>Server-side components</h3> * - <a href="http://code.google.com/p/openvcs/">OpenVCS</a>: OpenVCS stands for Open Source Video Conferencing Server and is used to manage Multipoint Control Units (MCU). Each MCU (a.k.a Bridge) can handle up to 64 participants * - <a href="http://code.google.com/p/flash2ims/">Flash2IMS</a>: Adobe Flash to SIP/IMS Gateway. * * <h2>Highlights</h2> * * - SIP(RFC 3261, 3GPP TS 24.229 Rel-9) * - TCP and UDP over IPv4 or IPv6 * - Signalling Compression, SigComp(RFC 3320, 3485, 4077, 4464, 4465, 4896, 5049, 5112 and 1951) * * - Enhanced Address Book (XCAP storage, authorizations, presence, ...) * - GSMA Rich Communication Suite release 3 * - Partial supports for One Voice Profile V1.0.0 (GSMA VoLTE) * - Partial supports for MMTel UNI (used by GSMA RCS and GSMA VoLTE) * * - IMS-AKA registration (both AKA-v1 and AKA-v2), Digest MD5, Basic * - 3GPP Early IMS Security (3GPP TS 33.978) * - Proxy-CSCF discovery using DNS NAPTR+SRV * - Private extension headers for 3GPP * - Service Route discovery * - Subscription to reg event package (Honoring network initiated (re/de/un)-registration events) * * - 3GPP SMS Over IP (3GPP TS 23.038, 24.040, 24.011, 24.341 and 24.451) * - Voice Call (G729AB1, AMR-NB, iLBC, GSM, PCMA, PCMU, Speex-NB) * - Video Call (H264, MP4V-ES, Theora, H.263, H.263-1998, H.261) * - DTMF (RFC 4733) * - QoS negotiation using Preconditions (RFC 3312, 4032 and 5027) * - SIP Session Timers (RFC 4028) * - Provisional Response Acknowledgments (PRACK) * - Communication Hold (3GPP TS 24.610) * - Message Waiting Indication (3GPP TS 24.606) * - Calling E.164 numbers by using ENUM protocol (RFC 3761) * - NAT Traversal using STUN2 (RFC 5389) with possibilities to automatically discover the server by using DNS SRV (TURN already implemented and ICE is under tests) * * - One2One and Group Chat * - File Transfer and Content sharing * * * @page page__Setting_Up_NGN_project Setting up NGN project * @anchor anchor_Setting_Up_NGN_project * This section explain how to setup a NGN project using Eclipse.<br /> * * <h2>Checking out the source code</h2> * To check out the source code of the NGN library you will need a SVN client.<br /> * Use this command to anonymously check out the last project source: * @code * svn checkout http://imsdroid.googlecode.com/svn imsdroid * @endcode * The source code of the library is under: * @code * This/branches/2.0/android-ngn-stack * @endcode * * <h2>Importing the NGN project into Eclipse</h2> * The NGN project is the Next Generation Network library. * - Open eclipse * - Go to File -> Import -> General -> Existing Project into workspace * - Select <b> android-ngn-stack </b> folder and click <b>Finish</b> * * @image html ngn_eclipse_import.png "Importing project into your workspace" * * <h2>Creating you first NGN application using Eclipse</h2> * - Open Eclipse and select File -> New -> Android Project * - From the next window (<b>"New Android Project"</b>) fill the text fields like this:<br /> * - Project name: <b>myFirstApp</b><br /> * - Location: < set any path ><br /> * - Build Target: <b>Android 2.0</b> (at least)<br /> * - Application name: <b>myFirstApp</b><br /> * - Package name: <b>org.doubango.test</b><br /> * - Check <b>"Create Activity"</b> and name it <b>"Main"</b><br /> * * @image html ngn_eclipse_newproj.png "Create your first NGN application" * - Click on Finish to create the project * - From the Eclipse package explorer, right click on <b>myFirstApp</b> and select <b>"Properties"</b> then "Android" from the left<br/><br/> * @image html ngn_eclipse_properties_1.png "NGN application properties" * - From the properties window, select <b>"Add"</b> button then select <b>android-ngn-stack</b> from the list of the available libraries<br/><br/> * @image html ngn_eclipse_properties_2.png "Add dependencies" * - Select <b>"Java Compiler"</b> from the left and change the version from 1.5 to 1.6<br/><br/> * @image html ngn_eclipse_jdk_version.png "Java Compiler version" * - Select <b>"Java Build Path"</b> from the left, then <b>"Libraries"</b><br/><br/> * @image html ngn_eclipse_java_buil_path_1.png "Java Build Path 1/2" * - From "Java Build Path 1/2", select <b>"Add JARs..."</b> then <b>android-ngn-stack/libs/simple-xml-2.3.4.jar</b>, then <b>"OK"</b> to close the window<br/><br/> * @image html ngn_eclipse_java_buil_path_2.png "Java Build Path 2/2" * - Click on <b>"OK"</b> to close the window * * <h2>Setting up Android Permissions</h2> * In order to use the framework you must enable some user-permission in your Android manifest. <br /> * Open <b>myFirstApp/AndroidManifest.xml</b>, then add this: * @code * <uses-permission android:name="android.permission.INTERNET" /> * <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> * <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> * <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> * <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /> * * <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> * <uses-permission android:name="android.permission.CAMERA" /> * <uses-permission android:name="android.permission.WAKE_LOCK" /> * <uses-permission android:name="android.permission.RECORD_AUDIO" /> * <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> * <uses-permission android:name="android.permission.VIBRATE" /> * <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> * * <uses-permission android:name="android.permission.WRITE_SETTINGS" /> * <uses-permission android:name="android.permission.DISABLE_KEYGUARD" /> * <uses-permission android:name="android.permission.READ_CONTACTS"/> * <uses-permission android:name="android.permission.WRITE_CONTACTS"/> * <uses-permission android:name="android.permission.READ_PHONE_STATE" /> * <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" /> * <uses-permission android:name="android.permission.CALL_PHONE" /> * <uses-permission android:name="android.permission.RAISED_THREAD_PRIORITY"/> * @endcode * ... just before @code </manifest> @endcode * * * <h2>Declaring your app as NGN</h2> * Decalaring your app as NGN is recommended if your are programming at <b>high</b> level. <br /> * - From the Eclipse package explorer, open <b>AndroidManifest.xml</b> and select <b>Application</b> tab from below * - Click on <b>browse</b> (on the right of <b>Name</b>) then, select <b>"NgnApplication"</b> from the list<br/><br/> * @image html ngn_eclipse_declaring_ngn_app.png "Declaring your app as NGN" * * * @page Architecture * The stack offers three levels of programming: <b>Low</b>, <b>Medium</b> and <b>High</b>.<br /> * Before building and running your project, you should take a look at the section @ref anchor_Setting_Up_NGN_project "explaining how to setup a NGN project". * <h2>Low level</h2> * This level allow you to directly have access to doubango functions through JNI. * This level is the most flexible one but is out of scoop because it's too difficult to manage. <br /> * All functions used in this level are in one single package: <b>org.doubango.tinyWRAP</b><br /> * For example, the code below shows how to register to a SIP/IMS server: * @code * final String realm = "sip:doubango.org"; * final String privateIdentity = "001"; * final String publicIdentity = "sip:001@doubango.org"; * final String password = "my secret"; * final String proxyHost = "192.168.0.1"; * RegistrationSession registrationSession; * // Sip Callback * final SipCallback callback = new SipCallback(){ * @Override * public int OnDialogEvent(DialogEvent e) { * final SipSession sipSession = e.getBaseSession(); * final long sipSessionId = sipSession.getId(); * final short code = e.getCode(); * switch (code){ * case tinyWRAPConstants.tsip_event_code_dialog_connecting: * if(registrationSession != null && registrationSession.getId() == sipSessionId){ * // Registration in progress * } * break; * case tinyWRAPConstants.tsip_event_code_dialog_connected: * if(registrationSession != null && registrationSession.getId() == sipSessionId){ * // You are registered * } * break; * case tinyWRAPConstants.tsip_event_code_dialog_terminating: * if(registrationSession != null && registrationSession.getId() == sipSessionId){ * // You are unregistering * } * break; * case tinyWRAPConstants.tsip_event_code_dialog_terminated: * if(registrationSession != null && registrationSession.getId() == sipSessionId){ * // You are unregistered * } * break; * } * * return 0; * } * * @Override * public int OnRegistrationEvent(RegistrationEvent e) { * // low level events * return 0; * } * }; * // Create the SipStack * SipStack sipStack = new SipStack(callback, realm, privateIdentity, publicIdentity); * // Set Proxy Host and port * sipStack.setProxyCSCF(proxyHost, 5060, "UDP", "IPv4"); * // Set password * sipStack.setPassword(password); * if(sipStack.isValid()){ * if(sipStack.start()){ * registrationSession = new RegistrationSession(sipStack); * registrationSession.setFromUri(publicIdentity); * // Send SIP register request * registrationSession.register_(); * } * } * @endcode * * <h2>Medium level</h2> * This level is built on of the <b>low</b> level. The main advantage of this level is that it's flexible without * being too complicated as all low level functions are wrapped into comprehensive API. * For example, if you want to implement a multi-stack (multi-account) application this is the right level. * * <h2>High level</h2> * This level is built in top of the <b>low</b> level and is much easier than the later. * The High level is composed of a set of Services managed by a single NGN engine instance. Each service is responsible for * a particular task. For example, you have one service for SIP, one for contact management, one for networking etc etc <br /> * * <h3>NGN Engine</h3> * The engine is a black box containing all the services. You must always retrieve the services through the engine. <br /> * You must also start/stop the services through the NGN engine.<br /> * The code below shows how to get an instance of the engine: * @code * // Gets an instance of the engine. This function will always returns the same instance * // which means that you can call it as many as you want from anywhere in your code * final NgnEngine mEngine = NgnEngine.getInstance(); * @endcode * The code below shows how to get some services from the engine: * @code * // Gets the configuration service * INgnConfigurationService mConfigurationService = mEngine.getConfigurationService(); * // Gets the SIP/IMS service * INgnSipService mSipService = mEngine.getSipService(); * // etc etc * @endocde * The code below shows how to start/stop the engine. * @code * // Starts the engine * mEngine.start(); * // Stops the engine * mEngine.stop(); * @endcode * Starting/Stopping the engine will start/stop all underlying services. * * /** * Global object defining the application. You should extends this class in your own * Android application. */ public class NgnApplication extends Application{ private final static String TAG = Utils.getTAG(NgnApplication.class.getCanonicalName()); private static NgnApplication sInstance; private static PackageManager sPackageManager; private static String sPackageName; private static String sDeviceURN; private static String sDeviceIMEI; private static int sSdkVersion; private static int sVersionCode; private static AudioManager sAudioManager; private static SensorManager sSensorManager; private static KeyguardManager sKeyguardManager; private static ConnectivityManager sConnectivityManager; private static PowerManager sPowerManager; private static PowerManager.WakeLock sPowerManagerLock; private static int sGlEsVersion; static final String sBuildModel = Build.MODEL.toLowerCase(); static final String[] sSLEs2FriendlyBuildModels = { "galaxy nexus", /* 4.1 */ "gt-i9100", /* Galaxy SII, 4.0.4 */ /*"gt-s5360",*//* 2.3.6 :robotic*/ "gt-s5570i", /* 2.3.0 */ "xt890", /* Motorola Razer i 4.0.4 */ "lg-p970" /* 2.3.4 */ }; static final String[] sSLEs2UnFriendlyBuildModels = { "gt-s5360",/* 2.3.6 :robotic*/ }; static final String[] sSetModeFriendlyBuildModels = { "gt-s5570i" /* 2.3.6 */ }; // This function is called by the package manager, you must never explicitly invoke it. // Do not forget to add/change the <application /> section in your manifest as done in IMSDroid or all other test apps public NgnApplication() { sInstance = this; } public static NgnApplication getInstance(){ return sInstance; } /** * Retrieve application's context * @return Android context */ public static Context getContext() { return getInstance(); } @Override public void onCreate() { super.onCreate(); sPackageManager = sInstance.getPackageManager(); sPackageName = sInstance.getPackageName(); Log.d(TAG,"Build.MODEL="+sBuildModel); Log.d(TAG,"Build.VERSION.SDK="+Build.VERSION.SDK); } /** * Gets Android SDK version * @return Android SDK version used to build the project */ public static int getSDKVersion(){ if(sSdkVersion == 0){ sSdkVersion = Integer.parseInt(Build.VERSION.SDK); } return sSdkVersion; } /** * Whether we need special hack for buggy speaker. For example, all Samsung devices * need to be hacked. * @return true if we need to apply the hack and false otherwise */ public static boolean useSetModeToHackSpeaker(){ return (isSamsung() && !isSamsungGalaxyMini() && getSDKVersion()<= 7) || sBuildModel.equalsIgnoreCase("blade") || // ZTE Blade sBuildModel.equalsIgnoreCase("htc_supersonic") || //HTC EVO sBuildModel.equalsIgnoreCase("U8110") || // Huawei U8110 sBuildModel.equalsIgnoreCase("U8150") // Huawei U8110 ; } public static boolean isARMv7WithoutNeon(){ return (isCpuARMv7() && !isCpuNeon()); } public static boolean isCpuARMv8(){ return ((AndroidUtils.getCpuFeatures().intValue() & CpuFeatures_t.ARMv7.swigValue()) != 0); } public static boolean isCpuARMv7(){ return ((AndroidUtils.getCpuFeatures().intValue() & CpuFeatures_t.ARMv7.swigValue()) != 0); } public static int androidCpuFeature(){ return AndroidUtils.getCpuFeatures().intValue(); } public static boolean isCpuNeon(){ return ((AndroidUtils.getCpuFeatures().intValue() & CpuFeatures_t.NEON.swigValue()) != 0); } /** * Whether the stack is running on a Samsung Galaxy Mini device * @return true if the stack is running on a Samsung Galaxy Mini device and false otherwise */ public static boolean isSamsungGalaxyMini(){ return sBuildModel.equalsIgnoreCase("gt-i5800"); } /** * Whether the stack is running on a Samsung Galaxy Mini device * @return true if the stack is running on a Samsung Galaxy Mini device and false otherwise */ public static boolean isSamsung(){ return sBuildModel.startsWith("gt-") || sBuildModel.contains("samsung") || sBuildModel.startsWith("sgh-") || sBuildModel.startsWith("sph-") || sBuildModel.startsWith("sch-"); } /** * Whether the stack is running on a HTC device * @return true if the stack is running on a HTC device and false otherwise */ public static boolean isHTC(){ return sBuildModel.startsWith("htc"); } /** * Whether the stack is running on a ZTE device * @return true if the stack is running on a ZTE device and false otherwise */ public static boolean isZTE(){ return sBuildModel.startsWith("zte"); } /** * Whether the stack is running on a LG device * @return true if the stack is running on a LG device and false otherwise */ public static boolean isLG(){ return sBuildModel.startsWith("lg-"); } /** * Whether the stack is running on a Toshiba device * @return true if the stack is running on a Toshiba device and false otherwise */ public static boolean isToshiba(){ return sBuildModel.startsWith("toshiba"); } /** * Whether the stack is running on a Hovis box * @return true if the stack is running on a Hovis box and false otherwise */ public static boolean isHovis(){ return sBuildModel.startsWith("hovis_box_"); } public static boolean isAudioRecreateRequired(){ return false; } public static boolean isSetModeAllowed(){ return isZTE() || isLG() || Arrays.asList(sSetModeFriendlyBuildModels).contains(sBuildModel); } public static boolean isBuggyProximitySensor(){ return isZTE(); } public static boolean isAGCSupported(){ return isSamsung() || isHTC(); } public static int getVersionCode(){ if(sVersionCode == 0 && sPackageManager != null){ try { sVersionCode = sPackageManager.getPackageInfo(sPackageName, 0).versionCode; } catch (NameNotFoundException e) { e.printStackTrace(); } } return sVersionCode; } public static String getVersionName(){ if(sPackageManager != null){ try { return sPackageManager.getPackageInfo(sPackageName, 0).versionName; } catch (NameNotFoundException e) { e.printStackTrace(); } } return "0.0"; } public static String getDeviceURN(){ if(NgnStringUtils.isNullOrEmpty(sDeviceURN)){ try{ final TelephonyManager telephonyMgr = (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE); final String msisdn = telephonyMgr.getLine1Number(); if(NgnStringUtils.isNullOrEmpty(msisdn)){ sDeviceURN = String.format("urn:imei:%s", telephonyMgr.getDeviceId()); } else{ sDeviceURN = String.format("urn:tel:%s", msisdn); } } catch(Exception e){ Log.d(TAG, e.toString()); sDeviceURN = "urn:uuid:3ca50bcb-7a67-44f1-afd0-994a55f930f4"; } } return sDeviceURN; } public static String getDeviceIMEI(){ if(NgnStringUtils.isNullOrEmpty(sDeviceIMEI)){ final TelephonyManager telephonyMgr = (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE); sDeviceIMEI = telephonyMgr.getDeviceId(); } return sDeviceIMEI; } public static AudioManager getAudioManager(){ if(sAudioManager == null){ sAudioManager = (AudioManager)getContext().getSystemService(Context.AUDIO_SERVICE); } return sAudioManager; } public static SensorManager getSensorManager(){ if(sSensorManager == null){ sSensorManager = (SensorManager)getContext().getSystemService(Context.SENSOR_SERVICE); } return sSensorManager; } public static KeyguardManager getKeyguardManager(){ if(sKeyguardManager == null){ sKeyguardManager = (KeyguardManager)getContext().getSystemService(Context.KEYGUARD_SERVICE); } return sKeyguardManager; } public static ConnectivityManager getConnectivityManager(){ if(sConnectivityManager == null){ sConnectivityManager = (ConnectivityManager) getContext().getSystemService(CONNECTIVITY_SERVICE); } return sConnectivityManager; } public static PowerManager getPowerManager(){ if(sPowerManager == null){ sPowerManager = (PowerManager) getContext().getSystemService(POWER_SERVICE); } return sPowerManager; } public static Display getDefaultDisplay(){ return ((WindowManager)getContext().getSystemService(WINDOW_SERVICE)).getDefaultDisplay(); } public static String getABI(){ try { Field field = android.os.Build.class.getField("CPU_ABI"); String abi = field.get(null).toString(); if(abi == null){ return "unknown"; } return abi; } catch (Exception e) { e.printStackTrace(); return "unknown"; } } public static int getGlEsVersion(){ if(sGlEsVersion == 0){ sGlEsVersion = ((ActivityManager) NgnApplication.getContext().getSystemService(Context.ACTIVITY_SERVICE)).getDeviceConfigurationInfo().reqGlEsVersion; Log.d(TAG, "sGlEsVersion=" + sGlEsVersion); } return sGlEsVersion; } public static boolean isGlEs2Supported(){ return getGlEsVersion() >= 0x20000; } public static boolean isSLEs2Supported(){ return (NgnApplication.getSDKVersion() >= 9); } public static boolean isSLEs2KnownToWork(){ // FIXME: one-way audio on Galaxy SII and Nexus //return isSLEs2Supported() && Arrays.asList(sSLEs2FriendlyBuildModels).contains(sBuildModel); // AcceptOnlyIn(Array) //return isSLEs2Supported() && !Arrays.asList(sSLEs2UnFriendlyBuildModels).contains(sBuildModel); // AcceptAllExceptIn(Array) //return false; return true; //return isHovis(); //false; } public static boolean acquirePowerLock(){ if(sPowerManagerLock == null){ final PowerManager powerManager = getPowerManager(); if(powerManager == null){ Log.e(TAG, "Null Power manager from the system"); return false; } if((sPowerManagerLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG)) == null){ Log.e(TAG, "Null Power manager lock from the system"); return false; } sPowerManagerLock.setReferenceCounted(false); } synchronized(sPowerManagerLock){ if(!sPowerManagerLock.isHeld()){ Log.d(TAG,"acquirePowerLock()"); sPowerManagerLock.acquire(); } } return true; } public static boolean releasePowerLock(){ if(sPowerManagerLock != null){ synchronized(sPowerManagerLock){ if(sPowerManagerLock.isHeld()){ Log.d(TAG,"releasePowerLock()"); sPowerManagerLock.release(); } } } return true; } }