From b179bab0620a323581f52cf6ad2d7913ac3e1368 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20B=C3=A9raud?= <adrien.beraud@savoirfairelinux.com> Date: Thu, 8 Oct 2015 12:04:22 -0400 Subject: [PATCH] android: first publishable implementation of Ring on Android Change-Id: I6d65993a0bbed0ac680d6fe5980aae0fb931116e --- compile.sh | 12 +- configure.sh | 2 +- ring-android/.idea/misc.xml | 26 +- ring-android/app/app.iml | 37 +- ring-android/app/build.gradle | 16 +- ring-android/app/src/main/AndroidManifest.xml | 62 +- .../adapters/AccountSelectionAdapter.java | 23 +- .../cx/ring/adapters/ContactPictureTask.java | 94 +- .../cx/ring/adapters/ContactsAdapter.java | 115 +- .../cx/ring/adapters/DiscussArrayAdapter.java | 106 -- .../ring/adapters/SectionsPagerAdapter.java | 3 +- .../ring/adapters/StarredContactsAdapter.java | 7 +- .../ring/client/AccountEditionActivity.java | 83 +- .../java/cx/ring/client/CallActivity.java | 194 ++-- .../cx/ring/client/ConversationActivity.java | 386 +++++++ .../java/cx/ring/client/HomeActivity.java | 201 ++-- .../ring/client/NewConversationActivity.java | 141 +++ .../fragments/AccountWrapperFragment.java | 100 -- .../fragments/AccountsManagementFragment.java | 187 ++-- .../fragments/AudioManagementFragment.java | 26 +- .../java/cx/ring/fragments/CallFragment.java | 436 +++++--- .../cx/ring/fragments/CallListFragment.java | 350 ++++-- .../fragments/CallableWrapperFragment.java | 15 +- .../ring/fragments/ConferenceDFragment.java | 12 +- .../ring/fragments/ContactListFragment.java | 154 +-- .../DetailsHistoryEntryFragment.java | 7 +- .../cx/ring/fragments/DialingFragment.java | 32 +- .../fragments/GeneralAccountFragment.java | 21 +- .../cx/ring/fragments/HistoryFragment.java | 28 +- .../java/cx/ring/fragments/IMFragment.java | 193 ---- .../java/cx/ring/fragments/MenuFragment.java | 80 +- .../fragments/NestedSettingsFragment.java | 22 +- .../fragments/SecurityAccountFragment.java | 17 +- .../cx/ring/fragments/TransferDFragment.java | 12 +- .../java/cx/ring/history/DatabaseHelper.java | 15 +- .../java/cx/ring/history/HistoryCall.java | 45 +- .../java/cx/ring/history/HistoryEntry.java | 94 +- .../java/cx/ring/history/HistoryManager.java | 25 +- .../java/cx/ring/history/HistoryText.java | 187 ++++ .../java/cx/ring/loaders/AccountsLoader.java | 13 +- .../java/cx/ring/loaders/ContactsLoader.java | 89 +- .../java/cx/ring/loaders/HistoryLoader.java | 5 +- .../src/main/java/cx/ring/model/Bubble.java | 2 +- .../java/cx/ring/model/BubbleContact.java | 4 +- .../main/java/cx/ring/model/BubblesView.java | 12 +- .../main/java/cx/ring/model/CallContact.java | 214 ++-- .../main/java/cx/ring/model/Conference.java | 40 +- .../main/java/cx/ring/model/Conversation.java | 232 ++++ .../java/cx/ring/model/SecureSipCall.java | 6 +- .../src/main/java/cx/ring/model/SipCall.java | 135 ++- .../main/java/cx/ring/model/SipMessage.java | 72 -- .../{SipMessage.aidl => TextMessage.aidl} | 2 +- .../main/java/cx/ring/model/TextMessage.java | 294 ++++++ .../java/cx/ring/model/account/Account.java | 19 +- .../cx/ring/model/account/AccountDetail.java | 21 +- .../model/account/AccountDetailAdvanced.java | 21 +- .../model/account/AccountDetailBasic.java | 30 +- .../ring/model/account/AccountDetailSrtp.java | 2 +- .../ring/model/account/AccountDetailTls.java | 2 +- .../cx/ring/service/CallManagerCallBack.java | 187 ++-- .../service/ConfigurationManagerCallback.java | 23 +- .../java/cx/ring/service/ISipService.aidl | 4 +- .../java/cx/ring/service/LocalService.java | 813 ++++++++++++++ .../main/java/cx/ring/service/SipService.java | 151 ++- .../main/java/cx/ring/utils/MediaManager.java | 2 +- .../java/cx/ring/utils/SipNotifications.java | 24 +- .../main/java/cx/ring/utils/Utilities.java | 35 + .../java/cx/ring/views/CallPaneLayout.java | 3 +- .../cx/ring/views/HalfCircleImageView.java | 2 +- .../cx/ring/views/NumberPickerPreference.java | 2 +- .../views/QuadNumberPickerPreference.java | 2 +- .../views/SwipeListViewTouchListener.java | 409 -------- .../stickylistheaders/AdapterWrapper.java | 225 ---- .../ApiLevelTooLowException.java | 11 - .../CheckableWrapperView.java | 31 - .../SectionIndexerAdapterWrapper.java | 32 - .../StickyListHeadersAdapter.java | 38 - .../StickyListHeadersListView.java | 993 ------------------ .../views/stickylistheaders/WrapperView.java | 150 --- .../stickylistheaders/WrapperViewList.java | 176 ---- ring-android/app/src/main/jni/Android.mk | 7 +- ring-android/app/src/main/jni/Application.mk | 2 +- .../res/drawable-hdpi/ic_action_end_call.png | Bin 442 -> 0 bytes .../drawable-hdpi/ic_call_end_black_24dp.png | Bin 0 -> 303 bytes .../drawable-hdpi/ic_call_end_black_36dp.png | Bin 0 -> 403 bytes .../drawable-hdpi/ic_call_end_white_24dp.png | Bin 0 -> 314 bytes .../res/drawable-hdpi/ic_call_white_24dp.png | Bin 0 -> 340 bytes .../res/drawable-hdpi/ic_chat_white_24dp.png | Bin 0 -> 168 bytes .../res/drawable-hdpi/ic_lock_white_24dp.png | Bin 0 -> 309 bytes .../res/drawable-hdpi/ic_send_black_24dp.png | Bin 0 -> 250 bytes .../res/drawable-hdpi/ic_share_white_24dp.png | Bin 0 -> 397 bytes .../drawable-hdpi/ic_videocam_white_24dp.png | Bin 0 -> 173 bytes .../ic_send_black_24dp.png | Bin 0 -> 250 bytes .../ic_send_black_24dp.png | Bin 0 -> 204 bytes .../ic_send_black_24dp.png | Bin 0 -> 341 bytes .../ic_send_black_24dp.png | Bin 0 -> 414 bytes .../ic_send_black_24dp.png | Bin 0 -> 551 bytes .../res/drawable-mdpi/ic_action_end_call.png | Bin 342 -> 0 bytes .../drawable-mdpi/ic_call_end_black_24dp.png | Bin 0 -> 235 bytes .../drawable-mdpi/ic_call_end_black_36dp.png | Bin 0 -> 303 bytes .../drawable-mdpi/ic_call_end_white_24dp.png | Bin 0 -> 235 bytes .../res/drawable-mdpi/ic_call_white_24dp.png | Bin 0 -> 246 bytes .../res/drawable-mdpi/ic_chat_white_24dp.png | Bin 0 -> 133 bytes .../res/drawable-mdpi/ic_lock_white_24dp.png | Bin 0 -> 208 bytes .../res/drawable-mdpi/ic_send_black_24dp.png | Bin 0 -> 205 bytes .../res/drawable-mdpi/ic_share_white_24dp.png | Bin 0 -> 268 bytes .../drawable-mdpi/ic_videocam_white_24dp.png | Bin 0 -> 131 bytes .../res/drawable-xhdpi/ic_action_end_call.png | Bin 552 -> 0 bytes .../drawable-xhdpi/ic_call_end_black_24dp.png | Bin 0 -> 371 bytes .../drawable-xhdpi/ic_call_end_black_36dp.png | Bin 0 -> 515 bytes .../drawable-xhdpi/ic_call_end_white_24dp.png | Bin 0 -> 389 bytes .../res/drawable-xhdpi/ic_call_white_24dp.png | Bin 0 -> 420 bytes .../res/drawable-xhdpi/ic_chat_white_24dp.png | Bin 0 -> 205 bytes .../res/drawable-xhdpi/ic_lock_white_24dp.png | Bin 0 -> 372 bytes .../res/drawable-xhdpi/ic_send_black_24dp.png | Bin 0 -> 333 bytes .../drawable-xhdpi/ic_share_white_24dp.png | Bin 0 -> 496 bytes .../drawable-xhdpi/ic_videocam_white_24dp.png | Bin 0 -> 178 bytes .../drawable-xxhdpi/ic_action_end_call.png | Bin 763 -> 0 bytes .../ic_call_end_black_24dp.png | Bin 0 -> 515 bytes .../ic_call_end_black_36dp.png | Bin 0 -> 721 bytes .../ic_call_end_white_24dp.png | Bin 0 -> 553 bytes .../drawable-xxhdpi/ic_call_white_24dp.png | Bin 0 -> 597 bytes .../drawable-xxhdpi/ic_chat_white_24dp.png | Bin 0 -> 270 bytes .../drawable-xxhdpi/ic_lock_white_24dp.png | Bin 0 -> 540 bytes .../drawable-xxhdpi/ic_send_black_24dp.png | Bin 0 -> 412 bytes .../drawable-xxhdpi/ic_share_white_24dp.png | Bin 0 -> 698 bytes .../ic_videocam_white_24dp.png | Bin 0 -> 234 bytes .../ic_call_end_black_24dp.png | Bin 0 -> 663 bytes .../ic_call_end_black_36dp.png | Bin 0 -> 954 bytes .../ic_call_end_white_24dp.png | Bin 0 -> 712 bytes .../drawable-xxxhdpi/ic_call_white_24dp.png | Bin 0 -> 778 bytes .../drawable-xxxhdpi/ic_chat_white_24dp.png | Bin 0 -> 344 bytes .../drawable-xxxhdpi/ic_lock_white_24dp.png | Bin 0 -> 702 bytes .../drawable-xxxhdpi/ic_send_black_24dp.png | Bin 0 -> 543 bytes .../drawable-xxxhdpi/ic_share_white_24dp.png | Bin 0 -> 938 bytes .../ic_videocam_white_24dp.png | Bin 0 -> 290 bytes .../app/src/main/res/drawable/call_button.xml | 14 +- .../main/res/drawable/drawer_disc_handle.xml | 24 +- .../res/drawable/item_contact_selector.xml | 27 +- .../res/drawable/item_generic_selector.xml | 6 +- .../res/drawable/item_history_selector.xml | 29 +- .../res/drawable/toggle_speaker_selector.xml | 2 +- .../res/layout/activity_account_settings.xml | 12 +- .../main/res/layout/activity_call_layout.xml | 19 +- .../app/src/main/res/layout/activity_home.xml | 60 +- .../res/layout/activity_new_conversation.xml | 16 + .../main/res/layout/frag_accounts_list.xml | 6 +- .../src/main/res/layout/frag_audio_mgmt.xml | 2 +- .../app/src/main/res/layout/frag_call.xml | 229 ++-- .../src/main/res/layout/frag_call_list.xml | 50 +- .../src/main/res/layout/frag_contact_list.xml | 56 +- .../res/layout/frag_contact_list_header.xml | 71 +- .../src/main/res/layout/frag_conversation.xml | 68 ++ .../app/src/main/res/layout/frag_home.xml | 11 +- .../src/main/res/layout/frag_imessaging.xml | 2 +- .../src/main/res/layout/frag_menu_header.xml | 2 +- .../app/src/main/res/layout/header.xml | 27 +- .../app/src/main/res/layout/item_account.xml | 10 +- .../src/main/res/layout/item_account_pref.xml | 12 +- .../main/res/layout/item_account_selected.xml | 11 +- .../app/src/main/res/layout/item_calllist.xml | 70 +- .../app/src/main/res/layout/item_codec.xml | 6 +- .../app/src/main/res/layout/item_contact.xml | 133 +-- .../main/res/layout/item_contact_starred.xml | 26 +- .../app/src/main/res/layout/item_history.xml | 19 +- .../app/src/main/res/layout/item_textmsg.xml | 155 +++ .../app/src/main/res/menu/ac_call.xml | 2 +- .../main/res/menu/conversation_actions.xml | 15 + .../src/main/res/menu/newconv_option_menu.xml | 8 + .../app/src/main/res/values-land/dimens.xml | 36 + .../src/main/res/values-sw600dp/dimens.xml | 36 + .../app/src/main/res/values-v21/dimens.xml | 37 + .../app/src/main/res/values-v21/styles.xml | 13 +- .../app/src/main/res/values/colors.xml | 30 +- .../app/src/main/res/values/dimens.xml | 8 +- .../app/src/main/res/values/strings.xml | 8 +- .../app/src/main/res/values/styles.xml | 37 +- ring-android/build.gradle | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 4 +- ring-android/project.properties | 2 +- ring-android/ring-android.iml | 4 +- 181 files changed, 5258 insertions(+), 4262 deletions(-) delete mode 100644 ring-android/app/src/main/java/cx/ring/adapters/DiscussArrayAdapter.java create mode 100644 ring-android/app/src/main/java/cx/ring/client/ConversationActivity.java create mode 100644 ring-android/app/src/main/java/cx/ring/client/NewConversationActivity.java delete mode 100644 ring-android/app/src/main/java/cx/ring/fragments/AccountWrapperFragment.java delete mode 100644 ring-android/app/src/main/java/cx/ring/fragments/IMFragment.java create mode 100644 ring-android/app/src/main/java/cx/ring/history/HistoryText.java create mode 100644 ring-android/app/src/main/java/cx/ring/model/Conversation.java delete mode 100644 ring-android/app/src/main/java/cx/ring/model/SipMessage.java rename ring-android/app/src/main/java/cx/ring/model/{SipMessage.aidl => TextMessage.aidl} (52%) create mode 100644 ring-android/app/src/main/java/cx/ring/model/TextMessage.java create mode 100644 ring-android/app/src/main/java/cx/ring/service/LocalService.java create mode 100644 ring-android/app/src/main/java/cx/ring/utils/Utilities.java delete mode 100644 ring-android/app/src/main/java/cx/ring/views/SwipeListViewTouchListener.java delete mode 100644 ring-android/app/src/main/java/cx/ring/views/stickylistheaders/AdapterWrapper.java delete mode 100644 ring-android/app/src/main/java/cx/ring/views/stickylistheaders/ApiLevelTooLowException.java delete mode 100644 ring-android/app/src/main/java/cx/ring/views/stickylistheaders/CheckableWrapperView.java delete mode 100644 ring-android/app/src/main/java/cx/ring/views/stickylistheaders/SectionIndexerAdapterWrapper.java delete mode 100644 ring-android/app/src/main/java/cx/ring/views/stickylistheaders/StickyListHeadersAdapter.java delete mode 100644 ring-android/app/src/main/java/cx/ring/views/stickylistheaders/StickyListHeadersListView.java delete mode 100644 ring-android/app/src/main/java/cx/ring/views/stickylistheaders/WrapperView.java delete mode 100644 ring-android/app/src/main/java/cx/ring/views/stickylistheaders/WrapperViewList.java delete mode 100644 ring-android/app/src/main/res/drawable-hdpi/ic_action_end_call.png create mode 100644 ring-android/app/src/main/res/drawable-hdpi/ic_call_end_black_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-hdpi/ic_call_end_black_36dp.png create mode 100644 ring-android/app/src/main/res/drawable-hdpi/ic_call_end_white_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-hdpi/ic_call_white_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-hdpi/ic_chat_white_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-hdpi/ic_lock_white_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-hdpi/ic_send_black_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-hdpi/ic_share_white_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-hdpi/ic_videocam_white_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-ldrtl-hdpi/ic_send_black_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-ldrtl-mdpi/ic_send_black_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-ldrtl-xhdpi/ic_send_black_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-ldrtl-xxhdpi/ic_send_black_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-ldrtl-xxxhdpi/ic_send_black_24dp.png delete mode 100644 ring-android/app/src/main/res/drawable-mdpi/ic_action_end_call.png create mode 100644 ring-android/app/src/main/res/drawable-mdpi/ic_call_end_black_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-mdpi/ic_call_end_black_36dp.png create mode 100644 ring-android/app/src/main/res/drawable-mdpi/ic_call_end_white_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-mdpi/ic_call_white_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-mdpi/ic_chat_white_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-mdpi/ic_lock_white_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-mdpi/ic_send_black_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-mdpi/ic_share_white_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-mdpi/ic_videocam_white_24dp.png delete mode 100644 ring-android/app/src/main/res/drawable-xhdpi/ic_action_end_call.png create mode 100644 ring-android/app/src/main/res/drawable-xhdpi/ic_call_end_black_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-xhdpi/ic_call_end_black_36dp.png create mode 100644 ring-android/app/src/main/res/drawable-xhdpi/ic_call_end_white_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-xhdpi/ic_call_white_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-xhdpi/ic_chat_white_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-xhdpi/ic_lock_white_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-xhdpi/ic_send_black_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-xhdpi/ic_share_white_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-xhdpi/ic_videocam_white_24dp.png delete mode 100644 ring-android/app/src/main/res/drawable-xxhdpi/ic_action_end_call.png create mode 100644 ring-android/app/src/main/res/drawable-xxhdpi/ic_call_end_black_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-xxhdpi/ic_call_end_black_36dp.png create mode 100644 ring-android/app/src/main/res/drawable-xxhdpi/ic_call_end_white_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-xxhdpi/ic_call_white_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-xxhdpi/ic_chat_white_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-xxhdpi/ic_lock_white_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-xxhdpi/ic_send_black_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-xxhdpi/ic_share_white_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-xxhdpi/ic_videocam_white_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-xxxhdpi/ic_call_end_black_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-xxxhdpi/ic_call_end_black_36dp.png create mode 100644 ring-android/app/src/main/res/drawable-xxxhdpi/ic_call_end_white_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-xxxhdpi/ic_call_white_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-xxxhdpi/ic_chat_white_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-xxxhdpi/ic_lock_white_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-xxxhdpi/ic_send_black_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-xxxhdpi/ic_share_white_24dp.png create mode 100644 ring-android/app/src/main/res/drawable-xxxhdpi/ic_videocam_white_24dp.png create mode 100644 ring-android/app/src/main/res/layout/activity_new_conversation.xml create mode 100644 ring-android/app/src/main/res/layout/frag_conversation.xml create mode 100644 ring-android/app/src/main/res/layout/item_textmsg.xml create mode 100644 ring-android/app/src/main/res/menu/conversation_actions.xml create mode 100644 ring-android/app/src/main/res/menu/newconv_option_menu.xml create mode 100644 ring-android/app/src/main/res/values-land/dimens.xml create mode 100644 ring-android/app/src/main/res/values-sw600dp/dimens.xml create mode 100644 ring-android/app/src/main/res/values-v21/dimens.xml diff --git a/compile.sh b/compile.sh index 137f2c8f2..f2913d1e5 100755 --- a/compile.sh +++ b/compile.sh @@ -122,7 +122,7 @@ case "$REL" in if [ "${HAVE_64}" = 1 ];then ANDROID_API=android-21 else - ANDROID_API=android-15 + ANDROID_API=android-16 fi CXXSTL="/"${GCCVER} ;; @@ -131,7 +131,7 @@ case "$REL" in echo "You need the NDKv10 or later for 64 bits build" exit 1 fi - ANDROID_API=android-15 + ANDROID_API=android-16 CXXSTL="/"${GCCVER} ;; 7|8|*) @@ -174,7 +174,7 @@ ANDROID_PATH="`pwd`" if [ "$FETCH" = 1 ] then # 1/ dring - TESTED_HASH=0012eab6f3bbc2df4461b85df788b86be1299f11 + TESTED_HASH=6f868d66377a2305120986ce79cc03411080d196 if [ ! -d "ring-daemon" ]; then echo "ring daemon source not found, cloning" git clone https://gerrit-ring.savoirfairelinux.com/ring-daemon.git @@ -326,8 +326,8 @@ else RELEASE=0 fi -echo "EXTRA_CFLAGS= -g ${EXTRA_CFLAGS}" >> config.mak -echo "EXTRA_CXXFLAGS= -g ${EXTRA_CXXFLAGS}" >> config.mak +echo "EXTRA_CFLAGS= -g -fpic ${EXTRA_CFLAGS}" >> config.mak +echo "EXTRA_CXXFLAGS= -g -fpic ${EXTRA_CXXFLAGS}" >> config.mak echo "EXTRA_LDFLAGS= ${EXTRA_LDFLAGS}" >> config.mak export RING_EXTRA_CFLAGS="${EXTRA_CFLAGS}" export RING_EXTRA_CXXFLAGS="${EXTRA_CXXFLAGS}" @@ -413,7 +413,7 @@ cd ../.. echo "Building Ring for Android ${PWD}" make $CLEAN -make -j1 TARGET_TUPLE=$TARGET_TUPLE PLATFORM_SHORT_ARCH=$PLATFORM_SHORT_ARCH CXXSTL=$CXXSTL RELEASE=$RELEASE $TARGET +ANDROID_ABI="${ANDROID_ABI}" make -j1 TARGET_TUPLE=$TARGET_TUPLE PLATFORM_SHORT_ARCH=$PLATFORM_SHORT_ARCH CXXSTL=$CXXSTL RELEASE=$RELEASE $TARGET # # Exporting a environment script with all the necessary variables diff --git a/configure.sh b/configure.sh index ad3ee46d4..6fd1d3234 100755 --- a/configure.sh +++ b/configure.sh @@ -18,7 +18,7 @@ fi RING_SOURCEDIR=`cd ..; pwd` -CFLAGS="-g -O2 -fstrict-aliasing -funsafe-math-optimizations" +CFLAGS="-fpic -g -O2 -fstrict-aliasing -funsafe-math-optimizations" if [ -n "$HAVE_ARM" -a ! -n "$HAVE_64" ]; then CFLAGS="${CFLAGS} -mlong-calls" fi diff --git a/ring-android/.idea/misc.xml b/ring-android/.idea/misc.xml index 70b496f78..d7bb3975f 100644 --- a/ring-android/.idea/misc.xml +++ b/ring-android/.idea/misc.xml @@ -3,7 +3,31 @@ <component name="EntryPointsManager"> <entry_points version="2.0" /> </component> - <component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="false" assert-keyword="true" jdk-15="true" project-jdk-name="Android API 22 Platform" project-jdk-type="Android SDK"> + <component name="NullableNotNullManager"> + <option name="myDefaultNullable" value="android.support.annotation.Nullable" /> + <option name="myDefaultNotNull" value="android.support.annotation.NonNull" /> + <option name="myNullables"> + <value> + <list size="4"> + <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" /> + <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" /> + <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" /> + <item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" /> + </list> + </value> + </option> + <option name="myNotNulls"> + <value> + <list size="4"> + <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" /> + <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" /> + <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" /> + <item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" /> + </list> + </value> + </option> + </component> + <component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="false" assert-keyword="true" jdk-15="true" project-jdk-name="1.7" project-jdk-type="JavaSDK"> <output url="file://$PROJECT_DIR$/out" /> </component> </project> \ No newline at end of file diff --git a/ring-android/app/app.iml b/ring-android/app/app.iml index ece991d93..3502718a0 100644 --- a/ring-android/app/app.iml +++ b/ring-android/app/app.iml @@ -12,10 +12,12 @@ <option name="SELECTED_TEST_ARTIFACT" value="_android_test_" /> <option name="ASSEMBLE_TASK_NAME" value="assembleDebug" /> <option name="COMPILE_JAVA_TASK_NAME" value="compileDebugSources" /> - <option name="SOURCE_GEN_TASK_NAME" value="generateDebugSources" /> <option name="ASSEMBLE_TEST_TASK_NAME" value="assembleDebugAndroidTest" /> <option name="COMPILE_JAVA_TEST_TASK_NAME" value="compileDebugAndroidTestSources" /> - <option name="TEST_SOURCE_GEN_TASK_NAME" value="generateDebugAndroidTestSources" /> + <afterSyncTasks> + <task>generateDebugAndroidTestSources</task> + <task>generateDebugSources</task> + </afterSyncTasks> <option name="ALLOW_USER_CONFIGURATION" value="false" /> <option name="MANIFEST_FILE_RELATIVE_PATH" value="/src/main/AndroidManifest.xml" /> <option name="RES_FOLDER_RELATIVE_PATH" value="/src/main/res" /> @@ -24,7 +26,7 @@ </configuration> </facet> </component> - <component name="NewModuleRootManager" inherit-compiler-output="false"> + <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="false"> <output url="file://$MODULE_DIR$/build/intermediates/classes/debug" /> <output-test url="file://$MODULE_DIR$/build/intermediates/classes/androidTest/debug" /> <exclude-output /> @@ -34,13 +36,13 @@ <sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/debug" isTestSource="false" generated="true" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/debug" isTestSource="false" generated="true" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/debug" type="java-resource" /> - <sourceFolder url="file://$MODULE_DIR$/build/generated/res/generated/debug" type="java-resource" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/debug" type="java-resource" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/source/r/androidTest/debug" isTestSource="true" generated="true" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/source/aidl/androidTest/debug" isTestSource="true" generated="true" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/source/buildConfig/androidTest/debug" isTestSource="true" generated="true" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/source/rs/androidTest/debug" isTestSource="true" generated="true" /> <sourceFolder url="file://$MODULE_DIR$/build/generated/res/rs/androidTest/debug" type="java-test-resource" /> - <sourceFolder url="file://$MODULE_DIR$/build/generated/res/generated/androidTest/debug" type="java-test-resource" /> + <sourceFolder url="file://$MODULE_DIR$/build/generated/res/resValues/androidTest/debug" type="java-test-resource" /> <sourceFolder url="file://$MODULE_DIR$/build-types/debug/res" type="java-resource" /> <sourceFolder url="file://$MODULE_DIR$/build-types/debug/resources" type="java-resource" /> <sourceFolder url="file://$MODULE_DIR$/build-types/debug/assets" type="java-resource" /> @@ -67,11 +69,12 @@ <excludeFolder url="file://$MODULE_DIR$/build/intermediates/dependency-cache" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/dex-cache" /> - <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/appcompat-v7/22.2.0/jars" /> - <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/design/22.2.0/jars" /> - <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-v13/22.2.0/jars" /> - <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-v4/22.2.0/jars" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/appcompat-v7/23.0.1/jars" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/design/23.0.1/jars" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-v13/23.0.1/jars" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.android.support/support-v4/23.0.1/jars" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/com.astuetz/pagerslidingtabstrip/1.0.1/jars" /> + <excludeFolder url="file://$MODULE_DIR$/build/intermediates/exploded-aar/se.emilsjolander/stickylistheaders/2.7.0/jars" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/incremental" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/jacoco" /> <excludeFolder url="file://$MODULE_DIR$/build/intermediates/javaResources" /> @@ -87,16 +90,18 @@ <excludeFolder url="file://$MODULE_DIR$/build/outputs" /> <excludeFolder url="file://$MODULE_DIR$/build/tmp" /> </content> - <orderEntry type="jdk" jdkName="Android API 22 Platform" jdkType="Android SDK" /> + <orderEntry type="jdk" jdkName="Android API 23 Platform" jdkType="Android SDK" /> <orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="library" exported="" name="ormlite-android-4.48" level="project" /> + <orderEntry type="library" exported="" name="libphonenumber-7.0.11" level="project" /> <orderEntry type="library" exported="" name="pagerslidingtabstrip-1.0.1" level="project" /> <orderEntry type="library" exported="" name="ormlite-core-4.48" level="project" /> - <orderEntry type="library" exported="" name="robotium-solo-5.0.1" level="project" /> - <orderEntry type="library" exported="" name="support-annotations-22.2.0" level="project" /> - <orderEntry type="library" exported="" name="support-v13-22.2.0" level="project" /> - <orderEntry type="library" exported="" name="support-v4-22.2.0" level="project" /> - <orderEntry type="library" exported="" name="design-22.2.0" level="project" /> - <orderEntry type="library" exported="" name="appcompat-v7-22.2.0" level="project" /> + <orderEntry type="library" exported="" name="support-v13-23.0.1" level="project" /> + <orderEntry type="library" exported="" name="stickylistheaders-2.7.0" level="project" /> + <orderEntry type="library" exported="" name="support-v4-23.0.1" level="project" /> + <orderEntry type="library" exported="" name="robotium-solo-5.4.1" level="project" /> + <orderEntry type="library" exported="" name="design-23.0.1" level="project" /> + <orderEntry type="library" exported="" name="appcompat-v7-23.0.1" level="project" /> + <orderEntry type="library" exported="" name="support-annotations-23.0.1" level="project" /> </component> </module> \ No newline at end of file diff --git a/ring-android/app/build.gradle b/ring-android/app/build.gradle index e1bcbff06..2ca1c1988 100644 --- a/ring-android/app/build.gradle +++ b/ring-android/app/build.gradle @@ -1,18 +1,20 @@ apply plugin: 'com.android.application' dependencies { - compile fileTree(dir: 'libs', include: '*.jar') + compile fileTree(include: '*.jar', dir: 'libs') compile 'com.j256.ormlite:ormlite-core:4.48' compile 'com.j256.ormlite:ormlite-android:4.48' - compile "com.android.support:support-v13:22.2.0" - compile 'com.android.support:design:22.2.0' + compile 'com.android.support:support-v13:23.0.+' + compile 'com.android.support:design:23.0.+' compile 'com.jayway.android.robotium:robotium-solo:5.4.1' compile 'com.astuetz:pagerslidingtabstrip:1.0.1' + compile 'com.googlecode.libphonenumber:libphonenumber:7.0.11' + compile 'se.emilsjolander:stickylistheaders:2.7.+' } android { - compileSdkVersion 22 - buildToolsVersion "22.0.1" + compileSdkVersion 23 + buildToolsVersion "23.0.1" sourceSets { main { @@ -33,4 +35,8 @@ android { debug.setRoot('build-types/debug') release.setRoot('build-types/release') } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_7 + targetCompatibility JavaVersion.VERSION_1_7 + } } \ No newline at end of file diff --git a/ring-android/app/src/main/AndroidManifest.xml b/ring-android/app/src/main/AndroidManifest.xml index ceb3bd83e..321aa6848 100644 --- a/ring-android/app/src/main/AndroidManifest.xml +++ b/ring-android/app/src/main/AndroidManifest.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- -Copyright (C) 2004-2014 Savoir-Faire Linux Inc. +Copyright (C) 2004-2015 Savoir-Faire Linux Inc. Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com> Adrien Beraud <adrien.beraud@gmail.com> @@ -36,6 +36,10 @@ as that of the covered work. android:versionCode="14" android:versionName="2.0.0" > + <uses-sdk + android:minSdkVersion="16" + android:targetSdkVersion="23" /> + <supports-screens android:anyDensity="true" android:largeScreens="true" @@ -43,10 +47,6 @@ as that of the covered work. android:smallScreens="true" android:xlargeScreens="true" /> - <uses-sdk - android:minSdkVersion="15" - android:targetSdkVersion="22" /> - <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> @@ -67,6 +67,8 @@ as that of the covered work. <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.READ_LOGS" /> <uses-permission android:name="android.permission.USE_SIP" /> + <uses-permission android:name="android.permission.GET_TASKS" /> + <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <uses-feature android:name="android.hardware.wifi" @@ -89,7 +91,7 @@ as that of the covered work. android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <activity - android:name="cx.ring.client.HomeActivity" + android:name=".client.HomeActivity" android:label="@string/title_activity_sflphone_home" android:screenOrientation="portrait" android:theme="@style/AppThemeWithOverlay" @@ -101,40 +103,74 @@ as that of the covered work. </intent-filter> </activity> <activity - android:name="cx.ring.client.AccountWizard" + android:name=".client.AccountWizard" android:screenOrientation="portrait" android:theme="@style/AppThemeWithoutOverlay" > <meta-data android:name="android.support.PARENT_ACTIVITY" - android:value="cx.ring.client.AccountPreferenceActivity" /> + android:value="cx.ring.client.AccountEditionActivity" /> </activity> <activity - android:name="cx.ring.client.AccountEditionActivity" + android:name=".client.AccountEditionActivity" android:label="@string/app_name" android:screenOrientation="portrait" android:theme="@style/AppThemeWithoutOverlay" /> <activity - android:name="cx.ring.client.DetailHistoryActivity" + android:name=".client.DetailHistoryActivity" android:label="@string/app_name" android:screenOrientation="portrait" android:theme="@style/AppThemeWithoutOverlay" /> + + <activity android:name=".client.NewConversationActivity" android:theme="@style/AppThemeWithoutOverlay" android:label="@string/app_name" > + </activity> + <activity - android:name="cx.ring.client.CallActivity" + android:name=".client.CallActivity" android:label="@string/app_name" android:screenOrientation="portrait" android:theme="@style/AppThemeWithoutOverlay" android:windowSoftInputMode="adjustPan" > <intent-filter> <action android:name="android.intent.action.CALL_PRIVILEGED" /> + <action android:name="android.intent.action.CALL" /> + <action android:name="android.intent.action.DIAL" /> + <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <data android:scheme="tel" /> + <data android:scheme="ring" /> + <data android:scheme="sip" /> + </intent-filter> + <intent-filter> + <action android:name="android.intent.action.CALL" /> + <action android:name="android.intent.action.DIAL" /> + + <category android:name="android.intent.category.DEFAULT" /> + + <data android:mimeType="vnd.android.cursor.item/phone" /> + <data android:mimeType="vnd.android.cursor.item/phone_v2" /> + <data android:mimeType="vnd.android.cursor.item/person" /> </intent-filter> </activity> + <activity + android:name=".client.ConversationActivity" + android:label="@string/app_name" + android:parentActivityName=".client.HomeActivity" + android:screenOrientation="portrait" + android:theme="@style/AppThemeWithoutOverlay" + android:windowSoftInputMode="adjustResize" > + </activity> <service - android:name="cx.ring.service.SipService" + android:name=".service.LocalService" + android:exported="false" > + <intent-filter> + <action android:name=".service.LocalService" /> + </intent-filter> + </service> + <service + android:name=".service.SipService" android:exported="false" > <intent-filter> <action android:name=".service.SipService" /> @@ -142,4 +178,4 @@ as that of the covered work. </service> </application> -</manifest> \ No newline at end of file +</manifest> diff --git a/ring-android/app/src/main/java/cx/ring/adapters/AccountSelectionAdapter.java b/ring-android/app/src/main/java/cx/ring/adapters/AccountSelectionAdapter.java index 003f34737..6a2ab3172 100644 --- a/ring-android/app/src/main/java/cx/ring/adapters/AccountSelectionAdapter.java +++ b/ring-android/app/src/main/java/cx/ring/adapters/AccountSelectionAdapter.java @@ -32,12 +32,11 @@ package cx.ring.adapters; import java.io.File; -import java.util.ArrayList; +import java.util.List; import cx.ring.R; import android.content.Context; -import android.graphics.Color; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -51,12 +50,12 @@ public class AccountSelectionAdapter extends BaseAdapter { private static final String TAG = AccountSelectionAdapter.class.getSimpleName(); - ArrayList<Account> accounts; + List<Account> accounts; Context mContext; int selectedAccount = -1; static final String DEFAULT_ACCOUNT_ID = "IP2IP"; - public AccountSelectionAdapter(Context cont, ArrayList<Account> newList) { + public AccountSelectionAdapter(Context cont, List<Account> newList) { super(); accounts = newList; mContext = cont; @@ -133,7 +132,11 @@ public class AccountSelectionAdapter extends BaseAdapter { private void updateAccountView(AccountView entryView, Account acc) { entryView.alias.setText(acc.getAlias()); - entryView.host.setText(acc.getHost()/*+ " - " + acc.getRegistered_state()*/); + if (acc.isRing()) { + entryView.host.setText(acc.getBasicDetails().getUsername()); + } else { + entryView.host.setText(acc.getBasicDetails().getUsername() + "@" + acc.getBasicDetails().getHostname()); + } entryView.error.setVisibility(acc.isRegistered() ? View.GONE : View.VISIBLE); } @@ -171,7 +174,13 @@ public class AccountSelectionAdapter extends BaseAdapter { } - public void addAll(ArrayList<Account> results) { + public void addAll(List<Account> results) { + accounts.addAll(results); + notifyDataSetChanged(); + } + + public void replaceAll(List<Account> results) { + accounts.clear(); accounts.addAll(results); notifyDataSetChanged(); } @@ -184,7 +193,7 @@ public class AccountSelectionAdapter extends BaseAdapter { for (Account a : accounts) { if (a.getAccountID().contentEquals(accoundID)) { - a.setRegistered_state(state, code); + a.setRegistrationState(state, code); Log.i(TAG, "updateAccount " + accoundID + " " + code); notifyDataSetChanged(); return; diff --git a/ring-android/app/src/main/java/cx/ring/adapters/ContactPictureTask.java b/ring-android/app/src/main/java/cx/ring/adapters/ContactPictureTask.java index 5596a0636..0377dc7f5 100644 --- a/ring-android/app/src/main/java/cx/ring/adapters/ContactPictureTask.java +++ b/ring-android/app/src/main/java/cx/ring/adapters/ContactPictureTask.java @@ -32,6 +32,8 @@ package cx.ring.adapters; import java.io.InputStream; +import java.lang.ref.WeakReference; +import java.util.ArrayList; import cx.ring.R; import cx.ring.model.CallContact; @@ -51,20 +53,55 @@ import android.graphics.RectF; import android.graphics.Shader; import android.net.Uri; import android.provider.ContactsContract; +import android.util.Log; +import android.util.LruCache; +import android.view.animation.AnimationUtils; import android.widget.ImageView; public class ContactPictureTask implements Runnable { - private ImageView view; - private CallContact contact; + static final String TAG = ContactPictureTask.class.getSimpleName(); + + private final WeakReference<ImageView> view; + private final CallContact contact; + private ContentResolver cr; - private static int PADDING = 5; + private final Resources res; + //private PictureLoadedCallback cb; + private final ArrayList<PictureLoadedCallback> callbacks = new ArrayList<>(1); + + //int w = photo_bmp.getWidth(), h = photo_bmp.getHeight(); + private final int vw, vh; + + public void addCallback(PictureLoadedCallback cb) { + //this.cb = callback; + synchronized (callbacks) { + view.clear(); + callbacks.add(cb); + } + } // private final String TAG = ContactPictureTask.class.getSimpleName(); + public interface PictureLoadedCallback { + void onPictureLoaded(Bitmap bmp); + }; + public ContactPictureTask(Context context, ImageView element, CallContact item) { contact = item; cr = context.getContentResolver(); - view = element; + res = context.getResources(); + view = new WeakReference<>(element); + vw = element.getWidth(); + vh = element.getHeight(); + } + public ContactPictureTask(Context context, ImageView element, CallContact item, PictureLoadedCallback cb) { + contact = item; + cr = context.getContentResolver(); + res = context.getResources(); + vw = element.getWidth(); + vh = element.getHeight(); + view = new WeakReference<>(element); + addCallback(cb); } public static Bitmap loadContactPhoto(ContentResolver cr, long id) { @@ -80,17 +117,27 @@ public class ContactPictureTask implements Runnable { @Override public void run() { + Log.i(TAG, "ContactPictureTask run " + contact.getId()); + Bitmap photo_bmp; try { photo_bmp = loadContactPhoto(cr, contact.getId()); } catch (IllegalArgumentException e) { photo_bmp = null; } + cr = null; + + /*final ImageView v = view.get(); + view.clear(); + if (v == null) { + Log.i(TAG, "ContactPictureTask cancelling: view is now null"); + return; + }*/ - int dpiPadding = (int) (PADDING * view.getResources().getDisplayMetrics().density); + //int dpiPadding = (int) (PADDING * view.getResources().getDisplayMetrics().density); if (photo_bmp == null) { - photo_bmp = decodeSampledBitmapFromResource(view.getResources(), R.drawable.ic_contact_picture, view.getWidth(), view.getHeight()); + photo_bmp = decodeSampledBitmapFromResource(res, R.drawable.ic_contact_picture, vw, vh); } int w = photo_bmp.getWidth(), h = photo_bmp.getHeight(); @@ -119,14 +166,41 @@ public class ContactPictureTask implements Runnable { // internalCanvas.drawOval(new RectF(PADDING, PADDING, externalBMP.getWidth() - dpiPadding, externalBMP.getHeight() - dpiPadding), paint); internalCanvas.drawOval(new RectF(0, 0, externalBMP.getWidth(), externalBMP.getHeight()), paint); - view.post(new Runnable() { + photo_bmp.recycle(); + + contact.setPhoto(externalBMP); + //v.invalidate(); + synchronized (callbacks) { + final ImageView v = view.get(); + view.clear(); + if (v == null) { + for (PictureLoadedCallback cb : callbacks) { + cb.onPictureLoaded(externalBMP); + } + } else { + v.post(new Runnable() { + @Override + public void run() { + v.setImageBitmap(externalBMP); + } + }); + } + callbacks.clear(); + } + + /*v.post(new Runnable() { @Override public void run() { - view.setImageBitmap(externalBMP); + Log.w(TAG, "ContactPictureTask END " + contact.getId()); + //v.setImageBitmap(externalBMP); contact.setPhoto(externalBMP); - view.invalidate(); + //v.invalidate(); + for (PictureLoadedCallback cb : callbacks) { + cb.onPictureLoaded(externalBMP); + } + callbacks.clear(); } - }); + });*/ } public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { diff --git a/ring-android/app/src/main/java/cx/ring/adapters/ContactsAdapter.java b/ring-android/app/src/main/java/cx/ring/adapters/ContactsAdapter.java index de47d94b4..1754589c0 100644 --- a/ring-android/app/src/main/java/cx/ring/adapters/ContactsAdapter.java +++ b/ring-android/app/src/main/java/cx/ring/adapters/ContactsAdapter.java @@ -33,28 +33,30 @@ package cx.ring.adapters; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.HashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import cx.ring.R; import cx.ring.fragments.ContactListFragment; import cx.ring.model.CallContact; -import cx.ring.views.stickylistheaders.StickyListHeadersAdapter; +import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter; import android.content.Context; +import android.graphics.Bitmap; +import android.util.LruCache; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; +import android.view.animation.AnimationUtils; import android.widget.BaseAdapter; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.SectionIndexer; import android.widget.TextView; -import android.widget.Toast; public class ContactsAdapter extends BaseAdapter implements StickyListHeadersAdapter, SectionIndexer { - private ExecutorService infos_fetcher = Executors.newCachedThreadPool(); Context mContext; @@ -64,30 +66,41 @@ public class ContactsAdapter extends BaseAdapter implements StickyListHeadersAda WeakReference<ContactListFragment> parent; private LayoutInflater mInflater; - // private static final String TAG = ContactsAdapter.class.getSimpleName(); + final private LruCache<Long, Bitmap> mMemoryCache; + final private HashMap<Long, WeakReference<ContactPictureTask>> running_tasks = new HashMap<>(); + + private static final String TAG = ContactsAdapter.class.getSimpleName(); public ContactsAdapter(ContactListFragment contactListFragment) { super(); mContext = contactListFragment.getActivity(); mInflater = LayoutInflater.from(mContext); - parent = new WeakReference<ContactListFragment>(contactListFragment); - mContacts = new ArrayList<CallContact>(); + parent = new WeakReference<>(contactListFragment); + mContacts = new ArrayList<>(); mSectionIndices = getSectionIndices(); mSectionLetters = getSectionLetters(); + final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); + final int cacheSize = maxMemory / 8; + mMemoryCache = new LruCache<Long, Bitmap>(cacheSize){ + @Override + protected int sizeOf(Long key, Bitmap bitmap) { + return bitmap.getByteCount() / 1024; + } + }; } public static final int TYPE_HEADER = 0; public static final int TYPE_CONTACT = 1; private int[] getSectionIndices() { - ArrayList<Integer> sectionIndices = new ArrayList<Integer>(); + ArrayList<Integer> sectionIndices = new ArrayList<>(); if (mContacts.isEmpty()) return new int[0]; - char lastFirstChar = mContacts.get(0).getmDisplayName().charAt(0); + char lastFirstChar = mContacts.get(0).getDisplayName().charAt(0); sectionIndices.add(0); for (int i = 1; i < mContacts.size(); i++) { - if (mContacts.get(i).getmDisplayName().charAt(0) != lastFirstChar) { - lastFirstChar = mContacts.get(i).getmDisplayName().charAt(0); + if (mContacts.get(i).getDisplayName().charAt(0) != lastFirstChar) { + lastFirstChar = mContacts.get(i).getDisplayName().charAt(0); sectionIndices.add(i); } } @@ -101,26 +114,27 @@ public class ContactsAdapter extends BaseAdapter implements StickyListHeadersAda private Character[] getSectionLetters() { Character[] letters = new Character[mSectionIndices.length]; for (int i = 0; i < mSectionIndices.length; i++) { - letters[i] = mContacts.get(mSectionIndices[i]).getmDisplayName().charAt(0); + letters[i] = mContacts.get(mSectionIndices[i]).getDisplayName().charAt(0); } return letters; } @Override - public View getView(int position, View convertView, ViewGroup root) { + public View getView(final int position, View convertView, ViewGroup root) { ContactView entryView; if (convertView == null) { convertView = mInflater.inflate(R.layout.item_contact, null); entryView = new ContactView(); - entryView.quick_starred = (ImageButton) convertView.findViewById(R.id.quick_starred); + /*entryView.quick_starred = (ImageButton) convertView.findViewById(R.id.quick_starred); entryView.quick_edit = (ImageButton) convertView.findViewById(R.id.quick_edit); entryView.quick_discard = (ImageButton) convertView.findViewById(R.id.quick_discard); entryView.quick_call = (ImageButton) convertView.findViewById(R.id.quick_call); - entryView.quick_msg = (ImageButton) convertView.findViewById(R.id.quick_message); + entryView.quick_msg = (ImageButton) convertView.findViewById(R.id.quick_message);*/ entryView.photo = (ImageView) convertView.findViewById(R.id.photo); entryView.display_name = (TextView) convertView.findViewById(R.id.display_name); + entryView.position = -1; convertView.setTag(entryView); } else { entryView = (ContactView) convertView.getTag(); @@ -128,14 +142,56 @@ public class ContactsAdapter extends BaseAdapter implements StickyListHeadersAda final CallContact item = mContacts.get(position); - entryView.display_name.setText(item.getmDisplayName()); + if (entryView.position == position || (entryView.contact != null && entryView.contact.get() != null && item.getId() == entryView.contact.get().getId())) + return convertView; + + entryView.display_name.setText(item.getDisplayName()); + entryView.contact = new WeakReference<>(item); + entryView.position = position; + final Long cid = item.getId(); + final Long pid = item.getPhotoId(); + Bitmap bmp = item.getPhoto(); + if (bmp == null) { + bmp = mMemoryCache.get(pid); + if (bmp != null) item.setPhoto(bmp); + } - if (item.hasPhoto()) { - entryView.photo.setImageBitmap(item.getPhoto()); + if (bmp != null) { + entryView.photo.setImageBitmap(bmp); } else { - infos_fetcher.execute(new ContactPictureTask(mContext, entryView.photo, item)); + entryView.photo.setImageBitmap(null); + final WeakReference<ContactView> wh = new WeakReference<>(entryView); + infos_fetcher.execute(new ContactPictureTask(mContext, entryView.photo, item, new ContactPictureTask.PictureLoadedCallback() { + @Override + public void onPictureLoaded(final Bitmap bmp) { + mMemoryCache.put(pid, bmp); + final ContactView fh = wh.get(); + if (fh == null || fh.photo.getParent() == null) + return; + if (fh.position == position) + fh.photo.post(new Runnable() { + @Override + public void run() { + final CallContact c = fh.contact.get(); + if (c.getId() == cid) { + c.setPhoto(bmp); + fh.photo.setImageBitmap(bmp); + fh.photo.startAnimation(AnimationUtils.loadAnimation(fh.photo.getContext(), R.anim.contact_fadein)); + } + } + }); + } + })); } + convertView.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + parent.get().mCallbacks.onTextContact(item); + } + }); + +/* entryView.quick_call.setOnClickListener(new OnClickListener() { @Override @@ -182,7 +238,7 @@ public class ContactsAdapter extends BaseAdapter implements StickyListHeadersAda entryView.quick_edit.setClickable(false); entryView.quick_discard.setClickable(false); entryView.quick_starred.setClickable(false); - +*/ return convertView; } @@ -190,9 +246,11 @@ public class ContactsAdapter extends BaseAdapter implements StickyListHeadersAda * ViewHolder Pattern *********************/ public class ContactView { - ImageButton quick_starred, quick_edit, quick_discard, quick_call, quick_msg; + ImageButton /*quick_starred, quick_edit, quick_discard, */quick_call, quick_msg; ImageView photo; TextView display_name; + WeakReference<CallContact> contact = new WeakReference<>(null); + int position; } @Override @@ -224,7 +282,7 @@ public class ContactsAdapter extends BaseAdapter implements StickyListHeadersAda } // set header text as first char in name - char headerChar = mContacts.get(position).getmDisplayName().subSequence(0, 1).charAt(0); + char headerChar = mContacts.get(position).getDisplayName().subSequence(0, 1).charAt(0); holder.text.setText("" + headerChar); @@ -240,7 +298,7 @@ public class ContactsAdapter extends BaseAdapter implements StickyListHeadersAda public long getHeaderId(int position) { // return the first character of the name as ID because this is what // headers are based upon - return mContacts.get(position).getmDisplayName().subSequence(0, 1).charAt(0); + return mContacts.get(position).getDisplayName().subSequence(0, 1).charAt(0); } @Override @@ -274,14 +332,14 @@ public class ContactsAdapter extends BaseAdapter implements StickyListHeadersAda } public void clear() { - mContacts = new ArrayList<CallContact>(); + mContacts = new ArrayList<>(); mSectionIndices = new int[0]; mSectionLetters = new Character[0]; notifyDataSetChanged(); } - +/* public void restore() { - mContacts = new ArrayList<CallContact>(); + mContacts = new ArrayList<>(); mSectionIndices = getSectionIndices(); mSectionLetters = getSectionLetters(); notifyDataSetChanged(); @@ -292,6 +350,13 @@ public class ContactsAdapter extends BaseAdapter implements StickyListHeadersAda mSectionIndices = getSectionIndices(); mSectionLetters = getSectionLetters(); notifyDataSetChanged(); + }*/ + + public void setData(ArrayList<CallContact> contacts) { + mContacts = contacts; + mSectionIndices = getSectionIndices(); + mSectionLetters = getSectionLetters(); + notifyDataSetChanged(); } } diff --git a/ring-android/app/src/main/java/cx/ring/adapters/DiscussArrayAdapter.java b/ring-android/app/src/main/java/cx/ring/adapters/DiscussArrayAdapter.java deleted file mode 100644 index 9cdfc411b..000000000 --- a/ring-android/app/src/main/java/cx/ring/adapters/DiscussArrayAdapter.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2004-2014 Savoir-Faire Linux Inc. - * - * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 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., 675 Mass Ave, Cambridge, MA 02139, USA. - * - * Additional permission under GNU GPL version 3 section 7: - * - * If you modify this program, or any covered work, by linking or - * combining it with the OpenSSL project's OpenSSL library (or a - * modified version of that library), containing parts covered by the - * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. - * grants you additional permission to convey the resulting work. - * Corresponding Source for a non-source form of such a combination - * shall include the source code for the parts of OpenSSL used as well - * as that of the covered work. - */ - -package cx.ring.adapters; - -import java.util.ArrayList; -import java.util.List; - -import cx.ring.R; -import cx.ring.model.SipMessage; - -import android.content.Context; -import android.os.Bundle; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.LinearLayout; -import android.widget.TextView; - -public class DiscussArrayAdapter extends BaseAdapter { - - private TextView countryName; - private List<SipMessage> messages = new ArrayList<SipMessage>(); - private LinearLayout wrapper; - private Context mContext; - - public DiscussArrayAdapter(Context context, Bundle args) { - mContext = context; - - if(args == null) - messages = new ArrayList<SipMessage>(); - else - messages = args.getParcelableArrayList("messages"); - - } - - public void add(SipMessage object) { - messages.add(object); - notifyDataSetChanged(); - } - - public int getCount() { - return this.messages.size(); - } - - public SipMessage getItem(int index) { - return this.messages.get(index); - } - - public View getView(int position, View convertView, ViewGroup parent) { - View row = convertView; - if (row == null) { - LayoutInflater inflater = LayoutInflater.from(mContext); - row = inflater.inflate(R.layout.item_message, parent, false); - } - - wrapper = (LinearLayout) row.findViewById(R.id.wrapper); - - SipMessage coment = getItem(position); - - countryName = (TextView) row.findViewById(R.id.comment); - - countryName.setText(coment.comment); - - countryName.setBackgroundResource(coment.left ? R.drawable.bubble_left_selector : R.drawable.bubble_right_selector); - wrapper.setGravity(coment.left ? Gravity.LEFT : Gravity.RIGHT); - - return row; - } - - @Override - public long getItemId(int position) { - return 0; - } - -} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/adapters/SectionsPagerAdapter.java b/ring-android/app/src/main/java/cx/ring/adapters/SectionsPagerAdapter.java index b9b2d132f..1f3be0040 100644 --- a/ring-android/app/src/main/java/cx/ring/adapters/SectionsPagerAdapter.java +++ b/ring-android/app/src/main/java/cx/ring/adapters/SectionsPagerAdapter.java @@ -50,12 +50,11 @@ public class SectionsPagerAdapter extends FragmentStatePagerAdapter implements P private static final String TAG = SectionsPagerAdapter.class.getSimpleName(); Context mContext; - ArrayList<Fragment> fragments; + private final ArrayList<Fragment> fragments = new ArrayList<>(); public SectionsPagerAdapter(Context c, FragmentManager fm) { super(fm); mContext = c; - fragments = new ArrayList<Fragment>(); fragments.add(new DialingFragment()); fragments.add(new CallListFragment()); fragments.add(new HistoryFragment()); diff --git a/ring-android/app/src/main/java/cx/ring/adapters/StarredContactsAdapter.java b/ring-android/app/src/main/java/cx/ring/adapters/StarredContactsAdapter.java index 6690341a0..d2a4c1d66 100644 --- a/ring-android/app/src/main/java/cx/ring/adapters/StarredContactsAdapter.java +++ b/ring-android/app/src/main/java/cx/ring/adapters/StarredContactsAdapter.java @@ -70,6 +70,11 @@ public class StarredContactsAdapter extends BaseAdapter { notifyDataSetChanged(); } + public void setData(ArrayList<CallContact> contacts) { + dataset = contacts; + notifyDataSetChanged(); + } + @Override public int getCount() { return dataset.size(); @@ -97,7 +102,7 @@ public class StarredContactsAdapter extends BaseAdapter { CallContact item = dataset.get(pos); - ((TextView) v.findViewById(R.id.display_name)).setText(item.getmDisplayName()); + ((TextView) v.findViewById(R.id.display_name)).setText(item.getDisplayName()); ImageView photo_view = (ImageView) v.findViewById(R.id.photo); if(item.hasPhoto()){ diff --git a/ring-android/app/src/main/java/cx/ring/client/AccountEditionActivity.java b/ring-android/app/src/main/java/cx/ring/client/AccountEditionActivity.java index 8026bb8ae..bd93dd18a 100644 --- a/ring-android/app/src/main/java/cx/ring/client/AccountEditionActivity.java +++ b/ring-android/app/src/main/java/cx/ring/client/AccountEditionActivity.java @@ -37,6 +37,7 @@ import android.app.AlertDialog; import android.app.Fragment; import android.app.FragmentManager; import android.content.*; +import android.net.Uri; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; @@ -49,26 +50,33 @@ import android.view.MenuItem; import cx.ring.R; import cx.ring.fragments.AdvancedAccountFragment; import cx.ring.fragments.AudioManagementFragment; +import cx.ring.fragments.HomeFragment; +import cx.ring.fragments.MenuFragment; import cx.ring.fragments.NestedSettingsFragment; import cx.ring.fragments.SecurityAccountFragment; import cx.ring.model.account.Account; import cx.ring.service.ISipService; +import cx.ring.service.LocalService; import cx.ring.service.SipService; import com.astuetz.PagerSlidingTabStrip; import java.util.ArrayList; import java.util.Locale; +import java.util.Map; import java.util.Observable; import java.util.Observer; import cx.ring.fragments.GeneralAccountFragment; -public class AccountEditionActivity extends Activity implements GeneralAccountFragment.Callbacks, AudioManagementFragment.Callbacks, +public class AccountEditionActivity extends Activity implements LocalService.Callbacks, GeneralAccountFragment.Callbacks, AudioManagementFragment.Callbacks, AdvancedAccountFragment.Callbacks, SecurityAccountFragment.Callbacks, NestedSettingsFragment.Callbacks { private static final String TAG = AccountEditionActivity.class.getSimpleName(); + public static final Uri CONTENT_URI = Uri.withAppendedPath(LocalService.AUTHORITY_URI, "accounts"); + private boolean mBound = false; - private ISipService service; - private Account acc_selected; + private LocalService service; + + private Account acc_selected = null; private NestedSettingsFragment toDisplay; @@ -81,24 +89,35 @@ public class AccountEditionActivity extends Activity implements GeneralAccountFr }; PreferencesPagerAdapter mPreferencesPagerAdapter; + private ServiceConnection mConnection = new ServiceConnection() { @Override - public void onServiceConnected(ComponentName className, IBinder binder) { - service = ISipService.Stub.asInterface(binder); + public void onServiceConnected(ComponentName className, IBinder s) { + LocalService.LocalBinder binder = (LocalService.LocalBinder) s; + service = binder.getService(); mBound = true; - ArrayList<Fragment> fragments = new ArrayList<Fragment>(); + setContentView(R.layout.activity_account_settings); + getActionBar().setDisplayHomeAsUpEnabled(true); + String account_id = getIntent().getData().getLastPathSegment(); + Log.i(TAG, "Service connected " + className.getClassName() + " " + getIntent().getData().toString()); + + acc_selected = service.getAccount(account_id); + acc_selected.addObserver(mAccountObserver); + getActionBar().setTitle(acc_selected.getAlias()); + + ArrayList<Fragment> fragments = new ArrayList<>(); if (acc_selected.isIP2IP()) { fragments.add(new AudioManagementFragment()); } else { fragments.add(new GeneralAccountFragment()); fragments.add(new AudioManagementFragment()); - if(acc_selected.isSip()) - { - fragments.add(new AdvancedAccountFragment()); - fragments.add(new SecurityAccountFragment()); - } + if(acc_selected.isSip()) + { + fragments.add(new AdvancedAccountFragment()); + fragments.add(new SecurityAccountFragment()); + } } ViewPager mViewPager = (ViewPager) findViewById(R.id.pager); @@ -110,33 +129,22 @@ public class AccountEditionActivity extends Activity implements GeneralAccountFr final PagerSlidingTabStrip strip = PagerSlidingTabStrip.class.cast(findViewById(R.id.pager_sliding_strip)); strip.setViewPager(mViewPager); - } @Override public void onServiceDisconnected(ComponentName arg0) { - + acc_selected.deleteObserver(mAccountObserver); + mBound = false; } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - - setContentView(R.layout.activity_account_settings); - - getActionBar().setDisplayHomeAsUpEnabled(true); - - acc_selected = getIntent().getExtras().getParcelable("account"); - - acc_selected.addObserver(mAccountObserver); - if (!mBound) { - Log.i(TAG, "onCreate: Binding service..."); - Intent intent = new Intent(this, SipService.class); + Intent intent = new Intent(this, LocalService.class); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } - } @Override @@ -206,8 +214,12 @@ public class AccountEditionActivity extends Activity implements GeneralAccountFr private void processAccount() { try { - service.setCredentials(acc_selected.getAccountID(), acc_selected.getCredentialsHashMapList()); - service.setAccountDetails(acc_selected.getAccountID(), acc_selected.getDetails()); + service.getRemoteService().setCredentials(acc_selected.getAccountID(), acc_selected.getCredentialsHashMapList()); + Map<String, String> details = acc_selected.getDetails(); + service.getRemoteService().setAccountDetails(acc_selected.getAccountID(), details); + Log.w(TAG, "service.setAccountDetails " + details.get("Account.hostname")); + getActionBar().setTitle(acc_selected.getAlias());; + } catch (RemoteException e) { e.printStackTrace(); } @@ -225,7 +237,7 @@ public class AccountEditionActivity extends Activity implements GeneralAccountFr bundle.putString("AccountID", acc_selected.getAccountID()); try { - service.removeAccount(acc_selected.getAccountID()); + service.getRemoteService().removeAccount(acc_selected.getAccountID()); } catch (RemoteException e) { e.printStackTrace(); } @@ -244,6 +256,16 @@ public class AccountEditionActivity extends Activity implements GeneralAccountFr return alertDialog; } + @Override + public ISipService getRemoteService() { + return service.getRemoteService(); + } + + @Override + public LocalService getService() { + return service; + } + public class PreferencesPagerAdapter extends FragmentStatePagerAdapter { Context mContext; @@ -289,11 +311,6 @@ public class AccountEditionActivity extends Activity implements GeneralAccountFr } } - @Override - public ISipService getService() { - return service; - } - @Override public Account getAccount() { return acc_selected; diff --git a/ring-android/app/src/main/java/cx/ring/client/CallActivity.java b/ring-android/app/src/main/java/cx/ring/client/CallActivity.java index 32682b735..c481bca87 100644 --- a/ring-android/app/src/main/java/cx/ring/client/CallActivity.java +++ b/ring-android/app/src/main/java/cx/ring/client/CallActivity.java @@ -33,28 +33,25 @@ package cx.ring.client; -import java.util.*; - import android.app.Activity; import android.util.Log; import cx.ring.R; import cx.ring.fragments.CallFragment; -import cx.ring.fragments.IMFragment; +import cx.ring.model.Conversation; +import cx.ring.model.TextMessage; import cx.ring.model.account.Account; import cx.ring.model.CallContact; import cx.ring.model.Conference; import cx.ring.model.SipCall; -import cx.ring.model.SipMessage; +import cx.ring.model.account.AccountDetailBasic; import cx.ring.service.ISipService; -import cx.ring.service.SipService; +import cx.ring.service.LocalService; import cx.ring.utils.CallProximityManager; -import cx.ring.views.CallPaneLayout; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; -import android.graphics.Color; import android.graphics.PixelFormat; import android.net.Uri; import android.os.Bundle; @@ -62,88 +59,37 @@ import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; -import android.support.v4.widget.SlidingPaneLayout; import android.view.KeyEvent; -import android.view.View; import android.view.Window; import android.view.WindowManager; -public class CallActivity extends Activity implements IMFragment.Callbacks, CallFragment.Callbacks, CallProximityManager.ProximityDirector { - +public class CallActivity extends Activity implements LocalService.Callbacks, CallFragment.Callbacks, CallProximityManager.ProximityDirector { @SuppressWarnings("unused") static final String TAG = "CallActivity"; - private ISipService mService; - CallPaneLayout mSlidingPaneLayout; + private boolean init = false; + private LocalService service; - IMFragment mIMFragment; CallFragment mCurrentCallFragment; private Conference mDisplayedConference; /* result code sent in case of call failure */ public static int RESULT_FAILURE = -10; - private CallProximityManager mProximityManager; + private CallProximityManager mProximityManager = null; @Override protected void onCreate(Bundle savedInstanceState) { + Log.i(TAG, "CallActivity onCreate"); super.onCreate(savedInstanceState); - setContentView(R.layout.activity_call_layout); Window w = getWindow(); w.setFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED, WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); - setUpSlidingPanel(); - - mProximityManager = new CallProximityManager(this, this); - mProximityManager.startTracking(); - - mCurrentCallFragment = new CallFragment(); - mIMFragment = new IMFragment(); - - if(!checkExternalCall()) { - mDisplayedConference = getIntent().getParcelableExtra("conference"); - Bundle IMBundle = new Bundle(); - if (getIntent().getBooleanExtra("resuming", false)) { - IMBundle.putParcelableArrayList("messages", mDisplayedConference.getMessages()); - mIMFragment.setArguments(IMBundle); - } else { - IMBundle.putParcelableArrayList("messages", new ArrayList<SipMessage>()); - mIMFragment.setArguments(IMBundle); - } - } - - mSlidingPaneLayout.setCurFragment(mCurrentCallFragment); - getFragmentManager().beginTransaction().replace(R.id.ongoingcall_pane, mCurrentCallFragment) - .replace(R.id.message_list_frame, mIMFragment).commit(); - } - - private void setUpSlidingPanel() { - mSlidingPaneLayout = (CallPaneLayout) findViewById(R.id.slidingpanelayout); - mSlidingPaneLayout.setParallaxDistance(500); - mSlidingPaneLayout.setSliderFadeColor(Color.TRANSPARENT); - - mSlidingPaneLayout.setPanelSlideListener(new SlidingPaneLayout.PanelSlideListener() { - - @Override - public void onPanelSlide(View view, float offSet) { - } - - @Override - public void onPanelOpened(View view) { - getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN); - } - - @Override - public void onPanelClosed(View view) { - mCurrentCallFragment.getBubbleView().restartDrawing(); - getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING); - } - }); + Intent intent = new Intent(this, LocalService.class); + bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } @Override public void onFragmentCreated() { - Intent intent = new Intent(this, SipService.class); - bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } @Override @@ -182,11 +128,12 @@ public class CallActivity extends Activity implements IMFragment.Callbacks, Call @Override protected void onDestroy() { - + Log.i(TAG, "CallActivity onDestroy"); unbindService(mConnection); - - mProximityManager.stopTracking(); - mProximityManager.release(0); + if (mProximityManager != null) { + mProximityManager.stopTracking(); + mProximityManager.release(0); + } super.onDestroy(); } @@ -198,15 +145,34 @@ public class CallActivity extends Activity implements IMFragment.Callbacks, Call @SuppressWarnings("unchecked") @Override public void onServiceConnected(ComponentName className, IBinder binder) { - mService = ISipService.Stub.asInterface(binder); + service = ((LocalService.LocalBinder)binder).getService(); + + if (!init) { + mProximityManager = new CallProximityManager(CallActivity.this, CallActivity.this); + mProximityManager.startTracking(); + + if(!checkExternalCall()) { + mDisplayedConference = getIntent().getParcelableExtra("conference"); + } + Log.i(TAG, "CallActivity onCreate in:" + mDisplayedConference.isIncoming() + " out:" + mDisplayedConference.isOnGoing() + " contact" + mDisplayedConference.getParticipants().get(0).getContact().getDisplayName()); + init = true; + } if (mDisplayedConference.getState().contentEquals("NONE")) { + SipCall call = mDisplayedConference.getParticipants().get(0); try { - mService.placeCall(mDisplayedConference.getParticipants().get(0)); + String callId = service.getRemoteService().placeCall(call); + if (callId == null || callId.isEmpty()) { + CallActivity.this.terminateCall(); + } + mDisplayedConference = service.getRemoteService().getConference(callId); } catch (RemoteException e) { e.printStackTrace(); } } + + setContentView(R.layout.activity_call_layout); + mCurrentCallFragment = (CallFragment) getFragmentManager().findFragmentById(R.id.ongoingcall_pane); } @Override @@ -217,25 +183,30 @@ public class CallActivity extends Activity implements IMFragment.Callbacks, Call private boolean checkExternalCall() { Uri u = getIntent().getData(); if (u != null) { - CallContact c = CallContact.ContactBuilder.buildUnknownContact(u.getSchemeSpecificPart()); + String number = u.getSchemeSpecificPart(); + Log.w(TAG, "number " + number); + number = CallContact.canonicalNumber(number); + Log.w(TAG, "canonicalNumber " + number); + CallContact c = service.findContactByNumber(number); + Conversation conv = service.getByContact(c); + if (conv == null) + conv = new Conversation(c); + Account acc = service.getAccounts().get(0); + String id = conv.getLastAccountUsed(); + if (id != null && !id.isEmpty()) { + Account alt_acc = service.getAccount(id); + Log.w(TAG, "Found suitable account for calling " + u.getSchemeSpecificPart() + " " + id + " " + alt_acc.getBasicDetails().getDetailString(AccountDetailBasic.CONFIG_ACCOUNT_TYPE)); + if (alt_acc.isEnabled()) + acc = alt_acc; + } else { + acc = service.guessAccount(c, number); + } try { - String accountID = (String) mService.getAccountList().get(1); // We use the first account to place outgoing calls - Map<String, String> details = (Map<String, String>) mService.getAccountDetails(accountID); - ArrayList<Map<String, String>> credentials = (ArrayList<Map<String, String>>) mService.getCredentials(accountID); - Map<String, String> state = (Map<String, String>) mService.getVolatileAccountDetails(accountID); - Account acc = new Account(accountID, details, credentials, state); - - Bundle args = new Bundle(); - args.putString(SipCall.ID, Integer.toString(Math.abs(new Random().nextInt()))); - args.putParcelable(SipCall.ACCOUNT, acc); - args.putInt(SipCall.STATE, SipCall.state.CALL_STATE_NONE); - args.putInt(SipCall.TYPE, SipCall.direction.CALL_TYPE_OUTGOING); - args.putParcelable(SipCall.CONTACT, c); - + SipCall call = new SipCall(null, acc.getAccountID(), number, SipCall.Direction.OUTGOING); + call.setCallState(SipCall.State.NONE); + call.setContact(c); mDisplayedConference = new Conference(Conference.DEFAULT_ID); - mDisplayedConference.getParticipants().add(new SipCall(args)); - } catch (RemoteException e) { - e.printStackTrace(); + mDisplayedConference.getParticipants().add(call); } catch (Exception e) { e.printStackTrace(); } @@ -245,8 +216,13 @@ public class CallActivity extends Activity implements IMFragment.Callbacks, Call } @Override - public ISipService getService() { - return mService; + public ISipService getRemoteService() { + return service.getRemoteService(); + } + + @Override + public LocalService getService() { + return service; } @Override @@ -273,30 +249,7 @@ public class CallActivity extends Activity implements IMFragment.Callbacks, Call @Override public void terminateCall() { mHandler.removeCallbacks(mUpdateTimeTask); - mCurrentCallFragment.getBubbleView().stopThread(); - TimerTask quit = new TimerTask() { - - @Override - public void run() { - finish(); - } - }; - - new Timer().schedule(quit, 1000); - } - - @Override - public boolean sendIM(SipMessage msg) { - - try { - Log.i(TAG, "Sending:"+msg.comment+"to"+mDisplayedConference.getId()); - mService.sendTextMessage(mDisplayedConference.getId(), msg); - } catch (RemoteException e) { - e.printStackTrace(); - return false; - } - - return true; + finish(); } @Override @@ -304,17 +257,6 @@ public class CallActivity extends Activity implements IMFragment.Callbacks, Call mHandler.postDelayed(mUpdateTimeTask, 0); } - @Override - public void slideChatScreen() { - - if (mSlidingPaneLayout.isOpen()) { - mSlidingPaneLayout.closePane(); - } else { - mCurrentCallFragment.getBubbleView().stopThread(); - mSlidingPaneLayout.openPane(); - } - } - @Override public boolean shouldActivateProximity() { return true; @@ -322,7 +264,5 @@ public class CallActivity extends Activity implements IMFragment.Callbacks, Call @Override public void onProximityTrackingChanged(boolean acquired) { - // TODO Stub de la méthode généré automatiquement - } } diff --git a/ring-android/app/src/main/java/cx/ring/client/ConversationActivity.java b/ring-android/app/src/main/java/cx/ring/client/ConversationActivity.java new file mode 100644 index 000000000..a0ce37504 --- /dev/null +++ b/ring-android/app/src/main/java/cx/ring/client/ConversationActivity.java @@ -0,0 +1,386 @@ +package cx.ring.client; + +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.net.Uri; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; + +import java.text.DateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import cx.ring.R; +import cx.ring.adapters.ContactPictureTask; +import cx.ring.model.CallContact; +import cx.ring.model.Conference; +import cx.ring.model.Conversation; +import cx.ring.model.SipCall; +import cx.ring.model.TextMessage; +import cx.ring.model.account.Account; +import cx.ring.service.LocalService; + +public class ConversationActivity extends Activity { + private static final String TAG = ConversationActivity.class.getSimpleName(); + + public static final Uri CONTENT_URI = Uri.withAppendedPath(LocalService.AUTHORITY_URI, "conversations"); + + private boolean mBound = false; + private LocalService service = null; + private Conversation conversation = null; + private String preferredNumber = null; + + + private ListView histList = null; + private View msgSendBtn = null; + private EditText msgEditTxt = null; + private ViewGroup bottomPane = null; + + private ConversationAdapter adapter = null; + + private ServiceConnection mConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName className, IBinder binder) { + service = ((LocalService.LocalBinder)binder).getService(); + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(LocalService.ACTION_CONF_UPDATE); + registerReceiver(receiver, intentFilter); + + mBound = true; + + String conv_id = getIntent().getData().getLastPathSegment(); + preferredNumber = getIntent().getStringExtra("number"); + conversation = service.getConversation(conv_id); + if (conversation == null) { + long contact_id = CallContact.contactIdFromId(conv_id); + CallContact contact; + if (contact_id >= 0) + contact = service.findContactById(contact_id); + else if (preferredNumber != null && !preferredNumber.isEmpty()) { + contact = service.findContactByNumber(preferredNumber); + if (contact == null) + contact = CallContact.ContactBuilder.buildUnknownContact(conv_id); + } else { + contact = service.findContactByNumber(conv_id); + if (contact == null) + contact = CallContact.ContactBuilder.buildUnknownContact(conv_id); + preferredNumber = conv_id; + } + conversation = service.startConversation(contact); + } + + Log.w(TAG, "ConversationActivity onServiceConnected " + conv_id); + + if (conversation == null) { + finish(); + return; + } + + getActionBar().setTitle(conversation.getContact().getDisplayName()); + + Conference conf = conversation.getCurrentCall(); + bottomPane.setVisibility(conf == null ? View.GONE : View.VISIBLE); + if (conf != null) { + Log.w(TAG, "ConversationActivity onServiceConnected " + conf.getId() + " " + conversation.getCurrentCall()); + bottomPane.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + startActivity(new Intent(ConversationActivity.this.getApplicationContext(), CallActivity.class).putExtra("conference", conversation.getCurrentCall())); + } + }); + } + + adapter.updateDataset(conversation.getHistory()); + scrolltoBottom(); + } + + @Override + public void onServiceDisconnected(ComponentName arg0) { + Log.w(TAG, "ConversationActivity onServiceDisconnected " + arg0.getClassName()); + mBound = false; + } + }; + final BroadcastReceiver receiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + Log.w(TAG, "onReceive " + intent.getAction() + " " + intent.getDataString()); + //conversation = service.getConversation(conversation.getId()); + conversation = service.getByContact(conversation.getContact()); + adapter.updateDataset(conversation.getHistory()); + scrolltoBottom(); + Conference conf = conversation.getCurrentCall(); + bottomPane.setVisibility(conf == null ? View.GONE : View.VISIBLE); + } + }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.frag_conversation); + msgEditTxt = (EditText) findViewById(R.id.msg_input_txt); + msgSendBtn = findViewById(R.id.msg_send); + msgSendBtn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + onSendTextMessage(msgEditTxt.getText().toString()); + msgEditTxt.setText(""); + } + }); + bottomPane = (ViewGroup) findViewById(R.id.ongoingcall_pane); + bottomPane.setVisibility(View.GONE); + //getActionBar().setDisplayHomeAsUpEnabled(true); + conversation = getIntent().getParcelableExtra("conversation"); + + adapter = new ConversationAdapter(this); + histList = (ListView) findViewById(R.id.hist_list); + histList.setAdapter(adapter); + + if (!mBound) { + Log.i(TAG, "onCreate: Binding service..."); + Intent intent = new Intent(this, LocalService.class); + bindService(intent, mConnection, Context.BIND_AUTO_CREATE); + service = null; + } + } + + private void scrolltoBottom() { + histList.post(new Runnable() { + @Override + public void run() { + // Select the last row so it will scroll into view... + histList.setSelection(adapter.getCount() - 1); + } + }); + } + + private class ConversationAdapter extends BaseAdapter { + final private Context context; + final private ArrayList<Conversation.ConversationElement> texts = new ArrayList<>(); + private ExecutorService infos_fetcher = Executors.newCachedThreadPool(); + + public void updateDataset(ArrayList<Conversation.ConversationElement> list) { + Log.i(TAG, "updateDataset " + list.size()); + if (list.size() == 0 && texts.size() == 0) + return; + texts.clear(); + texts.addAll(list); + notifyDataSetChanged(); + } + + ConversationAdapter(Context ctx) { + context = ctx; + } + + @Override + public int getCount() { + return texts.size(); + } + + @Override + public Conversation.ConversationElement getItem(int position) { + return texts.get(position); + } + + @Override + public long getItemId(int position) { + return 0; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) + convertView = LayoutInflater.from(context).inflate(R.layout.item_textmsg, null); + + ViewGroup txtEntry = (ViewGroup) convertView.findViewById(R.id.txt_entry); + TextView msgTxt = (TextView) convertView.findViewById(R.id.msg_txt); + TextView msgDetailTxt = (TextView) convertView.findViewById(R.id.msg_details_txt); + ImageView photo = (ImageView) convertView.findViewById(R.id.photo); + + ViewGroup txtEntryRight = (ViewGroup) convertView.findViewById(R.id.txt_entry_right); + TextView msgTxtRight = (TextView) convertView.findViewById(R.id.msg_txt_right); + TextView msgDetailTxtRight = (TextView) convertView.findViewById(R.id.msg_details_txt_right); + + ViewGroup callEntry = (ViewGroup) convertView.findViewById(R.id.call_entry); + TextView histTxt = (TextView) convertView.findViewById(R.id.call_hist_txt); + TextView histDetailTxt = (TextView) convertView.findViewById(R.id.call_details_txt); + + Conversation.ConversationElement txt = texts.get(position); + if (txt.text != null) { + callEntry.setVisibility(View.GONE); + if (txt.text.isIncoming()) { + txtEntry.setVisibility(View.VISIBLE); + txtEntryRight.setVisibility(View.GONE); + msgTxt.setText(txt.text.getMessage()); + msgDetailTxt.setText(DateFormat.getDateTimeInstance().format(new Date(txt.text.getTimestamp()))); + infos_fetcher.execute(new ContactPictureTask(context, photo, txt.text.getContact())); + } else { + txtEntry.setVisibility(View.GONE); + txtEntryRight.setVisibility(View.VISIBLE); + msgTxtRight.setText(txt.text.getMessage()); + msgDetailTxtRight.setText(DateFormat.getDateTimeInstance().format(new Date(txt.text.getTimestamp()))); + } + } else { + callEntry.setVisibility(View.VISIBLE); + txtEntry.setVisibility(View.GONE); + txtEntryRight.setVisibility(View.GONE); + msgTxt.setText(""); + histTxt.setText((txt.call.isIncoming() ? "Incoming" : "Outgoing") + " call with " + txt.call.getNumber()); + histDetailTxt.setText(DateFormat.getDateTimeInstance().format(txt.call.getStartDate())); + } + + return convertView; + } + } + + @Override + protected void onDestroy() { + if (mBound) { + unregisterReceiver(receiver); + unbindService(mConnection); + mBound = false; + } + super.onDestroy(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu items for use in the action bar + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.conversation_actions, menu); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle presses on the action bar items + switch (item.getItemId()) { + case R.id.conv_action_audiocall: + onAudioCall(); + return true; + case R.id.conv_action_videocall: + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + public void launchCallActivity(SipCall infos) { + Conference tmp = conversation.getCurrentCall(); + if (tmp == null) + //tmp = service.startConversation(infos.getContact()); + tmp = new Conference(Conference.DEFAULT_ID); + + tmp.getParticipants().add(infos); + Intent intent = new Intent().setClass(this, CallActivity.class); + intent.putExtra("conference", tmp); + intent.putExtra("resuming", false); + startActivityForResult(intent, HomeActivity.REQUEST_CODE_CALL); + // overridePendingTransition(R.anim.slide_down, R.anim.slide_up); + } + + private void onSendTextMessage(String txt) { + + Conference conf = conversation.getCurrentCall(); + if (conf == null || !conf.isOnGoing()) { + String account = conversation.getLastAccountUsed(); + if (account == null || account.isEmpty()) + account = service.guessAccount(conversation.getContact(), conversation.contact.getPhones().get(0).getNumber()).getAccountID(); + String number = preferredNumber; + if (number == null || number.isEmpty()) + number = conversation.getLastNumberUsed(account); + if (number == null || number.isEmpty()) + number = conversation.contact.getPhones().get(0).getNumber(); + try { + service.getRemoteService().sendAccountTextMessage(account, number, txt); + } catch (RemoteException e) { + e.printStackTrace(); + } + } else { + try { + service.getRemoteService().sendTextMessage(conf.getId(), new TextMessage(false, txt)); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + } + + private void onAudioCall() { + Conference conf = conversation.getCurrentCall(); + if (conf != null) { + startActivity(new Intent(ConversationActivity.this.getApplicationContext(), CallActivity.class).putExtra("conference", conversation.getCurrentCall())); + return; + } + + if (service.getAccounts().isEmpty()) { + //createNotRegisteredDialog().show(); + return; + } + + Account usedAccount = service.getAccounts().get(0); + CallContact contact = null; + if (conversation != null) { + String last_used = conversation.getLastAccountUsed(); + Account a = service.getAccount(last_used); + if (a != null/* && a.isEnabled()*/) + usedAccount = a; + else { + Set<String> acc_ids = conversation.getAccountsUsed(); + for (Account acc : service.getAccounts()) { + if (acc_ids.contains(acc.getAccountID())) { + usedAccount = acc; + break; + } + } + } + contact = conversation.getContact(); + } + + String number = preferredNumber; + if (number == null) + number = conversation.getLastNumberUsed(usedAccount.getAccountID()); + if (number == null && contact != null) + number = contact.getPhones().get(0).getNumber(); + + //conversation.getHistory().getAccountID() + //if (usedAccount.isRegistered() || usedAccount.isIP2IP()) { + /* Bundle args = new Bundle(); + args.putParcelable(SipCall.ACCOUNT, usedAccount); + args.putInt(SipCall.STATE, SipCall.State.NONE); + args.putInt(SipCall.TYPE, SipCall.Direction.OUTGOING); + args.putParcelable(SipCall.CONTACT, contact);*/ + SipCall call = new SipCall(null, usedAccount.getAccountID(), number, SipCall.Direction.OUTGOING); + call.setContact(contact); + + try { + launchCallActivity(call); + } catch (Exception e) { + e.printStackTrace(); + Log.e(TAG, e.toString()); + } + /*} else { + createNotRegisteredDialog().show(); + }*/ + + } +} diff --git a/ring-android/app/src/main/java/cx/ring/client/HomeActivity.java b/ring-android/app/src/main/java/cx/ring/client/HomeActivity.java index 9358e7ef5..52d9b3707 100644 --- a/ring-android/app/src/main/java/cx/ring/client/HomeActivity.java +++ b/ring-android/app/src/main/java/cx/ring/client/HomeActivity.java @@ -36,9 +36,8 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.util.Random; import java.util.Timer; -import java.util.TimerTask; +import java.util.regex.Pattern; import android.app.Activity; import android.app.AlertDialog; @@ -52,14 +51,13 @@ import cx.ring.fragments.HistoryFragment; import cx.ring.fragments.HomeFragment; import cx.ring.fragments.MenuFragment; import cx.ring.history.HistoryEntry; +import cx.ring.history.HistoryManager; +import cx.ring.loaders.LoaderConstants; import cx.ring.model.account.Account; -import cx.ring.model.CallContact; import cx.ring.model.Conference; import cx.ring.model.SipCall; import cx.ring.service.ISipService; -import cx.ring.service.SipService; -import cx.ring.views.SlidingUpPanelLayout; -import cx.ring.views.SlidingUpPanelLayout.PanelSlideListener; +import cx.ring.service.LocalService; import android.app.Fragment; import android.app.FragmentManager; @@ -72,18 +70,12 @@ import android.content.Intent; import android.content.ServiceConnection; import android.content.res.AssetManager; import android.content.res.Configuration; -import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.IBinder; import android.preference.PreferenceManager; -import android.provider.ContactsContract; -import android.provider.ContactsContract.CommonDataKinds.Phone; -import android.provider.ContactsContract.CommonDataKinds.SipAddress; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.NavigationView; -import android.support.v4.app.FragmentActivity; -import android.support.v4.view.GravityCompat; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBarDrawerToggle; import android.support.v7.app.AppCompatActivity; @@ -91,14 +83,14 @@ import android.support.v7.widget.Toolbar; import android.util.Log; import android.util.TypedValue; import android.view.Gravity; +import android.view.Menu; +import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.widget.RelativeLayout; -import android.widget.Toast; -public class HomeActivity extends AppCompatActivity implements DialingFragment.Callbacks, AccountsManagementFragment.Callbacks, - ContactListFragment.Callbacks, CallListFragment.Callbacks, HistoryFragment.Callbacks, NavigationView.OnNavigationItemSelectedListener, MenuFragment.Callbacks { +public class HomeActivity extends AppCompatActivity implements LocalService.Callbacks, DialingFragment.Callbacks, + /*ContactListFragment.Callbacks, */HistoryFragment.Callbacks, NavigationView.OnNavigationItemSelectedListener { static final String TAG = HomeActivity.class.getSimpleName(); @@ -107,12 +99,13 @@ public class HomeActivity extends AppCompatActivity implements DialingFragment.C private MenuFragment fMenuHead = null; private boolean mBound = false; - private ISipService service; + private LocalService service; public static final int REQUEST_CODE_PREFERENCES = 1; public static final int REQUEST_CODE_CALL = 3; + public static final int REQUEST_CODE_CONVERSATION = 4; - SlidingUpPanelLayout mContactDrawer; + //SlidingUpPanelLayout mContactDrawer; private DrawerLayout mNavigationDrawer; private ActionBarDrawerToggle mDrawerToggle; private Toolbar toolbar; @@ -123,6 +116,22 @@ public class HomeActivity extends AppCompatActivity implements DialingFragment.C protected Fragment fContent; + public static final Pattern RING_ID_REGEX = Pattern.compile("^\\s+(?:ring(?:[\\s\\:]+))?(\\p{XDigit}{40})\\s+$", Pattern.CASE_INSENSITIVE); + + private static void setDefaultUncaughtExceptionHandler() { + try { + Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread t, Throwable e) { + Log.e(TAG, "Uncaught Exception detected in thread ", e); + //e.printStackTrace(); + } + }); + } catch (SecurityException e) { + Log.e(TAG, "Could not set the Default Uncaught Exception Handler"); + } + } + /* called before activity is killed, e.g. rotation */ @Override protected void onSaveInstanceState(Bundle bundle) { @@ -131,14 +140,18 @@ public class HomeActivity extends AppCompatActivity implements DialingFragment.C @Override public void onCreate(Bundle savedInstanceState) { + setDefaultUncaughtExceptionHandler(); + super.onCreate(savedInstanceState); setContentView(R.layout.activity_home); // Bind to LocalService if (!mBound) { Log.i(TAG, "onStart: Binding service..."); - Intent intent = new Intent(this, SipService.class); + /*Intent intent = new Intent(this, SipService.class); startService(intent); + bindService(intent, mConnection, Context.BIND_AUTO_CREATE);*/ + Intent intent = new Intent(this, LocalService.class); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } @@ -148,9 +161,13 @@ public class HomeActivity extends AppCompatActivity implements DialingFragment.C fMenu = (NavigationView) findViewById(R.id.left_drawer); fMenu.setNavigationItemSelectedListener(this); - - mContactsFragment = new ContactListFragment(); - getFragmentManager().beginTransaction().replace(R.id.contacts_frame, mContactsFragment).commit(); +/* + FragmentManager fm = getFragmentManager(); + mContactsFragment = (ContactListFragment) fm.findFragmentByTag(ContactListFragment.TAG); + if(mContactsFragment == null) { + mContactsFragment = new ContactListFragment(); + getFragmentManager().beginTransaction().replace(R.id.contacts_frame, mContactsFragment, ContactListFragment.TAG).commit(); + } mContactDrawer = (SlidingUpPanelLayout) findViewById(R.id.contact_panel); // mContactDrawer.setShadowDrawable(getResources().getDrawable(R.drawable.above_shadow)); @@ -187,7 +204,7 @@ public class HomeActivity extends AppCompatActivity implements DialingFragment.C } }); - +*/ mNavigationDrawer = (DrawerLayout) findViewById(R.id.drawer_layout); getSupportActionBar().setDisplayHomeAsUpEnabled(true); @@ -217,17 +234,23 @@ public class HomeActivity extends AppCompatActivity implements DialingFragment.C @Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); - // Sync the toggle state after onRestoreInstanceState has occurred. + // Sync the toggle State after onRestoreInstanceState has occurred. mDrawerToggle.syncState(); - if (mContactDrawer.isExpanded()) { + /*if (mContactDrawer.isExpanded()) { getSupportActionBar().hide(); - } + }*/ } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - mDrawerToggle.onConfigurationChanged(newConfig); + //mDrawerToggle.onConfigurationChanged(newConfig); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.history, menu); + return super.onCreateOptionsMenu(menu); } @Override @@ -248,7 +271,11 @@ public class HomeActivity extends AppCompatActivity implements DialingFragment.C TypedValue tv = new TypedValue(); if (getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true)) { int abSz = TypedValue.complexToDimensionPixelSize(tv.data,getResources().getDisplayMetrics()); - ViewGroup.LayoutParams params = toolbar.getLayoutParams(); + ViewGroup.LayoutParams params = toolbar.getLayoutParams();//toolbar.setContentInsetsRelative(); + + //TypedArray a = obtainStyledAttributes(attrs, R.styleable.Toolbar_titleMarginBottom); + + //toolbar.get if (double_h) { params.height = abSz*2; actionButton.setVisibility(View.VISIBLE); @@ -261,6 +288,7 @@ public class HomeActivity extends AppCompatActivity implements DialingFragment.C toolbar.setMinimumHeight(abSz); } toolbar.setTitle(title_res); + //toolbar.setTitleTextAppearance(toolbar.getT); } public FloatingActionButton getActionButton() { @@ -336,10 +364,10 @@ public class HomeActivity extends AppCompatActivity implements DialingFragment.C return; } - if (mContactDrawer.isExpanded() || mContactDrawer.isAnchored()) { + /*if (mContactDrawer.isExpanded() || mContactDrawer.isAnchored()) { mContactDrawer.collapsePane(); return; - } + }*/ if (getFragmentManager().getBackStackEntryCount() > 1) { popCustomBackStack(); @@ -347,20 +375,7 @@ public class HomeActivity extends AppCompatActivity implements DialingFragment.C return; } - if (isClosing) { - super.onBackPressed(); - t.cancel(); - finish(); - } else { - t.schedule(new TimerTask() { - @Override - public void run() { - isClosing = false; - } - }, 3000); - Toast.makeText(this, getResources().getString(R.string.close_msg), Toast.LENGTH_SHORT).show(); - isClosing = true; - } + super.onBackPressed(); } private void popCustomBackStack() { @@ -376,20 +391,20 @@ public class HomeActivity extends AppCompatActivity implements DialingFragment.C @Override protected void onPause() { super.onPause(); - - if (mBound) { - unbindService(mConnection); - mBound = false; - } + Log.d(TAG, "onPause"); } /* activity finishes itself or is being killed by the system */ @Override protected void onDestroy() { super.onDestroy(); - Log.i(TAG, "onDestroy: destroying service..."); - Intent sipServiceIntent = new Intent(this, SipService.class); - stopService(sipServiceIntent); + if (mBound) { + unbindService(mConnection); + mBound = false; + } + //Log.i(TAG, "onDestroy: destroying service..."); + //Intent sipServiceIntent = new Intent(this, SipService.class); + //stopService(sipServiceIntent); } public void launchCallActivity(SipCall infos) { @@ -410,9 +425,13 @@ public class HomeActivity extends AppCompatActivity implements DialingFragment.C private ServiceConnection mConnection = new ServiceConnection() { @Override - public void onServiceConnected(ComponentName className, IBinder binder) { - service = ISipService.Stub.asInterface(binder); - fContent = new HomeFragment(); + public void onServiceConnected(ComponentName className, IBinder s) { + Log.i(TAG, "onServiceConnected " + className.getClassName()); + LocalService.LocalBinder binder = (LocalService.LocalBinder) s; + service = binder.getService(); + + //service = ISipService.Stub.asInterface(binder); + fContent = new CallListFragment(); if (fMenuHead != null) fMenu.removeHeaderView(fMenuHead.getView()); fMenu.inflateHeaderView(R.layout.menuheader); @@ -420,18 +439,19 @@ public class HomeActivity extends AppCompatActivity implements DialingFragment.C getFragmentManager().beginTransaction().replace(R.id.main_frame, fContent, "Home").addToBackStack("Home").commit(); mBound = true; - Log.d(TAG, "Service connected service=" + service); + Log.i(TAG, "Service connected service=" + service); } @Override - public void onServiceDisconnected(ComponentName arg0) { + public void onServiceDisconnected(ComponentName className) { + Log.i(TAG, "onServiceConnected " + className.getClassName()); if (fMenuHead != null) { fMenu.removeHeaderView(fMenuHead.getView()); fMenuHead = null; } mBound = false; - Log.d(TAG, "Service disconnected service=" + service); + Log.i(TAG, "Service disconnected service=" + service); } }; @@ -442,8 +462,17 @@ public class HomeActivity extends AppCompatActivity implements DialingFragment.C if (mDrawerToggle.onOptionsItemSelected(item)) { return true; } - - return super.onOptionsItemSelected(item); + switch (item.getItemId()) { + case R.id.menu_clear_history: + // TODO clean Database! + //mHistoryManager.clearDB(); + //getLoaderManager().restartLoader(LoaderConstants.HISTORY_LOADER, null, this); + HistoryManager m = new HistoryManager(this); + m.clearDB(); + return true; + default: + return super.onOptionsItemSelected(item); + } } @Override @@ -466,10 +495,16 @@ public class HomeActivity extends AppCompatActivity implements DialingFragment.C } @Override - public ISipService getService() { + public ISipService getRemoteService() { + return service.getRemoteService(); + } + + @Override + public LocalService getService() { return service; } + /* @Override public void onTextContact(final CallContact c) { // TODO @@ -483,6 +518,7 @@ public class HomeActivity extends AppCompatActivity implements DialingFragment.C startActivity(intent); } + @Override public void onCallContact(final CallContact c) { @@ -506,10 +542,10 @@ public class HomeActivity extends AppCompatActivity implements DialingFragment.C public void run() { Bundle args = new Bundle(); - args.putString(SipCall.ID, Integer.toString(Math.abs(new Random().nextInt()))); + //args.putString(SipCall.ID, Integer.toString(Math.abs(new Random().nextInt()))); args.putParcelable(SipCall.ACCOUNT, fMenuHead.getSelectedAccount()); - args.putInt(SipCall.STATE, SipCall.state.CALL_STATE_NONE); - args.putInt(SipCall.TYPE, SipCall.direction.CALL_TYPE_OUTGOING); + args.putInt(SipCall.STATE, SipCall.State.NONE); + args.putInt(SipCall.TYPE, SipCall.Direction.OUTGOING); Cursor cPhones = getContentResolver().query(Phone.CONTENT_URI, CONTACTS_PHONES_PROJECTION, Phone.CONTACT_ID + " =" + c.getId(), null, null); @@ -533,10 +569,10 @@ public class HomeActivity extends AppCompatActivity implements DialingFragment.C } }); launcher.start(); - mContactDrawer.collapsePane(); + //mContactDrawer.collapsePane(); } - +*/ @Override public void onCallHistory(HistoryEntry to) { @@ -549,10 +585,10 @@ public class HomeActivity extends AppCompatActivity implements DialingFragment.C if (usedAccount.isRegistered()) { Bundle args = new Bundle(); - args.putString(SipCall.ID, Integer.toString(Math.abs(new Random().nextInt()))); + //args.putString(SipCall.ID, Integer.toString(Math.abs(new Random().nextInt()))); args.putParcelable(SipCall.ACCOUNT, usedAccount); - args.putInt(SipCall.STATE, SipCall.state.CALL_STATE_NONE); - args.putInt(SipCall.TYPE, SipCall.direction.CALL_TYPE_OUTGOING); + args.putInt(SipCall.STATE, SipCall.State.NONE); + args.putInt(SipCall.TYPE, SipCall.Direction.OUTGOING); args.putParcelable(SipCall.CONTACT, to.getContact()); try { @@ -567,7 +603,15 @@ public class HomeActivity extends AppCompatActivity implements DialingFragment.C @Override public void onCallDialed(String to) { - Account usedAccount = fMenuHead.getSelectedAccount(); + Intent intent = new Intent() + .setClass(this, CallActivity.class) + .setAction(Intent.ACTION_CALL) + .setData(Uri.parse(to)); + /*intent.putExtra("conference", tmp); + intent.putExtra("resuming", false);*/ + startActivityForResult(intent, REQUEST_CODE_CALL); + + /*Account usedAccount = fMenuHead.getSelectedAccount(); if (usedAccount == null) { createAccountDialog().show(); @@ -576,10 +620,14 @@ public class HomeActivity extends AppCompatActivity implements DialingFragment.C if (usedAccount.isRegistered() || usedAccount.isIP2IP()) { Bundle args = new Bundle(); - args.putString(SipCall.ID, Integer.toString(Math.abs(new Random().nextInt()))); + + Matcher m = RING_ID_REGEX.matcher(to); + if (m.matches() && m.groupCount() > 0) { + to = "ring:"+m.group(1); + } args.putParcelable(SipCall.ACCOUNT, usedAccount); - args.putInt(SipCall.STATE, SipCall.state.CALL_STATE_NONE); - args.putInt(SipCall.TYPE, SipCall.direction.CALL_TYPE_OUTGOING); + args.putInt(SipCall.STATE, SipCall.State.NONE); + args.putInt(SipCall.TYPE, SipCall.Direction.OUTGOING); args.putParcelable(SipCall.CONTACT, CallContact.ContactBuilder.buildUnknownContact(to)); try { @@ -589,7 +637,7 @@ public class HomeActivity extends AppCompatActivity implements DialingFragment.C } } else { createNotRegisteredDialog().show(); - } + }*/ } private AlertDialog createNotRegisteredDialog() { @@ -634,6 +682,7 @@ public class HomeActivity extends AppCompatActivity implements DialingFragment.C return alertDialog; } + /* @Override public void onContactDragged() { mContactDrawer.collapsePane(); @@ -661,7 +710,7 @@ public class HomeActivity extends AppCompatActivity implements DialingFragment.C public void setDragView(RelativeLayout relativeLayout) { mContactDrawer.setDragView(relativeLayout); } - +*/ @Override public boolean onNavigationItemSelected(MenuItem pos) { pos.setChecked(true); @@ -670,7 +719,7 @@ public class HomeActivity extends AppCompatActivity implements DialingFragment.C switch (pos.getItemId()) { case R.id.menuitem_home: - if (fContent instanceof HomeFragment) + if (fContent instanceof CallListFragment) break; if (getFragmentManager().getBackStackEntryCount() == 1) diff --git a/ring-android/app/src/main/java/cx/ring/client/NewConversationActivity.java b/ring-android/app/src/main/java/cx/ring/client/NewConversationActivity.java new file mode 100644 index 000000000..9ccde7884 --- /dev/null +++ b/ring-android/app/src/main/java/cx/ring/client/NewConversationActivity.java @@ -0,0 +1,141 @@ +package cx.ring.client; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.widget.RelativeLayout; +import android.widget.SearchView; + +import cx.ring.R; +import cx.ring.fragments.ContactListFragment; +import cx.ring.model.CallContact; + +public class NewConversationActivity extends Activity implements ContactListFragment.Callbacks { + + //private SearchView searchView = null; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + getActionBar().setDisplayHomeAsUpEnabled(true); + setContentView(R.layout.activity_new_conversation); + } +/* + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.newconv_option_menu, menu); + //searchView = (SearchView) menu.findItem(R.id.contact_search).getActionView(); + return true; + } +*/ + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onCallContact(final CallContact c) { + if (c.getPhones().size() > 1) { + final CharSequence colors[] = new CharSequence[c.getPhones().size()]; + int i = 0; + for (CallContact.Phone p : c.getPhones()) + colors[i++] = p.getNumber(); + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("Choose a number"); + builder.setItems(colors, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + CharSequence selected = colors[which]; + Intent intent = new Intent() + .setClass(NewConversationActivity.this, ConversationActivity.class) + .setAction(Intent.ACTION_VIEW) + .setData(Uri.withAppendedPath(ConversationActivity.CONTENT_URI, c.getIds().get(0))) + .putExtra("number", selected); + startActivityForResult(intent, HomeActivity.REQUEST_CODE_CONVERSATION); + } + }); + builder.show(); + } else { + Intent intent = new Intent() + .setClass(this, ConversationActivity.class) + .setAction(Intent.ACTION_VIEW) + .setData(Uri.withAppendedPath(ConversationActivity.CONTENT_URI, c.getIds().get(0))); + startActivityForResult(intent, HomeActivity.REQUEST_CODE_CONVERSATION); + } + } + + @Override + public void onTextContact(final CallContact c) { + if (c.getPhones().size() > 1) { + final CharSequence colors[] = new CharSequence[c.getPhones().size()];// {"red", "green", "blue", "black"}; + int i = 0; + for (CallContact.Phone p : c.getPhones()) + colors[i++] = p.getNumber(); + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("Choose a number"); + builder.setItems(colors, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + CharSequence selected = colors[which]; + Intent intent = new Intent() + .setClass(NewConversationActivity.this, ConversationActivity.class) + .setAction(Intent.ACTION_VIEW) + .setData(Uri.withAppendedPath(ConversationActivity.CONTENT_URI, c.getIds().get(0))) + .putExtra("number", selected); + startActivityForResult(intent, HomeActivity.REQUEST_CODE_CONVERSATION); + } + }); + builder.show(); + } else { + Intent intent = new Intent() + .setClass(this, ConversationActivity.class) + .setAction(Intent.ACTION_VIEW) + .setData(Uri.withAppendedPath(ConversationActivity.CONTENT_URI, c.getIds().get(0))); + startActivityForResult(intent, HomeActivity.REQUEST_CODE_CONVERSATION); + } + } + + @Override + public void onContactDragged() { + + } + + @Override + public void toggleDrawer() { + + } + + @Override + public void onEditContact(CallContact item) { + + } + + @Override + public void setDragView(RelativeLayout relativeLayout) { + + } + + @Override + public void toggleForSearchDrawer() { + + } +/* + @Override + public SearchView getSearchView() { + return searchView; + }*/ +} diff --git a/ring-android/app/src/main/java/cx/ring/fragments/AccountWrapperFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/AccountWrapperFragment.java deleted file mode 100644 index e22fb0c68..000000000 --- a/ring-android/app/src/main/java/cx/ring/fragments/AccountWrapperFragment.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2004-2014 Savoir-Faire Linux Inc. - * - * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 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., 675 Mass Ave, Cambridge, MA 02139, USA. - * - * Additional permission under GNU GPL version 3 section 7: - * - * If you modify this program, or any covered work, by linking or - * combining it with the OpenSSL project's OpenSSL library (or a - * modified version of that library), containing parts covered by the - * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. - * grants you additional permission to convey the resulting work. - * Corresponding Source for a non-source form of such a combination - * shall include the source code for the parts of OpenSSL used as well - * as that of the covered work. - */ - -package cx.ring.fragments; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.Bundle; -import android.app.Fragment; -import android.util.Log; -import cx.ring.interfaces.AccountsInterface; -import cx.ring.service.ConfigurationManagerCallback; - -public abstract class AccountWrapperFragment extends Fragment implements AccountsInterface -{ - static final String TAG = "AccountWrapperFragment"; - - private AccountsReceiver mReceiver; - - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mReceiver = new AccountsReceiver(); - } - - @Override - public void onResume() { - super.onResume(); - IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(ConfigurationManagerCallback.ACCOUNT_STATE_CHANGED); - intentFilter.addAction(ConfigurationManagerCallback.ACCOUNTS_CHANGED); - getActivity().registerReceiver(mReceiver, intentFilter); - } - - @Override - public void accountsChanged() { - Log.i(TAG, "accountsChanged"); - } - - @Override - public void accountStateChanged(String accoundID, String state, int code) { - Log.i(TAG, "accountStateChanged" + accoundID + " " + state + " " + code); - } - - @Override - public void onPause() { - super.onPause(); - getActivity().unregisterReceiver(mReceiver); - } - - public class AccountsReceiver extends BroadcastReceiver { - - private final String TAG = AccountsReceiver.class.getSimpleName(); - - @Override - public void onReceive(Context context, Intent intent) { - if (intent.getAction().contentEquals(ConfigurationManagerCallback.ACCOUNT_STATE_CHANGED)) { - Log.i(TAG, "Received " + intent.getAction() + " " + intent.getStringExtra("Account") + " " + intent.getStringExtra("state") + " " + intent.getIntExtra("code", 0)); - accountStateChanged(intent.getStringExtra("Account"), intent.getStringExtra("state"), intent.getIntExtra("code", 0)); - } else if (intent.getAction().contentEquals(ConfigurationManagerCallback.ACCOUNTS_CHANGED)) { - Log.i(TAG, "Received" + intent.getAction()); - accountsChanged(); - } - - } - } - - -} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/fragments/AccountsManagementFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/AccountsManagementFragment.java index d9b2570ca..f8ca5ab5e 100644 --- a/ring-android/app/src/main/java/cx/ring/fragments/AccountsManagementFragment.java +++ b/ring-android/app/src/main/java/cx/ring/fragments/AccountsManagementFragment.java @@ -2,7 +2,8 @@ * Copyright (C) 2004-2014 Savoir-Faire Linux Inc. * * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> - * Alexandre Lision <alexandre.lision@savoirfairelinux.com> + * Alexandre Lision <alexandre.lision@savoirfairelinux.com> + * Adrien Béraud <adrien.beraud@savoirfairelinux.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -35,11 +36,12 @@ package cx.ring.fragments; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.app.Activity; -import android.app.LoaderManager; -import android.content.AsyncTaskLoader; +import android.app.Fragment; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.content.Loader; +import android.content.IntentFilter; +import android.net.Uri; import android.os.Bundle; import android.os.RemoteException; import android.support.design.widget.FloatingActionButton; @@ -53,28 +55,25 @@ import cx.ring.R; import cx.ring.client.AccountEditionActivity; import cx.ring.client.AccountWizard; import cx.ring.client.HomeActivity; -import cx.ring.loaders.AccountsStateLoader; -import cx.ring.loaders.AccountsLoader; -import cx.ring.loaders.LoaderConstants; import cx.ring.model.account.Account; -import cx.ring.model.account.AccountDetailAdvanced; import cx.ring.model.account.AccountDetailBasic; -import cx.ring.service.ISipService; +import cx.ring.service.LocalService; import cx.ring.views.dragsortlv.DragSortListView; import java.io.File; import java.util.ArrayList; -import java.util.Map; +import java.util.List; -public class AccountsManagementFragment extends AccountWrapperFragment implements LoaderManager.LoaderCallbacks<Bundle> { - static final String TAG = "AccountManagementFrag"; - static final String DEFAULT_ACCOUNT_ID = "IP2IP"; - static final int ACCOUNT_CREATE_REQUEST = 1; +public class AccountsManagementFragment extends Fragment { + static final String TAG = AccountsManagementFragment.class.getSimpleName(); + + private static final String DEFAULT_ACCOUNT_ID = "IP2IP"; + public static final int ACCOUNT_CREATE_REQUEST = 1; public static final int ACCOUNT_EDIT_REQUEST = 2; - AccountsAdapter mAccountsAdapter; - AccountsAdapter mIP2IPAdapter; + private AccountsAdapter mAccountsAdapter; + private AccountsAdapter mIP2IPAdapter; - DragSortListView mDnDListView; + private DragSortListView mDnDListView; private View mLoadingView; private int mShortAnimationDuration; @@ -86,46 +85,31 @@ public class AccountsManagementFragment extends AccountWrapperFragment implement mAccountsAdapter.remove(item); mAccountsAdapter.insert(item, to); try { - mCallbacks.getService().setAccountOrder(mAccountsAdapter.generateAccountOrder()); + mCallbacks.getService().getRemoteService().setAccountOrder(mAccountsAdapter.generateAccountOrder()); } catch (RemoteException e) { e.printStackTrace(); } } } - - }; - - private Callbacks mCallbacks = sDummyCallbacks; - private Account ip2ip; - private static Callbacks sDummyCallbacks = new Callbacks() { - - @Override - public ISipService getService() { - return null; - } }; - private AccountsLoader accountsLoader; - public interface Callbacks { - - public ISipService getService(); - - } + private LocalService.Callbacks mCallbacks = LocalService.DUMMY_CALLBACKS; + //private Account ip2ip; @Override public void onAttach(Activity activity) { super.onAttach(activity); - if (!(activity instanceof Callbacks)) { + if (!(activity instanceof LocalService.Callbacks)) { throw new IllegalStateException("Activity must implement fragment's callbacks."); } - mCallbacks = (Callbacks) activity; + mCallbacks = (LocalService.Callbacks) activity; } @Override public void onDetach() { super.onDetach(); - mCallbacks = sDummyCallbacks; + mCallbacks = LocalService.DUMMY_CALLBACKS; } @Override @@ -133,13 +117,23 @@ public class AccountsManagementFragment extends AccountWrapperFragment implement super.onCreate(savedInstanceState); Log.i(TAG, "Create Account Management Fragment"); - mAccountsAdapter = new AccountsAdapter(getActivity(), new ArrayList<Account>()); - mIP2IPAdapter = new AccountsAdapter(getActivity(), new ArrayList<Account>()); + mAccountsAdapter = new AccountsAdapter(getActivity()); + mIP2IPAdapter = new AccountsAdapter(getActivity()); this.setHasOptionsMenu(true); mShortAnimationDuration = getResources().getInteger(android.R.integer.config_mediumAnimTime); Log.i(TAG, "anim time: " + mShortAnimationDuration); - getLoaderManager().initLoader(LoaderConstants.ACCOUNTS_LOADER, null, this); + //getLoaderManager().initLoader(LoaderConstants.ACCOUNTS_LOADER, null, this); + + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(LocalService.ACTION_ACCOUNT_UPDATE); + getActivity().registerReceiver(mReceiver, intentFilter); + } + + @Override + public void onDestroy() { + super.onDestroy(); + getActivity().unregisterReceiver(mReceiver); } @Override @@ -170,8 +164,7 @@ public class AccountsManagementFragment extends AccountWrapperFragment implement @Override public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) { - launchAccountEditActivity(ip2ip); - + launchAccountEditActivity(mIP2IPAdapter.accounts.get(0)); } }); @@ -185,7 +178,8 @@ public class AccountsManagementFragment extends AccountWrapperFragment implement public void onResume() { super.onResume(); - accountsLoader.onContentChanged(); + //accountsLoader.onContentChanged(); + refreshAccountList(); ((HomeActivity) getActivity()).setToolbarState(true, R.string.menu_item_accounts); FloatingActionButton btn = ((HomeActivity) getActivity()).getActionButton(); btn.setImageResource(R.drawable.ic_add_white_24dp); @@ -196,6 +190,7 @@ public class AccountsManagementFragment extends AccountWrapperFragment implement startActivityForResult(intent, ACCOUNT_CREATE_REQUEST); } }); + crossfade(); } @Override @@ -220,29 +215,17 @@ public class AccountsManagementFragment extends AccountWrapperFragment implement private void launchAccountEditActivity(Account acc) { Log.i(TAG, "Launch account edit activity"); - Intent intent = new Intent().setClass(getActivity(), AccountEditionActivity.class); - Bundle bundle = new Bundle(); - bundle.putParcelable("account", acc); - - intent.putExtras(bundle); - + Intent intent = new Intent() + .setClass(getActivity(), AccountEditionActivity.class) + .setAction(Intent.ACTION_EDIT) + .setData(Uri.withAppendedPath(AccountEditionActivity.CONTENT_URI, acc.getAccountID())); startActivityForResult(intent, ACCOUNT_EDIT_REQUEST); } - @Override - public void accountsChanged() { - accountsLoader.onContentChanged(); - } - - @Override - public void accountStateChanged(String accoundID, String state, int code) { - mAccountsAdapter.updateAccount(accoundID, state, code); - } - @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); - accountsLoader.onContentChanged(); + refreshAccountList(); } /** @@ -254,13 +237,12 @@ public class AccountsManagementFragment extends AccountWrapperFragment implement // private static final String TAG = AccountSelectionAdapter.class.getSimpleName(); - ArrayList<Account> accounts; - Context mContext; + private final ArrayList<Account> accounts = new ArrayList<>(); + private final Context mContext; - public AccountsAdapter(Context cont, ArrayList<Account> newList) { + public AccountsAdapter(Context c) { super(); - accounts = newList; - mContext = cont; + mContext = c; } public void insert(Account item, int to) { @@ -335,7 +317,7 @@ public class AccountsManagementFragment extends AccountWrapperFragment implement public void onClick(View v) { item.setEnabled(!item.isEnabled()); try { - mCallbacks.getService().setAccountDetails(item.getAccountID(), item.getDetails()); + mCallbacks.getService().getRemoteService().setAccountDetails(item.getAccountID(), item.getDetails()); } catch (RemoteException e) { e.printStackTrace(); } @@ -381,39 +363,27 @@ public class AccountsManagementFragment extends AccountWrapperFragment implement } - public void addAll(ArrayList<Account> results) { + public void addAll(List<Account> results) { Log.i(TAG, "AccountsAdapter addAll " + results.size()); accounts.addAll(results); - /*for (final Account a : results) { - final String acc_id = a.getAccountID(); - getLoaderManager().initLoader((int)(Long.parseLong(acc_id.substring(0, 8), 16) & 0x00000000FFFFFFFFL), null, new LoaderManager.LoaderCallbacks<Map<String, String>>() { - @Override - public Loader<Map<String, String>> onCreateLoader(int id, Bundle args) { - Log.i(TAG, "AccountsAdapter addAll onCreateLoader " + id + " " + acc_id); - return new AccountsStateLoader(getActivity(), mCallbacks.getService(), acc_id); - } - @Override - public void onLoadFinished(Loader<Map<String, String>> loader, Map<String, String> data) { - Log.w(TAG, "onLoadFinished"); - a.setRegistered_state(data.get(AccountDetailAdvanced.CONFIG_ACCOUNT_REGISTRATION_STATUS), Integer.getInteger(data.get(AccountDetailAdvanced.CONFIG_ACCOUNT_REGISTRATION_STATE_CODE))); - notifyDataSetChanged(); - } - @Override - public void onLoaderReset(Loader<Map<String, String>> loader) { - } - }); - }*/ + notifyDataSetChanged(); + } + + public void replaceAll(List<Account> results) { + Log.i(TAG, "AccountsAdapter replaceAll " + results.size()); + accounts.clear(); + accounts.addAll(results); notifyDataSetChanged(); } /** - * Modify state of specific account + * Modify State of specific account */ public void updateAccount(String accoundID, String state, int code) { Log.i(TAG, "updateAccount:" + state); for (Account a : accounts) { if (a.getAccountID().contentEquals(accoundID)) { - a.setRegistered_state(state, code); + a.setRegistrationState(state, code); notifyDataSetChanged(); return; } @@ -453,33 +423,24 @@ public class AccountsManagementFragment extends AccountWrapperFragment implement }); } + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().contentEquals(LocalService.ACTION_ACCOUNT_UPDATE)) { + refreshAccountList(); + } + } + }; - @Override - public AsyncTaskLoader<Bundle> onCreateLoader(int arg0, Bundle arg1) { - accountsLoader = new AccountsLoader(getActivity(), mCallbacks.getService()); - return accountsLoader; - } - - @Override - public void onLoadFinished(Loader<Bundle> bundleLoader, Bundle results) { - mAccountsAdapter.removeAll(); - ArrayList<Account> tmp = results.getParcelableArrayList(AccountsStateLoader.ACCOUNTS); - ip2ip = results.getParcelable(AccountsStateLoader.ACCOUNT_IP2IP); - mAccountsAdapter.addAll(tmp); - mIP2IPAdapter.removeAll(); - mIP2IPAdapter.insert(ip2ip, 0); + private void refreshAccountList() { + Log.i(TAG, "refreshAccountList"); + mAccountsAdapter.replaceAll(mCallbacks.getService().getAccounts()); if (mAccountsAdapter.isEmpty()) { mDnDListView.setEmptyView(getView().findViewById(R.id.empty_account_list)); } - for (Account acc : tmp) { - - } - crossfade(); - } - - @Override - public void onLoaderReset(Loader<Bundle> bundleLoader) { - + mIP2IPAdapter.replaceAll(mCallbacks.getService().getIP2IPAccount()); + Log.i(TAG, "refreshAccountList DONE"); + mAccountsAdapter.notifyDataSetChanged(); + mIP2IPAdapter.notifyDataSetChanged(); } - } diff --git a/ring-android/app/src/main/java/cx/ring/fragments/AudioManagementFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/AudioManagementFragment.java index ec688e08a..4f90c0055 100644 --- a/ring-android/app/src/main/java/cx/ring/fragments/AudioManagementFragment.java +++ b/ring-android/app/src/main/java/cx/ring/fragments/AudioManagementFragment.java @@ -42,6 +42,7 @@ import cx.ring.model.account.AccountDetailAdvanced; import cx.ring.model.account.Account; import cx.ring.model.Codec; import cx.ring.service.ISipService; +import cx.ring.service.LocalService; import cx.ring.views.dragsortlv.DragSortListView; import android.app.Activity; @@ -73,26 +74,23 @@ public class AudioManagementFragment extends PreferenceFragment { ArrayList<Codec> codecs; private DragSortListView mCodecList; CodecAdapter listAdapter; - private static Callbacks sDummyCallbacks = new Callbacks() { - + private static final Callbacks sDummyCallbacks = new Callbacks() { @Override - public ISipService getService() { + public ISipService getRemoteService() { + return null; + } + @Override + public LocalService getService() { return null; } - @Override public Account getAccount() { return null; } - }; - public interface Callbacks { - - public ISipService getService(); - - public Account getAccount(); - + public interface Callbacks extends LocalService.Callbacks { + Account getAccount(); } @Override @@ -104,7 +102,7 @@ public class AudioManagementFragment extends PreferenceFragment { mCallbacks = (Callbacks) activity; try { - codecs = (ArrayList<Codec>) mCallbacks.getService().getAudioCodecList(mCallbacks.getAccount().getAccountID()); + codecs = (ArrayList<Codec>) mCallbacks.getRemoteService().getAudioCodecList(mCallbacks.getAccount().getAccountID()); //mCallbacks.getService().getRingtoneList(); } catch (RemoteException e) { e.printStackTrace(); @@ -125,7 +123,7 @@ public class AudioManagementFragment extends PreferenceFragment { listAdapter.remove(item); listAdapter.insert(item, to); try { - mCallbacks.getService().setActiveCodecList(getActiveCodecList(), mCallbacks.getAccount().getAccountID()); + mCallbacks.getRemoteService().setActiveCodecList(getActiveCodecList(), mCallbacks.getAccount().getAccountID()); } catch (RemoteException e) { e.printStackTrace(); } @@ -193,7 +191,7 @@ public class AudioManagementFragment extends PreferenceFragment { listAdapter.getItem(pos).toggleState(); listAdapter.notifyDataSetChanged(); try { - mCallbacks.getService().setActiveCodecList(getActiveCodecList(), mCallbacks.getAccount().getAccountID()); + mCallbacks.getRemoteService().setActiveCodecList(getActiveCodecList(), mCallbacks.getAccount().getAccountID()); } catch (RemoteException e) { e.printStackTrace(); } diff --git a/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.java index 24bd94f8d..1abbce9ef 100644 --- a/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.java +++ b/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.java @@ -33,59 +33,68 @@ package cx.ring.fragments; import android.app.Activity; import android.app.FragmentManager; +import android.net.Uri; +import android.support.design.widget.FloatingActionButton; +import android.support.v4.app.NotificationCompat; +import android.support.v4.app.NotificationManagerCompat; +import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Resources; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.PointF; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.os.Bundle; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.os.RemoteException; -import android.util.FloatMath; import android.util.Log; import android.view.*; -import android.view.SurfaceHolder.Callback; import android.view.View.OnClickListener; import android.view.inputmethod.InputMethodManager; import android.widget.*; import android.widget.CompoundButton.OnCheckedChangeListener; import cx.ring.R; +import cx.ring.adapters.ContactPictureTask; +import cx.ring.client.CallActivity; +import cx.ring.client.ConversationActivity; +import cx.ring.client.HomeActivity; import cx.ring.interfaces.CallInterface; -import cx.ring.service.ISipService; import java.util.ArrayList; import java.util.Locale; +import java.util.Random; -import cx.ring.model.Attractor; -import cx.ring.model.Bubble; import cx.ring.model.BubbleContact; -import cx.ring.model.BubbleModel; -import cx.ring.model.BubbleUser; -import cx.ring.model.BubblesView; import cx.ring.model.CallContact; import cx.ring.model.Conference; import cx.ring.model.SecureSipCall; import cx.ring.model.SipCall; +import cx.ring.model.account.Account; +import cx.ring.service.LocalService; +import cx.ring.service.SipService; -public class CallFragment extends CallableWrapperFragment implements CallInterface, Callback { +public class CallFragment extends CallableWrapperFragment implements CallInterface { static final String TAG = "CallFragment"; - - private float bubbleSize = 75; // dip private float attractorSize = 40; public static final int REQUEST_TRANSFER = 10; // Screen wake lock for incoming call private WakeLock mScreenWakeLock; + private ImageView contactBubbleView; + private TextView contactBubbleTxt; + private View acceptButton; + private View refuseButton; + private View hangupButton; + + private View securityIndicator; + /* private BubblesView mBubbleView; private BubbleModel mBubbleModel; @@ -95,15 +104,15 @@ public class CallFragment extends CallableWrapperFragment implements CallInterfa private Bitmap buttonUnhold; private Bitmap buttonTransfer; private Bitmap buttonHangUp; - +*/ private final int BTN_MSG_IDX = 0; private final int BTN_HOLD_IDX = 1; private final int BTN_TRANSFER_IDX = 2; private final int BTN_HUNGUP_IDX = 3; - +/* private BubbleModel.ActionGroup userActions; private BubbleModel.ActionGroup callActions; - +*/ ViewSwitcher mSecuritySwitch; private TextView mCallStatusTxt; private ToggleButton mToggleSpeakers; @@ -124,10 +133,11 @@ public class CallFragment extends CallableWrapperFragment implements CallInterfa @Override public void onCreate(Bundle savedBundle) { + Log.i(TAG, "onCreate"); super.onCreate(savedBundle); Resources r = getResources(); - +/* bubbleSize = r.getDimension(R.dimen.bubble_size); attractorSize = r.getDimension(R.dimen.bubble_action_size); float attractorMargin = r.getDimension(R.dimen.bubble_action_margin); @@ -203,7 +213,6 @@ public class CallFragment extends CallableWrapperFragment implements CallInterfa @Override public boolean bubbleEjected(Bubble b) { - //if (b.isUser) { try { if (b.isConference()) mCallbacks.getService().hangUpConference(b.getCallID()); @@ -214,15 +223,13 @@ public class CallFragment extends CallableWrapperFragment implements CallInterfa e.printStackTrace(); } return true; - /*} - return false;*/ } - }); + });*/ setHasOptionsMenu(true); PowerManager powerManager = (PowerManager) getActivity().getSystemService(Context.POWER_SERVICE); mScreenWakeLock = powerManager.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE, - "org.sflphone.onIncomingCall"); + "cx.ring.onIncomingCall"); mScreenWakeLock.setReferenceCounted(false); Log.d(TAG, "Acquire wake up lock"); @@ -240,65 +247,38 @@ public class CallFragment extends CallableWrapperFragment implements CallInterfa } /** - * A dummy implementation of the {@link Callbacks} interface that does nothing. Used only when this fragment is not attached to an activity. + * The Activity calling this fragment has to implement this interface */ - private static Callbacks sDummyCallbacks = new Callbacks() { - - @Override - public void onFragmentCreated() { - - } + public interface Callbacks extends LocalService.Callbacks { + void onFragmentCreated(); + void startTimer(); + void terminateCall(); + Conference getDisplayedConference(); + void updateDisplayedConference(Conference c); + } + /** + * A dummy implementation of the {@link Callbacks} interface that does nothing. Used only when this fragment is not attached to an activity. + */ + private static class DummyCallbacks extends LocalService.DummyCallbacks implements Callbacks { @Override - public ISipService getService() { - return null; - } - + public void onFragmentCreated() {} @Override - public void terminateCall() { - } - + public void terminateCall() {} @Override public Conference getDisplayedConference() { return null; } - @Override - public void updateDisplayedConference(Conference c) { - } - - @Override - public void startTimer() { - } - + public void updateDisplayedConference(Conference c) { } @Override - public void slideChatScreen() { - } - - }; - - /** - * The Activity calling this fragment has to implement this interface - */ - public interface Callbacks { - - public void onFragmentCreated(); - - public ISipService getService(); - - public void startTimer(); - - public void slideChatScreen(); - - public void terminateCall(); - - public Conference getDisplayedConference(); - - public void updateDisplayedConference(Conference c); + public void startTimer() { } } + private static final Callbacks sDummyCallbacks = new DummyCallbacks(); @Override public void onAttach(Activity activity) { + Log.i(TAG, "onAttach"); super.onAttach(activity); if (!(activity instanceof Callbacks)) { @@ -312,6 +292,30 @@ public class CallFragment extends CallableWrapperFragment implements CallInterfa } + public void refreshState() { + Conference conf = getConference(); + if (conf == null) { + contactBubbleView.setImageBitmap(null); + contactBubbleTxt.setText(""); + acceptButton.setVisibility(View.GONE); + refuseButton.setVisibility(View.GONE); + hangupButton.setVisibility(View.GONE); + } else if (conf.getParticipants().size() == 1) { + SipCall call = conf.getParticipants().get(0); + if (call.isIncoming() && call.isRinging()) { + Log.w(TAG, "CallFragment refreshState INCOMING " + call.getCallId()); + initIncomingCallDisplay(); + } else if (conf.getParticipants().get(0).isRinging()) { + Log.w(TAG, "CallFragment refreshState RINGING " + call.getCallId()); + initOutGoingCallDisplay(); + } else if (call.isOngoing()) { + initNormalStateDisplay(); + } + } else if (conf.getParticipants().size() > 1) { + initNormalStateDisplay(); + } + } + @Override public void onCreateOptionsMenu(Menu m, MenuInflater inf) { super.onCreateOptionsMenu(m, inf); @@ -323,7 +327,15 @@ public class CallFragment extends CallableWrapperFragment implements CallInterfa super.onOptionsItemSelected(item); switch (item.getItemId()) { case R.id.menuitem_chat: - mCallbacks.slideChatScreen(); + //mCallbacks.slideChatScreen(); + Intent intent = new Intent() + .setClass(getActivity(), ConversationActivity.class) + .setAction(Intent.ACTION_VIEW) + .setData(Uri.withAppendedPath(ConversationActivity.CONTENT_URI, getConference().getParticipants().get(0).getContact().getIds().get(0))); + intent.putExtra("resuming", true); + //intent.putExtra("contact", ((Conversation) v.getTag()).getContact()); + //intent.putExtra("conversation", (Conversation) v.getTag()); + startActivityForResult(intent, HomeActivity.REQUEST_CODE_CONVERSATION); break; } @@ -345,6 +357,7 @@ public class CallFragment extends CallableWrapperFragment implements CallInterfa public void onResume() { super.onResume(); initializeWiFiListener(); + refreshState(); } @Override @@ -358,7 +371,13 @@ public class CallFragment extends CallableWrapperFragment implements CallInterfa @Override public void callStateChanged(Conference updated, String callID, String newState) { - mCallbacks.updateDisplayedConference(updated); + Conference cur = getConference(); + if (cur.getId().equals(callID) || cur.getCallById(callID) != null) { + mCallbacks.updateDisplayedConference(updated); + } else { + return; + } + Log.i(TAG, "Call :" + callID + " " + newState); if (getConference().isOnGoing()) { @@ -371,6 +390,8 @@ public class CallFragment extends CallableWrapperFragment implements CallInterfa } else initOutGoingCallDisplay(); } else { + NotificationManagerCompat notificationManager = NotificationManagerCompat.from(getActivity()); + notificationManager.cancel(getConference().notificationId); mCallStatusTxt.setText(newState); mCallbacks.terminateCall(); } @@ -420,7 +441,7 @@ public class CallFragment extends CallableWrapperFragment implements CallInterfa transfer = data.getParcelableExtra("transfer"); try { - mCallbacks.getService().attendedTransfer(transfer.getCallId(), c.getParticipants().get(0).getCallId()); + mCallbacks.getRemoteService().attendedTransfer(transfer.getCallId(), c.getParticipants().get(0).getCallId()); } catch (RemoteException e) { e.printStackTrace(); @@ -431,17 +452,17 @@ public class CallFragment extends CallableWrapperFragment implements CallInterfa String to = data.getStringExtra("to_number"); transfer = data.getParcelableExtra("transfer"); try { - mCallbacks.getService().transfer(transfer.getCallId(), to); - mCallbacks.getService().hangUp(transfer.getCallId()); + mCallbacks.getRemoteService().transfer(transfer.getCallId(), to); + mCallbacks.getRemoteService().hangUp(transfer.getCallId()); } catch (RemoteException e) { e.printStackTrace(); } break; case Activity.RESULT_CANCELED: default: - synchronized (mBubbleModel) { + /*synchronized (mBubbleModel) { mBubbleModel.clear(); - } + }*/ initNormalStateDisplay(); break; } @@ -453,10 +474,17 @@ public class CallFragment extends CallableWrapperFragment implements CallInterfa Log.i(TAG, "onCreateView"); final ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.frag_call, container, false); +/* mBubbleView = (BubblesView) rootView.findViewById(R.id.main_view); //mBubbleView.setFragment(this); mBubbleView.setModel(mBubbleModel); mBubbleView.getHolder().addCallback(this); +*/ + contactBubbleView = (ImageView) rootView.findViewById(R.id.contact_bubble); + contactBubbleTxt = (TextView) rootView.findViewById(R.id.contact_bubble_txt); + acceptButton = rootView.findViewById(R.id.call_accept_btn); + refuseButton = rootView.findViewById(R.id.call_refuse_btn); + hangupButton = rootView.findViewById(R.id.call_hangup_btn); mCallStatusTxt = (TextView) rootView.findViewById(R.id.call_status_txt); @@ -468,18 +496,18 @@ public class CallFragment extends CallableWrapperFragment implements CallInterfa @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { try { - mCallbacks.getService().toggleSpeakerPhone(isChecked); + mCallbacks.getRemoteService().toggleSpeakerPhone(isChecked); } catch (RemoteException e) { e.printStackTrace(); } } }); - +/* synchronized (mBubbleModel) { mBubbleModel.setSize(mBubbleView.getWidth(), mBubbleView.getHeight() - mToggleSpeakers.getHeight(), bubbleSize); - } - + }*/ +/* rootView.findViewById(R.id.dialpad_btn).setOnClickListener(new OnClickListener() { @Override @@ -487,7 +515,9 @@ public class CallFragment extends CallableWrapperFragment implements CallInterfa InputMethodManager lManager = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); lManager.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, InputMethodManager.HIDE_IMPLICIT_ONLY); } - }); + });*/ + + securityIndicator = rootView.findViewById(R.id.security_indicator); return rootView; } @@ -498,7 +528,49 @@ public class CallFragment extends CallableWrapperFragment implements CallInterfa private void initNormalStateDisplay() { Log.i(TAG, "Start normal display"); - synchronized (mBubbleModel) { + mCallbacks.startTimer(); + acceptButton.setVisibility(View.GONE); + refuseButton.setVisibility(View.GONE); + + final SipCall call = getConference().getParticipants().get(0); + CallContact contact = call.getContact(); + //contactBubbleView.setImageBitmap(getContactPhoto(contact, contactBubbleView.getWidth())); + new ContactPictureTask(getActivity(), contactBubbleView, contact).run(); + contactBubbleTxt.setText(contact.getDisplayName()); + + hangupButton.setVisibility(View.VISIBLE); + hangupButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + try { + mCallbacks.getRemoteService().hangUp(call.getCallId()); + mCallbacks.terminateCall(); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + }); + + NotificationCompat.Builder noti = new NotificationCompat.Builder(getActivity()) + .setContentTitle("Current call with " + contact.getDisplayName()) + .setOngoing(true) + .setSmallIcon(R.drawable.ic_launcher) + .setContentText("call") + .setContentIntent(PendingIntent.getActivity(getActivity(), new Random().nextInt(), + new Intent(getActivity(), CallActivity.class).putExtra("conference", getConference()), PendingIntent.FLAG_ONE_SHOT)) + .addAction(R.drawable.ic_call_end_white_24dp, "Hangup", + PendingIntent.getService(getActivity(), new Random().nextInt(), + new Intent(getActivity(), SipService.class) + .setAction(SipService.ACTION_CALL_END) + .putExtra("conf", call.getCallId()), + PendingIntent.FLAG_ONE_SHOT)); + Log.w("CallNotification ", "Updating " + getConference().notificationId + " for " + contact.getDisplayName()); + NotificationManagerCompat notificationManager = NotificationManagerCompat.from(getActivity()); + notificationManager.notify(getConference().notificationId, noti.build()); + + getActivity().getActionBar().setTitle(contact.getDisplayName()); + + /*synchronized (mBubbleModel) { mCallbacks.startTimer(); mBubbleModel.clearAttractors(); PointF c = mBubbleModel.getCircleCenter(); @@ -513,19 +585,28 @@ public class CallFragment extends CallableWrapperFragment implements CallInterfa if (partee == null) { continue; } - float dX = FloatMath.cos(angle_part * i + angle_shift) * radiusCalls; - float dY = FloatMath.sin(angle_part * i + angle_shift) * radiusCalls; + double dX = Math.cos(angle_part * i + angle_shift) * radiusCalls; + double dY = Math.sin(angle_part * i + angle_shift) * radiusCalls; getBubbleFor(partee, (int) (c.x + dX), (int) (c.y + dY)); } } - mBubbleModel.curState = BubbleModel.State.Incall; + mBubbleModel.curState = BubbleModel.State.Incall;*/ updateSecurityDisplay(); } private void updateSecurityDisplay() { - //First we check if at least one participant use a security layer. - if (!getConference().useSecureLayer()) + boolean secure_call = false; + for (SipCall c : getConference().getParticipants()) { + Account acc = mCallbacks.getService().getAccount(c.getAccount()); + if (acc != null && (acc.isRing() || acc.useSecureLayer())) { + secure_call = true; + break; + } + } + + securityIndicator.setVisibility(secure_call ? View.VISIBLE : View.GONE); + if (!secure_call) return; Log.i(TAG, "Enable security display"); @@ -550,7 +631,7 @@ public class CallFragment extends CallableWrapperFragment implements CallInterfa @Override public void onClick(View v) { try { - mCallbacks.getService().confirmSAS(secured.getCallId()); + mCallbacks.getRemoteService().confirmSAS(secured.getCallId()); showLock(R.drawable.green_lock); } catch (RemoteException e) { e.printStackTrace(); @@ -573,18 +654,86 @@ public class CallFragment extends CallableWrapperFragment implements CallInterfa mSecuritySwitch.setVisibility(View.VISIBLE); } + protected Bitmap getContactPhoto(CallContact contact, int size) { + if (contact.getPhotoId() > 0) { + return ContactPictureTask.loadContactPhoto(getActivity().getContentResolver(), contact.getId()); + } else { + return ContactPictureTask.decodeSampledBitmapFromResource(getResources(), R.drawable.ic_contact_picture, size, size); + } + } + private void initIncomingCallDisplay() { Log.i(TAG, "Start incoming display"); - if (getConference().getParticipants().get(0).getAccount().isAutoanswerEnabled()) { + if (mCallbacks.getService().getAccount(getConference().getParticipants().get(0).getAccount()).isAutoanswerEnabled()) { try { - mCallbacks.getService().accept(getConference().getParticipants().get(0).getCallId()); + mCallbacks.getRemoteService().accept(getConference().getParticipants().get(0).getCallId()); } catch (RemoteException e) { e.printStackTrace(); } catch (NullPointerException e) { e.printStackTrace(); } } else { - getBubbleFor(getConference().getParticipants().get(0), mBubbleModel.getWidth() / 2, 2 * mBubbleModel.getHeight() / 3); + final SipCall call = getConference().getParticipants().get(0); + CallContact contact = call.getContact(); + //contactBubbleView.setImageBitmap(getContactPhoto(contact, contactBubbleView.getWidth())); + new ContactPictureTask(getActivity(), contactBubbleView, contact).run(); + contactBubbleTxt.setText(contact.getDisplayName()); + acceptButton.setVisibility(View.VISIBLE); + acceptButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + acceptButton.setOnClickListener(null); + refuseButton.setOnClickListener(null); + try { + mCallbacks.getRemoteService().accept(call.getCallId()); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + }); + refuseButton.setVisibility(View.VISIBLE); + refuseButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + acceptButton.setOnClickListener(null); + refuseButton.setOnClickListener(null); + try { + mCallbacks.getRemoteService().refuse(call.getCallId()); + mCallbacks.terminateCall(); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + }); + hangupButton.setVisibility(View.GONE); + + NotificationCompat.Builder noti = new NotificationCompat.Builder(getActivity()) + .setContentTitle("Incoming call with " + contact.getDisplayName()) + .setContentText("incoming call") + .setOngoing(true) + .setSmallIcon(R.drawable.ic_launcher) + .setContentIntent(PendingIntent.getActivity(getActivity(), new Random().nextInt(), + new Intent(getActivity(), CallActivity.class).putExtra("conference", getConference()), PendingIntent.FLAG_ONE_SHOT)) + .addAction(R.drawable.ic_action_accept, "Accept", + PendingIntent.getService(getActivity(), new Random().nextInt(), + new Intent(getActivity(), SipService.class) + .setAction(SipService.ACTION_CALL_ACCEPT) + .putExtra("conf", call.getCallId()), + PendingIntent.FLAG_ONE_SHOT)) + .addAction(R.drawable.ic_call_end_white_24dp, "Refuse", + PendingIntent.getService(getActivity(), new Random().nextInt(), + new Intent(getActivity(), SipService.class) + .setAction(SipService.ACTION_CALL_REFUSE) + .putExtra("conf", call.getCallId()), + PendingIntent.FLAG_ONE_SHOT)); + Log.w("CallNotification ", "Updating for incoming " + getConference().notificationId); + NotificationManagerCompat notificationManager = NotificationManagerCompat.from(getActivity()); + notificationManager.notify(getConference().notificationId, noti.build()); + + getActivity().getActionBar().setTitle(contact.getDisplayName()); + + + /*getBubbleFor(getConference().getParticipants().get(0), mBubbleModel.getWidth() / 2, 2 * mBubbleModel.getHeight() / 3); synchronized (mBubbleModel) { mBubbleModel.clearAttractors(); mBubbleModel.addAttractor(new Attractor(new PointF(3 * mBubbleModel.getWidth() / 4, 2 * mBubbleModel.getHeight() / 3), attractorSize, new Attractor.Callback() { @@ -616,13 +765,56 @@ public class CallFragment extends CallableWrapperFragment implements CallInterfa } }, buttonHangUp)); } - mBubbleModel.curState = BubbleModel.State.Incoming; + mBubbleModel.curState = BubbleModel.State.Incoming;*/ } } private void initOutGoingCallDisplay() { Log.i(TAG, "Start outgoing display"); - synchronized (mBubbleModel) { + + final SipCall call = getConference().getParticipants().get(0); + CallContact contact = call.getContact(); + //contactBubbleView.setImageBitmap(getContactPhoto(contact, contactBubbleView.getWidth())); + new ContactPictureTask(getActivity(), contactBubbleView, contact).run(); + contactBubbleTxt.setText(contact.getDisplayName()); + + acceptButton.setVisibility(View.GONE); + refuseButton.setVisibility(View.GONE); + + hangupButton.setVisibility(View.VISIBLE); + hangupButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + try { + mCallbacks.getRemoteService().hangUp(call.getCallId()); + mCallbacks.terminateCall(); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + }); + + NotificationCompat.Builder noti = new NotificationCompat.Builder(getActivity()) + .setContentTitle("Outgoing call with " + contact.getDisplayName()) + .setOngoing(true) + .setSmallIcon(R.drawable.ic_launcher) + .setContentText("Outgoing call") + .setContentIntent(PendingIntent.getActivity(getActivity(), new Random().nextInt(), + new Intent(getActivity(), CallActivity.class).putExtra("conference", getConference()), PendingIntent.FLAG_ONE_SHOT)) + .addAction(R.drawable.ic_call_end_white_24dp, "Cancel", + PendingIntent.getService(getActivity(), new Random().nextInt(), + new Intent(getActivity(), SipService.class) + .setAction(SipService.ACTION_CALL_END) + .putExtra("conf", call.getCallId()), + PendingIntent.FLAG_ONE_SHOT)); + + Log.w("CallNotification ", "Updating for outgoing " + getConference().notificationId); + NotificationManagerCompat notificationManager = NotificationManagerCompat.from(getActivity()); + notificationManager.notify(getConference().notificationId, noti.build()); + + getActivity().getActionBar().setTitle(contact.getDisplayName()); + + /*synchronized (mBubbleModel) { PointF c = mBubbleModel.getCircleCenter(); float radiusCalls = mBubbleModel.getCircleSize(); getBubbleForUser(getConference(), c.x, c.y); @@ -634,18 +826,18 @@ public class CallFragment extends CallableWrapperFragment implements CallInterfa } mBubbleModel.clearAttractors(); } - mBubbleModel.curState = BubbleModel.State.Outgoing; - } - - /** - * Retrieves or create a bubble for a given contact. If the bubble exists, it is moved to the new location. - * - * @param call The call associated to a contact - * @param x Initial or new x position. - * @param y Initial or new y position. - * @return Bubble corresponding to the contact. - */ - private Bubble getBubbleFor(SipCall call, float x, float y) { + mBubbleModel.curState = BubbleModel.State.Outgoing;*/ + } + /* + /** + * Retrieves or create a bubble for a given contact. If the bubble exists, it is moved to the new location. + * + * @param call The call associated to a contact + * @param x Initial or new x position. + * @param y Initial or new y position. + * @return Bubble corresponding to the contact. + */ + /* private Bubble getBubbleFor(SipCall call, float x, float y) { Bubble contact_bubble = mBubbleModel.getBubble(call.getCallId()); if (contact_bubble != null) { ((BubbleContact) contact_bubble).setCall(call); @@ -670,31 +862,41 @@ public class CallFragment extends CallableWrapperFragment implements CallInterfa contact_bubble = new BubbleUser(getActivity(), CallContact.ContactBuilder.buildUserContact(getActivity().getContentResolver()), conf, x, y, bubbleSize * 1.3f); -/* - try { - ((BubbleUser) contact_bubble).setMute(mCallbacks.getService().isCaptureMuted()); - } catch (RemoteException e) { - e.printStackTrace(); - } catch (NullPointerException e1) { - e1.printStackTrace(); - }*/ mBubbleModel.addBubble(contact_bubble); return contact_bubble; } + public boolean canOpenIMPanel() { return mBubbleModel.curState == BubbleModel.State.Incall && (mBubbleView == null || !mBubbleView.isDraggingBubble()); } + @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { synchronized (mBubbleModel) { mBubbleModel.setSize(width, height, bubbleSize); } + Log.w(TAG, "CallFragment surfaceChanged " + getConference().getParticipants().size()); if (getConference().getParticipants().size() == 1) { if (getConference().getParticipants().get(0).isIncoming() && getConference().getParticipants().get(0).isRinging()) { + Log.w(TAG, "CallFragment surfaceChanged INCOMING" + getConference().getParticipants().get(0).getCallId()); initIncomingCallDisplay(); } else if (getConference().getParticipants().get(0).isRinging()) { + Log.w(TAG, "CallFragment surfaceChanged RINGING" + getConference().getParticipants().get(0).getCallId()); + initOutGoingCallDisplay(); + } else if (getConference().getParticipants().get(0).isOngoing()) { + initNormalStateDisplay(); + } + } else if (getConference().getParticipants().size() > 1) { + initNormalStateDisplay(); + } + if (getConference().getParticipants().size() == 1) { + if (getConference().getParticipants().get(0).isIncoming() && getConference().getParticipants().get(0).isRinging()) { + Log.w(TAG, "CallFragment surfaceChanged INCOMING" + getConference().getParticipants().get(0).getCallId()); + initIncomingCallDisplay(); + } else if (getConference().getParticipants().get(0).isRinging()) { + Log.w(TAG, "CallFragment surfaceChanged RINGING" + getConference().getParticipants().get(0).getCallId()); initOutGoingCallDisplay(); } else if (getConference().getParticipants().get(0).isOngoing()) { initNormalStateDisplay(); @@ -703,13 +905,13 @@ public class CallFragment extends CallableWrapperFragment implements CallInterfa initNormalStateDisplay(); } } - +*/ public void makeTransfer(BubbleContact contact) { FragmentManager fm = getFragmentManager(); editName = TransferDFragment.newInstance(); Bundle b = new Bundle(); try { - b.putParcelableArrayList("calls", (ArrayList<Conference>) mCallbacks.getService().getConcurrentCalls()); + b.putParcelableArrayList("calls", (ArrayList<Conference>) mCallbacks.getRemoteService().getConcurrentCalls()); b.putParcelable("call_selected", contact.associated_call); editName.setArguments(b); editName.setTargetFragment(this, REQUEST_TRANSFER); @@ -719,7 +921,7 @@ public class CallFragment extends CallableWrapperFragment implements CallInterfa } } - +/* @Override public void surfaceCreated(SurfaceHolder holder) { @@ -738,10 +940,10 @@ public class CallFragment extends CallableWrapperFragment implements CallInterfa public BubblesView getBubbleView() { return mBubbleView; } - +*/ public void updateTime() { if (getConference() != null) { - long duration = System.currentTimeMillis() - getConference().getParticipants().get(0).getTimestampStart_(); + long duration = System.currentTimeMillis() - getConference().getParticipants().get(0).getTimestampStart(); duration = duration / 1000; if (getConference().isOnGoing()) mCallStatusTxt.setText(String.format("%d:%02d:%02d", duration / 3600, duration % 3600 / 60, duration % 60)); @@ -760,7 +962,7 @@ public class CallFragment extends CallableWrapperFragment implements CallInterfa String toSend = Character.toString(event.getDisplayLabel()); toSend = toSend.toUpperCase(Locale.getDefault()); Log.d(TAG, "toSend " + toSend); - mCallbacks.getService().playDtmf(toSend); + mCallbacks.getRemoteService().playDtmf(toSend); break; } } catch (RemoteException e) { diff --git a/ring-android/app/src/main/java/cx/ring/fragments/CallListFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/CallListFragment.java index 997085ea4..36a19db0a 100644 --- a/ring-android/app/src/main/java/cx/ring/fragments/CallListFragment.java +++ b/ring-android/app/src/main/java/cx/ring/fragments/CallListFragment.java @@ -31,60 +31,124 @@ package cx.ring.fragments; import android.app.Activity; +import android.app.Fragment; +import android.content.BroadcastReceiver; import android.content.ClipData; import android.content.ClipData.Item; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; +import android.graphics.Bitmap; import android.graphics.Color; +import android.net.Uri; import android.os.*; +import android.support.design.widget.FloatingActionButton; import android.util.Log; +import android.util.LruCache; import android.view.DragEvent; import android.view.LayoutInflater; import android.view.View; import android.view.View.DragShadowBuilder; import android.view.View.OnDragListener; import android.view.ViewGroup; +import android.view.animation.AnimationUtils; import android.widget.*; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemLongClickListener; -import cx.ring.client.CallActivity; +import cx.ring.R; +import cx.ring.adapters.ContactPictureTask; +import cx.ring.client.ConversationActivity; import cx.ring.client.HomeActivity; +import cx.ring.client.NewConversationActivity; import cx.ring.model.Conference; -import cx.ring.service.ISipService; +import cx.ring.model.Conversation; +import cx.ring.service.LocalService; +import java.lang.ref.WeakReference; +import java.text.DateFormat; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; -import java.util.Observable; -import java.util.Observer; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; -public class CallListFragment extends CallableWrapperFragment { +public class CallListFragment extends Fragment { private static final String TAG = CallListFragment.class.getSimpleName(); - private Callbacks mCallbacks = sDummyCallbacks; - private TextView mConversationsTitleTextView; - CallListAdapter mConferenceAdapter; + private LocalService.Callbacks mCallbacks = LocalService.DUMMY_CALLBACKS; + //private TextView mConversationsTitleTextView; + private CallListAdapter mConferenceAdapter; + private FloatingActionButton newconv_btn = null; - public static final int REQUEST_TRANSFER = 10; - public static final int REQUEST_CONF = 20; + @Override + public void onStart() { + Log.i(TAG, "onStart"); + super.onStart(); + // Bind to LocalService + /*Intent intent = new Intent(getActivity(), LocalService.class); + getActivity().bindService(intent, mConnection, Context.BIND_AUTO_CREATE);*/ + + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(LocalService.ACTION_CONF_UPDATE); + getActivity().registerReceiver(receiver, intentFilter); + updateLists(); + } - /** - * A dummy implementation of the {@link Callbacks} interface that does nothing. Used only when this fragment is not attached to an activity. - */ - private static Callbacks sDummyCallbacks = new Callbacks() { + @Override + public void onStop() { + Log.i(TAG, "onStop"); + super.onStop(); + // Unbind from the service + /*if (mBound) { + getActivity().unbindService(mConnection); + mBound = false; + }*/ + getActivity().unregisterReceiver(receiver); + } + final BroadcastReceiver receiver = new BroadcastReceiver() { @Override - public ISipService getService() { - Log.i(TAG, "I'm a dummy"); - return null; + public void onReceive(Context context, Intent intent) { + Log.w(TAG, "onReceive " + intent.getAction() + " " + intent.getDataString()); + updateLists(); } + }; +/* + private ServiceConnection mConnection = new ServiceConnection() { + + @Override + public void onServiceConnected(ComponentName className, IBinder service) { + Log.w(TAG, "onServiceConnected " + className.getClassName()); + // We've bound to LocalService, cast the IBinder and get LocalService instance + LocalService.LocalBinder binder = (LocalService.LocalBinder) service; + mService = binder.getService(); + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(LocalService.ACTION_CONF_UPDATE); + getActivity().registerReceiver(receiver, intentFilter); + mBound = true; + + updateLists(); + } + + @Override + public void onServiceDisconnected(ComponentName arg0) { + Log.w(TAG, "onServiceDisconnected " + arg0.getClassName()); + getActivity().unregisterReceiver(receiver); + mBound = false; + } }; +*/ + public static final int REQUEST_TRANSFER = 10; + public static final int REQUEST_CONF = 20; + + /* @Override - public void callStateChanged(Conference c, String callID, String state) { - Log.i(TAG, "callStateChanged" + callID + " " + state); + public void callStateChanged(Conference c, String callID, String State) { + Log.i(TAG, "callStateChanged " + callID + " " + State); updateLists(); } @@ -101,7 +165,7 @@ public class CallListFragment extends CallableWrapperFragment { } @Override - public void confChanged(Conference c, String id, String state) { + public void confChanged(Conference c, String id, String State) { Log.i(TAG, "confChanged"); updateLists(); } @@ -111,26 +175,29 @@ public class CallListFragment extends CallableWrapperFragment { Log.i(TAG, "confChanged"); updateLists(); } - - /** - * The Activity calling this fragment has to implement this interface - */ - public interface Callbacks { - public ISipService getService(); - } +*/ @Override public void onAttach(Activity activity) { + Log.i(TAG, "onAttach"); super.onAttach(activity); - if (!(activity instanceof Callbacks)) { + if (!(activity instanceof LocalService.Callbacks)) { throw new IllegalStateException("Activity must implement fragment's callbacks."); } - mCallbacks = (Callbacks) activity; - + mCallbacks = (LocalService.Callbacks) activity; + if (mCallbacks.getService() != null) { + /*mConvList = new ConversationList(getActivity(), mCallbacks.getService()); + if (mConferenceAdapter != null) { + Log.i(TAG, "mConvList.addObserver"); + mConferenceAdapter.updateDataset(mConvList.getConversations()); + mConvList.addObserver(mConferenceAdapter); + }*/ + } } + /* private Runnable mUpdateTimeTask = new Runnable() { public void run() { final long start = SystemClock.uptimeMillis(); @@ -142,52 +209,51 @@ public class CallListFragment extends CallableWrapperFragment { mConferenceAdapter.notifyDataSetChanged(); mHandler.postAtTime(this, start + (((minutes * 60) + seconds + 1) * 1000)); } - }; + };*/ - private Handler mHandler = new Handler(); + //private Handler mHandler = new Handler(); + /*9 @Override public void onResume() { super.onResume(); if (mCallbacks.getService() != null) { + if (mConvList != null) + mConvList.startListener(); updateLists(); - if (!mConferenceAdapter.isEmpty()) { - mHandler.postDelayed(mUpdateTimeTask, 0); - } } } - - @SuppressWarnings("unchecked") - // No proper solution with HashMap runtime cast +*/ public void updateLists() { - try { - HashMap<String, Conference> confs = (HashMap<String, Conference>) mCallbacks.getService().getConferenceList(); - String newTitle = getResources().getQuantityString(cx.ring.R.plurals.home_conferences_title, confs.size(), confs.size()); - mConversationsTitleTextView.setText(newTitle); - mConferenceAdapter.updateDataset(new ArrayList<Conference>(confs.values())); - } catch (RemoteException e) { - e.printStackTrace(); - } + if (mCallbacks.getService() != null) + mConferenceAdapter.updateDataset(mCallbacks.getService().getConversations()); } @Override public void onDetach() { + Log.i(TAG, "onDetach"); super.onDetach(); - mCallbacks = sDummyCallbacks; - + mCallbacks = LocalService.DUMMY_CALLBACKS; } @Override public void onCreate(Bundle savedInstanceState) { + Log.i(TAG, "onCreate"); super.onCreate(savedInstanceState); } @Override public void onPause() { super.onPause(); - mHandler.removeCallbacks(mUpdateTimeTask); + //mHandler.removeCallbacks(mUpdateTimeTask); + } + + @Override + public void onResume() { + super.onResume(); + ((HomeActivity)getActivity()).setToolbarState(false, R.string.app_name); } @Override @@ -200,24 +266,51 @@ public class CallListFragment extends CallableWrapperFragment { Log.i(TAG, "onCreateView"); View inflatedView = inflater.inflate(cx.ring.R.layout.frag_call_list, container, false); - mConversationsTitleTextView = (TextView) inflatedView.findViewById(cx.ring.R.id.confs_counter); + newconv_btn = (FloatingActionButton) inflatedView.findViewById(R.id.newconv_fab); + newconv_btn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + startActivity(new Intent().setClass(getActivity(), NewConversationActivity.class)); + } + }); + //mConversationsTitleTextView = (TextView) inflatedView.findViewById(cx.ring.R.id.confs_counter); +/* + if (mConferenceAdapter != null && mConvList != null) + mConvList.deleteObserver(mConferenceAdapter);*/ mConferenceAdapter = new CallListAdapter(getActivity()); - ((ListView) inflatedView.findViewById(cx.ring.R.id.confs_list)).setAdapter(mConferenceAdapter); - ((ListView) inflatedView.findViewById(cx.ring.R.id.confs_list)).setOnItemClickListener(callClickListener); - ((ListView) inflatedView.findViewById(cx.ring.R.id.confs_list)).setOnItemLongClickListener(mItemLongClickListener); + /*if (mConvList != null) { + Log.i(TAG, "mConvList.addObserver"); + mConferenceAdapter.updateDataset(mConvList.getConversations()); + mConvList.addObserver(mConferenceAdapter); + }*/ + /*if (mBound) { + mConferenceAdapter.updateDataset(mService.getConversations()); + }*/ + LocalService service = mCallbacks.getService(); + if (service != null) + mConferenceAdapter.updateDataset(mCallbacks.getService().getConversations()); + + ListView list = (ListView) inflatedView.findViewById(cx.ring.R.id.confs_list); + list.setAdapter(mConferenceAdapter); + list.setOnItemClickListener(callClickListener); + list.setOnItemLongClickListener(mItemLongClickListener); return inflatedView; } - OnItemClickListener callClickListener = new OnItemClickListener() { + private final OnItemClickListener callClickListener = new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> arg0, View v, int arg2, long arg3) { - Intent intent = new Intent().setClass(getActivity(), CallActivity.class); + Intent intent = new Intent() + .setClass(getActivity(), ConversationActivity.class) + .setAction(Intent.ACTION_VIEW) + .setData(Uri.withAppendedPath(ConversationActivity.CONTENT_URI, ((CallListAdapter.ViewHolder) v.getTag()).conv.getContact().getIds().get(0))); intent.putExtra("resuming", true); - intent.putExtra("conference", (Conference) v.getTag()); - startActivityForResult(intent, HomeActivity.REQUEST_CODE_CALL); + //intent.putExtra("contact", ((Conversation) v.getTag()).getContact()); + //intent.putExtra("conversation", (Conversation) v.getTag()); + startActivityForResult(intent, HomeActivity.REQUEST_CODE_CONVERSATION); } }; @@ -229,7 +322,8 @@ public class CallListFragment extends CallableWrapperFragment { vibe.vibrate(80); Intent i = new Intent(); Bundle b = new Bundle(); - b.putParcelable("conference", (Conference) adptv.getAdapter().getItem(pos)); + //b.putParcelable("conference", (Conference) adptv.getAdapter().getItem(pos)); + b.putParcelable("contact", ((Conversation) adptv.getAdapter().getItem(pos)).getContact()); i.putExtra("bconference", b); DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view); @@ -240,20 +334,32 @@ public class CallListFragment extends CallableWrapperFragment { }; - public class CallListAdapter extends BaseAdapter implements Observer { - - private ArrayList<Conference> calls; + public class CallListAdapter extends BaseAdapter /*implements Observer*/ { + final private ArrayList<Conversation> calls = new ArrayList<>(); + final private ExecutorService infos_fetcher = Executors.newCachedThreadPool(); + final private LruCache<Long, Bitmap> mMemoryCache; + final private HashMap<Long, WeakReference<ContactPictureTask>> running_tasks = new HashMap<>(); private Context mContext; public CallListAdapter(Context act) { super(); mContext = act; - calls = new ArrayList<Conference>(); - + final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); + final int cacheSize = maxMemory / 8; + Log.i(TAG, "CallListAdapter created " + cacheSize); + mMemoryCache = new LruCache<Long, Bitmap>(cacheSize){ + @Override + protected int sizeOf(Long key, Bitmap bitmap) { + return bitmap.getByteCount() / 1024; + } + }; } - public void updateDataset(ArrayList<Conference> list) { + public void updateDataset(Collection<Conversation> list) { + Log.i(TAG, "updateDataset " + list.size()); + if (list.size() == 0 && calls.size() == 0) + return; calls.clear(); calls.addAll(list); notifyDataSetChanged(); @@ -265,7 +371,7 @@ public class CallListFragment extends CallableWrapperFragment { } @Override - public Conference getItem(int position) { + public Conversation getItem(int position) { return calls.get(position); } @@ -274,38 +380,82 @@ public class CallListFragment extends CallableWrapperFragment { return 0; } + private class ViewHolder { + TextView conv_title; + TextView conv_status; + ImageView photo; + int position; + Conversation conv; + } + @Override - public View getView(int position, View convertView, ViewGroup parent) { + public View getView(final int position, View convertView, ViewGroup parent) { if (convertView == null) convertView = LayoutInflater.from(mContext).inflate(cx.ring.R.layout.item_calllist, null); - Conference call = calls.get(position); - if (call.getParticipants().size() == 1) { - ((TextView) convertView.findViewById(cx.ring.R.id.call_title)).setText(call.getParticipants().get(0).getmContact().getmDisplayName()); - - long duration = (System.currentTimeMillis() - (call.getParticipants().get(0).getTimestampStart_())) / 1000; - - ((TextView) convertView.findViewById(cx.ring.R.id.call_time)).setText(String.format("%d:%02d:%02d", duration / 3600, (duration % 3600) / 60, - (duration % 60))); + ViewHolder holder = (ViewHolder) convertView.getTag(); + if (holder == null) { + holder = new ViewHolder(); + holder.photo = (ImageView) convertView.findViewById(R.id.photo); + holder.conv_title = (TextView) convertView.findViewById(cx.ring.R.id.msg_txt); + holder.conv_status = (TextView) convertView.findViewById(cx.ring.R.id.call_status); + holder.position = -1; + convertView.setTag(holder); + } + final ViewHolder h = holder; + if (h.position == position && h.conv != null && h.conv == calls.get(position)) { + return convertView; + } + h.conv = calls.get(position); + h.position = position; + h.conv_title.setText(h.conv.getContact().getDisplayName()); + h.conv_status.setText(DateFormat.getDateTimeInstance().format(h.conv.getLastInteraction())); + + final Long cid = h.conv.getContact().getId(); + Bitmap bmp = mMemoryCache.get(cid); + if (bmp != null) { + h.photo.setImageBitmap(bmp); } else { -// String tmp = "Conference with " + call.getParticipants().size() + " participants"; - ((TextView) convertView.findViewById(cx.ring.R.id.call_title)).setText(getString(cx.ring.R.string.home_conf_item, call.getParticipants().size())); + holder.photo.setImageBitmap(mMemoryCache.get(-1l)); + final WeakReference<ViewHolder> wh = new WeakReference<>(holder); + final ContactPictureTask.PictureLoadedCallback cb = new ContactPictureTask.PictureLoadedCallback() { + @Override + public void onPictureLoaded(final Bitmap bmp) { + final ViewHolder fh = wh.get(); + if (fh == null || fh.photo.getParent() == null) + return; + //if (fh.position == position) { + if (fh.conv.getContact().getId() == cid) { + fh.photo.post(new Runnable() { + @Override + public void run() { + fh.photo.setImageBitmap(bmp); + fh.photo.startAnimation(AnimationUtils.loadAnimation(fh.photo.getContext(), R.anim.contact_fadein)); + } + }); + } + } + }; + WeakReference<ContactPictureTask> wtask = running_tasks.get(cid); + ContactPictureTask task = wtask == null ? null : wtask.get(); + if (task != null) { + task.addCallback(cb); + } else { + task = new ContactPictureTask(mContext, h.photo, h.conv.getContact(), new ContactPictureTask.PictureLoadedCallback() { + @Override + public void onPictureLoaded(Bitmap bmp) { + mMemoryCache.put(cid, bmp); + running_tasks.remove(cid); + } + }); + task.addCallback(cb); + running_tasks.put(cid, new WeakReference<>(task)); + infos_fetcher.execute(task); + } } - // ((TextView) convertView.findViewById(R.id.num_participants)).setText("" + call.getParticipants().size()); - ((TextView) convertView.findViewById(cx.ring.R.id.call_status)).setText(call.getState()); - convertView.setOnDragListener(dragListener); - convertView.setTag(call); - return convertView; } - - @Override - public void update(Observable observable, Object data) { - Log.i(TAG, "Updating views..."); - notifyDataSetChanged(); - } - } OnDragListener dragListener = new OnDragListener() { @@ -335,8 +485,8 @@ public class CallListFragment extends CallableWrapperFragment { Intent intent = i.getIntent(); intent.setExtrasClassLoader(Conference.class.getClassLoader()); - Conference initial = (Conference) view.getTag(); - Conference target = (Conference) v.getTag(); + Conversation initial = ((CallListAdapter.ViewHolder) view.getTag()).conv; + Conversation target = ((CallListAdapter.ViewHolder) v.getTag()).conv; if (initial == target) { return true; @@ -344,8 +494,8 @@ public class CallListFragment extends CallableWrapperFragment { DropActionsChoice dialog = DropActionsChoice.newInstance(); Bundle b = new Bundle(); - b.putParcelable("call_initial", initial); - b.putParcelable("call_targeted", target); + b.putParcelable("call_initial", initial.getCurrentCall()); + b.putParcelable("call_targeted", target.getCurrentCall()); dialog.setArguments(b); dialog.setTargetFragment(CallListFragment.this, 0); dialog.show(getFragmentManager(), "dialog"); @@ -376,7 +526,7 @@ public class CallListFragment extends CallableWrapperFragment { Conference c = data.getParcelableExtra("target"); transfer = data.getParcelableExtra("transfer"); try { - mCallbacks.getService().attendedTransfer(transfer.getParticipants().get(0).getCallId(), c.getParticipants().get(0).getCallId()); + mCallbacks.getService().getRemoteService().attendedTransfer(transfer.getParticipants().get(0).getCallId(), c.getParticipants().get(0).getCallId()); mConferenceAdapter.notifyDataSetChanged(); } catch (RemoteException e) { // TODO Auto-generated catch block @@ -389,10 +539,10 @@ public class CallListFragment extends CallableWrapperFragment { String to = data.getStringExtra("to_number"); transfer = data.getParcelableExtra("transfer"); try { - Toast.makeText(getActivity(), getString(cx.ring.R.string.home_transfering, transfer.getParticipants().get(0).getmContact().getmDisplayName(), to), + Toast.makeText(getActivity(), getString(cx.ring.R.string.home_transfering, transfer.getParticipants().get(0).getContact().getDisplayName(), to), Toast.LENGTH_SHORT).show(); - mCallbacks.getService().transfer(transfer.getParticipants().get(0).getCallId(), to); - mCallbacks.getService().hangUp(transfer.getParticipants().get(0).getCallId()); + mCallbacks.getService().getRemoteService().transfer(transfer.getParticipants().get(0).getCallId(), to); + mCallbacks.getService().getRemoteService().hangUp(transfer.getParticipants().get(0).getCallId()); } catch (RemoteException e) { // TODO Auto-generated catch block e.printStackTrace(); @@ -424,20 +574,20 @@ public class CallListFragment extends CallableWrapperFragment { if (call_target.hasMultipleParticipants() && !call_to_add.hasMultipleParticipants()) { - mCallbacks.getService().addParticipant(call_to_add.getParticipants().get(0), call_target.getId()); + mCallbacks.getService().getRemoteService().addParticipant(call_to_add.getParticipants().get(0), call_target.getId()); } else if (call_target.hasMultipleParticipants() && call_to_add.hasMultipleParticipants()) { // We join two conferences - mCallbacks.getService().joinConference(call_to_add.getId(), call_target.getId()); + mCallbacks.getService().getRemoteService().joinConference(call_to_add.getId(), call_target.getId()); } else if (!call_target.hasMultipleParticipants() && call_to_add.hasMultipleParticipants()) { - mCallbacks.getService().addParticipant(call_target.getParticipants().get(0), call_to_add.getId()); + mCallbacks.getService().getRemoteService().addParticipant(call_target.getParticipants().get(0), call_to_add.getId()); } else { // We join two single calls to create a conf - mCallbacks.getService().joinParticipant(call_to_add.getParticipants().get(0).getCallId(), + mCallbacks.getService().getRemoteService().joinParticipant(call_to_add.getParticipants().get(0).getCallId(), call_target.getParticipants().get(0).getCallId()); } diff --git a/ring-android/app/src/main/java/cx/ring/fragments/CallableWrapperFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/CallableWrapperFragment.java index eb0a6328b..3ea8dcab1 100644 --- a/ring-android/app/src/main/java/cx/ring/fragments/CallableWrapperFragment.java +++ b/ring-android/app/src/main/java/cx/ring/fragments/CallableWrapperFragment.java @@ -46,19 +46,11 @@ import java.util.HashMap; public abstract class CallableWrapperFragment extends Fragment implements CallInterface { - - private CallReceiver mReceiver; - + private final CallReceiver mReceiver = new CallReceiver(); @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mReceiver = new CallReceiver(); - } - - @Override - public void onResume() { - super.onResume(); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(CallManagerCallBack.INCOMING_CALL); intentFilter.addAction(CallManagerCallBack.INCOMING_TEXT); @@ -76,10 +68,9 @@ public abstract class CallableWrapperFragment extends Fragment implements CallIn getActivity().registerReceiver(mReceiver, intentFilter); } - @Override - public void onPause() { - super.onPause(); + public void onDestroy() { + super.onDestroy(); getActivity().unregisterReceiver(mReceiver); } diff --git a/ring-android/app/src/main/java/cx/ring/fragments/ConferenceDFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/ConferenceDFragment.java index 7bfbafae8..1e7075128 100644 --- a/ring-android/app/src/main/java/cx/ring/fragments/ConferenceDFragment.java +++ b/ring-android/app/src/main/java/cx/ring/fragments/ConferenceDFragment.java @@ -26,7 +26,7 @@ import android.widget.BaseAdapter; import android.widget.ListView; import android.widget.TextView; -public class ConferenceDFragment extends DialogFragment implements LoaderManager.LoaderCallbacks<Bundle> { +public class ConferenceDFragment extends DialogFragment implements LoaderManager.LoaderCallbacks<ContactsLoader.Result> { SimpleCallListAdapter mAdapter; @@ -75,7 +75,7 @@ public class ConferenceDFragment extends DialogFragment implements LoaderManager - final AlertDialog a = new AlertDialog.Builder(getActivity()).setView(rootView).setTitle("Transfer " + call_selected.getParticipants().get(0).getmContact()) + final AlertDialog a = new AlertDialog.Builder(getActivity()).setView(rootView).setTitle("Transfer " + call_selected.getParticipants().get(0).getContact()) .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { @@ -87,7 +87,7 @@ public class ConferenceDFragment extends DialogFragment implements LoaderManager } @Override - public Loader<Bundle> onCreateLoader(int id, Bundle args) { + public Loader<ContactsLoader.Result> onCreateLoader(int id, Bundle args) { Uri baseUri; if (args != null) { @@ -101,14 +101,14 @@ public class ConferenceDFragment extends DialogFragment implements LoaderManager } @Override - public void onLoadFinished(Loader<Bundle> loader, Bundle data) { + public void onLoadFinished(Loader<ContactsLoader.Result> loader, ContactsLoader.Result data) { // ArrayList<CallContact> tmp = data.getParcelableArrayList("Contacts"); } @Override - public void onLoaderReset(Loader<Bundle> loader) { + public void onLoaderReset(Loader<ContactsLoader.Result> loader) { // Thi is called when the last Cursor provided to onLoadFinished // mListAdapter.swapCursor(null); } @@ -136,7 +136,7 @@ public class ConferenceDFragment extends DialogFragment implements LoaderManager } if(calls.get(position).getParticipants().size() == 1){ - tv.setText(calls.get(position).getParticipants().get(0).getmContact().getmDisplayName()); + tv.setText(calls.get(position).getParticipants().get(0).getContact().getDisplayName()); } else { tv.setText("Conference with "+ calls.get(position).getParticipants().size() + " participants"); } diff --git a/ring-android/app/src/main/java/cx/ring/fragments/ContactListFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/ContactListFragment.java index 9640452f0..d3ff53a21 100644 --- a/ring-android/app/src/main/java/cx/ring/fragments/ContactListFragment.java +++ b/ring-android/app/src/main/java/cx/ring/fragments/ContactListFragment.java @@ -39,9 +39,7 @@ import cx.ring.adapters.StarredContactsAdapter; import cx.ring.loaders.ContactsLoader; import cx.ring.loaders.LoaderConstants; import cx.ring.model.CallContact; -import cx.ring.service.ISipService; -import cx.ring.views.SwipeListViewTouchListener; -import cx.ring.views.stickylistheaders.StickyListHeadersListView; +import se.emilsjolander.stickylistheaders.StickyListHeadersListView; import android.app.Activity; import android.app.Fragment; @@ -51,14 +49,13 @@ import android.net.Uri; import android.os.Bundle; import android.provider.ContactsContract.Contacts; import android.util.Log; -import android.view.DragEvent; import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.DragShadowBuilder; import android.view.View.MeasureSpec; -import android.view.View.OnClickListener; -import android.view.View.OnDragListener; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.widget.AdapterView; @@ -67,66 +64,58 @@ import android.widget.AdapterView.OnItemLongClickListener; import android.widget.GridView; import android.widget.LinearLayout; import android.widget.ListAdapter; -import android.widget.ListView; import android.widget.RelativeLayout; import android.widget.SearchView; import android.widget.SearchView.OnQueryTextListener; +import android.widget.TextView; -public class ContactListFragment extends Fragment implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Bundle> { - private static final String TAG = "ContactListFragment"; +public class ContactListFragment extends Fragment implements OnQueryTextListener, LoaderManager.LoaderCallbacks<ContactsLoader.Result> { + public static final String TAG = "ContactListFragment"; ContactsAdapter mListAdapter; StarredContactsAdapter mGridAdapter; - SearchView mQuickReturnSearchView; + //SearchView mQuickReturnSearchView; String mCurFilter; StickyListHeadersListView mContactList; + + // favorite contacts + private LinearLayout llMain; private GridView mStarredGrid; - private SwipeListViewTouchListener mSwipeLvTouchListener; + private TextView favHeadLabel; + //private SwipeListViewTouchListener mSwipeLvTouchListener; private LinearLayout mHeader; + private ViewGroup newcontact; @Override public void onCreate(Bundle savedInBundle) { super.onCreate(savedInBundle); mGridAdapter = new StarredContactsAdapter(getActivity()); mListAdapter = new ContactsAdapter(this); + setHasOptionsMenu(true); } public Callbacks mCallbacks = sDummyCallbacks; - private LinearLayout llMain; /** * A dummy implementation of the {@link Callbacks} interface that does nothing. Used only when this fragment is not attached to an activity. */ - private static Callbacks sDummyCallbacks = new Callbacks() { + private static final Callbacks sDummyCallbacks = new Callbacks() { @Override public void onCallContact(CallContact c) { } - @Override public void onTextContact(CallContact c) { } - @Override public void onEditContact(CallContact c) { } - - @Override - public ISipService getService() { - Log.i(TAG, "Dummy"); - return null; - } - @Override public void onContactDragged() { } - @Override public void toggleDrawer() { } - @Override public void setDragView(RelativeLayout relativeLayout) { - } - @Override public void toggleForSearchDrawer() { } @@ -134,21 +123,12 @@ public class ContactListFragment extends Fragment implements OnQueryTextListener public interface Callbacks { void onCallContact(CallContact c); - void onTextContact(CallContact c); - - public ISipService getService(); - void onContactDragged(); - void toggleDrawer(); - void onEditContact(CallContact item); - void setDragView(RelativeLayout relativeLayout); - void toggleForSearchDrawer(); - } @Override @@ -157,9 +137,7 @@ public class ContactListFragment extends Fragment implements OnQueryTextListener if (!(activity instanceof Callbacks)) { throw new IllegalStateException("Activity must implement fragment's callbacks."); } - mCallbacks = (Callbacks) activity; - } @Override @@ -168,11 +146,20 @@ public class ContactListFragment extends Fragment implements OnQueryTextListener mCallbacks = sDummyCallbacks; } + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.newconv_option_menu, menu); + SearchView searchView = (SearchView) menu.findItem(R.id.contact_search).getActionView(); + searchView.setOnQueryTextListener(ContactListFragment.this); + } + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View inflatedView = inflater.inflate(R.layout.frag_contact_list, container, false); mHeader = (LinearLayout) inflater.inflate(R.layout.frag_contact_list_header, null); mContactList = (StickyListHeadersListView) inflatedView.findViewById(R.id.contacts_stickylv); + //mContactList.setDividerHeight(0); + mContactList.setDivider(null); inflatedView.findViewById(R.id.drag_view).setOnTouchListener(new OnTouchListener() { @@ -182,7 +169,7 @@ public class ContactListFragment extends Fragment implements OnQueryTextListener } }); - inflatedView.findViewById(R.id.contact_search_button).setOnClickListener(new OnClickListener() { + /*inflatedView.findViewById(R.id.contact_search_button).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { @@ -203,10 +190,23 @@ public class ContactListFragment extends Fragment implements OnQueryTextListener }); mCallbacks.setDragView(((RelativeLayout) inflatedView.findViewById(R.id.slider_button))); - - mQuickReturnSearchView = (SearchView) mHeader.findViewById(R.id.contact_search); +*/ + //mQuickReturnSearchView = (SearchView) mHeader.findViewById(R.id.contact_search); mStarredGrid = (GridView) mHeader.findViewById(R.id.favorites_grid); llMain = (LinearLayout) mHeader.findViewById(R.id.llMain); + favHeadLabel = (TextView) mHeader.findViewById(R.id.fav_head_label); + newcontact = (ViewGroup) mHeader.findViewById(R.id.newcontact_element); + newcontact.setVisibility(View.GONE); + newcontact.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + CallContact c = (CallContact) v.getTag(); + if (c == null) + return; + mCallbacks.onCallContact(c); + } + }); + return inflatedView; } @@ -218,7 +218,7 @@ public class ContactListFragment extends Fragment implements OnQueryTextListener mContactList.setAdapter(mListAdapter); mStarredGrid.setAdapter(mGridAdapter); - mQuickReturnSearchView.setIconifiedByDefault(false); + /*mQuickReturnSearchView.setIconifiedByDefault(false); mQuickReturnSearchView.setOnClickListener(new OnClickListener() { @@ -228,7 +228,7 @@ public class ContactListFragment extends Fragment implements OnQueryTextListener mQuickReturnSearchView.setFocusable(true); } }); - mQuickReturnSearchView.setOnQueryTextListener(ContactListFragment.this); + mQuickReturnSearchView.setOnQueryTextListener(ContactListFragment.this);*/ getLoaderManager().initLoader(LoaderConstants.CONTACT_LOADER, null, this); @@ -246,7 +246,7 @@ public class ContactListFragment extends Fragment implements OnQueryTextListener }; private void setGridViewListeners() { - mStarredGrid.setOnDragListener(dragListener); + //mStarredGrid.setOnDragListener(dragListener); mStarredGrid.setOnItemClickListener(new OnItemClickListener() { @Override @@ -258,7 +258,7 @@ public class ContactListFragment extends Fragment implements OnQueryTextListener } private void setListViewListeners() { - mSwipeLvTouchListener = new SwipeListViewTouchListener(mContactList.getWrappedList(), new SwipeListViewTouchListener.OnSwipeCallback() { + /*mSwipeLvTouchListener = new SwipeListViewTouchListener(mContactList.getWrappedList(), new SwipeListViewTouchListener.OnSwipeCallback() { @Override public void onSwipeLeft(ListView listView, int[] reverseSortedPositions) { } @@ -270,21 +270,21 @@ public class ContactListFragment extends Fragment implements OnQueryTextListener down.findViewById(R.id.quick_starred).setClickable(true); } - }, true, false); + }, true, false);*/ - mContactList.getWrappedList().setOnDragListener(dragListener); - mContactList.getWrappedList().setOnTouchListener(mSwipeLvTouchListener); + /*mContactList.getWrappedList().setOnDragListener(dragListener); + mContactList.getWrappedList().setOnTouchListener(mSwipeLvTouchListener);*/ mContactList.getWrappedList().setOnItemLongClickListener(mItemLongClickListener); - mContactList.getWrappedList().setOnItemClickListener(new OnItemClickListener() { + /*mContactList.getWrappedList().setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> arg0, View view, int pos, long id) { Log.i(TAG, "Opening Item"); mSwipeLvTouchListener.openItem(view, pos, id); } - }); + });*/ } - +/* OnDragListener dragListener = new OnDragListener() { @Override @@ -309,29 +309,36 @@ public class ContactListFragment extends Fragment implements OnQueryTextListener return true; } - }; + };*/ @Override public boolean onQueryTextChange(String newText) { + mCurFilter = newText; if (newText.isEmpty()) { getLoaderManager().restartLoader(LoaderConstants.CONTACT_LOADER, null, this); + newcontact.setVisibility(View.GONE); return true; } - mCurFilter = newText; Bundle b = new Bundle(); b.putString("filter", mCurFilter); getLoaderManager().restartLoader(LoaderConstants.CONTACT_LOADER, b, this); + newcontact.setVisibility(View.VISIBLE); + ((TextView)newcontact.findViewById(R.id.display_name)).setText("Call \"" + newText + "\""); + CallContact contact = CallContact.ContactBuilder.buildUnknownContact(newText); + newcontact.setTag(contact); return true; } @Override public boolean onQueryTextSubmit(String query) { // Return false to let the SearchView perform the default action - return false; + //return false; + + return true; } @Override - public Loader<Bundle> onCreateLoader(int id, Bundle args) { + public Loader<ContactsLoader.Result> onCreateLoader(int id, Bundle args) { Uri baseUri; Log.i(TAG, "createLoader"); @@ -347,26 +354,27 @@ public class ContactListFragment extends Fragment implements OnQueryTextListener } @Override - public void onLoadFinished(Loader<Bundle> loader, Bundle data) { - - mGridAdapter.removeAll(); - mListAdapter.clear(); - ArrayList<CallContact> tmp = data.getParcelableArrayList("Contacts"); - ArrayList<CallContact> tmp2 = data.getParcelableArrayList("Starred"); - mListAdapter.addAll(tmp); - mGridAdapter.addAll(tmp2); - + public void onLoadFinished(Loader<ContactsLoader.Result> loader, ContactsLoader.Result data) { + mListAdapter.setData(data.contacts); setListViewListeners(); - setGridViewListeners(); - - mStarredGrid.post(new Runnable() { - - @Override - public void run() { - setGridViewHeight(mStarredGrid, llMain); - } - }); + if (data.starred.isEmpty()) { + llMain.setVisibility(View.GONE); + favHeadLabel.setVisibility(View.GONE); + mGridAdapter.removeAll(); + } else { + llMain.setVisibility(View.VISIBLE); + favHeadLabel.setVisibility(View.VISIBLE); + mGridAdapter.setData(data.starred); + setGridViewListeners(); + mStarredGrid.post(new Runnable() { + + @Override + public void run() { + setGridViewHeight(mStarredGrid, llMain); + } + }); + } } // Sets the GridView holder's height to fully expand it @@ -399,6 +407,6 @@ public class ContactListFragment extends Fragment implements OnQueryTextListener } @Override - public void onLoaderReset(Loader<Bundle> loader) { + public void onLoaderReset(Loader<ContactsLoader.Result> loader) { } } diff --git a/ring-android/app/src/main/java/cx/ring/fragments/DetailsHistoryEntryFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/DetailsHistoryEntryFragment.java index 608115bbe..5af66ebef 100644 --- a/ring-android/app/src/main/java/cx/ring/fragments/DetailsHistoryEntryFragment.java +++ b/ring-android/app/src/main/java/cx/ring/fragments/DetailsHistoryEntryFragment.java @@ -51,7 +51,6 @@ import cx.ring.model.SipCall; import cx.ring.service.ISipService; import java.util.ArrayList; -import java.util.HashMap; import java.util.Map; import java.util.NavigableMap; import java.util.Random; @@ -126,7 +125,7 @@ public class DetailsHistoryEntryFragment extends Fragment { lvMain.setAdapter(mAdapter); iv = (RelativeLayout) inflatedView.findViewById(R.id.iv); - ((TextView) iv.findViewById(R.id.history_call_name)).setText(toDisplay.getContact().getmDisplayName()); + ((TextView) iv.findViewById(R.id.history_call_name)).setText(toDisplay.getContact().getDisplayName()); tasker = new ContactPictureTask(getActivity(), (ImageView) inflatedView.findViewById(R.id.contact_photo), toDisplay.getContact()); tasker.run(); @@ -142,8 +141,8 @@ public class DetailsHistoryEntryFragment extends Fragment { Bundle args = new Bundle(); args.putString(SipCall.ID, Integer.toString(Math.abs(new Random().nextInt()))); args.putParcelable(SipCall.ACCOUNT, new Account(toDisplay.getAccountID(), details, creds, state)); - args.putInt(SipCall.STATE, SipCall.state.CALL_STATE_RINGING); - args.putInt(SipCall.TYPE, SipCall.direction.CALL_TYPE_OUTGOING); + args.putInt(SipCall.STATE, SipCall.State.RINGING); + args.putInt(SipCall.TYPE, SipCall.Direction.OUTGOING); args.putParcelable(SipCall.CONTACT, toDisplay.getContact()); mCallbacks.onCall(new SipCall(args)); diff --git a/ring-android/app/src/main/java/cx/ring/fragments/DialingFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/DialingFragment.java index 71425d431..7d434fc29 100644 --- a/ring-android/app/src/main/java/cx/ring/fragments/DialingFragment.java +++ b/ring-android/app/src/main/java/cx/ring/fragments/DialingFragment.java @@ -36,6 +36,7 @@ import java.util.Locale; import android.app.Fragment; import cx.ring.R; import cx.ring.service.ISipService; +import cx.ring.service.LocalService; import cx.ring.views.ClearableEditText; import android.app.Activity; @@ -64,31 +65,22 @@ public class DialingFragment extends Fragment implements OnTouchListener { ClearableEditText textField; private Callbacks mCallbacks = sDummyCallbacks; - /** - * A dummy implementation of the {@link Callbacks} interface that does nothing. Used only when this fragment is not attached to an activity. - */ - private static Callbacks sDummyCallbacks = new Callbacks() { - @Override - public void onCallDialed(String to) { - } - - @Override - public ISipService getService() { - // TODO Auto-generated method stub - return null; - } - }; - /** * The Activity calling this fragment has to implement this interface - * + * */ - public interface Callbacks { + public interface Callbacks extends LocalService.Callbacks { void onCallDialed(String account); + } - public ISipService getService(); - + /** + * A dummy implementation of the {@link Callbacks} interface that does nothing. Used only when this fragment is not attached to an activity. + */ + private static class DummyCallbacks extends LocalService.DummyCallbacks implements Callbacks { + @Override + public void onCallDialed(String to) {} } + private static final Callbacks sDummyCallbacks = new DummyCallbacks(); @Override public void setUserVisibleHint(boolean isVisibleToUser) { @@ -211,7 +203,7 @@ public class DialingFragment extends Fragment implements OnTouchListener { try { String toSend = Character.toString(s.charAt(start)); toSend.toUpperCase(Locale.getDefault()); - mCallbacks.getService().playDtmf(toSend); + mCallbacks.getRemoteService().playDtmf(toSend); } catch (RemoteException e) { e.printStackTrace(); } diff --git a/ring-android/app/src/main/java/cx/ring/fragments/GeneralAccountFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/GeneralAccountFragment.java index 6e9a5f23b..7e1774c72 100644 --- a/ring-android/app/src/main/java/cx/ring/fragments/GeneralAccountFragment.java +++ b/ring-android/app/src/main/java/cx/ring/fragments/GeneralAccountFragment.java @@ -60,7 +60,7 @@ public class GeneralAccountFragment extends PreferenceFragment { public interface Callbacks { - public Account getAccount(); + Account getAccount(); } @@ -133,30 +133,31 @@ public class GeneralAccountFragment extends PreferenceFragment { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { - Log.i(TAG, "Changing preference value:" + newValue); + Log.i(TAG, "Changing preference " + preference.getKey() + " to value:" + newValue); + final Account acc = mCallbacks.getAccount(); if (preference instanceof CheckBoxPreference) { - mCallbacks.getAccount().getBasicDetails().setDetailString(preference.getKey(), newValue.toString()); + acc.getBasicDetails().setDetailString(preference.getKey(), newValue.toString()); } else { if (preference instanceof PasswordPreference) { String tmp = ""; for (int i = 0; i < ((String) newValue).length(); ++i) { tmp += "*"; } - if(mCallbacks.getAccount().isSip()) - mCallbacks.getAccount().getCredentials().get(0).setDetailString(preference.getKey(), newValue.toString()); + if(acc.isSip()) + acc.getCredentials().get(0).setDetailString(preference.getKey(), newValue.toString()); preference.setSummary(tmp); } else if(preference.getKey().contentEquals(AccountDetailBasic.CONFIG_ACCOUNT_USERNAME)) { - if(mCallbacks.getAccount().isSip()){ - mCallbacks.getAccount().getCredentials().get(0).setDetailString(preference.getKey(), newValue.toString()); + if(acc.isSip()){ + acc.getCredentials().get(0).setDetailString(preference.getKey(), newValue.toString()); } preference.setSummary((CharSequence) newValue); } else { preference.setSummary((CharSequence) newValue); } - - mCallbacks.getAccount().getBasicDetails().setDetailString(preference.getKey(), newValue.toString()); + + acc.getBasicDetails().setDetailString(preference.getKey(), newValue.toString()); } - mCallbacks.getAccount().notifyObservers(); + acc.notifyObservers(); return true; } }; diff --git a/ring-android/app/src/main/java/cx/ring/fragments/HistoryFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/HistoryFragment.java index c7221b3c7..6b050550c 100644 --- a/ring-android/app/src/main/java/cx/ring/fragments/HistoryFragment.java +++ b/ring-android/app/src/main/java/cx/ring/fragments/HistoryFragment.java @@ -46,7 +46,6 @@ import cx.ring.history.HistoryManager; import cx.ring.loaders.HistoryLoader; import cx.ring.loaders.LoaderConstants; import cx.ring.history.HistoryEntry; -import cx.ring.service.ISipService; import android.app.Activity; import android.content.Context; @@ -71,27 +70,16 @@ public class HistoryFragment extends ListFragment implements LoaderManager.Loade private Callbacks mCallbacks = sDummyCallbacks; HistoryManager mHistoryManager; - private static Callbacks sDummyCallbacks = new Callbacks() { - @Override - public void onCallHistory(HistoryEntry to) { - } - + public interface Callbacks { + void onCallHistory(HistoryEntry to); + } + private static final Callbacks sDummyCallbacks = new Callbacks() { @Override - public ISipService getService() { - Log.i(TAG, "Dummy"); - return null; - } - + public void onCallHistory(HistoryEntry to) {} }; public static String ARGS = "Bundle.args"; - public interface Callbacks { - public void onCallHistory(HistoryEntry to); - - public ISipService getService(); - - } @Override public void onAttach(Activity activity) { @@ -216,11 +204,11 @@ public class HistoryFragment extends ListFragment implements LoaderManager.Loade // to the view objects // SipCall call = (SipCall) mCallList.values().toArray()[position]; - entryView.displayName.setText(dataset.get(pos).getContact().getmDisplayName()); + entryView.displayName.setText(dataset.get(pos).getContact().getDisplayName()); infos_fetcher.execute(new ContactPictureTask(mContext, entryView.photo, dataset.get(pos).getContact())); - entryView.incoming.setText(getString(R.string.hist_in_calls, dataset.get(pos).getIncoming_sum())); - entryView.outgoing.setText(getString(R.string.hist_out_calls, dataset.get(pos).getOutgoing_sum())); + entryView.incoming.setText(getString(R.string.hist_in_calls, dataset.get(pos).getIncomingSum())); + entryView.outgoing.setText(getString(R.string.hist_out_calls, dataset.get(pos).getOutgoingSum())); /*if (dataset.get(pos).getCalls().lastEntry().getValue().getRecordPath().length() > 0) { entryView.replay.setVisibility(View.VISIBLE); diff --git a/ring-android/app/src/main/java/cx/ring/fragments/IMFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/IMFragment.java deleted file mode 100644 index 0769d6348..000000000 --- a/ring-android/app/src/main/java/cx/ring/fragments/IMFragment.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright (C) 2004-2014 Savoir-Faire Linux Inc. - * - * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 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., 675 Mass Ave, Cambridge, MA 02139, USA. - * - * Additional permission under GNU GPL version 3 section 7: - * - * If you modify this program, or any covered work, by linking or - * combining it with the OpenSSL project's OpenSSL library (or a - * modified version of that library), containing parts covered by the - * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. - * grants you additional permission to convey the resulting work. - * Corresponding Source for a non-source form of such a combination - * shall include the source code for the parts of OpenSSL used as well - * as that of the covered work. - */ -package cx.ring.fragments; - -import android.widget.*; -import cx.ring.R; -import cx.ring.adapters.DiscussArrayAdapter; -import cx.ring.model.Conference; -import cx.ring.model.SipMessage; -import cx.ring.service.ISipService; - -import android.app.Activity; -import android.content.Intent; -import android.os.Bundle; -import android.util.Log; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.view.inputmethod.EditorInfo; -import android.widget.TextView.OnEditorActionListener; - -public class IMFragment extends CallableWrapperFragment { - static final String TAG = IMFragment.class.getSimpleName(); - - private Callbacks mCallbacks = sDummyCallbacks; - - DiscussArrayAdapter mAdapter; - ListView list; - - private EditText sendTextField; - - @Override - public void onCreate(Bundle savedBundle) { - super.onCreate(savedBundle); - - mAdapter = new DiscussArrayAdapter(getActivity(), getArguments()); - - } - - @Override - public void incomingText(Conference updated, String ID, String from, String msg) { - mCallbacks.updateDisplayedConference(updated); - if(updated.equals(mCallbacks.getDisplayedConference())){ - SipMessage sipMsg = new SipMessage(true, msg); - putMessage(sipMsg); - } - - } - - - /** - * A dummy implementation of the {@link Callbacks} interface that does nothing. Used only when this fragment is not attached to an activity. - */ - private static Callbacks sDummyCallbacks = new Callbacks() { - - @Override - public ISipService getService() { - return null; - } - - @Override - public Conference getDisplayedConference() { - return null; - } - - @Override - public boolean sendIM(SipMessage msg) { - return false; - } - - @Override - public void updateDisplayedConference(Conference c) { - - } - - }; - - /** - * The Activity calling this fragment has to implement this interface - */ - public interface Callbacks { - public ISipService getService(); - - public Conference getDisplayedConference(); - - public boolean sendIM(SipMessage msg); - - public void updateDisplayedConference(Conference c); - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - - if (!(activity instanceof Callbacks)) { - throw new IllegalStateException("Activity must implement fragment's callbacks."); - } - - mCallbacks = (Callbacks) activity; - } - - @Override - public void onDetach() { - super.onDetach(); - mCallbacks = sDummyCallbacks; - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.frag_imessaging, container, false); - - list = (ListView) rootView.findViewById(R.id.message_list); - list.setAdapter(mAdapter); - - sendTextField = (EditText) rootView.findViewById(R.id.send_im_edittext); - - sendTextField.setOnEditorActionListener(new OnEditorActionListener() { - - @Override - public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - - if (actionId == EditorInfo.IME_ACTION_SEND) { - sendMessage(); - } - return true; - } - }); - - rootView.findViewById(R.id.send_im_button).setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - sendMessage(); - } - }); - - - return rootView; - } - - private void sendMessage() { - if (sendTextField.getText().toString().length() > 0) { - SipMessage toSend = new SipMessage(false, sendTextField.getText().toString()); - if (mCallbacks.sendIM(toSend)) { - putMessage(toSend); - sendTextField.setText(""); - } else { - Toast.makeText(getActivity(), "Error sending message", Toast.LENGTH_SHORT).show(); - } - } - } - - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - } - - public void putMessage(SipMessage msg) { - mAdapter.add(msg); - Log.i(TAG, "Messages" + mAdapter.getCount()); - } -} diff --git a/ring-android/app/src/main/java/cx/ring/fragments/MenuFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/MenuFragment.java index 42bc78648..ad0ed2376 100644 --- a/ring-android/app/src/main/java/cx/ring/fragments/MenuFragment.java +++ b/ring-android/app/src/main/java/cx/ring/fragments/MenuFragment.java @@ -31,9 +31,13 @@ package cx.ring.fragments; import android.app.Activity; +import android.app.Fragment; import android.app.LoaderManager; import android.content.AsyncTaskLoader; +import android.content.BroadcastReceiver; +import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.Loader; import android.os.Bundle; import android.os.RemoteException; @@ -46,28 +50,25 @@ import android.widget.AdapterView.OnItemSelectedListener; import cx.ring.R; import cx.ring.adapters.AccountSelectionAdapter; import cx.ring.adapters.ContactPictureTask; +import cx.ring.client.HomeActivity; import cx.ring.loaders.AccountsLoader; import cx.ring.loaders.LoaderConstants; import cx.ring.model.account.Account; import cx.ring.model.CallContact; +import cx.ring.service.ConfigurationManagerCallback; import cx.ring.service.ISipService; +import cx.ring.service.LocalService; import java.util.ArrayList; -public class MenuFragment extends AccountWrapperFragment implements LoaderManager.LoaderCallbacks<Bundle> { +public class MenuFragment extends Fragment /*extends AccountWrapperFragment implements LoaderManager.LoaderCallbacks<Bundle>*/ { @SuppressWarnings("unused") private static final String TAG = MenuFragment.class.getSimpleName(); AccountSelectionAdapter mAccountAdapter; private Spinner spinnerAccounts; - private Callbacks mCallbacks = sDummyCallbacks; - private static Callbacks sDummyCallbacks = new Callbacks() { - @Override - public ISipService getService() { - return null; - } - }; + private LocalService.Callbacks mCallbacks = LocalService.DUMMY_CALLBACKS; public Account retrieveAccountById(String accountID) { Account toReturn; @@ -79,23 +80,20 @@ public class MenuFragment extends AccountWrapperFragment implements LoaderManage return toReturn; } - public interface Callbacks { - ISipService getService(); - } - @Override public void onAttach(Activity activity) { super.onAttach(activity); - if (!(activity instanceof Callbacks)) { + if (!(activity instanceof LocalService.Callbacks)) { throw new IllegalStateException("Activity must implement fragment's callbacks."); } - mCallbacks = (Callbacks) activity; + mCallbacks = (LocalService.Callbacks) activity; + updateAllAccounts(); } @Override public void onDetach() { super.onDetach(); - mCallbacks = sDummyCallbacks; + mCallbacks = LocalService.DUMMY_CALLBACKS; } @Override @@ -103,25 +101,34 @@ public class MenuFragment extends AccountWrapperFragment implements LoaderManage super.onCreate(savedInstanceState); } - public void onResume() { - super.onResume(); - - Log.i(TAG, "Resuming"); - getLoaderManager().restartLoader(LoaderConstants.ACCOUNTS_LOADER, null, this); - - } - @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); - getLoaderManager().restartLoader(LoaderConstants.ACCOUNTS_LOADER, null, this); + //getLoaderManager().restartLoader(LoaderConstants.ACCOUNTS_LOADER, null, this); } + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().contentEquals(LocalService.ACTION_ACCOUNT_UPDATE)) { + updateAllAccounts(); + } + } + }; + @Override + public void onResume() { + Log.i(TAG, "Resuming"); + super.onResume(); + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(LocalService.ACTION_ACCOUNT_UPDATE); + getActivity().registerReceiver(mReceiver, intentFilter); + } @Override public void onPause() { super.onPause(); + getActivity().unregisterReceiver(mReceiver); } @Override @@ -139,7 +146,7 @@ public class MenuFragment extends AccountWrapperFragment implements LoaderManage mAccountAdapter.setSelectedAccount(pos); //view.findViewById(R.id.account_selected).setVisibility(View.GONE); try { - mCallbacks.getService().setAccountOrder(mAccountAdapter.getAccountOrder()); + mCallbacks.getRemoteService().setAccountOrder(mAccountAdapter.getAccountOrder()); } catch (RemoteException e) { e.printStackTrace(); } @@ -155,7 +162,9 @@ public class MenuFragment extends AccountWrapperFragment implements LoaderManage CallContact user = CallContact.ContactBuilder.buildUserContact(getActivity().getContentResolver()); new ContactPictureTask(getActivity(), (ImageView) inflatedView.findViewById(R.id.user_photo), user).run(); - ((TextView) inflatedView.findViewById(R.id.user_name)).setText(user.getmDisplayName()); + ((TextView) inflatedView.findViewById(R.id.user_name)).setText(user.getDisplayName()); + + updateAllAccounts(); return inflatedView; } @@ -172,10 +181,15 @@ public class MenuFragment extends AccountWrapperFragment implements LoaderManage } public void updateAllAccounts() { - if (getActivity() != null) - getLoaderManager().restartLoader(LoaderConstants.ACCOUNTS_LOADER, null, this); + /*if (getActivity() != null) + getLoaderManager().restartLoader(LoaderConstants.ACCOUNTS_LOADER, null, this);*/ + if (mAccountAdapter != null && mCallbacks.getService() != null) { + mAccountAdapter.removeAll(); + mAccountAdapter.addAll(mCallbacks.getService().getAccounts()); + } } + /* @Override public void accountsChanged() { updateAllAccounts(); @@ -183,16 +197,16 @@ public class MenuFragment extends AccountWrapperFragment implements LoaderManage } @Override - public void accountStateChanged(String accoundID, String state, int code) { - Log.w(TAG, "accountStateChanged " + accoundID + " " + state); + public void accountStateChanged(String accoundID, String State, int code) { + Log.w(TAG, "accountStateChanged " + accoundID + " " + State); if (mAccountAdapter != null) - mAccountAdapter.updateAccount(accoundID, state, code); + mAccountAdapter.updateAccount(accoundID, State, code); } @Override public AsyncTaskLoader<Bundle> onCreateLoader(int arg0, Bundle arg1) { - AccountsLoader l = new AccountsLoader(getActivity(), mCallbacks.getService()); + AccountsLoader l = new AccountsLoader(getActivity(), mCallbacks.getRemoteService()); l.forceLoad(); return l; } @@ -208,6 +222,6 @@ public class MenuFragment extends AccountWrapperFragment implements LoaderManage @Override public void onLoaderReset(Loader<Bundle> loader) { - } + }*/ } diff --git a/ring-android/app/src/main/java/cx/ring/fragments/NestedSettingsFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/NestedSettingsFragment.java index 87e358183..6d192f12b 100644 --- a/ring-android/app/src/main/java/cx/ring/fragments/NestedSettingsFragment.java +++ b/ring-android/app/src/main/java/cx/ring/fragments/NestedSettingsFragment.java @@ -47,6 +47,7 @@ import cx.ring.model.account.SRTPManager; import cx.ring.model.account.TLSManager; import cx.ring.model.account.Account; import cx.ring.service.ISipService; +import cx.ring.service.LocalService; import java.util.ArrayList; @@ -62,23 +63,24 @@ public class NestedSettingsFragment extends PreferenceFragment { TLSManager mTlsManager; private static Callbacks sDummyCallbacks = new Callbacks() { - @Override - public Account getAccount() { + public ISipService getRemoteService() { return null; } - @Override - public ISipService getService() { + public LocalService getService() { + return null; + } + @Override + public Account getAccount() { return null; } - }; public String[] getTlsMethods() { ArrayList<String> methods = null; try { - methods = (ArrayList<String>) mCallbacks.getService().getTlsSupportedMethods(); + methods = (ArrayList<String>) mCallbacks.getRemoteService().getTlsSupportedMethods(); } catch (RemoteException e) { e.printStackTrace(); } @@ -105,12 +107,8 @@ public class NestedSettingsFragment extends PreferenceFragment { return false; } - public interface Callbacks { - - public Account getAccount(); - - public ISipService getService(); - + public interface Callbacks extends LocalService.Callbacks { + Account getAccount(); } @Override diff --git a/ring-android/app/src/main/java/cx/ring/fragments/SecurityAccountFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/SecurityAccountFragment.java index 01042e6ef..f03999540 100644 --- a/ring-android/app/src/main/java/cx/ring/fragments/SecurityAccountFragment.java +++ b/ring-android/app/src/main/java/cx/ring/fragments/SecurityAccountFragment.java @@ -50,8 +50,7 @@ public class SecurityAccountFragment extends PreferenceFragment { private static final String TAG = SecurityAccountFragment.class.getSimpleName(); private Callbacks mCallbacks = sDummyCallbacks; - private static Callbacks sDummyCallbacks = new Callbacks() { - + private static final Callbacks sDummyCallbacks = new Callbacks() { @Override public Account getAccount() { return null; @@ -68,19 +67,13 @@ public class SecurityAccountFragment extends PreferenceFragment { @Override public void displayTLSScreen() { } - }; public interface Callbacks { - - public Account getAccount(); - - public void displayCredentialsScreen(); - - public void displaySRTPScreen(); - - public void displayTLSScreen(); - + Account getAccount(); + void displayCredentialsScreen(); + void displaySRTPScreen(); + void displayTLSScreen(); } @Override diff --git a/ring-android/app/src/main/java/cx/ring/fragments/TransferDFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/TransferDFragment.java index cb331defb..a2d8a57a4 100644 --- a/ring-android/app/src/main/java/cx/ring/fragments/TransferDFragment.java +++ b/ring-android/app/src/main/java/cx/ring/fragments/TransferDFragment.java @@ -70,7 +70,7 @@ import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; -public class TransferDFragment extends DialogFragment implements LoaderManager.LoaderCallbacks<Bundle> { +public class TransferDFragment extends DialogFragment implements LoaderManager.LoaderCallbacks<ContactsLoader.Result> { public static final int RESULT_TRANSFER_CONF = Activity.RESULT_FIRST_USER + 1; public static final int RESULT_TRANSFER_NUMBER = Activity.RESULT_FIRST_USER + 2; @@ -124,7 +124,7 @@ public class TransferDFragment extends DialogFragment implements LoaderManager.L mEditText.setAdapter(autoCompleteAdapter); final AlertDialog a = new AlertDialog.Builder(getActivity()).setView(rootView) - .setTitle("Transfer " + call_selected.getmContact().getmDisplayName()) + .setTitle("Transfer " + call_selected.getContact().getDisplayName()) .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int whichButton) { @@ -165,7 +165,7 @@ public class TransferDFragment extends DialogFragment implements LoaderManager.L } @Override - public Loader<Bundle> onCreateLoader(int id, Bundle args) { + public Loader<ContactsLoader.Result> onCreateLoader(int id, Bundle args) { Uri baseUri; if (args != null) { @@ -179,14 +179,14 @@ public class TransferDFragment extends DialogFragment implements LoaderManager.L } @Override - public void onLoadFinished(Loader<Bundle> loader, Bundle data) { + public void onLoadFinished(Loader<ContactsLoader.Result> loader, ContactsLoader.Result data) { // ArrayList<CallContact> tmp = data.getParcelableArrayList("Contacts"); } @Override - public void onLoaderReset(Loader<Bundle> loader) { + public void onLoaderReset(Loader<ContactsLoader.Result> loader) { // Thi is called when the last Cursor provided to onLoadFinished // mListAdapter.swapCursor(null); } @@ -281,7 +281,7 @@ public class TransferDFragment extends DialogFragment implements LoaderManager.L tv = (TextView) mInflater.inflate(android.R.layout.simple_dropdown_item_1line, parent, false); } - tv.setText(calls.get(position).getParticipants().get(0).getmContact().getmDisplayName()); + tv.setText(calls.get(position).getParticipants().get(0).getContact().getDisplayName()); return tv; } diff --git a/ring-android/app/src/main/java/cx/ring/history/DatabaseHelper.java b/ring-android/app/src/main/java/cx/ring/history/DatabaseHelper.java index a623e989d..43c116f95 100644 --- a/ring-android/app/src/main/java/cx/ring/history/DatabaseHelper.java +++ b/ring-android/app/src/main/java/cx/ring/history/DatabaseHelper.java @@ -48,13 +48,12 @@ import java.sql.SQLException; */ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { - // name of the database file for your application -- change to something appropriate for your app private static final String DATABASE_NAME = "history.db"; // any time you make changes to your database objects, you may have to increase the database version - private static final int DATABASE_VERSION = 2; + private static final int DATABASE_VERSION = 4; - // the DAO object we use to access the SimpleData table private Dao<HistoryCall, Integer> historyDao = null; + private Dao<HistoryText, Integer> historyTextDao = null; public DatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); @@ -67,8 +66,10 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { @Override public void onCreate(SQLiteDatabase db, ConnectionSource connectionSource) { try { + //TableUtils.dropTable(connectionSource, HistoryCall.class, true); Log.i(DatabaseHelper.class.getName(), "onCreate"); TableUtils.createTable(connectionSource, HistoryCall.class); + TableUtils.createTable(connectionSource, HistoryText.class); } catch (SQLException e) { Log.e(DatabaseHelper.class.getName(), "Can't create database", e); throw new RuntimeException(e); @@ -84,6 +85,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { try { Log.i(DatabaseHelper.class.getName(), "onUpgrade"); TableUtils.dropTable(connectionSource, HistoryCall.class, true); + TableUtils.dropTable(connectionSource, HistoryText.class, true); // after we drop the old databases, we create the new ones onCreate(db, connectionSource); } catch (SQLException e) { @@ -102,6 +104,12 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { } return historyDao; } + public Dao<HistoryText, Integer> getTextHistoryDao() throws SQLException { + if (historyTextDao == null) { + historyTextDao = getDao(HistoryText.class); + } + return historyTextDao; + } /** * Close the database connections and clear any cached DAOs. @@ -110,5 +118,6 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper { public void close() { super.close(); historyDao = null; + historyTextDao = null; } } diff --git a/ring-android/app/src/main/java/cx/ring/history/HistoryCall.java b/ring-android/app/src/main/java/cx/ring/history/HistoryCall.java index 2e7672919..0e818d0ae 100644 --- a/ring-android/app/src/main/java/cx/ring/history/HistoryCall.java +++ b/ring-android/app/src/main/java/cx/ring/history/HistoryCall.java @@ -46,11 +46,11 @@ import java.util.TimeZone; public class HistoryCall implements Parcelable { @DatabaseField(index = true, columnName="TIMESTAMP_START") - long call_start; + public long call_start; @DatabaseField - long call_end; + public long call_end; @DatabaseField - String number; + public String number; @DatabaseField boolean missed; @DatabaseField @@ -62,6 +62,8 @@ public class HistoryCall implements Parcelable { @DatabaseField long contactID; @DatabaseField + String contactKey; + @DatabaseField String callID; public String getAccountID() { @@ -73,14 +75,15 @@ public class HistoryCall implements Parcelable { } public HistoryCall(SipCall call) { - call_start = call.getTimestampStart_(); - call_end = call.getTimestampEnd_(); - accountID = call.getAccount().getAccountID(); - number = call.getmContact().getPhones().get(0).getNumber(); + call_start = call.getTimestampStart(); + call_end = call.getTimestampEnd(); + accountID = call.getAccount(); + number = call.getNumber(); missed = call.isRinging() && call.isIncoming(); direction = call.getCallType(); recordPath = call.getRecordPath(); - contactID = call.getmContact().getId(); + contactID = call.getContact().getId(); + contactKey = call.getContact().getKey(); callID = call.getCallId(); } @@ -90,10 +93,10 @@ public class HistoryCall implements Parcelable { public String getDirection() { switch (direction) { - case SipCall.direction.CALL_TYPE_INCOMING: - return "CALL_TYPE_INCOMING"; - case SipCall.direction.CALL_TYPE_OUTGOING: - return "CALL_TYPE_OUTGOING"; + case SipCall.Direction.INCOMING: + return "INCOMING"; + case SipCall.Direction.OUTGOING: + return "OUTGOING"; default: return "CALL_TYPE_UNDETERMINED"; } @@ -102,9 +105,15 @@ public class HistoryCall implements Parcelable { public String getDate() { return HistoryTimeModel.timeToHistoryConst(call_start); } + public Date getStartDate() { + return new Date(call_start); + } + public Date getEndDate() { + return new Date(call_end); + } public String getStartString(String format) { - Timestamp stamp = new Timestamp(call_start * 1000); // in milliseconds + Timestamp stamp = new Timestamp(call_start); // in milliseconds Date date = new Date(stamp.getTime()); SimpleDateFormat sdf = new SimpleDateFormat(format, Locale.getDefault()); sdf.setTimeZone(TimeZone.getDefault()); @@ -114,7 +123,7 @@ public class HistoryCall implements Parcelable { public String getDurationString() { - long duration = call_end - call_start; + long duration = (call_end - call_start)/1000; if (duration < 60) return String.format(Locale.getDefault(), "%02d secs", duration); @@ -122,7 +131,6 @@ public class HistoryCall implements Parcelable { return String.format(Locale.getDefault(), "%02d mins %02d secs", (duration % 3600) / 60, (duration % 60)); return String.format(Locale.getDefault(), "%d h %02d mins %02d secs", duration / 3600, (duration % 3600) / 60, (duration % 60)); - } public long getDuration() { @@ -152,6 +160,7 @@ public class HistoryCall implements Parcelable { dest.writeInt(direction); dest.writeString(recordPath); dest.writeLong(contactID); + dest.writeString(contactKey); dest.writeString(callID); } @@ -174,6 +183,7 @@ public class HistoryCall implements Parcelable { direction = in.readInt(); recordPath = in.readString(); contactID = in.readLong(); + contactKey = in.readString(); callID = in.readString(); } @@ -182,11 +192,14 @@ public class HistoryCall implements Parcelable { } public boolean isIncoming() { - return direction == SipCall.direction.CALL_TYPE_INCOMING; + return direction == SipCall.Direction.INCOMING; } public boolean isMissed() { return missed; } + public CharSequence getCallId() { + return callID; + } } diff --git a/ring-android/app/src/main/java/cx/ring/history/HistoryEntry.java b/ring-android/app/src/main/java/cx/ring/history/HistoryEntry.java index 385355c75..433db3c56 100644 --- a/ring-android/app/src/main/java/cx/ring/history/HistoryEntry.java +++ b/ring-android/app/src/main/java/cx/ring/history/HistoryEntry.java @@ -34,15 +34,18 @@ package cx.ring.history; import android.os.Parcel; import android.os.Parcelable; import cx.ring.model.CallContact; +import cx.ring.model.TextMessage; import java.util.ArrayList; +import java.util.Date; import java.util.NavigableMap; import java.util.TreeMap; public class HistoryEntry implements Parcelable { private CallContact contact; - private NavigableMap<Long, HistoryCall> calls; + private final NavigableMap<Long, HistoryCall> calls = new TreeMap<>(); + private final NavigableMap<Long, TextMessage> text_messages = new TreeMap<>(); private String accountID; int missed_sum; int outgoing_sum; @@ -50,7 +53,6 @@ public class HistoryEntry implements Parcelable { public HistoryEntry(String account, CallContact c) { contact = c; - calls = new TreeMap<Long, HistoryCall>(); accountID = account; missed_sum = outgoing_sum = incoming_sum = 0; } @@ -66,6 +68,9 @@ public class HistoryEntry implements Parcelable { public NavigableMap<Long, HistoryCall> getCalls() { return calls; } + public NavigableMap<Long, TextMessage> getTextMessages() { + return text_messages; + } public CallContact getContact() { return contact; @@ -97,13 +102,24 @@ public class HistoryEntry implements Parcelable { setContact(linkedTo); } + public void addTextMessage(TextMessage text) { + text_messages.put(text.getTimestamp(), text); + if (contact.isUnknown() && !text.getContact().isUnknown()) + setContact(text.getContact()); + } + /*public void addTextMessage(HistoryText text) { + TextMessage txt = new TextMessage(text); + txt.setContact(contact); + text_messages.put(txt.getTimestamp(), txt); + }*/ + public String getNumber() { return calls.lastEntry().getValue().number; } public String getTotalDuration() { int duration = 0; - ArrayList<HistoryCall> all_calls = new ArrayList<HistoryCall>(calls.values()); + ArrayList<HistoryCall> all_calls = new ArrayList<>(calls.values()); for (HistoryCall all_call : all_calls) { duration += all_call.getDuration(); } @@ -114,15 +130,60 @@ public class HistoryEntry implements Parcelable { return duration / 60 + "min"; } - public int getMissed_sum() { + public Date getLastCallDate() { + /*Date d = new Date(0); + for (Map.Entry<Long, HistoryCall> c : getCalls().entrySet()) { + Date nd = c.getValue().getStartDate(); + if (d.compareTo(nd) < 0) + d = nd; + } + return d;*/ + return new Date(calls.isEmpty() ? 0 : calls.lastEntry().getKey()); + } + public Date getLastTextDate() { + return new Date(text_messages.isEmpty() ? 0 : text_messages.lastEntry().getKey()); + } + public Date getLastInteraction() { + return new Date(Math.max(calls.isEmpty() ? 0 : calls.lastEntry().getKey(), text_messages.isEmpty() ? 0 : text_messages.lastEntry().getKey())); + } + + public HistoryCall getLastOutgoingCall() { + for (HistoryCall c : calls.descendingMap().values()) + if (!c.isIncoming()) + return c; + return null; + } + public TextMessage getLastOutgoingText() { + for (TextMessage c : text_messages.descendingMap().values()) + if (c.isOutgoing()) + return c; + return null; + } + + public String getLastNumberUsed() { + HistoryCall call = getLastOutgoingCall(); + TextMessage text = getLastOutgoingText(); + if (call == null && text == null) + return null; + if (call == null) + return text.getNumber(); + if (text == null) + return call.getNumber(); + if (call.call_start < text.getTimestamp()) + return text.getNumber(); + else + return call.getNumber(); + } + + public int getMissedSum() { return missed_sum; } - public int getOutgoing_sum() { + public int getOutgoingSum() { return outgoing_sum; } - public int getIncoming_sum() { + public int getIncomingSum() { return incoming_sum; } @@ -136,8 +197,10 @@ public class HistoryEntry implements Parcelable { dest.writeParcelable(contact, 0); - dest.writeList(new ArrayList<HistoryCall>(calls.values())); - dest.writeList(new ArrayList<Long>(calls.keySet())); + dest.writeList(new ArrayList<>(calls.values())); + dest.writeList(new ArrayList<>(calls.keySet())); + dest.writeList(new ArrayList<>(text_messages.values())); + dest.writeList(new ArrayList<>(text_messages.keySet())); dest.writeString(accountID); dest.writeInt(missed_sum); @@ -159,21 +222,24 @@ public class HistoryEntry implements Parcelable { private HistoryEntry(Parcel in) { contact = in.readParcelable(CallContact.class.getClassLoader()); - ArrayList<HistoryCall> values = new ArrayList<HistoryCall>(); + ArrayList<HistoryCall> values = new ArrayList<>(); in.readList(values, HistoryCall.class.getClassLoader()); - - ArrayList<Long> keys = new ArrayList<Long>(); + ArrayList<Long> keys = new ArrayList<>(); in.readList(keys, Long.class.getClassLoader()); - - calls = new TreeMap<Long, HistoryCall>(); for (int i = 0; i < keys.size(); ++i) { calls.put(keys.get(i), values.get(i)); } + ArrayList<TextMessage> tvalues = new ArrayList<>(); + in.readList(tvalues, TextMessage.class.getClassLoader()); + ArrayList<Long> tkeys = new ArrayList<>(); + in.readList(tkeys, Long.class.getClassLoader()); + for (int i = 0; i < keys.size(); ++i) { + text_messages.put(tkeys.get(i), tvalues.get(i)); + } accountID = in.readString(); missed_sum = in.readInt(); outgoing_sum = in.readInt(); incoming_sum = in.readInt(); } - } diff --git a/ring-android/app/src/main/java/cx/ring/history/HistoryManager.java b/ring-android/app/src/main/java/cx/ring/history/HistoryManager.java index 591cbf06b..07355c29b 100644 --- a/ring-android/app/src/main/java/cx/ring/history/HistoryManager.java +++ b/ring-android/app/src/main/java/cx/ring/history/HistoryManager.java @@ -32,6 +32,8 @@ package cx.ring.history; import android.content.Context; +import android.util.Log; + import com.j256.ormlite.android.apptools.OpenHelperManager; import com.j256.ormlite.stmt.QueryBuilder; import com.j256.ormlite.table.TableUtils; @@ -53,9 +55,10 @@ public class HistoryManager { public boolean insertNewEntry(Conference toInsert){ for (SipCall call : toInsert.getParticipants()) { - call.setTimestampEnd_(System.currentTimeMillis()); + call.setTimestampEnd(System.currentTimeMillis()); HistoryCall persistent = new HistoryCall(call); try { + Log.w("HistoryManager", "HistoryDao().create() " + persistent.getNumber() + " " + persistent.getStartDate().toString() + " " + persistent.getEndDate()); getHelper().getHistoryDao().create(persistent); } catch (SQLException e) { e.printStackTrace(); @@ -65,6 +68,17 @@ public class HistoryManager { return true; } + public boolean insertNewTextMessage(HistoryText txt) { + try { + Log.w("HistoryManager", "HistoryDao().create() acc:" + txt.getAccountID() + " num:" + txt.getNumber() + " date:" + txt.getDate().toString() + " msg:" + txt.getMessage()); + getHelper().getTextHistoryDao().create(txt); + } catch (SQLException e) { + e.printStackTrace(); + return false; + } + return true; + } + /* * Necessary when user hang up a call in a Conference * The call creates an HistoryCall, but the conference still goes on @@ -84,16 +98,21 @@ public class HistoryManager { } public List<HistoryCall> getAll() throws SQLException { - QueryBuilder<HistoryCall, Integer> qb = getHelper().getHistoryDao().queryBuilder(); qb.orderBy("TIMESTAMP_START", true); - return getHelper().getHistoryDao().query(qb.prepare()); } + public List<HistoryText> getAllTextMessages() throws SQLException { + QueryBuilder<HistoryText, Integer> qb = getHelper().getTextHistoryDao().queryBuilder(); + qb.orderBy("TIMESTAMP", true); + return getHelper().getTextHistoryDao().query(qb.prepare()); + } + public boolean clearDB() { try { TableUtils.clearTable(getHelper().getConnectionSource(), HistoryCall.class); + TableUtils.clearTable(getHelper().getConnectionSource(), HistoryText.class); } catch (SQLException e) { e.printStackTrace(); return false; diff --git a/ring-android/app/src/main/java/cx/ring/history/HistoryText.java b/ring-android/app/src/main/java/cx/ring/history/HistoryText.java new file mode 100644 index 000000000..372c2038a --- /dev/null +++ b/ring-android/app/src/main/java/cx/ring/history/HistoryText.java @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2004-2014 Savoir-Faire Linux Inc. + * + * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + + +package cx.ring.history; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.j256.ormlite.field.DatabaseField; + +import java.sql.Timestamp; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +import cx.ring.model.CallContact; +import cx.ring.model.TextMessage; + +public class HistoryText implements Parcelable { + + @DatabaseField(index = true, columnName="id") + public String id; + @DatabaseField(index = true, columnName="TIMESTAMP") + public long time; + @DatabaseField + public String number; + @DatabaseField + int direction; + @DatabaseField + String accountID; + @DatabaseField + long contactID; + @DatabaseField + String contactKey; + @DatabaseField + String callID; + @DatabaseField + String message; + + public HistoryText(TextMessage txt) { + id = txt.getId(); + time = txt.getTimestamp(); + accountID = txt.getAccount(); + number = txt.getNumber(); + direction = txt.getCallType(); + message = txt.getMessage(); + callID = txt.getCallId(); + if (txt.getContact() != null) { + contactID = txt.getContact().getId(); + contactKey = txt.getContact().getKey(); + } + } + + public String getAccountID() { + return accountID; + } + + public long getContactID() { + return contactID; + } + + /* + public HistoryText(String account, String from, String msg, CallContact contact, boolean incoming) { + time = System.currentTimeMillis(); + accountID = account; + number = from; + direction = incoming ? TextMessage.direction.INCOMING : TextMessage.direction.OUTGOING; + if (contact != null) { + contactID = contact.getId(); + contactKey = contact.getKey(); + } + //callID = call.getCallId(); + message = msg; + }*/ + + /* Needed by ORMLite */ + public HistoryText() { + } + + public String getDirection() { + switch (direction) { + case TextMessage.direction.INCOMING: + return "INCOMING"; + case TextMessage.direction.OUTGOING: + return "OUTGOING"; + default: + return "CALL_TYPE_UNDETERMINED"; + } + } + + public Date getDate() { + return new Date(time); + } + + public String getTimeString(String format) { + Timestamp stamp = new Timestamp(time); // in milliseconds + Date date = new Date(stamp.getTime()); + SimpleDateFormat sdf = new SimpleDateFormat(format, Locale.getDefault()); + sdf.setTimeZone(TimeZone.getDefault()); + return sdf.format(date); + + } + + public String getNumber() { + return number; + } + + public String getMessage() { + return message; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(id); + dest.writeLong(time); + dest.writeString(accountID); + dest.writeString(number); + dest.writeInt(direction); + dest.writeLong(contactID); + dest.writeString(callID); + dest.writeString(message); + } + + public static final Creator<HistoryText> CREATOR = new Creator<HistoryText>() { + public HistoryText createFromParcel(Parcel in) { + return new HistoryText(in); + } + + public HistoryText[] newArray(int size) { + return new HistoryText[size]; + } + }; + + public HistoryText(Parcel in) { + id = in.readString(); + time = in.readLong(); + accountID = in.readString(); + number = in.readString(); + direction = in.readInt(); + contactID = in.readLong(); + callID = in.readString(); + message = in.readString(); + } + + public boolean isIncoming() { + return direction == TextMessage.direction.INCOMING; + } + + public String getCallId() { + return callID; + } +} diff --git a/ring-android/app/src/main/java/cx/ring/loaders/AccountsLoader.java b/ring-android/app/src/main/java/cx/ring/loaders/AccountsLoader.java index 32c2cc7f0..b96b6662c 100644 --- a/ring-android/app/src/main/java/cx/ring/loaders/AccountsLoader.java +++ b/ring-android/app/src/main/java/cx/ring/loaders/AccountsLoader.java @@ -76,24 +76,23 @@ public class AccountsLoader extends AsyncTaskLoader<Bundle> { Map<String, String> state; for (String id : accountIDs) { + details = (Map<String, String>) service.getAccountDetails(id); + state = (Map<String, String>) service.getVolatileAccountDetails(id); if (id.contentEquals(ACCOUNT_IP2IP)) { - details = (HashMap<String, String>) service.getAccountDetails(id); - state = (Map<String, String>) service.getVolatileAccountDetails(id); IP2IP = new Account(ACCOUNT_IP2IP, details, new ArrayList<Map<String, String>>(), state); // Empty credentials //accounts.add(IP2IP); continue; } - details = (Map<String, String>) service.getAccountDetails(id); + credentials = (ArrayList<Map<String, String>>) service.getCredentials(id); - state = (Map<String, String>) service.getVolatileAccountDetails(id); - for (Map.Entry<String, String> entry : state.entrySet()) { + /*for (Map.Entry<String, String> entry : state.entrySet()) { Log.i(TAG, "state:" + entry.getKey() + " -> " + entry.getValue()); - } + }*/ Account tmp = new Account(id, details, credentials, state); accounts.add(tmp); - Log.i(TAG, "account:" + tmp.getAlias() + " " + tmp.isEnabled()); + // Log.i(TAG, "account:" + tmp.getAlias() + " " + tmp.isEnabled()); } } catch (RemoteException | NullPointerException e) { diff --git a/ring-android/app/src/main/java/cx/ring/loaders/ContactsLoader.java b/ring-android/app/src/main/java/cx/ring/loaders/ContactsLoader.java index 3482d3479..fd71e80af 100644 --- a/ring-android/app/src/main/java/cx/ring/loaders/ContactsLoader.java +++ b/ring-android/app/src/main/java/cx/ring/loaders/ContactsLoader.java @@ -36,24 +36,32 @@ import java.util.ArrayList; import cx.ring.model.CallContact; import android.content.AsyncTaskLoader; +import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; +import android.provider.ContactsContract; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.CommonDataKinds.SipAddress; import android.provider.ContactsContract.Contacts; +import android.util.Log; -public class ContactsLoader extends AsyncTaskLoader<Bundle> { - -// private static final String TAG = ContactsLoader.class.getSimpleName(); +public class ContactsLoader extends AsyncTaskLoader<ContactsLoader.Result> +{ + private static final String TAG = ContactsLoader.class.getSimpleName(); + + public class Result { + public final ArrayList<CallContact> contacts = new ArrayList<>(); + public final ArrayList<CallContact> starred = new ArrayList<>(); + } // These are the Contacts rows that we will retrieve. - static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] { Contacts._ID, Contacts.DISPLAY_NAME, Contacts.PHOTO_ID, Contacts.LOOKUP_KEY, Contacts.STARRED }; + static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] { Contacts._ID, Contacts.LOOKUP_KEY, Contacts.DISPLAY_NAME, Contacts.PHOTO_ID, Contacts.STARRED }; static final String[] CONTACTS_PHONES_PROJECTION = new String[] { Phone.NUMBER, Phone.TYPE }; static final String[] CONTACTS_SIP_PROJECTION = new String[] { SipAddress.SIP_ADDRESS, SipAddress.TYPE }; - String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND (" + Contacts.HAS_PHONE_NUMBER + "=1) AND (" + Contacts.DISPLAY_NAME + " != '' ))"; + static private final String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND (" + Contacts.HAS_PHONE_NUMBER + "=1) AND (" + Contacts.DISPLAY_NAME + " != '' ))"; Uri baseUri; public ContactsLoader(Context context, Uri u) { @@ -62,52 +70,55 @@ public class ContactsLoader extends AsyncTaskLoader<Bundle> { } @Override - public Bundle loadInBackground() { - ArrayList<CallContact> contacts = new ArrayList<CallContact>(); - ArrayList<CallContact> starred = new ArrayList<CallContact>(); + public Result loadInBackground() { + Result res = new Result(); + + ContentResolver cr = getContext().getContentResolver(); + Cursor result = cr.query(baseUri, CONTACTS_SUMMARY_PROJECTION, select, null, Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC"); + if (result == null) + return res; - Cursor result = getContext().getContentResolver().query(baseUri, CONTACTS_SUMMARY_PROJECTION, select, null, - Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC"); int iID = result.getColumnIndex(Contacts._ID); + int iKey = result.getColumnIndex(ContactsContract.Data.LOOKUP_KEY); int iName = result.getColumnIndex(Contacts.DISPLAY_NAME); int iPhoto = result.getColumnIndex(Contacts.PHOTO_ID); int iStarred = result.getColumnIndex(Contacts.STARRED); CallContact.ContactBuilder builder = CallContact.ContactBuilder.getInstance(); - + while (result.moveToNext()) { - builder.startNewContact(result.getLong(iID), result.getString(iName), result.getLong(iPhoto)); + long cid = result.getLong(iID); + builder.startNewContact(cid, result.getString(iKey), result.getString(iName), result.getLong(iPhoto)); -// Cursor cPhones = getContext().getContentResolver().query(Phone.CONTENT_URI, CONTACTS_PHONES_PROJECTION, -// Phone.CONTACT_ID + " =" + result.getLong(iID), null, null); - -// while (cPhones.moveToNext()) { -// builder.addPhoneNumber(cPhones.getString(cPhones.getColumnIndex(Phone.NUMBER)), cPhones.getInt(cPhones.getColumnIndex(Phone.TYPE))); -//// Log.i(TAG,"Phone:"+cPhones.getString(cPhones.getColumnIndex(Phone.NUMBER))); -// } -// cPhones.close(); -// -// Cursor cSip = getContext().getContentResolver().query(Phone.CONTENT_URI, CONTACTS_SIP_PROJECTION, -// Phone.CONTACT_ID + "=" + result.getLong(iID), null, null); -// -// while (cSip.moveToNext()) { -// builder.addSipNumber(cSip.getString(cSip.getColumnIndex(SipAddress.SIP_ADDRESS)), cSip.getInt(cSip.getColumnIndex(SipAddress.TYPE))); -//// Log.i(TAG,"Phone:"+cSip.getString(cSip.getColumnIndex(SipAddress.SIP_ADDRESS))); -// } -// cSip.close(); - - contacts.add(builder.build()); + Cursor cPhones = cr.query(Phone.CONTENT_URI, CONTACTS_PHONES_PROJECTION, Phone.CONTACT_ID + " =" + cid, null, null); + if (cPhones != null) { + while (cPhones.moveToNext()) { + builder.addPhoneNumber(cPhones.getString(cPhones.getColumnIndex(Phone.NUMBER)), cPhones.getInt(cPhones.getColumnIndex(Phone.TYPE))); + Log.w(TAG,"Phone:"+cPhones.getString(cPhones.getColumnIndex(Phone.NUMBER))); + } + cPhones.close(); + } + + //Cursor cSip = cr.query(Phone.CONTENT_URI, CONTACTS_SIP_PROJECTION, Phone.CONTACT_ID + "=" + cid, null, null); + Cursor cSip = cr.query(ContactsContract.Data.CONTENT_URI, + CONTACTS_SIP_PROJECTION, + ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "=? AND " + ContactsContract.Data.MIMETYPE + "=?", + new String[]{String.valueOf(cid), ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE}, null); + if (cSip != null) { + while (cSip.moveToNext()) { + builder.addSipNumber(cSip.getString(cSip.getColumnIndex(SipAddress.SIP_ADDRESS)), cSip.getInt(cSip.getColumnIndex(SipAddress.TYPE))); + Log.w(TAG, "SIP Phone for " + cid + " :" + cSip.getString(cSip.getColumnIndex(SipAddress.SIP_ADDRESS))); + } + cSip.close(); + } + + res.contacts.add(builder.build()); if (result.getInt(iStarred) == 1) { - starred.add(builder.build()); + res.starred.add(builder.build()); } - } - + } result.close(); - Bundle toReturn = new Bundle(); - - toReturn.putParcelableArrayList("Contacts", contacts); - toReturn.putParcelableArrayList("Starred", starred); - return toReturn; + return res; } } diff --git a/ring-android/app/src/main/java/cx/ring/loaders/HistoryLoader.java b/ring-android/app/src/main/java/cx/ring/loaders/HistoryLoader.java index b6712ea9f..7606f2f85 100644 --- a/ring-android/app/src/main/java/cx/ring/loaders/HistoryLoader.java +++ b/ring-android/app/src/main/java/cx/ring/loaders/HistoryLoader.java @@ -63,7 +63,7 @@ public class HistoryLoader extends AsyncTaskLoader<ArrayList<HistoryEntry>> { @Override public ArrayList<HistoryEntry> loadInBackground() { - HashMap<String,HistoryEntry> historyEntries = new HashMap<String, HistoryEntry>(); + HashMap<String,HistoryEntry> historyEntries = new HashMap<>(); try { List<HistoryCall> list = historyManager.getAll(); @@ -77,11 +77,12 @@ public class HistoryLoader extends AsyncTaskLoader<ArrayList<HistoryEntry>> { ContactsContract.Contacts._ID + " = ?", new String[]{String.valueOf(call.getContactID())}, null); int iID = result.getColumnIndex(ContactsContract.Contacts._ID); + int iKey = result.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY); int iName = result.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME); int iPhoto = result.getColumnIndex(ContactsContract.Contacts.PHOTO_ID); if (result.moveToFirst()) { - builder.startNewContact(result.getLong(iID), result.getString(iName), result.getLong(iPhoto)); + builder.startNewContact(result.getLong(iID), result.getString(iKey), result.getString(iName), result.getLong(iPhoto)); builder.addPhoneNumber(call.getNumber(), 0); contact = builder.build(); } else { diff --git a/ring-android/app/src/main/java/cx/ring/model/Bubble.java b/ring-android/app/src/main/java/cx/ring/model/Bubble.java index 3cf6e8a05..3387427eb 100644 --- a/ring-android/app/src/main/java/cx/ring/model/Bubble.java +++ b/ring-android/app/src/main/java/cx/ring/model/Bubble.java @@ -143,7 +143,7 @@ public abstract class Bubble { } protected Bitmap getContactPhoto(Context context, CallContact contact, int size) { - if (contact.getPhoto_id() > 0) { + if (contact.getPhotoId() > 0) { return ContactPictureTask.loadContactPhoto(context.getContentResolver(), contact.getId()); } else { return ContactPictureTask.decodeSampledBitmapFromResource(context.getResources(), R.drawable.ic_contact_picture, size, size); diff --git a/ring-android/app/src/main/java/cx/ring/model/BubbleContact.java b/ring-android/app/src/main/java/cx/ring/model/BubbleContact.java index e21f98a7c..7c0ce6ed6 100644 --- a/ring-android/app/src/main/java/cx/ring/model/BubbleContact.java +++ b/ring-android/app/src/main/java/cx/ring/model/BubbleContact.java @@ -38,7 +38,7 @@ public class BubbleContact extends Bubble { public SipCall associated_call; public BubbleContact(Context context, SipCall call, float x, float y, float size) { - super(context, call.getmContact(), x, y, size); + super(context, call.getContact(), x, y, size); associated_call = call; } @@ -62,7 +62,7 @@ public class BubbleContact extends Bubble { @Override public String getName() { - return associated_call.getmContact().getmDisplayName(); + return associated_call.getContact().getDisplayName(); } @Override diff --git a/ring-android/app/src/main/java/cx/ring/model/BubblesView.java b/ring-android/app/src/main/java/cx/ring/model/BubblesView.java index 8b465dde2..890a2bec6 100644 --- a/ring-android/app/src/main/java/cx/ring/model/BubblesView.java +++ b/ring-android/app/src/main/java/cx/ring/model/BubblesView.java @@ -97,7 +97,7 @@ public class BubblesView extends GLSurfaceView implements SurfaceHolder.Callback SurfaceHolder holder = getHolder(); holder.addCallback(this); - this.setZOrderOnTop(true); // necessary + //this.setZOrderOnTop(true); // necessary holder.setFormat(PixelFormat.TRANSLUCENT); // create thread only; it's started in surfaceCreated() createThread(); @@ -313,9 +313,9 @@ public class BubblesView extends GLSurfaceView implements SurfaceHolder.Callback } else canvas.drawBitmap(ic_bg, null, a.getBounds(showed * 2.f, b.getPos(), showed), action_paint); canvas.drawBitmap(a.getBitmap(), null, a.getBounds(showed, b.getPos(), showed), null); - float dist_raw = FloatMath.sqrt((b.pos.x - a.pos.x) * (b.pos.x - a.pos.x) + (b.pos.y - a.pos.y) * (b.pos.y - a.pos.y)); - float dist_min = a.radius + b.radius + bubbleActionTextDistMin; - float dist = Math.max(0, dist_raw - dist_min); + double dist_raw = Math.sqrt((b.pos.x - a.pos.x) * (b.pos.x - a.pos.x) + (b.pos.y - a.pos.y) * (b.pos.y - a.pos.y)); + double dist_min = a.radius + b.radius + bubbleActionTextDistMin; + double dist = Math.max(0, dist_raw - dist_min); if (actions.enabled && dist < dist_range) { white_name_paint.setAlpha(255 - (int) (255 * dist / dist_range)); canvas.drawText(a.name, a.getBounds().centerX(), a.getBounds().centerY() - a.radius * 2.2f, white_name_paint); @@ -343,7 +343,7 @@ public class BubblesView extends GLSurfaceView implements SurfaceHolder.Callback return true; if (action == MotionEvent.ACTION_UP) { - if (thread.suspendFlag) { + if (thread != null && thread.suspendFlag) { Log.i(TAG, "Relaunch drawing thread"); thread.setPaused(false); } @@ -354,7 +354,7 @@ public class BubblesView extends GLSurfaceView implements SurfaceHolder.Callback } } dragging_bubble = false; - } else if (action != MotionEvent.ACTION_DOWN && !isDraggingBubble() && !thread.suspendFlag) { + } else if (action != MotionEvent.ACTION_DOWN && !isDraggingBubble() && thread != null && !thread.suspendFlag) { Log.i(TAG, "Not dragging thread should be stopped"); thread.setPaused(true); } diff --git a/ring-android/app/src/main/java/cx/ring/model/CallContact.java b/ring-android/app/src/main/java/cx/ring/model/CallContact.java index c067a750f..e11861c07 100644 --- a/ring-android/app/src/main/java/cx/ring/model/CallContact.java +++ b/ring-android/app/src/main/java/cx/ring/model/CallContact.java @@ -32,6 +32,9 @@ package cx.ring.model; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import android.content.ContentResolver; import android.database.Cursor; @@ -39,29 +42,70 @@ import android.graphics.Bitmap; import android.os.Parcel; import android.os.Parcelable; import android.provider.ContactsContract.Profile; +import android.support.annotation.NonNull; + +import cx.ring.client.ConversationActivity; +import cx.ring.service.LocalService; public class CallContact implements Parcelable { + static public final Pattern ANGLE_BRACKETS_PATTERN = Pattern.compile("(?:[^<>]+<)?([^<>]+)>?\\s*"); + public static final Pattern RING_ID_PATTERN = Pattern.compile("^\\s*(?:ring(?:[\\s\\:]+))?(\\p{XDigit}{40})(?:@ring\\.dht)?\\s*$", Pattern.CASE_INSENSITIVE); public static int DEFAULT_ID = 0; private long id; + private String key; private String mDisplayName; private long photo_id; - private ArrayList<Phone> phones, sip_phones; + private ArrayList<Phone> phones/*, sip_phones*/; private String mEmail; private boolean isUser; - private WeakReference<Bitmap> contact_photo = new WeakReference<Bitmap>(null); + private WeakReference<Bitmap> contact_photo = new WeakReference<>(null); - private CallContact(long cID, String displayName, long photoID, ArrayList<Phone> p, ArrayList<Phone> sip, String mail, boolean user) { + private CallContact(long cID, String k, String displayName, long photoID, ArrayList<Phone> p, String mail, boolean user) { id = cID; + key = k; mDisplayName = displayName; phones = p; - sip_phones = sip; mEmail = mail; photo_id = photoID; isUser = user; } + private static String nobracketsNumber(@NonNull String number) { + Matcher m = ANGLE_BRACKETS_PATTERN.matcher(number); + if (m.find()) + return m.group(1); + return number; + } + + public static String canonicalNumber(@NonNull String number) { + number = nobracketsNumber(number); + Matcher m = RING_ID_PATTERN.matcher(number); + if (m.find()) + return "ring:"+m.group(1); + return number; + } + + public ArrayList<String> getIds() { + ArrayList<String> ret = new ArrayList<>(1+phones.size()); + if (id != -1) + ret.add("c:" + Long.toHexString(id)); + for (Phone p : phones) + ret.add(canonicalNumber(p.getNumber())); + return ret; + } + + public static long contactIdFromId(String id) { + if (!id.startsWith("c:")) + return -1; + try { + return Long.parseLong(id.substring(2), 16); + } catch (Exception e) { + return -1; + } + } + public CallContact(Parcel in) { readFromParcel(in); } @@ -70,15 +114,21 @@ public class CallContact implements Parcelable { return id; } - public String getmDisplayName() { - return mDisplayName; + public String getDisplayName() { + if (!mDisplayName.isEmpty()) + return mDisplayName; + if (!phones.isEmpty()) + return phones.get(0).getNumber(); + /*if (!sip_phones.isEmpty()) + return sip_phones.get(0).getNumber();*/ + return ""; } - public long getPhoto_id() { + public long getPhotoId() { return photo_id; } - public void setPhoto_id(long photo_id) { + public void setPhotoId(long photo_id) { this.photo_id = photo_id; } @@ -90,53 +140,56 @@ public class CallContact implements Parcelable { this.phones = phones; } - public ArrayList<Phone> getSip_phones() { - return sip_phones; + public String getEmail() { + return mEmail; } - public void setSip_phones(ArrayList<Phone> sip_phones) { - this.sip_phones = sip_phones; + public void setEmail(String mEmail) { + this.mEmail = mEmail; } - public Phone getSipPhone() { - if (sip_phones.size() > 0) { - return sip_phones.get(0); - } - if (phones.size() > 0) { - return phones.get(0); - } - return null; + public boolean hasNumber(String number) { + if (number == null || number.isEmpty()) + return false; + number = canonicalNumber(number); + for (Phone p : phones) + if (canonicalNumber(p.getNumber()).equals(number)) + return true; + return false; } - public String getmEmail() { - return mEmail; + @Override + public String toString() { + return mDisplayName; } - public void setmEmail(String mEmail) { - this.mEmail = mEmail; + public void setId(long id) { + this.id = id; } - @Override - public String toString() { - return mDisplayName; + public void setDisplayName(String displayName) { + this.mDisplayName = displayName; + } + + public String getKey() { + return key; } public static class ContactBuilder { long contactID; + String key; String contactName; long contactPhoto; ArrayList<Phone> phones; - ArrayList<Phone> sip; String contactMail; - public ContactBuilder startNewContact(long id, String displayName, long photo_id) { + public ContactBuilder startNewContact(long id, String k, String displayName, long photo_id) { contactID = id; - + key = k; contactName = displayName; contactPhoto = photo_id; - phones = new ArrayList<Phone>(); - sip = new ArrayList<Phone>(); + phones = new ArrayList<>(); return this; } @@ -146,12 +199,12 @@ public class CallContact implements Parcelable { } public ContactBuilder addSipNumber(String num, int type) { - sip.add(new Phone(num, type)); + phones.add(new Phone(num, type, NumberType.SIP)); return this; } public CallContact build() { - return new CallContact(contactID, contactName, contactPhoto, phones, sip, contactMail, false); + return new CallContact(contactID, key, contactName, contactPhoto, phones, contactMail, false); } public static ContactBuilder getInstance() { @@ -159,25 +212,30 @@ public class CallContact implements Parcelable { } public static CallContact buildUnknownContact(String to) { - ArrayList<Phone> phones = new ArrayList<Phone>(); + ArrayList<Phone> phones = new ArrayList<>(); phones.add(new Phone(to, 0)); - return new CallContact(-1, to, 0, phones, new ArrayList<CallContact.Phone>(), "", false); + return new CallContact(-1, null, to, 0, phones, "", false); + } + public static CallContact buildUnknownContact(String to, int type) { + ArrayList<Phone> phones = new ArrayList<>(); + phones.add(new Phone(to, type)); + return new CallContact(-1, null, to, 0, phones, "", false); } public static CallContact buildUserContact(ContentResolver cr) { - String[] mProjection = new String[] { Profile._ID, Profile.DISPLAY_NAME_PRIMARY, Profile.PHOTO_ID }; + String[] mProjection = new String[] { Profile._ID, Profile.LOOKUP_KEY, Profile.DISPLAY_NAME_PRIMARY, Profile.PHOTO_ID }; Cursor mProfileCursor = cr.query(Profile.CONTENT_URI, mProjection, null, null, null); CallContact result; if (mProfileCursor.getCount() > 0) { mProfileCursor.moveToFirst(); + String key = mProfileCursor.getString(mProfileCursor.getColumnIndex(Profile.LOOKUP_KEY)); String displayName = mProfileCursor.getString(mProfileCursor.getColumnIndex(Profile.DISPLAY_NAME_PRIMARY)); - result = new CallContact(mProfileCursor.getLong(mProfileCursor.getColumnIndex(Profile._ID)), displayName, - mProfileCursor.getLong(mProfileCursor.getColumnIndex(Profile.PHOTO_ID)), new ArrayList<Phone>(), - new ArrayList<CallContact.Phone>(), "", true); + result = new CallContact(mProfileCursor.getLong(mProfileCursor.getColumnIndex(Profile._ID)), key, displayName, + mProfileCursor.getLong(mProfileCursor.getColumnIndex(Profile.PHOTO_ID)), new ArrayList<Phone>(), "", true); } else { - result = new CallContact(-1, "Me", 0, new ArrayList<Phone>(), new ArrayList<CallContact.Phone>(), "", true); + result = new CallContact(-1, null, "Me", 0, new ArrayList<Phone>(), "", true); } mProfileCursor.close(); return result; @@ -193,26 +251,22 @@ public class CallContact implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { dest.writeLong(id); + dest.writeString(key); dest.writeString(mDisplayName); dest.writeLong(photo_id); dest.writeTypedList(phones); - - dest.writeTypedList(sip_phones); - dest.writeString(mEmail); dest.writeByte((byte) (isUser ? 1 : 0)); } private void readFromParcel(Parcel in) { - id = in.readLong(); + key = in.readString(); mDisplayName = in.readString(); photo_id = in.readLong(); - phones = new ArrayList<CallContact.Phone>(); - sip_phones = new ArrayList<CallContact.Phone>(); + phones = new ArrayList<>(); in.readTypedList(phones, Phone.CREATOR); - in.readTypedList(sip_phones, Phone.CREATOR); mEmail = in.readString(); isUser = in.readByte() == 1; } @@ -229,15 +283,48 @@ public class CallContact implements Parcelable { } }; - public static class Phone implements Parcelable { + public enum NumberType { + UNKNOWN(0), + TEL(1), + SIP(2), + IP(2), + RING(3); + + private final int type; + NumberType(int t) { + type = t; + } + private static final NumberType[] VALS = NumberType.values(); + public static NumberType fromInteger(int _id) + { + for (NumberType v : VALS) + if (v.type == _id) + return v; + return UNKNOWN; + } + /*public static NumberType guess(String num) { + String canon = canonicalNumber(num); + Matcher m = URI_NUMBER_REGEX.matcher(canon); + + return UNKNOWN; + }*/ + } - int type; + public static class Phone implements Parcelable { + NumberType ntype; String number; + int category; // Home, work, custom etc. - public Phone(String num, int ty) { - type = ty; + public Phone(String num, int cat) { + ntype = NumberType.UNKNOWN; + category = cat; number = num; } + public Phone(String num, int cat, NumberType nty) { + ntype = nty; + number = num; + category = cat; + } public Phone(Parcel in) { readFromParcel(in); @@ -250,13 +337,15 @@ public class CallContact implements Parcelable { @Override public void writeToParcel(Parcel dest, int arg1) { - dest.writeInt(type); + dest.writeInt(ntype.type); dest.writeString(number); + dest.writeInt(category); } private void readFromParcel(Parcel in) { - type = in.readInt(); + ntype = NumberType.fromInteger(in.readInt()); number = in.readString(); + category = in.readInt(); } public static final Parcelable.Creator<Phone> CREATOR = new Parcelable.Creator<Phone>() { @@ -271,12 +360,12 @@ public class CallContact implements Parcelable { } }; - public int getType() { - return type; + public NumberType getType() { + return ntype; } public void setType(int type) { - this.type = type; + this.ntype = NumberType.fromInteger(type); } public String getNumber() { @@ -289,13 +378,12 @@ public class CallContact implements Parcelable { } - public void addPhoneNumber(String tel, int type) { - phones.add(new Phone(tel, type)); + public void addPhoneNumber(String tel, int car) { + phones.add(new Phone(tel, car)); } - - public void addSipNumber(String tel, int type) { - sip_phones.add(new Phone(tel, type)); + public void addNumber(String tel, int cat, NumberType type) { + phones.add(new Phone(tel, cat, type)); } @@ -314,7 +402,7 @@ public class CallContact implements Parcelable { } public void setPhoto(Bitmap externalBMP) { - contact_photo = new WeakReference<Bitmap>(externalBMP); + contact_photo = new WeakReference<>(externalBMP); } /** diff --git a/ring-android/app/src/main/java/cx/ring/model/Conference.java b/ring-android/app/src/main/java/cx/ring/model/Conference.java index db027f6a9..a604bb1bb 100644 --- a/ring-android/app/src/main/java/cx/ring/model/Conference.java +++ b/ring-android/app/src/main/java/cx/ring/model/Conference.java @@ -36,13 +36,20 @@ import java.util.ArrayList; import android.os.Parcel; import android.os.Parcelable; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; + public class Conference implements Parcelable { private String id; private int mConfState; private ArrayList<SipCall> participants; private boolean recording; - private ArrayList<SipMessage> messages; + private ArrayList<TextMessage> messages; + + public int notificationId; + + private final static Random rand = new Random(); public static String DEFAULT_ID = "-1"; @@ -54,13 +61,13 @@ public class Conference implements Parcelable { participants.remove(toRemove); } - public boolean useSecureLayer() { + /*public boolean useSecureLayer() { for(SipCall call : participants){ if(call.getAccount().useSecureLayer()) return true; } return false; - } + }*/ public interface state { int ACTIVE_ATTACHED = 0; @@ -104,8 +111,8 @@ public class Conference implements Parcelable { public void writeToParcel(Parcel out, int flags) { out.writeString(id); out.writeInt(mConfState); - ArrayList<SipCall> normal_calls = new ArrayList<SipCall>(); - ArrayList<SecureSipCall> secure_calls = new ArrayList<SecureSipCall>(); + ArrayList<SipCall> normal_calls = new ArrayList<>(); + ArrayList<SecureSipCall> secure_calls = new ArrayList<>(); for(SipCall part : participants){ if(part instanceof SecureSipCall) @@ -117,6 +124,7 @@ public class Conference implements Parcelable { out.writeTypedList(normal_calls); out.writeByte((byte) (recording ? 1 : 0)); out.writeTypedList(messages); + out.writeInt(notificationId); } public static final Parcelable.Creator<Conference> CREATOR = new Parcelable.Creator<Conference>() { @@ -131,36 +139,38 @@ public class Conference implements Parcelable { private Conference(Parcel in) { - participants = new ArrayList<SipCall>(); + participants = new ArrayList<>(); id = in.readString(); mConfState = in.readInt(); - ArrayList<SecureSipCall> tmp = new ArrayList<SecureSipCall>(); + ArrayList<SecureSipCall> tmp = new ArrayList<>(); in.readTypedList(tmp, SecureSipCall.CREATOR); in.readTypedList(participants, SipCall.CREATOR); participants.addAll(tmp); recording = in.readByte() == 1; - messages = new ArrayList<SipMessage>(); - in.readTypedList(messages, SipMessage.CREATOR); + messages = new ArrayList<>(); + in.readTypedList(messages, TextMessage.CREATOR); + notificationId = in.readInt(); } public Conference(SipCall call) { this(DEFAULT_ID); participants.add(call); + notificationId = rand.nextInt(); } public Conference(String cID) { id = cID; - participants = new ArrayList<SipCall>(); + participants = new ArrayList<>(); recording = false; - messages = new ArrayList<SipMessage>(); + messages = new ArrayList<>(); } public Conference(Conference c) { id = c.id; mConfState = c.mConfState; - participants = new ArrayList<SipCall>(c.participants); + participants = new ArrayList<>(c.participants); recording = c.recording; - messages = new ArrayList<SipMessage>(); + messages = new ArrayList<>(); } public String getId() { @@ -275,11 +285,11 @@ public class Conference implements Parcelable { return participants.size() == 1 && participants.get(0).isOngoing() || participants.size() > 1; } - public ArrayList<SipMessage> getMessages() { + public ArrayList<TextMessage> getMessages() { return messages; } - public void addSipMessage(SipMessage sipMessage) { + public void addSipMessage(TextMessage sipMessage) { messages.add(sipMessage); } diff --git a/ring-android/app/src/main/java/cx/ring/model/Conversation.java b/ring-android/app/src/main/java/cx/ring/model/Conversation.java new file mode 100644 index 000000000..db311cd51 --- /dev/null +++ b/ring-android/app/src/main/java/cx/ring/model/Conversation.java @@ -0,0 +1,232 @@ +package cx.ring.model; + +import android.database.ContentObservable; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import cx.ring.history.HistoryCall; +import cx.ring.history.HistoryEntry; +import cx.ring.history.HistoryText; +import cx.ring.model.account.Account; + +public class Conversation extends ContentObservable implements Parcelable +{ + static final String TAG = Conversation.class.getSimpleName(); + + public CallContact contact; + //private HistoryEntry history; + /** accountId -> histroy entries */ + final private Map<String, HistoryEntry> history = new HashMap<>(); + + //private Conference current_call = null; + public final ArrayList<Conference> current_calls; + + public String getLastNumberUsed(String accountID) { + HistoryEntry he = history.get(accountID); + if (he == null) + return null; + return he.getLastNumberUsed(); + } + + public Conference getConference(String id) { + for (Conference c : current_calls) + if (c.getId().contentEquals(id) || c.getCallById(id) != null) + return c; + return null; + } + + public Pair<HistoryEntry, HistoryCall> findHistoryByCallId(String id) { + for (HistoryEntry e : history.values()) { + for (HistoryCall c : e.getCalls().values()) { + if (c.getCallId().equals(id)) + return new Pair<>(e, c); + } + } + return null; + } + + public class ConversationElement { + public HistoryCall call = null; + public TextMessage text = null; + public ConversationElement(HistoryCall c) { + call = c; + } + public ConversationElement(TextMessage t) { + text = t; + } + public long getDate() { + if (text != null) + return text.getTimestamp(); + else if (call != null) + return call.call_start; + return 0; + } + } + + public Conversation(CallContact c) { + contact = c; + current_calls = new ArrayList<>(); + } + + public static final Creator<Conversation> CREATOR = new Creator<Conversation>() { + @Override + public Conversation createFromParcel(Parcel in) { + return new Conversation(in); + } + + @Override + public Conversation[] newArray(int size) { + return new Conversation[size]; + } + }; + + public CallContact getContact() { + return contact; + } + + public Date getLastInteraction() { + if (!current_calls.isEmpty()) { + return new Date(); + } + Date d = new Date(0); + + //for (Map.Entry<String, HistoryEntry> e : history.entrySet()) { + for (HistoryEntry e : history.values()) { + Date nd = e.getLastInteraction(); + if (d.compareTo(nd) < 0) + d = nd; + } + return d; + } + + public void addHistoryCall(HistoryCall c) { + String accountId = c.getAccountID(); + if (history.containsKey(accountId)) + history.get(accountId).addHistoryCall(c, contact); + else { + HistoryEntry e = new HistoryEntry(accountId, contact); + e.addHistoryCall(c, contact); + history.put(accountId, e); + } + } + public void addTextMessage(TextMessage txt) { + if (txt.getCallId() != null && !txt.getCallId().isEmpty()) { + Conference conf = getConference(txt.getCallId()); + if (conf == null) + return; + conf.addSipMessage(txt); + }// else { + if (txt.getContact() == null) + txt.setContact(contact); + String accountId = txt.getAccount(); + if (history.containsKey(accountId)) + history.get(accountId).addTextMessage(txt); + else { + HistoryEntry e = new HistoryEntry(accountId, contact); + e.addTextMessage(txt); + history.put(accountId, e); + } + //} + } + + /*public HistoryEntry getHistory(String account_id) { + return history.get(account_id); + }*/ + + public ArrayList<ConversationElement> getHistory() { + ArrayList<ConversationElement> all = new ArrayList<>(); + for (HistoryEntry e : history.values()) { + for (HistoryCall c : e.getCalls().values()) + all.add(new ConversationElement(c)); + for (TextMessage t : e.getTextMessages().values()) + all.add(new ConversationElement(t)); + } + Collections.sort(all, new Comparator<ConversationElement>() { + @Override + public int compare(ConversationElement lhs, ConversationElement rhs) { + return (int)((lhs.getDate() - rhs.getDate())/1000l); + } + }); + return all; + } + + public Set<String> getAccountsUsed() { + return history.keySet(); + } + + public String getLastAccountUsed() { + String last = null; + Date d = new Date(0); + for (Map.Entry<String, HistoryEntry> e : history.entrySet()) { + Date nd = e.getValue().getLastInteraction(); + if (d.compareTo(nd) < 0) { + d = nd; + last = e.getKey(); + } + } + return last; + } + + public Conference getCurrentCall() { + if (current_calls.isEmpty()) + return null; + return current_calls.get(0); + } + public void setCurrentCall(Conference c) { + current_calls.add(c); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(contact, flags); + //dest.writeParcelable(current_call, flags); + dest.writeList(current_calls); + dest.writeList(new ArrayList<>(history.values())); + dest.writeList(new ArrayList<>(history.keySet())); + } + + protected Conversation(Parcel in) { + contact = in.readParcelable(CallContact.class.getClassLoader()); + //current_call = in.readParcelable(Conference.class.getClassLoader()); + current_calls = in.readArrayList(Conference.class.getClassLoader()); + + ArrayList<HistoryEntry> values = new ArrayList<>(); + in.readList(values, HistoryEntry.class.getClassLoader()); + ArrayList<String> keys = new ArrayList<>(); + in.readList(keys, String.class.getClassLoader()); + for (int i = 0; i < keys.size(); ++i) + history.put(keys.get(i), values.get(i)); + } + + public ArrayList<TextMessage> getTextMessages() { + ArrayList<TextMessage> texts = new ArrayList<>(); + for (HistoryEntry h : history.values()) { + texts.addAll(h.getTextMessages().values()); + } + Collections.sort(texts, new Comparator<TextMessage>() { + @Override + public int compare(TextMessage lhs, TextMessage rhs) { + return (int)((lhs.getTimestamp() - rhs.getTimestamp())/1000l); + } + }); + return texts; + } + +} diff --git a/ring-android/app/src/main/java/cx/ring/model/SecureSipCall.java b/ring-android/app/src/main/java/cx/ring/model/SecureSipCall.java index c606cc130..8ca7ef131 100644 --- a/ring-android/app/src/main/java/cx/ring/model/SecureSipCall.java +++ b/ring-android/app/src/main/java/cx/ring/model/SecureSipCall.java @@ -55,10 +55,10 @@ public class SecureSipCall extends SipCall { private boolean isInitialized; - public SecureSipCall(SipCall call) { + public SecureSipCall(SipCall call, String keyExchange) { super(call); isInitialized = false; - String keyExchange = getAccount().getSrtpDetails().getDetailString(AccountDetailSrtp.CONFIG_SRTP_KEY_EXCHANGE); + //String keyExchange = getAccount().getSrtpDetails().getDetailString(AccountDetailSrtp.CONFIG_SRTP_KEY_EXCHANGE); /*if (keyExchange.contentEquals("zrtp")) { mSecureLayerUsed = SecureLayer.ZRTP_LAYER; } else */if (keyExchange.contentEquals("sdes")) { @@ -124,7 +124,7 @@ public class SecureSipCall extends SipCall { } /* - * returns what state should be visible during call + * returns what State should be visible during call */ public int displayModule() { /*if (isInitialized) { diff --git a/ring-android/app/src/main/java/cx/ring/model/SipCall.java b/ring-android/app/src/main/java/cx/ring/model/SipCall.java index 5524f2027..0aa9c511e 100644 --- a/ring-android/app/src/main/java/cx/ring/model/SipCall.java +++ b/ring-android/app/src/main/java/cx/ring/model/SipCall.java @@ -35,7 +35,6 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; -import cx.ring.model.account.Account; public class SipCall implements Parcelable { @@ -43,24 +42,34 @@ public class SipCall implements Parcelable { public static String ACCOUNT = "account"; public static String CONTACT = "contact"; public static String TYPE = "type"; - public static String STATE = "state"; + public static String STATE = "State"; + public static String NUMBER = "number"; private static final String TAG = SipCall.class.getSimpleName(); private String mCallID = ""; - private Account mAccount = null; + private String mAccount = ""; private CallContact mContact = null; + private String mNumber = ""; private boolean isRecording = false; private long timestampStart_ = 0; private long timestampEnd_ = 0; private int mCallType; - private int mCallState = state.CALL_STATE_NONE; + private int mCallState = State.NONE; + + public SipCall(String id, String account, String number, int direction) { + mCallID = id; + mAccount = account; + mNumber = number; + mCallType = direction; + } public SipCall(SipCall call) { mCallID = call.mCallID; mAccount = call.mAccount; mContact = call.mContact; + mNumber = call.mNumber; isRecording = call.isRecording; timestampStart_ = call.timestampStart_; timestampEnd_ = call.timestampEnd_; @@ -75,10 +84,10 @@ public class SipCall implements Parcelable { */ protected SipCall(Parcel in) { - mCallID = in.readString(); - mAccount = in.readParcelable(Account.class.getClassLoader()); + mAccount = in.readString(); mContact = in.readParcelable(CallContact.class.getClassLoader()); + mNumber = in.readString(); isRecording = in.readByte() == 1; mCallType = in.readInt(); mCallState = in.readInt(); @@ -88,14 +97,11 @@ public class SipCall implements Parcelable { public SipCall(Bundle args) { mCallID = args.getString(ID); - mAccount = args.getParcelable(ACCOUNT); + mAccount = args.getString(ACCOUNT); mCallType = args.getInt(TYPE); mCallState = args.getInt(STATE); mContact = args.getParcelable(CONTACT); - } - - public long getTimestampEnd_() { - return timestampEnd_; + mNumber = args.getString(NUMBER); } public String getRecordPath() { @@ -109,29 +115,29 @@ public class SipCall implements Parcelable { public Bundle getBundle() { Bundle args = new Bundle(); args.putString(SipCall.ID, mCallID); - args.putParcelable(SipCall.ACCOUNT, mAccount); + args.putString(SipCall.ACCOUNT, mAccount); args.putInt(SipCall.STATE, mCallState); args.putInt(SipCall.TYPE, mCallType); args.putParcelable(SipCall.CONTACT, mContact); + args.putString(SipCall.NUMBER, mNumber); return args; } - - public interface direction { - int CALL_TYPE_INCOMING = 1; - int CALL_TYPE_OUTGOING = 2; + public interface Direction { + int INCOMING = 1; + int OUTGOING = 2; } - public interface state { - int CALL_STATE_NONE = 0; - int CALL_STATE_CONNECTING = 1; - int CALL_STATE_RINGING = 2; - int CALL_STATE_CURRENT = 3; - int CALL_STATE_HUNGUP = 4; - int CALL_STATE_BUSY = 5; - int CALL_STATE_FAILURE = 6; - int CALL_STATE_HOLD = 7; - int CALL_STATE_UNHOLD = 8; + public interface State { + int NONE = 0; + int CONNECTING = 1; + int RINGING = 2; + int CURRENT = 3; + int HUNGUP = 4; + int BUSY = 5; + int FAILURE = 6; + int HOLD = 7; + int UNHOLD = 8; } @Override @@ -141,11 +147,10 @@ public class SipCall implements Parcelable { @Override public void writeToParcel(Parcel out, int flags) { - out.writeString(mCallID); - out.writeParcelable(mAccount, 0); - + out.writeString(mAccount); out.writeParcelable(mContact, 0); + out.writeString(mNumber); out.writeByte((byte) (isRecording ? 1 : 0)); out.writeInt(mCallType); out.writeInt(mCallState); @@ -171,32 +176,36 @@ public class SipCall implements Parcelable { return mCallID; } - public long getTimestampStart_() { + public long getTimestampStart() { return timestampStart_; } - public void setTimestampStart_(long timestampStart_) { - this.timestampStart_ = timestampStart_; + public void setTimestampStart(long timestampStart) { + this.timestampStart_ = timestampStart; } - public void setTimestampEnd_(long timestampEnd_) { - this.timestampEnd_ = timestampEnd_; + public long getTimestampEnd() { + return timestampEnd_; + } + + public void setTimestampEnd(long timestampEnd) { + this.timestampEnd_ = timestampEnd; } - public void setAccount(Account account) { + public void setAccount(String account) { mAccount = account; } - public Account getAccount() { + public String getAccount() { return mAccount; } public String getCallTypeString() { switch (mCallType) { - case direction.CALL_TYPE_INCOMING: - return "CALL_TYPE_INCOMING"; - case direction.CALL_TYPE_OUTGOING: - return "CALL_TYPE_OUTGOING"; + case Direction.INCOMING: + return "INCOMING"; + case Direction.OUTGOING: + return "OUTGOING"; default: return "CALL_TYPE_UNDETERMINED"; } @@ -206,37 +215,49 @@ public class SipCall implements Parcelable { mCallState = callState; } - public CallContact getmContact() { + public void setContact(CallContact c) { + mContact = c; + } + + public CallContact getContact() { return mContact; } + public void setNumber(String n) { + mNumber = n; + } + + public String getNumber() { + return mNumber; + } + public String getCallStateString() { String text_state; switch (mCallState) { - case state.CALL_STATE_NONE: + case State.NONE: text_state = "NONE"; break; - case state.CALL_STATE_RINGING: + case State.RINGING: text_state = "RINGING"; break; - case state.CALL_STATE_CURRENT: + case State.CURRENT: text_state = "CURRENT"; break; - case state.CALL_STATE_HUNGUP: + case State.HUNGUP: text_state = "HUNGUP"; break; - case state.CALL_STATE_BUSY: + case State.BUSY: text_state = "BUSY"; break; - case state.CALL_STATE_FAILURE: + case State.FAILURE: text_state = "FAILURE"; break; - case state.CALL_STATE_HOLD: + case State.HOLD: text_state = "HOLD"; break; - case state.CALL_STATE_UNHOLD: + case State.UNHOLD: text_state = "UNHOLD"; break; default: @@ -256,7 +277,7 @@ public class SipCall implements Parcelable { public void printCallInfo() { Log.i(TAG, "CallInfo: CallID: " + mCallID); - Log.i(TAG, " AccountID: " + mAccount.getAccountID()); + Log.i(TAG, " AccountID: " + mAccount); Log.i(TAG, " CallState: " + mCallState); Log.i(TAG, " CallType: " + mCallType); } @@ -270,29 +291,29 @@ public class SipCall implements Parcelable { } public boolean isOutGoing() { - return mCallType == direction.CALL_TYPE_OUTGOING; + return mCallType == Direction.OUTGOING; } public boolean isRinging() { - return mCallState == state.CALL_STATE_RINGING || mCallState == state.CALL_STATE_NONE; + return mCallState == State.RINGING || mCallState == State.NONE; } public boolean isIncoming() { - return mCallType == direction.CALL_TYPE_INCOMING; + return mCallType == Direction.INCOMING; } public boolean isOngoing() { - return !(mCallState == state.CALL_STATE_RINGING || mCallState == state.CALL_STATE_NONE || mCallState == state.CALL_STATE_FAILURE - || mCallState == state.CALL_STATE_BUSY || mCallState == state.CALL_STATE_HUNGUP); + return !(mCallState == State.RINGING || mCallState == State.NONE || mCallState == State.FAILURE + || mCallState == State.BUSY || mCallState == State.HUNGUP); } public boolean isOnHold() { - return mCallState == state.CALL_STATE_HOLD; + return mCallState == State.HOLD; } public boolean isCurrent() { - return mCallState == state.CALL_STATE_CURRENT; + return mCallState == State.CURRENT; } diff --git a/ring-android/app/src/main/java/cx/ring/model/SipMessage.java b/ring-android/app/src/main/java/cx/ring/model/SipMessage.java deleted file mode 100644 index f8a43cc15..000000000 --- a/ring-android/app/src/main/java/cx/ring/model/SipMessage.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2004-2014 Savoir-Faire Linux Inc. - * - * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 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., 675 Mass Ave, Cambridge, MA 02139, USA. - * - * Additional permission under GNU GPL version 3 section 7: - * - * If you modify this program, or any covered work, by linking or - * combining it with the OpenSSL project's OpenSSL library (or a - * modified version of that library), containing parts covered by the - * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. - * grants you additional permission to convey the resulting work. - * Corresponding Source for a non-source form of such a combination - * shall include the source code for the parts of OpenSSL used as well - * as that of the covered work. - */ -package cx.ring.model; - -import android.os.Parcel; -import android.os.Parcelable; - -public class SipMessage implements Parcelable { - public boolean left; - public String comment; - - public SipMessage(boolean left, String comment) { - super(); - this.left = left; - this.comment = comment; - } - - @Override - public int describeContents() { - return 0; - } - - @Override - public void writeToParcel(Parcel out, int flags) { - out.writeByte((byte) (left ? 1 : 0)); - out.writeString(comment); - } - - public static final Parcelable.Creator<SipMessage> CREATOR = new Parcelable.Creator<SipMessage>() { - public SipMessage createFromParcel(Parcel in) { - return new SipMessage(in); - } - - public SipMessage[] newArray(int size) { - return new SipMessage[size]; - } - }; - - private SipMessage(Parcel in) { - left = (in.readByte() == 1); - comment = in.readString(); - } - -} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/model/SipMessage.aidl b/ring-android/app/src/main/java/cx/ring/model/TextMessage.aidl similarity index 52% rename from ring-android/app/src/main/java/cx/ring/model/SipMessage.aidl rename to ring-android/app/src/main/java/cx/ring/model/TextMessage.aidl index 072914601..1af4f254e 100644 --- a/ring-android/app/src/main/java/cx/ring/model/SipMessage.aidl +++ b/ring-android/app/src/main/java/cx/ring/model/TextMessage.aidl @@ -1,4 +1,4 @@ package cx.ring.model; -parcelable SipMessage; \ No newline at end of file +parcelable TextMessage; \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/model/TextMessage.java b/ring-android/app/src/main/java/cx/ring/model/TextMessage.java new file mode 100644 index 000000000..e7d44df9f --- /dev/null +++ b/ring-android/app/src/main/java/cx/ring/model/TextMessage.java @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2004-2014 Savoir-Faire Linux Inc. + * + * Author: Alexandre Lision <alexandre.lision@savoirfairelinux> + * Alexandre Savard <alexandre.savard@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ +package cx.ring.model; + +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +import cx.ring.history.HistoryText; + +public class TextMessage implements Parcelable { + + public static String ID = "id"; + public static String ACCOUNT = "account"; + public static String CONTACT = "contact"; + public static String NUMBER = "number"; + public static String CALL = "call"; + public static String TYPE = "type"; + public static String STATE = "State"; + public static String MESSAGE = "message"; + public static String TIME = "time"; + + private static final String TAG = TextMessage.class.getSimpleName(); + + private String mID = ""; + private String mAccount = null; + private CallContact mContact = null; + private String mNumber = null; + private long mTimestamp = 0; + + private int mType; + private int mState = state.NONE; + private String mMessage; + private String mCallID = ""; + + public TextMessage(TextMessage msg) { + mID = msg.mID; + mAccount = msg.mAccount; + mContact = msg.mContact; + mNumber = msg.mNumber; + mTimestamp = msg.mTimestamp; + mType = msg.mType; + mState = msg.mState; + mMessage = msg.mMessage; + mCallID = msg.mCallID; + } + + /** + * ********************* + * Construtors + * ********************* + */ + + /*public TextMessage(String account, String number, String message, boolean in) { + mAccount = account; + mNumber = number; + mMessage = message; + mTimestamp = System.currentTimeMillis(); + mType = in ? direction.INCOMING : direction.OUTGOING; + }*/ + + public TextMessage(boolean in, String message) { + mMessage = message; + mType = in ? direction.INCOMING : direction.OUTGOING; + mTimestamp = System.currentTimeMillis(); + } + + public TextMessage(boolean in, String message, String number, String callid, String account) { + mAccount = account; + mNumber = number; + mMessage = message; + mTimestamp = System.currentTimeMillis(); + mCallID = callid; + mType = in ? direction.INCOMING : direction.OUTGOING; + } + + public TextMessage(HistoryText h) { + mID = h.id; + mAccount = h.getAccountID(); + mNumber = h.getNumber(); + mMessage = h.getMessage(); + mTimestamp = h.getDate().getTime(); + mType = h.isIncoming() ? direction.INCOMING : direction.OUTGOING; + mCallID = h.getCallId(); + } + + protected TextMessage(Parcel in) { + mID = in.readString(); + mAccount = in.readString(); + mContact = in.readParcelable(CallContact.class.getClassLoader()); + mNumber = in.readString(); + mCallID = in.readString(); + mType = in.readInt(); + mState = in.readInt(); + mTimestamp = in.readLong(); + mMessage = in.readString(); + } + + public TextMessage(Bundle args) { + mID = args.getString(ID); + mAccount = args.getParcelable(ACCOUNT); + mNumber = args.getString(NUMBER); + mCallID = args.getString(CALL); + mType = args.getInt(TYPE); + mState = args.getInt(STATE); + mContact = args.getParcelable(CONTACT); + mMessage = args.getString(MESSAGE); + mTimestamp = args.getLong(TIME); + } + + public String getRecordPath() { + return ""; + } + + public int getCallType() { + return mType; + } + + public Bundle getBundle() { + Bundle args = new Bundle(); + args.putString(ID, mID); + args.putString(ACCOUNT, mAccount); + args.putString(NUMBER, mNumber); + args.putString(CALL, mCallID); + args.putInt(STATE, mState); + args.putInt(TYPE, mType); + args.putParcelable(CONTACT, mContact); + args.putString(MESSAGE, mMessage); + args.putLong(TIME, mTimestamp); + return args; + } + + public String getMessage() { + return mMessage; + } + + public void setContact(CallContact contact) { + mContact = contact; + } + + public void setCallId(String callId) { + this.mCallID = callId; + } + + public String getCallId() { + return mCallID; + } + + public void setNumber(String number) { + this.mNumber = number; + } + + public interface direction { + int INCOMING = 1; + int OUTGOING = 2; + } + + public interface state { + int NONE = 0; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeString(mID); + out.writeString(mAccount); + out.writeParcelable(mContact, 0); + out.writeString(mNumber); + out.writeString(mCallID); + out.writeInt(mType); + out.writeInt(mState); + out.writeLong(mTimestamp); + out.writeString(mMessage); + } + + public static final Creator<TextMessage> CREATOR = new Creator<TextMessage>() { + public TextMessage createFromParcel(Parcel in) { + return new TextMessage(in); + } + + public TextMessage[] newArray(int size) { + return new TextMessage[size]; + } + }; + + public void setID(String id) { + mID = id; + } + + public String getId() { + return mID; + } + + public long getTimestamp() { + return mTimestamp; + } + + public void setTimestamp(long timestamp) { + this.mTimestamp = timestamp; + } + + public void setAccount(String account) { + mAccount = account; + } + + public String getAccount() { + return mAccount; + } + + public String getTypeString() { + switch (mType) { + case direction.INCOMING: + return "INCOMING"; + case direction.OUTGOING: + return "OUTGOING"; + default: + return "UNDETERMINED"; + } + } + + public void setState(int callState) { + mState = callState; + } + + public CallContact getContact() { + return mContact; + } + + public String getNumber() { + return mNumber; + } + + public String getStateString() { + String text_state; + switch (mState) { + case state.NONE: + text_state = "NONE"; + break; + default: + text_state = "NULL"; + } + return text_state; + } + + /** + * Compare sip calls based on call ID + */ + @Override + public boolean equals(Object c) { + return c instanceof TextMessage && ((TextMessage) c).mID.contentEquals((mID)); + } + + public boolean isOutgoing() { + return mType == direction.OUTGOING; + } + + public boolean isIncoming() { + return mType == direction.INCOMING; + } + +} diff --git a/ring-android/app/src/main/java/cx/ring/model/account/Account.java b/ring-android/app/src/main/java/cx/ring/model/account/Account.java index 0f938c125..4c0529943 100644 --- a/ring-android/app/src/main/java/cx/ring/model/account/Account.java +++ b/ring-android/app/src/main/java/cx/ring/model/account/Account.java @@ -57,10 +57,13 @@ public class Account extends java.util.Observable implements Parcelable { advancedDetails = new AccountDetailAdvanced(details); srtpDetails = new AccountDetailSrtp(details); tlsDetails = new AccountDetailTls(details); - volatileDetails = new AccountDetailVolatile(volatile_details); - credentialsDetails = new ArrayList<>(); - for (int i = 0; i < credentials.size(); ++i) { - credentialsDetails.add(new AccountCredentials(credentials.get(i))); + if (volatile_details != null) + volatileDetails = new AccountDetailVolatile(volatile_details); + if (credentials != null) { + credentialsDetails = new ArrayList<>(); + for (int i = 0; i < credentials.size(); ++i) { + credentialsDetails.add(new AccountCredentials(credentials.get(i))); + } } } @@ -84,8 +87,8 @@ public class Account extends java.util.Observable implements Parcelable { return volatileDetails.getDetailString(AccountDetailVolatile.CONFIG_ACCOUNT_REGISTRATION_STATUS); } - public void setRegistered_state(String registered_state, int code) { - Log.i(TAG, "setRegistered_state " + registered_state + " " + code); + public void setRegistrationState(String registered_state, int code) { + Log.i(TAG, "setRegistrationState " + registered_state + " " + code); volatileDetails.setDetailString(AccountDetailVolatile.CONFIG_ACCOUNT_REGISTRATION_STATUS, registered_state); volatileDetails.setDetailString(AccountDetailVolatile.CONFIG_ACCOUNT_REGISTRATION_STATE_CODE, Integer.toString(code)); } @@ -141,7 +144,7 @@ public class Account extends java.util.Observable implements Parcelable { advancedDetails = new AccountDetailAdvanced((HashMap<String, String>) in.readSerializable()); srtpDetails = new AccountDetailSrtp((HashMap<String, String>) in.readSerializable()); tlsDetails = new AccountDetailTls((HashMap<String, String>) in.readSerializable()); - credentialsDetails = new ArrayList<AccountCredentials>(); + credentialsDetails = new ArrayList<>(); int cred_count = in.readInt(); for (int i = 0; i < cred_count; ++i) { credentialsDetails.add(new AccountCredentials((HashMap<String, String>) in.readSerializable())); @@ -202,7 +205,7 @@ public class Account extends java.util.Observable implements Parcelable { } public HashMap<String, String> getDetails() { - HashMap<String, String> results = new HashMap<String, String>(); + HashMap<String, String> results = new HashMap<>(); results.putAll(basicDetails.getDetailsHashMap()); results.putAll(advancedDetails.getDetailsHashMap()); diff --git a/ring-android/app/src/main/java/cx/ring/model/account/AccountDetail.java b/ring-android/app/src/main/java/cx/ring/model/account/AccountDetail.java index 54fce72ae..9ec2ef288 100644 --- a/ring-android/app/src/main/java/cx/ring/model/account/AccountDetail.java +++ b/ring-android/app/src/main/java/cx/ring/model/account/AccountDetail.java @@ -26,10 +26,12 @@ import java.util.HashMap; public interface AccountDetail { - public static final String TRUE_STR = "true"; - public static final String FALSE_STR = "false"; + String TAG = AccountDetail.class.getSimpleName(); - public static class PreferenceEntry { + String TRUE_STR = "true"; + String FALSE_STR = "false"; + + class PreferenceEntry { public String mKey; public boolean isTwoState; public String mValue; @@ -47,20 +49,19 @@ public interface AccountDetail { } public boolean isChecked() { - return mValue.contentEquals("true"); + return mValue.contentEquals(TRUE_STR); } } - public static final String TAG = "PreferenceHashMap"; - public ArrayList<PreferenceEntry> getDetailValues(); + ArrayList<PreferenceEntry> getDetailValues(); - public ArrayList<String> getValuesOnly(); + ArrayList<String> getValuesOnly(); - public HashMap<String, String> getDetailsHashMap(); + HashMap<String, String> getDetailsHashMap(); - public String getDetailString(String key); + String getDetailString(String key); - public void setDetailString(String key, String newValue); + void setDetailString(String key, String newValue); } diff --git a/ring-android/app/src/main/java/cx/ring/model/account/AccountDetailAdvanced.java b/ring-android/app/src/main/java/cx/ring/model/account/AccountDetailAdvanced.java index 587550503..0691d1624 100644 --- a/ring-android/app/src/main/java/cx/ring/model/account/AccountDetailAdvanced.java +++ b/ring-android/app/src/main/java/cx/ring/model/account/AccountDetailAdvanced.java @@ -22,8 +22,11 @@ package cx.ring.model.account; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import android.util.Log; @@ -51,12 +54,26 @@ public class AccountDetailAdvanced implements AccountDetail { public static final String CONFIG_AUDIO_PORT_MIN = "Account.audioPortMin"; public static final String CONFIG_AUDIO_PORT_MAX = "Account.audioPortMax"; + private static final Set<String> CONFIG_KEYS = new HashSet<>(Arrays.asList( + CONFIG_ACCOUNT_MAILBOX, + CONFIG_ACCOUNT_REGISTRATION_EXPIRE, + CONFIG_CREDENTIAL_NUMBER, + CONFIG_ACCOUNT_DTMF_TYPE, + CONFIG_RINGTONE_PATH, CONFIG_RINGTONE_ENABLED, + CONFIG_KEEP_ALIVE_ENABLED, + CONFIG_LOCAL_INTERFACE, CONFIG_PUBLISHED_SAMEAS_LOCAL, CONFIG_LOCAL_PORT, + CONFIG_PUBLISHED_PORT, CONFIG_PUBLISHED_ADDRESS, + CONFIG_STUN_SERVER, CONFIG_STUN_ENABLE, + CONFIG_AUDIO_PORT_MIN, CONFIG_AUDIO_PORT_MAX)); + private ArrayList<AccountDetail.PreferenceEntry> privateArray; public AccountDetailAdvanced(Map<String, String> pref) { - privateArray = new ArrayList<AccountDetail.PreferenceEntry>(); + privateArray = new ArrayList<>(); for (String key : pref.keySet()) { + if (!CONFIG_KEYS.contains(key)) + continue; PreferenceEntry p = new PreferenceEntry(key); p.mValue = pref.get(key); @@ -75,7 +92,7 @@ public class AccountDetailAdvanced implements AccountDetail { } public ArrayList<String> getValuesOnly() { - ArrayList<String> valueList = new ArrayList<String>(); + ArrayList<String> valueList = new ArrayList<>(); for (AccountDetail.PreferenceEntry p : privateArray) { Log.i(TAG, "" + p.mValue); diff --git a/ring-android/app/src/main/java/cx/ring/model/account/AccountDetailBasic.java b/ring-android/app/src/main/java/cx/ring/model/account/AccountDetailBasic.java index 6e95f122c..9170ed4fd 100644 --- a/ring-android/app/src/main/java/cx/ring/model/account/AccountDetailBasic.java +++ b/ring-android/app/src/main/java/cx/ring/model/account/AccountDetailBasic.java @@ -45,10 +45,25 @@ public class AccountDetailBasic implements AccountDetail { public static final String CONFIG_ACCOUNT_ENABLE = "Account.enable"; public static final String CONFIG_PRESENCE_ENABLE = "Account.presenceEnabled"; + public static final String ACCOUNT_TYPE_RING = "RING"; + public static final String ACCOUNT_TYPE_SIP = "SIP"; + public static final String ACCOUNT_TYPE_IAX = "IAX"; + private ArrayList<AccountDetail.PreferenceEntry> privateArray; + public String getAlias() { + return getDetailString(CONFIG_ACCOUNT_ALIAS); + } + public String getUsername() { + return getDetailString(CONFIG_ACCOUNT_USERNAME); + } + public String getHostname() { + return getDetailString(CONFIG_ACCOUNT_HOSTNAME); + } + + public AccountDetailBasic(Map<String, String> pref) { - privateArray = new ArrayList<AccountDetail.PreferenceEntry>(); + privateArray = new ArrayList<>(); for (String key : pref.keySet()) { PreferenceEntry p = new PreferenceEntry(key); @@ -66,7 +81,7 @@ public class AccountDetailBasic implements AccountDetail { } public ArrayList<String> getValuesOnly() { - ArrayList<String> valueList = new ArrayList<String>(); + ArrayList<String> valueList = new ArrayList<>(); for (AccountDetail.PreferenceEntry p : privateArray) { Log.i(TAG, "" + p.mValue); @@ -77,7 +92,7 @@ public class AccountDetailBasic implements AccountDetail { } public HashMap<String, String> getDetailsHashMap() { - HashMap<String, String> map = new HashMap<String, String>(); + HashMap<String, String> map = new HashMap<>(); for (AccountDetail.PreferenceEntry p : privateArray) { map.put(p.mKey, p.mValue); @@ -100,13 +115,14 @@ public class AccountDetailBasic implements AccountDetail { } public void setDetailString(String key, String newValue) { - for (int i = 0; i < privateArray.size(); ++i) { - PreferenceEntry p = privateArray.get(i); + for (PreferenceEntry p : privateArray) { if (p.mKey.equals(key)) { - privateArray.get(i).mValue = newValue; + p.mValue = newValue; + Log.w(TAG, "setDetailString " + key + " -> " + newValue); + //return; } } - + Log.w(TAG, "setDetailString FAIL" + key + " -> " + newValue); } } diff --git a/ring-android/app/src/main/java/cx/ring/model/account/AccountDetailSrtp.java b/ring-android/app/src/main/java/cx/ring/model/account/AccountDetailSrtp.java index a70942338..81d1bc7c9 100644 --- a/ring-android/app/src/main/java/cx/ring/model/account/AccountDetailSrtp.java +++ b/ring-android/app/src/main/java/cx/ring/model/account/AccountDetailSrtp.java @@ -41,7 +41,7 @@ public class AccountDetailSrtp implements AccountDetail { private ArrayList<AccountDetail.PreferenceEntry> privateArray; public static ArrayList<AccountDetail.PreferenceEntry> getPreferenceEntries() { - ArrayList<AccountDetail.PreferenceEntry> preference = new ArrayList<AccountDetail.PreferenceEntry>(); + ArrayList<AccountDetail.PreferenceEntry> preference = new ArrayList<>(); preference.add(new PreferenceEntry(CONFIG_SRTP_ENABLE, true)); preference.add(new PreferenceEntry(CONFIG_SRTP_KEY_EXCHANGE, false)); diff --git a/ring-android/app/src/main/java/cx/ring/model/account/AccountDetailTls.java b/ring-android/app/src/main/java/cx/ring/model/account/AccountDetailTls.java index 595f9d820..9ebfa2415 100644 --- a/ring-android/app/src/main/java/cx/ring/model/account/AccountDetailTls.java +++ b/ring-android/app/src/main/java/cx/ring/model/account/AccountDetailTls.java @@ -46,7 +46,7 @@ public class AccountDetailTls implements AccountDetail { private ArrayList<AccountDetail.PreferenceEntry> privateArray; public static ArrayList<AccountDetail.PreferenceEntry> getPreferenceEntries() { - ArrayList<AccountDetail.PreferenceEntry> preference = new ArrayList<AccountDetail.PreferenceEntry>(); + ArrayList<AccountDetail.PreferenceEntry> preference = new ArrayList<>(); preference.add(new PreferenceEntry(CONFIG_TLS_LISTENER_PORT)); preference.add(new PreferenceEntry(CONFIG_TLS_ENABLE, true)); diff --git a/ring-android/app/src/main/java/cx/ring/service/CallManagerCallBack.java b/ring-android/app/src/main/java/cx/ring/service/CallManagerCallBack.java index 66bc4aeac..aaf74804e 100644 --- a/ring-android/app/src/main/java/cx/ring/service/CallManagerCallBack.java +++ b/ring-android/app/src/main/java/cx/ring/service/CallManagerCallBack.java @@ -1,28 +1,33 @@ package cx.ring.service; +import android.support.v4.app.NotificationCompat; +import android.app.PendingIntent; import android.content.Intent; import android.os.Bundle; import android.util.Log; + +import cx.ring.R; import cx.ring.client.CallActivity; +import cx.ring.history.HistoryText; +import cx.ring.model.CallContact; +import cx.ring.model.TextMessage; import cx.ring.model.account.Account; +import cx.ring.model.account.AccountDetailSrtp; import cx.ring.utils.SwigNativeConverter; import java.util.ArrayList; -import java.util.HashMap; import java.util.Map; -import cx.ring.model.CallContact; import cx.ring.model.Conference; import cx.ring.model.SecureSipCall; import cx.ring.model.SipCall; -import cx.ring.model.SipMessage; public class CallManagerCallBack extends Callback { private static final String TAG = "CallManagerCallBack"; private SipService mService; - static public final String CALL_STATE_CHANGED = "call-state-changed"; + static public final String CALL_STATE_CHANGED = "call-State-changed"; static public final String INCOMING_CALL = "incoming-call"; static public final String INCOMING_TEXT = "incoming-text"; static public final String CONF_CREATED = "conf_created"; @@ -46,11 +51,12 @@ public class CallManagerCallBack extends Callback { @Override public void callStateChanged(String callID, String newState, int detail_code) { - Log.d(TAG, "on_call_state_changed : (" + callID + ", " + newState + ")"); + Log.w(TAG, "on_call_state_changed : (" + callID + ", " + newState + ")"); Conference toUpdate = mService.findConference(callID); if (toUpdate == null) { + Log.w(TAG, "callStateChanged: can't find call " + callID); return; } @@ -59,38 +65,52 @@ public class CallManagerCallBack extends Callback { intent.putExtra("State", newState); intent.putExtra("DetailCode", detail_code); - if (newState.equals("RINGING")) { - toUpdate.setCallState(callID, SipCall.state.CALL_STATE_RINGING); - } else if (newState.equals("CURRENT")) { - if (toUpdate.isRinging()) { - toUpdate.getCallById(callID).setTimestampStart_(System.currentTimeMillis()); - } - toUpdate.setCallState(callID, SipCall.state.CALL_STATE_CURRENT); - } else if (newState.equals("HUNGUP")) { - Log.d(TAG, "Hanging up " + callID); - SipCall call = toUpdate.getCallById(callID); - if (!toUpdate.hasMultipleParticipants()) { - if (toUpdate.isRinging() && toUpdate.isIncoming()) { - mService.mNotificationManager.publishMissedCallNotification(mService.getConferences().get(callID)); + if (toUpdate.isRinging() && !newState.equals("RINGING")) { + Log.w(TAG, "Setting call start date " + callID); + toUpdate.getCallById(callID).setTimestampStart(System.currentTimeMillis()); + } + + switch (newState) { + case "CONNECTING": + toUpdate.setCallState(callID, SipCall.State.CONNECTING); break; + case "RINGING": + toUpdate.setCallState(callID, SipCall.State.RINGING); break; + case "CURRENT": + toUpdate.setCallState(callID, SipCall.State.CURRENT); break; + case "HOLD": + toUpdate.setCallState(callID, SipCall.State.HOLD); break; + case "UNHOLD": + toUpdate.setCallState(callID, SipCall.State.CURRENT); break; + case "HUNGUP": + case "INACTIVE": + Log.d(TAG, "Hanging up " + callID); + Log.w("CallNotification ", "Canceling " + toUpdate.notificationId); + mService.mNotificationManager.notificationManager.cancel(toUpdate.notificationId); + SipCall call = toUpdate.getCallById(callID); + if (!toUpdate.hasMultipleParticipants()) { + if (toUpdate.isRinging() && toUpdate.isIncoming()) { + mService.mNotificationManager.publishMissedCallNotification(mService.getConferences().get(callID)); + } + toUpdate.setCallState(callID, SipCall.State.HUNGUP); + mService.mHistoryManager.insertNewEntry(toUpdate); + mService.getConferences().remove(toUpdate.getId()); + } else { + toUpdate.setCallState(callID, SipCall.State.HUNGUP); + mService.mHistoryManager.insertNewEntry(call); } - toUpdate.setCallState(callID, SipCall.state.CALL_STATE_HUNGUP); - mService.mHistoryManager.insertNewEntry(toUpdate); + break; + case "BUSY": + mService.mNotificationManager.notificationManager.cancel(toUpdate.notificationId); + toUpdate.setCallState(callID, SipCall.State.BUSY); mService.getConferences().remove(toUpdate.getId()); - } else { - toUpdate.setCallState(callID, SipCall.state.CALL_STATE_HUNGUP); - mService.mHistoryManager.insertNewEntry(call); - } - } else if (newState.equals("BUSY")) { - toUpdate.setCallState(callID, SipCall.state.CALL_STATE_BUSY); - mService.getConferences().remove(toUpdate.getId()); - } else if (newState.equals("FAILURE")) { - toUpdate.setCallState(callID, SipCall.state.CALL_STATE_FAILURE); - mService.getConferences().remove(toUpdate.getId()); - Ringservice.hangUp(callID); - } else if (newState.equals("HOLD")) { - toUpdate.setCallState(callID, SipCall.state.CALL_STATE_HOLD); - } else if (newState.equals("UNHOLD")) { - toUpdate.setCallState(callID, SipCall.state.CALL_STATE_CURRENT); + break; + case "FAILURE": + Log.w("CallNotification ", "Canceling " + toUpdate.notificationId); + mService.mNotificationManager.notificationManager.cancel(toUpdate.notificationId); + toUpdate.setCallState(callID, SipCall.State.FAILURE); + mService.getConferences().remove(toUpdate.getId()); + Ringservice.hangUp(callID); + break; } intent.putExtra("conference", toUpdate); mService.sendBroadcast(intent); @@ -99,32 +119,28 @@ public class CallManagerCallBack extends Callback { @Override public void incomingCall(String accountID, String callID, String from) { - Log.d(TAG, "on_incoming_call(" + accountID + ", " + callID + ", " + from + ")"); + Log.w(TAG, "on_incoming_call(" + accountID + ", " + callID + ", " + from + ")"); try { StringMap details = Ringservice.getAccountDetails(accountID); - VectMap credentials = Ringservice.getCredentials(accountID); - StringMap state = Ringservice.getVolatileAccountDetails(accountID); - Account acc = new Account(accountID, details.toNative(), credentials.toNative(), state.toNative()); - - Bundle args = new Bundle(); - args.putString(SipCall.ID, callID); - args.putParcelable(SipCall.ACCOUNT, acc); - args.putInt(SipCall.STATE, SipCall.state.CALL_STATE_RINGING); - args.putInt(SipCall.TYPE, SipCall.direction.CALL_TYPE_INCOMING); - - CallContact unknow = CallContact.ContactBuilder.buildUnknownContact(from); - args.putParcelable(SipCall.CONTACT, unknow); + //VectMap credentials = Ringservice.getCredentials(accountID); + //StringMap state = Ringservice.getVolatileAccountDetails(accountID); + Account acc = new Account(accountID, details.toNative(), null, null); Intent toSend = new Intent(CallManagerCallBack.INCOMING_CALL); toSend.setClass(mService, CallActivity.class); toSend.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - SipCall newCall = new SipCall(args); - newCall.setTimestampStart_(System.currentTimeMillis()); + + CallContact unknown = CallContact.ContactBuilder.buildUnknownContact(from); + + SipCall newCall = new SipCall(callID, accountID, from, SipCall.Direction.INCOMING); + newCall.setContact(unknown); + newCall.setCallState(SipCall.State.RINGING); + newCall.setTimestampStart(System.currentTimeMillis()); Conference toAdd; if (acc.useSecureLayer()) { - SecureSipCall secureCall = new SecureSipCall(newCall); + SecureSipCall secureCall = new SecureSipCall(newCall, acc.getSrtpDetails().getDetailString(AccountDetailSrtp.CONFIG_SRTP_KEY_EXCHANGE)); toAdd = new Conference(secureCall); } else { toAdd = new Conference(newCall); @@ -132,6 +148,22 @@ public class CallManagerCallBack extends Callback { mService.getConferences().put(toAdd.getId(), toAdd); + NotificationCompat.Builder noti = new NotificationCompat.Builder(mService) + .setContentTitle("Incoming call with " + from) + .setContentText("incoming call") + .setOngoing(true) + .setSmallIcon(R.drawable.ic_launcher) + .addAction(R.drawable.ic_call_end_white_24dp, "End call", + PendingIntent.getService(mService, 4278, + new Intent(mService, SipService.class) + .setAction(SipService.ACTION_CALL_END) + .putExtra("conf", toAdd.getId()), + PendingIntent.FLAG_ONE_SHOT)); + + //mService.startForeground(toAdd.notificationId, noti); + Log.w("CallNotification ", "Adding for incoming " + toAdd.notificationId); + mService.mNotificationManager.notificationManager.notify(toAdd.notificationId, noti.build()); + Bundle bundle = new Bundle(); bundle.putParcelable("conference", toAdd); toSend.putExtra("resuming", false); @@ -175,33 +207,37 @@ public class CallManagerCallBack extends Callback { } @Override - public void incomingMessage(String ID, String from, StringMap messages) { + public void incomingMessage(String id, String from, StringMap messages) { Log.w(TAG, "on_incoming_message:" + messages); String msg = messages.get("text/plain"); if (msg == null) return; - Intent intent = new Intent(INCOMING_TEXT); - intent.putExtra("CallID", ID); - intent.putExtra("From", from); - intent.putExtra("Msg", msg); - - if (mService.getConferences().get(ID) != null) { - mService.getConferences().get(ID).addSipMessage(new SipMessage(true, msg)); - intent.putExtra("conference", mService.getConferences().get(ID)); - } else { - for (Map.Entry<String, Conference> stringConferenceEntry : mService.getConferences().entrySet()) { - Conference tmp = stringConferenceEntry.getValue(); - for (SipCall c : tmp.getParticipants()) { - if (c.getCallId().contentEquals(ID)) { - mService.getConferences().get(tmp.getId()).addSipMessage(new SipMessage(true, msg)); - intent.putExtra("conference", tmp); - } + Conference conf = mService.getConferences().get(id); + if (conf == null) { + for (Conference tmp : mService.getConferences().values()) + if (tmp.getCallById(id) != null) { + conf = tmp; + break; } + if (conf == null) { + Log.w(TAG, "Discarding message for unknown call " + id); + return; } - } + + TextMessage message = new TextMessage(true, msg, from, id, conf.hasMultipleParticipants() ? null : conf.getParticipants().get(0).getAccount()); + if (!conf.hasMultipleParticipants()) + message.setContact(conf.getParticipants().get(0).getContact()); + + conf.addSipMessage(message); + + mService.mHistoryManager.insertNewTextMessage(new HistoryText(message)); + + Intent intent = new Intent(INCOMING_TEXT); + intent.putExtra("txt", message); + intent.putExtra("conference", conf.getId()); mService.sendBroadcast(intent); } @@ -215,10 +251,21 @@ public class CallManagerCallBack extends Callback { for (SipCall call : toReInsert.getParticipants()) { mService.getConferences().put(call.getCallId(), new Conference(call)); } - intent.putExtra("conference", mService.getConferences().get(confID)); + + Conference conf = mService.getConferences().get(confID); + + Log.w("CallNotification ", "Canceling " + conf.notificationId); + //NotificationManager mNotifyMgr = (NotificationManager) mService.getSystemService(Context.NOTIFICATION_SERVICE); + mService.mNotificationManager.notificationManager.cancel(conf.notificationId); + + intent.putExtra("conference", conf); mService.getConferences().remove(confID); mService.sendBroadcast(intent); + if (mService.getConferences().size() == 0) { + mService.stopForeground(true); + } + } @Override diff --git a/ring-android/app/src/main/java/cx/ring/service/ConfigurationManagerCallback.java b/ring-android/app/src/main/java/cx/ring/service/ConfigurationManagerCallback.java index ceb49699c..e852a0d94 100644 --- a/ring-android/app/src/main/java/cx/ring/service/ConfigurationManagerCallback.java +++ b/ring-android/app/src/main/java/cx/ring/service/ConfigurationManagerCallback.java @@ -26,13 +26,17 @@ import android.content.Context; import android.content.Intent; import android.util.Log; +import cx.ring.history.HistoryText; +import cx.ring.model.TextMessage; + public class ConfigurationManagerCallback extends ConfigurationCallback { private SipService mService; private static final String TAG = "ConfigurationManagerCb"; static public final String ACCOUNTS_CHANGED = "accounts-changed"; - static public final String ACCOUNT_STATE_CHANGED = "account-state-changed"; + static public final String ACCOUNT_STATE_CHANGED = "account-State-changed"; + static public final String INCOMING_TEXT = "incoming--txt-msg"; public ConfigurationManagerCallback(SipService context) { super(); @@ -58,7 +62,19 @@ public class ConfigurationManagerCallback extends ConfigurationCallback { @Override public void registrationStateChanged(String account_id, String state, int code, String detail_str) { - sendAccountStateChangedMessage(account_id, state, 0); + Log.w(getClass().getName(), "registrationStateChanged: " + account_id + " " + state + " " + code + " " + detail_str); + sendAccountStateChangedMessage(account_id, state, code); + } + + @Override + public void incomingAccountMessage(String accountID, String from, String msg) { + Log.w(TAG, "incomingAccountMessage : " + accountID + " " + from + " " + msg); + + TextMessage message = new TextMessage(true, msg, from, null, accountID); + mService.mHistoryManager.insertNewTextMessage(new HistoryText(message)); + Intent intent = new Intent(INCOMING_TEXT); + intent.putExtra("txt", message); + mService.sendBroadcast(intent); } @Override @@ -69,7 +85,7 @@ public class ConfigurationManagerCallback extends ConfigurationCallback { private void sendAccountStateChangedMessage(String accoundID, String state, int code) { Intent intent = new Intent(ACCOUNT_STATE_CHANGED); intent.putExtra("Account", accoundID); - intent.putExtra("state", state); + intent.putExtra("State", state); intent.putExtra("code", code); mService.sendBroadcast(intent); } @@ -79,6 +95,7 @@ public class ConfigurationManagerCallback extends ConfigurationCallback { OpenSlParams audioParams = OpenSlParams.createInstance(mService); ret.add(audioParams.getSampleRate()); ret.add(audioParams.getBufferSize()); + Log.w(getClass().getName(), "getHardwareAudioFormat: " + audioParams.getSampleRate() + " " + audioParams.getBufferSize()); } @Override diff --git a/ring-android/app/src/main/java/cx/ring/service/ISipService.aidl b/ring-android/app/src/main/java/cx/ring/service/ISipService.aidl index 4708062d2..5281c5e7f 100644 --- a/ring-android/app/src/main/java/cx/ring/service/ISipService.aidl +++ b/ring-android/app/src/main/java/cx/ring/service/ISipService.aidl @@ -1,8 +1,8 @@ package cx.ring.service; import cx.ring.model.SipCall; +import cx.ring.model.TextMessage; import cx.ring.model.Conference; -import cx.ring.model.SipMessage; interface ISipService { @@ -57,7 +57,7 @@ interface ISipService { void playDtmf(in String key); /* IM */ - void sendTextMessage(in String callID, in SipMessage message); + void sendTextMessage(in String callID, in TextMessage message); void sendAccountTextMessage(in String accountid, in String to, in String msg); diff --git a/ring-android/app/src/main/java/cx/ring/service/LocalService.java b/ring-android/app/src/main/java/cx/ring/service/LocalService.java new file mode 100644 index 000000000..02c22f2a6 --- /dev/null +++ b/ring-android/app/src/main/java/cx/ring/service/LocalService.java @@ -0,0 +1,813 @@ +package cx.ring.service; + +import android.app.Service; +import android.content.AsyncTaskLoader; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.Loader; +import android.content.ServiceConnection; +import android.database.Cursor; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Binder; +import android.os.IBinder; +import android.os.RemoteException; +import android.provider.ContactsContract; +import android.support.annotation.NonNull; +import android.util.Log; +import android.util.LongSparseArray; +import android.util.Pair; + +import java.lang.ref.WeakReference; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import cx.ring.BuildConfig; +import cx.ring.history.HistoryCall; +import cx.ring.history.HistoryEntry; +import cx.ring.history.HistoryManager; +import cx.ring.history.HistoryText; +import cx.ring.model.CallContact; +import cx.ring.model.Conference; +import cx.ring.model.Conversation; +import cx.ring.model.SipCall; +import cx.ring.model.TextMessage; +import cx.ring.model.account.Account; +import cx.ring.utils.Utilities; + + +public class LocalService extends Service { + static final String TAG = LocalService.class.getSimpleName(); + static public final String ACTION_CONF_UPDATE = BuildConfig.APPLICATION_ID + ".action.CONF_UPDATE"; + static public final String ACTION_ACCOUNT_UPDATE = BuildConfig.APPLICATION_ID + ".action.ACCOUNT_UPDATE"; + + public static final String AUTHORITY = "cx.ring"; + public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY); + + private ISipService mService = null; + + // Binder given to clients + private final IBinder mBinder = new LocalBinder(); + + private Map<String, Conversation> conversations = new HashMap<>(); + private ArrayList<Account> all_accounts = new ArrayList<>(); + private List<Account> accounts = all_accounts; + private List<Account> ip2ip_account = all_accounts; + + private HistoryManager historyManager; + + AccountsLoader mAccountLoader = null; + + public interface Callbacks { + ISipService getRemoteService(); + LocalService getService(); + } + public static class DummyCallbacks implements Callbacks { + @Override + public ISipService getRemoteService() { + return null; + } + @Override + public LocalService getService() { + return null; + } + } + public static final Callbacks DUMMY_CALLBACKS = new DummyCallbacks(); + + @Override + public void onCreate() { + Log.e(TAG, "onCreate"); + super.onCreate(); + historyManager = new HistoryManager(this); + Intent intent = new Intent(this, SipService.class); + startService(intent); + bindService(intent, mConnection, BIND_AUTO_CREATE | BIND_IMPORTANT | BIND_ABOVE_CLIENT ); + } + + @Override + public void onDestroy() { + Log.e(TAG, "onDestroy"); + super.onDestroy(); + stopListener(); + } + + private final Loader.OnLoadCompleteListener<ArrayList<Account>> onAccountsLoaded = new Loader.OnLoadCompleteListener<ArrayList<Account>>() { + @Override + public void onLoadComplete(Loader<ArrayList<Account>> loader, ArrayList<Account> data) { + Log.w(TAG, "AccountsLoader Loader.OnLoadCompleteListener"); + all_accounts = data; + accounts = all_accounts.subList(0,data.size()-1); + ip2ip_account = all_accounts.subList(data.size()-1,data.size()); + sendBroadcast(new Intent(ACTION_ACCOUNT_UPDATE)); + } + }; + + private ServiceConnection mConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName className, IBinder service) { + Log.w(TAG, "onServiceConnected " + className.getClassName()); + mService = ISipService.Stub.asInterface(service); + //mBound = true; + mAccountLoader = new AccountsLoader(LocalService.this); + mAccountLoader.registerListener(1, onAccountsLoaded); + mAccountLoader.startLoading(); + mAccountLoader.forceLoad(); + startListener(); + } + + @Override + public void onServiceDisconnected(ComponentName arg0) { + Log.w(TAG, "onServiceDisconnected " + arg0.getClassName()); + if (mAccountLoader != null) { + mAccountLoader.unregisterListener(onAccountsLoaded); + mAccountLoader.cancelLoad(); + mAccountLoader.stopLoading(); + } + //mBound = false; + mService = null; + } + }; + + /** + * Class used for the client Binder. Because we know this service always + * runs in the same process as its clients, we don't need to deal with IPC. + */ + public class LocalBinder extends Binder { + public LocalService getService() { + // Return this instance of LocalService so clients can call public methods + return LocalService.this; + } + } + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + @Override + public boolean onUnbind(Intent intent) { + Log.e(TAG, "onUnbind"); + if (mConnection != null) { + unbindService(mConnection); + mConnection = null; + } + return super.onUnbind(intent); + } + + public ISipService getRemoteService() { + return mService; + } + + public List<Account> getAccounts() { return accounts; } + public List<Account> getIP2IPAccount() { return ip2ip_account; } + public Account getAccount(String account_id) { + for (Account acc : all_accounts) + if (acc.getAccountID().equals(account_id)) + return acc; + return null; + } + + public ArrayList<Conversation> getConversations() { + ArrayList<Conversation> convs = new ArrayList<>(conversations.values()); + Collections.sort(convs, new Comparator<Conversation>() { + @Override + public int compare(Conversation lhs, Conversation rhs) { + return (int) ((rhs.getLastInteraction().getTime() - lhs.getLastInteraction().getTime())/1000l); + } + }); + return convs; + } + + public Conversation getConversation(String id) { + return conversations.get(id); + } + + public Conference getConference(String id) { + for (Conversation conv : conversations.values()) { + Conference conf = conv.getConference(id); + if (conf != null) + return conf; + } + return null; + } + + public Conversation getByContact(CallContact contact) { + ArrayList<String> keys = contact.getIds(); + for (String k : keys) { + Conversation c = conversations.get(k); + if (c != null) + return c; + } + Log.w(TAG, "getByContact failed"); + return null; + } + public Conversation getByCallId(String callId) { + for (Conversation conv : conversations.values()) { + Conference conf = conv.getConference(callId); + if (conf != null) + return conv; + } + return null; + } + + public Conversation startConversation(CallContact contact) { + Conversation c = getByContact(contact); + if (c == null) { + c = new Conversation(contact); + conversations.put(contact.getIds().get(0), c); + } + return c; + } + + public CallContact findContactByNumber(String number) { + for (Conversation conv : conversations.values()) { + if (conv.contact.hasNumber(number)) + return conv.contact; + } + return findContactByNumber(getContentResolver(), number); + } + + public CallContact findContactById(long id) { + return findById(getContentResolver(), id); + } + + public Account guessAccount(CallContact c, String number) { + number = CallContact.canonicalNumber(number); + if (Utilities.isIpAddress(number)) + return ip2ip_account.get(0); + /*Conversation conv = getByContact(c); + if (conv != null) { + return + }*/ + return accounts.get(0); + } + + public static final String[] DATA_PROJECTION = { + ContactsContract.Data._ID, + ContactsContract.RawContacts.CONTACT_ID, + ContactsContract.Data.LOOKUP_KEY, + ContactsContract.Data.DISPLAY_NAME_PRIMARY, + ContactsContract.Data.PHOTO_ID, + ContactsContract.Data.PHOTO_THUMBNAIL_URI + }; + public static final String[] CONTACT_PROJECTION = { + ContactsContract.Data._ID, + ContactsContract.Data.LOOKUP_KEY, + ContactsContract.Data.DISPLAY_NAME_PRIMARY, + ContactsContract.Data.PHOTO_ID, + }; + + public static final String[] PHONELOOKUP_PROJECTION = { + ContactsContract.PhoneLookup._ID, + ContactsContract.PhoneLookup.LOOKUP_KEY, + ContactsContract.PhoneLookup.PHOTO_ID, + ContactsContract.Contacts.DISPLAY_NAME_PRIMARY + }; + + private static final String[] CONTACTS_PHONES_PROJECTION = { + ContactsContract.CommonDataKinds.Phone.NUMBER, + ContactsContract.CommonDataKinds.Phone.TYPE + }; + private static final String[] CONTACTS_SIP_PROJECTION = { + ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS, + ContactsContract.CommonDataKinds.SipAddress.TYPE + }; + /* private static final String[] CONTACTS_PHOTO_PROJECTION = { + ContactsContract.CommonDataKinds.Photo., + ContactsContract.CommonDataKinds.SipAddress.TYPE + }; +*/ + private static final String ID_SELECTION = ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "=?"; + private static final String KEY_SELECTION = ContactsContract.Contacts.LOOKUP_KEY + "=?"; + + private static void lookupDetails(@NonNull ContentResolver res, @NonNull CallContact c) { + Log.w(TAG, "lookupDetails " + c.getKey()); + + Cursor cPhones = res.query( + ContactsContract.CommonDataKinds.Phone.CONTENT_URI, + CONTACTS_PHONES_PROJECTION, + ID_SELECTION, + new String[]{String.valueOf(c.getId())}, null); + if (cPhones != null) { + final int iNum = cPhones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER); + final int iType = cPhones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.TYPE); + while (cPhones.moveToNext()) { + c.addNumber(cPhones.getString(iNum), cPhones.getInt(iType), CallContact.NumberType.TEL); + Log.w(TAG,"Phone:"+cPhones.getString(cPhones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER))); + } + cPhones.close(); + } + + Uri baseUri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, c.getId()); + Uri targetUri = Uri.withAppendedPath(baseUri, ContactsContract.Contacts.Data.CONTENT_DIRECTORY); + Cursor cSip = res.query(targetUri, + CONTACTS_SIP_PROJECTION, + ContactsContract.Data.MIMETYPE + "=?", + new String[]{ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE}, null); + if (cSip != null) { + final int iSip = cSip.getColumnIndex(ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS); + final int iType = cSip.getColumnIndex(ContactsContract.CommonDataKinds.SipAddress.TYPE); + while (cSip.moveToNext()) { + c.addNumber(cSip.getString(iSip), cSip.getInt(iType), CallContact.NumberType.SIP); + Log.w(TAG, "SIP phone:" + cSip.getString(iSip)); + } + cSip.close(); + } + + /*Cursor cPhoto = res.query(targetUri, + CONTACTS_SIP_PROJECTION, + ContactsContract.Data.MIMETYPE + "=?", + new String[]{ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE}, null); + if (cSip != null) { + final int iSip = cSip.getColumnIndex(ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS); + final int iType = cSip.getColumnIndex(ContactsContract.CommonDataKinds.SipAddress.TYPE); + while (cSip.moveToNext()) { + c.addNumber(cSip.getString(iSip), cSip.getInt(iType), CallContact.NumberType.SIP); + Log.w(TAG, "SIP phone:" + cSip.getString(iSip)); + } + cSip.close(); + }*/ + } + + public static CallContact findByKey(@NonNull ContentResolver res, String key) { + Log.e(TAG, "findByKey " + key); + + final CallContact.ContactBuilder builder = CallContact.ContactBuilder.getInstance(); + Cursor result = res.query( Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_LOOKUP_URI, key), CONTACT_PROJECTION, + null, null, null); + + CallContact contact = null; + if (result != null && result.moveToFirst()) { + int iID = result.getColumnIndex(ContactsContract.Data._ID); + int iKey = result.getColumnIndex(ContactsContract.Data.LOOKUP_KEY); + int iName = result.getColumnIndex(ContactsContract.Data.DISPLAY_NAME); + int iPhoto = result.getColumnIndex(ContactsContract.Data.PHOTO_ID); + long cid = result.getLong(iID); + + Log.w(TAG, "Contact id:" + cid + " key:" + result.getString(iKey)); + + builder.startNewContact(cid, result.getString(iKey), result.getString(iName), result.getLong(iPhoto)); + result.close(); + + contact = builder.build(); + lookupDetails(res, contact); + } + return contact; + } + + public static CallContact findById(@NonNull ContentResolver res, long id) { + Log.e(TAG, "findById " + id); + + final CallContact.ContactBuilder builder = CallContact.ContactBuilder.getInstance(); + Cursor result = res.query(ContactsContract.Contacts.CONTENT_URI, CONTACT_PROJECTION, + ContactsContract.Contacts._ID + " = ?", + new String[]{String.valueOf(id)}, null); + if (result == null) + return null; + + CallContact contact = null; + if (result.moveToFirst()) { + int iID = result.getColumnIndex(ContactsContract.Data._ID); + int iKey = result.getColumnIndex(ContactsContract.Data.LOOKUP_KEY); + int iName = result.getColumnIndex(ContactsContract.Data.DISPLAY_NAME); + int iPhoto = result.getColumnIndex(ContactsContract.Data.PHOTO_ID); + long cid = result.getLong(iID); + + Log.w(TAG, "Contact id:" + cid + " key:" + result.getString(iKey)); + + builder.startNewContact(cid, result.getString(iKey), result.getString(iName), result.getLong(iPhoto)); + contact = builder.build(); + lookupDetails(res, contact); + } + result.close(); + return contact; + } + /* + public static int getRawContactId(@NonNull ContentResolver res, long contactId) + { + Cursor c= res.query( + ContactsContract.RawContacts.CONTENT_URI, + new String[]{ContactsContract.RawContacts._ID}, + ContactsContract.RawContacts.CONTACT_ID+"=?", + new String[]{String.valueOf(contactId)}, + null); + if (c == null) + return -1; + if (c.moveToFirst()) { + int rawContactId = c.getInt(c.getColumnIndex(ContactsContract.RawContacts._ID)); + Log.d(TAG, "Contact Id: " + contactId + " Raw Contact Id: " + rawContactId); + return rawContactId; + } + c.close(); + return -1; + } +*/ + @NonNull + public static CallContact findContactBySipNumber(@NonNull ContentResolver res, String number) { + final CallContact.ContactBuilder builder = CallContact.ContactBuilder.getInstance(); + Cursor result = res.query(ContactsContract.Data.CONTENT_URI, + DATA_PROJECTION, + ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS + "=?" + " AND " + ContactsContract.Data.MIMETYPE + "=?", + new String[]{number, ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE}, null); + if (result == null) { + Log.w(TAG, "findContactBySipNumber " + number + " can't find contact."); + return CallContact.ContactBuilder.buildUnknownContact(number); + } + int iID = result.getColumnIndex(ContactsContract.Data._ID); + int icID = result.getColumnIndex(ContactsContract.RawContacts.CONTACT_ID); + int iKey = result.getColumnIndex(ContactsContract.Data.LOOKUP_KEY); + int iName = result.getColumnIndex(ContactsContract.Data.DISPLAY_NAME); + int iPhoto = result.getColumnIndex(ContactsContract.Data.PHOTO_ID); + int iPhotoThumb = result.getColumnIndex(ContactsContract.Data.PHOTO_THUMBNAIL_URI); + + ArrayList<CallContact> contacts = new ArrayList<>(1); + while (result.moveToNext()) { + long cid = result.getLong(icID); + long id = result.getLong(iID); + builder.startNewContact(cid, result.getString(iKey), result.getString(iName), result.getLong(iPhoto)); + CallContact contact = builder.build(); + //Log.w(TAG, "findContactBySipNumber " + number + " found name:" + contact.getDisplayName() + " id:" + contact.getId() + " key:" + contact.getKey() + " rawid:"+getRawContactId(res, contact.getId()) + " rid:"+id + " photo:"+result.getLong(iPhoto) + " thumb:" + result.getString(iPhotoThumb)); + lookupDetails(res, contact); + contacts.add(contact); + } + result.close(); + + //lookupDetails(res, contact); + /*if (contact == null) { + Log.w(TAG, "Can't find contact with number " + number); + contact = CallContact.ContactBuilder.buildUnknownContact(number); + }*/ + if (contacts.isEmpty()) { + Log.w(TAG, "findContactBySipNumber " + number + " can't find contact."); + return CallContact.ContactBuilder.buildUnknownContact(number); + } + return contacts.get(0); + } + + @NonNull + public static CallContact findContactByNumber(@NonNull ContentResolver res, String number) { + //Log.w(TAG, "findContactByNumber " + number); + + final CallContact.ContactBuilder builder = CallContact.ContactBuilder.getInstance(); + Uri uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)); + Cursor result = res.query(uri, PHONELOOKUP_PROJECTION, null, null, null); + if (result == null) { + Log.w(TAG, "findContactByNumber " + number + " can't find contact."); + return findContactBySipNumber(res, number); + } + if (!result.moveToFirst()) { + result.close(); + Log.w(TAG, "findContactByNumber " + number + " can't find contact."); + return findContactBySipNumber(res, number); + } + int iID = result.getColumnIndex(ContactsContract.Contacts._ID); + int iKey = result.getColumnIndex(ContactsContract.Data.LOOKUP_KEY); + int iName = result.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME); + int iPhoto = result.getColumnIndex(ContactsContract.Contacts.PHOTO_ID); + builder.startNewContact(result.getLong(iID), result.getString(iKey), result.getString(iName), result.getLong(iPhoto)); + result.close(); + CallContact contact = builder.build(); + lookupDetails(res, contact); + /*if (contact == null) { + Log.w(TAG, "Can't find contact with number " + number); + contact = CallContact.ContactBuilder.buildUnknownContact(number); + }*/ + Log.w(TAG, "findContactByNumber " + number + " found " + contact.getDisplayName()); + + return contact; + } + + private class ConversationLoader extends AsyncTask<Void, Void, Map<String, Conversation>> { + private final ContentResolver cr; + + public ConversationLoader(Context c) { + cr = c.getContentResolver(); + } + + private CallContact getByNumber(HashMap<String, CallContact> cache, String number) { + if (number == null || number.isEmpty()) + return null; + number = CallContact.canonicalNumber(number); + CallContact c = cache.get(number); + if (c == null) { + c = findContactByNumber(cr, number); + //if (c != null) + cache.put(number, c); + } + return c; + } + + Pair<HistoryEntry, HistoryCall> findHistoryByCallId(final Map<String, Conversation> confs, String id) { + for (Conversation c : confs.values()) { + Pair<HistoryEntry, HistoryCall> h = c.findHistoryByCallId(id); + if (h != null) + return h; + } + return null; + } + + @Override + protected Map<String, Conversation> doInBackground(Void... params) { + List<HistoryCall> history = null; + List<HistoryText> historyTexts = null; + Map<String, Conference> confs = null; + final Map<String, Conversation> ret = new HashMap<>(); + final LongSparseArray<CallContact> localContactCache = new LongSparseArray<>(64); + final HashMap<String, CallContact> localNumberCache = new HashMap<>(64); + + + try { + history = historyManager.getAll(); + historyTexts = historyManager.getAllTextMessages(); + confs = mService.getConferenceList(); + } catch (RemoteException | SQLException e) { + e.printStackTrace(); + } + + for (HistoryCall call : history) { + //Log.w(TAG, "History call : " + call.getNumber() + " " + call.call_start + " " + call.call_end + " " + call.getEndDate().toString()); + CallContact contact; + if (call.getContactID() <= CallContact.DEFAULT_ID) { + contact = getByNumber(localNumberCache, call.getNumber()); + } else { + contact = localContactCache.get(call.getContactID()); + if (contact == null) { + contact = findById(cr, call.getContactID()); + if (contact != null) + contact.addPhoneNumber(call.getNumber(), 0); + else { + Log.w(TAG, "Can't find contact with id " + call.getContactID()); + contact = getByNumber(localNumberCache, call.getNumber()); + } + localContactCache.put(contact.getId(), contact); + } + } + + Map.Entry<String, Conversation> merge = null; + for (Map.Entry<String, Conversation> ce : ret.entrySet()) { + Conversation c = ce.getValue(); + if ((contact.getId() > 0 && contact.getId() == c.contact.getId()) || c.contact.hasNumber(call.getNumber())) { + merge = ce; + break; + } + } + if (merge != null) { + Conversation c = merge.getValue(); + //Log.w(TAG, " Join to " + merge.getKey() + " " + c.getContact().getDisplayName() + " " + call.getNumber()); + if (c.getContact().getId() <= 0 && contact.getId() > 0) { + c.contact = contact; + ret.remove(merge.getKey()); + ret.put(contact.getIds().get(0), c); + } + c.addHistoryCall(call); + continue; + } + String key = contact.getIds().get(0); + if (ret.containsKey(key)) { + ret.get(key).addHistoryCall(call); + } else { + Conversation c = new Conversation(contact); + c.addHistoryCall(call); + ret.put(key, c); + } + } + + for (HistoryText htext : historyTexts) { + CallContact contact; + + if (htext.getContactID() <= CallContact.DEFAULT_ID) { + contact = getByNumber(localNumberCache, htext.getNumber()); + } else { + contact = localContactCache.get(htext.getContactID()); + if (contact == null) { + contact = findById(cr, htext.getContactID()); + if (contact != null) + contact.addPhoneNumber(htext.getNumber(), 0); + else { + Log.w(TAG, "Can't find contact with id " + htext.getContactID()); + contact = getByNumber(localNumberCache, htext.getNumber()); + } + localContactCache.put(contact.getId(), contact); + } + } + + Pair<HistoryEntry, HistoryCall> p = findHistoryByCallId(ret, htext.getCallId()); + + if (contact == null && p != null) + contact = p.first.getContact(); + if (contact == null) + continue; + + TextMessage msg = new TextMessage(htext); + msg.setContact(contact); + + if (p != null) { + if (msg.getNumber() == null || msg.getNumber().isEmpty()) + msg.setNumber(p.second.getNumber()); + p.first.addTextMessage(msg); + } + + String key = contact.getIds().get(0); + if (ret.containsKey(key)) { + ret.get(key).addTextMessage(msg); + } else { + Conversation c = new Conversation(contact); + c.addTextMessage(msg); + ret.put(key, c); + } + } + + /*context.clear(); + ctx = null;*/ + for (Map.Entry<String, Conference> c : confs.entrySet()) { + //Log.w(TAG, "ConversationLoader handling " + c.getKey() + " " + c.getValue().getId()); + Conference conf = c.getValue(); + ArrayList<SipCall> calls = conf.getParticipants(); + if (calls.size() >= 1) { + CallContact contact = calls.get(0).getContact(); + //Log.w(TAG, "Contact : " + contact.getId() + " " + contact.getDisplayName()); + Conversation conv = null; + ArrayList<String> ids = contact.getIds(); + for (String id : ids) { + //Log.w(TAG, " uri attempt : " + id); + conv = ret.get(id); + if (conv != null) break; + } + if (conv != null) { + //Log.w(TAG, "Adding conference to existing conversation "); + conv.current_calls.add(conf); + } else { + conv = new Conversation(contact); + conv.current_calls.add(conf); + ret.put(ids.get(0), conv); + } + } + } + for (Conversation c : ret.values()) + Log.w(TAG, "Conversation : " + c.getContact().getId() + " " + c.getContact().getDisplayName() + " " + c.getContact().getPhones().get(0).getNumber() + " " + c.getLastInteraction().toString()); + return ret; + } + } + + private void updated(Map<String, Conversation> res) { + Log.w(TAG, "Conversation list updated"); + conversations = res; + sendBroadcast(new Intent(ACTION_CONF_UPDATE)); + } + + public class AccountsLoader extends AsyncTaskLoader<ArrayList<Account>> { + public static final String ACCOUNTS = "accounts"; + public static final String ACCOUNT_IP2IP = "IP2IP"; + public AccountsLoader(Context context) { + super(context); + Log.w(TAG, "AccountsLoader constructor"); + } + @SuppressWarnings("unchecked") + @Override + public ArrayList<Account> loadInBackground() { + Log.w(TAG, "AccountsLoader loadInBackground"); + ArrayList<Account> accounts = new ArrayList<>(); + Account IP2IP = null; + try { + ArrayList<String> accountIDs = (ArrayList<String>) mService.getAccountList(); + Map<String, String> details; + ArrayList<Map<String, String>> credentials; + Map<String, String> state; + for (String id : accountIDs) { + details = (Map<String, String>) mService.getAccountDetails(id); + state = (Map<String, String>) mService.getVolatileAccountDetails(id); + if (id.contentEquals(ACCOUNT_IP2IP)) { + IP2IP = new Account(ACCOUNT_IP2IP, details, new ArrayList<Map<String, String>>(), state); // Empty credentials + //accounts.add(IP2IP); + continue; + } + credentials = (ArrayList<Map<String, String>>) mService.getCredentials(id); + /*for (Map.Entry<String, String> entry : State.entrySet()) { + Log.i(TAG, "State:" + entry.getKey() + " -> " + entry.getValue()); + }*/ + Account tmp = new Account(id, details, credentials, state); + accounts.add(tmp); + // Log.i(TAG, "account:" + tmp.getAlias() + " " + tmp.isEnabled()); + + } + } catch (RemoteException | NullPointerException e) { + Log.e(TAG, e.toString()); + } + accounts.add(IP2IP); + return accounts; + } + } + + final BroadcastReceiver receiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + switch(intent.getAction()) { + case ConnectivityManager.CONNECTIVITY_ACTION: + Log.w(TAG, "ConnectivityManager.CONNECTIVITY_ACTION " + " " + intent.getStringExtra(ConnectivityManager.EXTRA_EXTRA_INFO) + " " + intent.getStringExtra(ConnectivityManager.EXTRA_EXTRA_INFO)); + ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo ni = cm.getActiveNetworkInfo(); + Log.w(TAG, "ActiveNetworkInfo: " + (ni == null ? "null" : ni.toString())); + break; + case ConfigurationManagerCallback.ACCOUNT_STATE_CHANGED: + Log.w(TAG, "Received " + intent.getAction() + " " + intent.getStringExtra("Account") + " " + intent.getStringExtra("State") + " " + intent.getIntExtra("code", 0)); + //accountStateChanged(intent.getStringExtra("Account"), intent.getStringExtra("State"), intent.getIntExtra("code", 0)); + for (Account a : accounts) { + if (a.getAccountID().contentEquals(intent.getStringExtra("Account"))) { + a.setRegistrationState(intent.getStringExtra("State"), intent.getIntExtra("code", 0)); + //notifyDataSetChanged(); + sendBroadcast(new Intent(ACTION_ACCOUNT_UPDATE)); + break; + } + } + break; + case ConfigurationManagerCallback.ACCOUNTS_CHANGED: + Log.w(TAG, "Received" + intent.getAction()); + //accountsChanged(); + mAccountLoader.onContentChanged(); + mAccountLoader.startLoading(); + break; + case CallManagerCallBack.INCOMING_TEXT: + case ConfigurationManagerCallback.INCOMING_TEXT: + TextMessage txt = intent.getParcelableExtra("txt"); + String call = txt.getCallId(); + if (call != null && !call.isEmpty()) { + Conversation conv = getByCallId(call); + conv.addTextMessage(txt); + /*Conference conf = conv.getConference(call); + conf.addSipMessage(txt); + Conversation conv = getByContact(conf.)*/ + } else { + CallContact contact = findContactByNumber(txt.getNumber()); + Conversation conv = startConversation(contact); + txt.setContact(conv.getContact()); + Log.w(TAG, "New text messsage " + txt.getAccount() + " " + txt.getContact().getId() + " " + txt.getMessage()); + conv.addTextMessage(txt); + } + sendBroadcast(new Intent(ACTION_CONF_UPDATE)); + break; + default: + Log.w(TAG, "onReceive " + intent.getAction() + " " + intent.getDataString()); + new ConversationLoader(context){ + @Override + protected void onPostExecute(Map<String, Conversation> res) { + updated(res); + } + }.execute(); + } + } + }; + + public void startListener() { + final WeakReference<LocalService> self = new WeakReference<>(this); + new ConversationLoader(this){ + @Override + protected void onPreExecute() { + super.onPreExecute(); + Log.w(TAG, "onPreExecute"); + } + + @Override + protected void onPostExecute(Map<String, Conversation> res) { + Log.w(TAG, "onPostExecute"); + LocalService this_ = self.get(); + if (this_ != null) + this_.updated(res); + else + Log.e(TAG, "AsyncTask finished but parent is destroyed.."); + } + }.execute(); + + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(ConfigurationManagerCallback.ACCOUNT_STATE_CHANGED); + intentFilter.addAction(ConfigurationManagerCallback.ACCOUNTS_CHANGED); + intentFilter.addAction(ConfigurationManagerCallback.INCOMING_TEXT); + + intentFilter.addAction(CallManagerCallBack.INCOMING_CALL); + intentFilter.addAction(CallManagerCallBack.INCOMING_TEXT); + intentFilter.addAction(CallManagerCallBack.CALL_STATE_CHANGED); + intentFilter.addAction(CallManagerCallBack.CONF_CREATED); + intentFilter.addAction(CallManagerCallBack.CONF_CHANGED); + intentFilter.addAction(CallManagerCallBack.CONF_REMOVED); + + intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); + + registerReceiver(receiver, intentFilter); + } + + public void stopListener() { + unregisterReceiver(receiver); + } + +} diff --git a/ring-android/app/src/main/java/cx/ring/service/SipService.java b/ring-android/app/src/main/java/cx/ring/service/SipService.java index 8f9caae9d..1e3ea96f5 100644 --- a/ring-android/app/src/main/java/cx/ring/service/SipService.java +++ b/ring-android/app/src/main/java/cx/ring/service/SipService.java @@ -24,6 +24,7 @@ */ package cx.ring.service; +import android.app.Notification; import android.os.Handler; import java.util.ArrayList; @@ -36,12 +37,21 @@ import java.util.Map.Entry; import android.app.Service; import android.content.Intent; import android.os.*; +import android.telecom.Call; import android.util.Log; + +import cx.ring.BuildConfig; +import cx.ring.R; import cx.ring.history.HistoryManager; +import cx.ring.history.HistoryText; import cx.ring.model.Codec; import cx.ring.model.Conference; import cx.ring.model.SecureSipCall; -import cx.ring.model.SipMessage; +import cx.ring.model.TextMessage; +import cx.ring.model.account.AccountDetail; +import cx.ring.model.account.AccountDetailBasic; +import cx.ring.model.account.AccountDetailSrtp; +import cx.ring.model.account.AccountDetailTls; import cx.ring.utils.MediaManager; import cx.ring.utils.SipNotifications; import cx.ring.utils.SwigNativeConverter; @@ -54,8 +64,14 @@ public class SipService extends Service { private SipServiceExecutor mExecutor; private static HandlerThread executorThread; + static public final String ACTION_CALL_ACCEPT = BuildConfig.APPLICATION_ID + ".action.CALL_ACCEPT"; + static public final String ACTION_CALL_REFUSE = BuildConfig.APPLICATION_ID + ".action.CALL_REFUSE"; + //static public final String ACTION_CALL_REFUSE = BuildConfig.APPLICATION_ID + ".action.CALL_REFUSE"; + + static public final String ACTION_CALL_END = BuildConfig.APPLICATION_ID + ".action.CALL_END"; + private Handler handler = new Handler(); - private static int POLLING_TIMEOUT = 500; + private static int POLLING_TIMEOUT = 50; private Runnable pollEvents = new Runnable() { @Override public void run() { @@ -74,7 +90,7 @@ public class SipService extends Service { protected HistoryManager mHistoryManager; protected MediaManager mMediaManager; - private HashMap<String, Conference> mConferences = new HashMap<>(); + private final HashMap<String, Conference> mConferences = new HashMap<>(); private ConfigurationManagerCallback configurationCallback; private CallManagerCallBack callManagerCallBack; @@ -141,8 +157,36 @@ public class SipService extends Service { /* called for each startService() */ @Override public int onStartCommand(Intent intent, int flags, int startId) { - Log.i(TAG, "onStarted " + (intent == null ? "null" : intent.getAction()) + " " + flags); - super.onStartCommand(intent, flags, startId); + Log.i(TAG, "onStartCommand " + (intent == null ? "null" : intent.getAction()) + " " + flags + " " + startId); + String action = intent == null ? null : intent.getAction(); + try { + if (action != null) { + if (action.equals(ACTION_CALL_END)) { + Conference c = findConference(intent.getStringExtra("conf")); + if (c != null) { + for (SipCall call : c.getParticipants()) { + mBinder.hangUp(call.getCallId()); + } + mBinder.hangUpConference(c.getId()); + Log.w("CallNotification ", "Canceling " + c.notificationId); + mNotificationManager.notificationManager.cancel(c.notificationId); + } + } else if (action.equals(ACTION_CALL_ACCEPT)) { + Conference c = findConference(intent.getStringExtra("conf")); + if (c != null) { + mBinder.accept(c.getParticipants().get(0).getCallId()); + } + } else if (action.equals(ACTION_CALL_REFUSE)) { + Conference c = findConference(intent.getStringExtra("conf")); + if (c != null) { + mBinder.refuse(c.getParticipants().get(0).getCallId()); + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return START_STICKY; /* started and stopped explicitly */ } @@ -265,6 +309,8 @@ public class SipService extends Service { public void run() { try { mTask.run(); + } catch(Exception e){ + e.printStackTrace(); } finally { synchronized (this) { mDone = true; @@ -419,27 +465,59 @@ public class SipService extends Service { protected String doRun() throws SameThreadException { Log.i(TAG, "SipService.placeCall() thread running..."); Conference toAdd; - if(call.getAccount().useSecureLayer()){ - SecureSipCall secureCall = new SecureSipCall(call); - toAdd = new Conference(secureCall); - } else { - toAdd = new Conference(call); - } - mConferences.put(toAdd.getId(), toAdd); + //mConferences.put(toAdd.getId(), toAdd); mMediaManager.obtainAudioFocus(false); - return Ringservice.placeCall(call.getAccount().getAccountID(), call.getmContact().getPhones().get(0).getNumber()); + + String number = call.getNumber(); + if ((number == null || number.isEmpty()) && call.getContact() != null) { + number = call.getContact().getPhones().get(0).getNumber(); + } + + Log.i(TAG, "SipService.placeCall() calling... " + number); + String call_id = Ringservice.placeCall(call.getAccount(), number); + call.setCallID(call_id); + if (!call_id.isEmpty()) { + final Map<String, String> details = getAccountDetails(call.getAccount()); + if(details.get(AccountDetailBasic.CONFIG_ACCOUNT_TYPE).contentEquals(AccountDetailBasic.ACCOUNT_TYPE_RING) + || details.get(AccountDetailSrtp.CONFIG_SRTP_ENABLE).contentEquals(AccountDetail.TRUE_STR) + || details.get(AccountDetailTls.CONFIG_TLS_ENABLE).contentEquals(AccountDetail.TRUE_STR)) { + Log.i(TAG, "SipService.placeCall() call is secure"); + SecureSipCall secureCall = new SecureSipCall(call, details.get(AccountDetailSrtp.CONFIG_SRTP_KEY_EXCHANGE)); + toAdd = new Conference(secureCall); + } else { + toAdd = new Conference(call); + } + Log.i(TAG, "SipService.placeCall() returned with call id " + call_id); + mConferences.put(call_id, toAdd); + Notification noti = new Notification.Builder(SipService.this) + .setContentTitle("Ongoing call with " + call.getContact().getDisplayName()) + .setContentText("outgoing call") + .setOngoing(true) + .setSmallIcon(R.drawable.ic_launcher) + //.setContentIntent() + /*.setContentText(subject) + .setSmallIcon(R.drawable.new_mail) + .setLargeIcon(aBitmap)*/ + .build(); + //startForeground(toAdd.notificationId, noti); + Log.w("CallNotification ", "Adding for outgoing " + toAdd.notificationId); + mNotificationManager.notificationManager.notify(toAdd.notificationId, noti); + } + return call_id; } }); } @Override public void refuse(final String callID) { - + mMediaManager.stopRing(); + Log.e(TAG, "REFUSE"); getExecutor().execute(new SipRunnable() { @Override protected void doRun() throws SameThreadException { Log.i(TAG, "SipService.refuse() thread running..."); Ringservice.refuse(callID); + Ringservice.hangUp(callID); } }); } @@ -447,6 +525,7 @@ public class SipService extends Service { @Override public void accept(final String callID) { mMediaManager.stopRing(); + Log.e(TAG, "ACCEPT"); getExecutor().execute(new SipRunnable() { @Override protected void doRun() throws SameThreadException { @@ -925,28 +1004,38 @@ public class SipService extends Service { } @Override - public void sendTextMessage(final String callID, final SipMessage message) throws RemoteException { + public void sendTextMessage(final String callID, final TextMessage message) throws RemoteException { getExecutor().execute(new SipRunnable() { @Override protected void doRun() throws SameThreadException, RemoteException { Log.i(TAG, "SipService.sendTextMessage() thread running..."); + message.setCallId(callID); + //Conference conf = findConference(callID); + mHistoryManager.insertNewTextMessage(new HistoryText(message)); StringMap messages = new StringMap(); - messages.set("text/plain", message.comment); + messages.set("text/plain", message.getMessage()); Ringservice.sendTextMessage(callID, messages, "", false); if (getConferences().get(callID) != null) getConferences().get(callID).addSipMessage(message); + Intent intent = new Intent(CallManagerCallBack.INCOMING_TEXT); + intent.putExtra("txt", message); + sendBroadcast(intent); } }); - } @Override - public void sendAccountTextMessage(final String accountid, final String to, final String msg) { + public void sendAccountTextMessage(final String accountID, final String to, final String msg) { getExecutor().execute(new SipRunnable() { @Override protected void doRun() throws SameThreadException, RemoteException { - Log.i(TAG, "SipService.sendAccountTextMessage() thread running... " + accountid + " " + to + " " + msg); - Ringservice.sendAccountTextMessage(accountid, to, msg); + Log.i(TAG, "SipService.sendAccountTextMessage() thread running... " + accountID + " " + to + " " + msg); + TextMessage message = new TextMessage(false, msg, to, null, accountID); + mHistoryManager.insertNewTextMessage(new HistoryText(message)); + Ringservice.sendAccountTextMessage(accountID, to, msg); + Intent intent = new Intent(ConfigurationManagerCallback.INCOMING_TEXT); + intent.putExtra("txt", message); + sendBroadcast(intent); } }); } @@ -1271,22 +1360,22 @@ public class SipService extends Service { getConferences().remove(conf.getId()); else conf.removeParticipant(conf.getCallById(callID)); + Log.w("CallNotification ", "Canceling " + conf.notificationId); + mNotificationManager.notificationManager.cancel(conf.notificationId); } protected Conference findConference(String callID) { - Conference result = null; - if (getConferences().get(callID) != null) { - result = getConferences().get(callID); - } else { - for (Entry<String, Conference> stringConferenceEntry : getConferences().entrySet()) { - Conference tmp = stringConferenceEntry.getValue(); - for (SipCall c : tmp.getParticipants()) { - if (c.getCallId().contentEquals(callID)) { - result = tmp; - } + Conference result = getConferences().get(callID); + if (result != null) + return result; + for (Entry<String, Conference> stringConferenceEntry : getConferences().entrySet()) { + Conference tmp = stringConferenceEntry.getValue(); + for (SipCall c : tmp.getParticipants()) { + if (c.getCallId() != null && callID.contentEquals(c.getCallId())) { + return tmp; } } } - return result; + return null; } } diff --git a/ring-android/app/src/main/java/cx/ring/utils/MediaManager.java b/ring-android/app/src/main/java/cx/ring/utils/MediaManager.java index e1abe4e45..c9b59cb16 100644 --- a/ring-android/app/src/main/java/cx/ring/utils/MediaManager.java +++ b/ring-android/app/src/main/java/cx/ring/utils/MediaManager.java @@ -115,7 +115,7 @@ public class MediaManager implements OnAudioFocusChangeListener, BluetoothWrappe } - /** + /**5 * Start ringing announce for a given contact. * It will also focus audio for us. * @param remoteContact the contact to ring for. May resolve the contact ringtone if any. diff --git a/ring-android/app/src/main/java/cx/ring/utils/SipNotifications.java b/ring-android/app/src/main/java/cx/ring/utils/SipNotifications.java index 356552ba7..76fd929da 100644 --- a/ring-android/app/src/main/java/cx/ring/utils/SipNotifications.java +++ b/ring-android/app/src/main/java/cx/ring/utils/SipNotifications.java @@ -41,8 +41,6 @@ import cx.ring.client.HomeActivity; import cx.ring.model.Conference; import cx.ring.model.SipCall; -import android.app.Notification; -import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; @@ -51,6 +49,7 @@ import android.graphics.Typeface; import android.net.sip.SipProfile; import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat.Builder; +import android.support.v4.app.NotificationManagerCompat; import android.text.Spannable; import android.text.SpannableString; import android.text.TextUtils; @@ -58,7 +57,7 @@ import android.text.style.StyleSpan; public class SipNotifications { - private final NotificationManager notificationManager; + public final NotificationManagerCompat notificationManager; private final Context context; public static final String NOTIF_CREATION = "notif_creation"; @@ -76,7 +75,8 @@ public class SipNotifications { public SipNotifications(Context aContext) { context = aContext; - notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + notificationManager = NotificationManagerCompat.from(aContext); + ;//(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); if (!isInit) { cancelAll(); @@ -158,7 +158,7 @@ public class SipNotifications { nb.setTicker(tickerText); nb.setWhen(when); nb.setContentTitle(context.getString(R.string.notif_missed_call_title)); - nb.setContentText(context.getString(R.string.notif_missed_call_content, missedConf.getParticipants().get(0).getmContact().getmDisplayName())); + nb.setContentText(context.getString(R.string.notif_missed_call_content, missedConf.getParticipants().get(0).getContact().getDisplayName())); Intent notificationIntent = new Intent(context, HomeActivity.class); notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); PendingIntent contentIntent = PendingIntent.getActivity(context, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); @@ -168,12 +168,11 @@ public class SipNotifications { nb.setOnlyAlertOnce(true); nb.setContentIntent(contentIntent); - Notification notification = nb.build(); // We have to re-write content view because getNotification setLatestEventInfo implicitly // notification.contentView = contentView; // startForegroundCompat(CALL_NOTIF_ID, notification); - notificationManager.notify(CALL_NOTIF_ID, notification); + notificationManager.notify(CALL_NOTIF_ID, nb.build()); } public void makeNotification(HashMap<String, SipCall> calls) { @@ -183,21 +182,20 @@ public class SipNotifications { Intent notificationIntent = new Intent(context, HomeActivity.class); PendingIntent contentIntent = PendingIntent.getActivity(context, 007, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); - NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - nm.cancel(NOTIFICATION_ID); // clear previous notifications. + //NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + notificationManager.cancel(NOTIFICATION_ID); // clear previous notifications. NotificationCompat.Builder builder = new NotificationCompat.Builder(context); builder.setContentIntent(contentIntent).setOngoing(true).setSmallIcon(R.drawable.ic_launcher) .setContentTitle(calls.size() + " ongoing calls").setTicker("Pending calls").setWhen(System.currentTimeMillis()).setAutoCancel(false); builder.setPriority(NotificationCompat.PRIORITY_MAX); - Notification n = builder.build(); - nm.notify(NOTIFICATION_ID, n); + notificationManager.notify(NOTIFICATION_ID, builder.build()); } public void removeNotification() { - NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - nm.cancel(NOTIFICATION_ID); + //NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + notificationManager.cancel(NOTIFICATION_ID); } } \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/utils/Utilities.java b/ring-android/app/src/main/java/cx/ring/utils/Utilities.java new file mode 100644 index 000000000..1ac13374d --- /dev/null +++ b/ring-android/app/src/main/java/cx/ring/utils/Utilities.java @@ -0,0 +1,35 @@ +package cx.ring.utils; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +/** + * This class provides a variety of basic utility methods that are not + * dependent on any other classes within the org.jamwiki package structure. + */ +public class Utilities { + private static final String ipv4Pattern = "(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])"; + private static final String ipv6Pattern = "([0-9a-f]{1,4}:){7}([0-9a-f]){1,4}"; + private static final Pattern VALID_IPV4_PATTERN = Pattern.compile(ipv4Pattern, Pattern.CASE_INSENSITIVE); + private static final Pattern VALID_IPV6_PATTERN = Pattern.compile(ipv6Pattern, Pattern.CASE_INSENSITIVE); + + /** + * Determine if the given string is a valid IPv4 or IPv6 address. This method + * uses pattern matching to see if the given string could be a valid IP address. + * + * @param ipAddress A string that is to be examined to verify whether or not + * it could be a valid IP address. + * @return <code>true</code> if the string is a value that is a valid IP address, + * <code>false</code> otherwise. + */ + public static boolean isIpAddress(String ipAddress) { + + Matcher m1 = VALID_IPV4_PATTERN.matcher(ipAddress); + if (m1.matches()) { + return true; + } + Matcher m2 = VALID_IPV6_PATTERN.matcher(ipAddress); + return m2.matches(); + } +} diff --git a/ring-android/app/src/main/java/cx/ring/views/CallPaneLayout.java b/ring-android/app/src/main/java/cx/ring/views/CallPaneLayout.java index c9bd68664..cd2afcfbc 100644 --- a/ring-android/app/src/main/java/cx/ring/views/CallPaneLayout.java +++ b/ring-android/app/src/main/java/cx/ring/views/CallPaneLayout.java @@ -60,6 +60,7 @@ public class CallPaneLayout extends SlidingPaneLayout super(context, attrs, defStyle); } + /* @Override public boolean onInterceptTouchEvent(MotionEvent event) { @@ -68,6 +69,6 @@ public class CallPaneLayout extends SlidingPaneLayout } return super.onInterceptTouchEvent(event); - } + }*/ } diff --git a/ring-android/app/src/main/java/cx/ring/views/HalfCircleImageView.java b/ring-android/app/src/main/java/cx/ring/views/HalfCircleImageView.java index e9b0d6acc..46ed0d674 100644 --- a/ring-android/app/src/main/java/cx/ring/views/HalfCircleImageView.java +++ b/ring-android/app/src/main/java/cx/ring/views/HalfCircleImageView.java @@ -76,7 +76,7 @@ public class HalfCircleImageView extends ImageView private void setup() { backgroundPaint = new Paint(); - backgroundPaint.setColor(getResources().getColor(R.color.sfl_dark_blue)); + backgroundPaint.setColor(getResources().getColor(R.color.color_primary_dark)); backgroundPaint.setAntiAlias(true); // init paint paint = new Paint(); diff --git a/ring-android/app/src/main/java/cx/ring/views/NumberPickerPreference.java b/ring-android/app/src/main/java/cx/ring/views/NumberPickerPreference.java index bbf73429c..cc6b156fb 100644 --- a/ring-android/app/src/main/java/cx/ring/views/NumberPickerPreference.java +++ b/ring-android/app/src/main/java/cx/ring/views/NumberPickerPreference.java @@ -92,7 +92,7 @@ public class NumberPickerPreference extends DialogPreference { throw new RuntimeException("mNumberPicker is null!"); } - // Initialize state + // Initialize State mNumberPicker.setWrapSelectorWheel(false); mNumberPicker.setMaxValue(max); mNumberPicker.setMinValue(min); diff --git a/ring-android/app/src/main/java/cx/ring/views/QuadNumberPickerPreference.java b/ring-android/app/src/main/java/cx/ring/views/QuadNumberPickerPreference.java index 49bf9d3c1..3604fd29a 100644 --- a/ring-android/app/src/main/java/cx/ring/views/QuadNumberPickerPreference.java +++ b/ring-android/app/src/main/java/cx/ring/views/QuadNumberPickerPreference.java @@ -127,7 +127,7 @@ public class QuadNumberPickerPreference extends DialogPreference { throw new RuntimeException("mNumberPicker1 or mNumberPicker2 is null!"); } - // Initialize state + // Initialize State mNumberPicker1.setWrapSelectorWheel(false); mNumberPicker1.setMaxValue(mMax1); mNumberPicker1.setMinValue(mMin1); diff --git a/ring-android/app/src/main/java/cx/ring/views/SwipeListViewTouchListener.java b/ring-android/app/src/main/java/cx/ring/views/SwipeListViewTouchListener.java deleted file mode 100644 index 8e054662b..000000000 --- a/ring-android/app/src/main/java/cx/ring/views/SwipeListViewTouchListener.java +++ /dev/null @@ -1,409 +0,0 @@ -/* - * Copyright (C) 2004-2014 Savoir-Faire Linux Inc. - * - * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 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., 675 Mass Ave, Cambridge, MA 02139, USA. - * - * Additional permission under GNU GPL version 3 section 7: - * - * If you modify this program, or any covered work, by linking or - * combining it with the OpenSSL project's OpenSSL library (or a - * modified version of that library), containing parts covered by the - * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. - * grants you additional permission to convey the resulting work. - * Corresponding Source for a non-source form of such a combination - * shall include the source code for the parts of OpenSSL used as well - * as that of the covered work. - */ - -package cx.ring.views; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import cx.ring.R; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; -import android.graphics.Rect; -import android.view.MotionEvent; -import android.view.VelocityTracker; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.ViewGroup; -import android.widget.AbsListView; -import android.widget.ListView; - -public class SwipeListViewTouchListener implements View.OnTouchListener { - // Cached ViewConfiguration and system-wide constant values - private int mSlop; - private int mMinFlingVelocity; - private int mMaxFlingVelocity; - private long mAnimationTime; - - private static final String TAG = SwipeListViewTouchListener.class.getSimpleName(); - - // Fixed properties - private ListView mListView; - private OnSwipeCallback mCallback; - private int mViewWidth = 1; // 1 and not 0 to prevent dividing by zero - private boolean dismissLeft = true; - private boolean dismissRight = true; - - // Transient properties - private List<PendingSwipeData> mPendingSwipes = new ArrayList<PendingSwipeData>(); - private int mDismissAnimationRefCount = 0; - private float mDownX; - private float mDownY; - private boolean mSwiping; - private VelocityTracker mVelocityTracker; - private int mDownPosition; - private View mDownView, mUnderDownView; - private boolean mPaused; - - /** - * The callback interface used by {@link SwipeListViewTouchListener} to inform its client about a successful swipe of one or more list item - * positions. - */ - public interface OnSwipeCallback { - /** - * Called when the user has swiped the list item to the left. - * - * @param listView - * The originating {@link ListView}. - * @param reverseSortedPositions - * An array of positions to dismiss, sorted in descending order for convenience. - */ - void onSwipeLeft(ListView listView, int[] reverseSortedPositions); - - void onSwipeRight(ListView listView, View downView); - } - - /** - * Constructs a new swipe-to-action touch listener for the given list view. - * - * @param listView - * The list view whose items should be dismissable. - * @param callback - * The callback to trigger when the user has indicated that she would like to dismiss one or more list items. - */ - public SwipeListViewTouchListener(ListView listView, OnSwipeCallback callback) { - ViewConfiguration vc = ViewConfiguration.get(listView.getContext()); - mSlop = vc.getScaledTouchSlop(); - mMinFlingVelocity = vc.getScaledMinimumFlingVelocity(); - mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity(); - mAnimationTime = listView.getContext().getResources().getInteger(android.R.integer.config_shortAnimTime); - mListView = listView; - mCallback = callback; - } - - /** - * Constructs a new swipe-to-action touch listener for the given list view. - * - * @param listView - * The list view whose items should be dismissable. - * @param callback - * The callback to trigger when the user has indicated that she would like to dismiss one or more list items. - * @param dismissLeft - * set if the dismiss animation is up when the user swipe to the left - * @param dismissRight - * set if the dismiss animation is up when the user swipe to the right - * @see #SwipeListViewTouchListener(ListView, OnSwipeCallback, boolean, boolean) - */ - public SwipeListViewTouchListener(ListView listView, OnSwipeCallback callback, boolean dismissLeft, boolean dismissRight) { - this(listView, callback); - this.dismissLeft = dismissLeft; - this.dismissRight = dismissRight; - } - - /** - * Enables or disables (pauses or resumes) watching for swipe-to-dismiss gestures. - * - * @param enabled - * Whether or not to watch for gestures. - */ - public void setEnabled(boolean enabled) { - mPaused = !enabled; - } - - /** - * Returns an {@link android.widget.AbsListView.OnScrollListener} to be added to the {@link ListView} using - * {@link ListView#setOnScrollListener(android.widget.AbsListView.OnScrollListener)}. If a scroll listener is already assigned, the caller should - * still pass scroll changes through to this listener. This will ensure that this {@link SwipeListViewTouchListener} is paused during list view - * scrolling.</p> - * - * @see {@link SwipeListViewTouchListener} - */ - public AbsListView.OnScrollListener makeScrollListener() { - return new AbsListView.OnScrollListener() { - @Override - public void onScrollStateChanged(AbsListView absListView, int scrollState) { - setEnabled(scrollState != AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); - } - - @Override - public void onScroll(AbsListView absListView, int i, int i1, int i2) { - } - }; - } - - @Override - public boolean onTouch(View item, MotionEvent motionEvent) { - if (mViewWidth < 2) { - mViewWidth = mListView.getWidth(); - } - - switch (motionEvent.getActionMasked()) { - case MotionEvent.ACTION_DOWN: { - if (mPaused) { - return false; - } - - // TODO: ensure this is a finger, and set a flag - - // Find the child view that was touched (perform a hit test) - Rect rect = new Rect(); - int childCount = mListView.getChildCount(); - int[] listViewCoords = new int[2]; - mListView.getLocationOnScreen(listViewCoords); - int x = (int) motionEvent.getRawX() - listViewCoords[0]; - int y = (int) motionEvent.getRawY() - listViewCoords[1]; - View child; - for (int i = 0; i < childCount; i++) { - child = mListView.getChildAt(i); - child.getHitRect(rect); - if (rect.contains(x, y)) { - mDownView = child.findViewById(R.id.contactview); - mUnderDownView = child.findViewById(R.id.contact_underview); - break; - } - } - - if (mDownView != null) { - - mDownX = motionEvent.getRawX() - mDownView.getTranslationX(); - mDownY = motionEvent.getRawY(); - mDownPosition = mListView.getPositionForView(mDownView); - - mVelocityTracker = VelocityTracker.obtain(); - mVelocityTracker.addMovement(motionEvent); - } - item.onTouchEvent(motionEvent); - return true; - } - - case MotionEvent.ACTION_UP: { - if (mVelocityTracker == null) { - break; - } - - float deltaX = motionEvent.getRawX() - mDownX; - - mVelocityTracker.addMovement(motionEvent); - mVelocityTracker.computeCurrentVelocity(500); // 1000 by defaut but it was too much - float velocityX = Math.abs(mVelocityTracker.getXVelocity()); - float velocityY = Math.abs(mVelocityTracker.getYVelocity()); - boolean swipe = false; - boolean swipeRight = false; - - if (mDownView.getTranslationX() > mViewWidth / 2) { - swipe = true; - swipeRight = deltaX > 0; - } else if (mMinFlingVelocity <= velocityX && velocityX <= mMaxFlingVelocity && velocityY < velocityX) { - swipe = true; - swipeRight = mVelocityTracker.getXVelocity() > 0; - } - if (swipe) { - // sufficent swipe value - final View downView = mDownView; // mDownView gets null'd before animation ends - final int downPosition = mDownPosition; - final boolean toTheRight = swipeRight; - ++mDismissAnimationRefCount; - - if (toTheRight) { - mDownView.animate().translationX(mViewWidth / 2).alpha(1).setDuration(mAnimationTime).setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mListView.requestDisallowInterceptTouchEvent(false); - // mCallback.onSwipeRight(mListView, mUnderDownView); - toggleUnderLayerState(true); - // performSwipeAction(downView, downPosition, toTheRight,dismissRight); - } - }); - } else { - mDownView.animate().translationX(0).alpha(1).setDuration(mAnimationTime); - } - - } else { - // cancel - mDownView.animate().translationX(0).alpha(1).setDuration(mAnimationTime).setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - toggleUnderLayerState(false); - mUnderDownView = null; - } - }); - } - mVelocityTracker.recycle(); - mVelocityTracker = null; - mDownX = 0; - mDownView = null; - mDownPosition = ListView.INVALID_POSITION; - mSwiping = false; - break; - } - - case MotionEvent.ACTION_MOVE: { - if (mVelocityTracker == null || mPaused) { - break; - } - - mVelocityTracker.addMovement(motionEvent); - mVelocityTracker.computeCurrentVelocity(500); - float deltaX = motionEvent.getRawX() - mDownX; - float deltaY = motionEvent.getRawY() - mDownY; - - if (Math.abs(deltaX) < Math.abs(deltaY)) { - mListView.requestDisallowInterceptTouchEvent(false); - return false; - } - - if (Math.abs(deltaX) > mSlop) { - mSwiping = true; - mListView.requestDisallowInterceptTouchEvent(true); - - // Cancel ListView's touch (un-highlighting the item) - MotionEvent cancelEvent = MotionEvent.obtain(motionEvent); - cancelEvent.setAction(MotionEvent.ACTION_CANCEL | (motionEvent.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT)); - mListView.onTouchEvent(cancelEvent); - cancelEvent.recycle(); - } - if (deltaX < 0) - return true; - - if (mSwiping) { - mDownView.setTranslationX(deltaX); - // mDownView.setAlpha(Math.max(0f, Math.min(1f, 1f - 2f * Math.abs(deltaX) / mViewWidth))); - return true; - } - break; - } - } - return false; - } - - private void toggleUnderLayerState(boolean b) { - if (mUnderDownView == null) - return; - mUnderDownView.findViewById(R.id.quick_edit).setClickable(b); - mUnderDownView.findViewById(R.id.quick_discard).setClickable(b); - mUnderDownView.findViewById(R.id.quick_starred).setClickable(b); - } - - class PendingSwipeData implements Comparable<PendingSwipeData> { - public int position; - public View view; - - public PendingSwipeData(int position, View view) { - this.position = position; - this.view = view; - } - - @Override - public int compareTo(PendingSwipeData other) { - // Sort by descending position - return other.position - position; - } - } - - private void performSwipeAction(final View swipeView, final int swipePosition, boolean toTheRight, boolean dismiss) { - // Animate the dismissed list item to zero-height and fire the dismiss callback when - // all dismissed list item animations have completed. This triggers layout on each animation - // frame; in the future we may want to do something smarter and more performant. - - final ViewGroup.LayoutParams lp = swipeView.getLayoutParams(); - final int originalHeight = swipeView.getHeight(); - final boolean swipeRight = toTheRight; - - ValueAnimator animator; - if (dismiss) - animator = ValueAnimator.ofInt(originalHeight, 1).setDuration(mAnimationTime); - else - animator = ValueAnimator.ofInt(originalHeight, originalHeight - 1).setDuration(mAnimationTime); - - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - --mDismissAnimationRefCount; - if (mDismissAnimationRefCount == 0) { - // No active animations, process all pending dismisses. - // Sort by descending position - Collections.sort(mPendingSwipes); - - int[] swipePositions = new int[mPendingSwipes.size()]; - for (int i = mPendingSwipes.size() - 1; i >= 0; i--) { - swipePositions[i] = mPendingSwipes.get(i).position; - } - // if (swipeRight) - // mCallback.onSwipeRight(mListView, swipePositions); - // else - // mCallback.onSwipeLeft(mListView, swipePositions); - - ViewGroup.LayoutParams lp; - for (PendingSwipeData pendingDismiss : mPendingSwipes) { - // Reset view presentation - pendingDismiss.view.setAlpha(1f); - pendingDismiss.view.setTranslationX(0); - lp = pendingDismiss.view.getLayoutParams(); - lp.height = originalHeight; - pendingDismiss.view.setLayoutParams(lp); - } - - mPendingSwipes.clear(); - } - } - }); - - animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator valueAnimator) { - lp.height = (Integer) valueAnimator.getAnimatedValue(); - swipeView.setLayoutParams(lp); - } - }); - - mPendingSwipes.add(new PendingSwipeData(swipePosition, swipeView)); - animator.start(); - } - - public void openItem(View child, int pos, long id) { - - mDownView = child.findViewById(R.id.contactview); - mUnderDownView = child.findViewById(R.id.contact_underview); - if (mDownView.getTranslationX() > 0) - return; - mDownView.animate().translationX(mViewWidth / 2).setDuration(mAnimationTime).setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mListView.requestDisallowInterceptTouchEvent(false); - toggleUnderLayerState(true); - // performSwipeAction(downView, downPosition, toTheRight,dismissRight); - } - }); - } -} diff --git a/ring-android/app/src/main/java/cx/ring/views/stickylistheaders/AdapterWrapper.java b/ring-android/app/src/main/java/cx/ring/views/stickylistheaders/AdapterWrapper.java deleted file mode 100644 index 22b02a8a9..000000000 --- a/ring-android/app/src/main/java/cx/ring/views/stickylistheaders/AdapterWrapper.java +++ /dev/null @@ -1,225 +0,0 @@ -package cx.ring.views.stickylistheaders; - -import java.util.LinkedList; -import java.util.List; - -import android.content.Context; -import android.database.DataSetObserver; -import android.graphics.drawable.Drawable; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.Checkable; -import android.widget.ListAdapter; - -/** - * A {@link ListAdapter} which wraps a {@link StickyListHeadersAdapter} and - * automatically handles wrapping the result of - * {@link StickyListHeadersAdapter#getView(int, android.view.View, android.view.ViewGroup)} - * and - * {@link StickyListHeadersAdapter#getHeaderView(int, android.view.View, android.view.ViewGroup)} - * appropriately. - * - * @author Jake Wharton (jakewharton@gmail.com) - */ -class AdapterWrapper extends BaseAdapter implements StickyListHeadersAdapter { - - interface OnHeaderClickListener { - public void onHeaderClick(View header, int itemPosition, long headerId); - } - - final StickyListHeadersAdapter mDelegate; - private final List<View> mHeaderCache = new LinkedList<View>(); - private final Context mContext; - private Drawable mDivider; - private int mDividerHeight; - private OnHeaderClickListener mOnHeaderClickListener; - private DataSetObserver mDataSetObserver = new DataSetObserver() { - - @Override - public void onInvalidated() { - mHeaderCache.clear(); - AdapterWrapper.super.notifyDataSetInvalidated(); - } - - @Override - public void onChanged() { - AdapterWrapper.super.notifyDataSetChanged(); - } - }; - - AdapterWrapper(Context context, - StickyListHeadersAdapter delegate) { - this.mContext = context; - this.mDelegate = delegate; - delegate.registerDataSetObserver(mDataSetObserver); - } - - void setDivider(Drawable divider, int dividerHeight) { - this.mDivider = divider; - this.mDividerHeight = dividerHeight; - notifyDataSetChanged(); - } - - @Override - public boolean areAllItemsEnabled() { - return mDelegate.areAllItemsEnabled(); - } - - @Override - public boolean isEnabled(int position) { - return mDelegate.isEnabled(position); - } - - @Override - public int getCount() { - return mDelegate.getCount(); - } - - @Override - public Object getItem(int position) { - return mDelegate.getItem(position); - } - - @Override - public long getItemId(int position) { - return mDelegate.getItemId(position); - } - - @Override - public boolean hasStableIds() { - return mDelegate.hasStableIds(); - } - - @Override - public int getItemViewType(int position) { - return mDelegate.getItemViewType(position); - } - - @Override - public int getViewTypeCount() { - return mDelegate.getViewTypeCount(); - } - - @Override - public boolean isEmpty() { - return mDelegate.isEmpty(); - } - - /** - * Will recycle header from {@link WrapperView} if it exists - */ - private void recycleHeaderIfExists(WrapperView wv) { - View header = wv.mHeader; - if (header != null) { - // reset the headers visibility when adding it to the cache - header.setVisibility(View.VISIBLE); - mHeaderCache.add(header); - } - } - - /** - * Get a header view. This optionally pulls a header from the supplied - * {@link WrapperView} and will also recycle the divider if it exists. - */ - private View configureHeader(WrapperView wv, final int position) { - View header = wv.mHeader == null ? popHeader() : wv.mHeader; - header = mDelegate.getHeaderView(position, header, wv); - if (header == null) { - throw new NullPointerException("Header view must not be null."); - } - //if the header isn't clickable, the listselector will be drawn on top of the header - header.setClickable(true); - header.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - if(mOnHeaderClickListener != null){ - long headerId = mDelegate.getHeaderId(position); - mOnHeaderClickListener.onHeaderClick(v, position, headerId); - } - } - }); - return header; - } - - private View popHeader() { - if(mHeaderCache.size() > 0) { - return mHeaderCache.remove(0); - } - return null; - } - - /** Returns {@code true} if the previous position has the same header ID. */ - private boolean previousPositionHasSameHeader(int position) { - return position != 0 - && mDelegate.getHeaderId(position) == mDelegate - .getHeaderId(position - 1); - } - - @Override - public WrapperView getView(int position, View convertView, ViewGroup parent) { - WrapperView wv = (convertView == null) ? new WrapperView(mContext) : (WrapperView) convertView; - View item = mDelegate.getView(position, wv.mItem, parent); - View header = null; - if (previousPositionHasSameHeader(position)) { - recycleHeaderIfExists(wv); - } else { - header = configureHeader(wv, position); - } - if((item instanceof Checkable) && !(wv instanceof CheckableWrapperView)) { - // Need to create Checkable subclass of WrapperView for ListView to work correctly - wv = new CheckableWrapperView(mContext); - } else if(!(item instanceof Checkable) && (wv instanceof CheckableWrapperView)) { - wv = new WrapperView(mContext); - } - wv.update(item, header, mDivider, mDividerHeight); - return wv; - } - - public void setOnHeaderClickListener(OnHeaderClickListener onHeaderClickListener){ - this.mOnHeaderClickListener = onHeaderClickListener; - } - - @Override - public boolean equals(Object o) { - return mDelegate.equals(o); - } - - @Override - public View getDropDownView(int position, View convertView, ViewGroup parent) { - return ((BaseAdapter) mDelegate).getDropDownView(position, convertView, parent); - } - - @Override - public int hashCode() { - return mDelegate.hashCode(); - } - - @Override - public void notifyDataSetChanged() { - ((BaseAdapter) mDelegate).notifyDataSetChanged(); - } - - @Override - public void notifyDataSetInvalidated() { - ((BaseAdapter) mDelegate).notifyDataSetInvalidated(); - } - - @Override - public String toString() { - return mDelegate.toString(); - } - - @Override - public View getHeaderView(int position, View convertView, ViewGroup parent) { - return mDelegate.getHeaderView(position, convertView, parent); - } - - @Override - public long getHeaderId(int position) { - return mDelegate.getHeaderId(position); - } - -} diff --git a/ring-android/app/src/main/java/cx/ring/views/stickylistheaders/ApiLevelTooLowException.java b/ring-android/app/src/main/java/cx/ring/views/stickylistheaders/ApiLevelTooLowException.java deleted file mode 100644 index 075dfb4d6..000000000 --- a/ring-android/app/src/main/java/cx/ring/views/stickylistheaders/ApiLevelTooLowException.java +++ /dev/null @@ -1,11 +0,0 @@ -package cx.ring.views.stickylistheaders; - -public class ApiLevelTooLowException extends RuntimeException { - - private static final long serialVersionUID = -5480068364264456757L; - - public ApiLevelTooLowException(int versionCode) { - super("Requires API level " + versionCode); - } - -} diff --git a/ring-android/app/src/main/java/cx/ring/views/stickylistheaders/CheckableWrapperView.java b/ring-android/app/src/main/java/cx/ring/views/stickylistheaders/CheckableWrapperView.java deleted file mode 100644 index e22204f44..000000000 --- a/ring-android/app/src/main/java/cx/ring/views/stickylistheaders/CheckableWrapperView.java +++ /dev/null @@ -1,31 +0,0 @@ -package cx.ring.views.stickylistheaders; - -import android.content.Context; -import android.widget.Checkable; - -/** - * A WrapperView that implements the checkable interface - * - * @author Emil Sjölander - */ -class CheckableWrapperView extends WrapperView implements Checkable { - - public CheckableWrapperView(final Context context) { - super(context); - } - - @Override - public boolean isChecked() { - return ((Checkable) mItem).isChecked(); - } - - @Override - public void setChecked(final boolean checked) { - ((Checkable) mItem).setChecked(checked); - } - - @Override - public void toggle() { - setChecked(!isChecked()); - } -} diff --git a/ring-android/app/src/main/java/cx/ring/views/stickylistheaders/SectionIndexerAdapterWrapper.java b/ring-android/app/src/main/java/cx/ring/views/stickylistheaders/SectionIndexerAdapterWrapper.java deleted file mode 100644 index dd823ff82..000000000 --- a/ring-android/app/src/main/java/cx/ring/views/stickylistheaders/SectionIndexerAdapterWrapper.java +++ /dev/null @@ -1,32 +0,0 @@ -package cx.ring.views.stickylistheaders; - -import android.content.Context; -import android.widget.SectionIndexer; - -class SectionIndexerAdapterWrapper extends - AdapterWrapper implements SectionIndexer { - - final SectionIndexer mSectionIndexerDelegate; - - SectionIndexerAdapterWrapper(Context context, - StickyListHeadersAdapter delegate) { - super(context, delegate); - mSectionIndexerDelegate = (SectionIndexer) delegate; - } - - @Override - public int getPositionForSection(int section) { - return mSectionIndexerDelegate.getPositionForSection(section); - } - - @Override - public int getSectionForPosition(int position) { - return mSectionIndexerDelegate.getSectionForPosition(position); - } - - @Override - public Object[] getSections() { - return mSectionIndexerDelegate.getSections(); - } - -} diff --git a/ring-android/app/src/main/java/cx/ring/views/stickylistheaders/StickyListHeadersAdapter.java b/ring-android/app/src/main/java/cx/ring/views/stickylistheaders/StickyListHeadersAdapter.java deleted file mode 100644 index 236338d3d..000000000 --- a/ring-android/app/src/main/java/cx/ring/views/stickylistheaders/StickyListHeadersAdapter.java +++ /dev/null @@ -1,38 +0,0 @@ -package cx.ring.views.stickylistheaders; - -import android.view.View; -import android.view.ViewGroup; -import android.widget.ListAdapter; - -public interface StickyListHeadersAdapter extends ListAdapter { - /** - * Get a View that displays the header data at the specified position in the - * set. You can either create a View manually or inflate it from an XML layout - * file. - * - * @param position - * The position of the item within the adapter's data set of the item whose - * header view we want. - * @param convertView - * The old view to reuse, if possible. Note: You should check that this view is - * non-null and of an appropriate type before using. If it is not possible to - * convert this view to display the correct data, this method can create a new - * view. - * @param parent - * The parent that this view will eventually be attached to. - * @return - * A View corresponding to the data at the specified position. - */ - View getHeaderView(int position, View convertView, ViewGroup parent); - - /** - * Get the header id associated with the specified position in the list. - * - * @param position - * The position of the item within the adapter's data set whose header id we - * want. - * @return - * The id of the header at the specified position. - */ - long getHeaderId(int position); -} diff --git a/ring-android/app/src/main/java/cx/ring/views/stickylistheaders/StickyListHeadersListView.java b/ring-android/app/src/main/java/cx/ring/views/stickylistheaders/StickyListHeadersListView.java deleted file mode 100644 index 72780e9d6..000000000 --- a/ring-android/app/src/main/java/cx/ring/views/stickylistheaders/StickyListHeadersListView.java +++ /dev/null @@ -1,993 +0,0 @@ -package cx.ring.views.stickylistheaders; - -import cx.ring.R; -import cx.ring.adapters.ContactsAdapter; - -import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.content.Context; -import android.content.res.TypedArray; -import android.database.DataSetObserver; -import android.graphics.Canvas; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.util.AttributeSet; -import android.util.SparseBooleanArray; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AbsListView; -import android.widget.AbsListView.OnScrollListener; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.AdapterView.OnItemLongClickListener; -import android.widget.FrameLayout; -import android.widget.ListView; -import android.widget.SectionIndexer; - -/** - * Even though this is a FrameLayout subclass we it is called a ListView. This - * is because of 2 reasons. 1. It acts like as ListView 2. It used to be a - * ListView subclass and i did not was to change to name causing compatibility - * errors. - * - * @author Emil Sjölander - */ -public class StickyListHeadersListView extends FrameLayout { - - public interface OnHeaderClickListener { - public void onHeaderClick(StickyListHeadersListView l, View header, - int itemPosition, long headerId, boolean currentlySticky); - } - - /* --- Children --- */ - private WrapperViewList mList; - private View mHeader; - - /* --- Header state --- */ - private Long mHeaderId; - // used to not have to call getHeaderId() all the time - private Integer mHeaderPosition; - private Integer mHeaderOffset; - - /* --- Delegates --- */ - private OnScrollListener mOnScrollListenerDelegate; - - /* --- Settings --- */ - private boolean mAreHeadersSticky = true; - private boolean mClippingToPadding = true; - private boolean mIsDrawingListUnderStickyHeader = true; - private int mPaddingLeft = 0; - private int mPaddingTop = 0; - private int mPaddingRight = 0; - private int mPaddingBottom = 0; - - /* --- Other --- */ - private AdapterWrapper mAdapter; - private OnHeaderClickListener mOnHeaderClickListener; - private Drawable mDivider; - private int mDividerHeight; - private AdapterWrapperDataSetObserver mDataSetObserver; - - public StickyListHeadersListView(Context context) { - this(context, null); - } - - public StickyListHeadersListView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - @TargetApi(Build.VERSION_CODES.HONEYCOMB) - public StickyListHeadersListView(Context context, AttributeSet attrs, - int defStyle) { - super(context, attrs, defStyle); - - // Initialize the list - mList = new WrapperViewList(context, attrs); - mDivider = mList.getDivider(); - mDividerHeight = mList.getDividerHeight(); - - // null out divider, dividers are handled by adapter so they look good - // with headers - mList.setDivider(null); - mList.setDividerHeight(0); - - mList.setLifeCycleListener(new WrapperViewListLifeCycleListener()); - mList.setOnScrollListener(new WrapperListScrollListener()); - addView(mList); - - if (attrs != null) { - TypedArray a = context.getTheme().obtainStyledAttributes(attrs, - R.styleable.StickyListHeadersListView, 0, 0); - - try { - // Android attributes - if (a.hasValue(R.styleable.StickyListHeadersListView_android_padding)) { - int padding = a - .getDimensionPixelSize( - R.styleable.StickyListHeadersListView_android_padding, - 0); - mPaddingLeft = padding; - mPaddingTop = padding; - mPaddingRight = padding; - mPaddingBottom = padding; - } else { - mPaddingLeft = a - .getDimensionPixelSize( - R.styleable.StickyListHeadersListView_android_paddingLeft, - 0); - mPaddingTop = a - .getDimensionPixelSize( - R.styleable.StickyListHeadersListView_android_paddingTop, - 0); - mPaddingRight = a - .getDimensionPixelSize( - R.styleable.StickyListHeadersListView_android_paddingRight, - 0); - mPaddingBottom = a - .getDimensionPixelSize( - R.styleable.StickyListHeadersListView_android_paddingBottom, - 0); - } - setPadding(mPaddingLeft, mPaddingTop, mPaddingRight, - mPaddingBottom); - - // Set clip to padding on the list and reset value to default on - // wrapper - mClippingToPadding = a - .getBoolean( - R.styleable.StickyListHeadersListView_android_clipToPadding, - true); - super.setClipToPadding(true); - mList.setClipToPadding(mClippingToPadding); - - // ListView attributes - mList.setFadingEdgeLength(a - .getDimensionPixelSize( - R.styleable.StickyListHeadersListView_android_fadingEdgeLength, - mList.getVerticalFadingEdgeLength())); - final int fadingEdge = a - .getInt(R.styleable.StickyListHeadersListView_android_requiresFadingEdge, - 0); - if (fadingEdge == 0x00001000) { - mList.setVerticalFadingEdgeEnabled(false); - mList.setHorizontalFadingEdgeEnabled(true); - } else if (fadingEdge == 0x00002000) { - mList.setVerticalFadingEdgeEnabled(true); - mList.setHorizontalFadingEdgeEnabled(false); - } else { - mList.setVerticalFadingEdgeEnabled(false); - mList.setHorizontalFadingEdgeEnabled(false); - } - mList.setCacheColorHint(a - .getColor( - R.styleable.StickyListHeadersListView_android_cacheColorHint, - mList.getCacheColorHint())); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - mList.setChoiceMode(a - .getInt(R.styleable.StickyListHeadersListView_android_choiceMode, - mList.getChoiceMode())); - } - mList.setDrawSelectorOnTop(a - .getBoolean( - R.styleable.StickyListHeadersListView_android_drawSelectorOnTop, - false)); - mList.setFastScrollEnabled(a - .getBoolean( - R.styleable.StickyListHeadersListView_android_fastScrollEnabled, - mList.isFastScrollEnabled())); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - mList.setFastScrollAlwaysVisible(a - .getBoolean( - R.styleable.StickyListHeadersListView_android_fastScrollAlwaysVisible, - mList.isFastScrollAlwaysVisible())); - } - mList.setScrollBarStyle(a - .getInt(R.styleable.StickyListHeadersListView_android_scrollbarStyle, - 0)); - final Drawable selector = a - .getDrawable(R.styleable.StickyListHeadersListView_android_listSelector); - if (selector != null) { - mList.setSelector(selector); - } - mList.setScrollingCacheEnabled(a - .getBoolean( - R.styleable.StickyListHeadersListView_android_scrollingCache, - mList.isScrollingCacheEnabled())); - final Drawable divider = a - .getDrawable(R.styleable.StickyListHeadersListView_android_divider); - if (divider != null) { - mDivider = divider; - } - mDividerHeight = a - .getDimensionPixelSize( - R.styleable.StickyListHeadersListView_android_dividerHeight, - mDividerHeight); - - // StickyListHeaders attributes - mAreHeadersSticky = a.getBoolean( - R.styleable.StickyListHeadersListView_hasStickyHeaders, - true); - mIsDrawingListUnderStickyHeader = a - .getBoolean( - R.styleable.StickyListHeadersListView_isDrawingListUnderStickyHeader, - true); - } finally { - a.recycle(); - } - } - - mList.setVerticalScrollBarEnabled(isVerticalScrollBarEnabled()); - mList.setHorizontalScrollBarEnabled(isHorizontalScrollBarEnabled()); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - measureHeader(mHeader); - } - - private void ensureHeaderHasCorrectLayoutParams(View header) { - ViewGroup.LayoutParams lp = header.getLayoutParams(); - if (lp == null) { - lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); - } else if (lp.height == LayoutParams.MATCH_PARENT) { - lp.height = LayoutParams.WRAP_CONTENT; - } - header.setLayoutParams(lp); - } - - private void measureHeader(View header) { - if (header != null) { - final int width = getMeasuredWidth() - mPaddingLeft - mPaddingRight; - final int parentWidthMeasureSpec = MeasureSpec.makeMeasureSpec( - width, MeasureSpec.EXACTLY); - final int parentHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, - MeasureSpec.UNSPECIFIED); - measureChild(header, parentWidthMeasureSpec, - parentHeightMeasureSpec); - } - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, - int bottom) { - mList.layout(0, 0, mList.getMeasuredWidth(), getHeight()); - if (mHeader != null) { - MarginLayoutParams lp = (MarginLayoutParams) mHeader - .getLayoutParams(); - int headerTop = lp.topMargin - + (mClippingToPadding ? mPaddingTop : 0); - // The left parameter must for some reason be set to 0. - // I think it should be set to mPaddingLeft but apparently not - mHeader.layout(mPaddingLeft, headerTop, mHeader.getMeasuredWidth() - + mPaddingLeft, headerTop + mHeader.getMeasuredHeight()); - } - } - - @Override - protected void dispatchDraw(Canvas canvas) { - // Only draw the list here. - // The header should be drawn right after the lists children are drawn. - // This is done so that the header is above the list items - // but below the list decorators (scroll bars etc). - drawChild(canvas, mList, 0); - } - - // Reset values tied the header. also remove header form layout - // This is called in response to the data set or the adapter changing - private void clearHeader() { - if (mHeader != null) { - removeView(mHeader); - mHeader = null; - mHeaderId = null; - mHeaderPosition = null; - mHeaderOffset = null; - - // reset the top clipping length - mList.setTopClippingLength(0); - updateHeaderVisibilities(); - } - } - - private void updateOrClearHeader(int firstVisiblePosition) { - final int adapterCount = mAdapter == null ? 0 : mAdapter.getCount(); - if (adapterCount == 0 || !mAreHeadersSticky) { - return; - } - - final int headerViewCount = mList.getHeaderViewsCount(); - final int realFirstVisibleItem = firstVisiblePosition - headerViewCount; - - // It is not a mistake to call getFirstVisiblePosition() here. - // Most of the time getFixedFirstVisibleItem() should be called - // but that does not work great together with getChildAt() - final boolean doesListHaveChildren = mList.getChildCount() != 0; - final boolean isFirstViewBelowTop = doesListHaveChildren && mList.getFirstVisiblePosition() == 0 - && mList.getChildAt(0).getTop() > 0; - final boolean isFirstVisibleItemOutsideAdapterRange = realFirstVisibleItem > adapterCount - 1 - || realFirstVisibleItem < 0; - if (!doesListHaveChildren || isFirstVisibleItemOutsideAdapterRange - || isFirstViewBelowTop) { - clearHeader(); - return; - } - - updateHeader(realFirstVisibleItem); - } - - private void updateHeader(int firstVisiblePosition) { - - // check if there is a new header should be sticky - if (mHeaderPosition == null || mHeaderPosition != firstVisiblePosition) { - mHeaderPosition = firstVisiblePosition; - final long headerId = mAdapter.getHeaderId(firstVisiblePosition); - if (mHeaderId == null || mHeaderId != headerId) { - mHeaderId = headerId; - final View header = mAdapter.getHeaderView(mHeaderPosition, - mHeader, this); - if (mHeader != header) { - if (header == null) { - throw new NullPointerException("header may not be null"); - } - swapHeader(header); - } - - ensureHeaderHasCorrectLayoutParams(mHeader); - measureHeader(mHeader); - - // Reset mHeaderOffset to null ensuring - // that it will be set on the header and - // not skipped for performance reasons. - mHeaderOffset = null; - } - } - - int headerOffset = 0; - - // Calculate new header offset - // Skip looking at the first view. it never matters because it always - // results in a headerOffset = 0 - int headerBottom = mHeader.getMeasuredHeight() - + (mClippingToPadding ? mPaddingTop : 0); - for (int i = 0; i < mList.getChildCount(); i++) { - final View child = mList.getChildAt(i); - final boolean doesChildHaveHeader = child instanceof WrapperView - && ((WrapperView) child).hasHeader(); - final boolean isChildFooter = mList.containsFooterView(child); - if (child.getTop() >= (mClippingToPadding ? mPaddingTop : 0) - && (doesChildHaveHeader || isChildFooter)) { - headerOffset = Math.min(child.getTop() - headerBottom, 0); - break; - } - } - - setHeaderOffet(headerOffset); - - if (!mIsDrawingListUnderStickyHeader) { - mList.setTopClippingLength(mHeader.getMeasuredHeight() - + mHeaderOffset); - } - - updateHeaderVisibilities(); - } - - private void swapHeader(View newHeader) { - if (mHeader != null) { - removeView(mHeader); - } - mHeader = newHeader; - addView(mHeader); - mHeader.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - if (mOnHeaderClickListener != null) { - mOnHeaderClickListener.onHeaderClick( - StickyListHeadersListView.this, mHeader, - mHeaderPosition, mHeaderId, true); - } - } - - }); - } - - // hides the headers in the list under the sticky header. - // Makes sure the other ones are showing - private void updateHeaderVisibilities() { - int top; - if (mHeader != null) { - top = mHeader.getMeasuredHeight() - + (mHeaderOffset != null ? mHeaderOffset : 0); - } else { - top = mClippingToPadding ? mPaddingTop : 0; - } - int childCount = mList.getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = mList.getChildAt(i); - if (child instanceof WrapperView) { - WrapperView wrapperViewChild = (WrapperView) child; - if (wrapperViewChild.hasHeader()) { - View childHeader = wrapperViewChild.mHeader; - if (wrapperViewChild.getTop() < top) { - if (childHeader.getVisibility() != View.INVISIBLE) { - childHeader.setVisibility(View.INVISIBLE); - } - } else { - if (childHeader.getVisibility() != View.VISIBLE) { - childHeader.setVisibility(View.VISIBLE); - } - } - } - } - } - } - - // Wrapper around setting the header offset in different ways depending on - // the API version - @SuppressLint("NewApi") - private void setHeaderOffet(int offset) { - if (mHeaderOffset == null || mHeaderOffset != offset) { - mHeaderOffset = offset; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - mHeader.setTranslationY(mHeaderOffset); - } else { - MarginLayoutParams params = (MarginLayoutParams) mHeader - .getLayoutParams(); - params.topMargin = mHeaderOffset; - mHeader.setLayoutParams(params); - } - } - } - - private class AdapterWrapperDataSetObserver extends DataSetObserver { - - @Override - public void onChanged() { - clearHeader(); - } - - @Override - public void onInvalidated() { - clearHeader(); - } - - } - - private class WrapperListScrollListener implements OnScrollListener { - - @Override - public void onScroll(AbsListView view, int firstVisibleItem, - int visibleItemCount, int totalItemCount) { - if (mOnScrollListenerDelegate != null) { - mOnScrollListenerDelegate.onScroll(view, firstVisibleItem, - visibleItemCount, totalItemCount); - } - updateOrClearHeader(mList.getFixedFirstVisibleItem()); - } - - @Override - public void onScrollStateChanged(AbsListView view, int scrollState) { - if (mOnScrollListenerDelegate != null) { - mOnScrollListenerDelegate.onScrollStateChanged(view, - scrollState); - } - } - - } - - private class WrapperViewListLifeCycleListener implements WrapperViewList.LifeCycleListener { - - @Override - public void onDispatchDrawOccurred(Canvas canvas) { - // onScroll is not called often at all before froyo - // therefor we need to update the header here as well. - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO) { - updateOrClearHeader(mList.getFixedFirstVisibleItem()); - } - if (mHeader != null) { - if (mClippingToPadding) { - canvas.save(); - canvas.clipRect(0, mPaddingTop, getRight(), getBottom()); - drawChild(canvas, mHeader, 0); - canvas.restore(); - } else { - drawChild(canvas, mHeader, 0); - } - } - } - - } - - private class AdapterWrapperHeaderClickHandler implements - AdapterWrapper.OnHeaderClickListener { - - @Override - public void onHeaderClick(View header, int itemPosition, long headerId) { - mOnHeaderClickListener.onHeaderClick( - StickyListHeadersListView.this, header, itemPosition, - headerId, false); - } - - } - - private boolean isStartOfSection(int position) { - return position == 0 - || mAdapter.getHeaderId(position) != mAdapter - .getHeaderId(position - 1); - } - - private int getHeaderOverlap(int position) { - boolean isStartOfSection = isStartOfSection(position); - if (!isStartOfSection) { - View header = mAdapter.getHeaderView(position, null, mList); - if (header == null) { - throw new NullPointerException("header may not be null"); - } - ensureHeaderHasCorrectLayoutParams(header); - measureHeader(header); - return header.getMeasuredHeight(); - } - return 0; - } - - /* ---------- StickyListHeaders specific API ---------- */ - - public void setAreHeadersSticky(boolean areHeadersSticky) { - mAreHeadersSticky = areHeadersSticky; - if (!areHeadersSticky) { - clearHeader(); - } else { - updateOrClearHeader(mList.getFixedFirstVisibleItem()); - } - // invalidating the list will trigger dispatchDraw() - mList.invalidate(); - } - - public boolean areHeadersSticky() { - return mAreHeadersSticky; - } - - /** - * Use areHeadersSticky() method instead - */ - @Deprecated - public boolean getAreHeadersSticky() { - return areHeadersSticky(); - } - - public void setDrawingListUnderStickyHeader( - boolean drawingListUnderStickyHeader) { - mIsDrawingListUnderStickyHeader = drawingListUnderStickyHeader; - // reset the top clipping length - mList.setTopClippingLength(0); - } - - public boolean isDrawingListUnderStickyHeader() { - return mIsDrawingListUnderStickyHeader; - } - - public void setOnHeaderClickListener( - OnHeaderClickListener onHeaderClickListener) { - mOnHeaderClickListener = onHeaderClickListener; - if (mAdapter != null) { - if (mOnHeaderClickListener != null) { - mAdapter.setOnHeaderClickListener(new AdapterWrapperHeaderClickHandler()); - } else { - mAdapter.setOnHeaderClickListener(null); - } - } - } - - public View getListChildAt(int index) { - return mList.getChildAt(index); - } - - public int getListChildCount() { - return mList.getChildCount(); - } - - /** - * Use the method with extreme caution!! Changing any values on the - * underlying ListView might break everything. - * - * @return the ListView backing this view. - */ - public ListView getWrappedList() { - return mList; - } - - /* ---------- ListView delegate methods ---------- */ - - public void setAdapter(StickyListHeadersAdapter adapter) { - if (adapter == null) { - mList.setAdapter(null); - clearHeader(); - return; - } - if (mAdapter != null) { - mAdapter.unregisterDataSetObserver(mDataSetObserver); - } - - if (adapter instanceof SectionIndexer) { - mAdapter = new SectionIndexerAdapterWrapper(getContext(), adapter); - } else { - mAdapter = new AdapterWrapper(getContext(), adapter); - } - mDataSetObserver = new AdapterWrapperDataSetObserver(); - mAdapter.registerDataSetObserver(mDataSetObserver); - - if (mOnHeaderClickListener != null) { - mAdapter.setOnHeaderClickListener(new AdapterWrapperHeaderClickHandler()); - } else { - mAdapter.setOnHeaderClickListener(null); - } - - mAdapter.setDivider(mDivider, mDividerHeight); - - mList.setAdapter(mAdapter); - clearHeader(); - } - - public StickyListHeadersAdapter getAdapter() { - return mAdapter == null ? null : mAdapter.mDelegate; - } - - public void setDivider(Drawable divider) { - mDivider = divider; - if (mAdapter != null) { - mAdapter.setDivider(mDivider, mDividerHeight); - } - } - - public void setDividerHeight(int dividerHeight) { - mDividerHeight = dividerHeight; - if (mAdapter != null) { - mAdapter.setDivider(mDivider, mDividerHeight); - } - } - - public Drawable getDivider() { - return mDivider; - } - - public int getDividerHeight() { - return mDividerHeight; - } - - public void setOnScrollListener(OnScrollListener onScrollListener) { - mOnScrollListenerDelegate = onScrollListener; - } - - public void setOnItemClickListener(OnItemClickListener listener) { - mList.setOnItemClickListener(listener); - } - - public void setOnItemLongClickListener(OnItemLongClickListener listener) { - mList.setOnItemLongClickListener(listener); - } - - public void addHeaderView(View v, Object data, boolean isSelectable) { - mList.addHeaderView(v, data, isSelectable); - } - - public void addHeaderView(View v) { - mList.addHeaderView(v); - } - - public void removeHeaderView(View v) { - mList.removeHeaderView(v); - } - - public int getHeaderViewsCount() { - return mList.getHeaderViewsCount(); - } - - public void addFooterView(View v) { - mList.addFooterView(v); - } - - public void removeFooterView(View v) { - mList.removeFooterView(v); - } - - public int getFooterViewsCount() { - return mList.getFooterViewsCount(); - } - - public void setEmptyView(View v) { - mList.setEmptyView(v); - } - - public View getEmptyView() { - return mList.getEmptyView(); - } - - @Override - public void setVerticalScrollBarEnabled(boolean verticalScrollBarEnabled) { - mList.setVerticalScrollBarEnabled(verticalScrollBarEnabled); - } - - @Override - public void setHorizontalScrollBarEnabled(boolean horizontalScrollBarEnabled) { - mList.setHorizontalScrollBarEnabled(horizontalScrollBarEnabled); - } - - @TargetApi(Build.VERSION_CODES.FROYO) - public void smoothScrollBy(int distance, int duration) { - requireSdkVersion(Build.VERSION_CODES.FROYO); - mList.smoothScrollBy(distance, duration); - } - - @TargetApi(Build.VERSION_CODES.HONEYCOMB) - public void smoothScrollByOffset(int offset) { - requireSdkVersion(Build.VERSION_CODES.HONEYCOMB); - mList.smoothScrollByOffset(offset); - } - - @SuppressLint("NewApi") - @TargetApi(Build.VERSION_CODES.FROYO) - public void smoothScrollToPosition(int position) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { - mList.smoothScrollToPosition(position); - } else { - int offset = mAdapter == null ? 0 : getHeaderOverlap(position); - offset -= mClippingToPadding ? 0 : mPaddingTop; - mList.smoothScrollToPositionFromTop(position, offset); - } - } - - @TargetApi(Build.VERSION_CODES.FROYO) - public void smoothScrollToPosition(int position, int boundPosition) { - requireSdkVersion(Build.VERSION_CODES.FROYO); - mList.smoothScrollToPosition(position, boundPosition); - } - - @TargetApi(Build.VERSION_CODES.HONEYCOMB) - public void smoothScrollToPositionFromTop(int position, int offset) { - requireSdkVersion(Build.VERSION_CODES.HONEYCOMB); - offset += mAdapter == null ? 0 : getHeaderOverlap(position); - offset -= mClippingToPadding ? 0 : mPaddingTop; - mList.smoothScrollToPositionFromTop(position, offset); - } - - @TargetApi(Build.VERSION_CODES.HONEYCOMB) - public void smoothScrollToPositionFromTop(int position, int offset, - int duration) { - requireSdkVersion(Build.VERSION_CODES.HONEYCOMB); - offset += mAdapter == null ? 0 : getHeaderOverlap(position); - offset -= mClippingToPadding ? 0 : mPaddingTop; - mList.smoothScrollToPositionFromTop(position, offset, duration); - } - - public void setSelection(int position) { - setSelectionFromTop(position, 0); - } - - public void setSelectionAfterHeaderView() { - mList.setSelectionAfterHeaderView(); - } - - public void setSelectionFromTop(int position, int y) { - y += mAdapter == null ? 0 : getHeaderOverlap(position); - y -= mClippingToPadding ? 0 : mPaddingTop; - mList.setSelectionFromTop(position, y); - } - - public void setSelector(Drawable sel) { - mList.setSelector(sel); - } - - public void setSelector(int resID) { - mList.setSelector(resID); - } - - public int getFirstVisiblePosition() { - return mList.getFirstVisiblePosition(); - } - - public int getLastVisiblePosition() { - return mList.getLastVisiblePosition(); - } - - public void setChoiceMode(int choiceMode) { - mList.setChoiceMode(choiceMode); - } - - public void setItemChecked(int position, boolean value) { - mList.setItemChecked(position, value); - } - - @TargetApi(Build.VERSION_CODES.HONEYCOMB) - public int getCheckedItemCount() { - requireSdkVersion(Build.VERSION_CODES.HONEYCOMB); - return mList.getCheckedItemCount(); - } - - @TargetApi(Build.VERSION_CODES.FROYO) - public long[] getCheckedItemIds() { - requireSdkVersion(Build.VERSION_CODES.FROYO); - return mList.getCheckedItemIds(); - } - - public int getCheckedItemPosition() { - return mList.getCheckedItemPosition(); - } - - public SparseBooleanArray getCheckedItemPositions() { - return mList.getCheckedItemPositions(); - } - - public int getCount() { - return mList.getCount(); - } - - public Object getItemAtPosition(int position) { - return mList.getItemAtPosition(position); - } - - public long getItemIdAtPosition(int position) { - return mList.getItemIdAtPosition(position); - } - - public void setOnCreateContextMenuListener(OnCreateContextMenuListener l) { - mList.setOnCreateContextMenuListener(l); - } - - public boolean showContextMenu() { - return mList.showContextMenu(); - } - - public void invalidateViews() { - mList.invalidateViews(); - } - - @Override - public void setClipToPadding(boolean clipToPadding) { - if (mList != null) { - mList.setClipToPadding(clipToPadding); - } - mClippingToPadding = clipToPadding; - } - - @Override - public void setPadding(int left, int top, int right, int bottom) { - mPaddingLeft = left; - mPaddingTop = top; - mPaddingRight = right; - mPaddingBottom = bottom; - - if (mList != null) { - mList.setPadding(left, top, right, bottom); - } - super.setPadding(0, 0, 0, 0); - requestLayout(); - } - - @Override - public int getPaddingLeft() { - return mPaddingLeft; - } - - @Override - public int getPaddingTop() { - return mPaddingTop; - } - - @Override - public int getPaddingRight() { - return mPaddingRight; - } - - @Override - public int getPaddingBottom() { - return mPaddingBottom; - } - - public void setFastScrollEnabled(boolean fastScrollEnabled) { - mList.setFastScrollEnabled(fastScrollEnabled); - } - - /** - * @see android.widget.AbsListView#setFastScrollAlwaysVisible(boolean) - * @throws ApiLevelTooLowException on pre-Honeycomb device. - */ - @TargetApi(Build.VERSION_CODES.HONEYCOMB) - public void setFastScrollAlwaysVisible(boolean alwaysVisible) { - requireSdkVersion(Build.VERSION_CODES.HONEYCOMB); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - mList.setFastScrollAlwaysVisible(alwaysVisible); - } - } - - /** - * @see android.widget.AbsListView#isFastScrollAlwaysVisible() - * @return true if the fast scroller will always show. False on pre-Honeycomb devices. - */ - @TargetApi(Build.VERSION_CODES.HONEYCOMB) - public boolean isFastScrollAlwaysVisible(){ - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { - return false; - } - return mList.isFastScrollAlwaysVisible(); - } - - private void requireSdkVersion(int versionCode) { - if (Build.VERSION.SDK_INT < versionCode) { - throw new ApiLevelTooLowException(versionCode); - } - } - - public int getPositionForView(View view) { - return mList.getPositionForView(view); - } - - - - - - private int mTotalCount; - private int mItemOffsetY[]; - private boolean scrollIsComputed = false; - private int mHeight; - - - public int getListHeight() { - return mHeight; - } - - public void computeScrollY() { - mHeight = 0; - mTotalCount = getAdapter().getCount(); - - int sectionHeight = 0; - int itemHeight = 0; - int desiredWidth = MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST); - - if (mItemOffsetY == null) { - mItemOffsetY = new int[mTotalCount]; - } - for (int i = 0; i < mTotalCount; ++i) { - - if (i == 0) { - View view = getAdapter().getView(i, null, this); - view.measure(desiredWidth, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); - sectionHeight = view.getMeasuredHeight(); - mItemOffsetY[i] = mHeight; - mHeight += sectionHeight; - } else if (i == 1) { - View view = getAdapter().getView(i, null, this); - view.measure(desiredWidth, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); - itemHeight = view.getMeasuredHeight(); - mItemOffsetY[i] = mHeight; - mHeight += itemHeight; - } else { - int type = getAdapter().getItemViewType(i); - switch (type) { - case ContactsAdapter.TYPE_CONTACT: - mHeight += itemHeight; - case ContactsAdapter.TYPE_HEADER: - mHeight += sectionHeight; - } - mItemOffsetY[i] = mHeight; - mHeight += sectionHeight; - } - - System.out.println(mHeight); - } - scrollIsComputed = true; - } - - public boolean scrollYIsComputed() { - return scrollIsComputed; - } - - public int getComputedScrollY() { - int pos, nScrollY, nItemY; - View view = null; - pos = getFirstVisiblePosition(); - view = getChildAt(0); - nItemY = view.getTop(); - nScrollY = mItemOffsetY[pos] - nItemY; - return nScrollY; - } - -} diff --git a/ring-android/app/src/main/java/cx/ring/views/stickylistheaders/WrapperView.java b/ring-android/app/src/main/java/cx/ring/views/stickylistheaders/WrapperView.java deleted file mode 100644 index 9ee36ad50..000000000 --- a/ring-android/app/src/main/java/cx/ring/views/stickylistheaders/WrapperView.java +++ /dev/null @@ -1,150 +0,0 @@ -package cx.ring.views.stickylistheaders; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewParent; - -/** - * - * the view that wrapps a divider header and a normal list item. The listview sees this as 1 item - * - * @author Emil Sjölander - */ -public class WrapperView extends ViewGroup { - - View mItem; - Drawable mDivider; - int mDividerHeight; - View mHeader; - int mItemTop; - - WrapperView(Context c) { - super(c); - } - - public boolean hasHeader() { - return mHeader != null; - } - - public View getItem() { - return mItem; - } - - public View getHeader() { - return mHeader; - } - - void update(View item, View header, Drawable divider, int dividerHeight) { - - //every wrapperview must have a list item - if (item == null) { - throw new NullPointerException("List view item must not be null."); - } - - //only remove the current item if it is not the same as the new item. this can happen if wrapping a recycled view - if (this.mItem != item) { - removeView(this.mItem); - this.mItem = item; - final ViewParent parent = item.getParent(); - if(parent != null && parent != this) { - if(parent instanceof ViewGroup) { - ((ViewGroup) parent).removeView(item); - } - } - addView(item); - } - - //same logik as above but for the header - if (this.mHeader != header) { - if (this.mHeader != null) { - removeView(this.mHeader); - } - this.mHeader = header; - if (header != null) { - addView(header); - } - } - - if (this.mDivider != divider) { - this.mDivider = divider; - this.mDividerHeight = dividerHeight; - invalidate(); - } - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int measuredWidth = MeasureSpec.getSize(widthMeasureSpec); - int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(measuredWidth, - MeasureSpec.EXACTLY); - int measuredHeight = 0; - - //measure header or divider. when there is a header visible it acts as the divider - if (mHeader != null) { - ViewGroup.LayoutParams params = mHeader.getLayoutParams(); - if (params != null && params.height > 0) { - mHeader.measure(childWidthMeasureSpec, - MeasureSpec.makeMeasureSpec(params.height, MeasureSpec.EXACTLY)); - } else { - mHeader.measure(childWidthMeasureSpec, - MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); - } - measuredHeight += mHeader.getMeasuredHeight(); - } else if (mDivider != null) { - measuredHeight += mDividerHeight; - } - - //measure item - ViewGroup.LayoutParams params = mItem.getLayoutParams(); - if (params != null && params.height > 0) { - mItem.measure(childWidthMeasureSpec, - MeasureSpec.makeMeasureSpec(params.height, MeasureSpec.EXACTLY)); - } else { - mItem.measure(childWidthMeasureSpec, - MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); - } - measuredHeight += mItem.getMeasuredHeight(); - - setMeasuredDimension(measuredWidth, measuredHeight); - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - - l = 0; - t = 0; - r = getWidth(); - b = getHeight(); - - if (mHeader != null) { - int headerHeight = mHeader.getMeasuredHeight(); - mHeader.layout(l, t, r, headerHeight); - mItemTop = headerHeight; - mItem.layout(l, headerHeight, r, b); - } else if (mDivider != null) { - mDivider.setBounds(l, t, r, mDividerHeight); - mItemTop = mDividerHeight; - mItem.layout(l, mDividerHeight, r, b); - } else { - mItemTop = t; - mItem.layout(l, t, r, b); - } - } - - @Override - protected void dispatchDraw(Canvas canvas) { - super.dispatchDraw(canvas); - if (mHeader == null && mDivider != null) { - // Drawable.setBounds() does not seem to work pre-honeycomb. So have - // to do this instead - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { - canvas.clipRect(0, 0, getWidth(), mDividerHeight); - } - mDivider.draw(canvas); - } - } -} diff --git a/ring-android/app/src/main/java/cx/ring/views/stickylistheaders/WrapperViewList.java b/ring-android/app/src/main/java/cx/ring/views/stickylistheaders/WrapperViewList.java deleted file mode 100644 index c0c95d7e6..000000000 --- a/ring-android/app/src/main/java/cx/ring/views/stickylistheaders/WrapperViewList.java +++ /dev/null @@ -1,176 +0,0 @@ -package cx.ring.views.stickylistheaders; - -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.List; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Rect; -import android.os.Build; -import android.util.AttributeSet; -import android.view.View; -import android.widget.AbsListView; -import android.widget.ListView; - -class WrapperViewList extends ListView { - - interface LifeCycleListener { - void onDispatchDrawOccurred(Canvas canvas); - } - - private LifeCycleListener mLifeCycleListener; - private List<View> mFooterViews; - private int mTopClippingLength; - private Rect mSelectorRect = new Rect();// for if reflection fails - private Field mSelectorPositionField; - private boolean mClippingToPadding = true; - - public WrapperViewList(Context context, AttributeSet attrs) { - super(context, attrs); - - // Use reflection to be able to change the size/position of the list - // selector so it does not come under/over the header - try { - Field selectorRectField = AbsListView.class.getDeclaredField("mSelectorRect"); - selectorRectField.setAccessible(true); - mSelectorRect = (Rect) selectorRectField.get(this); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { - mSelectorPositionField = AbsListView.class.getDeclaredField("mSelectorPosition"); - mSelectorPositionField.setAccessible(true); - } - } catch (NoSuchFieldException e) { - e.printStackTrace(); - } catch (IllegalArgumentException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } - } - - @Override - public boolean performItemClick(View view, int position, long id) { - if (view instanceof WrapperView) { - view = ((WrapperView) view).mItem; - } - return super.performItemClick(view, position, id); - } - - private void positionSelectorRect() { - if (!mSelectorRect.isEmpty()) { - int selectorPosition = getSelectorPosition(); - if (selectorPosition >= 0) { - int firstVisibleItem = getFixedFirstVisibleItem(); - View v = getChildAt(selectorPosition - firstVisibleItem); - if (v instanceof WrapperView) { - WrapperView wrapper = ((WrapperView) v); - mSelectorRect.top = wrapper.getTop() + wrapper.mItemTop; - } - } - } - } - - private int getSelectorPosition() { - if (mSelectorPositionField == null) { // not all supported andorid - // version have this variable - for (int i = 0; i < getChildCount(); i++) { - if (getChildAt(i).getBottom() == mSelectorRect.bottom) { - return i + getFixedFirstVisibleItem(); - } - } - } else { - try { - return mSelectorPositionField.getInt(this); - } catch (IllegalArgumentException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } - } - return -1; - } - - @Override - protected void dispatchDraw(Canvas canvas) { - positionSelectorRect(); - if (mTopClippingLength != 0) { - canvas.save(); - Rect clipping = canvas.getClipBounds(); - clipping.top = mTopClippingLength; - canvas.clipRect(clipping); - super.dispatchDraw(canvas); - canvas.restore(); - } else { - super.dispatchDraw(canvas); - } - mLifeCycleListener.onDispatchDrawOccurred(canvas); - } - - void setLifeCycleListener(LifeCycleListener lifeCycleListener) { - mLifeCycleListener = lifeCycleListener; - } - - @Override - public void addFooterView(View v) { - super.addFooterView(v); - if (mFooterViews == null) { - mFooterViews = new ArrayList<View>(); - } - mFooterViews.add(v); - } - - @Override - public boolean removeFooterView(View v) { - if (super.removeFooterView(v)) { - mFooterViews.remove(v); - return true; - } - return false; - } - - boolean containsFooterView(View v) { - if (mFooterViews == null) { - return false; - } - return mFooterViews.contains(v); - } - - void setTopClippingLength(int topClipping) { - mTopClippingLength = topClipping; - } - - int getFixedFirstVisibleItem() { - int firstVisibleItem = getFirstVisiblePosition(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - return firstVisibleItem; - } - - // first getFirstVisiblePosition() reports items - // outside the view sometimes on old versions of android - for (int i = 0; i < getChildCount(); i++) { - if (getChildAt(i).getBottom() >= 0) { - firstVisibleItem += i; - break; - } - } - - // work around to fix bug with firstVisibleItem being to high - // because list view does not take clipToPadding=false into account - // on old versions of android - if (!mClippingToPadding && getPaddingTop() > 0 && firstVisibleItem > 0) { - if (getChildAt(0).getTop() > 0) { - firstVisibleItem -= 1; - } - } - - return firstVisibleItem; - } - - @Override - public void setClipToPadding(boolean clipToPadding) { - mClippingToPadding = clipToPadding; - super.setClipToPadding(clipToPadding); - } - -} diff --git a/ring-android/app/src/main/jni/Android.mk b/ring-android/app/src/main/jni/Android.mk index 37b98fd96..7f63ff540 100644 --- a/ring-android/app/src/main/jni/Android.mk +++ b/ring-android/app/src/main/jni/Android.mk @@ -43,7 +43,6 @@ MY_DATADIR=/data/data ARCH=$(ANDROID_ABI) CPP_STATIC= $(ANDROID_NDK)/sources/cxx-stl/gnu-libstdc++$(CXXSTL)/libs/$(ARCH)/libgnustl_static.a \ - $(RING_CONTRIB)/lib/libexpat.a \ $(RING_CONTRIB)/lib/libgnutls.a \ $(RING_CONTRIB)/lib/libnettle.a \ $(RING_CONTRIB)/lib/libhogweed.a \ @@ -82,12 +81,14 @@ LOCAL_C_INCLUDES += $(LOCAL_PATH) \ LOCAL_MODULE := libringjni +LOCAL_CFLAGS += -fpic + LOCAL_CPPFLAGS += -DCCPP_PREFIX \ -DPROGSHAREDIR=\"${MY_DATADIR}/ring\" \ -DHAVE_CONFIG_H \ -DHAVE_SPEEX_CODEC \ -DHAVE_GSM_CODEC \ - -w -frtti \ + -w -frtti -fpic \ -std=c++11 -fexceptions -fpermissive \ -DAPP_NAME=\"Ring\" \ -DSWIG_JAVA_ATTACH_CURRENT_THREAD_AS_DAEMON \ @@ -127,7 +128,7 @@ LOCAL_LDLIBS += -lpj-arm-unknown-linux-androideabi \ -lresample-arm-unknown-linux-androideabi endif -LOCAL_LDLIBS += -lexpat -lhogweed \ +LOCAL_LDLIBS += -lhogweed \ -lspeexdsp -lvorbisfile -lyaml-cpp \ -lFLAC -liax -lnettle \ -logg \ diff --git a/ring-android/app/src/main/jni/Application.mk b/ring-android/app/src/main/jni/Application.mk index 687227df3..fe89ddc37 100644 --- a/ring-android/app/src/main/jni/Application.mk +++ b/ring-android/app/src/main/jni/Application.mk @@ -30,7 +30,7 @@ -APP_PLATFORM := android-15 +APP_PLATFORM := android-16 APP_OPTIM := debug APP_ABI := ${ANDROID_ABI} NDK_TOOLCHAIN_VERSION := 4.9 diff --git a/ring-android/app/src/main/res/drawable-hdpi/ic_action_end_call.png b/ring-android/app/src/main/res/drawable-hdpi/ic_action_end_call.png deleted file mode 100644 index c807fe94bde1f0ada7e592bb7b5710f4f7f87175..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 442 zcmeAS@N?(olHy`uVBq!ia0y~yU@!n-4mJh`hH$2z?F<YIk|nMYCBgY=CFO}lsSJ)O z`AMk?p1FzXsX?iUDV2pMQ*9U+7-KwL978H@y_tTt_mF`=>)VX}B@F=#yh|7~61F#7 zy|<8&YeBCr!$k(x7N$!L+#IY2Y^}wFPcpg79amI%`+Ld7h>4H)JpZr3$^ZsG*1woC z{r0xAnz|Ox=7>)3-u+6P>u~C9>2q$`wKI<}aK{BskvZxiTe)n7<thnhE2l4$K8u8i zsCP=r3j8}eaeqcv^hb8-TDOw^3hU|h@fvs1Q@qt)EnpPN7k^OK@FR~;Tm62p)lQj& zN9{f)?+)|(A1!V9p?v<b)}bZ0Bemng=QouXU)#nlIpN!xi7oFZefu!`V^@#H!cO+w znad}YWb3%kd3<RbS0|$;tNu5Y9lZyidrtfDcfDYh{mR~)MGNbrq$AI7)JrvNYMfzq zPKf!ebiAGXmY>@Kg(a@N3(Nd*Zqlh^mN%X?vgWF&7nb+#*z|Z-`?`;^PtP})>`2@7 o>C7xuUbcEia5yk9oSx6X%#ddk$MF6&moG@n)78&qol`;+05syZxBvhE diff --git a/ring-android/app/src/main/res/drawable-hdpi/ic_call_end_black_24dp.png b/ring-android/app/src/main/res/drawable-hdpi/ic_call_end_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..3f193b16abe85c5a9fd67a141cfd0dc32e6af8e7 GIT binary patch literal 303 zcmeAS@N?(olHy`uVBq!ia0y~yU{C>J4i*Lm25-&)VFm_<Z=NoWAr*{o&+YY6P84Z) zm_8-b)j;s#F=>II8(rPYHf-7=+B|isz#AovD2|PH%cd_8kkQe7(>s09q+{|do%S2g z)}(kaB|WxTZhe2|^oghc8cnoJ6%92t4UG;v+tsrmBl=O?_VahF!VOa!PIPhS?_yr2 zyisS;!~WS<elDK2Y?m|pE2*e?!SAn6<M6fimVFy-pk}h}iACx4kPFGa)<1qqMm}pk zZJnyH>3MMZlmG+yGS@jQuOB)+l)3nG+6C=iyVvEyn?!!M{S)?XO1-Ui^GyZEK93_U zEBCAM27U0KadCFlro0I0xI;WgUg=HkNxC>Scw>y-_vU>JbyKfRlYLXMhk=2C!PC{x JWt~$(69A(ugAf1! literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-hdpi/ic_call_end_black_36dp.png b/ring-android/app/src/main/res/drawable-hdpi/ic_call_end_black_36dp.png new file mode 100644 index 0000000000000000000000000000000000000000..cac21f2190d82e689f071635b86dd526979fd77d GIT binary patch literal 403 zcmeAS@N?(olHy`uVBq!ia0y~yU@!w=4i*Lm1~s2WVGIn6QJyZ2Ar*{oFWF`XCrTWD zs9mzU^n%)jn+Ff>l9mw>=giuw`@?9Lg~ki>04*s|-j2zAN_^|~)wJ{Nd((7v%C0EU z?gXyAZyzYMKR&FuoUQfwuk+^npMKLnu}SCjCd1%mTV-#=+}@&@ye;B#VXVGRq<AZ@ zPUX7nV@qazi~4Y?ZN&y&{n(Ai1-9*e&?}L&ey;h#P1B<HbxHkRw3;(hSZqz_q`lv- z$coRsR+v#Nn;vuN^BMNQP5FA#(+#p+MYg?atkl)iEq)g*+WPgZhUv>rjho4#e_z(M zH~kHB`5W=_*EhxkmaiYJKYHju<bsE70?~5X?^dyyAN5FjD0SamEc)@|6G^9KOa(e) z!>{H}yumMPG4aPySL@KLTV{C3GSARG`*rS>09oGUI?IEf?%aKhH+gpTOOZ2wr~Q7i z!K&}a9Iu0a*58TVeP!$I-633jRZEtyw2P{e%lhq~5;-k3@|$ps(!*UB#P+OWU|?YI MboFyt=akR{0DoP&QUCw| literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-hdpi/ic_call_end_white_24dp.png b/ring-android/app/src/main/res/drawable-hdpi/ic_call_end_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..625b827c44e7d15ce385221dbea4c1733c5ea8f7 GIT binary patch literal 314 zcmeAS@N?(olHy`uVBq!ia0y~yU{C>J4i*Lm25-&)VFm_9Mo$;VkP61PXKeMI0~wBe zd_G~F(#5U~KbR)*g(;R8NUmLED5=`p+hX9c?yA=<$4AF9HauRe5WB}k>FcNKO9Y>P zx%0Z>_MHF5wx|6!-uTaTm_u~cm5A*1$CQkDk^{cA>=*K05;OU#s(Xkehhtl2U!_t= z_Y_YbIg_KTW!vXmR!E%8EA;AZdH-EWVLOu(ie;~RE;jGF<~=j_jg&}k#x%7&XN~m5 zJ7NQ0I<$qxJv=V3XG4>q^^aDC{kj)_3BL%u^09iEzPQ%JtBo2H9$iaetlgBFSKzJL zk(bq9ClWEScDm!CW$$J<$KSExm*)|BA99E@^-FJW@u|Fa$4B4016q_s?nyg5%QtiR Tet04S0|SGntDnm{r-UW|F|&n8 literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-hdpi/ic_call_white_24dp.png b/ring-android/app/src/main/res/drawable-hdpi/ic_call_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..4dc5065155baeba719d76845d4398431c289cde0 GIT binary patch literal 340 zcmeAS@N?(olHy`uVBq!ia0y~yU{C>J4i*Lm25-&)VFm_9X-^l&5D(tB2RG_D2QnOg z_}{j(#kIYw#lXX+CCER)p!|W_TxoCJh;tXpUo6<UQo_Kag27N=W0&~^sjS50j6XYe z&ib<L%3I6q{r*y?d7LG?k0xylIeC&d_0p`O!*-eGMThw^`HP%AZfQ5hZ1CI9x-(%~ zo$5}72Q#;{-6`<7weF6<rPX~CrHg7VgdRU3yIkd!#LtAOH`Kj99W~u({Bw~*lJmX{ zfox-S`zM)#yOV?JG%hKY9c=j%pjdcvN>ze|!lENejhhy_J(^ef_kh5~ug8?P%S^mn zz;eZ7&xV!x5=v~LU+uL@JmzfnIg-C-(*lp~hif(|o>x2^k?P*4{V&N>D$L^HT*t7* v$+L84*<93~Vx1O|Je#Y!MWQ$L8|x4MzgFurmu_QVU|{fc^>bP0l+XkKc@&W~ literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-hdpi/ic_chat_white_24dp.png b/ring-android/app/src/main/res/drawable-hdpi/ic_chat_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..d2cfdf1f6b334c05a8fa691c25ca0f91ffb179c7 GIT binary patch literal 168 zcmeAS@N?(olHy`uVBq!ia0y~yU{C>J4i*Lm25-&)VFm_<d`}n05Rc<`uR8KI7;vy0 zuzk7xcaz$lDL<ONrma~Xbxq>NJ#|f8h7kU8sk!;Z7k2bcC^R*5+Q;*ggLM+etg5GT zCak-W`$%Q+AK>rXHRJT&}teZ^a@6F29-esN7<BkRgUy%k5px3w|4@F*l@J!5H2 XS-1Un-UC$z1_lOCS3j3^P6<r_<~2e` literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-hdpi/ic_lock_white_24dp.png b/ring-android/app/src/main/res/drawable-hdpi/ic_lock_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..cd4f04aa1d0283908902ad51f4a9289d5ef7dc13 GIT binary patch literal 309 zcmeAS@N?(olHy`uVBq!ia0y~yU{C>J4i*Lm25-&)VFm_<Kb|g*Ar*{ouNZn62THJB zXplIlu*Bn^U4&N0uBjbjW=;jnnOjbCwfRUl{SRw-AhxCNSd~X&OJ?7MB!|N-l{SK{ zcdplppEEhU_HTem>DRkf*Mp+JUz?h%Y}s&UZv7!e_GSB?T}{-uV#dmyY#Z)&?|_dr zk5r@k+((x_vK&(k2<~e=xBT_K@+VPO7v`PZ{=a$is{r{gVehU#SY~CyAg=x0+u%p{ zg}#twUeAKQ7HNo1+9GqRMMb;aX>()4N|#5LV!O8Q&XH0pybySC?h(gm*X^?Gvf_6$ zWlSzcm{rR?Qi$W>J!@9~t=#!@hizBHi6t_t!tcHD3EFq_;rnoB`FZLe=SwDjb7Wv( OVDNPHb6Mw<&;$VQj)aN; literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-hdpi/ic_send_black_24dp.png b/ring-android/app/src/main/res/drawable-hdpi/ic_send_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..7be098fdeaff304a7a5d9c6b86d43035e540c26c GIT binary patch literal 250 zcmeAS@N?(olHy`uVBq!ia0y~yU{C>J4i*Lm25-&)VFm_<gPtyqAr*{ouO8%Wa*%L& zn7)bCgE48*)f;jzBHu7Y9W>g;VDsQ%Kc}#|_nrq855H{Gceu0hk{Ltni<#FN)ZFs! zoO~+a@XYaa1mD?k=3aTJg$BkU4rMEvN>*^b43NIGP^!q8HKm1Z#UqhA(~}&c^0|xp z45sq&PMBep&+%8xiv7h0#@LtB79260*Z9{d%Kz{Ozu@m8r%SfjaLfOfN#5gb<>uhl z)9^-u_oamVB}1W4Nh~T!J|{XZPU!pjHh$YZ`6|X)oF%H4WN*qaFfcH9y85}Sb4q9e E0MRjE*8l(j literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-hdpi/ic_share_white_24dp.png b/ring-android/app/src/main/res/drawable-hdpi/ic_share_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..b09a6926de5aa48dee59265aadac32da236f9e1c GIT binary patch literal 397 zcmeAS@N?(olHy`uVBq!ia0y~yU{C>J4i*Lm25-&)VFm`q5KkA!5D(tB2W_(_2TCwp zP!A~)?9{8{Q4-@i$Ca7&Qenr72P#S-iuDJx94>ekyj0@Yb;a>7bI=8o7jFWtH@Upl z*pV5ed+C~t^dau2WhbkxUs($N7d=yves|aQWy<qk|I^6XDOM?SadYtXhbI;)mKqoB z&@a_L75p*cmD`l1@0zdhU)4CwssEMtOO95~PN~Jk2V%3?e$BI7weIco`O|!kzKGeF z^<r<ap;c+|tYt~Fjc>K>n-$s_xH3y~i_NCY2ix2E+$5vQs#9%F1y29<B56vxUzT1` zhS%LLnRDKyF<mmZ%BpjfP6cgUvLWMMTO;phKG(=sdApO1w$1!-?1DM()EnH>YmK(# z7=1fb=WEMct1n}ocQ9R++s{3+uX0^}g0KI=)nZRqMox<|6??j}Lcvtn(qu`Rayxg> z<%x&w;@?~`6sx>aBD&97`sbWaSGq*~=k(Mcj(@Pmyux%{^fv|u1_n=8KbLh*2~7Zv CvaJsQ literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-hdpi/ic_videocam_white_24dp.png b/ring-android/app/src/main/res/drawable-hdpi/ic_videocam_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..d83e0d50c3dd1aa384568f658f815b35819462a0 GIT binary patch literal 173 zcmeAS@N?(olHy`uVBq!ia0y~yU{C>J4i*Lm25-&)VFm_<5>FS$5Rc=@2@<S}6AYUE zzxpr#Po2%?&-%6{-OQI4%=&+%DQkyZ|9{aLY^DOa0nB%1$QzikvNiVp^xx<hkgQ?K zk;l`@CfgwGywEX%sqTtG6sy_QMf0mTCv`=##4lSKWw_|uf5yOLcYd#L+MXZ{vV?(Q Z`>EcOA$prnGcYhPc)I$ztaD0e0stz4Jfr{s literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-ldrtl-hdpi/ic_send_black_24dp.png b/ring-android/app/src/main/res/drawable-ldrtl-hdpi/ic_send_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..e65515d62b7a9a164a64a2ccd02cc013725d5d93 GIT binary patch literal 250 zcmeAS@N?(olHy`uVBq!ia0y~yU{C>J4i*Lm25-&)VFm_<gPtyqAr*{ouN~xVau8^J zXwDkYwuLFSBfa(88aLsM3_1<X>W|m_R6cpQBFW<X`8sJ~&c7MkE=>O$_I&F?FLv2b z?Q1PrUvI6hT7BpcPgdM)H;u#x^9~=BP&_b0@le5u18{Uks?_)V6esQ(D<fn~4{u~} ze&4b%VEJYD7xRp!KMh!BDs{!kxBpR%`Ld@sSB4*-zPIF_kxIj@{mKo$6kC2NHh@vf uFU7W5ifu(F8j4Od9Gm}t)Asj^)pc+Bs2&UL`NP1#z~JfX=d#Wzp$Py$ZEp<# literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-ldrtl-mdpi/ic_send_black_24dp.png b/ring-android/app/src/main/res/drawable-ldrtl-mdpi/ic_send_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..d65d82294b084f83fb9395fb0b06e000b42a6191 GIT binary patch literal 204 zcmeAS@N?(olHy`uVBq!ia0y~yV2}V|4i*Lm2CurW#S9D#lRaG=Ln;{0UfIZd$Uws7 z;q<084r-m^jcXdD_ZWJ#Fdkyi)A-jU{A+2m-t=qp|J+G=aQ-cOk4}u{{>IKzt(+1z z8YX@{416v}co>*`Si~|~n57z-D_zo`>fIM}P&WH=z{_7Raap0-0dCfQ83vh^S5#hV z{t@$>FB_5Z@8bQ-4*ZuK_)RV_2)}4#i<-b9cH^P{174>oMv|UCV@@$JFfe$!`njxg HN@xNArX5Hk literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-ldrtl-xhdpi/ic_send_black_24dp.png b/ring-android/app/src/main/res/drawable-ldrtl-xhdpi/ic_send_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..3a25fbb866a157759a4c33cad2d9feda224b0e1e GIT binary patch literal 341 zcmeAS@N?(olHy`uVBq!ia0y~yU@!n-4i*LmhQHi~JPZtsGM+AuAr*{ouLpV^c93v= z7`*hR)1=lJ+tw{!Yi)4(P|9(xYphpTMMPU14!M5eH_zW$`+mC2DT7$U&teITYHJ^K z8t<-bm?eCEN=?yw_Og_3YZ!SNTu$0tIL~!OCCcmA??>ktb6)<^$a7dM(bzTFt&@49 z#;&PMA2rNC0=hGs-3l34-Hmvym=^^09B9*%Yn2v{W1qj8>7vH<i{f!R7D_XSc23j! z)?>x`OQX8$hw(r0jJj3Y9}Jo9En{X-?fl}t=sV+gA<rxMXD9Zx-50O2XT6ZH>%ebK zfgiq+eO=9LpXJM>H?b8nNuA+;Vfcvsi^3ev3km<CTiHG{OPygqkrg%R|Em=TckR^M tU-kb3bJg`SW~n!49)Hp=W{mmGcHn)H;Xlb_M+OE422WQ%mvv4FO#oeijYI$d literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-ldrtl-xxhdpi/ic_send_black_24dp.png b/ring-android/app/src/main/res/drawable-ldrtl-xxhdpi/ic_send_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..89afc01b5cfe42c07deefa9861ac6ce686b1b70d GIT binary patch literal 414 zcmeAS@N?(olHy`uVBq!ia0y~yVDJE84i*LmhW}5h&oD4Brh2+KhEy=Vonf!%5-8*5 zZ@6s!DWB6q9p+Wj9Ga)8UicZjktIt+r*W3RG>2@hEam)pE?;ML-l@5NHovQSAMcz$ z=X^dD&D7;;by|2KwcS0fg;BjHV};_p&~=iR|E%rn|Kf8wN>=jaj6iSIkc=B$PXsS1 z``%Qso20hH{gmz!_e-r`Ja$cx(M)_e(TMx&?TL$8+hPot38r^#IV;?o`po0knTcOs zPCV1yzl1aNtw*Go+{<?JcNXdMd8fT_b@}zARod@nYA>r*mC9${7LP-`DZgYsPW;Qp zIdR`t=^qgfKgBQ9kK6gQ$M#y}66dmKmOftJ&e@8~Y&dCTYh-vl>{ox({`qnN`{)0+ z7cbnOSLN`xuF9dd%Hi{uhG(83l1!&|Fu$4sNBj4co|(TdQc{I|&Vt44S6aU`?3$pt z;GfPLeu)nSdMs-$US$5&`lX>x@vrPT{U!T<{%(EJ!0)p>KtqIU|A{-3G(*b|$ucl7 OFnGH9xvX<aXaWGVBD~H3 literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-ldrtl-xxxhdpi/ic_send_black_24dp.png b/ring-android/app/src/main/res/drawable-ldrtl-xxxhdpi/ic_send_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..def070fd7fad796ee6d7e18f40132e919fd707b1 GIT binary patch literal 551 zcmeAS@N?(olHy`uVBq!ia0y~yU`POA4i*Lm29JsRH#0CWzVmc(45?szJImTH+EK#I zf8vd;+6JmRj4d0pBbYJ|TsSDS;IIu>1k;s0F4HDV@tk-g(&^H(K$WjsC&#F89SQeo z-1+Ur<bR*nKR6>Z!SuU2N0S0alfs4t=gi~{Q~QML0)rAf+N0a1@CW~JHC|9-)EaDZ z{Ld1X^cUj2GacsY{+e=9@|SC}(uId=g1zTXXi70N_8#+DFt<<5_K&Ll6%#4_Mm6_8 z3$7FjUdUL(l2!84C-UH*&0T8y9cE{DE@NWcpHcEp+GTRM!{UuX7c$PU%#z765uS4@ z@?ypu785Ds#+#aMi<2dK`w~s8$}aZ&IAwkAX7XHDsmE@`rbjdCj34v`mqnz`3)k;W zo9xe)KBrIZuw=NC_o9=f#~!;U7aZ%%sH!<%aBQk;a7e+in-R}GMKaa@I23*EMrtps z;E5AF?8)k1dcVhPI4?adW5UGiIZOU^^uAM<opSlF%71msyZ*W$=9VBw$-Dkm8r{Dy z&X=@Yen`G*<}m{e*`o#;M-5hVURaX;Ys(79pLXvSy%#^n^eZe^Yk}iTt_6-e>y}<w z;KOR75zJUS%{<1`gFSUu-sPq5brv{Ea=kFGy*<^K_f&*KxJVAGiN@u#vAm~39Jap( zar_&PL=`nAiBDPBkQXV<l@TBhi4_jHo$T!Qo(EpAwvAz6U|{fc^>bP0l+XkK)p+hz literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-mdpi/ic_action_end_call.png b/ring-android/app/src/main/res/drawable-mdpi/ic_action_end_call.png deleted file mode 100644 index ee026de7c7f77e8b0685edd1e7f69bc9577a27a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 342 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANL}k|nMYCBgY=CFO}lsSJ)O z`AMk?p1FzXsX?iUDV2pMQ*9U+7=C!VIEGZ*dJ}Sy_mF}>>#=yY1B@{ZtP&2-3wo{E zif=IPau9#PVCBHRqMwCl?NhF0e?P?)?G%W5cX#)6;iiKL2LEI?eQ7V0>G!ZI{5vVg z&Rn@6iR<JKi=T6v`7Zvx(|9Gog`rjVl4PQJ&@NXKg-erI0&bK>{?7Z}5WJLaqMhpl z$1~<0(og1|UbV<DaB-2{&X&W9T6r49it8lVvXvL#6Mml=&Yi#0%|$deV9i&j<05UM zs`gt~HMzf)yB}uU+#z#K(m*opkJ1LUjd}_TjNMF36P+5*Nne<`UQ?mqbG1s2@8qC_ uO#T(|VOa;c<R94{NHCD-+0MYsaQ2n^y#0UgCNeNEFnGH9xvX<aXaWGK5Qeq@ diff --git a/ring-android/app/src/main/res/drawable-mdpi/ic_call_end_black_24dp.png b/ring-android/app/src/main/res/drawable-mdpi/ic_call_end_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..3253503d67171641f77f31ddfbe224a30d5357a9 GIT binary patch literal 235 zcmeAS@N?(olHy`uVBq!ia0y~yV2}V|4i*Lm2CurW#S9D#n><|{Ln;{Gp4snpI6#K= zfxcBov0GtqLBUmV0T%Y;8WzVt?fMJ5?K<q5w>WR-ljT}^*ZK=<l%vZO7sZ%&0!@z; zES~)KZ7)9m^?TH7J=<9k8Y0h*$<2wrtgvzY;x9J~ma10^xkOhje6xFl^465dtm{)9 zGj^G=rr&#%zx>s@<nOOo-mUs@SmB<o{(dQ&;y232R`QnKPTN>N-7(IW%RI~^vUL5L odGee_&g}apOIu#~WxkIgDJ8z5j7$GE0|Nttr>mdKI;Vst0FJU<?EnA( literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-mdpi/ic_call_end_black_36dp.png b/ring-android/app/src/main/res/drawable-mdpi/ic_call_end_black_36dp.png new file mode 100644 index 0000000000000000000000000000000000000000..3f193b16abe85c5a9fd67a141cfd0dc32e6af8e7 GIT binary patch literal 303 zcmeAS@N?(olHy`uVBq!ia0y~yU{C>J4i*Lm25-&)VFm_<Z=NoWAr*{o&+YY6P84Z) zm_8-b)j;s#F=>II8(rPYHf-7=+B|isz#AovD2|PH%cd_8kkQe7(>s09q+{|do%S2g z)}(kaB|WxTZhe2|^oghc8cnoJ6%92t4UG;v+tsrmBl=O?_VahF!VOa!PIPhS?_yr2 zyisS;!~WS<elDK2Y?m|pE2*e?!SAn6<M6fimVFy-pk}h}iACx4kPFGa)<1qqMm}pk zZJnyH>3MMZlmG+yGS@jQuOB)+l)3nG+6C=iyVvEyn?!!M{S)?XO1-Ui^GyZEK93_U zEBCAM27U0KadCFlro0I0xI;WgUg=HkNxC>Scw>y-_vU>JbyKfRlYLXMhk=2C!PC{x JWt~$(69A(ugAf1! literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-mdpi/ic_call_end_white_24dp.png b/ring-android/app/src/main/res/drawable-mdpi/ic_call_end_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..378272ffc15f451e392eb042f955e24c02630763 GIT binary patch literal 235 zcmeAS@N?(olHy`uVBq!ia0y~yV2}V|4i*Lm2CurW#S9D#n><|{Lo5W}9^9{Wgpq;$ z!+XOMHH&km96ZIXky>z0dBy4d?^Giy1m-#Nm^C|g-hIFtFzMZ{`^P`Lo*r*M-B<G6 zXHT_%vntuo*;bhveR_0orfu<0&(piSTMN0wuWk8sc2f3I>EB^nd}m(U_UgjgOH+?d zdGjme<x<D^r-%K7)L&I)UI;Ijc@L()i`{P1@)x_5aC7r12ldW%if(;pQg}A|iAvn? n@;pAb_m9){y=Q&?NXM_6{oq@E<jV~V3=9mOu6{1-oD!M<m9}e$ literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-mdpi/ic_call_white_24dp.png b/ring-android/app/src/main/res/drawable-mdpi/ic_call_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..77f9de5e3ccb30fb6e580454412c98e2f6c553c1 GIT binary patch literal 246 zcmeAS@N?(olHy`uVBq!ia0y~yV2}V|4i*Lm2CurW#S9D#dp%toLo5W(9^9+voG8KY z;5$!Jl2u@up35DF$?tqF9Zb!LX*r^zqUYd}zd*2ie#^lJLN6}4`v1Og;<@^k=hDAz z1V5kJ>hFB^M54}17rxq)iBAgjU%K+CPg=Ni#d6)9TLr$Zk(|9F*iBBnBFIfIx+2s~ zuT{@ac|I@m7UlV}AyFTXE_yn{m3^wMYt4lZ3yPkeXiWR#o_PPrOi|wceS43ZY~#J~ z{mt|l=XQD5AIUMBEB#aPRpi#!H+sdD_s$4^-)a6#PFQG7Is*d(gQu&X%Q~loCIDM+ BXXpR` literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-mdpi/ic_chat_white_24dp.png b/ring-android/app/src/main/res/drawable-mdpi/ic_chat_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..dac4cb9fc4d84eea7685251640e7778ac51daaa6 GIT binary patch literal 133 zcmeAS@N?(olHy`uVBq!ia0y~yV2}V|4i*Lm2CurW#S9D#KAtX)Ar_~T6Bcm)kpJ=j zX}!~ftM&CQY==%cK2UXHp4d>tYw_2^l=tvO&nwp*e$P;2h?}tN;nDi|5?<aT=?a&o mG@F`CnC2=i!6wMSpmdkF>ez=Ze;61T7(8A5T-G@yGywqUeJ<Ak literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-mdpi/ic_lock_white_24dp.png b/ring-android/app/src/main/res/drawable-mdpi/ic_lock_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..1127f87f70b7a4ec8a37919cac19e9a8c27b005e GIT binary patch literal 208 zcmeAS@N?(olHy`uVBq!ia0y~yV2}V|4i*Lm2CurW#S9D#(>+}rLo808y>OC~$x(#m zg8i`vlO`MNNL7{SQb=2{?9oBqnaxJWpZf}I%?-G+OY}<iF6&2sB_dZ|oyKPQnR7)* z!k=Fye7m?>FA457iC9>jTdQ+vL73*cwGW@nVOx{`tIeO$x%&L3>JMLz$ZYG^n|x;d z!I0q7I`SsUN?SfVYAZRZ%b(d&dglMj>6;Gd?Yw`ddEVR4JB{{D<IeZ~dZYBe(02v~ O1_n=8KbLh*2~7YvtXbRu literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-mdpi/ic_send_black_24dp.png b/ring-android/app/src/main/res/drawable-mdpi/ic_send_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..83156aa38fc3644b0e394e8ab32ffd916be57ea6 GIT binary patch literal 205 zcmeAS@N?(olHy`uVBq!ia0y~yV2}V|4i*Lm2CurW#S9D#Q#@T9Ln;`LUO33x<RH-c z@O}%^5(dMIeb-OlV!Cl6h;v&5yRDCp+Q(KK{a-siO`L!8<I_4;DRH+jd7H*o?Wspu zH%^sqeb#(KrqND;eU}33EeCeC7nV!Y7hG;+w7YIN!T!Q?2j0Gv4eAG666@6GUgl<> z7_#!t-T8VwHfuJ!wUl0y*~shC!e*2t{epux!^(+y<Nqe{;!rn92JS=87#J8BJYD@< J);T3K0RY@@Ok4l} literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-mdpi/ic_share_white_24dp.png b/ring-android/app/src/main/res/drawable-mdpi/ic_share_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..e944fd70c428619696e7eb6fd8787959df32a774 GIT binary patch literal 268 zcmeAS@N?(olHy`uVBq!ia0y~yV2}V|4i*Lm2CurW#S9D#mpxq^Lo5W39z3tb!YIS= z;QpI~?+(sa{v_4)NyqDy@P@?4l55Il&z|&2tE*@$pM}!WyRv-IS@z2_{j81E_vt^H zpXr%;?ANTo_K<brr6-=a1)iNGzcX>l<qglB7fPq*>^y#BU$L@tY_Vat>Gc`AHppzw z@JuzTmA$`Xqg?6|uJzkg1O3#@4dt))JXI50JZs;f-LaliE?+S=Dp{s`c<$L1r@Op^ zgTuoX>q+N*e0pJ}_<28*MRShynYTV(k#=zTo#ZPIJ>Mz5x-ikY!%C%(hi8lGNB26$ ZkhN?QS&tXkFfcGMc)I$ztaD0e0sydXa#R2S literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-mdpi/ic_videocam_white_24dp.png b/ring-android/app/src/main/res/drawable-mdpi/ic_videocam_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..d146209a5145962cfa3226918807fb663d2c7267 GIT binary patch literal 131 zcmeAS@N?(olHy`uVBq!ia0y~yV2}V|4i*Lm2CurW#URSl#WBR<baH|Ot4s5L_5Y8V z)E8)p1eiWx6nOeye!>FAB{GX9YVZm?U6N7JP#{$(_3v^F^GQFYtCv;SwZC;(HwOO= hT;rK6@_>~=*sgb#$Ez6|7#J8BJYD@<);T3K0RVT~D%Jo1 literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-xhdpi/ic_action_end_call.png b/ring-android/app/src/main/res/drawable-xhdpi/ic_action_end_call.png deleted file mode 100644 index aef6bb0ce4a9486ed31329ced32a9dbc9959398f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 552 zcmeAS@N?(olHy`uVBq!ia0y~yU~m9o4mJh`hE<t`_ZS!$BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFrM;saSW-r^=8Jw!e#>j*NN}-6ht1dX&iWx$K`R# zqv-sTc_I_IVi@+di#RZgFt7$N+$>!p$#-OfkdVR8{IuiWc*NdJ-s04y#DD~TxUUc} zf4S?OgM4+@j7ND-KD8}cmN7Nx>N1vqMROiFx*Hij%vdyGQd4V)%Cfjt2Co^_rdLm@ z8LMbd40@&E;UVqgp#PHn?&a(TcQ?PniKhY%RsQiPjH-;?y@5w4@SKvv`PbpLN4B$Y zS4giG&r<o>V>hpa^}d#lqB!@0CzbQ|pT8l!Y}V}i&NBkj!uof*-R1VwSi)4)zUtnK zMIk%ZJ>B)KAU#Fzyz}{qzG4pMsh{UhyZR$Ji*eJ0gXh2I?EJ;k*L3$pz?7nIQ~zw; zUU%Cr`d@9pEx)3VEPae!w+vetZm*jED)sX9c}my59%$R^azMy-i^pv{mLD~VUQV1D zGPitexe79OPvlr3`~20x!+*+iH&5fp2)E#kxt*iR6yu*H#m^LTdyPtd)1H)?$U6<~ zH@~kv+y2<(#1}7t=sm0N@Xr;!@rKoB(bS)EkKVj<i+v$~_^+zlHt`pGOXmFY)StRy p@r>7sC+9rxKnib$4Uu&$5)AJ+CUSRK1ge8%JYD@<);T3K0RR|3>qGzm diff --git a/ring-android/app/src/main/res/drawable-xhdpi/ic_call_end_black_24dp.png b/ring-android/app/src/main/res/drawable-xhdpi/ic_call_end_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..d10890edcce32dd7b95e691c8e8c9d951c30bb55 GIT binary patch literal 371 zcmeAS@N?(olHy`uVBq!ia0y~yU@!n-4i*LmhQHi~JPZtsR-P`7Ar*{oFKp}*4wPVh zkgs;uZy~?HqN%<S*EzUniEsG8DB>#e)lo&Ekz>(~9L0_##Uw?sjhh8nq;;osd|Sq; z5#_t7)#_H&%<ji6CEu-|&iyYV;W<g=<g=F+uU6fjH#;)o=T~)YZsXwXF&lNSUvd<; z`RMz#zvk<WO7;vr<1ZJ2mR5T4&&dd@yOkY!dQXv;+nqxFR`>h9y>qs%UOnNo=&rK4 zhDK|Dw(7rG5xV1L`iGC{&ay|I`psewxA)26<f#eb{2V6yK1<L323te-x6o|9mPjl1 zly&X>Z`qq-CKozy-Pm|)#@}8(ou3l*-_qVTet+{rsCM7t&b=@GI?4B4^_y~Rn@Qk| zh@-DxtIxAqV#!x6ww_<zYkJDR@MoKh)~ugBLpmcnL2h;XKKFMoBi+x(ewY0?UBz>f c%1_?48Z}4aJ7oPC7#J8lUHx3vIVCg!0M0a_-~a#s literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-xhdpi/ic_call_end_black_36dp.png b/ring-android/app/src/main/res/drawable-xhdpi/ic_call_end_black_36dp.png new file mode 100644 index 0000000000000000000000000000000000000000..44794a9b9d2dbd58656bf4a0d107de3e830dafb4 GIT binary patch literal 515 zcmeAS@N?(olHy`uVBq!ia0y~yVDJE84i*LmhW}5h&oD4Bp7L~Y45?szdwI9_w0xQ4 zAFnT7wzOqMZr}s~-b;=d2H74;oV;2uHn?~PCr=P_6mU%NbyXE|xgucVI&uFrdD&$O zCY=j1inT;#TDY_;*B#n;`0UZQ#fOb!)}Ock@Qgd&bCL=Ksb+tbG|0KK!?XC=)Tw7@ z8)x20HlK4Ty?2t+)fXl&CQ2$6U6zyDu=7)5Y1Lg5-6wa{mCIHK3rfeBsfHxT$8L(N z^Qy8H)Y=ocd&;LHU#e9_woLza#Qk8{`PQCV<7e9(*-JC8FN!<6{%ZEcyxE7(?MObv zubBM*waC(=9h*K}*R6QCCXYAC<km~4V{=ch%T~MHcqabUH23&t|KEP~*S;%iwB1$C zy5-E>OGoCL{ZtT<oU*ap>d&(Y>zyt-O8(BVXHb34vhc)$6?4Oyq7FNSvap6<EeKpO zYrzAX&7q<uhigJvYai|t*xUQ*hMvoNxwUcJfwMO|$>r|j4y<0>dt(W!`_ob*qjb?D zs<syrHFw$cp6T?cT7Ue~)HlYGlPAv&F<aue)MTgF#K{v)mbx$fI{WO2lRi?LqXe&2 zN>68Ad#vT?-{qcxvuuUM%qPxeJ}qnhq$A2T>e&o!zl@MjSonHQn(z86QlszCKMn>4 O1_n=8KbLh*2~7YU6X$UN literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-xhdpi/ic_call_end_white_24dp.png b/ring-android/app/src/main/res/drawable-xhdpi/ic_call_end_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..a4fe6889d159cac861cac4f885ae3ec28cd9ca44 GIT binary patch literal 389 zcmeAS@N?(olHy`uVBq!ia0y~yU@!n-4i*LmhQHi~JPZtsKAtX)Ar*{o&l-9&CCVKC z_<wP?`4w(~>?}cJ=7lxPyn7oKbu6k<RBTBQaP@W;aCLTZHo0+4fJN6-#O>`)j#l60 zd{@mEF09$JdG}`NrsuzJ*UKBW&Oe!AB$zrQ)y#6S&r~+~wWceAmK|a9Uecj(S8d1I z`IdbvKSt}z7<$i_cbNFN!K!Z4&Ga+e3uZj&S`cg0_=>HksPUe*>L;Jcs(r3YOuH>2 zmz@Y+)OOD@GyOo)-11x1O70>rH*q`;$#~zZWGH*$gGah~NBiANH*HtWKVakgBbedU znHt8f&@W9)RoWVfyiUvu6il31Umaq87yF&(4rk`P!tB$+8+xNdCp8&7^0*<=QOTs! zWiscz;K7*|i3}e%I{M9hC%AC(hDZlRyX6r_uDus@RM$$G&~Q57=f3NViYFC!t4xra tme6k7CLqg^%VyBP$dk}e&sf9se8%y!qK{WGGB7YOc)I$ztaD0e0s!;aoi+df literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-xhdpi/ic_call_white_24dp.png b/ring-android/app/src/main/res/drawable-xhdpi/ic_call_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..ef45e933a99b720cc5f6127e6da22bc2fa679244 GIT binary patch literal 420 zcmeAS@N?(olHy`uVBq!ia0y~yU@!n-4i*LmhQHi~JPZts*`6+rArYK!5AM$jPLw(R z@cnHu9SH+(_3ciAN!ogaIwIV$*-a09mU@>>Uo;_fj!JnzjLy<-4!@<&-s=xMlXH5x zQq?<4;pD@|1vW=2?q&S@b8g!6iO-(-1S+N6&{e&usG>VXR;flTrIpXlGV-C+{LsjS zjpw~1A4=Qhr?gtxsc$-DXJ;QVg_rAx?Q~wT9m;DDcdaQnbH8fOBw41mubQWYEZ<4K z_#AcZhsz>=*KZY$kJfHFsJgLu&ZYxqos%{-Jno5@yl2w^tJ)-Yi_}Jbl~t#hPMdU; z-V70|IAyz0?EDl~EfpuTCr6XJbTXr^cx<^5!o?W7;HWs?jl^Deoh%`XS1P+g3V$&( z3TAGfVqzy#%GP({wK<d0A17ZX_ufXe)HyB=M?7YUh~DCv^SQ61{=yTDf}=M~c;!CK zlS{t1MWf)9gbDYy9S`hHw7X<3rKv<dP!zQjo>Zo6Z82}darb?n=Bn9u?nn$TE?!p0 Z=s77q>`q0569WSSgQu&X%Q~loCIEZ{tuz1t literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-xhdpi/ic_chat_white_24dp.png b/ring-android/app/src/main/res/drawable-xhdpi/ic_chat_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..ace0bef32ec67afbe5af73db991c704650d394bc GIT binary patch literal 205 zcmeAS@N?(olHy`uVBq!ia0y~yU@!n-4i*LmhQHi~JPZsBQ#@T9Ln02py&TEs;2_ZQ z(2lq7rGwDvg948QpSrf3+2d0Zx#%^+|LJFxlBzFGSahgiwe&>=?x(^f7oy}90vw9B z@^4wy@VU`bw1NK&n+wy${U;boA2hGeNR*$*xRK%SB>8&RdX@s?1@$M{?(kTc1r#-k zaq<^vFjw-QaIJpwhcn>`KZE*y<~4uVK73MovWLSmzq&x1uT*av!`<&n3=9kmp00i_ I>zopr0A!#`<p2Nx literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-xhdpi/ic_lock_white_24dp.png b/ring-android/app/src/main/res/drawable-xhdpi/ic_lock_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..ad8d91a99f9986d0cef4c876f1c3fa43696daeb6 GIT binary patch literal 372 zcmeAS@N?(olHy`uVBq!ia0y~yU@!n-4i*LmhQHi~JPZts)}AhoAr*{ouO0NVa+Enx z`0>Fcz517`NgqR9J|-wh*o9tjJ`x|e#QFRMMT1CHr6qgX7V5otD*eSdacS<hnUPi| z_jmsMZ744xaQ5%i4=*kypX-w?{#V@Su6NdRtIKTG<9jb{Vt%qyX{Gn4V-9bAbr|xy z7#<SH*qrY*`Ox!p)+n1z@9(gDh)}p2(BoT^H!)7*1#|3@mlwY}*(jENX_O24d^(f; zWt993W&u^3L!8kJ^S(C9GCV%=Q>0<7ox)w!bMGhSF!QZ2NIL8vZLH$K98%%FUA0k4 zBJq*~Z;nCcQZ}FB1Lc+pPE(asvsxk<g#XEwooe~kn3c0SPu}8Jc%r;UD({t~Xx`rr z9zQn}pE_fv;36wOpUwEYZ`pzOOt06}e`%j_C7hYjyQKQD4=aaZ!qSfxwL9Z@C1lt$ b=I~#1c9^!ObpAXB1_lOCS3j3^P6<r_Eh?I< literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-xhdpi/ic_send_black_24dp.png b/ring-android/app/src/main/res/drawable-xhdpi/ic_send_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..fd36be8b96d80ceb9336bb2acebfdd42ca6f3cdc GIT binary patch literal 333 zcmeAS@N?(olHy`uVBq!ia0y~yU@!n-4i*LmhQHi~JPZtsBAzaeAr*{orx_X@3lM4D z-}FW6(k2$SMd_R-3sX5uoQ?{u2>dbq!)nPk)eBy(y%W=e_8gmWa_Z#UdyB<B?ESv? z%-Pf<OP1W(=-igPkJChEay8Sgx}u}A<Q2Xp)+Fja443XZVapg8@sj02>rKYM8%$13 z3ny(z$P-axDoJEg;M%=Jgt6`3X5VGZC5dM?o>BWf$MEUQ^^S+Q&df}lSXyTJaF+YM zlEmbD@2BqapHa8J-){y}L{hV@-^>q3e$Q{<4*S16qg9vB;(6QjIM?<w-05#BpL9k3 z?MdvIqHyKYU5>?zK1qlMdNo?^5|Lfd%UrnNrG0JXp)E}!j0qDKt?4t}dH+-P^dwe^ pGFD!Nx0l=Qa0SIop7e=XkGb;wrX3oS85kHCJYD@<);T3K0RZppkKq6S literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-xhdpi/ic_share_white_24dp.png b/ring-android/app/src/main/res/drawable-xhdpi/ic_share_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..22a8783e70f01488cb1396f18645fef00c7bb299 GIT binary patch literal 496 zcmeAS@N?(olHy`uVBq!ia0y~yU@!n-4i*LmhQHi~JPZts+dW+zLn1ie9<<H~PLw(J z&^)O~XrV=?_?`y~9;oq2x^S@?i-^0c7Zzk6h<^3t*^<edFGYBD+>I%D`640zVa1{) ziykO6H92qGxK(rE^W~=v+ds`!y<Z>j;<Lx@f7MfVc+a-0Rmu`c?H1mnzV@);&4ov} ztoJHU5Y5_gXOgH>Ow=YP{^`Dv3)@fY-+aK7y+h}eynE)PrCRnZcarDoC|3S_U^L$& z=kpBnC9N{oK3E)<VP0>c9k}2W)9aGTvn-Xo-G6Ns`9I<gf6_kZUlRZIpMFV`f4{lB zLBr4!WN)6Z$DHLy4;1@lRmh~bZrN_q;aRdnLtf<Ngs1H0H?{5W9L$}qE;GU2DJINP zUHVJG%?YAaJ918N>BfX<o@O&I=$`p(!XLp4YTna5xjr2gRJ!CePiXRw6RJNKIZUnG z^(SQ>=O>|5F^P(knUTuJL-$R(@kmj5(=iVI8%iI0v!A5&2JcBon7HZR0;gs(L0+v- z$1UVNj`)1;*%I0H$#tGoc1q?cCia!WkImGir~K^kjt%Q5h@2*>6f-TQl}#^rlN0Oo zz?+HF=BA}|D*0YY<4EZ>e5JGbh{)nwU-^IWdP<+T;d^)51O^5M22WQ%mvv4FO#n?~ B*^vMM literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-xhdpi/ic_videocam_white_24dp.png b/ring-android/app/src/main/res/drawable-xhdpi/ic_videocam_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..1b2583d34e8bafff26a20f89c9d7cacf4525617e GIT binary patch literal 178 zcmeAS@N?(olHy`uVBq!ia0y~yU@!n-4i*LmhQHi~JPZsBm7Xq+Ar*{ouNiVN8}hgW z#v9DI<8i$An~?ohu4ZM4q{B6#w_liczkN8Tc=^%UPp-?UCEI>AemSL7btBuZt6w!5 z8B_%purN(=Xb|EEU}B77U}ih_-`8DnosAY(;nTv{PW!l-{B!MoPyeSP{W@Oyyx2hr ggAMj<JT;8W9ecyxRax6HFfcH9y85}Sb4q9e08!XHbpQYW literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-xxhdpi/ic_action_end_call.png b/ring-android/app/src/main/res/drawable-xxhdpi/ic_action_end_call.png deleted file mode 100644 index c34a8040b490e429e585d6cdce7327a6e9beb6bb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 763 zcmeAS@N?(olHy`uVBq!ia0y~yU`POA4mJh`hDS5XEf^RWBuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFwOOJaSW-r^=8&x-^UIjZnk@NGXynkXAo!Dv%WH& zdjUfk_X36m3|tXhC-&(_H*nt&5#TCdOw8LcagD~IWBVRUsz3Kow(fj#=jT4_{quB~ z8L)zeosWZfvNqp*@-OJc@l9WYZ+;g1+W32F@w$Tzhn>1NaL1q9ukb10$OQBFYgO`B zCCoQIu->?|Ciq~m1JBR(ZCCD1xEd(&fHi)_4e_e8>6c8+vyR!Drr1g|hm;8J$@==Z z*gfh<5YI-l!;+nPGizU-J}cPyZ>FUFzCBSrvgZ_TyMB3Z{3=do_e%SuV>7}Yx~2T_ z7nQT?sgO%*kyhB3^5-pQTFrbhMYTOF#s|9V{jZg8XUjjdcPHb-Bl#s4ZpVJ#pC9~Y z>#4iXm7KEQ<~F_7+;^rxPHk58b**(TxvpMcn6q}*@`%ZQK7HIDCAooTOV-`9D$|13 z*+sAZr1gO3-P!9arE(kCepxqs@V7gV$~AY}`o$cj;s-k9V>?T)Jyx8~C}wnhWyk)r zhx#Md@%Lyazr3=ppYhu6$t!aX_zFg6CN_M%<|MZuG%|H{sdz|)gg{cmF3$@M0)|h; z*jg4`s9mCRA!1RE(mdBS-(8(<%Ii*>!4|?P88CMdW9z$B$KKU_(s^XvW5?D~lNhTu z;jdzHh7M0e;(-;e>6>CNXi6kpV!f((ype^6L((}n(V=Mh$uyTw$qsWHJQR~(Tv>O% zY>Ut3e{3gsEWdn__`N6Xbwgth>$Ta1;jv#||8{cYIl<1g_tK_wOnY8(C7k$YdG)A6 zvQ+%})faUgnkVu6%-OuE_C{&-`~Q2+^>r{FJ5jmNU6|RV;r6vDZ!=oW4ANHjUw=B& jk^^gsVK|WaLGJ*AZszIUN9#HsgVcDs`njxgN@xNA+q+JV diff --git a/ring-android/app/src/main/res/drawable-xxhdpi/ic_call_end_black_24dp.png b/ring-android/app/src/main/res/drawable-xxhdpi/ic_call_end_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..44794a9b9d2dbd58656bf4a0d107de3e830dafb4 GIT binary patch literal 515 zcmeAS@N?(olHy`uVBq!ia0y~yVDJE84i*LmhW}5h&oD4Bp7L~Y45?szdwI9_w0xQ4 zAFnT7wzOqMZr}s~-b;=d2H74;oV;2uHn?~PCr=P_6mU%NbyXE|xgucVI&uFrdD&$O zCY=j1inT;#TDY_;*B#n;`0UZQ#fOb!)}Ock@Qgd&bCL=Ksb+tbG|0KK!?XC=)Tw7@ z8)x20HlK4Ty?2t+)fXl&CQ2$6U6zyDu=7)5Y1Lg5-6wa{mCIHK3rfeBsfHxT$8L(N z^Qy8H)Y=ocd&;LHU#e9_woLza#Qk8{`PQCV<7e9(*-JC8FN!<6{%ZEcyxE7(?MObv zubBM*waC(=9h*K}*R6QCCXYAC<km~4V{=ch%T~MHcqabUH23&t|KEP~*S;%iwB1$C zy5-E>OGoCL{ZtT<oU*ap>d&(Y>zyt-O8(BVXHb34vhc)$6?4Oyq7FNSvap6<EeKpO zYrzAX&7q<uhigJvYai|t*xUQ*hMvoNxwUcJfwMO|$>r|j4y<0>dt(W!`_ob*qjb?D zs<syrHFw$cp6T?cT7Ue~)HlYGlPAv&F<aue)MTgF#K{v)mbx$fI{WO2lRi?LqXe&2 zN>68Ad#vT?-{qcxvuuUM%qPxeJ}qnhq$A2T>e&o!zl@MjSonHQn(z86QlszCKMn>4 O1_n=8KbLh*2~7YU6X$UN literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-xxhdpi/ic_call_end_black_36dp.png b/ring-android/app/src/main/res/drawable-xxhdpi/ic_call_end_black_36dp.png new file mode 100644 index 0000000000000000000000000000000000000000..ab57d9001675c7bf204a25add47241e7e5ed8f67 GIT binary patch literal 721 zcmeAS@N?(olHy`uVBq!ia0y~yV8{Vs4i*Lm2G#ueZVU`eGdx`!Ln;{Go<E;`jaBBr z$LpKV)@YnboW4kJnxStQ*EWR?v8h&bZZ1^1kS#Ik$;mToT68=*cfGm5-;uC5nSCSY zQ^g5p-IJPL1*K)oUc77TqMgq#Tdw%?>95`S&FTE}um9Gs$X-7s$V+o667kAx$LSS! zzukJ@aG1|JVgKGTY15qMU80N9_8Oj_Ws>VBl5uSdCwp(?`el!=+<m@ba-Fc~ucMsp zJrQe}@8s?0*X>aIwtMotve~JdPJZ4er2A&hbDoUFb=7A&%)YKvUvssn`QEqojAHJ4 zTOzx!<P=BgB+K7ibnVSbSvz6b3tKKeJKOssW=hsM@nt?z<#z(zWeTqpK31FiY3b=j zA;#|lZ_4_0Jj(oWZb`G;CeOQ9ujFnyY^+wZTCwXnGqcz3bF0N%7fw4<aWw4jsi@bE z`j(HoG_J(Qm8}&z79-I5>xQ0r#<aSAD^;%2duB%$-mhL%aNG2@U*L}NnDq<TV??ym zbYcu1t@<S+n&jHs+9v+>m%RCJmy$;jjeff?)=QXahrNl|@xt$a(scWyU2@#qa-aE4 z^*Jv7OlGxO!FC}#rsQlyg){$`s`!$t5i>3vw!2uGoTz`l<mrv;CcBE>aP2R9dc)>@ z+0z@*cOGq8ab-f<?&y$LlV&Gut_<8Y|4Zh!>=>b?zi%>xL`QFm)$WO0wJkLFEqnCV zQ)2hSZ>-v2_S|#zmP=aIXF_jiZQS-^hUu-u-ej@WxuWsb*#XwPR?&&8AKY5x{A)|} zd}p!loT;(a`6r4bza=-W4ayF_7U}oeNA$kdzHhVQPOrWG+I3skdQ+RF!EY{W>}Qpp zE9^Am`v$*Fm)8g!lH9Jf@c4w|ch|4GyryiuJI^Xll*9o}D^c(A?bzEGdmpFOF)%PN Nc)I$ztaD0e0s#0iU-19{ literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-xxhdpi/ic_call_end_white_24dp.png b/ring-android/app/src/main/res/drawable-xxhdpi/ic_call_end_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..e1831d7afd086dcfc741a496d058af3d0308da99 GIT binary patch literal 553 zcmeAS@N?(olHy`uVBq!ia0y~yVDJE84i*LmhW}5h&oD4Be(-d045?szdv0%bY^hAc z$M-usJ$)w#OkTSs!9k`;pRrv+mM3X~OTfWx-Y+k$OkU{ey)b~QbxW}KZ2_*OTbvKB z)Nm93m@4FyB;Z(hIwelQ*+DgH$NWC06PA`ccTTTX-2eP1yItW6@1;R2L$s!PEe$d{ z-aFeQ`P`z^+1o-wb8l|Rxj6mZ&V)OLGjF_K_(#-vSxsW>&1*GzmtJqGYQELGGv9aZ z+8JN>ef!lAS9p0<)t7eZI6Dpv<7e4l^UhUo`rB+I{LFNfe67N5{ujRYwO`3|%gy?< zGLgBebj6An7qSmT`|Wx%QT4y(D{1Ga4tvu=-`?9MA7i#Cc*>5`yVuQ}9)2~}wz|=7 zquPGKz{8I}MSo1Rd%@Z-`rXZULAl6xhwpg{uK)21cKDN3p#Gv|f2LH2eeEB6jbheW zTut3Zg163G5K_on-ST=1Psi%S#Y-JtC$3)VP~Uof%KNLc9P+jAPyVoI{*>&?pG_No z{aGDj|C&41^R{2Nlzsf9%lb~1mx7xgG4TK1#^c_y*K@z5D7)R~iBI=hD2Ys7wXC$u z>h4=VL-!V?ORE9{pR4CQDRw!bu4|zs^})OD<l~MFleaMZ@Y<en!FC_B&P#FE1qDYU zFZ*Q*^Zv0+zf<Hl{RF$|e-^I=Xy`C|6`Q+Y*Y2OkmvA#MFfe$!`njxgN@xNAwpIZY literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-xxhdpi/ic_call_white_24dp.png b/ring-android/app/src/main/res/drawable-xxhdpi/ic_call_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..90ead2e4551b165530bd2430b3d69c34263c5c4e GIT binary patch literal 597 zcmeAS@N?(olHy`uVBq!ia0y~yVDJE84i*LmhW}5h&oD4B$#}XrhGek5J$Ny@H<97^ z$NSAy_sa~sXI)b>EZnSO?y@>Ft99B3^&JIAJA(9{94j+i>z8(R>LQhxH?DIhS$nQt znyII;^_G!$N!r4TH>U@Eo40xHciZ&mw&#mK*VzSWP4!y(=)q*g<EP>->)k#f@YH&C zs<`E%uP5|Ax$M^3{ljyM-A&iN4ChVVo{Ma!#P$g0hLnArl404ClQh9XcT*?#5lg+< z7Cv(rZ@O?7s-+0Goj;N})wf6Bc}vbCrNVC~)XIe4+PRq}&RDpG!GBv}cMy}!O?P!| z?hj9xUe8F}$>6{4g7-!S|92baIx)_>^N7=1EMr1;i9ng>y2Of;lPt|nn(_&jMXyV& z@QYX;lPq32&1X~Gy29oS))G5iZ7;XXos=!3n$uWY)T?;NZewU(;PL1~_bzH|zj4z1 z(6O5e+h3$j+)yYb%6vG^Z<Fg*NB-7(n|%d8NIslz{-wAlt!Glp!ud{{Wdb;Vq-cI? z4Yx^{5Vx+NEiIJ$ut95Gq158k4=07v=lxwJmMyXDhOJZx!+f2m62V^$VwT?!iT%Yt zU*~CLh>^u<rK9gIE!qF!WRRI^Yu+;rv-7EDla?}h@mbGE*m*z1#dA^L><~}S#~i^S zjwct-<Eaeu-0CQOK0@>=AA6<a(GYKmTbwI8viif;eVk}?lJDB3g94Y2oII(<lRM|R z;N~et3gQMP7mr#R*`!V3uX)N|S;X@|bE?<Upu&F)?~SwFcCSd$W?*1o@O1TaS?83{ F1OV_*3J3rI literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-xxhdpi/ic_chat_white_24dp.png b/ring-android/app/src/main/res/drawable-xxhdpi/ic_chat_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..424509055121817e606155f423cb866fb2501373 GIT binary patch literal 270 zcmeAS@N?(olHy`uVBq!ia0y~yVDJE84i*LmhW}5h&oD4BT=jHu45?szd*z_#u>b+r zhr6r3eTZJfqMNu%xF_j?7mMPKfd1DyaV`e=Y(?jDA4KygKfm)L!^h^sxr?<Qj>v{* zSZ}=B+B$J>(O&i}8{=K#QSv3uZrf`w*h<=dbIecK^38Gnk}K@Bb6Ha+IR0AR=*T9y zW&ZyQ<(2`5cqLONIHvz_uIo`}lWcL^-ymdkLZO*$OBnOns}8}s7o1kJMD180wP5PZ z&bEJiTqb$70rMwwafNV-tPs)&Pz_k%wZLJjL!(wBQz#SbDi)B0hJ48z9-|Wr<Yjiw WQ~fV?>;eM=1B0ilpUXO@geCy_?P!w# literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-xxhdpi/ic_lock_white_24dp.png b/ring-android/app/src/main/res/drawable-xxhdpi/ic_lock_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..0e52c7c75e58cc7700cedf8e808fcb4f598a2f54 GIT binary patch literal 540 zcmeAS@N?(olHy`uVBq!ia0y~yVDJE84i*LmhW}5h&oD4BKK68R45?szd*i&9bf8GX zM~5>#dTVy9{leK5^yjn4Qa#p}Y9$xvYS=YQahSentL6`$Rwwb&%e9<8+TAV&y;v8q zVe#vfNfYYQjCcAN<-X&w+&N+1d!1swpSf*~Co}(tR;&qH=H#&1m{IiJryi!AZzlgO znI=@OQhHiD&5buo{XzWQ4Q5sW^PF?ECQoOPJjb3>?H?ys;*qbrN~Uy~SNFYpp3T=f z%UvhFe7Byz;$ZoswVq9BoCaHOCN9cf?$BvJBX7n;=BY_@p4Z>iKIUN^q|k5udVgi_ z%V|wK8KRd(g_%Ft8`w@|oN6e_p11$V7dN&L#Z@P_9A&<s_NtplLt$0c*8jaL?i(#o z3v>_rJ-;*SM1zm~+HdxWK@*xprr1o*DRw)tfy*+L+g|&H;B=3YgE3+kRHl2B_Dz@D z(wj2-rDM8B*(5m;rAS>{4=Ywp4@WMk=zzJgVjn+7CiP!1eWBi;9R4|GH_KJt<<{IL zcN)ut^}pY36cg5;ez)<@k<*&2rO{q{VhY)BdGE0)VCS0oes}ewPaE5_#i}g!$LzOq z`WBhC%2-?G!iDLpn?#l<u%_nl*d3pvbhEJPg4m?oCG0<A6t0HbR8N>`d9Kxhe{-G1 w_nS{zqz<n-W++|pWK+|V^goVVK^JWM)#v(WaDLmsz`(%Z>FVdQ&MBb@0GKZ9ZvX%Q literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-xxhdpi/ic_send_black_24dp.png b/ring-android/app/src/main/res/drawable-xxhdpi/ic_send_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..40b74ebc16e0bf2a85ebc3d920353fc4eb8337aa GIT binary patch literal 412 zcmeAS@N?(olHy`uVBq!ia0y~yVDJE84i*LmhW}5h&oD4BCVRR#hEy=Vy<xBC5-8*T zG5AB3==xO7BNsNaNNv$+JU72fHDGZ9=M`mToyKdcS4{lvCbE6fgxQwo?>w5fyD`6R ze#gnj+r5?stt>e%CMqc^ajC#%?&SsVzaLaR{gbEbeH^n0+tKR<9&;}*;Mj2_BCGz~ zN5-{|b_)znFp02nHXK^8ky))rkYTH%-2!_JrgKk>HJIXdG&n!yy|Ltwyb8mvW*LXV z37ji<HZit3S|#)!SvDiRH`Z_G!_M<}7}RH^w&wQjeAIaT9pm5MKO6r2%{lPy{r5wS z%o+FI+sYc$UXWS+rr?li|DL~=FXp(;&uJ|EX7PC1bCpfC%XaT{{3X5N;1m114h}+j z3V&ib%{|;!-Kk&4G|AM%!HAvH<K<5eTenkgQkLSLCtQVsCbry(KjrkvKX9R$<jQ52 zqvTJ@t0?_!mg4vPQ82GZMI=OJ;Q}R2R#8ul74rJ6O}}d6R=ii5>a{e;^947v)g;Z3 UrsEqK7#J8lUHx3vIVCg!0HPbS_W%F@ literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-xxhdpi/ic_share_white_24dp.png b/ring-android/app/src/main/res/drawable-xxhdpi/ic_share_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..a35b3cd14af89804b7c3aab8f9b54cfe88771d19 GIT binary patch literal 698 zcmeAS@N?(olHy`uVBq!ia0y~yVDJE84i*LmhW}5h&oD4BHF~-@hGek5J!qRTr&NaF z!TnYhSrcDni<B>m<rQalmrN2$&RVgn`_`huYmRFoR%l#W=yi9o^SYDkny0vEY@O6% zCb(TJ(@t$^+Qo10+D@~6-jR|Kx2`h3^1uEeTYs1E@azBQb$J$>ZOyrD@VaEm$rYlJ z+k0FrdonYYzYAHC9&-5cm0p)9tuUDs%S%R@=UtYh_|3~&k`g@ctB0reytE}L?(@=? zJeg;cJ+Z4!KKq2RaHhrMd44_aA7#pY)5R8N@coYRPS5Idofxv`wSMIGzl-P2n!d=x zFKX)7<=aDS-it?e_qi7G?9RGg=R4g-vbtw=)b&eS(vDS#99Nvb;`FbB#er<=qo<c% z*A8S;KXbb3f%D!#5z)!da<Xo2%)I9E#_&bgw7>sM-@er73)J2;P3CIyscCCm^<5_R zWM({Xyr1&-;aQDofxL4we_av|)DhLT`n)%NQH5IRqrU0Nyv3ic)GRO$+NgQ{F4Qy8 zARjr;vz|C9vhtPPCGnLOYlZh$n+6qnr`MI31Q{$^XM9^vy5o`M(sgt9uRXQv{Twfm zvmL3C_SIoWEx)JU{gxzhzGYRZukP!#o}+Ti`z~#|zi!n-jsC~$POh?CZ-44>k7;Cf zT=%CJ<#Dms_6P^c9bR{9Yy4@^=8eY%gIDG4Sof;dInYLZ?WtQvOWo&pOUYgR(E3kf zmfZWBlfJH2eXCQg)s=q1ysRY6Gk!_J5r3=CkxL%&N`Jb_<EF*8bAjUdn;s{7Z0@^y z<(=3ppt)1;e9e*+w|Or;PEN7OclT;Dk$JMP(c|=UzE7D;mfhcUeCJB@OU5al*5<OE zMK6j2&n}kRwZQyT%7w`LQ5zC&S@XGvhlh9mV_tp9v$;dO)t!NXfx*+&&t;ucLK6Vh CcufKT literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-xxhdpi/ic_videocam_white_24dp.png b/ring-android/app/src/main/res/drawable-xxhdpi/ic_videocam_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..44c28e2f2830f927973beaa3a143ddfe439f20ed GIT binary patch literal 234 zcmeAS@N?(olHy`uVBq!ia0y~yVDJE84i*LmhW}5h&oD4BZ1i+-45?szd&7|Huz`R} zVCXs1qNL^n>}Pm;{0uag3!TVd7XDE$U0%1OU`y5)6D1`jrAyw^+g3@vvbqv>ynmke z)vqUC-_KsK@YOrc=bPDnISThFIXrV|IMc~6Q<%|6ohikS<-{DB36_Ei#Yzs(S{lx9 zGI9ziI5hk)X!~DkGTEXoN9KaXp;;=|7OZuh7<y~T|2=&#SG~KldxO=LQt^LQ&o^uD m-N4Ag0n)<2$RuJG|K`QJx(|o>yBQc57(8A5T-G@yGywo(w_QR2 literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-xxxhdpi/ic_call_end_black_24dp.png b/ring-android/app/src/main/res/drawable-xxxhdpi/ic_call_end_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..3a0ef395717b0c28bf3c0c298027501da08b59ec GIT binary patch literal 663 zcmeAS@N?(olHy`uVBq!ia0y~yU`POA4i*Lm29JsRH#0CW#d*3ohEy=Vy>z}iv{d5w z$Ll+d7hknltL42(%yUBU0*^rLa95wr+*=elloc~4PETC6KY^oZf$KE~vD7UaqEc9t zT&5Wan5<s0W9HMSjq$-ZQ_kAFO@49b_uGFnxX-`d`|r?aOPQxZUYbj%z=)l<@_wJK zyvf-er5Cj=>Fg}g(|TEvC5HryzukPQC4D6Ad#2Sp>D7yx!?wy?FIj(<ZIMytG0pmp z|2I?BP2YyRoP0{|yruT8nA3Y=_s-jRwMXBT|F_fSuT_ua@_&1OS*YE2b8)v;u$b$7 z>&J|t=6&<Gsjyv1mkSAemp8v-+wKQjSc~O0USE={*0U<)n9q&0OL4!?Mity*y;vrH zbL|P<dpV!`ZIkb9kuH6tcKKQDEC0aTv#!T1SLS8^tC98VpthV*U(w@bGKHR3N=^!E z<?gzA+^Lp(*V7dN_cd={T_v+Y$LvYNht0?TEZ4m!l(d04tLm=ln)iIV8y~W_tiO{d z8(b&oY^d<(N!|aV8?z*4F+R-qXAY>0>o~}#krBtLd{4mHQbFcwLKx56h6_)AFO>VB zYj}k@%+me}^SO^}G~TG*-K4-}eP+dXmq7nl)0`KrNR~1PGkIwJcvouoXV>Tb3_X?M zC08chF1gG%fBM{bqla^9rFN}-n3&Fe{(Zus{IuLl+OyC6*!reM;<Mk`!;|waX5X%j zD60G0a++`D7w4x7?!P|&KF8_w++Uf?*L$C9zgVhP^C0T4*~7oTKHr}GVUb|-g%3_A ze;T|BRcz5t3t@XMsl9amM?HVb2WidQt}d3_?u;DIOQ#4ukpEu&G3{DW_A&+r1_n=8 KKbLh*2~7ZxMnkIr literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-xxxhdpi/ic_call_end_black_36dp.png b/ring-android/app/src/main/res/drawable-xxxhdpi/ic_call_end_black_36dp.png new file mode 100644 index 0000000000000000000000000000000000000000..58888d833235bac9afeb4f183245acf9c69f94d4 GIT binary patch literal 954 zcmeAS@N?(olHy`uVBq!ia0y~yV3+{H94rhB48Hza#taP1jh-%!Ar*{o4mL6{FfcSM z_`6bkB@>7V#cA8`#jVkLY{6%rZG2lo%~;W3wxf1y&?e!)mm-qB?4C1Eavb&x;&hzg zG9_S^XNY;LVq8m!%w`!4%}pL$`dt$yNS$(C+8EfY&}1}u*Ilb$*ILc_c3#|izc%9B zzncHe?e~-Rn(94g3cj1YFm>13%@@mRzr6@A__AO0%k9PI%cR$So|h^%>v2``({m|P zI(O+@N?yHaLXm6qnvEXIIn%kWX>5;t<o&zl#O>GI-K7@pYNvN!?A}-Iv*JVFt&Ejr zKi}Lr^Y@yRz^XlU@-w6Fe=k|rUi)rCz+utaw0VKA*398sE!tvr({c43o#lE~_l4>? zN_Wf^lU}?mHhJn{C)xEcV#8NDc1G?KHrkYbX2Oan%krOrCvKR{4CP(oGJSq)&${bp zPASDck5)Xy_IATPukN$gSKdv@b<4h{GBM*)&K9%DTVI>p{~Q*T-|nb*$ZSWH>#hq@ z3)g)8d4>C(nP=RrPkN@eh35Z=*2sP@t)8W96MalOeYNPb4B^dT-y`0sD?a6`aEn{A z?Dn)zAMZa_Ob*>-_-^y9IK`u8yB%-#e%dx6OJ90nlxd}>$JVKCT8sJWxir78$odu` z`Tp(8psPu(3Mm{Kf8(ydF`4}+Jn;qtYqU?%DOI^Zfi}^Af&<KcE5+_MO|V_SQ5|f& zRC;;1#5RY6YHPhr&gLaXFthHCd97l*LW8Gz!Gj4qd2~O{Q7~!fd!MO3Wphjh!`uzw zPZ`{P1YBT{+Lc%7@r8qT!T-DLLTTK*3ns7YUN7ySx@}&)l!NNE^BdU$I(av3W(-N% zzj+U1NYeU?X$}JQ#i5%kn||C**Etg)vgp0h)i+*>zxZbH85mn#<QKZCb5VNkbFmdX z7X^bucW3s6^r`Dztc%SGT;BM@W@9JY-qbl!S6}sVl!r*167J7f>wRTcXVs(6M-*<o zQTrOS`(B5QU<Y4?*W{{ICz*aP{@|i;>p{}j8x`?$)%@9ys9L2o&Z*M<xWmb0o`}nB zpTPIN+f=#yxfWfGN?9m&cVdNK^NSVgs+)oyaW<<~<h9OlVSYDZYo58l)a5J}^}<gi zO@G~embskoC;y*kN|CvH;@fICWtB`$9zXs*V|#edl&9ymU5P(lc+W6y%Q+***>j8+ w9;uA_ds|Khl<gE4xPJZLskrj2AT(=1C^j$UxGAaHcRN8`Pgg&ebxsLQ0Q@Pgpa1{> literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-xxxhdpi/ic_call_end_white_24dp.png b/ring-android/app/src/main/res/drawable-xxxhdpi/ic_call_end_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..8801d0ded431c21d4d6e3e092e56d540b699962f GIT binary patch literal 712 zcmeAS@N?(olHy`uVBq!ia0y~yU`POA4i*Lm29JsRH#0CW^?SNFhEy=Vy>PyJYO2KX zhrex&vrPR~pPIDARp^O`M$na0p@Ns+FzIp}^6-dyk~rg~zC@Ek#zfNzu9CWI#3UOz zCaUtV?Ck2A9Jx;Px9L>hVB_#^F;^Me_djj*@63PytMPZ0(&<e)$mq3A`0dQ~k@}Wt zj)z&hr>%+FnsSkqm%Gn0<(iyzjB3^93wO<Sgx?K}kG|-qE!DSb_wlxe3|zv?=6=|n zxBJPCU2nS_8M$`byzYs+Zjj!-Xn%w6vf737YuCN|^^f7*w`umgi&EX+yi#3Yka=){ z_0u}n?+u5hTyDF%TrQx5dB^GFr^@eneonK^+0Hq;S2|Q<jd9*)&fRZKlka&xpZea} z+M4;!1K!lAvQu+3&AZIXCTkYkeQdwBw(4r>TgI$!za3Za`@V_2w^Pq7?UJJS)>n&; zi>5Me|KTz7nQMPp#7w^%m!tx2q)#;Ee4ySRC%NI`><wv*A7*9ivG0g&Hsycdz1@`A z=hsXxh94Je|Fg?%@k(X*^K@lBW9#z;K@2m3I}9F`YH>c;5H(}rCWcoJcY87K<J97f z;C!ydYau=FVy6B#9)tCl`Pu^CzgnYS#b&W!bKi>A4eOK-Z9i7UcH>2i`}6*e5Bv{h z-<IB5yQuy4v(0|z#23~cimblk(jWeLg7MpV`cBm?@n<9Se{K1??C+&>Kb8JW|4`Q5 zemqyS_MVvh6#Mv=O)cJh8ZQ5rZ@8cT^qlxYnS~Ge3=iaf4}JZ4HshfT;X}XIi89~Y z@jYt3zEk!gZnq->VV`SOe|F9At6<%b6Q$U;ZMkmjPig)CU;O-UJx?=ylKZLSc#iN{ zajktUDhiCE@k~M!8oKluIXxU)r#FCvI6WLdVjyNca~Y56glFq_lrk_dFnGH9xvX<a GXaWEZHAol$ literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-xxxhdpi/ic_call_white_24dp.png b/ring-android/app/src/main/res/drawable-xxxhdpi/ic_call_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..b0e020573d37e8b4acac23fcd3e01cc39531b5e4 GIT binary patch literal 778 zcmeAS@N?(olHy`uVBq!ia0y~yU`POA4i*Lm29JsRH#0CWUG#Ku45?szd(b;0Bwgm% z!|yF7aSAK1`+D!*;B@tY>B*j4%_fo8Rw2!z;ft(YC+chxarxf0-8IxnJ9f(@7eUdD z8eWb9xf?a|M2<+aJWpD5G3}S|n`QGXh4)K7_xUGZb6%O%QGmr!fQ56$d4}^6=Q);V zO!qyryrEl(J4KLrhtw2To5NQ-bQZPjR=ata`;PA<Tag0u*l!<L%8OI@w--#GY8!Lx zZuzG6`vupvt1TLK%ct<~-LYG%I-+@ZU{nh4=@o8n*9=;P-c_aYZ!akK`}V>3UEC$L z>>GZjyHa^2S8a5@_JQ$y;dR|=i{{nUKGSS-4xbI4c1p?axN9HZO_w9G_f*}Nzey5* zeEPEH>4*~*TqP&Xl=6Z+8++a)OP}mYdCHvjX;P8vwA12$JYpv5KjEJySRPdOkf~7e zXUo+iu_}}7JAO3p*!qd{zCii>u%FEx+(+I{^^b7s-2V6U^-rAbox8nf|2de`*6~_> zlKl;boxPvf1=BmFmwNwu5d7(g?E~eX&2pz=7l;0PAo29vC8c<SsrH)RxzB!Loj-B$ zox)j9x%Z{^6z#vXNbAC>?_Vw+HZhf(?OD_pW~p>q`QK;${g;nV_X$(p*sgN@X9VAg zAcc3L*Dbu{ZEKFkq#3rVEp6{`R;l!Gd+#ufDbLNqb@wGl;lC=9sm2qvl$;y0L?RcI zT=20!S@+0URmpi?7O$4BN#TT)?-2`)N+vF^Qug8(+`TfzKKGaWt`ot+OS^r0Cca^O z-SLHO?vs@hFCEjG7j#l}laTC_6*Dg#m&psbsbG1pS*mfRjG}6m$hAqztXVR44|z(L zyD25vI$xP8)f*|Zz~yd+(uR)DS31?Ww0AnmW;_zu*!6jjy06K!3+FXVC$pYO(5?>@ zzUgRfz0<d}EtR+U=Aq2CLSu#58Oxg2$~;#~4hc(`w<cMr`(+yYZ;OMj|DG9I&I?YO m!T3y*qe+3INx|cTRKtW{e}zw|yklTsVDNPHb6Mw<&;$TO%3S*Z literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-xxxhdpi/ic_chat_white_24dp.png b/ring-android/app/src/main/res/drawable-xxxhdpi/ic_chat_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..d708e15803ad3d231842aeacffdcad7310a930fd GIT binary patch literal 344 zcmeAS@N?(olHy`uVBq!ia0y~yU`POA4i*Lm29JsRH#0CW%6qyvhEy=Vy?oa9aDag8 z!<$F<Du{T^5er~)6Jfq`KuIHYL-CKwc2R5YXczx8qTlau*=4HDDL%Jz=Z>T&Cp!1P zJ2G*iuHpWKJm&8vT32@@M{rA2EJ^a`dCjmrBsYe6$AZAE$I2uNe4pk`V>}=7E>-e_ z*VARG2V7oGV^n9ZN^4+blMtAjeju6=#7b~z3}Iy3bDEiD-i?Mj;{y!LJU<o`u!)># zuqbAeNN`9$u$+<Y3+tD&49q+R3nUW`@cX`MXtqjN&YKaF#r&<s=s{zz-hz$k&TAM$ zvL79I_xsy<c9p<{{cI`=c|4pXC$tzTaHcv4ooY~-!sw~NJShOHL<aLCc}5-su<cBi Y>!hs|PcKwtU|?YIboFyt=akR{05>~**8l(j literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-xxxhdpi/ic_lock_white_24dp.png b/ring-android/app/src/main/res/drawable-xxxhdpi/ic_lock_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..a55147be1c9ac2a2b274453c3a163b46331a138d GIT binary patch literal 702 zcmeAS@N?(olHy`uVBq!ia0y~yU`POA4i*Lm29JsRH#0CWwR*ZZhEy=Vy|v$4I#J@l zN0k{zO5XG@&3d^=<I*y|rFyQv{I@)KHDLkYBJWpmuhyAuY|)YRZoLq+UFk2gibsG+ z!rFr;)2{rHcz65jJ!Q+{ouBVnSL}QL$L2%hbn#~KiF^Debr_zu+8vU4VWD%g*8Pg} z+DR^Z?BeuglAFD&LZ2%uA7r&jY3JL1RqcGU0@Jh13;F*m#C06zoVR!@?Ci*R=dpV8 zm*x-whQe<WTi)|zvfqfACza|oMI(4|`0ZVcpG#i%gq|+8JSFwK<n>y=1IkhnyVI5) zKiaYR81J=pJJ$1WXx>`9ELVMAapP9q*akzZ6@L@ITwAGrB{2JA1b+ha*6d}!XH`22 zZ{@dQFw_f-epzkSev38mKjY>Vb1f?_3ttJWercZ|yEWAPvgMVcf6Qfp(uF2}MYgX0 z$GmFBYRh^5yLUSOXXHIIRrdV*#CNCa9oUOikAIFgxc*IkL0ExJyo)ablRyIli$Kl+ z9#+51;_F*275#5TJ9H`iOAAQ2q3zIh|3$|9#C!(VlBwUB44U`xTzbdITC(k}TtU~l zzPrq~UaWa4x4@c_!vSKBLW2Pl-w6Z1CKln8l`Y%_hnrHpr|_KJz>rsQxAd~i*#ZW! zs&}W_7z+|MYkR5wXy|#n?dSvMW4YUX+2ki|xI0bcz+7gvobA4>@)Ne+-6m7x(7NKR zan%a>2hS8^n+zCcohiNhr7g^Y;ev|um7>aN8Lh9+zvSxnnWS=3<h0Bwekmk}Fixs3 znZIDO^7jTCtJ@{~9?1)|o73MhTG{Wosq~07VMcAqm+mFf4PF`v57OD9RrdaUV|3Q+ x_?DUqi~buS@Ah#w98+e9?_*$5U|{^i-qoklH_vD31O^5M22WQ%mvv4FO#n*UHN^k` literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-xxxhdpi/ic_send_black_24dp.png b/ring-android/app/src/main/res/drawable-xxxhdpi/ic_send_black_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..761929f4319ed27f6d1f995d09eb2b9bc2de316a GIT binary patch literal 543 zcmeAS@N?(olHy`uVBq!ia0y~yU`POA4i*Lm29JsRH#0CWKJ#>O45?szJImHD+EK*K zf8q^O(Hp8dj4cOEbs9|)UL<f{NaSJFVSM#RGtw!@DKTdngKisB)_+3}pXMF2m|AS! zPky&k+rrr0-+%FGZJFbRGRF(&aBtI4_L+ac);*+^H7@Ny{;N|=g}bU8c{%c*^*OKF z#H6MX*k~bZrrFK;g4I-I#zx1#i`MBRG*1!~;K*|E@UvQx>d7b^;u)^s*DofT+UUe$ zs#bF-=F);CItk4}A{~x@=adBavsimMMlx<(u%;m*Ak0B$g~7y^g$}O%2iUf%XeKmY z65il=R)hP-f=}E}xU;${uibb$r9Q?ug#Q)y<~1A68rHw~WN>gn-IAuDUGvSPI~SC^ zQ<t4`?(dNWSt=8D&Myh@`d;j9EI8%D-!I7lKUtqeq_zwHD?0jkH_N`wGt?Kd?%Q}= z^8e1C1x~+x#DDi(zMM99{ZaeJB_>SibN;;(`PXy#;*_qsV=ptZexA4RHc`BP<9;g- z)6e=FO(7;?8#8pYE<UM`$_biSd&l(R362}v+x{^9yuY}&A<Sgj#wZgv2VE)U1){yF zQ~u7hX>RCr3tZsrTzXf{O?h|GwXTaAoHH`Musk}|aO2VHPB+g3F3Kti<`2KHB$*g} yx%%yDZ$q3(m_nXQchNeb-p3Ywk1hH>^LS`J4|G^lGmn9Rfx*+&&t;ucLK6VqkLx=C literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-xxxhdpi/ic_share_white_24dp.png b/ring-android/app/src/main/res/drawable-xxxhdpi/ic_share_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..e351c7beb089e9513ef5ddaa2ac3f1a6638474cf GIT binary patch literal 938 zcmeAS@N?(olHy`uVBq!ia0y~yU`POA4i*Lm29JsRH#0CW7kaulhEy=VJ?QUozf|V< z$LV~MC)VGQKXFrku2rOKd+ev6<x5_;w?9kV;`~<qpqQ+fa`>8DmYv?yj%+xschlgM z<i811b)&i#wC}l+x#8I@j?~|w(oy@D?Y#f~`TO5JOPv4xsH^{7TQ~jv?H_h>K`KI> zF8}OTEQ^vB<iC8_g!@l&W9GfY?PgrJEc)FI`vap=<@G;{J5KXFW!6`$e>_DzcFJSd zn=aa?rzCgI-P9rSRBD>-mPdi>n9go$o4ljK`<g*xclV|?6TQ3>QbjvXoRSJFEDXO^ zz%2fGQ(I<Cjjne^59ghd(_Gsw_{*7BKHAif75C8W-MLdt^PaG4eYCa~F?+-lDB17V z+I@CT3TOTPM}c0A;i7t0JNx#FR;^e%*|kY$Q+VQ|z<sJ7U2~(HnVFwxPJ68UN>uKZ zg|A%w^a`)n7Y+#Sdtz07;)C$>%3fJ6{;M)i#Qup{o>7ZDCS23MpDF!@-sy@7{+vcf z<y!l-SQCto?`+;*`AysB&7)kkpS?_S+h;ssIUCjfd}kZT%Fv1o#a8!?OwJ}*y!{_| z9fM4?ePj}5DoB1Sw3nZ7_`a%@h~B*=CjG2`bc<&Aa*68)&SXlzv2Me~GeT2mB=ze& zD86%cbG+FFCq{GaIg7%=3Yg3_XQgsJ-XSsFbV;G1diIHjN>E})XSZeKVb_?R(^7p8 zpK+I+6q*%r^la=sRW<7$Jmuz=4pVa$OtfElq})bNx`MIatAgi4gU$mExuq3%j@_3p z310kRV?U3h*iG-AxJ^HPPi#5lKF2>|X~ew#%nMStB$Q7`<Z{Uu?)up^KQgV=QtyLR z;acY=?H{K<$r;{s=XF2%K;L(g)=hU?=87M)7sg66zCH8c{9~Q3oBFjTE%`Y8A#YZS zykXVJNBPs9hqQ?CeNsHkbMd@lR0?BmkKhxg-Cy<RrFJD<R!L#xT=zi#^zS>z#oRcI zPMNj#OZ81Ucwawe@s!mjCz)PJ9#|~2IcF;G7Gq~sw-nY;70<`-GH*Ke#BJI!bBgX3 z!#3`Ix5&eqF(oIZ{2njzzh=<W?HGAjK#whz_wSAulTBp~xdvUk;Plhx(WdsyQ>KdD z9s%q4ZXVHiYN5XR#<9?MPmM}9b*rUmJ~iS$X|~PCX+rUpOyviXY9D{-_j6ipnRvlN qyq3qvL*DaImy6Owk0m_y%+EZkv|Zo6JkP+uz~JfX=d#Wzp$Pz~IH^?t literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable-xxxhdpi/ic_videocam_white_24dp.png b/ring-android/app/src/main/res/drawable-xxxhdpi/ic_videocam_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..ed20c0706292403018b019329a4608db85d99e06 GIT binary patch literal 290 zcmeAS@N?(olHy`uVBq!ia0y~yU`POA4i*Lm29JsRH#0CWy!3Q&45?szd&@Vg)j@zE zQD68-^tVnC+X$W&uTNQZrxY0U@`i4%nf>p9?z7_u&p^SObf27<KjrG$%Fge9{axOv zzv-6beD<7_Z6<8JzZ<&G^d0+ghh62m#C`*X1x7?5GX_Q`Ij;r=MkW@H0^{dpR{tFy zPrT$@+x+0@wD9iy{dc}r?REUndD4B~oAhg&e>s2mO8fkIzEsbfl&>e<{~wpD7ZmGj uu|I#uwvPG3KUD{aK>`YJ#)9%%<{C?e$g0J%KN%Pp7(8A5T-G@yGywpp?sRYf literal 0 HcmV?d00001 diff --git a/ring-android/app/src/main/res/drawable/call_button.xml b/ring-android/app/src/main/res/drawable/call_button.xml index 7e07ee47d..0a5c97189 100644 --- a/ring-android/app/src/main/res/drawable/call_button.xml +++ b/ring-android/app/src/main/res/drawable/call_button.xml @@ -1,11 +1,15 @@ <?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_enabled="true" android:state_pressed="true"><shape android:shape="oval"> + <item android:state_enabled="true" android:state_pressed="true"> + <shape android:shape="oval"> <solid android:color="@color/sfl_light_blue" /> - </shape></item> - <item><shape android:shape="oval"> - <solid android:color="@color/sfl_dark_blue" /> - </shape></item> + </shape> + </item> + <item> + <shape android:shape="oval"> + <solid android:color="@color/color_primary_dark" /> + </shape> + </item> </selector> \ No newline at end of file diff --git a/ring-android/app/src/main/res/drawable/drawer_disc_handle.xml b/ring-android/app/src/main/res/drawable/drawer_disc_handle.xml index 6d0063863..bf5d4b91f 100644 --- a/ring-android/app/src/main/res/drawable/drawer_disc_handle.xml +++ b/ring-android/app/src/main/res/drawable/drawer_disc_handle.xml @@ -1,14 +1,20 @@ <?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_enabled="true" android:state_pressed="true"><shape android:shape="oval"> - <solid android:color="@color/sfl_dark_blue" /> - </shape></item> - <item android:state_enabled="false"><shape android:shape="oval"> - <solid android:color="@color/sfl_dark_blue" /> - </shape></item> - <item><shape android:shape="oval"> - <solid android:color="@color/sfl_dark_blue" /> - </shape></item> + <item android:state_enabled="true" android:state_pressed="true"> + <shape android:shape="oval"> + <solid android:color="@color/color_primary_dark" /> + </shape> + </item> + <item android:state_enabled="false"> + <shape android:shape="oval"> + <solid android:color="@color/color_primary_dark" /> + </shape> + </item> + <item> + <shape android:shape="oval"> + <solid android:color="@color/color_primary_dark" /> + </shape> + </item> </selector> \ No newline at end of file diff --git a/ring-android/app/src/main/res/drawable/item_contact_selector.xml b/ring-android/app/src/main/res/drawable/item_contact_selector.xml index 222289ec4..432c3fa25 100644 --- a/ring-android/app/src/main/res/drawable/item_contact_selector.xml +++ b/ring-android/app/src/main/res/drawable/item_contact_selector.xml @@ -1,20 +1,23 @@ <?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_pressed="true"><shape xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_pressed="true"> + <shape> <solid android:color="@color/sfl_pantone631_blue" /> - - <padding android:bottom="10dp" android:left="10dp" android:right="10dp" android:top="10dp" /> - </shape></item> - <item android:state_focused="true"><shape xmlns:android="http://schemas.android.com/apk/res/android"> + <padding android:bottom="@dimen/padding_large" android:left="@dimen/padding_large" android:right="@dimen/padding_large" android:top="@dimen/padding_large" /> + </shape> + </item> + <item android:state_focused="true"> + <shape> <solid android:color="@color/sfl_pantone631_blue" /> - - <padding android:bottom="10dp" android:left="10dp" android:right="10dp" android:top="10dp" /> - </shape></item> - <item><shape xmlns:android="http://schemas.android.com/apk/res/android"> + <padding android:bottom="@dimen/padding_large" android:left="@dimen/padding_large" android:right="@dimen/padding_large" android:top="@dimen/padding_large" /> + </shape> + </item> + <item> + <shape> <solid android:color="@color/sfl_blue_0" /> - - <padding android:bottom="10dp" android:left="10dp" android:right="10dp" android:top="10dp" /> - </shape></item> + <padding android:bottom="@dimen/padding_large" android:left="@dimen/padding_large" android:right="@dimen/padding_large" android:top="@dimen/padding_large" /> + </shape> + </item> </selector> \ No newline at end of file diff --git a/ring-android/app/src/main/res/drawable/item_generic_selector.xml b/ring-android/app/src/main/res/drawable/item_generic_selector.xml index da0ba9513..231cdf47f 100644 --- a/ring-android/app/src/main/res/drawable/item_generic_selector.xml +++ b/ring-android/app/src/main/res/drawable/item_generic_selector.xml @@ -4,17 +4,17 @@ <item android:state_pressed="true"><shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="@color/darker_gray" /> - <padding android:bottom="10dp" android:left="10dp" android:right="10dp" android:top="10dp" /> + <padding android:bottom="@dimen/padding_large" android:left="@dimen/padding_large" android:right="@dimen/padding_large" android:top="@dimen/padding_large" /> </shape></item> <item android:state_focused="true"><shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="@color/light" /> - <padding android:bottom="10dp" android:left="10dp" android:right="10dp" android:top="10dp" /> + <padding android:bottom="@dimen/padding_large" android:left="@dimen/padding_large" android:right="@dimen/padding_large" android:top="@dimen/padding_large" /> </shape></item> <item><shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="@color/light" /> - <padding android:bottom="10dp" android:left="10dp" android:right="10dp" android:top="10dp" /> + <padding android:bottom="@dimen/padding_large" android:left="@dimen/padding_large" android:right="@dimen/padding_large" android:top="@dimen/padding_large" /> </shape></item> </selector> \ No newline at end of file diff --git a/ring-android/app/src/main/res/drawable/item_history_selector.xml b/ring-android/app/src/main/res/drawable/item_history_selector.xml index 77f7649a1..ee1120e12 100644 --- a/ring-android/app/src/main/res/drawable/item_history_selector.xml +++ b/ring-android/app/src/main/res/drawable/item_history_selector.xml @@ -1,20 +1,23 @@ <?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_pressed="true"><shape xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_pressed="true"> + <shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="@color/transparent_grey" /> - - <padding android:bottom="10dp" android:left="10dp" android:right="10dp" android:top="10dp" /> - </shape></item> - <item android:state_focused="true"><shape xmlns:android="http://schemas.android.com/apk/res/android"> + <padding android:bottom="@dimen/padding_large" android:left="@dimen/padding_large" android:right="@dimen/padding_large" android:top="@dimen/padding_large" /> + </shape> + </item> + <item android:state_focused="true"> + <shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="@color/transparent_grey" /> - - <padding android:bottom="10dp" android:left="10dp" android:right="10dp" android:top="10dp" /> - </shape></item> - <item><shape xmlns:android="http://schemas.android.com/apk/res/android"> - <solid android:color="@color/transparent_light" /> - - <padding android:bottom="10dp" android:left="10dp" android:right="10dp" android:top="10dp" /> - </shape></item> + <padding android:bottom="@dimen/padding_large" android:left="@dimen/padding_large" android:right="@dimen/padding_large" android:top="@dimen/padding_large" /> + </shape> + </item> + <item> + <shape xmlns:android="http://schemas.android.com/apk/res/android"> + <solid android:color="@android:color/transparent" /> + <padding android:bottom="@dimen/padding_large" android:left="@dimen/padding_large" android:right="@dimen/padding_large" android:top="@dimen/padding_large" /> + </shape> + </item> </selector> \ No newline at end of file diff --git a/ring-android/app/src/main/res/drawable/toggle_speaker_selector.xml b/ring-android/app/src/main/res/drawable/toggle_speaker_selector.xml index 7fed3f3f7..8160c5daf 100644 --- a/ring-android/app/src/main/res/drawable/toggle_speaker_selector.xml +++ b/ring-android/app/src/main/res/drawable/toggle_speaker_selector.xml @@ -16,7 +16,7 @@ </layer-list></item> <item android:state_checked="true"><layer-list> <item><shape android:shape="rectangle"> - <solid android:color="@color/sfl_action_blue" /> + <solid android:color="@color/color_primary_light" /> <stroke android:width="1dp" android:color="@color/sfl_blue_lines" /> diff --git a/ring-android/app/src/main/res/layout/activity_account_settings.xml b/ring-android/app/src/main/res/layout/activity_account_settings.xml index 13d726a2c..f9473ad64 100644 --- a/ring-android/app/src/main/res/layout/activity_account_settings.xml +++ b/ring-android/app/src/main/res/layout/activity_account_settings.xml @@ -1,20 +1,22 @@ <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - android:orientation="vertical" > + android:orientation="vertical" + tools:context=".client.AccountEditionActivity"> <com.astuetz.PagerSlidingTabStrip android:id="@+id/pager_sliding_strip" android:layout_width="match_parent" android:layout_height="?android:attr/actionBarSize" android:layout_alignParentTop="true" - android:background="@color/sfl_blue_0" android:textColor="@color/white" - app:pstsIndicatorColor="@color/sfl_light_blue" - app:pstsUnderlineColor="@color/sfl_light_blue" + android:background="@color/color_primary_light_shadow" + app:pstsIndicatorColor="@color/color_primary_light" + app:pstsShouldExpand="true" + app:pstsUnderlineColor="@color/color_primary_light" /> - <!----> <android.support.v4.view.ViewPager android:id="@+id/pager" diff --git a/ring-android/app/src/main/res/layout/activity_call_layout.xml b/ring-android/app/src/main/res/layout/activity_call_layout.xml index 8f1878a66..6649ef3de 100644 --- a/ring-android/app/src/main/res/layout/activity_call_layout.xml +++ b/ring-android/app/src/main/res/layout/activity_call_layout.xml @@ -30,11 +30,14 @@ shall include the source code for the parts of OpenSSL used as well as that of the covered work. --> -<cx.ring.views.CallPaneLayout xmlns:android="http://schemas.android.com/apk/res/android" +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:id="@+id/slidingpanelayout" android:layout_width="match_parent" - android:layout_height="match_parent" > + android:layout_height="match_parent" + tools:context=".client.CallActivity"> + <!-- <FrameLayout android:id="@+id/message_list_frame" android:layout_width="300dp" @@ -43,6 +46,14 @@ as that of the covered work. <FrameLayout android:id="@+id/ongoingcall_pane" android:layout_width="match_parent" - android:layout_height="match_parent" /> + android:layout_height="match_parent" + android:layout_gravity="right|center_vertical" /> +--> + <fragment + android:id="@+id/ongoingcall_pane" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:name="cx.ring.fragments.CallFragment" + tools:layout="@layout/frag_call" /> -</cx.ring.views.CallPaneLayout> \ No newline at end of file +</FrameLayout> \ No newline at end of file diff --git a/ring-android/app/src/main/res/layout/activity_home.xml b/ring-android/app/src/main/res/layout/activity_home.xml index 5bbadcc73..8efc6e34e 100644 --- a/ring-android/app/src/main/res/layout/activity_home.xml +++ b/ring-android/app/src/main/res/layout/activity_home.xml @@ -32,20 +32,22 @@ as that of the covered work. <android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent" - android:fitsSystemWindows="true"> - - <cx.ring.views.SlidingUpPanelLayout - android:id="@+id/contact_panel" - android:layout_width="match_parent" - android:layout_height="match_parent"> + android:fitsSystemWindows="true" + tools:context=".client.HomeActivity"> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent"> + <ViewStub + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/viewStub" /> + <android.support.v7.widget.Toolbar android:id="@+id/main_toolbar" android:layout_width="match_parent" @@ -53,53 +55,47 @@ as that of the covered work. android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_alignParentTop="true" + android:background="@color/actionbar" - android:background="@color/sfl_dark_blue" android:elevation="4dp" android:gravity="bottom" android:longClickable="true" - android:minHeight="?android:attr/actionBarSize" + android:minHeight="?attr/actionBarSize" android:popupTheme="@style/Theme.AppCompat.Light.NoActionBar" - android:theme="@style/MyActionBar" + android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" app:contentInsetStart="72dp" app:elevation="4dp" app:popupTheme="@style/Theme.AppCompat.Light.NoActionBar" - app:titleMarginBottom="16dp" /> - - <FrameLayout - android:id="@+id/main_frame" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_alignParentLeft="true" - android:layout_alignParentStart="true" - android:layout_below="@id/main_toolbar" - android:orientation="vertical" /> + app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" + app:titleMarginBottom="@dimen/action_bar_title_margin_bottom" /> <android.support.design.widget.FloatingActionButton android:id="@+id/action_button" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignBottom="@id/main_toolbar" + android:layout_alignBottom="@+id/main_toolbar" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" - android:layout_centerVertical="true" - android:layout_marginBottom="-20dp" + android:layout_marginBottom="@dimen/action_button_bpadding" android:layout_marginLeft="16dp" - android:layout_marginStart="20dp" + android:layout_marginStart="16dp" + android:adjustViewBounds="false" + android:baselineAlignBottom="false" android:elevation="4dp" android:visibility="gone" app:elevation="4dp" app:fabSize="mini" /> - </RelativeLayout> - <FrameLayout - android:id="@+id/contacts_frame" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:clickable="true" - android:focusable="true" - android:focusableInTouchMode="true" /> - </cx.ring.views.SlidingUpPanelLayout> + <FrameLayout + android:id="@+id/main_frame" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" + android:layout_below="@id/main_toolbar" + android:orientation="vertical" /> + + </RelativeLayout> <android.support.design.widget.NavigationView android:id="@+id/left_drawer" diff --git a/ring-android/app/src/main/res/layout/activity_new_conversation.xml b/ring-android/app/src/main/res/layout/activity_new_conversation.xml new file mode 100644 index 000000000..89be1eedf --- /dev/null +++ b/ring-android/app/src/main/res/layout/activity_new_conversation.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context="cx.ring.client.NewConversationActivity"> + + <fragment + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:name="cx.ring.fragments.ContactListFragment" + android:id="@+id/fragment" + android:layout_alignParentTop="true" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" + tools:layout="@layout/frag_contact_list" /> +</RelativeLayout> diff --git a/ring-android/app/src/main/res/layout/frag_accounts_list.xml b/ring-android/app/src/main/res/layout/frag_accounts_list.xml index 22d71dceb..ead1b9627 100644 --- a/ring-android/app/src/main/res/layout/frag_accounts_list.xml +++ b/ring-android/app/src/main/res/layout/frag_accounts_list.xml @@ -3,9 +3,7 @@ xmlns:dslv="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" - android:orientation="vertical" - android:background="@color/white" - > + android:orientation="vertical"> <TextView android:layout_width="match_parent" @@ -33,7 +31,7 @@ dslv:drag_scroll_start="0.33" dslv:drag_start_mode="onDown" dslv:float_alpha="0.6" - dslv:float_background_color="@color/sfl_action_blue" + dslv:float_background_color="@color/action_blue" dslv:remove_enabled="false" dslv:slide_shuffle_speed="0.3" /> diff --git a/ring-android/app/src/main/res/layout/frag_audio_mgmt.xml b/ring-android/app/src/main/res/layout/frag_audio_mgmt.xml index 672492bf0..b89a291a0 100644 --- a/ring-android/app/src/main/res/layout/frag_audio_mgmt.xml +++ b/ring-android/app/src/main/res/layout/frag_audio_mgmt.xml @@ -60,7 +60,7 @@ dslv:drag_scroll_start="0.33" dslv:drag_start_mode="onDown" dslv:float_alpha="0.6" - dslv:float_background_color="@color/sfl_action_blue" + dslv:float_background_color="@color/action_blue" dslv:remove_enabled="false" dslv:slide_shuffle_speed="0.3" /> </LinearLayout> diff --git a/ring-android/app/src/main/res/layout/frag_call.xml b/ring-android/app/src/main/res/layout/frag_call.xml index ce4d2b06a..fae787b81 100644 --- a/ring-android/app/src/main/res/layout/frag_call.xml +++ b/ring-android/app/src/main/res/layout/frag_call.xml @@ -1,84 +1,195 @@ <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="@drawable/bg_72" - android:divider="@drawable/divider"> - - <cx.ring.model.BubblesView - android:id="@+id/main_view" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_above="@+id/speaker_toggle"/> + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + xmlns:app="http://schemas.android.com/apk/res-auto" + tools:context=".client.CallActivity"> <RelativeLayout - android:id="@+id/call_status_bar" - android:layout_width="match_parent" - android:layout_height="?android:attr/actionBarSize" - android:layout_alignParentTop="true"> + android:id="@+id/main_view" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_alignParentTop="true" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true"> - <ImageView - android:id="@+id/image_call" - android:layout_width="wrap_content" + <!-- + <Button + android:id="@+id/call_accept_btn" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentBottom="true" + android:layout_toLeftOf="@+id/call_refuse_btn" + android:layout_toStartOf="@+id/call_refuse_btn" + android:backgroundTint="#447542" + android:text="Accept" /> + + <Button + android:id="@+id/call_refuse_btn" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:layout_alignParentRight="true" + android:layout_alignTop="@+id/call_accept_btn" + android:layout_marginEnd="58dp" + android:layout_marginRight="58dp" + android:backgroundTint="#ff6e6e" + android:text="Refuse" /> +--> + <RelativeLayout + android:id="@+id/contact_bubble_layout" + android:layout_width="fill_parent" + android:layout_height="200dp" + android:layout_centerHorizontal="true" + android:layout_centerVertical="true" + android:layout_marginBottom="16dp"> + + <ImageView + android:id="@+id/contact_bubble" + android:layout_width="200dp" + android:layout_height="fill_parent" + android:layout_above="@+id/contact_bubble_txt" + android:layout_centerHorizontal="true" /> + + <TextView + android:id="@+id/contact_bubble_txt" + android:layout_width="fill_parent" android:layout_height="wrap_content" - android:layout_centerVertical="true" - android:layout_gravity="left" - android:layout_marginLeft="15dp" - android:layout_marginRight="10dp" - android:src="@drawable/ic_action_call"/> + android:layout_alignParentBottom="true" + android:layout_centerHorizontal="true" + android:gravity="center_horizontal" + android:text="Adrien Béraud" + android:textAlignment="gravity" + android:textAppearance="?android:attr/textAppearanceLarge" + android:textIsSelectable="true" + android:textColor="@color/text_color_primary" /> - <TextView - android:id="@+id/call_status_txt" + </RelativeLayout> + + <LinearLayout + android:orientation="horizontal" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentBottom="false" + android:layout_centerHorizontal="true" + android:layout_below="@+id/contact_bubble_layout"> + + <android.support.design.widget.FloatingActionButton android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_centerVertical="true" - android:layout_toRightOf="@+id/image_call" - android:textSize="12sp" - android:textColor="@color/white"/> + android:id="@+id/call_refuse_btn" + android:src="@drawable/ic_call_end_white_24dp" + app:backgroundTint="@color/error_red" + app:rippleColor="@android:color/white" + app:elevation="6dp" + app:pressedTranslationZ="12dp" + android:layout_below="@+id/contact_bubble_layout" + android:layout_centerHorizontal="true" + android:layout_margin="8dp" /> - <ViewSwitcher - android:id="@+id/security_switcher" + <android.support.design.widget.FloatingActionButton android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_centerVertical="true" - android:visibility="gone" - android:layout_toLeftOf="@+id/dialpad_btn"> + android:id="@+id/call_accept_btn" + android:src="@drawable/ic_call_white_24dp" + app:backgroundTint="#4caf50" + app:rippleColor="@android:color/white" + app:elevation="6dp" + app:pressedTranslationZ="12dp" + android:layout_below="@+id/contact_bubble_layout" + android:layout_toLeftOf="@+id/call_refuse_btn" + android:layout_toStartOf="@+id/call_refuse_btn" + android:layout_margin="8dp" /> + </LinearLayout> + + <android.support.design.widget.FloatingActionButton + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/call_hangup_btn" + android:src="@drawable/ic_call_end_white_24dp" + app:backgroundTint="@color/error_red" + app:rippleColor="@android:color/white" + app:elevation="6dp" + app:pressedTranslationZ="12dp" + android:layout_below="@+id/contact_bubble_layout" + android:layout_centerHorizontal="true" + android:layout_margin="8dp" + android:visibility="gone" /> + + </RelativeLayout> + + <RelativeLayout + android:id="@+id/call_status_bar" + android:layout_width="match_parent" + android:layout_height="?android:attr/actionBarSize" + android:layout_alignParentTop="true" + android:visibility="visible"> + + <ImageView + android:id="@+id/image_call" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_gravity="left" + android:layout_marginLeft="15dp" + android:layout_marginRight="10dp" + android:src="@drawable/ic_action_call" /> + + <TextView + android:id="@+id/call_status_txt" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_toRightOf="@+id/image_call" + android:textColor="@color/text_color_primary" + android:textSize="12sp" /> + + <ViewSwitcher + android:id="@+id/security_switcher" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:visibility="gone"> <Button - android:id="@+id/confirm_sas" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:textSize="12sp" - android:textColor="@color/white"/> + android:id="@+id/confirm_sas" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:textColor="@color/white" + android:textSize="12sp" /> <ImageView - android:id="@+id/lock_image" - android:layout_gravity="end|center_vertical" - android:layout_width="wrap_content" - android:layout_height="wrap_content"/> + android:id="@+id/lock_image" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="end|center_vertical" /> </ViewSwitcher> - <ImageButton - android:id="@+id/dialpad_btn" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentRight="true" - android:layout_centerVertical="true" - android:layout_marginRight="10dp" - android:background="@null" - android:src="@drawable/ic_action_dial_pad_light"/> + <ImageView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/security_indicator" + android:src="@drawable/ic_lock_white_24dp" + android:tint="#4caf50" + android:layout_centerVertical="true" + android:layout_alignParentRight="true" + android:layout_alignParentEnd="true" + android:layout_margin="16dp" + android:visibility="gone" /> </RelativeLayout> <ToggleButton - android:id="@+id/speaker_toggle" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:textOn="" - android:textOff="" - android:background="@drawable/toggle_speaker_selector" - android:layout_alignParentBottom="true"/> + android:id="@+id/speaker_toggle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_alignParentBottom="true" + android:background="@drawable/toggle_speaker_selector" + android:textOff="" + android:textOn="" + android:visibility="gone" /> </RelativeLayout> \ No newline at end of file diff --git a/ring-android/app/src/main/res/layout/frag_call_list.xml b/ring-android/app/src/main/res/layout/frag_call_list.xml index 3be66f9e7..91d868629 100644 --- a/ring-android/app/src/main/res/layout/frag_call_list.xml +++ b/ring-android/app/src/main/res/layout/frag_call_list.xml @@ -31,30 +31,34 @@ as that of the covered work. --> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent"> + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:paddingTop="@dimen/padding_small" + android:background="@android:color/white"> - <RelativeLayout - android:id="@+id/confs_layouts" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_margin="10dp" - android:background="@drawable/item_generic_selector"> + <ListView + android:id="@+id/confs_list" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:divider="@null" + tools:listitem="@layout/item_calllist"> + </ListView> - <TextView - android:id="@+id/confs_counter" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:gravity="center" - android:layout_alignParentTop="true" - android:textSize="30sp"/> - - <ListView - android:id="@+id/confs_list" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_below="@+id/confs_counter"> - </ListView> - </RelativeLayout> + <android.support.design.widget.FloatingActionButton + android:id="@+id/newconv_fab" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentBottom="true" + android:layout_alignParentEnd="true" + android:layout_alignParentRight="true" + android:layout_gravity="bottom|end" + android:layout_margin="@dimen/fab_compat_margin" + android:src="@drawable/ic_add_white_24dp" + app:backgroundTint="@color/error_red" + app:rippleColor="@android:color/white" + app:elevation="6dp" + app:pressedTranslationZ="12dp" /> </RelativeLayout> \ No newline at end of file diff --git a/ring-android/app/src/main/res/layout/frag_contact_list.xml b/ring-android/app/src/main/res/layout/frag_contact_list.xml index 740e72f7f..f056e77fa 100644 --- a/ring-android/app/src/main/res/layout/frag_contact_list.xml +++ b/ring-android/app/src/main/res/layout/frag_contact_list.xml @@ -32,65 +32,21 @@ as that of the covered work. --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:swipe="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" android:id="@+id/drag_view" android:layout_width="match_parent" android:layout_height="match_parent" - android:orientation="vertical" > + android:orientation="vertical" + tools:context=".client.DetailHistoryActivity"> - <RelativeLayout - android:id="@+id/slider_button" - android:layout_width="match_parent" - android:layout_height="68dp" > - - <RelativeLayout - android:layout_width="match_parent" - android:layout_height="@dimen/contact_drawer_handle_height" - android:layout_alignParentBottom="true" - android:background="@color/sfl_dark_blue" > - - <TextView - android:id="@+id/handle_title" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_alignParentBottom="true" - android:layout_centerInParent="true" - android:gravity="center" - android:text="Contacts" - android:textColor="@color/white" - android:textStyle="bold" /> - - <ImageButton - android:id="@+id/contact_search_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentBottom="true" - android:layout_alignParentRight="true" - android:background="@color/sfl_dark_blue" - android:gravity="center" - android:src="@drawable/ic_btn_search" > - </ImageButton> - </RelativeLayout> - - <!-- Declared after for implicit z order --> - - <cx.ring.views.HalfCircleImageView - android:id="@+id/hello" - android:layout_width="112dp" - android:layout_height="68dp" - android:layout_alignParentBottom="true" - android:layout_centerHorizontal="true" - android:src="@drawable/ic_action_group" /> - </RelativeLayout> - - <cx.ring.views.stickylistheaders.StickyListHeadersListView + <se.emilsjolander.stickylistheaders.StickyListHeadersListView android:id="@+id/contacts_stickylv" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="@color/sfl_dark_blue" android:drawSelectorOnTop="true" android:fastScrollEnabled="true" - android:scrollbarStyle="outsideOverlay" /> + android:scrollbarStyle="outsideOverlay" + android:divider="@null" /> <TextView android:id="@android:id/empty" diff --git a/ring-android/app/src/main/res/layout/frag_contact_list_header.xml b/ring-android/app/src/main/res/layout/frag_contact_list_header.xml index 873e92cf8..d2ee4d899 100644 --- a/ring-android/app/src/main/res/layout/frag_contact_list_header.xml +++ b/ring-android/app/src/main/res/layout/frag_contact_list_header.xml @@ -1,42 +1,53 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/container" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="@color/sfl_dark_blue" - android:orientation="vertical"> + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> - <SearchView - android:id="@+id/contact_search" - android:layout_width="match_parent" - android:queryHint="@string/searchbar_hint" - android:layout_height="?android:attr/actionBarSize" - android:background="@color/lighter_gray"/> + <include + android:layout_width="wrap_content" + android:layout_height="wrap_content" + layout="@layout/item_contact" + android:layout_gravity="center_horizontal" + android:id="@+id/newcontact_element" /> <TextView - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:drawableLeft="@drawable/ic_action_important" - android:gravity="center_vertical" - android:text="@string/starred_contacts_title" - android:textColor="@color/white" - android:textStyle="bold"/> + android:layout_width="match_parent" + android:layout_height="48dp" + android:background="@android:color/white" + android:gravity="center_vertical" + android:text="@string/starred_contacts_title" + android:textColor="@color/text_color_secondary" + android:id="@+id/fav_head_label" + android:textStyle="bold" + android:textSize="16sp" + android:paddingLeft="16dp" /> + + <ImageView + android:layout_width="fill_parent" + android:layout_height="1dp" + android:id="@+id/imageView3" + android:layout_gravity="bottom" + android:background="#e0e0e0" /> <LinearLayout - android:id="@+id/llMain" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="@color/sfl_blue_0" - android:padding="10dp"> + android:id="@+id/llMain" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="8dp" + android:background="@android:color/white"> <GridView - android:id="@+id/favorites_grid" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:columnWidth="80dp" - android:numColumns="auto_fit" - android:stretchMode="spacingWidth" - android:verticalSpacing="@dimen/contact_vertical_spacing"/> + android:id="@+id/favorites_grid" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:columnWidth="80dp" + android:numColumns="auto_fit" + android:stretchMode="spacingWidth" + android:verticalSpacing="@dimen/contact_vertical_spacing" + tools:listitem="@layout/item_contact_starred" /> </LinearLayout> </LinearLayout> \ No newline at end of file diff --git a/ring-android/app/src/main/res/layout/frag_conversation.xml b/ring-android/app/src/main/res/layout/frag_conversation.xml new file mode 100644 index 000000000..b2f8ee6a7 --- /dev/null +++ b/ring-android/app/src/main/res/layout/frag_conversation.xml @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:orientation="vertical" android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".client.ConversationActivity"> + + <ListView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:id="@+id/hist_list" + android:layout_weight="1" + android:transcriptMode="alwaysScroll" + android:stackFromBottom="true" + android:divider="@null" + android:background="#ebeff0" + android:listSelector="@android:color/transparent" + tools:listitem="@layout/item_textmsg" /> + + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:background="#e3c1c1" + android:id="@+id/ongoingcall_pane"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="Ongoing call" + android:id="@+id/textView2" + android:layout_centerVertical="true" + android:layout_centerHorizontal="true" + android:layout_margin="10dp" /> + </RelativeLayout> + + <ImageView + android:layout_width="fill_parent" + android:layout_height="1dp" + android:id="@+id/divider" + android:layout_gravity="center_horizontal" + android:background="#bdbdbd" /> + + <LinearLayout + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@android:color/white" + android:elevation="6dp"> + + <EditText + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:id="@+id/msg_input_txt" + android:layout_weight="1" /> + + <ImageButton + android:layout_width="wrap_content" + android:layout_height="fill_parent" + android:id="@+id/msg_send" + android:src="@drawable/ic_send_black_24dp" + android:tint="@android:color/darker_gray" + android:background="@android:color/transparent" + android:padding="8dp" /> + </LinearLayout> + +</LinearLayout> \ No newline at end of file diff --git a/ring-android/app/src/main/res/layout/frag_home.xml b/ring-android/app/src/main/res/layout/frag_home.xml index c3ddd10f0..fc63c4335 100644 --- a/ring-android/app/src/main/res/layout/frag_home.xml +++ b/ring-android/app/src/main/res/layout/frag_home.xml @@ -3,23 +3,22 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > - +<!-- <ImageView android:layout_width="match_parent" android:layout_height="?android:attr/actionBarSize" android:layout_alignParentTop="true" android:background="@color/sfl_dark_blue" /> - +--> <com.astuetz.PagerSlidingTabStrip android:id="@+id/pts_main" android:layout_width="match_parent" android:layout_height="?android:attr/actionBarSize" android:layout_alignParentTop="true" - android:background="@color/sfl_blue_0" - app:pstsIndicatorColor="@color/sfl_light_blue" + android:background="@color/color_primary_light_shadow" + app:pstsIndicatorColor="@color/color_primary_light" app:pstsShouldExpand="true" - app:pstsUnderlineColor="@color/sfl_light_blue" - /> + app:pstsUnderlineColor="@color/color_primary_light" /> <android.support.v4.view.ViewPager android:id="@+id/pager" diff --git a/ring-android/app/src/main/res/layout/frag_imessaging.xml b/ring-android/app/src/main/res/layout/frag_imessaging.xml index e975a7518..cc569d027 100644 --- a/ring-android/app/src/main/res/layout/frag_imessaging.xml +++ b/ring-android/app/src/main/res/layout/frag_imessaging.xml @@ -10,7 +10,7 @@ android:layout_above="@+id/form" android:stackFromBottom="true" android:transcriptMode="alwaysScroll" - android:background="@color/sfl_dark_blue" + android:background="@color/color_primary_dark" android:layout_alignParentTop="true" > </ListView> diff --git a/ring-android/app/src/main/res/layout/frag_menu_header.xml b/ring-android/app/src/main/res/layout/frag_menu_header.xml index 43686adbd..6a6f118ad 100644 --- a/ring-android/app/src/main/res/layout/frag_menu_header.xml +++ b/ring-android/app/src/main/res/layout/frag_menu_header.xml @@ -2,7 +2,7 @@ <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" - android:background="@color/sfl_blue_0" + android:background="@color/color_primary_dark" android:paddingBottom="8dp" android:paddingRight="5dp" android:paddingTop="40dp" diff --git a/ring-android/app/src/main/res/layout/header.xml b/ring-android/app/src/main/res/layout/header.xml index d9c5b4788..36fc52c54 100644 --- a/ring-android/app/src/main/res/layout/header.xml +++ b/ring-android/app/src/main/res/layout/header.xml @@ -1,8 +1,8 @@ <?xml version="1.0" encoding="utf-8"?> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="@null" + android:layout_height="48dp" + android:background="@android:color/white" android:clickable="false" android:focusable="false" android:orientation="vertical" > @@ -13,10 +13,21 @@ android:layout_height="wrap_content" android:clickable="false" android:focusable="false" - android:textColor="@color/white" - android:background="@color/sfl_dark_blue" + android:textColor="@color/text_color_secondary" android:paddingLeft="5dp" - android:textSize="14sp" - android:textStyle="bold" /> + android:textSize="16sp" + android:textStyle="bold" + android:layout_marginLeft="16dp" + android:text="A" + android:layout_gravity="center_vertical" /> -</LinearLayout> \ No newline at end of file + <ImageView + android:layout_width="fill_parent" + android:layout_height="1dp" + android:id="@+id/imageView2" + android:layout_gravity="bottom" + android:background="#e0e0e0" + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" /> + +</FrameLayout> \ No newline at end of file diff --git a/ring-android/app/src/main/res/layout/item_account.xml b/ring-android/app/src/main/res/layout/item_account.xml index 9629be8ad..d6a902038 100644 --- a/ring-android/app/src/main/res/layout/item_account.xml +++ b/ring-android/app/src/main/res/layout/item_account.xml @@ -20,7 +20,11 @@ android:layout_alignParentLeft="true" android:layout_below="@+id/account_alias" android:textAppearance="@style/ListSecondary" - android:text="hostname" /> + android:text="hostnamehostnamehostnamehostnamehostnamehostnamehostnamehostname" + android:ellipsize="middle" + android:singleLine="true" + android:layout_toLeftOf="@+id/error_indicator" + android:layout_toStartOf="@+id/error_indicator" /> <ImageView android:id="@+id/error_indicator" @@ -31,6 +35,8 @@ android:clickable="false" android:focusable="false" android:src="@drawable/ic_error_white_24dp" - android:focusableInTouchMode="false" /> + android:focusableInTouchMode="false" + android:tint="@android:color/holo_red_light" + android:layout_marginLeft="16dp" /> </RelativeLayout> \ No newline at end of file diff --git a/ring-android/app/src/main/res/layout/item_account_pref.xml b/ring-android/app/src/main/res/layout/item_account_pref.xml index 92f0ae126..ee774ce51 100644 --- a/ring-android/app/src/main/res/layout/item_account_pref.xml +++ b/ring-android/app/src/main/res/layout/item_account_pref.xml @@ -27,13 +27,16 @@ <TextView android:id="@+id/account_host" - android:layout_width="wrap_content" + android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_below="@+id/account_alias" android:layout_marginLeft="72dp" android:textAppearance="@style/ListSecondary" - android:text="hostname" - android:layout_alignParentLeft="true" /> + android:text="hostnamehostnamehostnamehostnamehostname" + android:layout_alignParentLeft="true" + android:ellipsize="middle" + android:singleLine="true" + android:layout_marginRight="56dp" /> <CheckBox android:id="@+id/account_checked" @@ -64,6 +67,7 @@ android:layout_marginRight="16dp" android:layout_centerVertical="true" android:layout_toStartOf="@+id/account_checked" - android:layout_toLeftOf="@+id/loading_indicator" /> + android:layout_toLeftOf="@+id/loading_indicator" + android:tint="@color/error_red" /> </RelativeLayout> \ No newline at end of file diff --git a/ring-android/app/src/main/res/layout/item_account_selected.xml b/ring-android/app/src/main/res/layout/item_account_selected.xml index 3c2a19897..6abbe19e7 100644 --- a/ring-android/app/src/main/res/layout/item_account_selected.xml +++ b/ring-android/app/src/main/res/layout/item_account_selected.xml @@ -24,8 +24,12 @@ android:layout_alignParentLeft="true" android:layout_below="@+id/account_alias" android:textAppearance="?android:attr/textAppearanceSmall" - android:textColor="@color/white" - android:text="hostname" /> + android:textColor="@color/secondary_text_default_material_dark" + android:text="hostnamehostnamehostnamehostnamehostnamehostnamehostname" + android:singleLine="true" + android:ellipsize="middle" + android:layout_toLeftOf="@+id/error_indicator" + android:layout_toStartOf="@+id/error_indicator" /> <ImageView android:id="@+id/error_indicator" @@ -35,6 +39,7 @@ android:layout_centerVertical="true" android:clickable="false" android:focusable="false" - android:src="@drawable/ic_error_white_24dp" /> + android:src="@drawable/ic_error_white_24dp" + android:layout_marginLeft="16dp" /> </RelativeLayout> \ No newline at end of file diff --git a/ring-android/app/src/main/res/layout/item_calllist.xml b/ring-android/app/src/main/res/layout/item_calllist.xml index aa426e411..7cfe3e50c 100644 --- a/ring-android/app/src/main/res/layout/item_calllist.xml +++ b/ring-android/app/src/main/res/layout/item_calllist.xml @@ -1,42 +1,66 @@ <?xml version="1.0" encoding="utf-8"?> +<!-- +Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + +Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3 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., 675 Mass Ave, Cambridge, MA 02139, USA. +--> + <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:id="@+id/call_entry" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_alignParentLeft="true" - android:layout_centerVertical="true" - android:background="@drawable/item_generic_selector" > + android:layout_width="match_parent" + android:layout_height="72dp" + android:background="@drawable/item_history_selector" + android:descendantFocusability="blocksDescendants"> + + <ImageView + android:id="@+id/photo" + android:layout_width="40dp" + android:layout_height="40dp" + android:background="@null" + android:scaleType="centerCrop" + android:layout_centerVertical="true" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" + android:layout_marginRight="16dp" /> <TextView - android:id="@+id/call_title" + android:id="@+id/msg_txt" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentLeft="true" + android:layout_alignParentLeft="false" android:layout_alignParentTop="true" android:ellipsize="marquee" android:marqueeRepeatLimit="marquee_forever" android:scrollHorizontally="true" android:singleLine="true" - android:text="" - android:textSize="20sp" /> + android:textSize="16sp" + android:layout_toRightOf="@+id/photo" + android:textColor="@color/text_color_primary" + android:layout_marginTop="2dp" /> <TextView android:id="@+id/call_status" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentLeft="true" - android:layout_below="@+id/call_title" - android:text="" - android:textSize="12sp" /> - - <TextView - android:id="@+id/call_time" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_below="@+id/call_title" - android:layout_toRightOf="@+id/call_status" - android:paddingLeft="10dp" - android:text="" - android:textSize="12sp" /> + android:layout_alignParentLeft="false" + android:layout_below="@+id/msg_txt" + android:textSize="14sp" + android:layout_toRightOf="@+id/photo" + android:textColor="@color/text_color_secondary" /> </RelativeLayout> \ No newline at end of file diff --git a/ring-android/app/src/main/res/layout/item_codec.xml b/ring-android/app/src/main/res/layout/item_codec.xml index a604aa1cf..cd0bd51a4 100644 --- a/ring-android/app/src/main/res/layout/item_codec.xml +++ b/ring-android/app/src/main/res/layout/item_codec.xml @@ -1,8 +1,10 @@ <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:id="@+id/codec_container" android:layout_width="match_parent" - android:layout_height="?android:attr/listPreferredItemHeight" > + android:layout_height="?android:attr/listPreferredItemHeight" + tools:context=".client.AccountEditionActivity"> <ImageView android:id="@+id/drag_handle" @@ -11,7 +13,7 @@ android:layout_centerVertical="true" android:layout_alignParentLeft="true" android:layout_marginLeft="10dp" - android:src="@drawable/handle"/> + android:src="@drawable/ic_reorder_black_24dp"/> <TextView android:id="@+id/codec_name" diff --git a/ring-android/app/src/main/res/layout/item_contact.xml b/ring-android/app/src/main/res/layout/item_contact.xml index 8b961b0fd..4d2647e47 100644 --- a/ring-android/app/src/main/res/layout/item_contact.xml +++ b/ring-android/app/src/main/res/layout/item_contact.xml @@ -33,110 +33,63 @@ as that of the covered work. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" - android:layout_height="match_parent" > + android:layout_height="wrap_content" > <RelativeLayout + android:id="@+id/contactview" android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="@color/sfl_action_blue" + android:layout_height="wrap_content" + android:padding="16dp" + android:background="@android:color/white" android:descendantFocusability="blocksDescendants" > - <LinearLayout - android:id="@+id/contact_underview" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_alignParentBottom="true" - android:layout_alignParentTop="true" - android:layout_centerVertical="true" - android:orientation="horizontal" - android:weightSum="6" > + <ImageView + android:id="@+id/photo" + android:layout_width="40dp" + android:layout_height="40dp" + android:contentDescription="@string/contact_picture_description" + android:scaleType="centerCrop" /> - <ImageButton - android:id="@+id/quick_starred" - android:layout_width="0dp" - android:layout_height="match_parent" - android:layout_weight="1" - android:background="@null" - android:contentDescription="@string/contact_quick_starred_description" - android:src="@drawable/ic_action_not_important" /> + <TextView + android:id="@+id/display_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center_vertical" + android:textColor="@color/text_color_primary" + android:textIsSelectable="false" + android:layout_centerVertical="true" + android:layout_toRightOf="@+id/photo" + android:layout_toEndOf="@+id/photo" + android:textSize="16sp" + android:layout_marginLeft="16dp" /> - <ImageButton - android:id="@+id/quick_edit" - android:layout_width="0dp" - android:layout_height="match_parent" - android:layout_weight="1" - android:background="@null" - android:contentDescription="@string/contact_quick_edit_description" - android:src="@drawable/ic_action_edit" /> + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_centerVertical="true" + android:divider="@drawable/divider" + android:showDividers="middle" + android:visibility="gone"> <ImageButton - android:id="@+id/quick_discard" - android:layout_width="0dp" - android:layout_height="match_parent" - android:layout_weight="1" - android:background="@null" - android:contentDescription="@string/contact_quick_discard_description" - android:src="@drawable/ic_action_discard" /> - - <Space - android:layout_width="0dp" - android:layout_height="match_parent" - android:layout_weight="3" /> - </LinearLayout> - - <RelativeLayout - android:id="@+id/contactview" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="@drawable/item_contact_selector" > - - <ImageView - android:id="@+id/photo" - android:layout_width="70dp" - android:layout_height="70dp" - android:contentDescription="@string/contact_picture_description" - android:scaleType="centerCrop" /> - - <TextView - android:id="@+id/display_name" + android:id="@+id/quick_call" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignBottom="@+id/photo" - android:layout_alignTop="@+id/photo" - android:layout_marginLeft="15dp" - android:layout_toRightOf="@+id/photo" - android:gravity="center_vertical" - android:textAppearance="?android:attr/textAppearanceMedium" - android:textColor="@color/white" - android:textSize="14sp" /> + android:layout_margin="5dp" + android:background="@null" + android:contentDescription="@string/contact_quick_call_description" + android:src="@drawable/ic_action_call" /> - <LinearLayout + <ImageButton + android:id="@+id/quick_message" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentRight="true" - android:layout_centerVertical="true" - android:divider="@drawable/divider" - android:showDividers="middle" > - - <ImageButton - android:id="@+id/quick_call" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_margin="5dp" - android:background="@null" - android:contentDescription="@string/contact_quick_call_description" - android:src="@drawable/ic_action_call" /> - - <ImageButton - android:id="@+id/quick_message" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_margin="5dp" - android:background="@null" - android:contentDescription="@string/contact_quick_msg_description" - android:src="@drawable/ic_action_chat" /> - </LinearLayout> - </RelativeLayout> + android:layout_margin="5dp" + android:background="@null" + android:contentDescription="@string/contact_quick_msg_description" + android:src="@drawable/ic_action_chat" /> + </LinearLayout> </RelativeLayout> </LinearLayout> \ No newline at end of file diff --git a/ring-android/app/src/main/res/layout/item_contact_starred.xml b/ring-android/app/src/main/res/layout/item_contact_starred.xml index d3426a757..6b9e396b9 100644 --- a/ring-android/app/src/main/res/layout/item_contact_starred.xml +++ b/ring-android/app/src/main/res/layout/item_contact_starred.xml @@ -1,32 +1,30 @@ <?xml version="1.0" encoding="utf-8"?> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" > - - <RelativeLayout - android:id="@+id/contactview" - android:layout_width="match_parent" - android:layout_height="match_parent" > +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:id="@+id/contactview" + android:orientation="vertical"> <ImageView android:id="@+id/photo" - android:layout_width="70dp" - android:layout_height="70dp" + android:layout_width="40dp" + android:layout_height="40dp" android:layout_centerHorizontal="true" android:contentDescription="@string/contact_picture_description" - android:scaleType="centerCrop" /> + android:scaleType="centerCrop" + android:layout_gravity="center_horizontal" /> <TextView android:id="@+id/display_name" - android:layout_width="wrap_content" + android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_below="@+id/photo" android:layout_centerHorizontal="true" android:gravity="center" android:singleLine="true" - android:textColor="@color/white" + android:textColor="@color/text_color_primary" android:textSize="12sp" /> - </RelativeLayout> </LinearLayout> \ No newline at end of file diff --git a/ring-android/app/src/main/res/layout/item_history.xml b/ring-android/app/src/main/res/layout/item_history.xml index d56455ae2..129787393 100644 --- a/ring-android/app/src/main/res/layout/item_history.xml +++ b/ring-android/app/src/main/res/layout/item_history.xml @@ -17,25 +17,15 @@ 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., 675 Mass Ave, Cambridge, MA 02139, USA. - -Additional permission under GNU GPL version 3 section 7: - -If you modify this program, or any covered work, by linking or -combining it with the OpenSSL project's OpenSSL library (or a -modified version of that library), containing parts covered by the -terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. -grants you additional permission to convey the resulting work. -Corresponding Source for a non-source form of such a combination -shall include the source code for the parts of OpenSSL used as well -as that of the covered work. --> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:id="@+id/contactview" android:layout_width="match_parent" - android:layout_height="match_parent" + android:layout_height="100dp" android:background="@drawable/item_history_selector" - android:descendantFocusability="blocksDescendants" > + android:descendantFocusability="blocksDescendants"> <ImageButton android:id="@+id/photo" @@ -54,7 +44,8 @@ as that of the covered work. android:layout_toRightOf="@+id/photo" android:paddingLeft="@dimen/padding_small" android:singleLine="true" - android:textAppearance="?android:attr/textAppearanceMedium" /> + android:textAppearance="?android:attr/textAppearanceMedium" + android:text="Adrien" /> <TextView android:id="@+id/date_start" diff --git a/ring-android/app/src/main/res/layout/item_textmsg.xml b/ring-android/app/src/main/res/layout/item_textmsg.xml new file mode 100644 index 000000000..ddef7b47a --- /dev/null +++ b/ring-android/app/src/main/res/layout/item_textmsg.xml @@ -0,0 +1,155 @@ +<?xml version="1.0" encoding="utf-8"?><!-- +Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + +Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3 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., 675 Mass Ave, Cambridge, MA 02139, USA. +--> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + + <RelativeLayout + android:id="@+id/txt_entry_right" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@android:color/transparent" + android:padding="@dimen/padding_large" + android:focusable="false" + android:layout_gravity="right" + android:visibility="gone"> + + <TextView + android:id="@+id/msg_txt_right" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="@android:color/white" + android:ellipsize="marquee" + android:marqueeRepeatLimit="marquee_forever" + android:padding="12dp" + android:scrollHorizontally="true" + android:singleLine="false" + android:text="Ceci est un long message sur plusieurs lignes. Il apparaitera en multilignes" + android:textColor="@color/text_color_primary" + android:textSize="16sp" + android:focusable="true" + android:textIsSelectable="true" + android:layout_alignParentTop="true" + android:layout_alignParentRight="true" + android:layout_alignParentEnd="true" + android:layout_marginLeft="48dp" /> + + <TextView + android:id="@+id/msg_details_txt_right" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Adrien - 12 mars" + android:textColor="@color/text_color_secondary" + android:textSize="14sp" + android:layout_below="@+id/msg_txt_right" + android:layout_alignRight="@+id/msg_txt_right" + android:layout_alignEnd="@+id/msg_txt_right" /> + </RelativeLayout> + + <RelativeLayout + android:id="@+id/txt_entry" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@android:color/transparent" + android:padding="@dimen/padding_large" + android:focusable="false"> + + <ImageView + android:id="@+id/photo" + android:layout_width="40dp" + android:layout_height="40dp" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" + android:layout_alignParentTop="true" + android:layout_marginRight="16dp" + android:background="@null" + android:scaleType="centerCrop" /> + + <TextView + android:id="@+id/msg_txt" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentTop="true" + android:layout_toEndOf="@+id/photo" + android:layout_toRightOf="@+id/photo" + android:background="@android:color/white" + android:ellipsize="marquee" + android:marqueeRepeatLimit="marquee_forever" + android:padding="12dp" + android:scrollHorizontally="true" + android:singleLine="false" + android:text="Ceci est un long message sur plusieurs lignes. Il apparaitera en multilignes" + android:textColor="@color/text_color_primary" + android:textSize="16sp" + android:focusable="true" + android:textIsSelectable="true" + android:layout_marginRight="48dp" /> + + <TextView + android:id="@+id/msg_details_txt" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentLeft="false" + android:layout_below="@+id/msg_txt" + android:layout_toRightOf="@+id/photo" + android:text="Adrien - 12 mars" + android:textColor="@color/text_color_secondary" + android:textSize="14sp" /> + + </RelativeLayout> + + <RelativeLayout + android:id="@+id/call_entry" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:focusable="false" + android:descendantFocusability="blocksDescendants" + android:background="#ced8da" + android:padding="12dp" + android:layout_marginBottom="16dp" + android:layout_gravity="right|bottom" + android:visibility="gone"> + + <TextView + android:id="@+id/call_hist_txt" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:ellipsize="marquee" + android:marqueeRepeatLimit="marquee_forever" + android:scrollHorizontally="true" + android:singleLine="false" + android:text="Appel manqué" + android:textColor="@color/text_color_primary" + android:textSize="14sp" + /> + + <TextView + android:id="@+id/call_details_txt" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentLeft="false" + android:text="Adrien - 12 mars" + android:textColor="@color/text_color_secondary" + android:textSize="12sp" + android:layout_below="@+id/call_hist_txt" /> + + </RelativeLayout> +</FrameLayout> diff --git a/ring-android/app/src/main/res/menu/ac_call.xml b/ring-android/app/src/main/res/menu/ac_call.xml index 7e7469926..eff7bf789 100644 --- a/ring-android/app/src/main/res/menu/ac_call.xml +++ b/ring-android/app/src/main/res/menu/ac_call.xml @@ -4,7 +4,7 @@ <item android:id="@+id/menuitem_chat" android:showAsAction="always" - android:icon="@drawable/ic_action_chat" + android:icon="@drawable/ic_chat_white_24dp" android:title="@string/ab_action_chat"/> </menu> \ No newline at end of file diff --git a/ring-android/app/src/main/res/menu/conversation_actions.xml b/ring-android/app/src/main/res/menu/conversation_actions.xml new file mode 100644 index 000000000..601faf44d --- /dev/null +++ b/ring-android/app/src/main/res/menu/conversation_actions.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + <item + android:id="@+id/conv_action_videocall" + android:icon="@drawable/ic_videocam_white_24dp" + android:title="Video call" + android:showAsAction="always" + /> + <item + android:id="@+id/conv_action_audiocall" + android:icon="@drawable/ic_call_white_24dp" + android:title="Audio call" + android:showAsAction="always"/> +</menu> \ No newline at end of file diff --git a/ring-android/app/src/main/res/menu/newconv_option_menu.xml b/ring-android/app/src/main/res/menu/newconv_option_menu.xml new file mode 100644 index 000000000..9ef81223b --- /dev/null +++ b/ring-android/app/src/main/res/menu/newconv_option_menu.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:id="@+id/contact_search" + android:title="Nom d'un contact ou numéro" + android:icon="@drawable/ic_btn_search" + android:showAsAction="collapseActionView|ifRoom" + android:actionViewClass="android.widget.SearchView" /> +</menu> \ No newline at end of file diff --git a/ring-android/app/src/main/res/values-land/dimens.xml b/ring-android/app/src/main/res/values-land/dimens.xml new file mode 100644 index 000000000..b2c3f525d --- /dev/null +++ b/ring-android/app/src/main/res/values-land/dimens.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +Copyright (C) 2004-2014 Savoir-Faire Linux Inc. + +Author: Adrien Beraud <adrien.beraud@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +Additional permission under GNU GPL version 3 section 7: + +If you modify this program, or any covered work, by linking or +combining it with the OpenSSL project's OpenSSL library (or a +modified version of that library), containing parts covered by the +terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. +grants you additional permission to convey the resulting work. +Corresponding Source for a non-source form of such a combination +shall include the source code for the parts of OpenSSL used as well +as that of the covered work. +--> +<resources> + + <dimen name="action_bar_title_margin_bottom">10dp</dimen> + +</resources> \ No newline at end of file diff --git a/ring-android/app/src/main/res/values-sw600dp/dimens.xml b/ring-android/app/src/main/res/values-sw600dp/dimens.xml new file mode 100644 index 000000000..bcb3ccb03 --- /dev/null +++ b/ring-android/app/src/main/res/values-sw600dp/dimens.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +Copyright (C) 2004-2014 Savoir-Faire Linux Inc. + +Author: Adrien Beraud <adrien.beraud@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +Additional permission under GNU GPL version 3 section 7: + +If you modify this program, or any covered work, by linking or +combining it with the OpenSSL project's OpenSSL library (or a +modified version of that library), containing parts covered by the +terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. +grants you additional permission to convey the resulting work. +Corresponding Source for a non-source form of such a combination +shall include the source code for the parts of OpenSSL used as well +as that of the covered work. +--> +<resources> + + <dimen name="action_bar_title_margin_bottom">20dp</dimen> + +</resources> \ No newline at end of file diff --git a/ring-android/app/src/main/res/values-v21/dimens.xml b/ring-android/app/src/main/res/values-v21/dimens.xml new file mode 100644 index 000000000..f3c4cba48 --- /dev/null +++ b/ring-android/app/src/main/res/values-v21/dimens.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +Copyright (C) 2004-2014 Savoir-Faire Linux Inc. + +Author: Adrien Beraud <adrien.beraud@gmail.com> + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 3 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +Additional permission under GNU GPL version 3 section 7: + +If you modify this program, or any covered work, by linking or +combining it with the OpenSSL project's OpenSSL library (or a +modified version of that library), containing parts covered by the +terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. +grants you additional permission to convey the resulting work. +Corresponding Source for a non-source form of such a combination +shall include the source code for the parts of OpenSSL used as well +as that of the covered work. +--> +<resources> + + <dimen name="action_button_bpadding">-20dp</dimen> + <dimen name="fab_compat_margin">16dp</dimen> + +</resources> \ No newline at end of file diff --git a/ring-android/app/src/main/res/values-v21/styles.xml b/ring-android/app/src/main/res/values-v21/styles.xml index cbf5228c9..89b7d60e2 100644 --- a/ring-android/app/src/main/res/values-v21/styles.xml +++ b/ring-android/app/src/main/res/values-v21/styles.xml @@ -1,22 +1,23 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android"> <style name="AppThemeWithOverlay" parent="AppThemeBase"> - <item name="android:actionBarStyle">@style/MyActionBar</item> - <item name="android:windowActionBarOverlay">true</item> - <item name="windowActionBarOverlay">true</item> - <item name="android:windowBackground">@drawable/bg_72</item> <item name="android:windowDrawsSystemBarBackgrounds">true</item> - <item name="android:statusBarColor">@android:color/transparent</item> + <item name="android:statusBarColor">@color/color_primary_light</item> <item name="android:windowTranslucentStatus">true</item> </style> <style name="AppThemeWithoutOverlay" parent="@android:style/Theme.Material.Light.DarkActionBar"> <item name="android:actionBarStyle">@style/NativeActionBar</item> + <item name="android:colorAccent">@color/color_primary_light</item> + <item name="android:colorPrimary">@color/color_primary_light</item> + <item name="android:colorPrimaryDark">@color/color_primary_dark</item> + </style> <style name="NativeActionBar" parent="@android:style/Widget.ActionBar"> - <item name="android:background">@color/sfl_dark_blue</item> + <item name="android:background">@color/actionbar</item> <item name="android:titleTextStyle">@style/NativeActionBar.Text</item> + <item name="android:elevation">4dp</item> </style> <style name="NativeActionBar.Text" parent="@android:style/TextAppearance.Material.Widget.ActionBar.Title"> diff --git a/ring-android/app/src/main/res/values/colors.xml b/ring-android/app/src/main/res/values/colors.xml index b21f6b043..bb377d68b 100644 --- a/ring-android/app/src/main/res/values/colors.xml +++ b/ring-android/app/src/main/res/values/colors.xml @@ -1,14 +1,24 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - - - <!-- SFL colors --> + + <color name="color_primary_light">#3AC0D2</color> + <color name="color_primary_light_shadow">#40b5c6</color> + + <color name="color_primary_dark">#004C60</color> + + + <color name="action_blue">#004C60</color> + + <color name="actionbar">@color/color_primary_light</color> + + + <!-- SFL colors--> <color name="sfl_pantone631_blue">#56B0C9</color> - <color name="sfl_dark_blue">#09353c</color> + <!-- <color name="sfl_dark_blue">#09353c</color> <color name="sfl_another_blue">#051d21</color> <color name="sfl_action_blue">#AA2eadda</color> - + --> <color name="sfl_blue_0">#002930</color> @@ -51,8 +61,12 @@ <color name="holo_orange_dark">#ffff8800</color> <!-- A really bright Holo shade of blue --> <color name="holo_blue_bright">#ff00ddff</color> - - + <color name="transparent_grey">#AACCCCCC</color> - + + <color name="text_color_primary">@color/abc_primary_text_material_light</color> + <color name="text_color_secondary">@color/abc_secondary_text_material_light</color> + <color name="text_color_primary_dark">@color/abc_primary_text_material_dark</color> + <color name="text_color_secondary_dark">@color/abc_secondary_text_material_dark</color> + </resources> diff --git a/ring-android/app/src/main/res/values/dimens.xml b/ring-android/app/src/main/res/values/dimens.xml index 2d3ac13ef..31e5c9287 100644 --- a/ring-android/app/src/main/res/values/dimens.xml +++ b/ring-android/app/src/main/res/values/dimens.xml @@ -47,8 +47,14 @@ as that of the covered work. <dimen name="contact_drawer_handle_height">36dp</dimen> <dimen name="contact_drawer_handle_height_with_shadow">40dp</dimen> - <dimen name="contact_vertical_spacing">10dp</dimen> + <dimen name="contact_vertical_spacing">16dp</dimen> <dimen name="header_history_detail">200dp</dimen> + + <dimen name="action_button_bpadding">-35dp</dimen> + <dimen name="fab_compat_margin">0dp</dimen> + + <dimen name="action_bar_title_margin_bottom">12dp</dimen> + </resources> \ No newline at end of file diff --git a/ring-android/app/src/main/res/values/strings.xml b/ring-android/app/src/main/res/values/strings.xml index b2584e7bd..597891934 100644 --- a/ring-android/app/src/main/res/values/strings.xml +++ b/ring-android/app/src/main/res/values/strings.xml @@ -63,8 +63,8 @@ as that of the covered work. <!-- Dialing Fragment --> <string name="dial_action_call">Call</string> - <string name="dial_error_no_number_dialed">Dial a number</string> - <string name="dial_hint">Type phone number</string> + <string name="dial_error_no_number_dialed">Dial a mNumber</string> + <string name="dial_hint">Type phone mNumber</string> <!-- History Fragment --> <string name="hist_replay_button">Replay</string> @@ -89,7 +89,7 @@ as that of the covered work. <!-- ContactList Fragment --> <string name="no_contact_found">No contact found</string> <string name="starred_contacts_title">Favorites</string> - <string name="searchbar_hint">Enter name or phone number…</string> + <string name="searchbar_hint">Enter name or phone mNumber…</string> <!-- FileExplorerDFragement --> <string name="file_explorer_title">Select a file</string> @@ -97,7 +97,7 @@ as that of the covered work. <!-- TransferDFragment --> <string name="transfer_to_another_call">Transfer to another current call:</string> <string name="transfer_no_other_call">No other calls pending</string> - <string name="transfer_type_number">Type number to transfer to:</string> + <string name="transfer_type_number">Type mNumber to transfer to:</string> <!-- Notifications --> <string name="notif_missed_call_title">Missed call</string> diff --git a/ring-android/app/src/main/res/values/styles.xml b/ring-android/app/src/main/res/values/styles.xml index 89379e3f8..0e1b43b60 100644 --- a/ring-android/app/src/main/res/values/styles.xml +++ b/ring-android/app/src/main/res/values/styles.xml @@ -1,16 +1,15 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android"> - <style name="AppThemeBase" parent="Theme.AppCompat.Light.NoActionBar"> - + <style name="AppThemeBase" parent="@style/Theme.AppCompat.Light.NoActionBar"> + <item name="actionBarStyle">@style/MyActionBar</item> + <item name="colorAccent">@color/color_primary_dark</item> + <item name="colorPrimary">@color/color_primary_light</item> + <item name="colorPrimaryDark">@color/color_primary_dark</item> + <item name="android:windowActionBarOverlay">true</item> + <item name="windowActionBarOverlay">true</item> </style> <style name="AppThemeWithOverlay" parent="AppThemeBase"> - - <item name="android:actionBarStyle">@style/MyActionBar</item> - - <item name="android:windowActionBarOverlay">true</item> - <item name="windowActionBarOverlay">true</item> - <item name="android:windowBackground">@drawable/bg_72</item> <item name="android:activatedBackgroundIndicator">@drawable/navigation_selector</item> </style> @@ -24,22 +23,26 @@ <item name="android:actionBarStyle">@style/NativeActionBar</item> </style> - <style name="NativeActionBar" parent="@android:style/Widget.ActionBar"> - <item name="android:background">@color/sfl_dark_blue</item> - <item name="titleTextStyle">@style/TextAppearance.AppCompat.Widget.ActionBar.Title.Inverse</item> - - </style> - - <style name="NativeActionBar.Text" parent="@android:style/TextAppearance.Holo.Widget.ActionBar.Title"> + <style name="NativeActionBar" parent="@android:style/Widget.DeviceDefault.Light.ActionBar.Solid.Inverse"> + <item name="android:background">@color/color_primary_light</item> + <item name="elevation">4dp</item> </style> - <style name="MyActionBar" parent="@style/Widget.AppCompat.Light.ActionBar.Solid"> + <style name="MyActionBar" parent="@style/Widget.AppCompat.ActionBar.Solid"> <item name="android:textColorPrimary">@android:color/primary_text_dark</item> + <item name="android:textColorHighlight">@android:color/primary_text_dark</item> <item name="android:textColorSecondary">@android:color/secondary_text_dark</item> + <item name="colorControlNormal">@color/white</item> <item name="selectableItemBackground">?android:selectableItemBackground</item> <item name="selectableItemBackgroundBorderless">?android:selectableItemBackground</item> - <item name="actionMenuTextColor">@color/abc_primary_text_material_light</item> + <item name="titleTextStyle">@style/MyTitleTextStyle</item> + <item name="elevation">4dp</item> </style> + + <style name="MyTitleTextStyle" parent="@style/TextAppearance.AppCompat.Widget.ActionBar.Title"> + <item name="android:textColor">@color/error_red</item> + </style> + <style name="MenuHeader" parent="Theme.AppCompat.Light.NoActionBar"> <item name="android:textColorPrimary">@android:color/primary_text_dark</item> <item name="android:textColorSecondary">@android:color/secondary_text_dark</item> diff --git a/ring-android/build.gradle b/ring-android/build.gradle index cb5115855..f0c9817f0 100644 --- a/ring-android/build.gradle +++ b/ring-android/build.gradle @@ -4,7 +4,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:1.2.3' + classpath 'com.android.tools.build:gradle:1.3.+' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/ring-android/gradle/wrapper/gradle-wrapper.properties b/ring-android/gradle/wrapper/gradle-wrapper.properties index 9e4462dda..62354ba52 100644 --- a/ring-android/gradle/wrapper/gradle-wrapper.properties +++ b/ring-android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Jun 17 16:19:43 EDT 2015 +#Wed Jun 17 18:34:27 EDT 2015 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip diff --git a/ring-android/project.properties b/ring-android/project.properties index 00cf62bac..916037e33 100644 --- a/ring-android/project.properties +++ b/ring-android/project.properties @@ -11,4 +11,4 @@ #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt # Project target. -target=android-22 +target=android-23 diff --git a/ring-android/ring-android.iml b/ring-android/ring-android.iml index aa770af9c..c30d36ecf 100644 --- a/ring-android/ring-android.iml +++ b/ring-android/ring-android.iml @@ -8,14 +8,14 @@ </configuration> </facet> </component> - <component name="NewModuleRootManager" inherit-compiler-output="true"> + <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_7" inherit-compiler-output="true"> <output url="file://$MODULE_DIR$/build/intermediates/classes/debug" /> <output-test url="file://$MODULE_DIR$/build/intermediates/classes/androidTest/debug" /> <exclude-output /> <content url="file://$MODULE_DIR$"> <excludeFolder url="file://$MODULE_DIR$/.gradle" /> </content> - <orderEntry type="jdk" jdkName="Android API 22 Platform" jdkType="Android SDK" /> + <orderEntry type="jdk" jdkName="Android API 23 Platform" jdkType="Android SDK" /> <orderEntry type="sourceFolder" forTests="false" /> </component> </module> \ No newline at end of file -- GitLab