diff --git a/.classpath b/.classpath
index 73e3fe0d3..d150c0422 100644
--- a/.classpath
+++ b/.classpath
@@ -6,6 +6,7 @@
+
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 1c7bdc86f..fcea8747d 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -36,13 +36,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
@@ -99,10 +125,21 @@
+
+
+
+
-
-
+
+
+
+
+
+
+
diff --git a/res/drawable-hdpi/jog_tab_bar_left_end_confirm_green.9.png b/res/drawable-hdpi/jog_tab_bar_left_end_confirm_green.9.png
new file mode 100644
index 000000000..7c4f40ea9
Binary files /dev/null and b/res/drawable-hdpi/jog_tab_bar_left_end_confirm_green.9.png differ
diff --git a/res/drawable-hdpi/jog_tab_bar_left_end_normal.9.png b/res/drawable-hdpi/jog_tab_bar_left_end_normal.9.png
new file mode 100644
index 000000000..b9ec2374a
Binary files /dev/null and b/res/drawable-hdpi/jog_tab_bar_left_end_normal.9.png differ
diff --git a/res/drawable-hdpi/jog_tab_bar_left_end_pressed.9.png b/res/drawable-hdpi/jog_tab_bar_left_end_pressed.9.png
new file mode 100644
index 000000000..2800cabeb
Binary files /dev/null and b/res/drawable-hdpi/jog_tab_bar_left_end_pressed.9.png differ
diff --git a/res/drawable-hdpi/jog_tab_bar_right_end_confirm_red.9.png b/res/drawable-hdpi/jog_tab_bar_right_end_confirm_red.9.png
new file mode 100644
index 000000000..fd98571d3
Binary files /dev/null and b/res/drawable-hdpi/jog_tab_bar_right_end_confirm_red.9.png differ
diff --git a/res/drawable-hdpi/jog_tab_bar_right_end_normal.9.png b/res/drawable-hdpi/jog_tab_bar_right_end_normal.9.png
new file mode 100644
index 000000000..49ec1184f
Binary files /dev/null and b/res/drawable-hdpi/jog_tab_bar_right_end_normal.9.png differ
diff --git a/res/drawable-hdpi/jog_tab_bar_right_end_pressed.9.png b/res/drawable-hdpi/jog_tab_bar_right_end_pressed.9.png
new file mode 100644
index 000000000..ffc54339d
Binary files /dev/null and b/res/drawable-hdpi/jog_tab_bar_right_end_pressed.9.png differ
diff --git a/res/drawable-hdpi/jog_tab_left_confirm_green.png b/res/drawable-hdpi/jog_tab_left_confirm_green.png
new file mode 100644
index 000000000..23acc95f1
Binary files /dev/null and b/res/drawable-hdpi/jog_tab_left_confirm_green.png differ
diff --git a/res/drawable-hdpi/jog_tab_left_normal.png b/res/drawable-hdpi/jog_tab_left_normal.png
new file mode 100644
index 000000000..18216f946
Binary files /dev/null and b/res/drawable-hdpi/jog_tab_left_normal.png differ
diff --git a/res/drawable-hdpi/jog_tab_left_pressed.png b/res/drawable-hdpi/jog_tab_left_pressed.png
new file mode 100644
index 000000000..86f49c685
Binary files /dev/null and b/res/drawable-hdpi/jog_tab_left_pressed.png differ
diff --git a/res/drawable-hdpi/jog_tab_right_confirm_red.png b/res/drawable-hdpi/jog_tab_right_confirm_red.png
new file mode 100644
index 000000000..bb0fa4754
Binary files /dev/null and b/res/drawable-hdpi/jog_tab_right_confirm_red.png differ
diff --git a/res/drawable-hdpi/jog_tab_right_normal.png b/res/drawable-hdpi/jog_tab_right_normal.png
new file mode 100644
index 000000000..68545ef51
Binary files /dev/null and b/res/drawable-hdpi/jog_tab_right_normal.png differ
diff --git a/res/drawable-hdpi/jog_tab_right_pressed.png b/res/drawable-hdpi/jog_tab_right_pressed.png
new file mode 100644
index 000000000..3d75c0d9b
Binary files /dev/null and b/res/drawable-hdpi/jog_tab_right_pressed.png differ
diff --git a/res/drawable-hdpi/jog_tab_target_green.png b/res/drawable-hdpi/jog_tab_target_green.png
new file mode 100644
index 000000000..17f6b101e
Binary files /dev/null and b/res/drawable-hdpi/jog_tab_target_green.png differ
diff --git a/res/drawable-hdpi/jog_tab_target_red.png b/res/drawable-hdpi/jog_tab_target_red.png
new file mode 100644
index 000000000..8db20bb63
Binary files /dev/null and b/res/drawable-hdpi/jog_tab_target_red.png differ
diff --git a/res/drawable-hdpi/unknown_person.png b/res/drawable-hdpi/unknown_person.png
new file mode 100644
index 000000000..0e5179932
Binary files /dev/null and b/res/drawable-hdpi/unknown_person.png differ
diff --git a/res/drawable-ldpi/unknown_person.png b/res/drawable-ldpi/unknown_person.png
new file mode 100644
index 000000000..4425150f2
Binary files /dev/null and b/res/drawable-ldpi/unknown_person.png differ
diff --git a/res/drawable-mdpi/unknown_person.png b/res/drawable-mdpi/unknown_person.png
new file mode 100644
index 000000000..ed45aedcd
Binary files /dev/null and b/res/drawable-mdpi/unknown_person.png differ
diff --git a/res/drawable/conf_callee_active_bg.xml b/res/drawable/conf_callee_active_bg.xml
new file mode 100644
index 000000000..662710e79
--- /dev/null
+++ b/res/drawable/conf_callee_active_bg.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/res/drawable/conf_callee_bg.xml b/res/drawable/conf_callee_bg.xml
new file mode 100644
index 000000000..34c4f8687
--- /dev/null
+++ b/res/drawable/conf_callee_bg.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
diff --git a/res/drawable/conf_callee_incoming_bg.xml b/res/drawable/conf_callee_incoming_bg.xml
new file mode 100644
index 000000000..65ed06b42
--- /dev/null
+++ b/res/drawable/conf_callee_incoming_bg.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/res/drawable/conf_callee_inconf_bg.xml b/res/drawable/conf_callee_inconf_bg.xml
new file mode 100644
index 000000000..ada123c15
--- /dev/null
+++ b/res/drawable/conf_callee_inconf_bg.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/res/drawable/conf_callee_pressed_bg.xml b/res/drawable/conf_callee_pressed_bg.xml
new file mode 100644
index 000000000..31cfd4a6f
--- /dev/null
+++ b/res/drawable/conf_callee_pressed_bg.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/res/drawable/conf_callee_selected_bg.xml b/res/drawable/conf_callee_selected_bg.xml
new file mode 100644
index 000000000..d98b22f5a
--- /dev/null
+++ b/res/drawable/conf_callee_selected_bg.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
diff --git a/res/drawable/conf_callee_selector_active.xml b/res/drawable/conf_callee_selector_active.xml
new file mode 100644
index 000000000..cb64a4a1e
--- /dev/null
+++ b/res/drawable/conf_callee_selector_active.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/res/drawable/conf_callee_selector_incoming.xml b/res/drawable/conf_callee_selector_incoming.xml
new file mode 100644
index 000000000..b7e045d00
--- /dev/null
+++ b/res/drawable/conf_callee_selector_incoming.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/res/drawable/conf_callee_selector_inconf.xml b/res/drawable/conf_callee_selector_inconf.xml
new file mode 100644
index 000000000..85e3ea5ff
--- /dev/null
+++ b/res/drawable/conf_callee_selector_inconf.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/res/drawable/conf_callee_selector_normal.xml b/res/drawable/conf_callee_selector_normal.xml
new file mode 100644
index 000000000..2da1f36ac
--- /dev/null
+++ b/res/drawable/conf_callee_selector_normal.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/res/drawable/conf_merge.png b/res/drawable/conf_merge.png
new file mode 100644
index 000000000..2f76509b5
Binary files /dev/null and b/res/drawable/conf_merge.png differ
diff --git a/res/drawable/conf_merge_all.png b/res/drawable/conf_merge_all.png
new file mode 100644
index 000000000..66626fe71
Binary files /dev/null and b/res/drawable/conf_merge_all.png differ
diff --git a/res/drawable/conf_pause.png b/res/drawable/conf_pause.png
new file mode 100644
index 000000000..9ab5bae92
Binary files /dev/null and b/res/drawable/conf_pause.png differ
diff --git a/res/drawable/conf_permute.png b/res/drawable/conf_permute.png
new file mode 100644
index 000000000..225b8bf7d
Binary files /dev/null and b/res/drawable/conf_permute.png differ
diff --git a/res/drawable/conf_remove.png b/res/drawable/conf_remove.png
new file mode 100644
index 000000000..7570cc599
Binary files /dev/null and b/res/drawable/conf_remove.png differ
diff --git a/res/drawable/conf_resume.png b/res/drawable/conf_resume.png
new file mode 100644
index 000000000..b8a2d50cd
Binary files /dev/null and b/res/drawable/conf_resume.png differ
diff --git a/res/drawable/conf_status_inconf.png b/res/drawable/conf_status_inconf.png
new file mode 100644
index 000000000..3f89fb3fb
Binary files /dev/null and b/res/drawable/conf_status_inconf.png differ
diff --git a/res/drawable/conf_status_paused.png b/res/drawable/conf_status_paused.png
new file mode 100644
index 000000000..2d6a3cb4d
Binary files /dev/null and b/res/drawable/conf_status_paused.png differ
diff --git a/res/drawable/conf_terminate.png b/res/drawable/conf_terminate.png
new file mode 100644
index 000000000..0a11c639e
Binary files /dev/null and b/res/drawable/conf_terminate.png differ
diff --git a/res/drawable/conf_transfer.png b/res/drawable/conf_transfer.png
new file mode 100644
index 000000000..d539cd241
Binary files /dev/null and b/res/drawable/conf_transfer.png differ
diff --git a/res/drawable/conf_unhook.png b/res/drawable/conf_unhook.png
new file mode 100644
index 000000000..2f141582d
Binary files /dev/null and b/res/drawable/conf_unhook.png differ
diff --git a/res/drawable/conf_video.png b/res/drawable/conf_video.png
new file mode 100644
index 000000000..6e7616404
Binary files /dev/null and b/res/drawable/conf_video.png differ
diff --git a/res/drawable/jog_tab_bar_left_answer.xml b/res/drawable/jog_tab_bar_left_answer.xml
new file mode 100644
index 000000000..32ce3dcda
--- /dev/null
+++ b/res/drawable/jog_tab_bar_left_answer.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/drawable/jog_tab_bar_left_end_confirm_green.9.png b/res/drawable/jog_tab_bar_left_end_confirm_green.9.png
new file mode 100644
index 000000000..e8be7bf37
Binary files /dev/null and b/res/drawable/jog_tab_bar_left_end_confirm_green.9.png differ
diff --git a/res/drawable/jog_tab_bar_left_end_normal.9.png b/res/drawable/jog_tab_bar_left_end_normal.9.png
new file mode 100644
index 000000000..747745378
Binary files /dev/null and b/res/drawable/jog_tab_bar_left_end_normal.9.png differ
diff --git a/res/drawable/jog_tab_bar_left_end_pressed.9.png b/res/drawable/jog_tab_bar_left_end_pressed.9.png
new file mode 100644
index 000000000..c79a35cf3
Binary files /dev/null and b/res/drawable/jog_tab_bar_left_end_pressed.9.png differ
diff --git a/res/drawable/jog_tab_bar_right_decline.xml b/res/drawable/jog_tab_bar_right_decline.xml
new file mode 100644
index 000000000..83183ac1b
--- /dev/null
+++ b/res/drawable/jog_tab_bar_right_decline.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/drawable/jog_tab_bar_right_end_confirm_red.9.png b/res/drawable/jog_tab_bar_right_end_confirm_red.9.png
new file mode 100644
index 000000000..0242a42b5
Binary files /dev/null and b/res/drawable/jog_tab_bar_right_end_confirm_red.9.png differ
diff --git a/res/drawable/jog_tab_bar_right_end_normal.9.png b/res/drawable/jog_tab_bar_right_end_normal.9.png
new file mode 100644
index 000000000..2e6ca2ebf
Binary files /dev/null and b/res/drawable/jog_tab_bar_right_end_normal.9.png differ
diff --git a/res/drawable/jog_tab_bar_right_end_pressed.9.png b/res/drawable/jog_tab_bar_right_end_pressed.9.png
new file mode 100644
index 000000000..8bdfd8406
Binary files /dev/null and b/res/drawable/jog_tab_bar_right_end_pressed.9.png differ
diff --git a/res/drawable/jog_tab_left_answer.xml b/res/drawable/jog_tab_left_answer.xml
new file mode 100644
index 000000000..18ec7fa15
--- /dev/null
+++ b/res/drawable/jog_tab_left_answer.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/drawable/jog_tab_left_confirm_green.png b/res/drawable/jog_tab_left_confirm_green.png
new file mode 100755
index 000000000..829b1462d
Binary files /dev/null and b/res/drawable/jog_tab_left_confirm_green.png differ
diff --git a/res/drawable/jog_tab_left_normal.png b/res/drawable/jog_tab_left_normal.png
new file mode 100755
index 000000000..eb91e97ea
Binary files /dev/null and b/res/drawable/jog_tab_left_normal.png differ
diff --git a/res/drawable/jog_tab_left_pressed.png b/res/drawable/jog_tab_left_pressed.png
new file mode 100755
index 000000000..995199292
Binary files /dev/null and b/res/drawable/jog_tab_left_pressed.png differ
diff --git a/res/drawable/jog_tab_right_confirm_red.png b/res/drawable/jog_tab_right_confirm_red.png
new file mode 100644
index 000000000..2e1e105c6
Binary files /dev/null and b/res/drawable/jog_tab_right_confirm_red.png differ
diff --git a/res/drawable/jog_tab_right_decline.xml b/res/drawable/jog_tab_right_decline.xml
new file mode 100644
index 000000000..a3bca5e92
--- /dev/null
+++ b/res/drawable/jog_tab_right_decline.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/drawable/jog_tab_right_normal.png b/res/drawable/jog_tab_right_normal.png
new file mode 100755
index 000000000..f2113f26e
Binary files /dev/null and b/res/drawable/jog_tab_right_normal.png differ
diff --git a/res/drawable/jog_tab_right_pressed.png b/res/drawable/jog_tab_right_pressed.png
new file mode 100755
index 000000000..65cd51ea6
Binary files /dev/null and b/res/drawable/jog_tab_right_pressed.png differ
diff --git a/res/drawable/jog_tab_target_green.png b/res/drawable/jog_tab_target_green.png
new file mode 100644
index 000000000..188f3cc83
Binary files /dev/null and b/res/drawable/jog_tab_target_green.png differ
diff --git a/res/drawable/jog_tab_target_red.png b/res/drawable/jog_tab_target_red.png
new file mode 100644
index 000000000..a36394dc5
Binary files /dev/null and b/res/drawable/jog_tab_target_red.png differ
diff --git a/res/drawable/mini_stopcall_red.png b/res/drawable/mini_stopcall_red.png
new file mode 100644
index 000000000..fcf26c8bd
Binary files /dev/null and b/res/drawable/mini_stopcall_red.png differ
diff --git a/res/drawable/numpad_big.png b/res/drawable/numpad_big.png
new file mode 100644
index 000000000..2f1706869
Binary files /dev/null and b/res/drawable/numpad_big.png differ
diff --git a/res/drawable/picker_plus.png b/res/drawable/picker_plus.png
new file mode 100644
index 000000000..e75c7c27c
Binary files /dev/null and b/res/drawable/picker_plus.png differ
diff --git a/res/drawable/picker_transfer.png b/res/drawable/picker_transfer.png
new file mode 100644
index 000000000..0251581f3
Binary files /dev/null and b/res/drawable/picker_transfer.png differ
diff --git a/res/drawable/plus.png b/res/drawable/plus.png
new file mode 100644
index 000000000..20327957a
Binary files /dev/null and b/res/drawable/plus.png differ
diff --git a/res/drawable/resume_blue.png b/res/drawable/resume_blue.png
new file mode 100644
index 000000000..8274e4a8d
Binary files /dev/null and b/res/drawable/resume_blue.png differ
diff --git a/res/drawable/unknown_person.png b/res/drawable/unknown_person.png
new file mode 100644
index 000000000..ed45aedcd
Binary files /dev/null and b/res/drawable/unknown_person.png differ
diff --git a/res/layout-land/dialer.xml b/res/layout-land/dialer.xml
index da2a314e5..9502261f6 100644
--- a/res/layout-land/dialer.xml
+++ b/res/layout-land/dialer.xml
@@ -40,6 +40,7 @@
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/conf_callee_older_devices.xml b/res/layout/conf_callee_older_devices.xml
new file mode 100644
index 000000000..5e2493081
--- /dev/null
+++ b/res/layout/conf_callee_older_devices.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/conf_choices_admin.xml b/res/layout/conf_choices_admin.xml
new file mode 100644
index 000000000..cc7963fb3
--- /dev/null
+++ b/res/layout/conf_choices_admin.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/conf_choices_dialog.xml b/res/layout/conf_choices_dialog.xml
new file mode 100644
index 000000000..b79550925
--- /dev/null
+++ b/res/layout/conf_choices_dialog.xml
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/conferencing.xml b/res/layout/conferencing.xml
new file mode 100644
index 000000000..d6537d0ba
--- /dev/null
+++ b/res/layout/conferencing.xml
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/dialer.xml b/res/layout/dialer.xml
index a5b8ed433..4af69efbc 100644
--- a/res/layout/dialer.xml
+++ b/res/layout/dialer.xml
@@ -39,13 +39,14 @@
-
-
+
+
+
-
-
+
+
diff --git a/res/layout/incall_view.xml b/res/layout/incall_view.xml
index 74a4bd303..714cf5cbf 100644
--- a/res/layout/incall_view.xml
+++ b/res/layout/incall_view.xml
@@ -24,9 +24,7 @@
-
+
@@ -34,8 +32,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
diff --git a/res/layout/incoming.xml b/res/layout/incoming.xml
new file mode 100644
index 000000000..401c254d0
--- /dev/null
+++ b/res/layout/incoming.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/simplified_dialer.xml b/res/layout/simplified_dialer.xml
new file mode 100644
index 000000000..7574a9166
--- /dev/null
+++ b/res/layout/simplified_dialer.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/layout/uri_picker.xml b/res/layout/uri_picker.xml
new file mode 100644
index 000000000..be34db025
--- /dev/null
+++ b/res/layout/uri_picker.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/values/callee_style.xml b/res/values/callee_style.xml
new file mode 100644
index 000000000..a6fe15731
--- /dev/null
+++ b/res/values/callee_style.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/values/conf_style.xml b/res/values/conf_style.xml
new file mode 100644
index 000000000..3aa046546
--- /dev/null
+++ b/res/values/conf_style.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/res/values/custom.xml b/res/values/custom.xml
index a5602db26..0b975b0c4 100644
--- a/res/values/custom.xml
+++ b/res/values/custom.xml
@@ -3,23 +3,9 @@
Linphone
- test.linphone.org
-
- true
-
- false
- true
- true
- false
- true
- true
-
Linphone
- true
Registered to %s
Fails to register to %s
-
Linphone %s SIP (rfc 3261) compatible phone under GNU Public License V2\n http://www.linphone.org\n\nInstructions\nhttp://www.linphone.org/m/help\n\n© 2011 Belledonne Communications
- linphone-android@belledonne-communications.com
\ No newline at end of file
diff --git a/res/values/digit_style.xml b/res/values/digit_style.xml
index 400132100..d5f17e13a 100644
--- a/res/values/digit_style.xml
+++ b/res/values/digit_style.xml
@@ -9,4 +9,13 @@
- 20sp
- 1
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/values/non_localizable_custom.xml b/res/values/non_localizable_custom.xml
new file mode 100644
index 000000000..2f7d968bf
--- /dev/null
+++ b/res/values/non_localizable_custom.xml
@@ -0,0 +1,25 @@
+
+
+
+
+ test.linphone.org
+
+ true
+
+ false
+ true
+ true
+ false
+ true
+ true
+ false
+ true
+
+ true
+
+ true
+ #191970
+
+ linphone-android@belledonne-communications.com
+
+
diff --git a/res/values/slidingtab_style.xml b/res/values/slidingtab_style.xml
new file mode 100644
index 000000000..81c1693f6
--- /dev/null
+++ b/res/values/slidingtab_style.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f715b4e77..b533b1158 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1,6 +1,60 @@
+ Starting up...
+ An error occurred while accepting call
+
+ Canceled
+ Error adding new call
+ Transfer initiated
+ Transfer call to
+
+ Warning: service is not ready
+
+ Close
+
+ conf
+ active
+ paused
+ paused
+
+ incoming
+ ringing
+
+ Mute
+ Speaker
+ Bluetooth
+
+ Cancel
+ Add call
+ Transfer call
+
+ Enter conference
+ Momentarily leave conference
+ Terminate conference
+
+ Transfer to existing call
+ Transfer to new call
+ Resume
+ Pause
+ Add to conference
+ Add all calls to conference
+ Remove from the conference
+ Terminate
+
+ Hang up
+ Merge
+ Transfer
+ Permute
+ No active call
+ DTMFs
+
+ You host a conference
+ You are part of it
+ Go out
+ You are out of it
+ Go in
+
Audio hacks
Use specific mode hack
0=MODE_NORMAL (default), 2=MODE_IN_CALL
@@ -54,9 +108,9 @@
Terminate call
Video settings
Share my camera
- Automatically send my camera on incoming calls
- Initiate video calls
- Always send my camera on outgoing calls
+ Send my camera on video capable calls
+ Initiate video capable calls
+ Disable to remove negotiation of video codecs for outgoing calls
Enable Video
Replace + by 00
iLBC might be unavailable depending on ARM processor and Android OS version.
diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml
index f17a6bb6f..b6f689f0c 100644
--- a/res/xml/preferences.xml
+++ b/res/xml/preferences.xml
@@ -4,16 +4,16 @@
+ android:key="@string/pref_username_key" android:inputType="text|textEmailAddress">
+ android:key="@string/pref_domain_key" android:inputType="text|textEmailAddress">
+ android:key="@string/pref_proxy_key" android:inputType="text|textEmailAddress">
,
+ OnClickListener {
+
+ private View confHeaderView;
+ static boolean active;
+
+ private boolean unMuteOnReturnFromUriPicker;
+
+ // Start Override to test block
+ protected LinphoneCore lc() {
+ return LinphoneManager.getLc();
+ }
+
+ protected List getInitialCalls() {
+ return LinphoneUtils.getLinphoneCalls(lc());
+ }
+
+ // End override to test block
+
+ private static final int numpad_dialog_id = 1;
+ public static final String ADD_CALL = "add_call";
+ public static final String TRANSFER_TO_NEW_CALL = "transfer_to_new_call";
+ public static final String CALL_NATIVE_ID = "call_native_id";
+ private static final int ID_ADD_CALL = 1;
+ private static final int ID_TRANSFER_CALL = 2;
+
+
+
+ @SuppressWarnings("unused")
+ private void workaroundStatusBarBug() {
+ // call from onCreate to get a clean display on full screen no icons
+ // otherwise the upper side of the activity may be corrupted
+ getWindow().setFlags(
+ WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
+ WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
+ }
+
+ private void pauseCurrentCallOrLeaveConference() {
+ LinphoneCall call = lc().getCurrentCall();
+ if (call != null) lc().pauseCall(call);
+ lc().leaveConference();
+ }
+
+ private LinphoneManagerWaitHelper waitHelper;
+ private ToggleButton mMuteMicButton;
+ private ToggleButton mSpeakerButton;
+ private boolean useVideoActivity;
+ private int multipleCallsLimit;
+ private boolean allowTransfers;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ setContentView(R.layout.conferencing);
+
+ allowTransfers = getResources().getBoolean(R.bool.allow_transfers);
+
+ confHeaderView = findViewById(R.id.conf_header);
+ confHeaderView.setOnClickListener(this);
+
+ findViewById(R.id.addCall).setOnClickListener(this);
+
+ findViewById(R.id.incallNumpadShow).setOnClickListener(this);
+ findViewById(R.id.conf_simple_merge).setOnClickListener(this);
+ View transferView = findViewById(R.id.conf_simple_transfer);
+ transferView.setOnClickListener(this);
+ if (!allowTransfers) {
+ transferView.setVisibility(View.GONE);
+ }
+ findViewById(R.id.conf_simple_permute).setOnClickListener(this);
+
+ mMuteMicButton = (ToggleButton) findViewById(R.id.toggleMuteMic);
+ mMuteMicButton.setOnClickListener(this);
+ mSpeakerButton = (ToggleButton) findViewById(R.id.toggleSpeaker);
+ mSpeakerButton.setOnClickListener(this);
+
+ waitHelper = new LinphoneManagerWaitHelper(this, this);
+ waitHelper.doManagerDependentOnCreate();
+ useVideoActivity = getResources().getBoolean(R.bool.use_video_activity);
+
+// workaroundStatusBarBug();
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ public void onCreateWhenManagerReady() {
+ List calls = getInitialCalls();
+ setListAdapter(new CalleeListAdapter(calls));
+
+ findViewById(R.id.incallHang).setOnClickListener(this);
+ multipleCallsLimit = lc().getMaxCalls();
+ }
+ @Override
+ public void onResumeWhenManagerReady() {
+ registerLinphoneListener(true);
+ updateCalleeImage();
+ updateConfState();
+ updateSimpleControlButtons();
+ CalleeListAdapter adapter = (CalleeListAdapter) getListAdapter();
+ if (adapter.linphoneCalls.size() != lc().getCallsNb()) {
+ adapter.linphoneCalls.clear();
+ adapter.linphoneCalls.addAll(getInitialCalls());
+ }
+ recreateActivity(adapter);
+ LinphoneManager.startProximitySensorForActivity(this);
+ mSpeakerButton.setChecked(LinphoneManager.getInstance().isSpeakerOn());
+ mMuteMicButton.setChecked(LinphoneManager.getLc().isMicMuted());
+
+ if (multipleCallsLimit > 0) {
+ updateAddCallButton();
+ }
+
+ LinphoneCall currentCall = LinphoneManager.getLc().getCurrentCall();
+ if (currentCall != null) {
+ tryToStartVideoActivity(currentCall, currentCall.getState());
+ }
+ }
+
+ private void updateAddCallButton() {
+ boolean limitReached = lc().getCallsNb() >= multipleCallsLimit;
+ findViewById(R.id.addCall).setVisibility(limitReached ? GONE : VISIBLE);
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ }
+
+ protected void registerLinphoneListener(boolean register) {
+ if (register)
+ LinphoneManager.addListener(this);
+ else
+ LinphoneManager.removeListener(this);
+ }
+
+
+
+ @Override
+ protected void onResume() {
+ active=true;
+ waitHelper.doManagerDependentOnResume();
+ super.onResume();
+ }
+
+ @Override
+ protected void onPause() {
+ active=false;
+ registerLinphoneListener(false);
+ LinphoneManager.stopProximitySensorForActivity(this);
+ super.onPause();
+ }
+
+ private void updateCalleeImage() {
+ ImageView view = (ImageView) findViewById(R.id.incall_picture);
+ LinphoneCall currentCall = lc().getCurrentCall();
+
+ if (lc().getCallsNb() != 1 || currentCall == null) {
+ view.setVisibility(GONE);
+ return;
+ }
+
+ Uri picture = LinphoneUtils.findUriPictureOfContactAndSetDisplayName(
+ currentCall.getRemoteAddress(), getContentResolver());
+ LinphoneUtils.setImagePictureFromUri(this, view, picture, R.drawable.unknown_person);
+ view.setVisibility(VISIBLE);
+ }
+
+ private void enableView(View root, int id, OnClickListener l, boolean enable) {
+ View v = root.findViewById(id);
+ v.setVisibility(enable ? VISIBLE : GONE);
+ v.setOnClickListener(l);
+ }
+ @Override
+ protected Dialog onCreateDialog(final int id) {
+ if (id == LinphoneManagerWaitHelper.DIALOG_ID) {
+ return waitHelper.createWaitDialog();
+ }
+
+ switch (id) {
+ case numpad_dialog_id:
+ return new AlertDialog.Builder(this).setView(
+ getLayoutInflater().inflate(R.layout.numpad, null))
+ // .setIcon(R.drawable.logo_linphone_57x57)
+ // .setTitle("Send DTMFs")
+ .setPositiveButton(getString(R.string.close_button_text), new
+ DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int whichButton)
+ {
+ dismissDialog(id);
+ }
+ })
+ .create();
+ default:
+ throw new RuntimeException("unkown dialog id " + id);
+ }
+
+ }
+
+ // protected void conferenceMerge(boolean hostInTheConference, LinphoneCall
+ // ... calls) {
+ // for (LinphoneCall call: calls) {
+ // getLc().addToConference(call, false);
+ // }
+ // getLc().enterConference(hostInTheConference);
+ // }
+
+ // FIXME hack; should have an event?
+ protected final void hackTriggerConfStateUpdate() {
+ updateConfState();
+ }
+
+ private final void updateConfState() {
+ if (lc().getCallsNb() == 0) {
+ setResult(RESULT_OK);
+ finish();
+ }
+
+ boolean inConf = lc().isInConference();
+
+ int bgColor = getResources().getColor(inConf? R.color.conf_active_bg_color : android.R.color.transparent);
+ confHeaderView.setBackgroundColor(bgColor);
+ confHeaderView.setVisibility(lc().getConferenceSize() > 0 ? VISIBLE: GONE);
+
+// TextView v = (TextView) confHeaderView
+// .findViewById(R.id.conf_self_attending);
+// v.setText(inConf ? R.string.in_conf : R.string.out_conf);
+ }
+
+ private LinphoneCall activateCallOnReturnFromUriPicker;
+ private boolean enterConferenceOnReturnFromUriPicker;
+ private void openUriPicker(String pickerType, int requestCode) {
+ activateCallOnReturnFromUriPicker = lc().getCurrentCall();
+ enterConferenceOnReturnFromUriPicker = lc().isInConference();
+ pauseCurrentCallOrLeaveConference();
+ Intent intent = new Intent().setClass(this, UriPickerActivity.class);
+ intent.putExtra(UriPickerActivity.EXTRA_PICKER_TYPE, pickerType);
+ startActivityForResult(intent, requestCode);
+ if (!lc().isMicMuted()) {
+ unMuteOnReturnFromUriPicker = true;
+ lc().muteMic(true);
+ ((ToggleButton) findViewById(R.id.toggleMuteMic)).setChecked(true);
+ }
+ }
+
+ public void onClick(View v) {
+ switch (v.getId()) {
+ case R.id.addCall:
+ openUriPicker(UriPickerActivity.EXTRA_PICKER_TYPE_ADD, ID_ADD_CALL);
+ break;
+ case R.id.conf_header:
+ View content = getLayoutInflater().inflate(R.layout.conf_choices_admin, null);
+ final Dialog dialog = new AlertDialog.Builder(ConferenceActivity.this).setView(content).create();
+ boolean isInConference = lc().isInConference();
+ OnClickListener l = new OnClickListener() {
+ public void onClick(View v) {
+ switch (v.getId()) {
+ case R.id.conf_add_all_to_conference_button:
+ lc().addAllToConference();
+ updateConfState();
+ break;
+ case R.id.conf_enter_button:
+ lc().enterConference();
+ updateConfState();
+ break;
+ case R.id.conf_leave_button:
+ lc().leaveConference();
+ updateConfState();
+ break;
+ case R.id.conf_terminate_button:
+ lc().terminateConference();
+ findViewById(R.id.conf_header).setVisibility(GONE);
+ break;
+ default:
+ break;
+ }
+ dialog.dismiss();
+ }
+ };
+ enableView(content, R.id.conf_enter_button, l, !isInConference);
+ enableView(content, R.id.conf_leave_button, l, isInConference);
+ content.findViewById(R.id.conf_terminate_button).setOnClickListener(l);
+ content.findViewById(R.id.conf_add_all_to_conference_button).setOnClickListener(l);
+
+ dialog.show();
+ break;
+ case R.id.incallHang:
+ lc().terminateAllCalls();
+ setResult(RESULT_OK);
+ finish();
+ break;
+ case R.id.incallNumpadShow:
+ showDialog(numpad_dialog_id);
+ break;
+ case R.id.conf_simple_merge:
+ findViewById(R.id.conf_control_buttons).setVisibility(GONE);
+ lc().addAllToConference();
+ break;
+ case R.id.conf_simple_transfer:
+ findViewById(R.id.conf_control_buttons).setVisibility(GONE);
+ LinphoneCall tCall = lc().getCurrentCall();
+ if (tCall != null) {
+ prepareForTransferingExistingCall(tCall);
+ } else {
+ Toast.makeText(this, R.string.conf_simple_no_current_call, Toast.LENGTH_SHORT).show();
+ }
+ break;
+ case R.id.conf_simple_permute:
+ findViewById(R.id.conf_control_buttons).setVisibility(GONE);
+ for (LinphoneCall call : LinphoneUtils.getLinphoneCalls(lc())) {
+ if (State.Paused == call.getState()) {
+ lc().resumeCall(call);
+ break;
+ }
+ }
+ break;
+ case R.id.toggleMuteMic:
+ lc().muteMic(((ToggleButton) v).isChecked());
+ break;
+ case R.id.toggleSpeaker:
+ if (((ToggleButton) v).isChecked()) {
+ LinphoneManager.getInstance().routeAudioToSpeaker(true);
+ } else {
+ LinphoneManager.getInstance().routeAudioToReceiver(true);
+ }
+ break;
+ default:
+ break;
+ }
+
+ }
+
+ private void prepareForTransferingExistingCall(final LinphoneCall call) {
+ final List existingCalls = LinphoneUtils.getLinphoneCalls(lc());
+ existingCalls.remove(call);
+ final List numbers = new ArrayList(existingCalls.size());
+ Resources r = getResources();
+ for(LinphoneCall c : existingCalls) {
+ numbers.add(LinphoneManager.extractADisplayName(r, c.getRemoteAddress()));
+ }
+ ListAdapter adapter = new ArrayAdapter(ConferenceActivity.this, android.R.layout.select_dialog_item, numbers);
+ DialogInterface.OnClickListener l = new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ lc().transferCallToAnother(call, existingCalls.get(which));
+ }
+ };
+ new AlertDialog.Builder(ConferenceActivity.this).setTitle(R.string.transfer_dialog_title).setAdapter(adapter, l).create().show();
+ }
+
+ private class CallActionListener implements OnClickListener {
+ private LinphoneCall call;
+ private Dialog dialog;
+ public CallActionListener(LinphoneCall call, Dialog dialog) {
+ this.call = call;
+ this.dialog = dialog;
+ }
+ public CallActionListener(LinphoneCall call) {
+ this.call = call;
+ }
+ public void onClick(View v) {
+ switch (v.getId()) {
+ case R.id.merge_to_conference:
+ lc().addToConference(call);
+ break;
+ case R.id.terminate_call:
+ lc().terminateCall(call);
+ break;
+ case R.id.pause:
+ lc().pauseCall(call);
+ break;
+ case R.id.resume:
+ lc().resumeCall(call);
+ break;
+ case R.id.unhook_call:
+ try {
+ lc().acceptCall(call);
+ } catch (LinphoneCoreException e) {
+ throw new RuntimeException(e);
+ }
+ break;
+ case R.id.transfer_existing:
+ prepareForTransferingExistingCall(call);
+ break;
+ case R.id.transfer_new:
+ openUriPicker(UriPickerActivity.EXTRA_PICKER_TYPE_TRANSFER, ID_TRANSFER_CALL);
+ callToTransfer = call;
+ break;
+ case R.id.remove_from_conference:
+ lc().removeFromConference(call);
+ break;
+ case R.id.addVideo:
+ LinphoneManager.getInstance().addVideo();
+ break;
+ default:
+ throw new RuntimeException("unknown id " + v.getId());
+ }
+ if (dialog != null) dialog.dismiss();
+ }
+ }
+ private class CalleeListAdapter extends BaseAdapter {
+ private List linphoneCalls;
+
+ public CalleeListAdapter(List calls) {
+ linphoneCalls = calls;
+ }
+
+ public int getCount() {
+ return linphoneCalls != null ? linphoneCalls.size() : 0;
+ }
+
+ public Object getItem(int position) {
+ return linphoneCalls.get(position);
+ }
+
+ public long getItemId(int position) {
+ return position;
+ }
+
+ private boolean aConferenceIsPossible() {
+ if (lc().getCallsNb() < 2) {
+ return false;
+ }
+ int count = 0;
+ for (LinphoneCall call : linphoneCalls) {
+ final LinphoneCall.State state = call.getState();
+ boolean connectionEstablished = state == State.StreamsRunning
+ || state == State.Paused
+ || state == State.PausedByRemote;
+ if (connectionEstablished)
+ count++;
+ if (count >= 2)
+ return true;
+ }
+ return false;
+ }
+
+ private void setVisibility(View v, int id, boolean visible) {
+ v.findViewById(id).setVisibility(visible ? VISIBLE : GONE);
+ }
+ private void setVisibility(View v, boolean visible) {
+ v.setVisibility(visible ? VISIBLE : GONE);
+ }
+ private void setStatusLabel(View v, State state, boolean inConf, boolean activeOne) {
+ String statusLabel = getStateText(state);
+
+ if (activeOne)
+ statusLabel=getString(R.string.status_active_call);
+
+ if (inConf)
+ statusLabel=getString(R.string.status_conf_call);
+
+ ((TextView) v.findViewById(R.id.status_label)).setText(statusLabel);
+ }
+
+ public View getView(int position, View v, ViewGroup parent) {
+ Log.i("ConferenceActivity.getView(",position,") out of ", linphoneCalls.size());
+ if (v == null) {
+ if (Version.sdkAboveOrEqual(Version.API06_ECLAIR_201)) {
+ v = getLayoutInflater().inflate(R.layout.conf_callee, null);
+ } else {
+ v = getLayoutInflater().inflate(R.layout.conf_callee_older_devices, null);
+ }
+ }
+
+ final LinphoneCall call = linphoneCalls.get(position);
+ final LinphoneCall.State state = call.getState();
+
+ LinphoneAddress address = call.getRemoteAddress();
+ String mainText = address.getDisplayName();
+ String complText = address.getUserName();
+ if ((getResources().getBoolean(R.bool.show_full_remote_address_on_incoming_call))) {
+ complText += "@" + address.getDomain();
+ }
+ TextView mainTextView = (TextView) v.findViewById(R.id.name);
+ TextView complTextView = (TextView) v.findViewById(R.id.address);
+ if (TextUtils.isEmpty(mainText)) {
+ mainTextView.setText(complText);
+ complTextView.setVisibility(View.GONE);
+ } else {
+ mainTextView.setText(mainText);
+ complTextView.setText(complText);
+ complTextView.setVisibility(View.VISIBLE);
+ }
+
+ final boolean isInConference = call.isInConference();
+ boolean currentlyActiveCall = !isInConference
+ && state == State.StreamsRunning;
+
+ setStatusLabel(v, state, isInConference, currentlyActiveCall);
+
+
+ int bgDrawableId = R.drawable.conf_callee_selector_normal;
+ if (state == State.IncomingReceived) {
+ bgDrawableId = R.drawable.conf_callee_selector_incoming;
+ } else if (currentlyActiveCall) {
+ bgDrawableId = R.drawable.conf_callee_selector_active;
+ } else if (isInConference) {
+ bgDrawableId = R.drawable.conf_callee_selector_inconf;
+ }
+ v.setBackgroundResource(bgDrawableId);
+
+ boolean connectionEstablished = state == State.StreamsRunning
+ || state == State.Paused
+ || state == State.PausedByRemote;
+ View confButton = v.findViewById(R.id.merge_to_conference);
+ final boolean showMergeToConf = !isInConference && connectionEstablished
+ && aConferenceIsPossible();
+ setVisibility(confButton, false);
+
+ View unhookCallButton = v.findViewById(R.id.unhook_call);
+ boolean showUnhook = state == State.IncomingReceived;
+ setVisibility(unhookCallButton, showUnhook);
+
+ View terminateCallButton = v.findViewById(R.id.terminate_call);
+ boolean showTerminate = state == State.IncomingReceived;
+ setVisibility(terminateCallButton, showTerminate);
+
+ View pauseButton = v.findViewById(R.id.pause);
+ final boolean showPause = !isInConference
+ && state == State.StreamsRunning;
+ setVisibility(pauseButton, false);
+
+ View resumeButton = v.findViewById(R.id.resume);
+ final boolean showResume = !isInConference
+ && state == State.Paused;
+ setVisibility(resumeButton, false);
+
+ View removeFromConfButton = v.findViewById(R.id.remove_from_conference);
+ setVisibility(removeFromConfButton, false);
+
+ final int numberOfCalls = linphoneCalls.size();
+ boolean showAddVideo = State.StreamsRunning == state && !isInConference
+ && useVideoActivity
+ && Version.isVideoCapable()
+ && LinphoneManager.getInstance().isVideoEnabled();
+ View addVideoButton = v.findViewById(R.id.addVideo);
+ setVisibility(addVideoButton, showAddVideo);
+
+ boolean statusPaused = state== State.Paused || state == State.PausedByRemote;
+ setVisibility(v, R.id.callee_status_paused, statusPaused);
+
+ setVisibility(v, R.id.callee_status_inconf, isInConference);
+
+ final OnClickListener l = new CallActionListener(call);
+ confButton.setOnClickListener(l);
+ terminateCallButton.setOnClickListener(l);
+ pauseButton.setOnClickListener(l);
+ resumeButton.setOnClickListener(l);
+ unhookCallButton.setOnClickListener(l);
+ removeFromConfButton.setOnClickListener(l);
+ addVideoButton.setOnClickListener(l);
+
+ v.setOnClickListener(new OnClickListener() {
+ public void onClick(View v) {
+ View content = getLayoutInflater().inflate(R.layout.conf_choices_dialog, null);
+ Dialog dialog = new AlertDialog.Builder(ConferenceActivity.this).setView(content).create();
+ OnClickListener l = new CallActionListener(call, dialog);
+ enableView(content, R.id.transfer_existing, l, allowTransfers && !isInConference && numberOfCalls >=2);
+ enableView(content, R.id.transfer_new, l, allowTransfers && !isInConference);
+ enableView(content, R.id.remove_from_conference, l, isInConference);
+ enableView(content, R.id.merge_to_conference, l, showMergeToConf);
+ enableView(content, R.id.pause, l,!isInConference && showPause);
+ enableView(content, R.id.resume, l, !isInConference && showResume);
+ enableView(content, R.id.terminate_call, l, true);
+ dialog.show();
+ }
+ });
+
+ ImageView pictureView = (ImageView) v.findViewById(R.id.picture);
+ if (numberOfCalls != 1) {
+ // May be greatly sped up using a drawable cache
+ Uri uri = LinphoneUtils.findUriPictureOfContactAndSetDisplayName(address, getContentResolver());
+ LinphoneUtils.setImagePictureFromUri(ConferenceActivity.this, pictureView, uri, R.drawable.unknown_person);
+ pictureView.setVisibility(VISIBLE);
+ } else {
+ pictureView.setVisibility(GONE);
+ }
+
+ return v;
+ }
+ }
+
+ private String getStateText(State state) {
+ int id;
+ if (state == State.IncomingReceived) {
+ id=R.string.state_incoming_received;
+ } else if (state == State.OutgoingRinging) {
+ id=R.string.state_outgoing_ringing;
+ } else if (state == State.Paused) {
+ id=R.string.state_paused;
+ } else if (state == State.PausedByRemote) {
+ id=R.string.state_paused_by_remote;
+ } else {
+ return "";
+ }
+ return getString(id);
+ }
+
+ private Handler mHandler = new Handler();
+
+ private void updateSimpleControlButtons() {
+ LinphoneCall activeCall = lc().getCurrentCall();
+ View controlLayout = findViewById(R.id.conf_control_buttons);
+ int callNb = lc().getCallsNb();
+ boolean hide = activeCall == null || callNb !=2 || lc().getConferenceSize() > 0;
+ controlLayout.setVisibility(hide ? GONE : VISIBLE);
+ }
+
+ private void tryToStartVideoActivity(LinphoneCall call, State state) {
+ if (State.StreamsRunning == state && call.getCurrentParamsCopy().getVideoEnabled()) {
+ if (call.cameraEnabled() ) {
+ LinphoneActivity.instance().startVideoActivity();
+ } else {
+ Log.i("Not starting video call activity as the camera is disabled");
+ }
+ }
+ }
+
+ public void onCallStateChanged(final LinphoneCall call, final State state,
+ final String message) {
+ final String stateStr = call + " " + state.toString();
+ Log.d("ConferenceActivity received state ",stateStr);
+
+ tryToStartVideoActivity(call, state);
+
+ mHandler.post(new Runnable() {
+ public void run() {
+ CalleeListAdapter adapter = (CalleeListAdapter) getListAdapter();
+ Log.d("ConferenceActivity applying state ",stateStr);
+ updateSimpleControlButtons();
+ updateCalleeImage();
+ if (state == State.IncomingReceived || state == State.OutgoingRinging) {
+ if (!adapter.linphoneCalls.contains(call)) {
+ adapter.linphoneCalls.add(call);
+ Collections.sort(adapter.linphoneCalls, ConferenceActivity.this);
+ recreateActivity(adapter);
+ } else {
+ Log.e("Call should not be in the call lists : ", stateStr);
+ }
+ } else if (state == State.Paused || state == State.PausedByRemote || state == State.StreamsRunning) {
+ Collections.sort(adapter.linphoneCalls, ConferenceActivity.this);
+ adapter.notifyDataSetChanged();
+ } else if (state == State.CallEnd) {
+ adapter.linphoneCalls.remove(call);
+ Collections.sort(adapter.linphoneCalls, ConferenceActivity.this);
+ updateAddCallButton();
+ recreateActivity(adapter);
+ }
+
+ updateConfState();
+ }
+ });
+ }
+
+ private void recreateActivity(CalleeListAdapter adapter) {
+ adapter.notifyDataSetInvalidated();
+ adapter.notifyDataSetChanged();
+ }
+
+ public int compare(LinphoneCall c1, LinphoneCall c2) {
+ if (c1 == c2)
+ return 0;
+
+ boolean inConfC1 = c1.isInConference();
+ boolean inConfC2 = c2.isInConference();
+ if (inConfC1 && !inConfC2)
+ return -1;
+ if (!inConfC1 && inConfC2)
+ return 1;
+
+ int durationDiff = c2.getDuration() - c1.getDuration();
+ return durationDiff;
+
+ }
+
+ private LinphoneCall callToTransfer;
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (unMuteOnReturnFromUriPicker) {
+ lc().muteMic(false);
+ ((ToggleButton) findViewById(R.id.toggleMuteMic)).setChecked(false);
+ }
+
+ if (resultCode != RESULT_OK) {
+ callToTransfer = null;
+ Toast.makeText(this, R.string.uri_picking_canceled, Toast.LENGTH_LONG).show();
+ eventuallyResumeConfOrCallOnPickerReturn(true);
+ return;
+ }
+
+ String uri = data.getStringExtra(UriPickerActivity.EXTRA_CALLEE_URI);
+ switch (requestCode) {
+ case ID_ADD_CALL:
+ try {
+ lc().invite(uri);
+ eventuallyResumeConfOrCallOnPickerReturn(false);
+ } catch (LinphoneCoreException e) {
+ Log.e(e);
+ Toast.makeText(this, R.string.error_adding_new_call, Toast.LENGTH_LONG).show();
+ }
+ break;
+ case ID_TRANSFER_CALL:
+ lc().transferCall(callToTransfer, uri);
+ // don't re-enter conference if call to transfer from conference
+ boolean doResume = !callToTransfer.isInConference();
+ // don't resume call if it is the call to transfer
+ doResume &= activateCallOnReturnFromUriPicker != callToTransfer;
+ eventuallyResumeConfOrCallOnPickerReturn(doResume);
+ Toast.makeText(this, R.string.transfer_started, Toast.LENGTH_LONG).show();
+ break;
+ default:
+ throw new RuntimeException("unhandled request code " + requestCode);
+ }
+ }
+
+ private void eventuallyResumeConfOrCallOnPickerReturn(boolean doCallConfResuming) {
+ if (doCallConfResuming) {
+ if (activateCallOnReturnFromUriPicker != null) {
+ lc().resumeCall(activateCallOnReturnFromUriPicker);
+ } else if (enterConferenceOnReturnFromUriPicker) {
+ lc().enterConference();
+ }
+ }
+ activateCallOnReturnFromUriPicker = null;
+ enterConferenceOnReturnFromUriPicker = false;
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (LinphoneUtils.onKeyBackGoHome(this, keyCode)) return true;
+ return super.onKeyUp(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (LinphoneUtils.onKeyVolumeSoftAdjust(keyCode)) return true;
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public void onAudioStateChanged(final AudioState state) {
+ mSpeakerButton.post(new Runnable() {
+ @Override
+ public void run() {
+ switch (state) {
+ case SPEAKER:
+ mSpeakerButton.setChecked(true);
+ break;
+ case EARPIECE:
+ mSpeakerButton.setChecked(false);
+ break;
+ default:
+ throw new RuntimeException("Unkown audio state " + state);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onRequestedVideoCallReady(LinphoneCall call) {
+ LinphoneActivity.instance().startVideoActivity();
+ }
+
+ /*
+ * public int compare(LinphoneCall c1, LinphoneCall c2) { if (c1 == c2)
+ * return 0;
+ *
+ * boolean inConfC1 = c1.isInConference(); boolean inConfC2 =
+ * c2.isInConference(); if (inConfC1 && !inConfC2) return -1; if (!inConfC1
+ * && inConfC2) return 1;
+ *
+ * int compUserName =
+ * c1.getRemoteAddress().getUserName().compareToIgnoreCase
+ * (c2.getRemoteAddress().getUserName()); if (inConfC1 && inConfC2) { return
+ * compUserName; }
+ *
+ * // bellow, ringings and incoming int c1State = c1.getState().value(); int
+ * c2State = c2.getState().value();
+ *
+ * boolean c1StateIsEstablishing = c1State == State.IncomingReceived ||
+ * c1State == State.ID_OUTGOING_RINGING; boolean c2StateIsEstablishing =
+ * c2State == State.IncomingReceived || c2State ==
+ * State.ID_OUTGOING_RINGING;
+ *
+ * // Xor only one establishing state if (c1StateIsEstablishing ^
+ * c2StateIsEstablishing) { // below return !c1StateIsEstablishing ? -1 : 1;
+ * }
+ *
+ * // Xor only one paused state if (c1State == State.Paused ^ c2State ==
+ * State.Paused) { return c1State == State.Paused ? -1 : 1; }
+ *
+ * return compUserName; //Duration() - c1.getDuration(); }
+ */
+}
diff --git a/src/org/linphone/ContactHelper.java b/src/org/linphone/ContactHelper.java
new file mode 100644
index 000000000..2ab2bd3e8
--- /dev/null
+++ b/src/org/linphone/ContactHelper.java
@@ -0,0 +1,258 @@
+/*
+ContactHelper.java
+Copyright (C) 2011 Belledonne Communications, Grenoble, France
+
+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.linphone;
+
+import org.linphone.core.LinphoneAddress;
+import org.linphone.mediastream.Version;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.database.Cursor;
+import android.net.Uri;
+import android.telephony.PhoneNumberUtils;
+import android.text.TextUtils;
+
+public final class ContactHelper {
+
+ private String username;
+ private String domain;
+ private ContentResolver resolver;
+
+
+ private Uri foundPhotoUri;
+ public Uri getUri() {
+ return foundPhotoUri;
+ }
+
+
+ private String displayName;
+// public String getDisplayName() {
+// return displayName;
+// }
+
+ private LinphoneAddress address;
+ public ContactHelper(LinphoneAddress address, ContentResolver resolver) {
+ username = address.getUserName();
+ domain = address.getDomain();
+ this.resolver = resolver;
+ this.address = address;
+ }
+
+ public boolean query() {
+ boolean succeeded;
+ if (Version.sdkAboveOrEqual(Version.API06_ECLAIR_201)) {
+ ContactHelperNew helper = new ContactHelperNew();
+ succeeded = helper.queryNewContactAPI();
+ } else {
+ succeeded = queryOldContactAPI();
+ }
+ if (succeeded && !TextUtils.isEmpty(displayName)) {
+ address.setDisplayName(displayName);
+ }
+ return succeeded;
+ }
+
+
+
+ public static boolean testPhotoUri(Cursor c) {
+ if (c == null) return false;
+ if (!c.moveToNext()) {
+ return false;
+ }
+ byte[] data = c.getBlob(0);
+ if (data == null) {
+ // TODO: simplify all this stuff
+ // which is here only to check that the
+ // photoUri really points to some data.
+ // Not retrieving the data now would be better.
+ return false;
+ }
+ return true;
+ }
+
+ public static boolean testPhotoUriAndCloseCursor(Cursor c) {
+ boolean valid = testPhotoUri(c);
+ if (c != null) c.close();
+ return valid;
+ }
+
+ public static boolean testPhotoUri(ContentResolver resolver, Uri photoUriToTest, String photoCol) {
+ Cursor cursor = resolver.query(photoUriToTest, new String[]{photoCol}, null, null, null);
+ return testPhotoUriAndCloseCursor(cursor);
+ }
+
+
+ // OLD API
+ @SuppressWarnings("deprecation")
+ private final boolean queryOldContactAPI() {
+ String normalizedNumber = PhoneNumberUtils.getStrippedReversed(username);
+ if (TextUtils.isEmpty(normalizedNumber)) {
+ // non phone username
+ return false;
+ }
+ String[] projection = {android.provider.Contacts.Phones.PERSON_ID, android.provider.Contacts.Phones.DISPLAY_NAME};
+ String selection = android.provider.Contacts.Phones.NUMBER_KEY + "=" + normalizedNumber;
+ Cursor c = resolver.query(android.provider.Contacts.Phones.CONTENT_URI, projection, selection, null, null);
+ if (c == null) return false;
+
+ while (c.moveToNext()) {
+ long id = c.getLong(c.getColumnIndex(android.provider.Contacts.Phones.PERSON_ID));
+ Uri personUri = ContentUris.withAppendedId(android.provider.Contacts.People.CONTENT_URI, id);
+ Uri potentialPictureUri = Uri.withAppendedPath(personUri, android.provider.Contacts.Photos.CONTENT_DIRECTORY);
+ boolean valid = testPhotoUri(resolver, potentialPictureUri, android.provider.Contacts.Photos.DATA);
+ if (valid) {
+ displayName = c.getString(c.getColumnIndex(android.provider.Contacts.Phones.DISPLAY_NAME));
+ foundPhotoUri = personUri; // hack (not returning pictureUri as it crashes when reading from it)
+ c.close();
+ return true;
+ }
+ }
+ c.close();
+ return false;
+ }
+
+ // END OLD API
+
+
+
+
+
+
+
+
+
+
+
+ // START NEW CONTACT API
+
+ private class ContactHelperNew {
+
+
+
+ private final boolean checkPhotosUris(ContentResolver resolver, Cursor c, String idCol, String nameCol) {
+ if (c == null) return false;
+ while (c.moveToNext()) {
+ long id = c.getLong(c.getColumnIndex(idCol));
+ Uri contactUri = ContentUris.withAppendedId(android.provider.ContactsContract.Contacts.CONTENT_URI, id);
+ Uri photoUri = Uri.withAppendedPath(contactUri, android.provider.ContactsContract.Contacts.Photo.CONTENT_DIRECTORY);
+ if (photoUri == null) {
+ return false;
+ }
+ String[] projection = {android.provider.ContactsContract.CommonDataKinds.Photo.PHOTO};
+ Cursor photoCursor = resolver.query(photoUri, projection, null, null, null);
+ boolean valid = testPhotoUriAndCloseCursor(photoCursor);
+ if (valid) {
+ foundPhotoUri = photoUri;
+ displayName = c.getString(c.getColumnIndex(nameCol));
+ c.close();
+ return true;
+ }
+ }
+ c.close();
+ return false;
+ }
+
+ private final boolean queryNewContactAPI() {
+ String sipUri = username + "@" + domain;
+
+ // Try first using sip field
+ Uri uri = android.provider.ContactsContract.Data.CONTENT_URI;
+ String[] projection = {
+ android.provider.ContactsContract.Data.CONTACT_ID,
+ android.provider.ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME};
+ String selection = new StringBuilder()
+ .append(android.provider.ContactsContract.CommonDataKinds.Im.DATA).append(" = ? AND ")
+ .append(android.provider.ContactsContract.Data.MIMETYPE)
+ .append(" = '")
+ .append(android.provider.ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE)
+ .append("' AND lower(")
+ .append(android.provider.ContactsContract.CommonDataKinds.Im.CUSTOM_PROTOCOL)
+ .append(") = 'sip'").toString();
+ Cursor c = resolver.query(uri, projection, selection, new String[] {sipUri}, null);
+ boolean valid = checkPhotosUris(resolver, c,
+ android.provider.ContactsContract.Data.CONTACT_ID,
+ android.provider.ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME);
+ if (valid) return true;
+
+
+ // Then using custom SIP field
+ if (Version.sdkAboveOrEqual(Version.API09_GINGERBREAD_23)) {
+ selection = new StringBuilder()
+ .append(android.provider.ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS)
+ .append(" = ? AND ")
+ .append(android.provider.ContactsContract.Data.MIMETYPE)
+ .append(" = '")
+ .append(android.provider.ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE)
+ .append("'")
+ .toString();
+ c = resolver.query(uri, projection, selection, new String[] {sipUri}, null);
+ valid = checkPhotosUris(resolver, c,
+ android.provider.ContactsContract.Data.CONTACT_ID,
+ android.provider.ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME);
+ if (valid) return true;
+ }
+
+ // Finally using phone number
+ String normalizedNumber = PhoneNumberUtils.getStrippedReversed(username);
+ if (TextUtils.isEmpty(normalizedNumber)) {
+ // non phone username
+ return false;
+ }
+ Uri lookupUri = Uri.withAppendedPath(android.provider.ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(username));
+ projection = new String[]{
+ android.provider.ContactsContract.PhoneLookup._ID,
+ android.provider.ContactsContract.PhoneLookup.NUMBER,
+ android.provider.ContactsContract.PhoneLookup.DISPLAY_NAME};
+ c = resolver.query(lookupUri, projection, null, null, null);
+ while (c.moveToNext()) {
+ long id = c.getLong(c.getColumnIndex(android.provider.ContactsContract.PhoneLookup._ID));
+ String enteredNumber = c.getString(c.getColumnIndex(android.provider.ContactsContract.PhoneLookup.NUMBER));
+ if (!normalizedNumber.equals(PhoneNumberUtils.getStrippedReversed(enteredNumber))) {
+ continue;
+ }
+
+ Uri contactUri = ContentUris.withAppendedId(android.provider.ContactsContract.Contacts.CONTENT_URI, id);
+ Uri photoUri = Uri.withAppendedPath(contactUri, android.provider.ContactsContract.Contacts.Photo.CONTENT_DIRECTORY);
+ if (photoUri == null) {
+ continue;
+ }
+ String[] photoProj = {android.provider.ContactsContract.CommonDataKinds.Photo.PHOTO};
+ Cursor cursor = resolver.query(photoUri, photoProj, null, null, null);
+ valid = testPhotoUriAndCloseCursor(cursor);
+ if (valid) {
+ displayName = c.getString(c.getColumnIndex(android.provider.ContactsContract.PhoneLookup.DISPLAY_NAME));
+ foundPhotoUri = photoUri;
+ c.close();
+ return true;
+ }
+ }
+ c.close();
+ return false;
+ }
+
+
+ }
+
+
+
+
+
+
+
+}
diff --git a/src/org/linphone/ContactPickerActivityNew.java b/src/org/linphone/ContactPickerActivityNew.java
index c15789da8..82eafbceb 100644
--- a/src/org/linphone/ContactPickerActivityNew.java
+++ b/src/org/linphone/ContactPickerActivityNew.java
@@ -23,6 +23,7 @@ import java.util.List;
import org.linphone.mediastream.Version;
+import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Intent;
import android.database.Cursor;
@@ -52,35 +53,28 @@ public class ContactPickerActivityNew extends AbstractContactPickerActivity {
@Override
public Uri getPhotoUri(String id) {
- Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, Long.parseLong(id));
+ return retrievePhotoUriAndSetDisplayName(getContentResolver(), Long.parseLong(id));
+ }
+
+ private static Uri retrievePhotoUriAndSetDisplayName(ContentResolver resolver, long id) {
+ Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, id);
Uri photoUri = Uri.withAppendedPath(contactUri, Contacts.Photo.CONTENT_DIRECTORY);
if (photoUri == null) {
return null;
}
- Cursor cursor = getContentResolver().query(photoUri,
- new String[]{ContactsContract.CommonDataKinds.Photo.PHOTO}, null, null, null);
- try {
- if (cursor == null || !cursor.moveToNext()) {
- return null;
- }
- byte[] data = cursor.getBlob(0);
- if (data == null) {
- return null;
- }
+ if (ContactHelper.testPhotoUri(resolver, photoUri, ContactsContract.CommonDataKinds.Photo.PHOTO)) {
return photoUri;
- } finally {
- if (cursor != null) {
- cursor.close();
- }
}
+ return null;
}
protected List extractPhones(String id) {
List list = new ArrayList();
Uri uri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
+ String[] projection = {ContactsContract.CommonDataKinds.Phone.NUMBER};
String selection = ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?";
String[] selArgs = new String[] {id};
- Cursor c = this.getContentResolver().query(uri, null, selection, selArgs, null);
+ Cursor c = this.getContentResolver().query(uri, projection, selection, selArgs, null);
int nbId = c.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
@@ -96,35 +90,41 @@ public class ContactPickerActivityNew extends AbstractContactPickerActivity {
protected List extractSipNumbers(String id) {
List list = new ArrayList();
Uri uri = ContactsContract.Data.CONTENT_URI;
+ String[] projection = {ContactsContract.CommonDataKinds.Im.DATA};
String selection = new StringBuilder()
- .append(ContactsContract.Data.CONTACT_ID).append(" = ? AND ")
- .append(ContactsContract.Data.MIMETYPE).append(" = ? ")
- .append(" AND lower(")
- .append(ContactsContract.CommonDataKinds.Im.CUSTOM_PROTOCOL)
- .append(") = 'sip'").toString();
- String[] selArgs = new String[] {id, ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE};
- Cursor c = this.getContentResolver().query(uri, null, selection, selArgs, null);
+ .append(ContactsContract.Data.CONTACT_ID).append(" = ? AND ")
+ .append(ContactsContract.Data.MIMETYPE).append(" = '")
+ .append(ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE)
+ .append("' AND lower(")
+ .append(ContactsContract.CommonDataKinds.Im.CUSTOM_PROTOCOL)
+ .append(") = 'sip'")
+ .toString();
+ Cursor c = getContentResolver().query(uri, projection, selection, new String[]{id}, null);
int nbId = c.getColumnIndex(ContactsContract.CommonDataKinds.Im.DATA);
-
while (c.moveToNext()) {
list.add("sip:" + c.getString(nbId));
}
-
c.close();
+
// Using the SIP contact field added in SDK 9
if (Version.sdkAboveOrEqual(Version.API09_GINGERBREAD_23)) {
- selection = ContactsContract.Data.CONTACT_ID + " = ? AND " + ContactsContract.Data.MIMETYPE + " = ?";
- String projection[] = new String[] {ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS};
- selArgs = new String[] {id, ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE};
- c = this.getContentResolver().query(uri, projection, selection, selArgs, null);
+ selection = new StringBuilder()
+ .append(ContactsContract.Data.CONTACT_ID)
+ .append(" = ? AND ")
+ .append(ContactsContract.Data.MIMETYPE)
+ .append(" = '")
+ .append(ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE)
+ .append("'")
+ .toString();
+ projection = new String[] {ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS};
+ c = this.getContentResolver().query(uri, projection, selection, new String[]{id}, null);
nbId = c.getColumnIndex(ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS);
while (c.moveToNext()) {
list.add("sip:" + c.getString(nbId));
}
-
c.close();
}
@@ -162,13 +162,14 @@ public class ContactPickerActivityNew extends AbstractContactPickerActivity {
private String retrieveContactName(String id) {
//Uri uri = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
Uri uri = ContactsContract.Contacts.CONTENT_URI;
+ String[] projection = new String[] {ContactsContract.Contacts.DISPLAY_NAME};
String selection = ContactsContract.Contacts._ID + " = ?";
String[] selArgs = new String[] {id};
- Cursor c = this.getContentResolver().query(uri, null, selection, selArgs, null);
+ Cursor c = this.getContentResolver().query(uri, projection, selection, selArgs, null);
String name = "";
if (c.moveToFirst()) {
- name = c.getString(c.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
+ name = c.getString(c.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
}
c.close();
diff --git a/src/org/linphone/ContactPickerActivityOld.java b/src/org/linphone/ContactPickerActivityOld.java
index cf7489816..56347519e 100644
--- a/src/org/linphone/ContactPickerActivityOld.java
+++ b/src/org/linphone/ContactPickerActivityOld.java
@@ -26,7 +26,6 @@ import android.net.Uri;
import android.os.Bundle;
import android.provider.Contacts;
import android.provider.Contacts.People;
-import android.provider.Contacts.Photos;
@SuppressWarnings("deprecation")
public class ContactPickerActivityOld extends Activity {
@@ -48,21 +47,6 @@ public class ContactPickerActivityOld extends Activity {
}
- private Uri getPhotoUri(Uri photoUri) {
- Cursor cursor = getContentResolver().query(photoUri, new String[]{Photos.DATA}, null, null, null);
- try {
- if (cursor == null || !cursor.moveToNext()) {
- return null;
- }
- byte[] data = cursor.getBlob(0);
- if (data == null) {
- return null;
- }
- return photoUri;
- } finally {
- if (cursor != null) cursor.close();
- }
- }
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
@@ -81,8 +65,10 @@ public class ContactPickerActivityOld extends Activity {
String lPhoneNo = lCur.getString(lCur.getColumnIndex(People.NUMBER));
long id = lCur.getLong(lCur.getColumnIndex(People._ID));
Uri personUri = ContentUris.withAppendedId(People.CONTENT_URI, id);
- Uri potentialPictureUri = Uri.withAppendedPath(personUri, Contacts.Photos.CONTENT_DIRECTORY);
- Uri pictureUri = getPhotoUri(potentialPictureUri);
+ Uri pictureUri = Uri.withAppendedPath(personUri, Contacts.Photos.CONTENT_DIRECTORY);
+ if (!ContactHelper.testPhotoUri(getContentResolver(), pictureUri, Contacts.Photos.CONTENT_DIRECTORY)) {
+ pictureUri = null;
+ }
// FIXME surprisingly all this picture stuff doesn't seem to work
DialerActivity.instance().setContactAddress(lPhoneNo, lName, pictureUri);
}
@@ -91,4 +77,5 @@ public class ContactPickerActivityOld extends Activity {
LinphoneActivity.instance().getTabHost().setCurrentTabByTag(LinphoneActivity.DIALER_TAB);
}
}
+
}
diff --git a/src/org/linphone/DialerActivity.java b/src/org/linphone/DialerActivity.java
index 0b6bceebd..70cb5ac8d 100644
--- a/src/org/linphone/DialerActivity.java
+++ b/src/org/linphone/DialerActivity.java
@@ -19,13 +19,14 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
package org.linphone;
import org.linphone.LinphoneManager.NewOutgoingCallUiListener;
+import org.linphone.LinphoneManagerWaitHelper.LinphoneManagerReadyListener;
import org.linphone.LinphoneService.LinphoneGuiListener;
import org.linphone.core.CallDirection;
+import org.linphone.core.LinphoneAddress;
import org.linphone.core.LinphoneCall;
import org.linphone.core.LinphoneCore;
import org.linphone.core.Log;
import org.linphone.core.LinphoneCall.State;
-import org.linphone.mediastream.Version;
import org.linphone.ui.AddVideoButton;
import org.linphone.ui.AddressAware;
import org.linphone.ui.AddressText;
@@ -35,16 +36,15 @@ import org.linphone.ui.HangCallButton;
import org.linphone.ui.MuteMicButton;
import org.linphone.ui.SpeakerButton;
-import android.app.AlertDialog;
+import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.Intent;
-import android.content.SharedPreferences;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.PowerManager;
-import android.preference.PreferenceManager;
+import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.TextView;
@@ -62,7 +62,7 @@ import android.widget.Toast;
*
*
*/
-public class DialerActivity extends LinphoneManagerWaitActivity implements LinphoneGuiListener, NewOutgoingCallUiListener {
+public class DialerActivity extends Activity implements LinphoneGuiListener, LinphoneManagerReadyListener, NewOutgoingCallUiListener, OnClickListener {
private TextView mStatus;
private View mHangup;
@@ -81,16 +81,12 @@ public class DialerActivity extends LinphoneManagerWaitActivity implements Linph
private static DialerActivity instance;
private PowerManager.WakeLock mWakeLock;
- private SharedPreferences mPref;
- private LinphoneCall mCurrentCall;
private boolean useIncallActivity;
- private boolean useVideoActivity;
+ private boolean useConferenceActivity;
private static final String CURRENT_ADDRESS = "org.linphone.current-address";
private static final String CURRENT_DISPLAYNAME = "org.linphone.current-displayname";
- private static final int incomingCallDialogId = 1;
-
/**
* @return null if not ready yet
*/
@@ -98,22 +94,21 @@ public class DialerActivity extends LinphoneManagerWaitActivity implements Linph
return instance;
}
-
+ private LinphoneManagerWaitHelper waitHelper;
public void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.dialer);
useIncallActivity = getResources().getBoolean(R.bool.use_incall_activity);
- useVideoActivity = getResources().getBoolean(R.bool.use_video_activity);
+ useConferenceActivity = getResources().getBoolean(R.bool.use_conference_activity);
// Don't use Linphone Manager in the onCreate as it takes time in LinphoneService to initialize it.
- mPref = PreferenceManager.getDefaultSharedPreferences(this);
- PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
- mWakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK|PowerManager.ON_AFTER_RELEASE,Log.TAG);
+ PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
+ mWakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK|PowerManager.ON_AFTER_RELEASE,Log.TAG+"#"+getClass().getName());
mAddress = (AddressText) findViewById(R.id.SipUri);
mDisplayNameView = (TextView) findViewById(R.id.DisplayNameView);
- ((EraseButton) findViewById(R.id.Erase)).setAddressView(mAddress);
+ ((EraseButton) findViewById(R.id.Erase)).setAddressWidget(mAddress);
mCall = (CallButton) findViewById(R.id.Call);
@@ -121,6 +116,7 @@ public class DialerActivity extends LinphoneManagerWaitActivity implements Linph
mCallControlRow = findViewById(R.id.CallControlRow);
+ mCallControlRow.findViewById(R.id.BackToConference).setOnClickListener(this);
mAddressLayout = findViewById(R.id.Addresslayout);
mInCallControlRow = findViewById(R.id.IncallControlRow);
@@ -128,15 +124,19 @@ public class DialerActivity extends LinphoneManagerWaitActivity implements Linph
mInCallAddressLayout = findViewById(R.id.IncallAddressLayout);
mInCallAddressLayout.setVisibility(View.GONE);
- if (useIncallActivity) {
- mHangup = findViewById(R.id.HangUp);
+ HangCallButton hang = (HangCallButton) findViewById(R.id.HangUp);
+ HangCallButton decline = (HangCallButton) findViewById(R.id.Decline);
+ hang.setTerminateAllCalls(true);
+ decline.setTerminateAllCalls(true);
+
+ if (useConferenceActivity || useIncallActivity) {
+ mHangup = hang;
} else {
mMute = (MuteMicButton) findViewById(R.id.mic_mute_button);
mSpeaker = (SpeakerButton) findViewById(R.id.speaker_button);
- mHangup = findViewById(R.id.Decline);
+ mHangup = decline;
}
-
mStatus = (TextView) findViewById(R.id.status_label);
AddressAware numpad = (AddressAware) findViewById(R.id.Dialer);
@@ -149,18 +149,19 @@ public class DialerActivity extends LinphoneManagerWaitActivity implements Linph
checkIfOutgoingCallIntentReceived();
+ waitHelper = new LinphoneManagerWaitHelper(this, this);
+ waitHelper.doManagerDependentOnCreate();
instance = this;
+ super.onCreate(savedInstanceState);
}
@Override
- protected void onLinphoneManagerAvailable(LinphoneManager m) {
- LinphoneCore lc = LinphoneManager.getLc();
- if (lc.isIncall()) {
- if(lc.isInComingInvitePending()) {
- callPending(lc.getCurrentCall());
- } else {
- enterIncallMode(lc);
- }
+ public void onCreateWhenManagerReady() {
+ LinphoneCall pendingCall = LinphoneManager.getInstance().getPendingIncomingCall();
+ if (pendingCall != null) {
+ LinphoneActivity.instance().startIncomingCallActivity(pendingCall);
+ } else if (LinphoneManager.getLc().isIncall()) {
+ enterIncallMode();
}
}
@@ -221,17 +222,21 @@ public class DialerActivity extends LinphoneManagerWaitActivity implements Linph
- private void enterIncallMode(LinphoneCore lc) {
- mDisplayNameView.setText(LinphoneManager.getInstance().extractADisplayName());
+ private void enterIncallMode() {
+ LinphoneCore lc = LinphoneManager.getLc();
+ LinphoneAddress address = lc.getRemoteAddress();
+ mDisplayNameView.setText(LinphoneManager.extractADisplayName(getResources(), address));
// setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);
- LinphoneActivity.instance().startProxymitySensor();
+ LinphoneManager.startProximitySensorForActivity(LinphoneActivity.instance());
if (!mWakeLock.isHeld()) mWakeLock.acquire();
if (useIncallActivity) {
LinphoneActivity.instance().startIncallActivity(
mDisplayNameView.getText(), mAddress.getPictureUri());
+ } else if (useConferenceActivity) {
+ LinphoneActivity.instance().startConferenceActivity();
} else {
loadMicAndSpeakerUiStateFromManager();
mCallControlRow.setVisibility(View.GONE);
@@ -247,7 +252,8 @@ public class DialerActivity extends LinphoneManagerWaitActivity implements Linph
private void updateIncallVideoCallButton() {
- if (useIncallActivity) throw new RuntimeException("Internal error");
+ if (useIncallActivity || useConferenceActivity)
+ throw new RuntimeException("Internal error");
boolean prefVideoEnabled = LinphoneManager.getInstance().isVideoEnabled();
AddVideoButton mAddVideo = (AddVideoButton) findViewById(R.id.AddVideo);
@@ -262,7 +268,8 @@ public class DialerActivity extends LinphoneManagerWaitActivity implements Linph
private void loadMicAndSpeakerUiStateFromManager() {
- if (useIncallActivity) throw new RuntimeException("Internal error"); // only dialer widgets are updated with this
+ if (useIncallActivity || useConferenceActivity)
+ throw new RuntimeException("Internal error"); // only dialer widgets are updated with this
mMute.setChecked(LinphoneManager.getLc().isMicMuted());
mSpeaker.setSpeakerOn(LinphoneManager.getInstance().isSpeakerOn());
@@ -270,14 +277,11 @@ public class DialerActivity extends LinphoneManagerWaitActivity implements Linph
private void exitCallMode() {
- // Remove dialog if existing
- try {
- dismissDialog(incomingCallDialogId);
- } catch (Throwable e) {/* Exception if never created */}
-
if (useIncallActivity) {
LinphoneActivity.instance().closeIncallActivity();
- } else {
+ } else if(useConferenceActivity) {
+ LinphoneActivity.instance().closeConferenceActivity();
+ }else {
mCallControlRow.setVisibility(View.VISIBLE);
mInCallControlRow.setVisibility(View.GONE);
mInCallAddressLayout.setVisibility(View.GONE);
@@ -289,68 +293,18 @@ public class DialerActivity extends LinphoneManagerWaitActivity implements Linph
mHangup.setEnabled(false);
-
- if (useVideoActivity && LinphoneManager.getLc().isVideoEnabled()) {
- LinphoneActivity.instance().finishVideoActivity();
- BandwidthManager.getInstance().setUserRestriction(false);
- LinphoneManager.getInstance().resetCameraFromPreferences();
- }
-
if (mWakeLock.isHeld()) mWakeLock.release();
- LinphoneActivity.instance().stopProxymitySensor();
+ LinphoneManager.stopProximitySensorForActivity(LinphoneActivity.instance());
setVolumeControlStream(AudioManager.USE_DEFAULT_STREAM_TYPE);
mCall.setEnabled(true);
}
- private void callPending(final LinphoneCall call) {
- showDialog(incomingCallDialogId);
- }
-
- @Override
- protected void onPrepareDialog(int id, Dialog dialog) {
- if (id == incomingCallDialogId) {
- String from = LinphoneManager.getInstance().extractIncomingRemoteName();
- String msg = String.format(getString(R.string.incoming_call_dialog_title), from);
- ((AlertDialog) dialog).setMessage(msg);
- } else {
- super.onPrepareDialog(id, dialog);
- }
- }
-
@Override
protected Dialog onCreateDialog(int id) {
- if (id == incomingCallDialogId) {
- View incomingCallView = getLayoutInflater().inflate(R.layout.incoming_call, null);
-
- final Dialog dialog = new AlertDialog.Builder(this)
- .setMessage("")
- .setCancelable(false)
- .setView(incomingCallView).create();
-
-
- ((CallButton) incomingCallView.findViewById(R.id.Call)).setExternalClickListener(new OnClickListener() {
- public void onClick(View v) {
- dialog.dismiss();
- if (Version.isVideoCapable()) {
- LinphoneManager.getInstance().resetCameraFromPreferences();
-
- // Privacy setting to not share the user camera by default
- boolean prefVideoEnable = LinphoneManager.getInstance().isVideoEnabled();
- int key = R.string.pref_video_automatically_share_my_video_key;
- boolean prefAutoShareMyCamera = mPref.getBoolean(getString(key), false);
- boolean videoMuted = !(prefVideoEnable && prefAutoShareMyCamera);
-
- LinphoneManager.getLc().getCurrentCall().enableCamera(prefAutoShareMyCamera);
- }
- }
- });
- ((HangCallButton) incomingCallView.findViewById(R.id.Decline)).setExternalClickListener(new OnClickListener() {
- public void onClick(View v) {dialog.dismiss();}
- });
-
- return dialog;
+ if (id == LinphoneManagerWaitHelper.DIALOG_ID) {
+ return waitHelper.createWaitDialog();
} else {
return super.onCreateDialog(id);
}
@@ -405,42 +359,32 @@ public class DialerActivity extends LinphoneManagerWaitActivity implements Linph
public void onCallStateChanged(LinphoneCall call, State state, String message) {
- Log.i("OnCallStateChanged: call=", call, ", state=", state, ", message=", message, ", currentCall=", mCurrentCall);
+ Log.i("OnCallStateChanged: call=", call, ", state=", state, ", message=", message);
LinphoneCore lc = LinphoneManager.getLcIfManagerNotDestroyedOrNull();
if (lc==null) {
/* we are certainly exiting, ignore then.*/
return;
}
- if (state==LinphoneCall.State.OutgoingInit){
- mCurrentCall=call;
- call.enableCamera(LinphoneManager.getInstance().shareMyCamera());
- enterIncallMode(lc);
- LinphoneActivity.instance().startOrientationSensor();
- }else if (state==LinphoneCall.State.IncomingReceived){
- mCurrentCall=call;
- callPending(call);
- call.enableCamera(LinphoneManager.getInstance().shareMyCamera());
- LinphoneActivity.instance().startOrientationSensor();
- }else if (state==LinphoneCall.State.Connected){
+ if (state==State.OutgoingInit){
+ enterIncallMode();
+ }else if (state==State.Connected){
if (call.getDirection() == CallDirection.Incoming) {
- enterIncallMode(lc);
+ enterIncallMode();
}
- }else if (state==LinphoneCall.State.Error){
- if (lc.getCurrentCall() == null || lc.getCurrentCall()==call){
+ }else if (state==State.Error){
+ showToast(R.string.call_error, message);
+ if (lc.getCallsNb() == 0){
if (mWakeLock.isHeld()) mWakeLock.release();
- showToast(R.string.call_error, message);
exitCallMode();
- LinphoneActivity.instance().stopOrientationSensor();
- mCurrentCall=null;
}
- }else if (state==LinphoneCall.State.CallEnd){
- if (lc.getCurrentCall() == null || lc.getCurrentCall()==call){
+ }else if (state==State.CallEnd){
+ if (lc.getCallsNb() == 0){
exitCallMode();
- LinphoneActivity.instance().stopOrientationSensor();
- mCurrentCall=null;
}
}
+
+ updateCallControlRow();
}
private void showToast(int id, String txt) {
@@ -456,7 +400,7 @@ public class DialerActivity extends LinphoneManagerWaitActivity implements Linph
public void onGlobalStateChangedToOn(String message) {
mCall.setEnabled(!LinphoneManager.getLc().isIncall());
- if (!useIncallActivity) updateIncallVideoCallButton();
+ if (!useIncallActivity && !useConferenceActivity) updateIncallVideoCallButton();
else mHangup.setEnabled(!mCall.isEnabled());
if (getIntent().getData() != null) {
@@ -476,17 +420,65 @@ public class DialerActivity extends LinphoneManagerWaitActivity implements Linph
}
@Override
- protected void onResume() {
+ public void onResumeWhenManagerReady() {
+ updateCallControlRow();
+
// When coming back from a video call, if the phone orientation is different
// Android will destroy the previous Dialer and create a new one.
// Unfortunately the "call end" status event is received in the meanwhile
// and set to the to be destroyed Dialer.
// Note1: We wait as long as possible before setting the last message.
// Note2: Linphone service is in charge of instantiating LinphoneManager
- if (LinphoneService.isReady()) {
- mStatus.setText(LinphoneManager.getInstance().getLastLcStatusMessage());
+ mStatus.setText(LinphoneManager.getInstance().getLastLcStatusMessage());
+ if (LinphoneManager.getLc().getCallsNb() > 0) {
+ LinphoneManager.startProximitySensorForActivity(LinphoneActivity.instance());
+ // removing is done directly in LinphoneActivity.onPause()
}
+ }
+
+ @Override
+ protected void onResume() {
+ waitHelper.doManagerDependentOnResume();
super.onResume();
}
-
+
+
+ private void updateCallControlRow() {
+ if (useConferenceActivity) {
+ if (LinphoneManager.isInstanciated()) {
+ LinphoneCore lc = LinphoneManager.getLc();
+ int calls = lc.getCallsNb();
+ View backToConf = mCallControlRow.findViewById(R.id.BackToConference);
+ View callButton = mCallControlRow.findViewById(R.id.Call);
+ View hangButton = mCallControlRow.findViewById(R.id.Decline);
+ if (calls > 0) {
+ backToConf.setVisibility(View.VISIBLE);
+ callButton.setVisibility(View.GONE);
+ hangButton.setEnabled(true);
+ } else {
+ backToConf.setVisibility(View.GONE);
+ callButton.setVisibility(View.VISIBLE);
+ hangButton.setEnabled(false);
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (LinphoneUtils.onKeyVolumeSoftAdjust(keyCode)) return true;
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public void onClick(View v) {
+ switch (v.getId()) {
+ case R.id.BackToConference:
+ LinphoneActivity.instance().startConferenceActivity();
+ break;
+ default:
+ break;
+ }
+
+ }
}
diff --git a/src/org/linphone/HistoryActivity.java b/src/org/linphone/HistoryActivity.java
index 4d0bd2ef5..4381a7515 100644
--- a/src/org/linphone/HistoryActivity.java
+++ b/src/org/linphone/HistoryActivity.java
@@ -57,11 +57,12 @@ public class HistoryActivity extends ListActivity {
super.onListItemClick(l, v, position, id);
TextView lFirstLineView = (TextView) v.findViewById(R.id.history_cell_first_line);
TextView lSecondLineView = (TextView) v.findViewById(R.id.history_cell_second_line);
+ ContactPicked parent = (ContactPicked) getParent();
if (lSecondLineView.getVisibility() == View.GONE) {
// no display name
- LinphoneActivity.setAddressAndGoToDialer(lFirstLineView.getText().toString(), null, null);
+ parent.setAddressAndGoToDialer(lFirstLineView.getText().toString(), null, null);
} else {
- LinphoneActivity.setAddressAndGoToDialer(
+ parent.setAddressAndGoToDialer(
lSecondLineView.getText().toString(),
lFirstLineView.getText().toString(),
null);
diff --git a/src/org/linphone/IncallActivity.java b/src/org/linphone/IncallActivity.java
index e108d5c1f..d8faa0899 100644
--- a/src/org/linphone/IncallActivity.java
+++ b/src/org/linphone/IncallActivity.java
@@ -23,7 +23,7 @@ import java.util.TimerTask;
import org.linphone.ui.HangCallButton;
-import android.content.Intent;
+import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
@@ -32,7 +32,11 @@ import android.view.View;
import android.view.View.OnClickListener;
import android.widget.TextView;
-public class IncallActivity extends SoftVolumeActivity implements OnClickListener {
+/**
+ * @author Guillaume Beraudo
+ *
+ */
+public class IncallActivity extends Activity implements OnClickListener {
public static final String CONTACT_KEY = "contact";
public static final String PICTURE_URI_KEY = "picture_uri";
@@ -98,7 +102,7 @@ public class IncallActivity extends SoftVolumeActivity implements OnClickListene
@Override
protected void onResume() {
super.onResume();
-
+ LinphoneManager.startProximitySensorForActivity(this);
task = new TimerTask() {
@Override
public void run() {
@@ -121,6 +125,7 @@ public class IncallActivity extends SoftVolumeActivity implements OnClickListene
@Override
protected void onPause() {
super.onPause();
+ LinphoneManager.stopProximitySensorForActivity(this);
if (task != null) {
task.cancel();
@@ -128,16 +133,15 @@ public class IncallActivity extends SoftVolumeActivity implements OnClickListene
}
}
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (LinphoneUtils.onKeyVolumeSoftAdjust(keyCode)) return true;
+ return super.onKeyDown(keyCode, event);
+ }
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
- if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
- startActivity(new Intent()
- .setAction(Intent.ACTION_MAIN)
- .addCategory(Intent.CATEGORY_HOME));
- return true;
- } else {
- return super.onKeyUp(keyCode, event);
- }
+ if (LinphoneUtils.onKeyBackGoHome(this, keyCode)) return true;
+ return super.onKeyUp(keyCode, event);
}
}
diff --git a/src/org/linphone/IncomingCallActivity.java b/src/org/linphone/IncomingCallActivity.java
new file mode 100644
index 000000000..766e3ffa7
--- /dev/null
+++ b/src/org/linphone/IncomingCallActivity.java
@@ -0,0 +1,176 @@
+/*
+IncomingCallActivity.java
+Copyright (C) 2011 Belledonne Communications, Grenoble, France
+
+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.linphone;
+
+import org.linphone.LinphoneManagerWaitHelper.LinphoneManagerReadyListener;
+import org.linphone.LinphoneSimpleListener.LinphoneOnCallStateChangedListener;
+import org.linphone.core.LinphoneAddress;
+import org.linphone.core.LinphoneCall;
+import org.linphone.core.Log;
+import org.linphone.core.LinphoneCall.State;
+import org.linphone.ui.SlidingTab;
+import org.linphone.ui.SlidingTab.OnTriggerListener;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+/**
+ * Activity displayed when a call comes in.
+ * It should bypass the screen lock mechanism.
+ *
+ * @author Guillaume Beraudo
+ */
+public class IncomingCallActivity extends Activity implements LinphoneManagerReadyListener, LinphoneOnCallStateChangedListener, OnTriggerListener {
+
+ private TextView mNameView;
+ private TextView mNumberView;
+ private ImageView mPictureView;
+ private LinphoneCall mCall;
+ private LinphoneManagerWaitHelper mHelper;
+ private SlidingTab mIncomingCallWidget;
+
+ private void findIncomingCall(Intent intent) {
+ String stringUri = intent.getStringExtra("stringUri");
+ mCall = LinphoneManager.getLc().findCallFromUri(stringUri);
+ if (mCall == null) {
+ Log.e("Couldn't find incoming call from ", stringUri);
+ Toast.makeText(this, "Error", Toast.LENGTH_SHORT);
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ setContentView(R.layout.incoming);
+
+ mNameView = (TextView) findViewById(R.id.incoming_caller_name);
+ mNumberView = (TextView) findViewById(R.id.incoming_caller_number);
+ mPictureView = (ImageView) findViewById(R.id.incoming_picture);
+
+ // set this flag so this activity will stay in front of the keyguard
+ int flags = WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
+ flags |= WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
+ getWindow().addFlags(flags);
+
+
+ // "Dial-to-answer" widget for incoming calls.
+ mIncomingCallWidget = (SlidingTab) findViewById(R.id.sliding_widget);
+
+ // For now, we only need to show two states: answer and decline.
+ // TODO: make left hint work
+// mIncomingCallWidget.setLeftHintText(R.string.slide_to_answer_hint);
+// mIncomingCallWidget.setRightHintText(R.string.slide_to_decline_hint);
+
+ mIncomingCallWidget.setOnTriggerListener(this);
+
+
+ mHelper = new LinphoneManagerWaitHelper(this, this);
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ public void onCreateWhenManagerReady() {}
+
+ @Override
+ public void onResumeWhenManagerReady() {
+ LinphoneManager.addListener(this);
+ findIncomingCall(getIntent());
+ if (mCall == null) {
+ finish();
+ return;
+ }
+ LinphoneAddress address = mCall.getRemoteAddress();
+ // May be greatly sped up using a drawable cache
+ Uri uri = LinphoneUtils.findUriPictureOfContactAndSetDisplayName(address, getContentResolver());
+ LinphoneUtils.setImagePictureFromUri(this, mPictureView, uri, R.drawable.unknown_person);
+
+ // To be done after findUriPictureOfContactAndSetDisplayName called
+ mNameView.setText(address.getDisplayName());
+ if (getResources().getBoolean(R.bool.show_full_remote_address_on_incoming_call)) {
+ mNumberView.setText(address.asStringUriOnly());
+ } else {
+ mNumberView.setText(address.getUserName());
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mHelper.doManagerDependentOnResume();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ LinphoneManager.removeListener(this);
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (LinphoneManager.isInstanciated() && (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_HOME)) {
+ LinphoneManager.getLc().terminateCall(mCall);
+ finish();
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public void onCallStateChanged(LinphoneCall call, State state, String msg) {
+ if (call == mCall && State.CallEnd == state) {
+ finish();
+ }
+ }
+
+ private void decline() {
+ LinphoneManager.getLc().terminateCall(mCall);
+ }
+ private void answer() {
+ if (!LinphoneManager.getInstance().acceptCall(mCall)) {
+ // the above method takes care of Samsung Galaxy S
+ Toast.makeText(this, R.string.couldnt_accept_call, Toast.LENGTH_LONG);
+ }
+ }
+ @Override
+ public void onGrabbedStateChange(View v, int grabbedState) {
+ }
+
+ @Override
+ public void onTrigger(View v, int whichHandle) {
+ switch (whichHandle) {
+ case OnTriggerListener.LEFT_HANDLE:
+ answer();
+ finish();
+ break;
+ case OnTriggerListener.RIGHT_HANDLE:
+ decline();
+ finish();
+ break;
+ default:
+ break;
+ }
+ }
+
+}
diff --git a/src/org/linphone/LinphoneActivity.java b/src/org/linphone/LinphoneActivity.java
index 0cb9efd7f..be968e857 100644
--- a/src/org/linphone/LinphoneActivity.java
+++ b/src/org/linphone/LinphoneActivity.java
@@ -1,5 +1,5 @@
/*
-iLinphoneActivity.java
+LinphoneActivity.java
Copyright (C) 2010 Belledonne Communications, Grenoble, France
This program is free software; you can redistribute it and/or
@@ -21,12 +21,14 @@ package org.linphone;
import static android.content.Intent.ACTION_MAIN;
-import java.util.List;
-
import org.linphone.LinphoneManager.EcCalibrationListener;
+import org.linphone.LinphoneSimpleListener.LinphoneOnCallStateChangedListener;
+import org.linphone.core.LinphoneAddress;
+import org.linphone.core.LinphoneCall;
import org.linphone.core.LinphoneCore;
import org.linphone.core.LinphoneCoreException;
import org.linphone.core.Log;
+import org.linphone.core.LinphoneCall.State;
import org.linphone.core.LinphoneCore.EcCalibratorStatus;
import org.linphone.core.LinphoneCore.RegistrationState;
import org.linphone.mediastream.Version;
@@ -51,33 +53,28 @@ import android.text.Html;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
-import android.view.View;
-import android.view.WindowManager;
-import android.widget.FrameLayout;
import android.widget.TabWidget;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.TabHost.TabSpec;
-public class LinphoneActivity extends TabActivity implements SensorEventListener {
+public class LinphoneActivity extends TabActivity implements SensorEventListener, ContactPicked, LinphoneOnCallStateChangedListener {
public static final String DIALER_TAB = "dialer";
public static final String PREF_FIRST_LAUNCH = "pref_first_launch";
private static final int video_activity = 100;
static final int FIRST_LOGIN_ACTIVITY = 101;
static final int INCALL_ACTIVITY = 102;
static final int INCOMING_CALL_ACTIVITY = 103;
+ private static final int conference_activity = 104;
private static final String PREF_CHECK_CONFIG = "pref_check_config";
private static LinphoneActivity instance;
- private FrameLayout mMainFrame;
private SensorManager mSensorManager;
private Sensor mAccelerometer;
private int previousRotation = -1;
- private static SensorEventListener mSensorEventListener;
- private static final String SCREEN_IS_HIDDEN = "screen_is_hidden";
private Handler mHandler = new Handler();
@@ -98,14 +95,9 @@ public class LinphoneActivity extends TabActivity implements SensorEventListener
throw new RuntimeException("LinphoneActivity not instantiated yet");
}
- protected void onSaveInstanceState (Bundle outState) {
- super.onSaveInstanceState(outState);
- outState.putBoolean(SCREEN_IS_HIDDEN, mMainFrame.getVisibility() == View.INVISIBLE);
- }
-
public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
instance = this;
+ super.onCreate(savedInstanceState);
setContentView(R.layout.main);
LinphonePreferenceManager.getInstance(this);
@@ -118,7 +110,6 @@ public class LinphoneActivity extends TabActivity implements SensorEventListener
startService(new Intent(ACTION_MAIN).setClass(this, LinphoneService.class));
- mMainFrame = (FrameLayout) findViewById(R.id.main_frame);
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
@@ -138,18 +129,16 @@ public class LinphoneActivity extends TabActivity implements SensorEventListener
checkAccount = false;
}
}
-
- if (savedInstanceState !=null && savedInstanceState.getBoolean(SCREEN_IS_HIDDEN,false)) {
- hideScreen(true);
- }
+
+ LinphoneManager.addListener(this);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-
- if (requestCode == FIRST_LOGIN_ACTIVITY) {
+ switch (requestCode) {
+ case FIRST_LOGIN_ACTIVITY:
if (resultCode == RESULT_OK) {
Toast.makeText(this, getString(R.string.ec_calibration_launch_message), Toast.LENGTH_LONG).show();
try {
@@ -170,6 +159,11 @@ public class LinphoneActivity extends TabActivity implements SensorEventListener
finish();
stopService(new Intent(ACTION_MAIN).setClass(this, LinphoneService.class));
}
+ break;
+ case conference_activity:
+ break;
+ default:
+ break;
}
super.onActivityResult(requestCode, resultCode, data);
@@ -217,7 +211,10 @@ public class LinphoneActivity extends TabActivity implements SensorEventListener
gotToDialer();
} else {
if (getResources().getBoolean(R.bool.use_incall_activity)) {
- startIncallActivity(LinphoneManager.getInstance().extractADisplayName(), null);
+ LinphoneAddress address = LinphoneManager.getLc().getRemoteAddress();
+ startIncallActivity(LinphoneManager.extractADisplayName(getResources(), address), null);
+ } if (getResources().getBoolean(R.bool.use_conference_activity)) {
+ startConferenceActivity();
} else {
// TODO
Log.e("Not handled case: recreation while in call and not using incall activity");
@@ -257,11 +254,12 @@ public class LinphoneActivity extends TabActivity implements SensorEventListener
@Override
protected void onPause() {
super.onPause();
-
if (isFinishing()) {
- //restore audio settings
- LinphoneManager.getInstance().routeAudioToReceiver();
- stopProxymitySensor();//just in case
+ //restore audio settings
+ boolean isUserRequest = false;
+ LinphoneManager.getInstance().routeAudioToReceiver(isUserRequest);
+ LinphoneManager.removeListener(this);
+ LinphoneManager.stopProximitySensorForActivity(this);
instance = null;
}
}
@@ -318,39 +316,12 @@ public class LinphoneActivity extends TabActivity implements SensorEventListener
}
-
- void hideScreen(boolean isHidden) {
- WindowManager.LayoutParams lAttrs =getWindow().getAttributes();
- if (isHidden) {
- lAttrs.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN;
- mMainFrame.setVisibility(View.INVISIBLE);
- } else {
- lAttrs.flags &= (~WindowManager.LayoutParams.FLAG_FULLSCREEN);
- mMainFrame.setVisibility(View.VISIBLE);
- }
- getWindow().setAttributes(lAttrs);
+ public synchronized void stopOrientationSensor() {
+ if (mSensorManager!=null)
+ mSensorManager.unregisterListener(this, mAccelerometer);
}
- synchronized void startProxymitySensor() {
- if (mSensorEventListener != null) {
- Log.i("proximity sensor already active");
- return;
- }
- List lSensorList = mSensorManager.getSensorList(Sensor.TYPE_PROXIMITY);
- mSensorEventListener = new SensorEventListener() {
- public void onSensorChanged(SensorEvent event) {
- //just ignoring for nexus 1
- if (event.timestamp == 0) return;
- instance().hideScreen(LinphoneManager.isProximitySensorNearby(event));
- }
-
- public void onAccuracyChanged(Sensor sensor, int accuracy) {}
- };
- if (lSensorList.size() >0) {
- mSensorManager.registerListener(mSensorEventListener,lSensorList.get(0),SensorManager.SENSOR_DELAY_UI);
- Log.i("Proximity sensor detected, registering");
- }
- }
+
public synchronized void startOrientationSensor() {
if (mSensorManager!=null) {
@@ -362,20 +333,6 @@ public class LinphoneActivity extends TabActivity implements SensorEventListener
previousRotation = -1;
onSensorChanged(null);
}
-
- public synchronized void stopOrientationSensor() {
- if (mSensorManager!=null)
- mSensorManager.unregisterListener(this, mAccelerometer);
- }
-
- protected synchronized void stopProxymitySensor() {
- if (mSensorManager!=null) {
- mSensorManager.unregisterListener(mSensorEventListener);
- mSensorEventListener=null;
- }
- hideScreen(false);
- }
-
void showPreferenceErrorDialog(String message) {
if (!useMenuSettings) {
@@ -471,7 +428,7 @@ public class LinphoneActivity extends TabActivity implements SensorEventListener
builder.create().show();
}
- static void setAddressAndGoToDialer(String number, String name, Uri photo) {
+ public void setAddressAndGoToDialer(String number, String name, Uri photo) {
DialerActivity.instance().setContactAddress(number, name, photo);
instance.gotToDialer();
}
@@ -500,9 +457,11 @@ public class LinphoneActivity extends TabActivity implements SensorEventListener
public void closeIncallActivity() {
finishActivity(INCALL_ACTIVITY);
}
+ public void closeConferenceActivity() {
+ finishActivity(conference_activity);
+ }
public void startVideoActivity() {
-
mHandler.post(new Runnable() {
public void run() {
startActivityForResult(new Intent().setClass(
@@ -511,9 +470,32 @@ public class LinphoneActivity extends TabActivity implements SensorEventListener
video_activity);
}
});
- LinphoneManager.getInstance().routeAudioToSpeaker();
+ boolean isUserRequest = false;
+ LinphoneManager.getInstance().routeAudioToSpeaker(isUserRequest);
}
-
+
+ public void startConferenceActivity() {
+ if (ConferenceActivity.active) {
+ return;
+ }
+
+ mHandler.post(new Runnable() {
+ public void run() {
+ startActivityForResult(new Intent().setClass(
+ LinphoneActivity.this,
+ ConferenceActivity.class),
+ conference_activity);
+ }
+ });
+ }
+
+ public void startIncomingCallActivity(LinphoneCall pendingCall) {
+ Intent intent = new Intent()
+ .setClass(this, IncomingCallActivity.class)
+ .putExtra("stringUri", pendingCall.getRemoteAddress().asStringUriOnly());
+ startActivityForResult(intent, INCOMING_CALL_ACTIVITY);
+ }
+
public void finishVideoActivity() {
mHandler.post(new Runnable() {
public void run() {
@@ -521,4 +503,25 @@ public class LinphoneActivity extends TabActivity implements SensorEventListener
}
});
}
+
+ @Override
+ public void onCallStateChanged(LinphoneCall call, State state,
+ String message) {
+ if (state==State.IncomingReceived) {
+ startIncomingCallActivity(call);
+ }
+ if (state==State.OutgoingInit || state==State.IncomingReceived) {
+ startOrientationSensor();
+ } else if (state==State.Error || state==State.CallEnd){
+ stopOrientationSensor();
+ finishActivity(INCOMING_CALL_ACTIVITY);
+ }
+
+ }
+}
+
+
+
+interface ContactPicked {
+ void setAddressAndGoToDialer(String number, String name, Uri photo);
}
diff --git a/src/org/linphone/LinphoneManager.java b/src/org/linphone/LinphoneManager.java
index 0ee22cace..65e475e36 100644
--- a/src/org/linphone/LinphoneManager.java
+++ b/src/org/linphone/LinphoneManager.java
@@ -39,9 +39,17 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
+import org.linphone.LinphoneSimpleListener.LinphoneOnAudioChangedListener;
+import org.linphone.LinphoneSimpleListener.LinphoneServiceListener;
+import org.linphone.LinphoneSimpleListener.LinphoneOnAudioChangedListener.AudioState;
+import org.linphone.core.CallDirection;
import org.linphone.core.LinphoneAddress;
import org.linphone.core.LinphoneAuthInfo;
import org.linphone.core.LinphoneCall;
@@ -66,6 +74,7 @@ import org.linphone.mediastream.video.capture.hwconf.AndroidCameraConfiguration;
import org.linphone.mediastream.video.capture.hwconf.Hacks;
import org.linphone.mediastream.video.capture.hwconf.AndroidCameraConfiguration.AndroidCamera;
+import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -74,18 +83,25 @@ import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.res.Resources;
+import android.hardware.Sensor;
import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
-import android.os.Build;
import android.os.PowerManager;
import android.os.Vibrator;
import android.os.PowerManager.WakeLock;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
+import android.telephony.TelephonyManager;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
/**
*
@@ -116,7 +132,19 @@ public final class LinphoneManager implements LinphoneCoreListener {
private String basePath;
private static boolean sExited;
-
+ private WakeLock mIncallWakeLock;
+
+ private static List simpleListeners = new ArrayList();
+ public static void addListener(LinphoneSimpleListener listener) {
+ if (!simpleListeners.contains(listener)) {
+ simpleListeners.add(listener);
+ }
+ }
+ public static void removeListener(LinphoneSimpleListener listener) {
+ simpleListeners.remove(listener);
+ }
+
+
private LinphoneManager(final Context c) {
sExited=false;
basePath = c.getFilesDir().getAbsolutePath();
@@ -132,6 +160,8 @@ public final class LinphoneManager implements LinphoneCoreListener {
mPref = PreferenceManager.getDefaultSharedPreferences(c);
mPowerManager = (PowerManager) c.getSystemService(Context.POWER_SERVICE);
mR = c.getResources();
+ TelephonyManager tm = (TelephonyManager) c.getSystemService(Context.TELEPHONY_SERVICE);
+ gsmIdle = tm.getCallState() == TelephonyManager.CALL_STATE_IDLE;
}
private static final int LINPHONE_VOLUME_STREAM = STREAM_VOICE_CALL;
@@ -153,6 +183,12 @@ public final class LinphoneManager implements LinphoneCoreListener {
getInstance().routeAudioToSpeakerHelperHelper(speakerOn);
}
private void routeAudioToSpeakerHelperHelper(boolean speakerOn) {
+ boolean different = isSpeakerOn() ^ speakerOn;
+ if (!different) {
+ Log.d("Skipping change audio route by the same route ",
+ speakerOn ? "speaker" : "earpiece");
+ return;
+ }
if (Hacks.needGalaxySAudioHack() || lpm.useGalaxySHack())
setAudioModeIncallForGalaxyS();
@@ -167,6 +203,9 @@ public final class LinphoneManager implements LinphoneCoreListener {
} else {
mAudioManager.setSpeakerphoneOn(speakerOn);
}
+ for (LinphoneOnAudioChangedListener listener : getSimpleListeners(LinphoneOnAudioChangedListener.class)) {
+ listener.onAudioStateChanged(speakerOn ? AudioState.SPEAKER : AudioState.EARPIECE);
+ }
}
private synchronized void routeAudioToSpeakerHelper(boolean speakerOn) {
final LinphoneCall call = mLc.getCurrentCall();
@@ -178,8 +217,24 @@ public final class LinphoneManager implements LinphoneCoreListener {
}
}
-
- public void routeAudioToSpeaker() {
+ private static boolean sUserRequestedSpeaker;
+ public static final boolean isUserRequestedSpeaker() {return sUserRequestedSpeaker;}
+
+ public void restoreUserRequestedSpeaker() {
+ if (sUserRequestedSpeaker) {
+ routeAudioToSpeaker(false);
+ } else {
+ routeAudioToReceiver(false);
+ }
+ }
+ /**
+ *
+ * @param isUserRequest true if the setting is permanent, otherwise it can be lost
+ * eg: video activity imply speaker on, which is not a request from the user.
+ * when the activity stops, the sound is routed to the previously user requested route.
+ */
+ public void routeAudioToSpeaker(boolean isUserRequest) {
+ if (isUserRequest) sUserRequestedSpeaker = true;
routeAudioToSpeakerHelper(true);
if (mLc.isIncall()) {
/*disable EC, it is not efficient enough on speaker mode due to bad quality of speakers and saturation*/
@@ -190,7 +245,14 @@ public final class LinphoneManager implements LinphoneCoreListener {
}
- public void routeAudioToReceiver() {
+ /**
+ *
+ * @param isUserRequest true if the setting is permanent, otherwise it can be lost
+ * eg: video activity imply speaker on, which is not a request from the user.
+ * when the activity stops, the sound is routed to the previously user requested route.
+ */
+ public void routeAudioToReceiver(boolean isUserRequest) {
+ if (isUserRequest) sUserRequestedSpeaker = false;
routeAudioToSpeakerHelper(false);
if (mLc.isIncall()) {
//Restore default value
@@ -203,21 +265,21 @@ public final class LinphoneManager implements LinphoneCoreListener {
}
}
}
-
+
public synchronized static final LinphoneManager createAndStart(
Context c, LinphoneServiceListener listener) {
if (instance != null)
throw new RuntimeException("Linphone Manager is already initialized");
instance = new LinphoneManager(c);
- instance.serviceListener = listener;
+ instance.listenerDispatcher.setServiceListener(listener);
instance.startLibLinphone(c);
if (Version.isVideoCapable())
AndroidVideoApi5JniWrapper.setAndroidSdkVersion(Version.sdk());
return instance;
}
-
+
public static synchronized final LinphoneManager getInstance() {
if (instance != null) return instance;
@@ -236,23 +298,26 @@ public final class LinphoneManager implements LinphoneCoreListener {
public boolean isSpeakerOn() {
- return (Integer.parseInt(Build.VERSION.SDK) <=4 && mAudioManager.getRouting(MODE_NORMAL) == ROUTE_SPEAKER)
- || Integer.parseInt(Build.VERSION.SDK) >4 &&mAudioManager.isSpeakerphoneOn();
+ if (Hacks.needRoutingAPI() || lpm.useAudioRoutingAPIHack()) {
+ return mAudioManager.getRouting(MODE_NORMAL) == ROUTE_SPEAKER;
+ } else {
+ return mAudioManager.isSpeakerphoneOn();
+ }
}
public void newOutgoingCall(AddressType address) {
String to = address.getText().toString();
- if (mLc.isIncall()) {
- serviceListener.tryingNewOutgoingCallButAlreadyInCall();
- return;
- }
+// if (mLc.isIncall()) {
+// listenerDispatcher.tryingNewOutgoingCallButAlreadyInCall();
+// return;
+// }
LinphoneAddress lAddress;
try {
lAddress = mLc.interpretUrl(to);
} catch (LinphoneCoreException e) {
- serviceListener.tryingNewOutgoingCallButWrongDestinationAddress();
+ listenerDispatcher.tryingNewOutgoingCallButWrongDestinationAddress();
return;
}
lAddress.setDisplayName(address.getDisplayedName());
@@ -269,7 +334,7 @@ public final class LinphoneManager implements LinphoneCoreListener {
} catch (LinphoneCoreException e) {
- serviceListener.tryingNewOutgoingCallButCannotGetCallParameters();
+ listenerDispatcher.tryingNewOutgoingCallButCannotGetCallParameters();
return;
}
}
@@ -646,25 +711,11 @@ public final class LinphoneManager implements LinphoneCoreListener {
- public interface LinphoneServiceListener {
- void onGlobalStateChanged(GlobalState state, String message);
- void tryingNewOutgoingCallButCannotGetCallParameters();
- void tryingNewOutgoingCallButWrongDestinationAddress();
- void tryingNewOutgoingCallButAlreadyInCall();
- void onRegistrationStateChanged(RegistrationState state, String message);
- void onCallStateChanged(LinphoneCall call, State state, String message);
- void onRingerPlayerCreated(MediaPlayer mRingerPlayer);
- void onDisplayStatus(String message);
- void onAlreadyInVideoCall();
- void onCallEncryptionChanged(LinphoneCall call, boolean encrypted,
- String authenticationToken);
- }
-
public interface EcCalibrationListener {
void onEcCalibrationStatus(EcCalibratorStatus status, int delayMs);
}
- private LinphoneServiceListener serviceListener;
+ private ListenerDispatcher listenerDispatcher = new ListenerDispatcher();
private LinphoneCall ringingCall;
private MediaPlayer mRingerPlayer;
@@ -689,35 +740,37 @@ public final class LinphoneManager implements LinphoneCoreListener {
public void displayStatus(final LinphoneCore lc, final String message) {
Log.i(message);
lastLcStatusMessage=message;
- serviceListener.onDisplayStatus(message);
+ listenerDispatcher.onDisplayStatus(message);
}
public void globalState(final LinphoneCore lc, final LinphoneCore.GlobalState state, final String message) {
Log.i("new state [",state,"]");
- serviceListener.onGlobalStateChanged(state, message);
+ listenerDispatcher.onGlobalStateChanged(state, message);
}
public void registrationState(final LinphoneCore lc, final LinphoneProxyConfig cfg,final LinphoneCore.RegistrationState state,final String message) {
Log.i("new state ["+state+"]");
- serviceListener.onRegistrationStateChanged(state, message);
+ listenerDispatcher.onRegistrationStateChanged(state, message);
}
-
+ public static boolean gsmIdle;
public void callState(final LinphoneCore lc,final LinphoneCall call, final State state, final String message) {
Log.i("new state [",state,"]");
if (state == IncomingReceived && !call.equals(lc.getCurrentCall())) {
- if (call.getReplacedCall()==null){
- //no multicall support, just decline
- lc.terminateCall(call);
- }//otherwise it will be accepted automatically.
-
- return;
+ if (call.getReplacedCall()!=null){
+ // attended transfer
+ // it will be accepted automatically.
+ return;
+ }
}
if (state == IncomingReceived) {
+ if (!gsmIdle) {
+ mLc.terminateCall(call);
+ }
// Brighten screen for at least 10 seconds
WakeLock wl = mPowerManager.newWakeLock(
PowerManager.ACQUIRE_CAUSES_WAKEUP
@@ -726,15 +779,16 @@ public final class LinphoneManager implements LinphoneCoreListener {
"incoming_call");
wl.acquire(10000);
- startRinging();
- ringingCall = call;
- } else if (call == ringingCall) {
+ if (mLc.getCallsNb() == 1) {
+ ringingCall = call;
+ startRinging();
+ // otherwise there is the beep
+ }
+ } else if (call == ringingCall && isRinging) {
//previous state was ringing, so stop ringing
stopRinging();
- ringingCall = null;
- routeAudioToReceiver();
}
-
+
if (state == CallEnd || state == Error) {
mAudioManager.setMode(MODE_NORMAL);
}
@@ -745,12 +799,33 @@ public final class LinphoneManager implements LinphoneCoreListener {
}
}
- serviceListener.onCallStateChanged(call, state, message);
+ if (state == CallEnd) {
+ if (mLc.getCallsNb() == 0) {
+ if (mIncallWakeLock != null && mIncallWakeLock.isHeld()) {
+ mIncallWakeLock.release();
+ Log.i("Last call ended: releasing incall (CPU only) wake lock");
+ } else {
+ Log.i("Last call ended: no incall (CPU only) wake lock were held");
+ }
+ }
+ }
+ if (state == State.StreamsRunning) {
+ if (mIncallWakeLock == null) {
+ mIncallWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "incall");
+ }
+ if (!mIncallWakeLock.isHeld()) {
+ Log.i("New call active : acquiring incall (CPU only) wake lock");
+ mIncallWakeLock.acquire();
+ } else {
+ Log.i("New call active while incall (CPU only) wake lock already active");
+ }
+ }
+ listenerDispatcher.onCallStateChanged(call, state, message);
}
public void callEncryptionChanged(LinphoneCore lc, LinphoneCall call,
boolean encrypted, String authenticationToken) {
- serviceListener.onCallEncryptionChanged(call, encrypted, authenticationToken);
+ listenerDispatcher.onCallEncryptionChanged(call, encrypted, authenticationToken);
}
public void ecCalibrationStatus(final LinphoneCore lc,final EcCalibratorStatus status, final int delayMs,
@@ -779,6 +854,7 @@ public final class LinphoneManager implements LinphoneCoreListener {
+ private boolean isRinging;
private synchronized void startRinging() {
if (Hacks.needGalaxySAudioHack()) {
mAudioManager.setMode(MODE_RINGTONE);
@@ -792,7 +868,7 @@ public final class LinphoneManager implements LinphoneCoreListener {
if (mRingerPlayer == null) {
mRingerPlayer = new MediaPlayer();
mRingerPlayer.setAudioStreamType(STREAM_RING);
- serviceListener.onRingerPlayerCreated(mRingerPlayer);
+ listenerDispatcher.onRingerPlayerCreated(mRingerPlayer);
mRingerPlayer.prepare();
mRingerPlayer.setLooping(true);
mRingerPlayer.start();
@@ -802,7 +878,7 @@ public final class LinphoneManager implements LinphoneCoreListener {
} catch (Exception e) {
Log.e(e,"cannot handle incoming call");
}
-
+ isRinging = true;
}
private synchronized void stopRinging() {
@@ -815,25 +891,27 @@ public final class LinphoneManager implements LinphoneCoreListener {
mVibrator.cancel();
}
+ isRinging = false;
// You may need to call galaxys audio hack after this method
+ boolean isUserRequest = false;
+ routeAudioToReceiver(isUserRequest);
}
- public String extractADisplayName() {
- final LinphoneAddress remote = mLc.getRemoteAddress();
- if (remote == null) return mR.getString(R.string.unknown_incoming_call_name);
+ public static String extractADisplayName(Resources r, LinphoneAddress address) {
+ if (address == null) return r.getString(R.string.unknown_incoming_call_name);
- final String displayName = remote.getDisplayName();
+ final String displayName = address.getDisplayName();
if (displayName!=null) {
return displayName;
- } else if (remote.getUserName() != null){
- return remote.getUserName();
+ } else if (address.getUserName() != null){
+ return address.getUserName();
} else {
- String rms = remote.toString();
+ String rms = address.toString();
if (rms != null && rms.length() > 1)
return rms;
- return mR.getString(R.string.unknown_incoming_call_name);
+ return r.getString(R.string.unknown_incoming_call_name);
}
}
@@ -846,13 +924,11 @@ public final class LinphoneManager implements LinphoneCoreListener {
}
public boolean shareMyCamera() {
- return mPref.getBoolean(getString(R.string.pref_video_automatically_share_my_video_key), false);
+ return isVideoEnabled() && mPref.getBoolean(getString(R.string.pref_video_automatically_share_my_video_key), false);
}
public void setAudioModeIncallForGalaxyS() {
- stopRinging();
mAudioManager.setMode(MODE_IN_CALL);
- ringingCall = null;
}
// Called on first launch only
@@ -884,13 +960,17 @@ public final class LinphoneManager implements LinphoneCoreListener {
e.commit();
}
- public void addVideo() {
- if (!LinphoneManager.getLc().isIncall()) return;
- if (!reinviteWithVideo()) {
- serviceListener.onAlreadyInVideoCall();
- }
- }
+ private LinphoneCall requestedVideoCall;
+ public boolean addVideo() {
+ requestedVideoCall = mLc.getCurrentCall();
+ if (requestedVideoCall == null) return false;
+ if (!reinviteWithVideo()) {
+ listenerDispatcher.onAlreadyInVideoCall();
+ }
+ return true;
+ }
+
public boolean acceptCallIfIncomingPending() throws LinphoneCoreException {
if (Hacks.needGalaxySAudioHack() || lpm.useGalaxySHack())
setAudioModeIncallForGalaxyS();
@@ -902,15 +982,27 @@ public final class LinphoneManager implements LinphoneCoreListener {
return false;
}
- public String extractIncomingRemoteName() {
- if (!mR.getBoolean(R.bool.show_full_remote_address_on_incoming_call))
- return extractADisplayName();
+ public boolean acceptCall(LinphoneCall call) {
+ if (Hacks.needGalaxySAudioHack() || lpm.useGalaxySHack())
+ setAudioModeIncallForGalaxyS();
- LinphoneAddress remote = mLc.getRemoteAddress();
- if (remote != null)
- return remote.toString();
+ try {
+ mLc.acceptCall(call);
+ return true;
+ } catch (LinphoneCoreException e) {
+ Log.i(e, "Accept call failed");
+ }
+ return false;
+ }
- return mR.getString(R.string.unknown_incoming_call_name);
+ public static String extractIncomingRemoteName(Resources r, LinphoneAddress linphoneAddress) {
+ if (!r.getBoolean(R.bool.show_full_remote_address_on_incoming_call))
+ return extractADisplayName(r, linphoneAddress);
+
+ if (linphoneAddress != null)
+ return linphoneAddress.asStringUriOnly();
+
+ return r.getString(R.string.unknown_incoming_call_name);
}
public void adjustSoftwareVolume(int i) {
@@ -938,6 +1030,79 @@ public final class LinphoneManager implements LinphoneCoreListener {
return distanceInCm < threshold;
}
+
+
+ private static boolean sLastProximitySensorValueNearby;
+ private static Set sProximityDependentActivities = new HashSet();
+ private static SensorEventListener sProximitySensorListener = new SensorEventListener() {
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ if (event.timestamp == 0) return; //just ignoring for nexus 1
+ sLastProximitySensorValueNearby = isProximitySensorNearby(event);
+ proximityNearbyChanged();
+ }
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {}
+ };
+
+
+ private static void hideActivityViewAsIfProximitySensorNearby(Activity activity) {
+ final Window window = activity.getWindow();
+ View view = ((ViewGroup) window.getDecorView().findViewById(android.R.id.content)).getChildAt(0);
+ WindowManager.LayoutParams lAttrs = activity.getWindow().getAttributes();
+ lAttrs.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN;
+ view.setVisibility(View.INVISIBLE);
+ window.setAttributes(lAttrs);
+ }
+
+ private static void proximityNearbyChanged() {
+ boolean nearby = sLastProximitySensorValueNearby;
+ for (Activity activity : sProximityDependentActivities) {
+ final Window window = activity.getWindow();
+ WindowManager.LayoutParams lAttrs = activity.getWindow().getAttributes();
+ View view = ((ViewGroup) window.getDecorView().findViewById(android.R.id.content)).getChildAt(0);
+ if (nearby) {
+ lAttrs.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN;
+ view.setVisibility(View.INVISIBLE);
+ } else {
+ lAttrs.flags &= (~WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ view.setVisibility(View.VISIBLE);
+ }
+ window.setAttributes(lAttrs);
+ }
+ }
+
+ public static synchronized void startProximitySensorForActivity(Activity activity) {
+ if (sProximityDependentActivities.contains(activity)) {
+ Log.i("proximity sensor already active for " + activity.getLocalClassName());
+ return;
+ }
+
+ if (sProximityDependentActivities.isEmpty()) {
+ SensorManager sm = (SensorManager) activity.getSystemService(Context.SENSOR_SERVICE);
+ Sensor s = sm.getDefaultSensor(Sensor.TYPE_PROXIMITY);
+ if (s != null) {
+ sm.registerListener(sProximitySensorListener,s,SensorManager.SENSOR_DELAY_UI);
+ Log.i("Proximity sensor detected, registering");
+ }
+ } else if (sLastProximitySensorValueNearby){
+ hideActivityViewAsIfProximitySensorNearby(activity);
+ }
+
+ sProximityDependentActivities.add(activity);
+ }
+
+ public static synchronized void stopProximitySensorForActivity(Activity activity) {
+ sProximityDependentActivities.remove(activity);
+ if (sProximityDependentActivities.isEmpty()) {
+ SensorManager sm = (SensorManager) activity.getSystemService(Context.SENSOR_SERVICE);
+ sm.unregisterListener(sProximitySensorListener);
+ sLastProximitySensorValueNearby = false;
+ proximityNearbyChanged();
+ }
+ }
+
+
public static synchronized LinphoneCore getLcIfManagerNotDestroyedOrNull() {
if (sExited) {
// Can occur if the UI thread play a posted event but in the meantime the LinphoneManager was destroyed
@@ -948,4 +1113,98 @@ public final class LinphoneManager implements LinphoneCoreListener {
return getLc();
}
+ @SuppressWarnings("unchecked")
+ private List getSimpleListeners(Class clazz) {
+ List list = new ArrayList();
+ for (LinphoneSimpleListener l : simpleListeners) {
+ if (clazz.isInstance(l)) list.add((T) l);
+ }
+ return list;
+ }
+
+ private class ListenerDispatcher implements LinphoneServiceListener {
+ private LinphoneServiceListener serviceListener;
+
+ public void setServiceListener(LinphoneServiceListener s) {
+ this.serviceListener = s;
+ }
+
+ public void onAlreadyInVideoCall() {
+ if (serviceListener != null) serviceListener.onAlreadyInVideoCall();
+ }
+
+ public void onCallEncryptionChanged(LinphoneCall call,
+ boolean encrypted, String authenticationToken) {
+ if (serviceListener != null) serviceListener.onCallEncryptionChanged(call, encrypted, authenticationToken);
+ }
+
+ public void onCallStateChanged(LinphoneCall call, State state,
+ String message) {
+ if (state==State.OutgoingInit || state==State.IncomingReceived) {
+ boolean sendCamera = shareMyCamera() && mLc.getConferenceSize() == 0;
+ call.enableCamera(sendCamera);
+ }
+ if (state == State.CallEnd && call == requestedVideoCall) {
+ requestedVideoCall = null; // drop reference
+ }
+ if (state == State.CallEnd && mLc.getCallsNb() == 0) {
+ routeAudioToReceiver(true);
+ }
+ if (state == State.StreamsRunning && call == requestedVideoCall && call.getCurrentParamsCopy().getVideoEnabled()) {
+ for (LinphoneOnVideoCallReadyListener l : getSimpleListeners(LinphoneOnVideoCallReadyListener.class)) {
+ l.onRequestedVideoCallReady(call);
+ }
+ requestedVideoCall = null;
+ }
+ if (serviceListener != null) serviceListener.onCallStateChanged(call, state, message);
+ for (LinphoneOnCallStateChangedListener l : getSimpleListeners(LinphoneOnCallStateChangedListener.class)) {
+ l.onCallStateChanged(call, state, message);
+ }
+ }
+
+ public void onDisplayStatus(String message) {
+ if (serviceListener != null) serviceListener.onDisplayStatus(message);
+ }
+
+ public void onGlobalStateChanged(GlobalState state, String message) {
+ if (serviceListener != null) serviceListener.onGlobalStateChanged( state, message);
+ }
+
+ public void onRegistrationStateChanged(RegistrationState state,
+ String message) {
+ if (serviceListener != null) serviceListener.onRegistrationStateChanged(state, message);
+ }
+
+ public void onRingerPlayerCreated(MediaPlayer mRingerPlayer) {
+ if (serviceListener != null) serviceListener.onRingerPlayerCreated(mRingerPlayer);
+ }
+
+ public void tryingNewOutgoingCallButAlreadyInCall() {
+ if (serviceListener != null) serviceListener.tryingNewOutgoingCallButAlreadyInCall();
+ }
+
+ public void tryingNewOutgoingCallButCannotGetCallParameters() {
+ if (serviceListener != null) serviceListener.tryingNewOutgoingCallButCannotGetCallParameters();
+ }
+
+ public void tryingNewOutgoingCallButWrongDestinationAddress() {
+ if (serviceListener != null) serviceListener.tryingNewOutgoingCallButWrongDestinationAddress();
+ }
+ }
+
+ public static final boolean isInstanciated() {
+ return instance != null;
+ }
+
+ public synchronized LinphoneCall getPendingIncomingCall() {
+ LinphoneCall currentCall = mLc.getCurrentCall();
+ if (currentCall == null) return null;
+
+ LinphoneCall.State state = currentCall.getState();
+ boolean incomingPending = currentCall.getDirection() == CallDirection.Incoming
+ && (state == State.IncomingReceived || state == State.CallIncomingEarlyMedia);
+
+ return incomingPending ? currentCall : null;
+ }
+
}
diff --git a/src/org/linphone/LinphoneManagerWaitActivity.java b/src/org/linphone/LinphoneManagerWaitActivity.java
deleted file mode 100644
index 3899aefb3..000000000
--- a/src/org/linphone/LinphoneManagerWaitActivity.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
-LinphoneManagerWaitActivity.java
-Copyright (C) 2011 Belledonne Communications, Grenoble, France
-
-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.linphone;
-
-import org.linphone.core.Log;
-
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.os.Bundle;
-import android.os.Handler;
-import android.view.View;
-
-/**
- * Activity requiring access to LinphoneManager should inherit from this class.
- *
- * @author Guillaume Beraudo
- *
- */
-public abstract class LinphoneManagerWaitActivity extends SoftVolumeActivity {
-
- private final int waitServiceDialogId = 314159265;
- private Handler mHandler = new Handler();
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- if (LinphoneService.isReady()) {
- onLinphoneManagerAvailable(LinphoneManager.getInstance());
- } else {
- showDialog(waitServiceDialogId);
- thread = new ServiceWaitThread();
- thread.start();
- }
- }
-
- private ServiceWaitThread thread;
-
- @Override
- protected void onDestroy() {
- if (thread != null) thread.interrupt();
- super.onDestroy();
- }
-
- @Override
- protected Dialog onCreateDialog(int id) {
- if (id == waitServiceDialogId) {
- View v = getLayoutInflater().inflate((R.layout.wait_service_dialog), null);
- return new AlertDialog.Builder(this).setView(v).setCancelable(false).create();
- }
- return super.onCreateDialog(id);
- }
-
- protected abstract void onLinphoneManagerAvailable(LinphoneManager m);
-
- private void dismissDialogFromThread(final int id) {
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- try {
- dismissDialog(id);
- } catch (Throwable e) {
- // Discarding exception which may be thrown if the dialog wasn't showing.
- }
- }
- });
- }
-
- private class ServiceWaitThread extends Thread {
- @Override
- public void run() {
- while (!LinphoneService.isReady()) {
- try {
- sleep(30);
- } catch (InterruptedException e) {
- Log.e("waiting thread sleep() has been interrupted, exiting as requested");
- dismissDialogFromThread(waitServiceDialogId); // FIXME, may not be the best thing to do
- return;
- }
- }
- onLinphoneManagerAvailable(LinphoneManager.getInstance());
- dismissDialogFromThread(waitServiceDialogId);
- super.run();
- }
- }
-
-}
diff --git a/src/org/linphone/LinphoneService.java b/src/org/linphone/LinphoneService.java
index 7e82ea52d..c6742d037 100644
--- a/src/org/linphone/LinphoneService.java
+++ b/src/org/linphone/LinphoneService.java
@@ -19,9 +19,11 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
package org.linphone;
import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
-import org.linphone.LinphoneManager.LinphoneServiceListener;
import org.linphone.LinphoneManager.NewOutgoingCallUiListener;
+import org.linphone.LinphoneSimpleListener.LinphoneServiceListener;
import org.linphone.core.LinphoneCall;
import org.linphone.core.LinphoneCoreFactoryImpl;
import org.linphone.core.Log;
@@ -35,7 +37,6 @@ import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
-import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -69,7 +70,7 @@ public final class LinphoneService extends Service implements LinphoneServiceLis
private Handler mHandler = new Handler();
private static LinphoneService instance;
- static boolean isReady() { return (instance!=null); }
+ public static boolean isReady() { return (instance!=null); }
/**
* @throws RuntimeException service not instantiated
@@ -81,7 +82,6 @@ public final class LinphoneService extends Service implements LinphoneServiceLis
}
- private NotificationManager mNotificationMgr;
private final static int NOTIF_ID=1;
private Notification mNotif;
@@ -101,7 +101,7 @@ public final class LinphoneService extends Service implements LinphoneServiceLis
@Override
public void onCreate() {
super.onCreate();
-
+
// In case restart after a crash. Main in LinphoneActivity
LinphonePreferenceManager.getInstance(this);
@@ -116,7 +116,7 @@ public final class LinphoneService extends Service implements LinphoneServiceLis
dumpDeviceInformation();
dumpInstalledLinphoneInformation();
- mNotificationMgr = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
+ mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
mNotif = new Notification(R.drawable.status_level, "", System.currentTimeMillis());
mNotif.iconLevel=IC_LEVEL_ORANGE;
mNotif.flags |= Notification.FLAG_ONGOING_EVENT;
@@ -124,16 +124,106 @@ public final class LinphoneService extends Service implements LinphoneServiceLis
Intent notifIntent = new Intent(this, LinphoneActivity.class);
mNotifContentIntent = PendingIntent.getActivity(this, 0, notifIntent, 0);
mNotif.setLatestEventInfo(this, notificationTitle,"", mNotifContentIntent);
- mNotificationMgr.notify(NOTIF_ID, mNotif);
-
-
+
LinphoneManager.createAndStart(this, this);
LinphoneManager.getLc().setPresenceInfo(0, null, OnlineStatus.Online);
instance = this; // instance is ready once linphone manager has been created
+
+
+ // Retrieve methods to publish notification and keep Android
+ // from killing us and keep the audio quality high.
+ if (Version.sdkStrictlyBelow(Version.API05_ECLAIR_20)) {
+ try {
+ mSetForeground = getClass().getMethod("setForeground", mSetFgSign);
+ } catch (NoSuchMethodException e) {
+ Log.e(e, "Couldn't find foreground method");
+ }
+ } else {
+ try {
+ mStartForeground = getClass().getMethod("startForeground", mStartFgSign);
+ mStopForeground = getClass().getMethod("stopForeground", mStopFgSign);
+ } catch (NoSuchMethodException e) {
+ Log.e(e, "Couldn't find startGoreground or stopForeground");
+ }
+ }
+
+ startForegroundCompat(NOTIF_ID, mNotif);
}
+ private static final Class>[] mSetFgSign = new Class[] {boolean.class};
+ private static final Class>[] mStartFgSign = new Class[] {
+ int.class, Notification.class};
+ private static final Class>[] mStopFgSign = new Class[] {boolean.class};
+
+ private NotificationManager mNM;
+ private Method mSetForeground;
+ private Method mStartForeground;
+ private Method mStopForeground;
+ private Object[] mSetForegroundArgs = new Object[1];
+ private Object[] mStartForegroundArgs = new Object[2];
+ private Object[] mStopForegroundArgs = new Object[1];
+
+ void invokeMethod(Method method, Object[] args) {
+ try {
+ method.invoke(this, args);
+ } catch (InvocationTargetException e) {
+ // Should not happen.
+ Log.w(e, "Unable to invoke method");
+ } catch (IllegalAccessException e) {
+ // Should not happen.
+ Log.w(e, "Unable to invoke method");
+ }
+ }
+
+ /**
+ * This is a wrapper around the new startForeground method, using the older
+ * APIs if it is not available.
+ */
+ void startForegroundCompat(int id, Notification notification) {
+ // If we have the new startForeground API, then use it.
+ if (mStartForeground != null) {
+ mStartForegroundArgs[0] = Integer.valueOf(id);
+ mStartForegroundArgs[1] = notification;
+ invokeMethod(mStartForeground, mStartForegroundArgs);
+ return;
+ }
+
+ // Fall back on the old API.
+ if (mSetForeground != null) {
+ mSetForegroundArgs[0] = Boolean.TRUE;
+ invokeMethod(mSetForeground, mSetForegroundArgs);
+ // continue
+ }
+
+ mNM.notify(id, notification);
+ }
+
+ /**
+ * This is a wrapper around the new stopForeground method, using the older
+ * APIs if it is not available.
+ */
+ void stopForegroundCompat(int id) {
+ // If we have the new stopForeground API, then use it.
+ if (mStopForeground != null) {
+ mStopForegroundArgs[0] = Boolean.TRUE;
+ invokeMethod(mStopForeground, mStopForegroundArgs);
+ return;
+ }
+
+ // Fall back on the old API. Note to cancel BEFORE changing the
+ // foreground state, since we could be killed at that point.
+ mNM.cancel(id);
+ if (mSetForeground != null) {
+ mSetForegroundArgs[0] = Boolean.FALSE;
+ invokeMethod(mSetForeground, mSetForegroundArgs);
+ }
+ }
+
+
+
+
public static final String START_LINPHONE_LOGS = " ==== Phone information dump ====";
private void dumpDeviceInformation() {
StringBuilder sb = new StringBuilder();
@@ -170,7 +260,7 @@ public final class LinphoneService extends Service implements LinphoneServiceLis
}
mNotif.setLatestEventInfo(this, notificationTitle, text, mNotifContentIntent);
- mNotificationMgr.notify(NOTIF_ID, mNotif);
+ mNM.notify(NOTIF_ID, mNotif);
}
@@ -184,10 +274,11 @@ public final class LinphoneService extends Service implements LinphoneServiceLis
@Override
public void onDestroy() {
super.onDestroy();
+ // Make sure our notification is gone.
+ stopForegroundCompat(NOTIF_ID);
+
LinphoneManager.getLcIfManagerNotDestroyedOrNull().setPresenceInfo(0, null, OnlineStatus.Offline);
LinphoneManager.destroy(this);
-
- mNotificationMgr.cancel(NOTIF_ID);
instance=null;
}
@@ -252,6 +343,7 @@ public final class LinphoneService extends Service implements LinphoneServiceLis
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
} else if (state == LinphoneCall.State.StreamsRunning) {
if (Version.isVideoCapable()
+ && getResources().getBoolean(R.bool.autostart_video_activity)
&& getResources().getBoolean(R.bool.use_video_activity)
&& !VideoCallActivity.launched && LinphoneActivity.isInstanciated()
&& call.getCurrentParamsCopy().getVideoEnabled()) {
diff --git a/src/org/linphone/LinphoneSimpleListener.java b/src/org/linphone/LinphoneSimpleListener.java
new file mode 100644
index 000000000..20439db92
--- /dev/null
+++ b/src/org/linphone/LinphoneSimpleListener.java
@@ -0,0 +1,59 @@
+/*
+LinphoneServiceListener.java
+Copyright (C) 2011 Belledonne Communications, Grenoble, France
+
+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.linphone;
+
+import org.linphone.core.LinphoneCall;
+import org.linphone.core.LinphoneCall.State;
+import org.linphone.core.LinphoneCore.GlobalState;
+import org.linphone.core.LinphoneCore.RegistrationState;
+
+import android.media.MediaPlayer;
+
+public interface LinphoneSimpleListener {
+
+ public static interface LinphoneServiceListener
+ extends LinphoneOnGlobalStateChangedListener, LinphoneOnCallStateChangedListener {
+ void tryingNewOutgoingCallButCannotGetCallParameters();
+ void tryingNewOutgoingCallButWrongDestinationAddress();
+ void tryingNewOutgoingCallButAlreadyInCall();
+ void onRegistrationStateChanged(RegistrationState state, String message);
+ void onRingerPlayerCreated(MediaPlayer mRingerPlayer);
+ void onDisplayStatus(String message);
+ void onCallEncryptionChanged(LinphoneCall call, boolean encrypted, String authenticationToken);
+ void onAlreadyInVideoCall();
+
+ }
+
+ public static interface LinphoneOnGlobalStateChangedListener extends LinphoneSimpleListener {
+ void onGlobalStateChanged(GlobalState state, String message);
+ }
+
+ public static interface LinphoneOnCallStateChangedListener extends LinphoneSimpleListener {
+ void onCallStateChanged(LinphoneCall call, State state, String message);
+ }
+
+ public static interface LinphoneOnAudioChangedListener extends LinphoneSimpleListener {
+ public enum AudioState {EARPIECE, SPEAKER}
+ void onAudioStateChanged(AudioState state);
+ }
+
+ public static interface LinphoneOnVideoCallReadyListener extends LinphoneSimpleListener {
+ void onRequestedVideoCallReady(LinphoneCall call);
+ }
+}
diff --git a/src/org/linphone/LinphoneUtils.java b/src/org/linphone/LinphoneUtils.java
new file mode 100644
index 000000000..7148c29cc
--- /dev/null
+++ b/src/org/linphone/LinphoneUtils.java
@@ -0,0 +1,135 @@
+/*
+SoftVolume.java
+Copyright (C) 2011 Belledonne Communications, Grenoble, France
+
+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.linphone;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.List;
+
+import org.linphone.core.LinphoneAddress;
+import org.linphone.core.LinphoneCall;
+import org.linphone.core.LinphoneCore;
+import org.linphone.core.Log;
+import org.linphone.mediastream.Version;
+import org.linphone.mediastream.video.capture.hwconf.Hacks;
+
+import android.app.Activity;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.view.KeyEvent;
+import android.widget.ImageView;
+
+/**
+ * Helpers.
+ * @author Guillaume Beraudo
+ *
+ */
+public final class LinphoneUtils {
+
+ private LinphoneUtils(){}
+
+ private static boolean preventVolumeBarToDisplay = false;
+
+ public static boolean onKeyBackGoHome(Activity activity, int keyCode) {
+ if (!(keyCode == KeyEvent.KEYCODE_BACK)) {
+ return false; // continue
+ }
+
+ activity.startActivity(new Intent()
+ .setAction(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_HOME));
+ return true;
+ }
+
+ public static boolean onKeyVolumeSoftAdjust(int keyCode) {
+ if (!((keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)
+ && Hacks.needSoftvolume())) {
+ return false; // continue
+ }
+
+ if (!LinphoneService.isReady()) {
+ Log.i("Couldn't change softvolume has service is not running");
+ return true;
+ } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
+ LinphoneManager.getInstance().adjustSoftwareVolume(1);
+ } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
+ LinphoneManager.getInstance().adjustSoftwareVolume(-1);
+ }
+ return preventVolumeBarToDisplay;
+ }
+
+
+ /**
+ * @param contact sip uri
+ * @return url/uri of the resource
+ */
+ public static Uri findUriPictureOfContactAndSetDisplayName(LinphoneAddress address, ContentResolver resolver) {
+ ContactHelper helper = new ContactHelper(address, resolver);
+ helper.query();
+ return helper.getUri();
+ }
+
+ public static Bitmap downloadBitmap(Uri uri) {
+ URL url;
+ InputStream is = null;
+ try {
+ url = new URL(uri.toString());
+ is = url.openStream();
+ return BitmapFactory.decodeStream(is);
+ } catch (MalformedURLException e) {
+ Log.e(e, e.getMessage());
+ } catch (IOException e) {
+ Log.e(e, e.getMessage());
+ } finally {
+ try {is.close();} catch (IOException x) {}
+ }
+ return null;
+ }
+
+ public static void setImagePictureFromUri(Context c, ImageView view, Uri uri, int notFoundResource) {
+ if (uri == null) {
+ view.setImageResource(notFoundResource);
+ return;
+ }
+ if (uri.getScheme().startsWith("http")) {
+ Bitmap bm = downloadBitmap(uri);
+ if (bm == null) view.setImageResource(notFoundResource);
+ view.setImageBitmap(bm);
+ } else {
+ if (Version.sdkAboveOrEqual(Version.API06_ECLAIR_201)) {
+ view.setImageURI(uri);
+ } else {
+ Bitmap bitmap = android.provider.Contacts.People.loadContactPhoto(c, uri, notFoundResource, null);
+ view.setImageBitmap(bitmap);
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static final List getLinphoneCalls(LinphoneCore lc) {
+ return (List) lc.getCalls();
+ }
+}
+
diff --git a/src/org/linphone/PhoneStateChangedReceiver.java b/src/org/linphone/PhoneStateChangedReceiver.java
new file mode 100644
index 000000000..b715cc2d3
--- /dev/null
+++ b/src/org/linphone/PhoneStateChangedReceiver.java
@@ -0,0 +1,57 @@
+/*
+PhoneStateReceiver.java
+Copyright (C) 2011 Belledonne Communications, Grenoble, France
+
+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.linphone;
+
+import org.linphone.core.Log;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.telephony.TelephonyManager;
+
+/**
+ * Pause current SIP calls when GSM phone rings or is active.
+ *
+ * @author Guillaume Beraudo
+ *
+ */
+public class PhoneStateChangedReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+
+
+ final String extraState = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
+
+ if (TelephonyManager.EXTRA_STATE_RINGING.equals(extraState) || TelephonyManager.EXTRA_STATE_OFFHOOK.equals(extraState)) {
+ LinphoneManager.gsmIdle = false;
+ if (LinphoneManager.isInstanciated()) {
+ Log.i("GSM call state changed but manager not instantiated");
+ return;
+ }
+ LinphoneManager.getLc().pauseAllCalls();
+ } else if (TelephonyManager.EXTRA_STATE_IDLE.equals(extraState)) {
+ LinphoneManager.gsmIdle = true;
+ }
+
+
+ // do nothing
+ }
+
+}
diff --git a/src/org/linphone/SoftVolumeActivity.java b/src/org/linphone/SoftVolumeActivity.java
deleted file mode 100644
index af3cb5b25..000000000
--- a/src/org/linphone/SoftVolumeActivity.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
-SoftVolume.java
-Copyright (C) 2011 Belledonne Communications, Grenoble, France
-
-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.linphone;
-
-import org.linphone.core.Log;
-import org.linphone.mediastream.video.capture.hwconf.Hacks;
-
-import android.app.Activity;
-import android.view.KeyEvent;
-
-/**
- * Activity which handles softvolume.
- * @author Guillaume Beraudo
- *
- */
-public class SoftVolumeActivity extends Activity {
-
- private static boolean preventVolumeBarToDisplay = false;
-
-
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)
- && (Hacks.needSoftvolume() || LinphonePreferenceManager.getInstance(this).useSoftvolume())) {
-
- if (!LinphoneService.isReady()) {
- Log.i("Couldn't change softvolume has service is not running");
- return true;
- } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
- LinphoneManager.getInstance().adjustSoftwareVolume(1);
- } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
- LinphoneManager.getInstance().adjustSoftwareVolume(-1);
- }
- }
-
- if (!preventVolumeBarToDisplay) {
- return super.onKeyDown(keyCode, event);
- } else return true;
- }
-
-
-}
-
diff --git a/src/org/linphone/UriPickerActivity.java b/src/org/linphone/UriPickerActivity.java
new file mode 100644
index 000000000..105face5f
--- /dev/null
+++ b/src/org/linphone/UriPickerActivity.java
@@ -0,0 +1,176 @@
+/*
+LinphoneActivity.java
+Copyright (C) 2010 Belledonne Communications, Grenoble, France
+
+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.linphone;
+
+
+import org.linphone.mediastream.Version;
+import org.linphone.ui.AddressAware;
+import org.linphone.ui.AddressText;
+
+import android.app.Activity;
+import android.app.TabActivity;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.TabWidget;
+import android.widget.TabHost.TabSpec;
+
+/**
+ * @author Guillaume Beraudo
+ */
+public class UriPickerActivity extends TabActivity implements ContactPicked {
+ private static final String DIALER_TAB = "dialer";
+ public static final String EXTRA_CALLEE_NAME = "callee_name";
+ public static final String EXTRA_CALLEE_URI = "callee_uri";
+ public static final String EXTRA_CALLEE_PHOTO_URI = "callee_photo_uri";
+ public static final String EXTRA_PICKER_TYPE = "picker_type";
+ public static final String EXTRA_PICKER_TYPE_ADD = "picker_type_add";
+ public static final String EXTRA_PICKER_TYPE_TRANSFER = "picker_type_transfer";
+
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+ fillTabHost();
+ }
+
+
+ private synchronized void fillTabHost() {
+ if (((TabWidget) findViewById(android.R.id.tabs)).getChildCount() != 0) return;
+
+ startActivityInTab("history",
+ new Intent().setClass(this, HistoryActivity.class),
+ R.string.tab_history, R.drawable.history_orange);
+
+
+ startActivityInTab(DIALER_TAB,
+ new Intent().setClass(this, DialerActivity.class).setData(getIntent().getData())
+ .putExtra(EXTRA_PICKER_TYPE, getIntent().getStringExtra(EXTRA_PICKER_TYPE)),
+ R.string.tab_dialer, R.drawable.dialer_orange);
+
+
+ startActivityInTab("contact",
+ new Intent().setClass(this, Version.sdkAboveOrEqual(5) ?
+ ContactPickerActivityNew.class : ContactPickerActivityOld.class),
+ R.string.tab_contact, R.drawable.contact_orange);
+
+
+ gotToDialer();
+ }
+
+
+ private void gotToDialer() {
+ getTabHost().setCurrentTabByTag(DIALER_TAB);
+ }
+
+ private void startActivityInTab(String tag, Intent intent, int indicatorId, int drawableId) {
+ Drawable tabDrawable = getResources().getDrawable(drawableId);
+ TabSpec spec = getTabHost().newTabSpec(tag)
+ .setIndicator(getString(indicatorId), tabDrawable)
+ .setContent(intent);
+ getTabHost().addTab(spec);
+ }
+
+
+
+ void terminate(String number, String name, Uri photo) {
+ Intent intent = new Intent()
+ .putExtra(EXTRA_CALLEE_NAME, name)
+ .putExtra(EXTRA_CALLEE_URI, number)
+ .putExtra(EXTRA_CALLEE_PHOTO_URI, photo);
+ setResult(Activity.RESULT_OK, intent);
+ finish();
+ }
+
+
+
+
+
+ public static class DialerActivity extends Activity implements OnClickListener {
+
+ private AddressText mAddress;
+ private Button addButton;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ setContentView(R.layout.simplified_dialer);
+ mAddress = (AddressText) findViewById(R.id.SipUri);
+
+ addButton = (Button) findViewById(R.id.AddCallButton);
+// addButton.setCompoundDrawablePadding(100);
+ addButton.setOnClickListener(this);
+ String type = getIntent().getStringExtra(EXTRA_PICKER_TYPE);
+ if (EXTRA_PICKER_TYPE_ADD.equals(type)) {
+ addButton.setCompoundDrawablesWithIntrinsicBounds(0, R.drawable.picker_plus, 0, 0);
+ addButton.setText(getString(R.string.AddCallButtonText));
+ } else if (EXTRA_PICKER_TYPE_TRANSFER.equals(type)) {
+ addButton.setCompoundDrawablesWithIntrinsicBounds(0, R.drawable.picker_transfer, 0, 0);
+ addButton.setText(getString(R.string.TransferCallButtonText));
+ } else {
+ throw new RuntimeException("unknown type");
+ }
+
+// findViewById(R.id.AddCallCancelButton).setOnClickListener(this);
+
+ ((AddressAware)findViewById(R.id.Dialer)).setAddressWidget(mAddress);
+ ((AddressAware)findViewById(R.id.Erase)).setAddressWidget(mAddress);
+ super.onCreate(savedInstanceState);
+ }
+
+ public void setContactAddress(String number, String name, Uri photo) {
+ mAddress.setText(number);
+ mAddress.setDisplayedName(name);
+ mAddress.setPictureUri(photo);
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (v == addButton) {
+ UriPickerActivity parent = (UriPickerActivity) getParent();
+ parent.terminate(mAddress.getText().toString(), mAddress.getDisplayedName(), mAddress.getPictureUri());
+ } else {
+ // propagate finish to parent through finishFromChild
+ finish();
+ }
+ }
+ }
+
+
+ public void setAddressAndGoToDialer(String number, String name, Uri photo) {
+ DialerActivity dialer = (DialerActivity) getLocalActivityManager().getActivity(DIALER_TAB);
+ dialer.setContactAddress(number, name, photo);
+ gotToDialer();
+ }
+
+ @Override
+ protected void onPause() {
+ LinphoneManager.stopProximitySensorForActivity(this);
+ super.onPause();
+ }
+
+ @Override
+ protected void onResume() {
+ LinphoneManager.startProximitySensorForActivity(this);
+ super.onResume();
+ }
+
+}
diff --git a/src/org/linphone/VideoCallActivity.java b/src/org/linphone/VideoCallActivity.java
index 672ca7559..30753c247 100755
--- a/src/org/linphone/VideoCallActivity.java
+++ b/src/org/linphone/VideoCallActivity.java
@@ -20,17 +20,21 @@ package org.linphone;
+import org.linphone.LinphoneSimpleListener.LinphoneOnCallStateChangedListener;
import org.linphone.core.LinphoneCall;
import org.linphone.core.Log;
+import org.linphone.core.LinphoneCall.State;
import org.linphone.mediastream.video.AndroidVideoWindowImpl;
import org.linphone.mediastream.video.capture.hwconf.AndroidCameraConfiguration;
+import android.app.Activity;
import android.content.Context;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.os.Handler;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
+import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
@@ -44,10 +48,11 @@ import android.widget.ImageView;
* @author Guillaume Beraudo
*
*/
-public class VideoCallActivity extends SoftVolumeActivity {
+public class VideoCallActivity extends Activity implements LinphoneOnCallStateChangedListener {
private SurfaceView mVideoViewReady;
private SurfaceView mVideoCaptureViewReady;
public static boolean launched = false;
+ private LinphoneCall videoCall;
private WakeLock mWakeLock;
private Handler refreshHandler = new Handler();
@@ -103,13 +108,11 @@ public class VideoCallActivity extends SoftVolumeActivity {
// Before creating the graph, the orientation must be known to LC => this is done here
LinphoneManager.getLc().setDeviceRotation(AndroidVideoWindowImpl.rotationToAngle(getWindowManager().getDefaultDisplay().getOrientation()));
- if (LinphoneManager.getLc().isIncall()) {
- LinphoneCall call = LinphoneManager.getLc().getCurrentCall();
- if (call != null) {
- updatePreview(call.cameraEnabled());
- }
+ videoCall = LinphoneManager.getLc().getCurrentCall();
+ if (videoCall != null) {
+ updatePreview(videoCall.cameraEnabled());
}
-
+
PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK|PowerManager.ON_AFTER_RELEASE,Log.TAG);
@@ -166,6 +169,7 @@ public class VideoCallActivity extends SoftVolumeActivity {
if (mVideoViewReady != null)
((GLSurfaceView)mVideoViewReady).onResume();
launched=true;
+ LinphoneManager.addListener(this);
refreshHandler.postDelayed(mCallQualityUpdater=new Runnable(){
LinphoneCall mCurrentCall=LinphoneManager.getLc().getCurrentCall();
public void run() {
@@ -263,6 +267,11 @@ public class VideoCallActivity extends SoftVolumeActivity {
return true;
}
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (LinphoneUtils.onKeyVolumeSoftAdjust(keyCode)) return true;
+ return super.onKeyDown(keyCode, event);
+ }
@Override
protected void onDestroy() {
@@ -273,7 +282,11 @@ public class VideoCallActivity extends SoftVolumeActivity {
@Override
protected void onPause() {
Log.d("onPause VideoCallActivity (isFinishing:", isFinishing(), ", inCall:", LinphoneManager.getLc().isIncall(), ", changingConf:", getChangingConfigurations());
-
+ LinphoneManager.removeListener(this);
+ if (isFinishing()) {
+ videoCall = null; // release reference
+ }
+ LinphoneManager.getInstance().restoreUserRequestedSpeaker();
launched=false;
synchronized (androidVideoWindowImpl) {
/* this call will destroy native opengl renderer
@@ -303,4 +316,14 @@ public class VideoCallActivity extends SoftVolumeActivity {
if (mVideoViewReady != null)
((GLSurfaceView)mVideoViewReady).onPause();
}
+
+ @Override
+ public void onCallStateChanged(LinphoneCall call, State state,
+ String message) {
+ if (call == videoCall && state == State.CallEnd) {
+ BandwidthManager.getInstance().setUserRestriction(false);
+ LinphoneManager.getInstance().resetCameraFromPreferences();
+ finish();
+ }
+ }
}
diff --git a/src/org/linphone/core/LinphoneCallImpl.java b/src/org/linphone/core/LinphoneCallImpl.java
index 70968e79d..6370dce17 100644
--- a/src/org/linphone/core/LinphoneCallImpl.java
+++ b/src/org/linphone/core/LinphoneCallImpl.java
@@ -46,7 +46,7 @@ class LinphoneCallImpl implements LinphoneCall {
/*
* This method must always be called from JNI, nothing else.
*/
- protected LinphoneCallImpl(long aNativePtr) {
+ private LinphoneCallImpl(long aNativePtr) {
nativePtr = aNativePtr;
}
protected void finalize() throws Throwable {
@@ -84,9 +84,21 @@ class LinphoneCallImpl implements LinphoneCall {
public boolean cameraEnabled() {
return cameraEnabled(nativePtr);
}
+
+ @Override
public boolean equals(Object call) {
+ if (this == call) return true;
+ if (call == null) return false;
+ if (!(call instanceof LinphoneCallImpl)) return false;
return nativePtr == ((LinphoneCallImpl)call).nativePtr;
}
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + (int) (nativePtr ^ (nativePtr >>> 32));
+ return result;
+ }
public void enableEchoCancellation(boolean enable) {
enableEchoCancellation(nativePtr,enable);
@@ -123,8 +135,14 @@ class LinphoneCallImpl implements LinphoneCall {
public boolean areStreamsEncrypted() {
return areStreamsEncrypted(nativePtr);
}
+
public boolean isInConference() {
- // TODO Auto-generated method stub
- return false;
+ LinphoneCallParamsImpl params = new LinphoneCallParamsImpl(getCurrentParamsCopy(nativePtr));
+ return params.localConferenceMode();
+ }
+
+ @Override
+ public String toString() {
+ return "Call " + nativePtr;
}
}
diff --git a/src/org/linphone/core/LinphoneCallParamsImpl.java b/src/org/linphone/core/LinphoneCallParamsImpl.java
index c346dcd1f..8a7fba15c 100644
--- a/src/org/linphone/core/LinphoneCallParamsImpl.java
+++ b/src/org/linphone/core/LinphoneCallParamsImpl.java
@@ -58,4 +58,9 @@ public class LinphoneCallParamsImpl implements LinphoneCallParams {
public void setMediaEnctyption(String menc) {
setMediaEncryption(nativePtr, menc);
}
+
+ private native boolean localConferenceMode(long nativePtr);
+ public boolean localConferenceMode() {
+ return localConferenceMode(nativePtr);
+ }
}
diff --git a/src/org/linphone/core/LinphoneCoreImpl.java b/src/org/linphone/core/LinphoneCoreImpl.java
index 355523d59..b2a26a976 100644
--- a/src/org/linphone/core/LinphoneCoreImpl.java
+++ b/src/org/linphone/core/LinphoneCoreImpl.java
@@ -20,6 +20,7 @@ package org.linphone.core;
import java.io.File;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
@@ -481,7 +482,7 @@ class LinphoneCoreImpl implements LinphoneCore {
public synchronized void setZrtpSecretsCache(String file) {
setZrtpSecretsCache(nativePtr,file);
}
- public void enableEchoLimiter(boolean val) {
+ public synchronized void enableEchoLimiter(boolean val) {
enableEchoLimiter(nativePtr,val);
}
public void setVideoDevice(int id) {
@@ -493,64 +494,83 @@ class LinphoneCoreImpl implements LinphoneCore {
public int getVideoDevice() {
return getVideoDevice(nativePtr);
}
- public void addAllToConference() {
- // TODO Auto-generated method stub
+
+
+ private native void leaveConference(long nativePtr);
+ public synchronized void leaveConference() {
+ leaveConference(nativePtr);
+ }
+
+ private native void enterConference(long nativePtr);
+ public synchronized void enterConference() {
+ enterConference(nativePtr);
+ }
+
+ private native boolean isInConference(long nativePtr);
+ public synchronized boolean isInConference() {
+ return isInConference(nativePtr);
+ }
+
+ private native void terminateConference(long nativePtr);
+ public synchronized void terminateConference() {
+ terminateConference(nativePtr);
+ }
+ private native int getConferenceSize(long nativePtr);
+ public synchronized int getConferenceSize() {
+ return getConferenceSize(nativePtr);
+ }
+ private native int getCallsNb(long nativePtr);
+ public synchronized int getCallsNb() {
+ return getCallsNb(nativePtr);
+ }
+ private native void terminateAllCalls(long nativePtr);
+ public synchronized void terminateAllCalls() {
+ terminateAllCalls(nativePtr);
+ }
+ private native Object getCall(long nativePtr, int position);
+ @SuppressWarnings("unchecked") public synchronized List getCalls() {
+ int size = getCallsNb(nativePtr);
+ List calls = new ArrayList(size);
+ for (int i=0; i < size; i++) {
+ calls.add((LinphoneCall)getCall(nativePtr, i));
+ }
+ return calls;
+ }
+ private native void addAllToConference(long nativePtr);
+ public synchronized void addAllToConference() {
+ addAllToConference(nativePtr);
}
- public void addToConference(LinphoneCall call) {
- // TODO Auto-generated method stub
+ private native void addToConference(long nativePtr, long nativePtrLcall);
+ public synchronized void addToConference(LinphoneCall call) {
+ addToConference(nativePtr, getCallPtr(call));
}
- public void enterConference() {
- // TODO Auto-generated method stub
-
+ private native void removeFromConference(long nativePtr);
+ public synchronized void removeFromConference(LinphoneCall call) {
+ removeFromConference(getCallPtr(call));
}
- public List getCalls() {
- // TODO Auto-generated method stub
- return null;
+
+ private long getCallPtr(LinphoneCall call) {
+ return ((LinphoneCallImpl)call).nativePtr;
}
- public int getCallsNb() {
- // TODO Auto-generated method stub
- return 0;
+
+ private native int transferCall(long nativePtr, long callPtr, String referTo);
+ public synchronized void transferCall(LinphoneCall call, String referTo) {
+ transferCall(nativePtr, getCallPtr(call), referTo);
}
- public int getConferenceSize() {
- // TODO Auto-generated method stub
- return 0;
+
+ private native int transferCallToAnother(long nativePtr, long callPtr, long destPtr);
+ public synchronized void transferCallToAnother(LinphoneCall call, LinphoneCall dest) {
+ transferCallToAnother(nativePtr, getCallPtr(call), getCallPtr(dest));
}
- public boolean isInConference() {
- // TODO Auto-generated method stub
- return false;
- }
- public void leaveConference() {
- // TODO Auto-generated method stub
-
+
+ private native Object findCallFromUri(long nativePtr, String uri);
+ @Override
+ public synchronized LinphoneCall findCallFromUri(String uri) {
+ return (LinphoneCall) findCallFromUri(nativePtr, uri);
}
- public void removeFromConference(LinphoneCall call) {
- // TODO Auto-generated method stub
-
- }
- public void terminateAllCalls() {
- // TODO Auto-generated method stub
-
- }
- public void terminateConference() {
- // TODO Auto-generated method stub
-
- }
- public void transferCall(LinphoneCall call, String referTo) {
- // TODO Auto-generated method stub
-
- }
- public void transferCallToAnother(LinphoneCall callToTransfer,
- LinphoneCall destination) {
- // TODO Auto-generated method stub
-
- }
- public LinphoneCall findCallFromUri(String uri) {
- // TODO Auto-generated method stub
- return null;
- }
-
+
public String getMediaEncryption() {
return getMediaEncryption(nativePtr);
}
@@ -563,4 +583,9 @@ class LinphoneCoreImpl implements LinphoneCore {
public void setMediaEncryptionMandatory(boolean yesno) {
setMediaEncryptionMandatory(nativePtr, yesno);
}
+
+ private native int getMaxCalls(long nativePtr);
+ public int getMaxCalls() {
+ return getMaxCalls(nativePtr);
+ }
}
diff --git a/src/org/linphone/ui/Digit.java b/src/org/linphone/ui/Digit.java
index 902657045..75e833845 100644
--- a/src/org/linphone/ui/Digit.java
+++ b/src/org/linphone/ui/Digit.java
@@ -19,19 +19,30 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
package org.linphone.ui;
import org.linphone.LinphoneManager;
+import org.linphone.LinphoneService;
+import org.linphone.R;
import org.linphone.core.LinphoneCore;
+import org.linphone.core.Log;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
+import android.widget.Toast;
public class Digit extends Button implements AddressAware {
private AddressText mAddress;
+ public void setAddressWidget(AddressText address) {
+ mAddress = address;
+ }
+
+ private boolean mPlayDtmf;
+ public void setPlayDtmf(boolean play) {
+ mPlayDtmf = play;
+ }
-
@Override
protected void onTextChanged(CharSequence text, int start, int before,
int after) {
@@ -71,20 +82,34 @@ public class Digit extends Button implements AddressAware {
private class DialKeyListener implements OnClickListener, OnTouchListener, OnLongClickListener {
final CharSequence mKeyCode;
- boolean mIsDtmfStarted=false;
+ boolean mIsDtmfStarted;
DialKeyListener() {
mKeyCode = Digit.this.getText().subSequence(0, 1);
}
- public void onClick(View v) {
- LinphoneCore lc = LinphoneManager.getLc();
- lc.stopDtmf();
- mIsDtmfStarted =false;
+ private boolean linphoneServiceReady() {
+ if (!LinphoneService.isReady()) {
+ Log.w("Service is not ready while pressing digit");
+ Toast.makeText(getContext(), getContext().getString(R.string.skipable_error_service_not_ready), Toast.LENGTH_SHORT).show();
+ return false;
+ }
+ return true;
+ }
- if (lc.isIncall()) {
- lc.sendDtmf(mKeyCode.charAt(0));
- } else if (mAddress != null) {
+ public void onClick(View v) {
+ if (mPlayDtmf) {
+ if (!linphoneServiceReady()) return;
+ LinphoneCore lc = LinphoneManager.getLc();
+ lc.stopDtmf();
+ mIsDtmfStarted =false;
+ if (lc.isIncall()) {
+ lc.sendDtmf(mKeyCode.charAt(0));
+ return;
+ }
+ }
+
+ if (mAddress != null) {
int lBegin = mAddress.getSelectionStart();
if (lBegin == -1) {
lBegin = mAddress.length();
@@ -96,22 +121,29 @@ public class Digit extends Button implements AddressAware {
}
public boolean onTouch(View v, MotionEvent event) {
+ if (!mPlayDtmf) return false;
+ if (!linphoneServiceReady()) return true;
+
LinphoneCore lc = LinphoneManager.getLc();
- if (event.getAction() == MotionEvent.ACTION_DOWN && mIsDtmfStarted ==false) {
+ if (event.getAction() == MotionEvent.ACTION_DOWN && !mIsDtmfStarted) {
LinphoneManager.getInstance().playDtmf(getContext().getContentResolver(), mKeyCode.charAt(0));
mIsDtmfStarted=true;
} else {
- if (event.getAction() == MotionEvent.ACTION_UP)
+ if (event.getAction() == MotionEvent.ACTION_UP) {
lc.stopDtmf();
- mIsDtmfStarted =false;
+ mIsDtmfStarted=false;
+ }
}
return false;
}
public boolean onLongClick(View v) {
- // Called if "0+" dtmf
- LinphoneCore lc = LinphoneManager.getLc();
- lc.stopDtmf();
+ if (mPlayDtmf) {
+ if (!linphoneServiceReady()) return true;
+ // Called if "0+" dtmf
+ LinphoneCore lc = LinphoneManager.getLc();
+ lc.stopDtmf();
+ }
if (mAddress == null) return true;
@@ -126,7 +158,5 @@ public class Digit extends Button implements AddressAware {
}
};
- public void setAddressWidget(AddressText address) {
- mAddress = address;
- }
+
}
diff --git a/src/org/linphone/ui/EraseButton.java b/src/org/linphone/ui/EraseButton.java
index 8e253e765..289b8fa50 100644
--- a/src/org/linphone/ui/EraseButton.java
+++ b/src/org/linphone/ui/EraseButton.java
@@ -25,7 +25,7 @@ import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.widget.Button;
-public class EraseButton extends Button implements OnClickListener, OnLongClickListener{
+public class EraseButton extends Button implements AddressAware, OnClickListener, OnLongClickListener{
private AddressText address;
@@ -52,7 +52,7 @@ public class EraseButton extends Button implements OnClickListener, OnLongClickL
return true;
}
- public void setAddressView(AddressText view) {
+ public void setAddressWidget(AddressText view) {
address = view;
}
diff --git a/src/org/linphone/ui/HangCallButton.java b/src/org/linphone/ui/HangCallButton.java
index 706df35ec..9194c844d 100644
--- a/src/org/linphone/ui/HangCallButton.java
+++ b/src/org/linphone/ui/HangCallButton.java
@@ -29,6 +29,9 @@ import android.widget.ImageButton;
public class HangCallButton extends ImageButton implements OnClickListener {
+ private boolean terminateAllCalls;
+ public void setTerminateAllCalls(boolean all) {terminateAllCalls = all;}
+
private OnClickListener externalClickListener;
public void setExternalClickListener(OnClickListener e) {externalClickListener = e;}
@@ -39,7 +42,11 @@ public class HangCallButton extends ImageButton implements OnClickListener {
public void onClick(View v) {
LinphoneCore lc = LinphoneManager.getLc();
- lc.terminateCall(lc.getCurrentCall());
+ if (terminateAllCalls) {
+ lc.terminateAllCalls();
+ } else {
+ lc.terminateCall(lc.getCurrentCall());
+ }
if (externalClickListener != null) externalClickListener.onClick(v);
}
diff --git a/src/org/linphone/ui/Numpad.java b/src/org/linphone/ui/Numpad.java
index 5a0a19e1d..fe02e34c2 100644
--- a/src/org/linphone/ui/Numpad.java
+++ b/src/org/linphone/ui/Numpad.java
@@ -24,6 +24,7 @@ import java.util.Collection;
import org.linphone.R;
import android.content.Context;
+import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -36,29 +37,42 @@ import android.widget.LinearLayout;
*/
public class Numpad extends LinearLayout implements AddressAware {
+ private boolean mPlayDtmf;
+
+
public Numpad(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.numpad, this);
setLongClickable(true);
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Numpad);
+ mPlayDtmf = 1 == a.getInt(org.linphone.R.styleable.Numpad_play_dtmf, 1);
+ a.recycle();
}
+ @Override
+ protected void onFinishInflate() {
+ for (Digit v : retrieveChildren(this, Digit.class)) {
+ v.setPlayDtmf(mPlayDtmf);
+ }
+ super.onFinishInflate();
+ }
public void setAddressWidget(AddressText address) {
- for (AddressAware v : retrieveChildren(this)) {
+ for (AddressAware v : retrieveChildren(this, AddressAware.class)) {
v.setAddressWidget(address);
}
}
- private Collection retrieveChildren(ViewGroup viewGroup) {
- final Collection views = new ArrayList();
+ private Collection retrieveChildren(ViewGroup viewGroup, Class clazz) {
+ final Collection views = new ArrayList();
for (int i = 0; i < viewGroup.getChildCount(); i++) {
View v = viewGroup.getChildAt(i);
if (v instanceof ViewGroup) {
- views.addAll(retrieveChildren((ViewGroup) v));
+ views.addAll(retrieveChildren((ViewGroup) v, clazz));
} else {
- if (v instanceof AddressAware)
- views.add((AddressAware) v);
+ if (clazz.isInstance(v))
+ views.add(clazz.cast(v));
}
}
diff --git a/src/org/linphone/ui/SlidingTab.java b/src/org/linphone/ui/SlidingTab.java
new file mode 100644
index 000000000..c58670e39
--- /dev/null
+++ b/src/org/linphone/ui/SlidingTab.java
@@ -0,0 +1,844 @@
+/*
+ * Derived from Android "SlidingTab" source.
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.linphone.ui;
+
+
+import org.linphone.R;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Vibrator;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.LinearInterpolator;
+import android.view.animation.TranslateAnimation;
+import android.view.animation.Animation.AnimationListener;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.ImageView.ScaleType;
+
+/**
+ * A special widget containing two Sliders and a threshold for each. Moving either slider beyond
+ * the threshold will cause the registered OnTriggerListener.onTrigger() to be called with
+ * whichHandle being {@link OnTriggerListener#LEFT_HANDLE} or {@link OnTriggerListener#RIGHT_HANDLE}
+ * Equivalently, selecting a tab will result in a call to
+ * {@link OnTriggerListener#onGrabbedStateChange(View, int)} with one of these two states. Releasing
+ * the tab will result in whichHandle being {@link OnTriggerListener#NO_HANDLE}.
+ *
+ */
+public class SlidingTab extends ViewGroup {
+ private static final String LOG_TAG = "SlidingTab";
+ private static final int HORIZONTAL = 0; // as defined in attrs.xml
+ private static final int VERTICAL = 1;
+
+ // TODO: Make these configurable
+ private static final float THRESHOLD = 2.0f / 3.0f;
+ private static final long VIBRATE_SHORT = 30;
+ private static final long VIBRATE_LONG = 40;
+ private static final int TRACKING_MARGIN = 50;
+ private static final int ANIM_DURATION = 250; // Time for most animations (in ms)
+ private static final int ANIM_TARGET_TIME = 500; // Time to show targets (in ms)
+ private boolean mHoldLeftOnTransition = false;
+ private boolean mHoldRightOnTransition = false;
+
+ private OnTriggerListener mOnTriggerListener;
+ private int mGrabbedState = OnTriggerListener.NO_HANDLE;
+ private boolean mTriggered = false;
+ private Vibrator mVibrator;
+
+ /**
+ * Either {@link #HORIZONTAL} or {@link #VERTICAL}.
+ */
+ private int mOrientation;
+
+ private Slider mLeftSlider;
+ private Slider mRightSlider;
+ private Slider mCurrentSlider;
+ private boolean mTracking;
+ private float mThreshold;
+ private Slider mOtherSlider;
+ private boolean mAnimating;
+ private Rect mTmpRect;
+
+ /**
+ * Listener used to reset the view when the current animation completes.
+ */
+ private final AnimationListener mAnimationDoneListener = new AnimationListener() {
+ public void onAnimationStart(Animation animation) {
+
+ }
+
+ public void onAnimationRepeat(Animation animation) {
+
+ }
+
+ public void onAnimationEnd(Animation animation) {
+ onAnimationDone();
+ }
+ };
+
+ /**
+ * Interface definition for a callback to be invoked when a tab is triggered
+ * by moving it beyond a threshold.
+ */
+ public interface OnTriggerListener {
+ /**
+ * The interface was triggered because the user let go of the handle without reaching the
+ * threshold.
+ */
+ public static final int NO_HANDLE = 0;
+
+ /**
+ * The interface was triggered because the user grabbed the left handle and moved it past
+ * the threshold.
+ */
+ public static final int LEFT_HANDLE = 1;
+
+ /**
+ * The interface was triggered because the user grabbed the right handle and moved it past
+ * the threshold.
+ */
+ public static final int RIGHT_HANDLE = 2;
+
+ /**
+ * Called when the user moves a handle beyond the threshold.
+ *
+ * @param v The view that was triggered.
+ * @param whichHandle Which "dial handle" the user grabbed,
+ * either {@link #LEFT_HANDLE}, {@link #RIGHT_HANDLE}.
+ */
+ void onTrigger(View v, int whichHandle);
+
+ /**
+ * Called when the "grabbed state" changes (i.e. when the user either grabs or releases
+ * one of the handles.)
+ *
+ * @param v the view that was triggered
+ * @param grabbedState the new state: {@link #NO_HANDLE}, {@link #LEFT_HANDLE},
+ * or {@link #RIGHT_HANDLE}.
+ */
+ void onGrabbedStateChange(View v, int grabbedState);
+ }
+
+ /**
+ * Simple container class for all things pertinent to a slider.
+ * A slider consists of 3 Views:
+ *
+ * {@link #tab} is the tab shown on the screen in the default state.
+ * {@link #text} is the view revealed as the user slides the tab out.
+ * {@link #target} is the target the user must drag the slider past to trigger the slider.
+ *
+ */
+ private static class Slider {
+ /**
+ * Tab alignment - determines which side the tab should be drawn on
+ */
+ public static final int ALIGN_LEFT = 0;
+ public static final int ALIGN_RIGHT = 1;
+ public static final int ALIGN_TOP = 2;
+ public static final int ALIGN_BOTTOM = 3;
+ public static final int ALIGN_UNKNOWN = 4;
+
+ /**
+ * States for the view.
+ */
+ private static final int STATE_NORMAL = 0;
+ private static final int STATE_PRESSED = 1;
+ private static final int STATE_ACTIVE = 2;
+
+ private final ImageView tab;
+ private final TextView text;
+ private final ImageView target;
+ private int currentState = STATE_NORMAL;
+ private int alignment = ALIGN_UNKNOWN;
+ private int alignment_value;
+
+ /**
+ * Constructor
+ *
+ * @param parent the container view of this one
+ * @param tabId drawable for the tab
+ * @param barId drawable for the bar
+ * @param targetId drawable for the target
+ */
+ Slider(ViewGroup parent, int iconId, int tabId, int barId, int targetId) {
+ // Create tab
+ tab = new ImageView(parent.getContext());
+ tab.setImageResource(iconId);
+ tab.setBackgroundResource(tabId);
+ tab.setScaleType(ScaleType.CENTER);
+ tab.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT));
+
+ // Create hint TextView
+ text = new TextView(parent.getContext());
+ text.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
+ LayoutParams.FILL_PARENT));
+ text.setBackgroundResource(barId);
+ text.setTextAppearance(parent.getContext(), R.style.TextAppearance_SlidingTabNormal);
+ // hint.setSingleLine(); // Hmm.. this causes the text to disappear off-screen
+
+ // Create target
+ target = new ImageView(parent.getContext());
+ target.setImageResource(targetId);
+ target.setScaleType(ScaleType.CENTER);
+ target.setLayoutParams(
+ new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+ target.setVisibility(View.INVISIBLE);
+
+ parent.addView(target); // this needs to be first - relies on painter's algorithm
+ parent.addView(tab);
+ parent.addView(text);
+ }
+
+ void setIcon(int iconId) {
+ tab.setImageResource(iconId);
+ }
+
+ void setTabBackgroundResource(int tabId) {
+ tab.setBackgroundResource(tabId);
+ }
+
+ void setBarBackgroundResource(int barId) {
+ text.setBackgroundResource(barId);
+ }
+
+ void setHintText(int resId) {
+ text.setText(resId);
+ }
+
+ void hide() {
+ boolean horiz = alignment == ALIGN_LEFT || alignment == ALIGN_RIGHT;
+ int dx = horiz ? (alignment == ALIGN_LEFT ? alignment_value - tab.getRight()
+ : alignment_value - tab.getLeft()) : 0;
+ int dy = horiz ? 0 : (alignment == ALIGN_TOP ? alignment_value - tab.getBottom()
+ : alignment_value - tab.getTop());
+
+ Animation trans = new TranslateAnimation(0, dx, 0, dy);
+ trans.setDuration(ANIM_DURATION);
+ trans.setFillAfter(true);
+ tab.startAnimation(trans);
+ text.startAnimation(trans);
+ target.setVisibility(View.INVISIBLE);
+ }
+
+ void show(boolean animate) {
+ text.setVisibility(View.VISIBLE);
+ tab.setVisibility(View.VISIBLE);
+ //target.setVisibility(View.INVISIBLE);
+ if (animate) {
+ boolean horiz = alignment == ALIGN_LEFT || alignment == ALIGN_RIGHT;
+ int dx = horiz ? (alignment == ALIGN_LEFT ? tab.getWidth() : -tab.getWidth()) : 0;
+ int dy = horiz ? 0: (alignment == ALIGN_TOP ? tab.getHeight() : -tab.getHeight());
+
+ Animation trans = new TranslateAnimation(-dx, 0, -dy, 0);
+ trans.setDuration(ANIM_DURATION);
+ tab.startAnimation(trans);
+ text.startAnimation(trans);
+ }
+ }
+
+ void setState(int state) {
+ text.setPressed(state == STATE_PRESSED);
+ tab.setPressed(state == STATE_PRESSED);
+ if (state == STATE_ACTIVE) {
+ final int[] activeState = new int[] {android.R.attr.state_active};
+ if (text.getBackground().isStateful()) {
+ text.getBackground().setState(activeState);
+ }
+ if (tab.getBackground().isStateful()) {
+ tab.getBackground().setState(activeState);
+ }
+ text.setTextAppearance(text.getContext(), R.style.TextAppearance_SlidingTabActive);
+ } else {
+ text.setTextAppearance(text.getContext(), R.style.TextAppearance_SlidingTabNormal);
+ }
+ currentState = state;
+ }
+
+ void showTarget() {
+ AlphaAnimation alphaAnim = new AlphaAnimation(0.0f, 1.0f);
+ alphaAnim.setDuration(ANIM_TARGET_TIME);
+ target.startAnimation(alphaAnim);
+ target.setVisibility(View.VISIBLE);
+ }
+
+ void reset(boolean animate) {
+ setState(STATE_NORMAL);
+ text.setVisibility(View.VISIBLE);
+ text.setTextAppearance(text.getContext(), R.style.TextAppearance_SlidingTabNormal);
+ tab.setVisibility(View.VISIBLE);
+ target.setVisibility(View.INVISIBLE);
+ final boolean horiz = alignment == ALIGN_LEFT || alignment == ALIGN_RIGHT;
+ int dx = horiz ? (alignment == ALIGN_LEFT ? alignment_value - tab.getLeft()
+ : alignment_value - tab.getRight()) : 0;
+ int dy = horiz ? 0 : (alignment == ALIGN_TOP ? alignment_value - tab.getTop()
+ : alignment_value - tab.getBottom());
+ if (animate) {
+ TranslateAnimation trans = new TranslateAnimation(0, dx, 0, dy);
+ trans.setDuration(ANIM_DURATION);
+ trans.setFillAfter(false);
+ text.startAnimation(trans);
+ tab.startAnimation(trans);
+ } else {
+ if (horiz) {
+ text.offsetLeftAndRight(dx);
+ tab.offsetLeftAndRight(dx);
+ } else {
+ text.offsetTopAndBottom(dy);
+ tab.offsetTopAndBottom(dy);
+ }
+ text.clearAnimation();
+ tab.clearAnimation();
+ target.clearAnimation();
+ }
+ }
+
+ void setTarget(int targetId) {
+ target.setImageResource(targetId);
+ }
+
+ /**
+ * Layout the given widgets within the parent.
+ *
+ * @param l the parent's left border
+ * @param t the parent's top border
+ * @param r the parent's right border
+ * @param b the parent's bottom border
+ * @param alignment which side to align the widget to
+ */
+ void layout(int l, int t, int r, int b, int alignment) {
+ this.alignment = alignment;
+ final Drawable tabBackground = tab.getBackground();
+ final int handleWidth = tabBackground.getIntrinsicWidth();
+ final int handleHeight = tabBackground.getIntrinsicHeight();
+ final Drawable targetDrawable = target.getDrawable();
+ final int targetWidth = targetDrawable.getIntrinsicWidth();
+ final int targetHeight = targetDrawable.getIntrinsicHeight();
+ final int parentWidth = r - l;
+ final int parentHeight = b - t;
+
+ final int leftTarget = (int) (THRESHOLD * parentWidth) - targetWidth + handleWidth / 2;
+ final int rightTarget = (int) ((1.0f - THRESHOLD) * parentWidth) - handleWidth / 2;
+ final int left = (parentWidth - handleWidth) / 2;
+ final int right = left + handleWidth;
+
+ if (alignment == ALIGN_LEFT || alignment == ALIGN_RIGHT) {
+ // horizontal
+ final int targetTop = (parentHeight - targetHeight) / 2;
+ final int targetBottom = targetTop + targetHeight;
+ final int top = (parentHeight - handleHeight) / 2;
+ final int bottom = (parentHeight + handleHeight) / 2;
+ if (alignment == ALIGN_LEFT) {
+ tab.layout(0, top, handleWidth, bottom);
+ text.layout(0 - parentWidth, top, 0, bottom);
+ text.setGravity(Gravity.RIGHT);
+ target.layout(leftTarget, targetTop, leftTarget + targetWidth, targetBottom);
+ alignment_value = l;
+ } else {
+ tab.layout(parentWidth - handleWidth, top, parentWidth, bottom);
+ text.layout(parentWidth, top, parentWidth + parentWidth, bottom);
+ target.layout(rightTarget, targetTop, rightTarget + targetWidth, targetBottom);
+ text.setGravity(Gravity.TOP);
+ alignment_value = r;
+ }
+ } else {
+ // vertical
+ final int targetLeft = (parentWidth - targetWidth) / 2;
+ final int targetRight = (parentWidth + targetWidth) / 2;
+ final int top = (int) (THRESHOLD * parentHeight) + handleHeight / 2 - targetHeight;
+ final int bottom = (int) ((1.0f - THRESHOLD) * parentHeight) - handleHeight / 2;
+ if (alignment == ALIGN_TOP) {
+ tab.layout(left, 0, right, handleHeight);
+ text.layout(left, 0 - parentHeight, right, 0);
+ target.layout(targetLeft, top, targetRight, top + targetHeight);
+ alignment_value = t;
+ } else {
+ tab.layout(left, parentHeight - handleHeight, right, parentHeight);
+ text.layout(left, parentHeight, right, parentHeight + parentHeight);
+ target.layout(targetLeft, bottom, targetRight, bottom + targetHeight);
+ alignment_value = b;
+ }
+ }
+ }
+
+ public void updateDrawableStates() {
+ setState(currentState);
+ }
+
+ /**
+ * Ensure all the dependent widgets are measured.
+ */
+ public void measure() {
+ tab.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
+ text.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
+ }
+
+ /**
+ * Get the measured tab width. Must be called after {@link Slider#measure()}.
+ * @return
+ */
+ public int getTabWidth() {
+ return tab.getMeasuredWidth();
+ }
+
+ /**
+ * Get the measured tab width. Must be called after {@link Slider#measure()}.
+ * @return
+ */
+ public int getTabHeight() {
+ return tab.getMeasuredHeight();
+ }
+
+ /**
+ * Start animating the slider. Note we need two animations since an Animator
+ * keeps internal state of the invalidation region which is just the view being animated.
+ *
+ * @param anim1
+ * @param anim2
+ */
+ public void startAnimation(Animation anim1, Animation anim2) {
+ tab.startAnimation(anim1);
+ text.startAnimation(anim2);
+ }
+
+ public void hideTarget() {
+ target.clearAnimation();
+ target.setVisibility(View.INVISIBLE);
+ }
+ }
+
+ public SlidingTab(Context context) {
+ this(context, null);
+ }
+
+ /**
+ * Constructor used when this widget is created from a layout file.
+ */
+ public SlidingTab(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ // Allocate a temporary once that can be used everywhere.
+ mTmpRect = new Rect();
+
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SlidingTab);
+ mOrientation = a.getInt(org.linphone.R.styleable.SlidingTab_orientation, HORIZONTAL);
+ a.recycle();
+ mLeftSlider = new Slider(this,
+ R.drawable.startcall_green,
+ R.drawable.jog_tab_left_answer,
+ R.drawable.jog_tab_bar_left_answer,
+ R.drawable.jog_tab_target_green
+ );
+ mRightSlider = new Slider(this,
+ R.drawable.stopcall_red,
+ R.drawable.jog_tab_right_decline,
+ R.drawable.jog_tab_bar_right_decline,
+ R.drawable.jog_tab_target_red
+ );
+
+ // setBackgroundColor(0x80808080);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
+ int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
+
+ int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
+ int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
+
+ if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
+ Log.e("SlidingTab", "SlidingTab cannot have UNSPECIFIED MeasureSpec"
+ +"(wspec=" + widthSpecMode + ", hspec=" + heightSpecMode + ")",
+ new RuntimeException(LOG_TAG + "stack:"));
+ }
+
+ mLeftSlider.measure();
+ mRightSlider.measure();
+ final int leftTabWidth = mLeftSlider.getTabWidth();
+ final int rightTabWidth = mRightSlider.getTabWidth();
+ final int leftTabHeight = mLeftSlider.getTabHeight();
+ final int rightTabHeight = mRightSlider.getTabHeight();
+ final int width;
+ final int height;
+ if (isHorizontal()) {
+ width = Math.max(widthSpecSize, leftTabWidth + rightTabWidth);
+ height = Math.max(leftTabHeight, rightTabHeight);
+ } else {
+ width = Math.max(leftTabWidth, rightTabHeight);
+ height = Math.max(heightSpecSize, leftTabHeight + rightTabHeight);
+ }
+ setMeasuredDimension(width, height);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ final int action = event.getAction();
+ final float x = event.getX();
+ final float y = event.getY();
+
+ if (mAnimating) {
+ return false;
+ }
+
+ View leftHandle = mLeftSlider.tab;
+ leftHandle.getHitRect(mTmpRect);
+ boolean leftHit = mTmpRect.contains((int) x, (int) y);
+
+ View rightHandle = mRightSlider.tab;
+ rightHandle.getHitRect(mTmpRect);
+ boolean rightHit = mTmpRect.contains((int)x, (int) y);
+
+ if (!mTracking && !(leftHit || rightHit)) {
+ return false;
+ }
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN: {
+ mTracking = true;
+ mTriggered = false;
+ vibrate(VIBRATE_SHORT);
+ if (leftHit) {
+ mCurrentSlider = mLeftSlider;
+ mOtherSlider = mRightSlider;
+ mThreshold = isHorizontal() ? THRESHOLD : 1.0f - THRESHOLD;
+ setGrabbedState(OnTriggerListener.LEFT_HANDLE);
+ } else {
+ mCurrentSlider = mRightSlider;
+ mOtherSlider = mLeftSlider;
+ mThreshold = isHorizontal() ? 1.0f - THRESHOLD : THRESHOLD;
+ setGrabbedState(OnTriggerListener.RIGHT_HANDLE);
+ }
+ mCurrentSlider.setState(Slider.STATE_PRESSED);
+ mCurrentSlider.showTarget();
+ mOtherSlider.hide();
+ break;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Reset the tabs to their original state and stop any existing animation.
+ * Animate them back into place if animate is true.
+ *
+ * @param animate
+ */
+ public void reset(boolean animate) {
+ mLeftSlider.reset(animate);
+ mRightSlider.reset(animate);
+ if (!animate) {
+ mAnimating = false;
+ }
+ }
+
+ @Override
+ public void setVisibility(int visibility) {
+ // Clear animations so sliders don't continue to animate when we show the widget again.
+ if (visibility != getVisibility() && visibility == View.INVISIBLE) {
+ reset(false);
+ }
+ super.setVisibility(visibility);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (mTracking) {
+ final int action = event.getAction();
+ final float x = event.getX();
+ final float y = event.getY();
+
+ switch (action) {
+ case MotionEvent.ACTION_MOVE:
+ if (withinView(x, y, this) ) {
+ moveHandle(x, y);
+ float position = isHorizontal() ? x : y;
+ float target = mThreshold * (isHorizontal() ? getWidth() : getHeight());
+ boolean thresholdReached;
+ if (isHorizontal()) {
+ thresholdReached = mCurrentSlider == mLeftSlider ?
+ position > target : position < target;
+ } else {
+ thresholdReached = mCurrentSlider == mLeftSlider ?
+ position < target : position > target;
+ }
+ if (!mTriggered && thresholdReached) {
+ mTriggered = true;
+ mTracking = false;
+ mCurrentSlider.setState(Slider.STATE_ACTIVE);
+ boolean isLeft = mCurrentSlider == mLeftSlider;
+ dispatchTriggerEvent(isLeft ?
+ OnTriggerListener.LEFT_HANDLE : OnTriggerListener.RIGHT_HANDLE);
+
+ startAnimating(isLeft ? mHoldLeftOnTransition : mHoldRightOnTransition);
+ setGrabbedState(OnTriggerListener.NO_HANDLE);
+ }
+ break;
+ }
+ // Intentionally fall through - we're outside tracking rectangle
+
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mTracking = false;
+ mTriggered = false;
+ mOtherSlider.show(true);
+ mCurrentSlider.reset(false);
+ mCurrentSlider.hideTarget();
+ mCurrentSlider = null;
+ mOtherSlider = null;
+ setGrabbedState(OnTriggerListener.NO_HANDLE);
+ break;
+ }
+ }
+
+ return mTracking || super.onTouchEvent(event);
+ }
+
+ void startAnimating(final boolean holdAfter) {
+ mAnimating = true;
+ final Animation trans1;
+ final Animation trans2;
+ final Slider slider = mCurrentSlider;
+ final int dx;
+ final int dy;
+ if (isHorizontal()) {
+ int right = slider.tab.getRight();
+ int width = slider.tab.getWidth();
+ int left = slider.tab.getLeft();
+ int viewWidth = getWidth();
+ int holdOffset = holdAfter ? 0 : width; // how much of tab to show at the end of anim
+ dx = slider == mRightSlider ? - (right + viewWidth - holdOffset)
+ : (viewWidth - left) + viewWidth - holdOffset;
+ dy = 0;
+ } else {
+ int top = slider.tab.getTop();
+ int bottom = slider.tab.getBottom();
+ int height = slider.tab.getHeight();
+ int viewHeight = getHeight();
+ int holdOffset = holdAfter ? 0 : height; // how much of tab to show at end of anim
+ dx = 0;
+ dy = slider == mRightSlider ? (top + viewHeight - holdOffset)
+ : - ((viewHeight - bottom) + viewHeight - holdOffset);
+ }
+ trans1 = new TranslateAnimation(0, dx, 0, dy);
+ trans1.setDuration(ANIM_DURATION);
+ trans1.setInterpolator(new LinearInterpolator());
+ trans1.setFillAfter(true);
+ trans2 = new TranslateAnimation(0, dx, 0, dy);
+ trans2.setDuration(ANIM_DURATION);
+ trans2.setInterpolator(new LinearInterpolator());
+ trans2.setFillAfter(true);
+
+ trans1.setAnimationListener(new AnimationListener() {
+ public void onAnimationEnd(Animation animation) {
+ Animation anim;
+ if (holdAfter) {
+ anim = new TranslateAnimation(dx, dx, dy, dy);
+ anim.setDuration(1000); // plenty of time for transitions
+ mAnimating = false;
+ } else {
+ anim = new AlphaAnimation(0.5f, 1.0f);
+ anim.setDuration(ANIM_DURATION);
+ resetView();
+ }
+ anim.setAnimationListener(mAnimationDoneListener);
+
+ /* Animation can be the same for these since the animation just holds */
+ mLeftSlider.startAnimation(anim, anim);
+ mRightSlider.startAnimation(anim, anim);
+ }
+
+ public void onAnimationRepeat(Animation animation) {
+
+ }
+
+ public void onAnimationStart(Animation animation) {
+
+ }
+
+ });
+
+ slider.hideTarget();
+ slider.startAnimation(trans1, trans2);
+ }
+
+ private void onAnimationDone() {
+ resetView();
+ mAnimating = false;
+ }
+
+ private boolean withinView(final float x, final float y, final View view) {
+ return isHorizontal() && y > - TRACKING_MARGIN && y < TRACKING_MARGIN + view.getHeight()
+ || !isHorizontal() && x > -TRACKING_MARGIN && x < TRACKING_MARGIN + view.getWidth();
+ }
+
+ private boolean isHorizontal() {
+ return mOrientation == HORIZONTAL;
+ }
+
+ private void resetView() {
+ mLeftSlider.reset(false);
+ mRightSlider.reset(false);
+ // onLayout(true, getLeft(), getTop(), getLeft() + getWidth(), getTop() + getHeight());
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ if (!changed) return;
+
+ // Center the widgets in the view
+ mLeftSlider.layout(l, t, r, b, isHorizontal() ? Slider.ALIGN_LEFT : Slider.ALIGN_BOTTOM);
+ mRightSlider.layout(l, t, r, b, isHorizontal() ? Slider.ALIGN_RIGHT : Slider.ALIGN_TOP);
+ }
+
+ private void moveHandle(float x, float y) {
+ final View handle = mCurrentSlider.tab;
+ final View content = mCurrentSlider.text;
+ if (isHorizontal()) {
+ int deltaX = (int) x - handle.getLeft() - (handle.getWidth() / 2);
+ handle.offsetLeftAndRight(deltaX);
+ content.offsetLeftAndRight(deltaX);
+ } else {
+ int deltaY = (int) y - handle.getTop() - (handle.getHeight() / 2);
+ handle.offsetTopAndBottom(deltaY);
+ content.offsetTopAndBottom(deltaY);
+ }
+ invalidate(); // TODO: be more conservative about what we're invalidating
+ }
+
+ /**
+ * Sets the left handle icon to a given resource.
+ *
+ * The resource should refer to a Drawable object, or use 0 to remove
+ * the icon.
+ *
+ * @param iconId the resource ID of the icon drawable
+ * @param targetId the resource of the target drawable
+ * @param barId the resource of the bar drawable (stateful)
+ * @param tabId the resource of the
+ */
+ public void setLeftTabResources(int iconId, int targetId, int barId, int tabId) {
+ mLeftSlider.setIcon(iconId);
+ mLeftSlider.setTarget(targetId);
+ mLeftSlider.setBarBackgroundResource(barId);
+ mLeftSlider.setTabBackgroundResource(tabId);
+ mLeftSlider.updateDrawableStates();
+ }
+
+ /**
+ * Sets the left handle hint text to a given resource string.
+ *
+ * @param resId
+ */
+ public void setLeftHintText(int resId) {
+ if (isHorizontal()) {
+ mLeftSlider.setHintText(resId);
+ }
+ }
+
+ /**
+ * Sets the right handle icon to a given resource.
+ *
+ * The resource should refer to a Drawable object, or use 0 to remove
+ * the icon.
+ *
+ * @param iconId the resource ID of the icon drawable
+ * @param targetId the resource of the target drawable
+ * @param barId the resource of the bar drawable (stateful)
+ * @param tabId the resource of the
+ */
+ public void setRightTabResources(int iconId, int targetId, int barId, int tabId) {
+ mRightSlider.setIcon(iconId);
+ mRightSlider.setTarget(targetId);
+ mRightSlider.setBarBackgroundResource(barId);
+ mRightSlider.setTabBackgroundResource(tabId);
+ mRightSlider.updateDrawableStates();
+ }
+
+ /**
+ * Sets the left handle hint text to a given resource string.
+ *
+ * @param resId
+ */
+ public void setRightHintText(int resId) {
+ if (isHorizontal()) {
+ mRightSlider.setHintText(resId);
+ }
+ }
+
+ public void setHoldAfterTrigger(boolean holdLeft, boolean holdRight) {
+ mHoldLeftOnTransition = holdLeft;
+ mHoldRightOnTransition = holdRight;
+ }
+
+ /**
+ * Triggers haptic feedback.
+ */
+ private synchronized void vibrate(long duration) {
+ if (mVibrator == null) {
+ mVibrator = (android.os.Vibrator)
+ getContext().getSystemService(Context.VIBRATOR_SERVICE);
+ }
+ mVibrator.vibrate(duration);
+ }
+
+ /**
+ * Registers a callback to be invoked when the user triggers an event.
+ *
+ * @param listener the OnDialTriggerListener to attach to this view
+ */
+ public void setOnTriggerListener(OnTriggerListener listener) {
+ mOnTriggerListener = listener;
+ }
+
+ /**
+ * Dispatches a trigger event to listener. Ignored if a listener is not set.
+ * @param whichHandle the handle that triggered the event.
+ */
+ private void dispatchTriggerEvent(int whichHandle) {
+ vibrate(VIBRATE_LONG);
+ if (mOnTriggerListener != null) {
+ mOnTriggerListener.onTrigger(this, whichHandle);
+ }
+ }
+
+ /**
+ * Sets the current grabbed state, and dispatches a grabbed state change
+ * event to our listener.
+ */
+ private void setGrabbedState(int newState) {
+ if (newState != mGrabbedState) {
+ mGrabbedState = newState;
+ if (mOnTriggerListener != null) {
+ mOnTriggerListener.onGrabbedStateChange(this, mGrabbedState);
+ }
+ }
+ }
+}
diff --git a/src/org/linphone/ui/SpeakerButton.java b/src/org/linphone/ui/SpeakerButton.java
index 35d2beb1b..05b53867a 100644
--- a/src/org/linphone/ui/SpeakerButton.java
+++ b/src/org/linphone/ui/SpeakerButton.java
@@ -48,9 +48,9 @@ public class SpeakerButton extends ToggleImageButton implements OnCheckedChangeL
public void onCheckedChanged(ToggleImageButton button, boolean checked) {
if (checked) {
- LinphoneManager.getInstance().routeAudioToSpeaker();
+ LinphoneManager.getInstance().routeAudioToSpeaker(true);
} else {
- LinphoneManager.getInstance().routeAudioToReceiver();
+ LinphoneManager.getInstance().routeAudioToReceiver(true);
}
}
diff --git a/submodules/linphone b/submodules/linphone
index a0f8cbb73..c6d065690 160000
--- a/submodules/linphone
+++ b/submodules/linphone
@@ -1 +1 @@
-Subproject commit a0f8cbb73c1c028182972147d1ce3f0fdae2921f
+Subproject commit c6d065690599ed7993d252776f552482dbed267f
diff --git a/test/org/linphone/TestConferenceActivity.java b/test/org/linphone/TestConferenceActivity.java
new file mode 100644
index 000000000..31fd3a971
--- /dev/null
+++ b/test/org/linphone/TestConferenceActivity.java
@@ -0,0 +1,394 @@
+/*
+TestIncallCalleeBoxes.java
+Copyright (C) 2011 Belledonne Communications, Grenoble, France
+
+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.linphone;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.linphone.core.CallDirection;
+import org.linphone.core.LinphoneAddress;
+import org.linphone.core.LinphoneAuthInfo;
+import org.linphone.core.LinphoneCall;
+import org.linphone.core.LinphoneCallLog;
+import org.linphone.core.LinphoneCallParams;
+import org.linphone.core.LinphoneChatRoom;
+import org.linphone.core.LinphoneCore;
+import org.linphone.core.LinphoneCoreException;
+import org.linphone.core.LinphoneFriend;
+import org.linphone.core.LinphoneProxyConfig;
+import org.linphone.core.OnlineStatus;
+import org.linphone.core.PayloadType;
+import org.linphone.core.VideoSize;
+import org.linphone.core.LinphoneCall.State;
+
+import android.os.Bundle;
+import android.os.Handler;
+
+/**
+ * @author Guillaume Beraudo
+ */
+public class TestConferenceActivity extends ConferenceActivity {
+
+ private Handler mHandler = new Handler();
+ private LinphoneCoreTest mTestLc;
+ protected final LinphoneCore lc() {return mTestLc;}
+
+ private void simulateCallAccepted(final LinphoneCall call, int millis) {
+ mHandler.postDelayed(new Runnable() {
+ public void run() {
+ lc().pauseAllCalls();
+ ((LinphoneCallTest)call).state = State.StreamsRunning;
+ onCallStateChanged(call, call.getState(), "simulated out call answered");
+ }
+ }, millis);
+ }
+ private void delayedCallEvent(final LinphoneCall call, final State targetState, int millis) {
+ mHandler.postDelayed(new Runnable() {
+ public void run() {
+ ((LinphoneCallTest)call).state = targetState;
+ onCallStateChanged(call, call.getState(), "simulated delayed state change " + targetState);
+ }
+ }, millis);
+ }
+
+ protected final List getInitialCalls() {
+ List calls = new ArrayList();
+ int duration=0;
+ mTestLc = new LinphoneCoreTest(calls);
+ calls.add(new LinphoneCallTest(duration++, "Tartampion", "06.25.45.98.54", State.StreamsRunning));
+ calls.add(new LinphoneCallTest(duration++, "Durand", "durand@sip.linphone.org", State.StreamsRunning));
+ // calls.add(new LinphoneCallTest(duration++, "Poupoux", "01.58.68.75.32", State.StreamsRunning));
+ calls.add(new LinphoneCallTest(duration++, "Tante Germaine", "+33 1.58.68.75.32", State.Paused));
+ // calls.add(new LinphoneCallTest(duration++, "M. Le président ", "3615 Elysée", State.Paused));
+ calls.add(new LinphoneCallTest(duration++, "01.58.68.75.32", "01.58.68.75.32", State.StreamsRunning));
+ calls.add(new LinphoneCallTest(duration++, "A ringing out guy", "out-ringing@sip.linphone.org", State.OutgoingRinging));
+ calls.add(new LinphoneCallTest(duration++, "A calling in guy", "in@sip.linphone.org", State.IncomingReceived));
+
+ ((LinphoneCallTest)calls.get(0)).inConf=true;
+ ((LinphoneCallTest)calls.get(1)).inConf=true;
+
+ simulateCallAccepted(calls.get(4), 5000);
+ Collections.sort(calls, this);
+
+ mTestLc = new LinphoneCoreTest(calls);
+ return calls;
+ }
+
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ LinphoneManagerWaitHelper.disabled=true;
+ //if (!serviceStarted) startService(new Intent(ACTION_MAIN).setClass(this, LinphoneService.class));
+
+ findViewById(R.id.toggleMuteMic).setOnClickListener(null);
+ findViewById(R.id.toggleSpeaker).setOnClickListener(null);
+
+ super.onCreate(savedInstanceState);
+ }
+
+
+
+
+ private class LinphoneCoreTest implements LinphoneCore {
+ List calls;
+ public LinphoneCoreTest(List calls) {
+ this.calls = new ArrayList(calls);
+ //don't keep only the list reference (concurrent access in onStateChanged).
+ }
+ public void acceptCall(LinphoneCall call) throws LinphoneCoreException {
+ if (isInConference()) {
+ leaveConference();
+ } else {
+ LinphoneCall current = getCurrentCall();
+ if (current != null) pauseCall(current);
+ }
+ changeState(call, State.StreamsRunning);
+ }
+ public void addAuthInfo(LinphoneAuthInfo info) {}
+ public void addFriend(LinphoneFriend lf) throws LinphoneCoreException {}
+ public void addProxyConfig(LinphoneProxyConfig p) throws LinphoneCoreException {}
+ public void adjustSoftwareVolume(int i) {}
+ public void clearAuthInfos() {}
+ public void clearCallLogs() {}
+ public void clearProxyConfigs() {}
+ public LinphoneChatRoom createChatRoom(String to) {return null;}
+ public LinphoneCallParams createDefaultCallParameters() {return null;}
+ public void destroy() {}
+ public void enableEchoCancellation(boolean enable) {}
+ public void enableEchoLimiter(boolean val) {}
+ public void enableIpv6(boolean enable) {}
+ public void enableKeepAlive(boolean enable) {}
+ public void enablePayloadType(PayloadType pt, boolean e)throws LinphoneCoreException {}
+ public void enableSpeaker(boolean value) {}
+ public void enableVideo(boolean vcapEnabled, boolean displayEnabled) {}
+ public PayloadType findPayloadType(String mime, int clockRate) {return null;}
+ public PayloadType[] getAudioCodecs() {return null;}
+ @SuppressWarnings("unchecked")
+ public List getCallLogs() {return null;}
+ public LinphoneCall getCurrentCall() {
+ LinphoneCall active = null;
+ for (LinphoneCall call : calls) {
+ if (call.isInConference() || !call.getState().equals(State.StreamsRunning)) continue;
+ if (active != null) throw new RuntimeException("There are several active calls!");
+ active = call;
+ }
+ return active;
+ }
+ public LinphoneProxyConfig getDefaultProxyConfig() {return null;}
+ public FirewallPolicy getFirewallPolicy() {return null;}
+ public int getPlayLevel() {return 0;}
+ public float getPlaybackGain() {return 0;}
+ public VideoSize getPreferredVideoSize() {return null;}
+ public LinphoneAddress getRemoteAddress() {return null;}
+ public String getRing() {return null;}
+ public Transports getSignalingTransportPorts() {return null;}
+ public String getStunServer() {return null;}
+ public PayloadType[] getVideoCodecs() {return null;}
+ public LinphoneAddress interpretUrl(String d)throws LinphoneCoreException {return null;}
+ public LinphoneCall invite(String d) throws LinphoneCoreException {return null;}
+ public LinphoneCall invite(LinphoneAddress to)throws LinphoneCoreException {return null;}
+ public LinphoneCall inviteAddressWithParams(LinphoneAddress d, LinphoneCallParams p)
+ throws LinphoneCoreException {return null;}
+ public boolean isEchoCancellationEnabled() {return false;}
+ public boolean isInComingInvitePending() {return false;}
+ public boolean isIncall() {return false;}
+ public boolean isKeepAliveEnabled() {return false;}
+ public boolean isMicMuted() {return false;}
+ public boolean isNetworkReachable() {return false;}
+ public boolean isSpeakerEnabled() {return false;}
+ public boolean isVideoEnabled() {return false;}
+ public void iterate() {}
+ public void muteMic(boolean isMuted) {}
+ public boolean pauseAllCalls() {
+ // FIXME may not be right
+ for (LinphoneCall call : calls) {
+ if (!call.isInConference()) {
+ if (call.getState().equals(State.StreamsRunning) || call.getState().equals(State.PausedByRemote))
+ pauseCall(call);
+ }
+ }
+ return false;
+ }
+ public boolean pauseCall(LinphoneCall call) {
+ changeState(call, State.Paused);
+ return true;
+ }
+ public void playDtmf(char number, int duration) {}
+ public boolean resumeCall(LinphoneCall call) {
+ if (isInConference()) leaveConference();
+ pauseAllCalls();
+ changeState(call, State.StreamsRunning);
+ return true;
+ }
+ public void sendDtmf(char number) {}
+ public void setDefaultProxyConfig(LinphoneProxyConfig proxyCfg) {}
+ public void setDownloadBandwidth(int bw) {}
+ public void setDownloadPtime(int ptime) {}
+ public void setFirewallPolicy(FirewallPolicy pol) {}
+ public void setNetworkReachable(boolean isReachable) {}
+ public void setPlayLevel(int level) {}
+ public void setPlaybackGain(float gain) {}
+ public void setPreferredVideoSize(VideoSize vSize) {}
+ public void setPresenceInfo(int m, String a, OnlineStatus s) {}
+ public void setPreviewWindow(Object w) {}
+ public void setRing(String path) {}
+ public void setRootCA(String path) {}
+ public void setSignalingTransportPorts(Transports transports) {}
+ public void setStunServer(String stunServer) {}
+ public void setUploadBandwidth(int bw) {}
+ public void setUploadPtime(int ptime) {}
+ public void setVideoWindow(Object w) {}
+ public void setZrtpSecretsCache(String file) {}
+ public void startEchoCalibration(Object d)throws LinphoneCoreException {}
+ public void stopDtmf() {}
+ public void terminateCall(LinphoneCall call) {
+ changeStateInConf(call, false);
+ changeState(call, State.CallEnd);
+ }
+ public int updateCall(LinphoneCall call, LinphoneCallParams params) {return 0;}
+ private boolean partOfConf;
+ public void enterConference() {
+ pauseAllCalls();
+ partOfConf=true;
+ hackTriggerConfStateUpdate(); // FIXME hack; should have an event?
+ }
+ public void leaveConference() {
+ partOfConf=false;
+ hackTriggerConfStateUpdate(); // FIXME hack; should have an event?
+ }
+ public boolean isInConference() {return partOfConf;}
+ public int getConferenceSize() {
+ int count=0;
+ for (LinphoneCall c : calls) {
+ if (c.isInConference()) count++;
+ }
+ return count;
+ }
+ public void addAllToConference() {
+ for (LinphoneCall c : calls) {
+ final LinphoneCall.State state = c.getState();
+ boolean connectionEstablished = state == State.StreamsRunning || state == State.Paused || state == State.PausedByRemote;
+ if (connectionEstablished) {
+ changeState(c, State.StreamsRunning);
+ changeStateInConf(c, true);
+ }
+ }
+ enterConference();
+ }
+ public void addToConference(LinphoneCall call) {
+ if (getConferenceSize() == 0) {
+ addAllToConference();
+ } else {
+ boolean mergingActiveCall = call.equals(getCurrentCall());
+ changeState(call, State.StreamsRunning);
+ changeStateInConf(call, true);
+ if (mergingActiveCall) enterConference();
+ }
+ }
+ public void terminateConference() {
+ leaveConference();
+ for (LinphoneCall call : calls) {
+ if (!call.isInConference()) continue;
+ terminateCall(call);
+ }
+ }
+ private void changeState(LinphoneCall call, State state) {
+ ((LinphoneCallTest)call).state=state;
+ onCallStateChanged(call, state, "triggered by stub");
+ }
+ private void changeStateInConf(LinphoneCall call, boolean inConf) {
+ ((LinphoneCallTest)call).inConf=inConf;
+ onCallStateChanged(call, call.getState(), "in conf state changed");
+ }
+ public int getCallsNb() {
+ int count=0;
+ for (LinphoneCall call : calls) {
+ if (!State.CallEnd.equals(call.getState())) count++;
+ }
+ return count;
+ }
+ public void terminateAllCalls() {
+ terminateConference();
+ for(LinphoneCall call : calls) {
+ if (!State.CallEnd.equals(call.getState())) terminateCall(call);
+ }
+ }
+ @SuppressWarnings("unchecked")
+ public List getCalls() {
+ return new ArrayList(calls);
+ }
+ public void removeFromConference(LinphoneCall call) {
+ changeStateInConf(call, false);
+ changeState(call, State.Paused);
+ }
+ public void transferCall(LinphoneCall call, String referTo) {
+ terminateCall(call);
+ }
+ public void transferCallToAnother(LinphoneCall callToTransfer, LinphoneCall destination) {
+ if (!State.Paused.equals(callToTransfer.getState())) {
+ throw new RuntimeException("call to transfer should be paused first");
+ }
+ terminateCall(callToTransfer);
+ delayedCallEvent(destination, State.CallEnd, 3000);
+ }
+ public int getVideoDevice() {return 0;}
+ public void setDeviceRotation(int rotation) {}
+ public void setVideoDevice(int id) {}
+ @Override
+ public LinphoneCall findCallFromUri(String uri) {
+ for (LinphoneCall call : calls) {
+ if (call.getRemoteAddress().asStringUriOnly().equals(uri)) {
+ return call;
+ }
+ }
+ return null;
+ }
+ @Override
+ public int getMaxCalls() {
+ return 10;
+ }
+ }
+
+
+ private static class LinphoneAddressTest implements LinphoneAddress {
+ private String displayName;
+ private String number;
+ public LinphoneAddressTest(String niceName, String number) {
+ this.displayName = niceName;
+ this.number = number;}
+ public String asString() {return displayName;}
+ public String asStringUriOnly() {return getUserName() + "@" + getDomain();}
+ public String getDisplayName() {return displayName;}
+ public String getDomain() {return "example.org";}
+ public String getPort() {return "5060";}
+ public int getPortInt() {return 5060;}
+ public String getUserName() {return number;}
+ public void setDisplayName(String name) {}
+ public void setDomain(String domain) {}
+ public void setPort(String port) {}
+ public void setPortInt(int port) {}
+ public void setUserName(String username) {}
+ public void clean() {}
+ @Override public String toString() {return displayName;}
+ }
+
+
+
+ private static class LinphoneCallTest implements LinphoneCall {
+ private boolean inConf;
+ private State state;
+ private LinphoneAddress remoteAddress;
+ private int duration;
+
+ public LinphoneCallTest(int duration, String name, String number, State state) {
+ this.duration = duration;
+ this.state = state;
+ remoteAddress = new LinphoneAddressTest(name, number);
+ }
+
+ public boolean areStreamsEncrypted() {return false;}
+ public void enableCamera(boolean enabled) {}
+ public void enableEchoCancellation(boolean enable) {}
+ public void enableEchoLimiter(boolean enable) {}
+ public String getAuthenticationToken() {return null;}
+ public float getAverageQuality() {return 0;}
+ public LinphoneCallLog getCallLog() {return null;}
+ public LinphoneCallParams getCurrentParamsCopy() {return null;}
+ public float getCurrentQuality() {return 0;}
+ public CallDirection getDirection() {return null;}
+ public int getDuration() {return duration;}
+ public LinphoneAddress getRemoteAddress() {return remoteAddress;}
+ public LinphoneCall getReplacedCall() {return null;}
+ public State getState() {return state;}
+ public boolean isAuthenticationTokenVerified() {return false;}
+ public boolean isEchoCancellationEnabled() {return false;}
+ public boolean isEchoLimiterEnabled() {return false;}
+ public boolean isInConference() { return inConf;}
+ public boolean cameraEnabled() {return false;}
+ }
+
+
+ @Override
+ protected void registerLinphoneListener(boolean register) {
+ // Do nothing (especially, don't call LinphoneManager!)
+ }
+}
+
+