From 7ecdadbbaa4a77ba0a2f33b494460e11fc3f59d4 Mon Sep 17 00:00:00 2001 From: Nanang Izzuddin Date: Mon, 19 Dec 2016 04:48:35 +0000 Subject: Re #1986: Convert pjsua2 sample app Android project from Eclipse to Android Studio. git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@5502 74dad513-b988-da41-8d7b-12977e46ad98 --- pjsip-apps/src/swig/java/Makefile | 6 +- pjsip-apps/src/swig/java/android/.classpath | 9 - pjsip-apps/src/swig/java/android/.project | 33 -- .../android/.settings/org.eclipse.jdt.core.prefs | 4 - .../src/swig/java/android/AndroidManifest.xml | 48 -- pjsip-apps/src/swig/java/android/app/build.gradle | 23 + .../java/android/app/src/main/AndroidManifest.xml | 48 ++ .../app/src/main/java/org/pjsip/PjCamera.java | 210 ++++++++ .../app/src/main/java/org/pjsip/PjCameraInfo.java | 110 ++++ .../java/org/pjsip/pjsua2/app/CallActivity.java | 364 +++++++++++++ .../java/org/pjsip/pjsua2/app/MainActivity.java | 581 +++++++++++++++++++++ .../src/main/java/org/pjsip/pjsua2/app/MyApp.java | 540 +++++++++++++++++++ .../app/src/main/res/drawable-hdpi/ic_launcher.png | Bin 0 -> 7658 bytes .../app/src/main/res/drawable-mdpi/ic_launcher.png | Bin 0 -> 3777 bytes .../src/main/res/drawable-xhdpi/ic_launcher.png | Bin 0 -> 12516 bytes .../src/main/res/drawable-xxhdpi/ic_launcher.png | Bin 0 -> 24777 bytes .../java/android/app/src/main/res/drawable/bkg.xml | 5 + .../app/src/main/res/layout/activity_call.xml | 73 +++ .../app/src/main/res/layout/activity_main.xml | 65 +++ .../app/src/main/res/layout/dlg_account_config.xml | 77 +++ .../app/src/main/res/layout/dlg_add_buddy.xml | 25 + .../java/android/app/src/main/res/menu/call.xml | 9 + .../java/android/app/src/main/res/menu/main.xml | 14 + .../app/src/main/res/values-sw600dp/dimens.xml | 8 + .../src/main/res/values-sw720dp-land/dimens.xml | 9 + .../android/app/src/main/res/values-v11/styles.xml | 11 + .../android/app/src/main/res/values-v14/styles.xml | 12 + .../android/app/src/main/res/values/colors.xml | 5 + .../android/app/src/main/res/values/dimens.xml | 7 + .../android/app/src/main/res/values/strings.xml | 10 + .../android/app/src/main/res/values/styles.xml | 20 + pjsip-apps/src/swig/java/android/build.gradle | 15 + .../java/android/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 53636 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + pjsip-apps/src/swig/java/android/gradlew | 160 ++++++ pjsip-apps/src/swig/java/android/gradlew.bat | 90 ++++ .../src/swig/java/android/ic_launcher-web.png | Bin 51394 -> 0 bytes pjsip-apps/src/swig/java/android/jni/Android.mk | 13 - .../src/swig/java/android/jni/Application.mk | 2 - .../src/swig/java/android/proguard-project.txt | 20 - .../src/swig/java/android/project.properties | 14 - .../java/android/res/drawable-hdpi/ic_launcher.png | Bin 7658 -> 0 bytes .../java/android/res/drawable-mdpi/ic_launcher.png | Bin 3777 -> 0 bytes .../android/res/drawable-xhdpi/ic_launcher.png | Bin 12516 -> 0 bytes .../android/res/drawable-xxhdpi/ic_launcher.png | Bin 24777 -> 0 bytes .../src/swig/java/android/res/drawable/bkg.xml | 5 - .../swig/java/android/res/layout/activity_call.xml | 73 --- .../swig/java/android/res/layout/activity_main.xml | 65 --- .../java/android/res/layout/dlg_account_config.xml | 77 --- .../swig/java/android/res/layout/dlg_add_buddy.xml | 25 - pjsip-apps/src/swig/java/android/res/menu/call.xml | 9 - pjsip-apps/src/swig/java/android/res/menu/main.xml | 14 - .../java/android/res/values-sw600dp/dimens.xml | 8 - .../android/res/values-sw720dp-land/dimens.xml | 9 - .../swig/java/android/res/values-v11/styles.xml | 11 - .../swig/java/android/res/values-v14/styles.xml | 12 - .../src/swig/java/android/res/values/colors.xml | 5 - .../src/swig/java/android/res/values/dimens.xml | 7 - .../src/swig/java/android/res/values/strings.xml | 10 - .../src/swig/java/android/res/values/styles.xml | 20 - pjsip-apps/src/swig/java/android/settings.gradle | 1 + .../src/org/pjsip/pjsua2/app/CallActivity.java | 364 ------------- .../src/org/pjsip/pjsua2/app/MainActivity.java | 581 --------------------- .../android/src/org/pjsip/pjsua2/app/MyApp.java | 540 ------------------- 64 files changed, 2501 insertions(+), 1981 deletions(-) delete mode 100644 pjsip-apps/src/swig/java/android/.classpath delete mode 100644 pjsip-apps/src/swig/java/android/.project delete mode 100644 pjsip-apps/src/swig/java/android/.settings/org.eclipse.jdt.core.prefs delete mode 100644 pjsip-apps/src/swig/java/android/AndroidManifest.xml create mode 100644 pjsip-apps/src/swig/java/android/app/build.gradle create mode 100644 pjsip-apps/src/swig/java/android/app/src/main/AndroidManifest.xml create mode 100644 pjsip-apps/src/swig/java/android/app/src/main/java/org/pjsip/PjCamera.java create mode 100644 pjsip-apps/src/swig/java/android/app/src/main/java/org/pjsip/PjCameraInfo.java create mode 100644 pjsip-apps/src/swig/java/android/app/src/main/java/org/pjsip/pjsua2/app/CallActivity.java create mode 100644 pjsip-apps/src/swig/java/android/app/src/main/java/org/pjsip/pjsua2/app/MainActivity.java create mode 100644 pjsip-apps/src/swig/java/android/app/src/main/java/org/pjsip/pjsua2/app/MyApp.java create mode 100644 pjsip-apps/src/swig/java/android/app/src/main/res/drawable-hdpi/ic_launcher.png create mode 100644 pjsip-apps/src/swig/java/android/app/src/main/res/drawable-mdpi/ic_launcher.png create mode 100644 pjsip-apps/src/swig/java/android/app/src/main/res/drawable-xhdpi/ic_launcher.png create mode 100644 pjsip-apps/src/swig/java/android/app/src/main/res/drawable-xxhdpi/ic_launcher.png create mode 100644 pjsip-apps/src/swig/java/android/app/src/main/res/drawable/bkg.xml create mode 100644 pjsip-apps/src/swig/java/android/app/src/main/res/layout/activity_call.xml create mode 100644 pjsip-apps/src/swig/java/android/app/src/main/res/layout/activity_main.xml create mode 100644 pjsip-apps/src/swig/java/android/app/src/main/res/layout/dlg_account_config.xml create mode 100644 pjsip-apps/src/swig/java/android/app/src/main/res/layout/dlg_add_buddy.xml create mode 100644 pjsip-apps/src/swig/java/android/app/src/main/res/menu/call.xml create mode 100644 pjsip-apps/src/swig/java/android/app/src/main/res/menu/main.xml create mode 100644 pjsip-apps/src/swig/java/android/app/src/main/res/values-sw600dp/dimens.xml create mode 100644 pjsip-apps/src/swig/java/android/app/src/main/res/values-sw720dp-land/dimens.xml create mode 100644 pjsip-apps/src/swig/java/android/app/src/main/res/values-v11/styles.xml create mode 100644 pjsip-apps/src/swig/java/android/app/src/main/res/values-v14/styles.xml create mode 100644 pjsip-apps/src/swig/java/android/app/src/main/res/values/colors.xml create mode 100644 pjsip-apps/src/swig/java/android/app/src/main/res/values/dimens.xml create mode 100644 pjsip-apps/src/swig/java/android/app/src/main/res/values/strings.xml create mode 100644 pjsip-apps/src/swig/java/android/app/src/main/res/values/styles.xml create mode 100644 pjsip-apps/src/swig/java/android/build.gradle create mode 100644 pjsip-apps/src/swig/java/android/gradle/wrapper/gradle-wrapper.jar create mode 100644 pjsip-apps/src/swig/java/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 pjsip-apps/src/swig/java/android/gradlew create mode 100644 pjsip-apps/src/swig/java/android/gradlew.bat delete mode 100644 pjsip-apps/src/swig/java/android/ic_launcher-web.png delete mode 100644 pjsip-apps/src/swig/java/android/jni/Android.mk delete mode 100644 pjsip-apps/src/swig/java/android/jni/Application.mk delete mode 100644 pjsip-apps/src/swig/java/android/proguard-project.txt delete mode 100644 pjsip-apps/src/swig/java/android/project.properties delete mode 100644 pjsip-apps/src/swig/java/android/res/drawable-hdpi/ic_launcher.png delete mode 100644 pjsip-apps/src/swig/java/android/res/drawable-mdpi/ic_launcher.png delete mode 100644 pjsip-apps/src/swig/java/android/res/drawable-xhdpi/ic_launcher.png delete mode 100644 pjsip-apps/src/swig/java/android/res/drawable-xxhdpi/ic_launcher.png delete mode 100644 pjsip-apps/src/swig/java/android/res/drawable/bkg.xml delete mode 100644 pjsip-apps/src/swig/java/android/res/layout/activity_call.xml delete mode 100644 pjsip-apps/src/swig/java/android/res/layout/activity_main.xml delete mode 100644 pjsip-apps/src/swig/java/android/res/layout/dlg_account_config.xml delete mode 100644 pjsip-apps/src/swig/java/android/res/layout/dlg_add_buddy.xml delete mode 100644 pjsip-apps/src/swig/java/android/res/menu/call.xml delete mode 100644 pjsip-apps/src/swig/java/android/res/menu/main.xml delete mode 100644 pjsip-apps/src/swig/java/android/res/values-sw600dp/dimens.xml delete mode 100644 pjsip-apps/src/swig/java/android/res/values-sw720dp-land/dimens.xml delete mode 100644 pjsip-apps/src/swig/java/android/res/values-v11/styles.xml delete mode 100644 pjsip-apps/src/swig/java/android/res/values-v14/styles.xml delete mode 100644 pjsip-apps/src/swig/java/android/res/values/colors.xml delete mode 100644 pjsip-apps/src/swig/java/android/res/values/dimens.xml delete mode 100644 pjsip-apps/src/swig/java/android/res/values/strings.xml delete mode 100644 pjsip-apps/src/swig/java/android/res/values/styles.xml create mode 100644 pjsip-apps/src/swig/java/android/settings.gradle delete mode 100644 pjsip-apps/src/swig/java/android/src/org/pjsip/pjsua2/app/CallActivity.java delete mode 100644 pjsip-apps/src/swig/java/android/src/org/pjsip/pjsua2/app/MainActivity.java delete mode 100644 pjsip-apps/src/swig/java/android/src/org/pjsip/pjsua2/app/MyApp.java (limited to 'pjsip-apps/src') diff --git a/pjsip-apps/src/swig/java/Makefile b/pjsip-apps/src/swig/java/Makefile index 2230670d..3f9bb78c 100644 --- a/pjsip-apps/src/swig/java/Makefile +++ b/pjsip-apps/src/swig/java/Makefile @@ -21,7 +21,7 @@ else LIBPJSUA2_SO=$(OUT_DIR)/libpjsua2.jnilib else ifeq ($(OS),android) - LIBPJSUA2_SO=android/libs/armeabi/libpjsua2.so + LIBPJSUA2_SO=android/app/src/main/jniLibs/armeabi/libpjsua2.so else LIBPJSUA2_SO=$(OUT_DIR)/libpjsua2.so endif @@ -149,12 +149,12 @@ MY_CFLAGS := $(PJ_CXXFLAGS) $(MY_JNI_CFLAGS) $(CFLAGS) MY_LDFLAGS := $(PJ_LDXXFLAGS) $(PJ_LDXXLIBS) $(MY_JNI_LDFLAGS) $(LDFLAGS) MY_PACKAGE_NAME := org.pjsip.pjsua2 ifeq ($(OS),android) - MY_PACKAGE_PATH := android/src/$(subst .,/,$(MY_PACKAGE_NAME)) + MY_PACKAGE_PATH := android/app/src/main/java/$(subst .,/,$(MY_PACKAGE_NAME)) else MY_PACKAGE_PATH := $(OUT_DIR)/$(subst .,/,$(MY_PACKAGE_NAME)) endif -MY_APP_JAVA := android/src/$(subst .,/,$(MY_PACKAGE_NAME))/app/MyApp.java +MY_APP_JAVA := android/app/src/main/java/$(subst .,/,$(MY_PACKAGE_NAME))/app/MyApp.java .PHONY: all java install uninstall diff --git a/pjsip-apps/src/swig/java/android/.classpath b/pjsip-apps/src/swig/java/android/.classpath deleted file mode 100644 index b76ec6cd..00000000 --- a/pjsip-apps/src/swig/java/android/.classpath +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/pjsip-apps/src/swig/java/android/.project b/pjsip-apps/src/swig/java/android/.project deleted file mode 100644 index 434a3408..00000000 --- a/pjsip-apps/src/swig/java/android/.project +++ /dev/null @@ -1,33 +0,0 @@ - - - Pjsua2 - - - - - - com.android.ide.eclipse.adt.ResourceManagerBuilder - - - - - com.android.ide.eclipse.adt.PreCompilerBuilder - - - - - org.eclipse.jdt.core.javabuilder - - - - - com.android.ide.eclipse.adt.ApkBuilder - - - - - - com.android.ide.eclipse.adt.AndroidNature - org.eclipse.jdt.core.javanature - - diff --git a/pjsip-apps/src/swig/java/android/.settings/org.eclipse.jdt.core.prefs b/pjsip-apps/src/swig/java/android/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index 48ab4c6b..00000000 --- a/pjsip-apps/src/swig/java/android/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,4 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 -org.eclipse.jdt.core.compiler.compliance=1.6 -org.eclipse.jdt.core.compiler.source=1.6 diff --git a/pjsip-apps/src/swig/java/android/AndroidManifest.xml b/pjsip-apps/src/swig/java/android/AndroidManifest.xml deleted file mode 100644 index 9a7463a0..00000000 --- a/pjsip-apps/src/swig/java/android/AndroidManifest.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/pjsip-apps/src/swig/java/android/app/build.gradle b/pjsip-apps/src/swig/java/android/app/build.gradle new file mode 100644 index 00000000..2fab9e88 --- /dev/null +++ b/pjsip-apps/src/swig/java/android/app/build.gradle @@ -0,0 +1,23 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 15 + buildToolsVersion "23.0.3" + + defaultConfig { + applicationId "org.pjsip.pjsua2.app" + minSdkVersion 11 + targetSdkVersion 15 + + ndk { + moduleName "libpjsua2" + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + } + } +} diff --git a/pjsip-apps/src/swig/java/android/app/src/main/AndroidManifest.xml b/pjsip-apps/src/swig/java/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..9a7463a0 --- /dev/null +++ b/pjsip-apps/src/swig/java/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pjsip-apps/src/swig/java/android/app/src/main/java/org/pjsip/PjCamera.java b/pjsip-apps/src/swig/java/android/app/src/main/java/org/pjsip/PjCamera.java new file mode 100644 index 00000000..566b0faa --- /dev/null +++ b/pjsip-apps/src/swig/java/android/app/src/main/java/org/pjsip/PjCamera.java @@ -0,0 +1,210 @@ +/* $Id: PjCamera.java 5019 2015-03-23 06:35:45Z nanang $ */ +/* + * Copyright (C) 2015 Teluu Inc. (http://www.teluu.com) + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License 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.pjsip; + +import android.graphics.SurfaceTexture; +import android.hardware.Camera; +import android.util.Log; +import android.view.SurfaceView; +import android.view.SurfaceHolder; + +import java.io.IOException; + +public class PjCamera implements Camera.PreviewCallback, SurfaceHolder.Callback +{ + private final String TAG = "PjCamera"; + + public class Param { + public int width; + public int height; + public int format; + public int fps1000; + } + + private Camera camera = null; + private boolean isRunning = false; + private int camIdx; + private long userData; + + private Param param = null; + + private SurfaceView surfaceView = null; + private SurfaceHolder surfaceHolder = null; + private SurfaceTexture surfaceTexture = null; + + public PjCamera(int idx, int w, int h, int fmt, int fps, + long userData_, SurfaceView surface) + { + camIdx = idx; + userData = userData_; + + param = new Param(); + param.width = w; + param.height = h; + param.format = fmt; + param.fps1000 = fps; + + SetSurfaceView(surface); + } + + public void SetSurfaceView(SurfaceView surface) + { + boolean isCaptureRunning = isRunning; + + if (isCaptureRunning) + Stop(); + + if (surface != null) { + surfaceView = surface; + surfaceHolder = surfaceView.getHolder(); + } else { + // Create dummy texture + surfaceHolder = null; + surfaceView = null; + if (surfaceTexture == null) { + surfaceTexture = new SurfaceTexture(10); + } + } + + if (isCaptureRunning) + Start(); + } + + public int SwitchDevice(int idx) + { + boolean isCaptureRunning = isRunning; + int oldIdx = camIdx; + + if (isCaptureRunning) + Stop(); + + camIdx = idx; + + if (isCaptureRunning) { + int ret = Start(); + if (ret != 0) { + /* Try to revert back */ + camIdx = oldIdx; + Start(); + return ret; + } + } + + return 0; + } + + public int Start() + { + try { + camera = Camera.open(camIdx); + } catch (Exception e) { + Log.d("IOException", e.getMessage()); + return -10; + } + + try { + if (surfaceHolder != null) { + camera.setPreviewDisplay(surfaceHolder); + surfaceHolder.addCallback(this); + } else { + camera.setPreviewTexture(surfaceTexture); + } + } catch (IOException e) { + Log.d("IOException", e.getMessage()); + return -20; + } + + Camera.Parameters cp = camera.getParameters(); + cp.setPreviewSize(param.width, param.height); + cp.setPreviewFormat(param.format); + // Some devices such as Nexus require an exact FPS range from the + // supported FPS ranges, specifying a subset range will raise + // exception. + //cp.setPreviewFpsRange(param.fps1000, param.fps1000); + try { + camera.setParameters(cp); + } catch (RuntimeException e) { + Log.d("RuntimeException", e.getMessage()); + return -30; + } + + camera.setPreviewCallback(this); + camera.startPreview(); + isRunning = true; + + return 0; + } + + public void Stop() + { + isRunning = false; + if (camera == null) + return; + + if (surfaceHolder != null) + surfaceHolder.removeCallback(this); + + camera.setPreviewCallback(null); + camera.stopPreview(); + camera.release(); + camera = null; + } + + native void PushFrame(byte[] data, int length, long userData_); + + public void onPreviewFrame(byte[] data, Camera camera) + { + if (isRunning) { + PushFrame(data, data.length, userData); + } + } + + public void surfaceChanged(SurfaceHolder holder, + int format, int width, int height) + { + Log.d(TAG, "VideoCaptureAndroid::surfaceChanged"); + } + + public void surfaceCreated(SurfaceHolder holder) + { + Log.d(TAG, "VideoCaptureAndroid::surfaceCreated"); + try { + if(camera != null) { + camera.setPreviewDisplay(holder); + } + } catch (IOException e) { + Log.e(TAG, "Failed to set preview surface!", e); + } + } + + public void surfaceDestroyed(SurfaceHolder holder) + { + Log.d(TAG, "VideoCaptureAndroid::surfaceDestroyed"); + try { + if(camera != null) { + camera.setPreviewDisplay(null); + } + } catch (IOException e) { + Log.e(TAG, "Failed to clear preview surface!", e); + } catch (RuntimeException e) { + Log.w(TAG, "Clear preview surface useless", e); + } + } + +} diff --git a/pjsip-apps/src/swig/java/android/app/src/main/java/org/pjsip/PjCameraInfo.java b/pjsip-apps/src/swig/java/android/app/src/main/java/org/pjsip/PjCameraInfo.java new file mode 100644 index 00000000..d44cdb95 --- /dev/null +++ b/pjsip-apps/src/swig/java/android/app/src/main/java/org/pjsip/PjCameraInfo.java @@ -0,0 +1,110 @@ +/* $Id: PjCameraInfo.java 5019 2015-03-23 06:35:45Z nanang $ */ +/* + * Copyright (C) 2015 Teluu Inc. (http://www.teluu.com) + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License 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.pjsip; + +import java.util.List; + +import android.hardware.Camera; +import android.util.Log; + +public class PjCameraInfo { + public int facing; + public int orient; + public int[] supportedSize; // [w1, h1, w2, h2, ...] + public int[] supportedFps1000; // [min1, max1, min2, max2, ...] + public int[] supportedFormat; // [fmt1, fmt2, ...] + + // convert Format list {fmt1, fmt2, ...} to [fmt1, fmt2, ...] + private static int[] IntegerListToIntArray(List list) + { + int[] li = new int[list.size()]; + int i = 0; + for (Integer e : list) { + li[i++] = e.intValue(); + } + return li; + } + + // convert Fps list {[min1, max1], [min2, max2], ...} to + // [min1, max1, min2, max2, ...] + private static int[] IntArrayListToIntArray(List list) + { + int[] li = new int[list.size() * 2]; + int i = 0; + for (int[] e : list) { + li[i++] = e[0]; + li[i++] = e[1]; + } + return li; + } + + // convert Size list {{w1, h1}, {w2, h2}, ...} to [w1, h1, w2, h2, ...] + private static int[] CameraSizeListToIntArray(List list) + { + int[] li = new int[list.size() * 2]; + int i = 0; + for (Camera.Size e : list) { + li[i++] = e.width; + li[i++] = e.height; + } + return li; + } + + public static int GetCameraCount() + { + return Camera.getNumberOfCameras(); + } + + // Get camera info: facing, orientation, supported size/fps/format. + // Camera must not be opened. + public static PjCameraInfo GetCameraInfo(int idx) + { + if (idx < 0 || idx >= GetCameraCount()) + return null; + + Camera cam; + try { + cam = Camera.open(idx); + } catch (Exception e) { + Log.d("IOException", e.getMessage()); + return null; + } + + PjCameraInfo pjci = new PjCameraInfo(); + + Camera.CameraInfo ci = new Camera.CameraInfo(); + Camera.getCameraInfo(idx, ci); + + pjci.facing = ci.facing; + pjci.orient = ci.orientation; + + Camera.Parameters param = cam.getParameters(); + cam.release(); + cam = null; + + pjci.supportedFormat = IntegerListToIntArray( + param.getSupportedPreviewFormats()); + pjci.supportedFps1000 = IntArrayListToIntArray( + param.getSupportedPreviewFpsRange()); + pjci.supportedSize = CameraSizeListToIntArray( + param.getSupportedPreviewSizes()); + + return pjci; + } +} diff --git a/pjsip-apps/src/swig/java/android/app/src/main/java/org/pjsip/pjsua2/app/CallActivity.java b/pjsip-apps/src/swig/java/android/app/src/main/java/org/pjsip/pjsua2/app/CallActivity.java new file mode 100644 index 00000000..8700cde3 --- /dev/null +++ b/pjsip-apps/src/swig/java/android/app/src/main/java/org/pjsip/pjsua2/app/CallActivity.java @@ -0,0 +1,364 @@ +/* $Id: CallActivity.java 5138 2015-07-30 06:23:35Z ming $ */ +/* + * Copyright (C) 2013 Teluu Inc. (http://www.teluu.com) + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License 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.pjsip.pjsua2.app; + +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.view.Display; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.TextView; +import android.app.Activity; +import android.content.Context; +import android.content.res.Configuration; + +import org.pjsip.pjsua2.*; + +class VideoPreviewHandler implements SurfaceHolder.Callback +{ + public boolean videoPreviewActive = false; + + public void updateVideoPreview(SurfaceHolder holder) + { + if (MainActivity.currentCall != null && + MainActivity.currentCall.vidWin != null && + MainActivity.currentCall.vidPrev != null) + { + if (videoPreviewActive) { + VideoWindowHandle vidWH = new VideoWindowHandle(); + vidWH.getHandle().setWindow(holder.getSurface()); + VideoPreviewOpParam vidPrevParam = new VideoPreviewOpParam(); + vidPrevParam.setWindow(vidWH); + try { + MainActivity.currentCall.vidPrev.start(vidPrevParam); + } catch (Exception e) { + System.out.println(e); + } + } else { + try { + MainActivity.currentCall.vidPrev.stop(); + } catch (Exception e) { + System.out.println(e); + } + } + } + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) + { + updateVideoPreview(holder); + } + + @Override + public void surfaceCreated(SurfaceHolder holder) + { + + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) + { + try { + MainActivity.currentCall.vidPrev.stop(); + } catch (Exception e) { + System.out.println(e); + } + } +} + +public class CallActivity extends Activity + implements Handler.Callback, SurfaceHolder.Callback +{ + + public static Handler handler_; + private static VideoPreviewHandler previewHandler = + new VideoPreviewHandler(); + + private final Handler handler = new Handler(this); + private static CallInfo lastCallInfo; + + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_call); + + SurfaceView surfaceInVideo = (SurfaceView) + findViewById(R.id.surfaceIncomingVideo); + SurfaceView surfacePreview = (SurfaceView) + findViewById(R.id.surfacePreviewCapture); + Button buttonShowPreview = (Button) + findViewById(R.id.buttonShowPreview); + + if (MainActivity.currentCall == null || + MainActivity.currentCall.vidWin == null) + { + surfaceInVideo.setVisibility(View.GONE); + buttonShowPreview.setVisibility(View.GONE); + } + setupVideoPreview(surfacePreview, buttonShowPreview); + surfaceInVideo.getHolder().addCallback(this); + surfacePreview.getHolder().addCallback(previewHandler); + + handler_ = handler; + if (MainActivity.currentCall != null) { + try { + lastCallInfo = MainActivity.currentCall.getInfo(); + updateCallState(lastCallInfo); + } catch (Exception e) { + System.out.println(e); + } + } else { + updateCallState(lastCallInfo); + } + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + WindowManager wm; + Display display; + int rotation; + pjmedia_orient orient; + + wm = (WindowManager)this.getSystemService(Context.WINDOW_SERVICE); + display = wm.getDefaultDisplay(); + rotation = display.getRotation(); + System.out.println("Device orientation changed: " + rotation); + + switch (rotation) { + case Surface.ROTATION_0: // Portrait + orient = pjmedia_orient.PJMEDIA_ORIENT_ROTATE_270DEG; + break; + case Surface.ROTATION_90: // Landscape, home button on the right + orient = pjmedia_orient.PJMEDIA_ORIENT_NATURAL; + break; + case Surface.ROTATION_180: + orient = pjmedia_orient.PJMEDIA_ORIENT_ROTATE_90DEG; + break; + case Surface.ROTATION_270: // Landscape, home button on the left + orient = pjmedia_orient.PJMEDIA_ORIENT_ROTATE_180DEG; + break; + default: + orient = pjmedia_orient.PJMEDIA_ORIENT_UNKNOWN; + } + + if (MyApp.ep != null && MainActivity.account != null) { + try { + AccountConfig cfg = MainActivity.account.cfg; + int cap_dev = cfg.getVideoConfig().getDefaultCaptureDevice(); + MyApp.ep.vidDevManager().setCaptureOrient(cap_dev, orient, + true); + } catch (Exception e) { + System.out.println(e); + } + } + } + + @Override + protected void onDestroy() + { + super.onDestroy(); + handler_ = null; + } + + private void updateVideoWindow(boolean show) + { + if (MainActivity.currentCall != null && + MainActivity.currentCall.vidWin != null && + MainActivity.currentCall.vidPrev != null) + { + SurfaceView surfaceInVideo = (SurfaceView) + findViewById(R.id.surfaceIncomingVideo); + + VideoWindowHandle vidWH = new VideoWindowHandle(); + if (show) { + vidWH.getHandle().setWindow( + surfaceInVideo.getHolder().getSurface()); + } else { + vidWH.getHandle().setWindow(null); + } + try { + MainActivity.currentCall.vidWin.setWindow(vidWH); + } catch (Exception e) { + System.out.println(e); + } + } + } + + public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) + { + updateVideoWindow(true); + } + + public void surfaceCreated(SurfaceHolder holder) + { + } + + public void surfaceDestroyed(SurfaceHolder holder) + { + updateVideoWindow(false); + } + + public void acceptCall(View view) + { + CallOpParam prm = new CallOpParam(); + prm.setStatusCode(pjsip_status_code.PJSIP_SC_OK); + try { + MainActivity.currentCall.answer(prm); + } catch (Exception e) { + System.out.println(e); + } + + view.setVisibility(View.GONE); + } + + public void hangupCall(View view) + { + handler_ = null; + finish(); + + if (MainActivity.currentCall != null) { + CallOpParam prm = new CallOpParam(); + prm.setStatusCode(pjsip_status_code.PJSIP_SC_DECLINE); + try { + MainActivity.currentCall.hangup(prm); + } catch (Exception e) { + System.out.println(e); + } + } + } + + public void setupVideoPreview(SurfaceView surfacePreview, + Button buttonShowPreview) + { + surfacePreview.setVisibility(previewHandler.videoPreviewActive? + View.VISIBLE:View.GONE); + + buttonShowPreview.setText(previewHandler.videoPreviewActive? + getString(R.string.hide_preview): + getString(R.string.show_preview)); + } + + public void showPreview(View view) + { + SurfaceView surfacePreview = (SurfaceView) + findViewById(R.id.surfacePreviewCapture); + + Button buttonShowPreview = (Button) + findViewById(R.id.buttonShowPreview); + + + previewHandler.videoPreviewActive = !previewHandler.videoPreviewActive; + + setupVideoPreview(surfacePreview, buttonShowPreview); + + previewHandler.updateVideoPreview(surfacePreview.getHolder()); + } + + private void setupVideoSurface() + { + SurfaceView surfaceInVideo = (SurfaceView) + findViewById(R.id.surfaceIncomingVideo); + SurfaceView surfacePreview = (SurfaceView) + findViewById(R.id.surfacePreviewCapture); + Button buttonShowPreview = (Button) + findViewById(R.id.buttonShowPreview); + surfaceInVideo.setVisibility(View.VISIBLE); + buttonShowPreview.setVisibility(View.VISIBLE); + surfacePreview.setVisibility(View.GONE); + } + + @Override + public boolean handleMessage(Message m) + { + if (m.what == MainActivity.MSG_TYPE.CALL_STATE) { + + lastCallInfo = (CallInfo) m.obj; + updateCallState(lastCallInfo); + + } else if (m.what == MainActivity.MSG_TYPE.CALL_MEDIA_STATE) { + + if (MainActivity.currentCall.vidWin != null) { + /* Set capture orientation according to current + * device orientation. + */ + onConfigurationChanged(getResources().getConfiguration()); + /* If there's incoming video, display it. */ + setupVideoSurface(); + } + + } else { + + /* Message not handled */ + return false; + + } + + return true; + } + + private void updateCallState(CallInfo ci) { + TextView tvPeer = (TextView) findViewById(R.id.textViewPeer); + TextView tvState = (TextView) findViewById(R.id.textViewCallState); + Button buttonHangup = (Button) findViewById(R.id.buttonHangup); + Button buttonAccept = (Button) findViewById(R.id.buttonAccept); + String call_state = ""; + + if (ci.getRole() == pjsip_role_e.PJSIP_ROLE_UAC) { + buttonAccept.setVisibility(View.GONE); + } + + if (ci.getState().swigValue() < + pjsip_inv_state.PJSIP_INV_STATE_CONFIRMED.swigValue()) + { + if (ci.getRole() == pjsip_role_e.PJSIP_ROLE_UAS) { + call_state = "Incoming call.."; + /* Default button texts are already 'Accept' & 'Reject' */ + } else { + buttonHangup.setText("Cancel"); + call_state = ci.getStateText(); + } + } + else if (ci.getState().swigValue() >= + pjsip_inv_state.PJSIP_INV_STATE_CONFIRMED.swigValue()) + { + buttonAccept.setVisibility(View.GONE); + call_state = ci.getStateText(); + if (ci.getState() == pjsip_inv_state.PJSIP_INV_STATE_CONFIRMED) { + buttonHangup.setText("Hangup"); + } else if (ci.getState() == + pjsip_inv_state.PJSIP_INV_STATE_DISCONNECTED) + { + buttonHangup.setText("OK"); + call_state = "Call disconnected: " + ci.getLastReason(); + } + } + + tvPeer.setText(ci.getRemoteUri()); + tvState.setText(call_state); + } +} diff --git a/pjsip-apps/src/swig/java/android/app/src/main/java/org/pjsip/pjsua2/app/MainActivity.java b/pjsip-apps/src/swig/java/android/app/src/main/java/org/pjsip/pjsua2/app/MainActivity.java new file mode 100644 index 00000000..cd5c74e9 --- /dev/null +++ b/pjsip-apps/src/swig/java/android/app/src/main/java/org/pjsip/pjsua2/app/MainActivity.java @@ -0,0 +1,581 @@ +/* $Id: MainActivity.java 5022 2015-03-25 03:41:21Z nanang $ */ +/* + * Copyright (C) 2013 Teluu Inc. (http://www.teluu.com) + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License 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.pjsip.pjsua2.app; + +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.SimpleAdapter; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import org.pjsip.pjsua2.*; + +public class MainActivity extends Activity + implements Handler.Callback, MyAppObserver +{ + public static MyApp app = null; + public static MyCall currentCall = null; + public static MyAccount account = null; + public static AccountConfig accCfg = null; + + private ListView buddyListView; + private SimpleAdapter buddyListAdapter; + private int buddyListSelectedIdx = -1; + ArrayList> buddyList; + private String lastRegStatus = ""; + + private final Handler handler = new Handler(this); + public class MSG_TYPE + { + public final static int INCOMING_CALL = 1; + public final static int CALL_STATE = 2; + public final static int REG_STATE = 3; + public final static int BUDDY_STATE = 4; + public final static int CALL_MEDIA_STATE = 5; + } + + private HashMap putData(String uri, String status) + { + HashMap item = new HashMap(); + item.put("uri", uri); + item.put("status", status); + return item; + } + + private void showCallActivity() + { + Intent intent = new Intent(this, CallActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); + } + + @Override + protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + if (app == null) { + app = new MyApp(); + // Wait for GDB to init, for native debugging only + if (false && + (getApplicationInfo().flags & + ApplicationInfo.FLAG_DEBUGGABLE) != 0) + { + try { + Thread.sleep(5000); + } catch (InterruptedException e) {} + } + + app.init(this, getFilesDir().getAbsolutePath()); + } + + if (app.accList.size() == 0) { + accCfg = new AccountConfig(); + accCfg.setIdUri("sip:localhost"); + accCfg.getNatConfig().setIceEnabled(true); + accCfg.getVideoConfig().setAutoTransmitOutgoing(true); + accCfg.getVideoConfig().setAutoShowIncoming(true); + account = app.addAcc(accCfg); + } else { + account = app.accList.get(0); + accCfg = account.cfg; + } + + buddyList = new ArrayList>(); + for (int i = 0; i < account.buddyList.size(); i++) { + buddyList.add(putData(account.buddyList.get(i).cfg.getUri(), + account.buddyList.get(i).getStatusText())); + } + + String[] from = { "uri", "status" }; + int[] to = { android.R.id.text1, android.R.id.text2 }; + buddyListAdapter = new SimpleAdapter( + this, buddyList, + android.R.layout.simple_list_item_2, + from, to); + + buddyListView = (ListView) findViewById(R.id.listViewBuddy);; + buddyListView.setAdapter(buddyListAdapter); + buddyListView.setOnItemClickListener( + new AdapterView.OnItemClickListener() + { + @Override + public void onItemClick(AdapterView parent, + final View view, + int position, long id) + { + view.setSelected(true); + buddyListSelectedIdx = position; + } + } + ); + + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) + { + // Inflate the menu; this adds items to the action bar + // if it is present. + getMenuInflater().inflate(R.menu.main, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) + { + switch (item.getItemId()) { + case R.id.action_acc_config: + dlgAccountSetting(); + break; + + case R.id.action_quit: + Message m = Message.obtain(handler, 0); + m.sendToTarget(); + break; + + default: + break; + } + + return true; + } + + @Override + public boolean handleMessage(Message m) + { + if (m.what == 0) { + + app.deinit(); + finish(); + Runtime.getRuntime().gc(); + android.os.Process.killProcess(android.os.Process.myPid()); + + } else if (m.what == MSG_TYPE.CALL_STATE) { + + CallInfo ci = (CallInfo) m.obj; + + /* Forward the message to CallActivity */ + if (CallActivity.handler_ != null) { + Message m2 = Message.obtain(CallActivity.handler_, + MSG_TYPE.CALL_STATE, ci); + m2.sendToTarget(); + } + + } else if (m.what == MSG_TYPE.CALL_MEDIA_STATE) { + + /* Forward the message to CallActivity */ + if (CallActivity.handler_ != null) { + Message m2 = Message.obtain(CallActivity.handler_, + MSG_TYPE.CALL_MEDIA_STATE, + null); + m2.sendToTarget(); + } + + } else if (m.what == MSG_TYPE.BUDDY_STATE) { + + MyBuddy buddy = (MyBuddy) m.obj; + int idx = account.buddyList.indexOf(buddy); + + /* Update buddy status text, if buddy is valid and + * the buddy lists in account and UI are sync-ed. + */ + if (idx >= 0 && account.buddyList.size() == buddyList.size()) + { + buddyList.get(idx).put("status", buddy.getStatusText()); + buddyListAdapter.notifyDataSetChanged(); + // TODO: selection color/mark is gone after this, + // dont know how to return it back. + //buddyListView.setSelection(buddyListSelectedIdx); + //buddyListView.performItemClick(buddyListView, + // buddyListSelectedIdx, + // buddyListView. + // getItemIdAtPosition(buddyListSelectedIdx)); + + /* Return back Call activity */ + notifyCallState(currentCall); + } + + } else if (m.what == MSG_TYPE.REG_STATE) { + + String msg_str = (String) m.obj; + lastRegStatus = msg_str; + + } else if (m.what == MSG_TYPE.INCOMING_CALL) { + + /* Incoming call */ + final MyCall call = (MyCall) m.obj; + CallOpParam prm = new CallOpParam(); + + /* Only one call at anytime */ + if (currentCall != null) { + /* + prm.setStatusCode(pjsip_status_code.PJSIP_SC_BUSY_HERE); + try { + call.hangup(prm); + } catch (Exception e) {} + */ + // TODO: set status code + call.delete(); + return true; + } + + /* Answer with ringing */ + prm.setStatusCode(pjsip_status_code.PJSIP_SC_RINGING); + try { + call.answer(prm); + } catch (Exception e) {} + + currentCall = call; + showCallActivity(); + + } else { + + /* Message not handled */ + return false; + + } + + return true; + } + + + private void dlgAccountSetting() + { + LayoutInflater li = LayoutInflater.from(this); + View view = li.inflate(R.layout.dlg_account_config, null); + + if (lastRegStatus.length()!=0) { + TextView tvInfo = (TextView)view.findViewById(R.id.textViewInfo); + tvInfo.setText("Last status: " + lastRegStatus); + } + + AlertDialog.Builder adb = new AlertDialog.Builder(this); + adb.setView(view); + adb.setTitle("Account Settings"); + + final EditText etId = (EditText)view.findViewById(R.id.editTextId); + final EditText etReg = (EditText)view.findViewById(R.id.editTextRegistrar); + final EditText etProxy = (EditText)view.findViewById(R.id.editTextProxy); + final EditText etUser = (EditText)view.findViewById(R.id.editTextUsername); + final EditText etPass = (EditText)view.findViewById(R.id.editTextPassword); + + etId. setText(accCfg.getIdUri()); + etReg. setText(accCfg.getRegConfig().getRegistrarUri()); + StringVector proxies = accCfg.getSipConfig().getProxies(); + if (proxies.size() > 0) + etProxy.setText(proxies.get(0)); + else + etProxy.setText(""); + AuthCredInfoVector creds = accCfg.getSipConfig().getAuthCreds(); + if (creds.size() > 0) { + etUser. setText(creds.get(0).getUsername()); + etPass. setText(creds.get(0).getData()); + } else { + etUser. setText(""); + etPass. setText(""); + } + + adb.setCancelable(false); + adb.setPositiveButton("OK", + new DialogInterface.OnClickListener() + { + public void onClick(DialogInterface dialog,int id) + { + String acc_id = etId.getText().toString(); + String registrar = etReg.getText().toString(); + String proxy = etProxy.getText().toString(); + String username = etUser.getText().toString(); + String password = etPass.getText().toString(); + + accCfg.setIdUri(acc_id); + accCfg.getRegConfig().setRegistrarUri(registrar); + AuthCredInfoVector creds = accCfg.getSipConfig(). + getAuthCreds(); + creds.clear(); + if (username.length() != 0) { + creds.add(new AuthCredInfo("Digest", "*", username, 0, + password)); + } + StringVector proxies = accCfg.getSipConfig().getProxies(); + proxies.clear(); + if (proxy.length() != 0) { + proxies.add(proxy); + } + + /* Enable ICE */ + accCfg.getNatConfig().setIceEnabled(true); + + /* Finally */ + lastRegStatus = ""; + try { + account.modify(accCfg); + } catch (Exception e) {} + } + } + ); + adb.setNegativeButton("Cancel", + new DialogInterface.OnClickListener() + { + public void onClick(DialogInterface dialog,int id) + { + dialog.cancel(); + } + } + ); + + AlertDialog ad = adb.create(); + ad.show(); + } + + + public void makeCall(View view) + { + if (buddyListSelectedIdx == -1) + return; + + /* Only one call at anytime */ + if (currentCall != null) { + return; + } + + HashMap item = (HashMap) buddyListView. + getItemAtPosition(buddyListSelectedIdx); + String buddy_uri = item.get("uri"); + + MyCall call = new MyCall(account, -1); + CallOpParam prm = new CallOpParam(true); + + try { + call.makeCall(buddy_uri, prm); + } catch (Exception e) { + call.delete(); + return; + } + + currentCall = call; + showCallActivity(); + } + + private void dlgAddEditBuddy(BuddyConfig initial) + { + final BuddyConfig cfg = new BuddyConfig(); + final BuddyConfig old_cfg = initial; + final boolean is_add = initial == null; + + LayoutInflater li = LayoutInflater.from(this); + View view = li.inflate(R.layout.dlg_add_buddy, null); + + AlertDialog.Builder adb = new AlertDialog.Builder(this); + adb.setView(view); + + final EditText etUri = (EditText)view.findViewById(R.id.editTextUri); + final CheckBox cbSubs = (CheckBox)view.findViewById(R.id.checkBoxSubscribe); + + if (is_add) { + adb.setTitle("Add Buddy"); + } else { + adb.setTitle("Edit Buddy"); + etUri. setText(initial.getUri()); + cbSubs.setChecked(initial.getSubscribe()); + } + + adb.setCancelable(false); + adb.setPositiveButton("OK", + new DialogInterface.OnClickListener() + { + public void onClick(DialogInterface dialog,int id) + { + cfg.setUri(etUri.getText().toString()); + cfg.setSubscribe(cbSubs.isChecked()); + + if (is_add) { + account.addBuddy(cfg); + buddyList.add(putData(cfg.getUri(), "")); + buddyListAdapter.notifyDataSetChanged(); + buddyListSelectedIdx = -1; + } else { + if (!old_cfg.getUri().equals(cfg.getUri())) { + account.delBuddy(buddyListSelectedIdx); + account.addBuddy(cfg); + buddyList.remove(buddyListSelectedIdx); + buddyList.add(putData(cfg.getUri(), "")); + buddyListAdapter.notifyDataSetChanged(); + buddyListSelectedIdx = -1; + } else if (old_cfg.getSubscribe() != + cfg.getSubscribe()) + { + MyBuddy bud = account.buddyList.get( + buddyListSelectedIdx); + try { + bud.subscribePresence(cfg.getSubscribe()); + } catch (Exception e) {} + } + } + } + } + ); + adb.setNegativeButton("Cancel", + new DialogInterface.OnClickListener() + { + public void onClick(DialogInterface dialog,int id) { + dialog.cancel(); + } + } + ); + + AlertDialog ad = adb.create(); + ad.show(); + } + + public void addBuddy(View view) + { + dlgAddEditBuddy(null); + } + + public void editBuddy(View view) + { + if (buddyListSelectedIdx == -1) + return; + + BuddyConfig old_cfg = account.buddyList.get(buddyListSelectedIdx).cfg; + dlgAddEditBuddy(old_cfg); + } + + public void delBuddy(View view) { + if (buddyListSelectedIdx == -1) + return; + + final HashMap item = (HashMap) + buddyListView.getItemAtPosition(buddyListSelectedIdx); + String buddy_uri = item.get("uri"); + + DialogInterface.OnClickListener ocl = + new DialogInterface.OnClickListener() + { + @Override + public void onClick(DialogInterface dialog, int which) + { + switch (which) { + case DialogInterface.BUTTON_POSITIVE: + account.delBuddy(buddyListSelectedIdx); + buddyList.remove(item); + buddyListAdapter.notifyDataSetChanged(); + buddyListSelectedIdx = -1; + break; + case DialogInterface.BUTTON_NEGATIVE: + break; + } + } + }; + + AlertDialog.Builder adb = new AlertDialog.Builder(this); + adb.setTitle(buddy_uri); + adb.setMessage("\nDelete this buddy?\n"); + adb.setPositiveButton("Yes", ocl); + adb.setNegativeButton("No", ocl); + adb.show(); + } + + + /* + * === MyAppObserver === + * + * As we cannot do UI from worker thread, the callbacks mostly just send + * a message to UI/main thread. + */ + + public void notifyIncomingCall(MyCall call) + { + Message m = Message.obtain(handler, MSG_TYPE.INCOMING_CALL, call); + m.sendToTarget(); + } + + public void notifyRegState(pjsip_status_code code, String reason, + int expiration) + { + String msg_str = ""; + if (expiration == 0) + msg_str += "Unregistration"; + else + msg_str += "Registration"; + + if (code.swigValue()/100 == 2) + msg_str += " successful"; + else + msg_str += " failed: " + reason; + + Message m = Message.obtain(handler, MSG_TYPE.REG_STATE, msg_str); + m.sendToTarget(); + } + + public void notifyCallState(MyCall call) + { + if (currentCall == null || call.getId() != currentCall.getId()) + return; + + CallInfo ci; + try { + ci = call.getInfo(); + } catch (Exception e) { + ci = null; + } + Message m = Message.obtain(handler, MSG_TYPE.CALL_STATE, ci); + m.sendToTarget(); + + if (ci != null && + ci.getState() == pjsip_inv_state.PJSIP_INV_STATE_DISCONNECTED) + { + currentCall = null; + } + } + + public void notifyCallMediaState(MyCall call) + { + Message m = Message.obtain(handler, MSG_TYPE.CALL_MEDIA_STATE, null); + m.sendToTarget(); + } + + public void notifyBuddyState(MyBuddy buddy) + { + Message m = Message.obtain(handler, MSG_TYPE.BUDDY_STATE, buddy); + m.sendToTarget(); + } + + /* === end of MyAppObserver ==== */ + +} diff --git a/pjsip-apps/src/swig/java/android/app/src/main/java/org/pjsip/pjsua2/app/MyApp.java b/pjsip-apps/src/swig/java/android/app/src/main/java/org/pjsip/pjsua2/app/MyApp.java new file mode 100644 index 00000000..b8c50671 --- /dev/null +++ b/pjsip-apps/src/swig/java/android/app/src/main/java/org/pjsip/pjsua2/app/MyApp.java @@ -0,0 +1,540 @@ +/* $Id: MyApp.java 5361 2016-06-28 14:32:08Z nanang $ */ +/* + * Copyright (C) 2013 Teluu Inc. (http://www.teluu.com) + * + * This program 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 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License 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.pjsip.pjsua2.app; + +import java.io.File; +import java.util.ArrayList; + +import org.pjsip.pjsua2.*; + + +/* Interface to separate UI & engine a bit better */ +interface MyAppObserver +{ + abstract void notifyRegState(pjsip_status_code code, String reason, + int expiration); + abstract void notifyIncomingCall(MyCall call); + abstract void notifyCallState(MyCall call); + abstract void notifyCallMediaState(MyCall call); + abstract void notifyBuddyState(MyBuddy buddy); +} + + +class MyLogWriter extends LogWriter +{ + @Override + public void write(LogEntry entry) + { + System.out.println(entry.getMsg()); + } +} + + +class MyCall extends Call +{ + public VideoWindow vidWin; + public VideoPreview vidPrev; + + MyCall(MyAccount acc, int call_id) + { + super(acc, call_id); + vidWin = null; + } + + @Override + public void onCallState(OnCallStateParam prm) + { + MyApp.observer.notifyCallState(this); + try { + CallInfo ci = getInfo(); + if (ci.getState() == + pjsip_inv_state.PJSIP_INV_STATE_DISCONNECTED) + { + this.delete(); + } + } catch (Exception e) { + return; + } + } + + @Override + public void onCallMediaState(OnCallMediaStateParam prm) + { + CallInfo ci; + try { + ci = getInfo(); + } catch (Exception e) { + return; + } + + CallMediaInfoVector cmiv = ci.getMedia(); + + for (int i = 0; i < cmiv.size(); i++) { + CallMediaInfo cmi = cmiv.get(i); + if (cmi.getType() == pjmedia_type.PJMEDIA_TYPE_AUDIO && + (cmi.getStatus() == + pjsua_call_media_status.PJSUA_CALL_MEDIA_ACTIVE || + cmi.getStatus() == + pjsua_call_media_status.PJSUA_CALL_MEDIA_REMOTE_HOLD)) + { + // unfortunately, on Java too, the returned Media cannot be + // downcasted to AudioMedia + Media m = getMedia(i); + AudioMedia am = AudioMedia.typecastFromMedia(m); + + // connect ports + try { + MyApp.ep.audDevManager().getCaptureDevMedia(). + startTransmit(am); + am.startTransmit(MyApp.ep.audDevManager(). + getPlaybackDevMedia()); + } catch (Exception e) { + continue; + } + } else if (cmi.getType() == pjmedia_type.PJMEDIA_TYPE_VIDEO && + cmi.getStatus() == + pjsua_call_media_status.PJSUA_CALL_MEDIA_ACTIVE && + cmi.getVideoIncomingWindowId() != pjsua2.INVALID_ID) + { + vidWin = new VideoWindow(cmi.getVideoIncomingWindowId()); + vidPrev = new VideoPreview(cmi.getVideoCapDev()); + } + } + + MyApp.observer.notifyCallMediaState(this); + } +} + + +class MyAccount extends Account +{ + public ArrayList buddyList = new ArrayList(); + public AccountConfig cfg; + + MyAccount(AccountConfig config) + { + super(); + cfg = config; + } + + public MyBuddy addBuddy(BuddyConfig bud_cfg) + { + /* Create Buddy */ + MyBuddy bud = new MyBuddy(bud_cfg); + try { + bud.create(this, bud_cfg); + } catch (Exception e) { + bud.delete(); + bud = null; + } + + if (bud != null) { + buddyList.add(bud); + if (bud_cfg.getSubscribe()) + try { + bud.subscribePresence(true); + } catch (Exception e) {} + } + + return bud; + } + + public void delBuddy(MyBuddy buddy) + { + buddyList.remove(buddy); + buddy.delete(); + } + + public void delBuddy(int index) + { + MyBuddy bud = buddyList.get(index); + buddyList.remove(index); + bud.delete(); + } + + @Override + public void onRegState(OnRegStateParam prm) + { + MyApp.observer.notifyRegState(prm.getCode(), prm.getReason(), + prm.getExpiration()); + } + + @Override + public void onIncomingCall(OnIncomingCallParam prm) + { + System.out.println("======== Incoming call ======== "); + MyCall call = new MyCall(this, prm.getCallId()); + MyApp.observer.notifyIncomingCall(call); + } + + @Override + public void onInstantMessage(OnInstantMessageParam prm) + { + System.out.println("======== Incoming pager ======== "); + System.out.println("From : " + prm.getFromUri()); + System.out.println("To : " + prm.getToUri()); + System.out.println("Contact : " + prm.getContactUri()); + System.out.println("Mimetype : " + prm.getContentType()); + System.out.println("Body : " + prm.getMsgBody()); + } +} + + +class MyBuddy extends Buddy +{ + public BuddyConfig cfg; + + MyBuddy(BuddyConfig config) + { + super(); + cfg = config; + } + + String getStatusText() + { + BuddyInfo bi; + + try { + bi = getInfo(); + } catch (Exception e) { + return "?"; + } + + String status = ""; + if (bi.getSubState() == pjsip_evsub_state.PJSIP_EVSUB_STATE_ACTIVE) { + if (bi.getPresStatus().getStatus() == + pjsua_buddy_status.PJSUA_BUDDY_STATUS_ONLINE) + { + status = bi.getPresStatus().getStatusText(); + if (status == null || status.length()==0) { + status = "Online"; + } + } else if (bi.getPresStatus().getStatus() == + pjsua_buddy_status.PJSUA_BUDDY_STATUS_OFFLINE) + { + status = "Offline"; + } else { + status = "Unknown"; + } + } + return status; + } + + @Override + public void onBuddyState() + { + MyApp.observer.notifyBuddyState(this); + } + +} + + +class MyAccountConfig +{ + public AccountConfig accCfg = new AccountConfig(); + public ArrayList buddyCfgs = new ArrayList(); + + public void readObject(ContainerNode node) + { + try { + ContainerNode acc_node = node.readContainer("Account"); + accCfg.readObject(acc_node); + ContainerNode buddies_node = acc_node.readArray("buddies"); + buddyCfgs.clear(); + while (buddies_node.hasUnread()) { + BuddyConfig bud_cfg = new BuddyConfig(); + bud_cfg.readObject(buddies_node); + buddyCfgs.add(bud_cfg); + } + } catch (Exception e) {} + } + + public void writeObject(ContainerNode node) + { + try { + ContainerNode acc_node = node.writeNewContainer("Account"); + accCfg.writeObject(acc_node); + ContainerNode buddies_node = acc_node.writeNewArray("buddies"); + for (int j = 0; j < buddyCfgs.size(); j++) { + buddyCfgs.get(j).writeObject(buddies_node); + } + } catch (Exception e) {} + } +} + + +class MyApp { + static { + try{ + System.loadLibrary("openh264"); + // Ticket #1937: libyuv is now included as static lib + //System.loadLibrary("yuv"); + } catch (UnsatisfiedLinkError e) { + System.out.println("UnsatisfiedLinkError: " + e.getMessage()); + System.out.println("This could be safely ignored if you " + + "don't need video."); + } + System.loadLibrary("pjsua2"); + System.out.println("Library loaded"); + } + + public static Endpoint ep = new Endpoint(); + public static MyAppObserver observer; + public ArrayList accList = new ArrayList(); + + private ArrayList accCfgs = + new ArrayList(); + private EpConfig epConfig = new EpConfig(); + private TransportConfig sipTpConfig = new TransportConfig(); + private String appDir; + + /* Maintain reference to log writer to avoid premature cleanup by GC */ + private MyLogWriter logWriter; + + private final String configName = "pjsua2.json"; + private final int SIP_PORT = 6000; + private final int LOG_LEVEL = 4; + + public void init(MyAppObserver obs, String app_dir) + { + init(obs, app_dir, false); + } + + public void init(MyAppObserver obs, String app_dir, + boolean own_worker_thread) + { + observer = obs; + appDir = app_dir; + + /* Create endpoint */ + try { + ep.libCreate(); + } catch (Exception e) { + return; + } + + + /* Load config */ + String configPath = appDir + "/" + configName; + File f = new File(configPath); + if (f.exists()) { + loadConfig(configPath); + } else { + /* Set 'default' values */ + sipTpConfig.setPort(SIP_PORT); + } + + /* Override log level setting */ + epConfig.getLogConfig().setLevel(LOG_LEVEL); + epConfig.getLogConfig().setConsoleLevel(LOG_LEVEL); + + /* Set log config. */ + LogConfig log_cfg = epConfig.getLogConfig(); + logWriter = new MyLogWriter(); + log_cfg.setWriter(logWriter); + log_cfg.setDecor(log_cfg.getDecor() & + ~(pj_log_decoration.PJ_LOG_HAS_CR.swigValue() | + pj_log_decoration.PJ_LOG_HAS_NEWLINE.swigValue())); + + /* Set ua config. */ + UaConfig ua_cfg = epConfig.getUaConfig(); + ua_cfg.setUserAgent("Pjsua2 Android " + ep.libVersion().getFull()); + StringVector stun_servers = new StringVector(); + stun_servers.add("stun.pjsip.org"); + ua_cfg.setStunServer(stun_servers); + if (own_worker_thread) { + ua_cfg.setThreadCnt(0); + ua_cfg.setMainThreadOnly(true); + } + + /* Init endpoint */ + try { + ep.libInit(epConfig); + } catch (Exception e) { + return; + } + + /* Create transports. */ + try { + ep.transportCreate(pjsip_transport_type_e.PJSIP_TRANSPORT_UDP, + sipTpConfig); + } catch (Exception e) { + System.out.println(e); + } + + try { + ep.transportCreate(pjsip_transport_type_e.PJSIP_TRANSPORT_TCP, + sipTpConfig); + } catch (Exception e) { + System.out.println(e); + } + + /* Create accounts. */ + for (int i = 0; i < accCfgs.size(); i++) { + MyAccountConfig my_cfg = accCfgs.get(i); + + /* Customize account config */ + my_cfg.accCfg.getNatConfig().setIceEnabled(true); + my_cfg.accCfg.getVideoConfig().setAutoTransmitOutgoing(true); + my_cfg.accCfg.getVideoConfig().setAutoShowIncoming(true); + + MyAccount acc = addAcc(my_cfg.accCfg); + if (acc == null) + continue; + + /* Add Buddies */ + for (int j = 0; j < my_cfg.buddyCfgs.size(); j++) { + BuddyConfig bud_cfg = my_cfg.buddyCfgs.get(j); + acc.addBuddy(bud_cfg); + } + } + + /* Start. */ + try { + ep.libStart(); + } catch (Exception e) { + return; + } + } + + public MyAccount addAcc(AccountConfig cfg) + { + MyAccount acc = new MyAccount(cfg); + try { + acc.create(cfg); + } catch (Exception e) { + acc = null; + return null; + } + + accList.add(acc); + return acc; + } + + public void delAcc(MyAccount acc) + { + accList.remove(acc); + } + + private void loadConfig(String filename) + { + JsonDocument json = new JsonDocument(); + + try { + /* Load file */ + json.loadFile(filename); + ContainerNode root = json.getRootContainer(); + + /* Read endpoint config */ + epConfig.readObject(root); + + /* Read transport config */ + ContainerNode tp_node = root.readContainer("SipTransport"); + sipTpConfig.readObject(tp_node); + + /* Read account configs */ + accCfgs.clear(); + ContainerNode accs_node = root.readArray("accounts"); + while (accs_node.hasUnread()) { + MyAccountConfig acc_cfg = new MyAccountConfig(); + acc_cfg.readObject(accs_node); + accCfgs.add(acc_cfg); + } + } catch (Exception e) { + System.out.println(e); + } + + /* Force delete json now, as I found that Java somehow destroys it + * after lib has been destroyed and from non-registered thread. + */ + json.delete(); + } + + private void buildAccConfigs() + { + /* Sync accCfgs from accList */ + accCfgs.clear(); + for (int i = 0; i < accList.size(); i++) { + MyAccount acc = accList.get(i); + MyAccountConfig my_acc_cfg = new MyAccountConfig(); + my_acc_cfg.accCfg = acc.cfg; + + my_acc_cfg.buddyCfgs.clear(); + for (int j = 0; j < acc.buddyList.size(); j++) { + MyBuddy bud = acc.buddyList.get(j); + my_acc_cfg.buddyCfgs.add(bud.cfg); + } + + accCfgs.add(my_acc_cfg); + } + } + + private void saveConfig(String filename) + { + JsonDocument json = new JsonDocument(); + + try { + /* Write endpoint config */ + json.writeObject(epConfig); + + /* Write transport config */ + ContainerNode tp_node = json.writeNewContainer("SipTransport"); + sipTpConfig.writeObject(tp_node); + + /* Write account configs */ + buildAccConfigs(); + ContainerNode accs_node = json.writeNewArray("accounts"); + for (int i = 0; i < accCfgs.size(); i++) { + accCfgs.get(i).writeObject(accs_node); + } + + /* Save file */ + json.saveFile(filename); + } catch (Exception e) {} + + /* Force delete json now, as I found that Java somehow destroys it + * after lib has been destroyed and from non-registered thread. + */ + json.delete(); + } + + public void deinit() + { + String configPath = appDir + "/" + configName; + saveConfig(configPath); + + /* Try force GC to avoid late destroy of PJ objects as they should be + * deleted before lib is destroyed. + */ + Runtime.getRuntime().gc(); + + /* Shutdown pjsua. Note that Endpoint destructor will also invoke + * libDestroy(), so this will be a test of double libDestroy(). + */ + try { + ep.libDestroy(); + } catch (Exception e) {} + + /* Force delete Endpoint here, to avoid deletion from a non- + * registered thread (by GC?). + */ + ep.delete(); + ep = null; + } +} diff --git a/pjsip-apps/src/swig/java/android/app/src/main/res/drawable-hdpi/ic_launcher.png b/pjsip-apps/src/swig/java/android/app/src/main/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 00000000..288b6655 Binary files /dev/null and b/pjsip-apps/src/swig/java/android/app/src/main/res/drawable-hdpi/ic_launcher.png differ diff --git a/pjsip-apps/src/swig/java/android/app/src/main/res/drawable-mdpi/ic_launcher.png b/pjsip-apps/src/swig/java/android/app/src/main/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 00000000..6ae570b4 Binary files /dev/null and b/pjsip-apps/src/swig/java/android/app/src/main/res/drawable-mdpi/ic_launcher.png differ diff --git a/pjsip-apps/src/swig/java/android/app/src/main/res/drawable-xhdpi/ic_launcher.png b/pjsip-apps/src/swig/java/android/app/src/main/res/drawable-xhdpi/ic_launcher.png new file mode 100644 index 00000000..d4fb7cd9 Binary files /dev/null and b/pjsip-apps/src/swig/java/android/app/src/main/res/drawable-xhdpi/ic_launcher.png differ diff --git a/pjsip-apps/src/swig/java/android/app/src/main/res/drawable-xxhdpi/ic_launcher.png b/pjsip-apps/src/swig/java/android/app/src/main/res/drawable-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..85a60815 Binary files /dev/null and b/pjsip-apps/src/swig/java/android/app/src/main/res/drawable-xxhdpi/ic_launcher.png differ diff --git a/pjsip-apps/src/swig/java/android/app/src/main/res/drawable/bkg.xml b/pjsip-apps/src/swig/java/android/app/src/main/res/drawable/bkg.xml new file mode 100644 index 00000000..e9676f5f --- /dev/null +++ b/pjsip-apps/src/swig/java/android/app/src/main/res/drawable/bkg.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/pjsip-apps/src/swig/java/android/app/src/main/res/layout/activity_call.xml b/pjsip-apps/src/swig/java/android/app/src/main/res/layout/activity_call.xml new file mode 100644 index 00000000..17acf1c5 --- /dev/null +++ b/pjsip-apps/src/swig/java/android/app/src/main/res/layout/activity_call.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + +