From 418f9ba4c9acd3f136948e933e17190c4d3fd37f Mon Sep 17 00:00:00 2001 From: Sylvain Berfini Date: Mon, 15 Jul 2024 15:55:03 +0200 Subject: [PATCH] 6.0.0 cleanup --- .gitignore | 29 - .gitlab-ci-files/job-android.yml | 35 - .gitlab-ci-files/job-upload.yml | 12 - .gitlab-ci.yml | 19 - app/.gitignore | 1 - app/build.gradle | 285 ---- app/contacts.xml | 11 - app/google-services.json | 57 - app/proguard-rules.pro | 41 - app/src/main/AndroidManifest.xml | 256 --- app/src/main/assets/assistant_default_values | 42 - .../assets/assistant_linphone_default_values | 42 - app/src/main/assets/linphonerc_default | 44 - app/src/main/assets/linphonerc_factory | 54 - .../java/org/linphone/LinphoneApplication.kt | 143 -- .../linphone/activities/GenericActivity.kt | 137 -- .../linphone/activities/GenericFragment.kt | 185 --- .../org/linphone/activities/Navigation.kt | 1222 --------------- .../activities/ProximitySensorActivity.kt | 87 -- .../linphone/activities/SnackBarActivity.kt | 28 - .../activities/assistant/AssistantActivity.kt | 65 - .../adapters/CountryPickerAdapter.kt | 99 -- .../fragments/AbstractPhoneFragment.kt | 91 -- .../fragments/AccountLoginFragment.kt | 142 -- .../fragments/CountryPickerFragment.kt | 87 -- .../EchoCancellerCalibrationFragment.kt | 87 -- .../fragments/EmailAccountCreationFragment.kt | 71 - .../EmailAccountValidationFragment.kt | 81 - .../fragments/GenericAccountLoginFragment.kt | 108 -- .../GenericAccountWarningFragment.kt | 41 - .../fragments/NoPushWarningFragment.kt | 36 - .../fragments/PhoneAccountCreationFragment.kt | 90 -- .../fragments/PhoneAccountLinkingFragment.kt | 113 -- .../PhoneAccountValidationFragment.kt | 122 -- .../assistant/fragments/QrCodeFragment.kt | 112 -- .../fragments/RemoteProvisioningFragment.kt | 84 - .../assistant/fragments/TopBarFragment.kt | 37 - .../assistant/fragments/WelcomeFragment.kt | 170 -- .../viewmodels/AbstractPhoneViewModel.kt | 84 - .../viewmodels/AbstractPushTokenViewModel.kt | 143 -- .../viewmodels/AccountLoginViewModel.kt | 303 ---- .../EchoCancellerCalibrationViewModel.kt | 68 - .../EmailAccountCreationViewModel.kt | 199 --- .../EmailAccountValidationViewModel.kt | 132 -- .../viewmodels/GenericLoginViewModel.kt | 159 -- .../PhoneAccountCreationViewModel.kt | 271 ---- .../PhoneAccountLinkingViewModel.kt | 157 -- .../PhoneAccountValidationViewModel.kt | 162 -- .../assistant/viewmodels/QrCodeViewModel.kt | 73 - .../viewmodels/RemoteProvisioningViewModel.kt | 89 -- .../viewmodels/SharedAssistantViewModel.kt | 59 - .../assistant/viewmodels/WelcomeViewModel.kt | 37 - .../chat_bubble/ChatBubbleActivity.kt | 225 --- .../linphone/activities/main/MainActivity.kt | 740 --------- .../activities/main/about/AboutFragment.kt | 81 - .../activities/main/about/AboutViewModel.kt | 29 - .../main/adapters/SelectionListAdapter.kt | 40 - .../main/chat/ChatScrollListener.kt | 95 -- .../main/chat/GroupChatRoomMember.kt | 32 - .../chat/adapters/ChatMessagesListAdapter.kt | 656 -------- .../chat/adapters/ChatRoomsListAdapter.kt | 139 -- .../adapters/GroupInfoParticipantsAdapter.kt | 100 -- .../main/chat/adapters/ImdnAdapter.kt | 130 -- .../chat/data/ChatMessageAttachmentData.kt | 48 - .../main/chat/data/ChatMessageContentData.kt | 560 ------- .../main/chat/data/ChatMessageData.kt | 351 ----- .../main/chat/data/ChatMessageReactionData.kt | 35 - .../chat/data/ChatMessageReactionsListData.kt | 98 -- .../activities/main/chat/data/ChatRoomData.kt | 277 ---- .../main/chat/data/DevicesListChildData.kt | 49 - .../main/chat/data/DevicesListGroupData.kt | 78 - .../main/chat/data/EphemeralDurationData.kt | 37 - .../activities/main/chat/data/EventData.kt | 137 -- .../activities/main/chat/data/EventLogData.kt | 39 - .../chat/data/GroupInfoParticipantData.kt | 73 - .../main/chat/data/ImdnParticipantData.kt | 32 - .../ChatMessageReactionsListDialogFragment.kt | 109 -- .../fragments/ChatRoomCreationFragment.kt | 193 --- .../chat/fragments/DetailChatRoomFragment.kt | 1369 ---------------- .../main/chat/fragments/DevicesFragment.kt | 64 - .../main/chat/fragments/EphemeralFragment.kt | 66 - .../main/chat/fragments/GroupInfoFragment.kt | 238 --- .../main/chat/fragments/ImdnFragment.kt | 101 -- .../chat/fragments/MasterChatRoomsFragment.kt | 412 ----- .../chat/receivers/RichContentReceiver.kt | 50 - .../viewmodels/ChatMessageSendingViewModel.kt | 596 ------- .../viewmodels/ChatMessagesListViewModel.kt | 270 ---- .../viewmodels/ChatRoomCreationViewModel.kt | 155 -- .../main/chat/viewmodels/ChatRoomViewModel.kt | 447 ------ .../chat/viewmodels/ChatRoomsListViewModel.kt | 195 --- .../chat/viewmodels/DevicesListViewModel.kt | 83 - .../chat/viewmodels/EphemeralViewModel.kt | 136 -- .../chat/viewmodels/GroupInfoViewModel.kt | 257 --- .../main/chat/viewmodels/ImdnViewModel.kt | 85 - .../MultiLineWrapContentWidthTextView.kt | 74 - .../main/chat/views/RichEditText.kt | 97 -- .../adapters/ScheduledConferencesAdapter.kt | 195 --- .../ConferenceSchedulingParticipantData.kt | 53 - .../main/conference/data/Duration.kt | 30 - .../data/ScheduledConferenceData.kt | 193 --- .../main/conference/data/TimeZoneData.kt | 59 - .../fragments/ConferenceSchedulingFragment.kt | 129 -- ...renceSchedulingParticipantsListFragment.kt | 124 -- .../ConferenceSchedulingSummaryFragment.kt | 72 - .../ConferenceWaitingRoomFragment.kt | 204 --- .../fragments/ScheduledConferencesFragment.kt | 188 --- .../ConferenceSchedulingViewModel.kt | 416 ----- .../ConferenceWaitingRoomViewModel.kt | 499 ------ .../ScheduledConferencesViewModel.kt | 160 -- .../contact/adapters/ContactsListAdapter.kt | 151 -- .../contact/adapters/SyncAccountAdapter.kt | 49 - .../main/contact/data/ContactEditorData.kt | 360 ----- .../data/ContactNumberOrAddressData.kt | 61 - .../contact/data/NumberOrAddressEditorData.kt | 37 - .../fragments/ContactEditorFragment.kt | 251 --- .../fragments/DetailContactFragment.kt | 194 --- .../fragments/MasterContactsFragment.kt | 392 ----- .../fragments/SyncAccountPickerFragment.kt | 76 - .../contact/viewmodels/ContactViewModel.kt | 271 ---- .../viewmodels/ContactsListViewModel.kt | 232 --- .../main/dialer/NumpadDigitListener.kt | 25 - .../dialer/fragments/ConfigViewerFragment.kt | 64 - .../main/dialer/fragments/DialerFragment.kt | 401 ----- .../dialer/viewmodels/ConfigFileViewModel.kt | 32 - .../main/dialer/viewmodels/DialerViewModel.kt | 249 --- .../files/adapters/PdfPagesListAdapter.kt | 52 - .../files/fragments/AudioViewerFragment.kt | 98 -- .../files/fragments/GenericViewerFragment.kt | 51 - .../files/fragments/ImageViewerFragment.kt | 72 - .../main/files/fragments/PdfViewerFragment.kt | 64 - .../files/fragments/TextViewerFragment.kt | 55 - .../main/files/fragments/TopBarFragment.kt | 184 --- .../files/fragments/VideoViewerFragment.kt | 128 -- .../files/viewmodels/AudioFileViewModel.kt | 106 -- .../files/viewmodels/FileViewerViewModel.kt | 57 - .../files/viewmodels/ImageFileViewModel.kt | 35 - .../main/files/viewmodels/PdfFileViewModel.kt | 114 -- .../files/viewmodels/TextFileViewModel.kt | 72 - .../files/viewmodels/VideoFileViewModel.kt | 35 - .../main/fragments/EmptyFragment.kt | 40 - .../main/fragments/ListTopBarFragment.kt | 61 - .../main/fragments/MasterFragment.kt | 169 -- .../main/fragments/SecureFragment.kt | 101 -- .../main/fragments/StatusFragment.kt | 65 - .../activities/main/fragments/TabsFragment.kt | 148 -- .../history/adapters/CallLogsListAdapter.kt | 167 -- .../main/history/data/GroupedCallLogData.kt | 53 - .../fragments/DetailCallLogFragment.kt | 152 -- .../DetailConferenceCallLogFragment.kt | 65 - .../fragments/MasterCallLogsFragment.kt | 314 ---- .../history/viewmodels/CallLogViewModel.kt | 260 ---- .../viewmodels/CallLogsListViewModel.kt | 170 -- .../adapters/RecordingsListAdapter.kt | 163 -- .../main/recordings/data/RecordingData.kt | 214 --- .../fragments/RecordingsFragment.kt | 149 -- .../viewmodels/RecordingsViewModel.kt | 120 -- .../main/settings/SettingListener.kt | 32 - .../main/settings/SettingListenerStub.kt | 32 - .../fragments/AccountSettingsFragment.kt | 150 -- .../fragments/AdvancedSettingsFragment.kt | 147 -- .../fragments/AudioSettingsFragment.kt | 124 -- .../fragments/CallSettingsFragment.kt | 193 --- .../fragments/ChatSettingsFragment.kt | 95 -- .../fragments/ConferencesSettingsFragment.kt | 43 - .../fragments/ContactsSettingsFragment.kt | 142 -- .../fragments/GenericSettingFragment.kt | 33 - .../fragments/LdapSettingsFragment.kt | 66 - .../fragments/NetworkSettingsFragment.kt | 43 - .../settings/fragments/SettingsFragment.kt | 186 --- .../fragments/TunnelSettingsFragment.kt | 43 - .../fragments/VideoSettingsFragment.kt | 108 -- .../viewmodels/AccountSettingsViewModel.kt | 561 ------- .../viewmodels/AdvancedSettingsViewModel.kt | 187 --- .../viewmodels/AudioSettingsViewModel.kt | 296 ---- .../viewmodels/CallSettingsViewModel.kt | 317 ---- .../viewmodels/ChatSettingsViewModel.kt | 173 -- .../ConferencesSettingsViewModel.kt | 62 - .../viewmodels/ContactsSettingsViewModel.kt | 151 -- .../viewmodels/GenericSettingsViewModel.kt | 29 - .../viewmodels/LdapSettingsViewModel.kt | 297 ---- .../viewmodels/NetworkSettingsViewModel.kt | 81 - .../settings/viewmodels/SettingsViewModel.kt | 122 -- .../viewmodels/TunnelSettingsViewModel.kt | 137 -- .../viewmodels/VideoSettingsViewModel.kt | 195 --- .../sidemenu/fragments/SideMenuFragment.kt | 266 ---- .../sidemenu/viewmodels/SideMenuViewModel.kt | 128 -- .../main/viewmodels/CallOverlayViewModel.kt | 79 - .../main/viewmodels/DialogViewModel.kt | 112 -- .../main/viewmodels/ListTopBarViewModel.kt | 82 - .../main/viewmodels/LogsUploadViewModel.kt | 80 - .../viewmodels/MessageNotifierViewModel.kt | 29 - .../main/viewmodels/SharedMainViewModel.kt | 121 -- .../main/viewmodels/StatusViewModel.kt | 118 -- .../main/viewmodels/TabsViewModel.kt | 122 -- .../linphone/activities/voip/CallActivity.kt | 331 ---- .../activities/voip/ConferenceDisplayMode.kt | 26 - .../linphone/activities/voip/data/CallData.kt | 315 ---- .../voip/data/CallStatisticsData.kt | 148 -- .../data/ConferenceInfoParticipantData.kt | 31 - .../voip/data/ConferenceParticipantData.kt | 87 -- .../data/ConferenceParticipantDeviceData.kt | 186 --- .../activities/voip/data/StatItemData.kt | 127 -- .../voip/fragments/CallsListFragment.kt | 189 --- .../activities/voip/fragments/ChatFragment.kt | 267 ---- .../ConferenceAddParticipantsFragment.kt | 134 -- .../voip/fragments/ConferenceCallFragment.kt | 592 ------- .../fragments/ConferenceLayoutFragment.kt | 97 -- .../ConferenceParticipantsFragment.kt | 99 -- .../fragments/GenericVideoPreviewFragment.kt | 78 - .../voip/fragments/IncomingCallFragment.kt | 95 -- .../voip/fragments/OutgoingCallFragment.kt | 100 -- .../voip/fragments/SingleCallFragment.kt | 265 ---- .../voip/fragments/StatusFragment.kt | 174 --- .../voip/viewmodels/CallsViewModel.kt | 330 ---- .../ConferenceParticipantsViewModel.kt | 81 - .../voip/viewmodels/ConferenceViewModel.kt | 772 --------- .../voip/viewmodels/ControlsViewModel.kt | 595 ------- .../viewmodels/StatisticsListViewModel.kt | 91 -- .../voip/viewmodels/StatusViewModel.kt | 175 --- .../activities/voip/views/GridBoxLayout.kt | 121 -- .../voip/views/RoundCornersTextureView.kt | 117 -- .../activities/voip/views/ScrollDotsView.kt | 261 ---- .../compatibility/Api23Compatibility.kt | 286 ---- .../compatibility/Api25Compatibility.kt | 59 - .../compatibility/Api26Compatibility.kt | 415 ----- .../compatibility/Api27Compatibility.kt | 42 - .../compatibility/Api28Compatibility.kt | 32 - .../compatibility/Api29Compatibility.kt | 268 ---- .../compatibility/Api30Compatibility.kt | 95 -- .../compatibility/Api31Compatibility.kt | 305 ---- .../compatibility/Api33Compatibility.kt | 66 - .../compatibility/Api34Compatibility.kt | 146 -- .../linphone/compatibility/Compatibility.kt | 503 ------ .../compatibility/PhoneStateInterface.kt | 26 - .../compatibility/PhoneStateListener.kt | 65 - .../compatibility/TelephonyListener.kt | 78 - .../compatibility/XiaomiCompatibility.kt | 128 -- .../contact/ContactAvatarGenerator.kt | 113 -- .../linphone/contact/ContactDataInterface.kt | 102 -- .../org/linphone/contact/ContactLoader.kt | 344 ---- .../linphone/contact/ContactSelectionData.kt | 105 -- .../org/linphone/contact/ContactsManager.kt | 465 ------ .../contact/ContactsSelectionAdapter.kt | 160 -- .../contact/ContactsSelectionViewModel.kt | 177 --- .../contact/DummyAuthenticationService.kt | 96 -- .../org/linphone/contact/DummySyncService.kt | 59 - .../linphone/contact/NativeContactEditor.kt | 594 ------- .../java/org/linphone/core/BootReceiver.kt | 60 - .../java/org/linphone/core/CoreContext.kt | 1319 ---------------- .../java/org/linphone/core/CorePreferences.kt | 769 --------- .../org/linphone/core/CorePushReceiver.kt | 33 - .../java/org/linphone/core/CoreService.kt | 126 -- .../NotificationBroadcastReceiver.kt | 150 -- .../notifications/NotificationsManager.kt | 1385 ----------------- .../org/linphone/telecom/NativeCallWrapper.kt | 178 --- .../telecom/TelecomConnectionService.kt | 299 ---- .../org/linphone/telecom/TelecomHelper.kt | 251 --- .../org/linphone/utils/ActivityMonitor.kt | 121 -- .../main/java/org/linphone/utils/AppUtils.kt | 255 --- .../org/linphone/utils/AudioRouteUtils.kt | 343 ---- .../java/org/linphone/utils/ContactUtils.kt | 71 - .../org/linphone/utils/DataBindingUtils.kt | 898 ----------- .../java/org/linphone/utils/DialogUtils.kt | 90 -- app/src/main/java/org/linphone/utils/Event.kt | 41 - .../main/java/org/linphone/utils/FileUtils.kt | 562 ------- .../java/org/linphone/utils/ImageUtils.kt | 110 -- .../java/org/linphone/utils/LinphoneUtils.kt | 346 ---- .../linphone/utils/PatternClickableSpan.kt | 73 - .../org/linphone/utils/PermissionHelper.kt | 82 - .../org/linphone/utils/PhoneNumberUtils.kt | 142 -- .../org/linphone/utils/PowerManagerUtils.kt | 163 -- .../utils/RecyclerViewHeaderDecoration.kt | 98 -- .../linphone/utils/RecyclerViewSwipeUtils.kt | 344 ---- .../org/linphone/utils/ShortcutsHelper.kt | 247 --- .../org/linphone/utils/SingletonHolder.kt | 62 - .../java/org/linphone/utils/TimestampUtils.kt | 174 --- .../org/linphone/views/MarqueeTextView.kt | 52 - .../views/SettingTextInputEditText.kt | 54 - .../linphone/views/VoiceRecordProgressBar.kt | 352 ----- .../res/color/dark_primary_text_color.xml | 5 - .../res/color/light_primary_text_color.xml | 5 - .../color/voip_extra_button_text_color.xml | 6 - .../topbar_call_notification.png | Bin 1517 -> 0 bytes .../topbar_call_paused_notification.png | Bin 712 -> 0 bytes .../topbar_chat_notification.png | Bin 1702 -> 0 bytes .../topbar_missed_call_notification.png | Bin 2165 -> 0 bytes .../topbar_service_notification.png | Bin 6278 -> 0 bytes .../topbar_videocall_notification.png | Bin 1050 -> 0 bytes .../topbar_call_notification.png | Bin 1103 -> 0 bytes .../topbar_call_paused_notification.png | Bin 644 -> 0 bytes .../topbar_chat_notification.png | Bin 1233 -> 0 bytes .../topbar_missed_call_notification.png | Bin 1400 -> 0 bytes .../topbar_service_notification.png | Bin 4597 -> 0 bytes .../topbar_videocall_notification.png | Bin 833 -> 0 bytes .../audio_recording_default.png | Bin 8867 -> 0 bytes .../audio_recording_reply_preview_default.png | Bin 924 -> 0 bytes .../main/res/drawable-xhdpi/back_default.png | Bin 1594 -> 0 bytes .../res/drawable-xhdpi/backspace_default.png | Bin 2335 -> 0 bytes app/src/main/res/drawable-xhdpi/call_add.png | Bin 3597 -> 0 bytes .../drawable-xhdpi/call_alt_start_default.png | Bin 4597 -> 0 bytes .../res/drawable-xhdpi/call_audio_start.png | Bin 3142 -> 0 bytes .../res/drawable-xhdpi/call_back_default.png | Bin 4666 -> 0 bytes .../main/res/drawable-xhdpi/call_hangup.png | Bin 2173 -> 0 bytes .../main/res/drawable-xhdpi/call_incoming.png | Bin 864 -> 0 bytes .../main/res/drawable-xhdpi/call_missed.png | Bin 698 -> 0 bytes .../main/res/drawable-xhdpi/call_outgoing.png | Bin 528 -> 0 bytes .../call_quality_indicator_0.png | Bin 1117 -> 0 bytes .../call_quality_indicator_1.png | Bin 1172 -> 0 bytes .../call_quality_indicator_2.png | Bin 9599 -> 0 bytes .../call_quality_indicator_3.png | Bin 1332 -> 0 bytes .../call_quality_indicator_4.png | Bin 845 -> 0 bytes .../res/drawable-xhdpi/call_start_default.png | Bin 2490 -> 0 bytes .../drawable-xhdpi/call_status_incoming.png | Bin 3244 -> 0 bytes .../res/drawable-xhdpi/call_status_missed.png | Bin 3201 -> 0 bytes .../drawable-xhdpi/call_status_outgoing.png | Bin 3200 -> 0 bytes .../main/res/drawable-xhdpi/call_transfer.png | Bin 3625 -> 0 bytes .../res/drawable-xhdpi/call_video_start.png | Bin 2885 -> 0 bytes .../drawable-xhdpi/camera_switch_default.png | Bin 10246 -> 0 bytes .../res/drawable-xhdpi/chat_delivered.png | Bin 1759 -> 0 bytes .../main/res/drawable-xhdpi/chat_error.png | Bin 1278 -> 0 bytes .../res/drawable-xhdpi/chat_file_over.png | Bin 2109 -> 0 bytes .../drawable-xhdpi/chat_group_add_default.png | Bin 1837 -> 0 bytes .../res/drawable-xhdpi/chat_group_delete.png | Bin 815 -> 0 bytes .../drawable-xhdpi/chat_group_new_default.png | Bin 16861 -> 0 bytes .../res/drawable-xhdpi/chat_new_default.png | Bin 14072 -> 0 bytes app/src/main/res/drawable-xhdpi/chat_read.png | Bin 1853 -> 0 bytes .../res/drawable-xhdpi/chat_send_over.png | Bin 9673 -> 0 bytes .../res/drawable-xhdpi/chat_start_default.png | Bin 2258 -> 0 bytes .../res/drawable-xhdpi/check_selected.png | Bin 1300 -> 0 bytes .../res/drawable-xhdpi/check_unselected.png | Bin 1320 -> 0 bytes .../chevron_list_close_default.png | Bin 499 -> 0 bytes .../chevron_list_open_default.png | Bin 509 -> 0 bytes .../res/drawable-xhdpi/collapse_default.png | Bin 4007 -> 0 bytes .../conference_schedule_calendar_default.png | Bin 182 -> 0 bytes ...nference_schedule_participants_default.png | Bin 660 -> 0 bytes .../conference_schedule_time_default.png | Bin 442 -> 0 bytes .../drawable-xhdpi/contact_add_default.png | Bin 2332 -> 0 bytes .../res/drawable-xhdpi/contact_default.png | Bin 2245 -> 0 bytes .../drawable-xhdpi/contacts_all_default.png | Bin 3807 -> 0 bytes .../drawable-xhdpi/contacts_sip_default.png | Bin 4261 -> 0 bytes .../res/drawable-xhdpi/delete_default.png | Bin 1777 -> 0 bytes .../drawable-xhdpi/deselect_all_default.png | Bin 858 -> 0 bytes .../res/drawable-xhdpi/dialer_background.png | Bin 67421 -> 0 bytes .../main/res/drawable-xhdpi/edit_default.png | Bin 2895 -> 0 bytes .../main/res/drawable-xhdpi/emoji_default.png | Bin 5203 -> 0 bytes .../ephemeral_messages_default.png | Bin 8497 -> 0 bytes .../res/drawable-xhdpi/file_audio_default.png | Bin 1675 -> 0 bytes .../main/res/drawable-xhdpi/file_default.png | Bin 1821 -> 0 bytes .../res/drawable-xhdpi/file_pdf_default.png | Bin 2300 -> 0 bytes .../drawable-xhdpi/file_picture_default.png | Bin 2072 -> 0 bytes .../res/drawable-xhdpi/file_video_default.png | Bin 11226 -> 0 bytes .../main/res/drawable-xhdpi/footer_chat.png | Bin 1994 -> 0 bytes .../res/drawable-xhdpi/footer_contacts.png | Bin 1959 -> 0 bytes .../main/res/drawable-xhdpi/footer_dialer.png | Bin 3246 -> 0 bytes .../res/drawable-xhdpi/footer_history.png | Bin 1918 -> 0 bytes .../forward_message_default.png | Bin 3732 -> 0 bytes .../forwarded_message_default.png | Bin 1081 -> 0 bytes .../res/drawable-xhdpi/fullscreen_default.png | Bin 2839 -> 0 bytes .../drawable-xhdpi/history_all_default.png | Bin 5529 -> 0 bytes .../drawable-xhdpi/history_missed_default.png | Bin 4486 -> 0 bytes app/src/main/res/drawable-xhdpi/info.png | Bin 2935 -> 0 bytes .../drawable-xhdpi/linphone_app_name_logo.png | Bin 8422 -> 0 bytes .../main/res/drawable-xhdpi/linphone_logo.png | Bin 15831 -> 0 bytes .../drawable-xhdpi/list_details_default.png | Bin 924 -> 0 bytes .../res/drawable-xhdpi/menu_about_default.png | Bin 3435 -> 0 bytes .../res/drawable-xhdpi/menu_add_contact.png | Bin 1708 -> 0 bytes .../drawable-xhdpi/menu_assistant_default.png | Bin 5273 -> 0 bytes .../main/res/drawable-xhdpi/menu_contact.png | Bin 1606 -> 0 bytes .../drawable-xhdpi/menu_copy_text_default.png | Bin 1370 -> 0 bytes .../main/res/drawable-xhdpi/menu_default.png | Bin 397 -> 0 bytes .../drawable-xhdpi/menu_delete_default.png | Bin 9869 -> 0 bytes .../menu_ephemeral_messages_default.png | Bin 11727 -> 0 bytes .../drawable-xhdpi/menu_forward_default.png | Bin 11917 -> 0 bytes .../menu_group_info_default.png | Bin 12813 -> 0 bytes .../drawable-xhdpi/menu_meeting_schedule.png | Bin 13214 -> 0 bytes .../drawable-xhdpi/menu_notifications_off.png | Bin 3511 -> 0 bytes .../drawable-xhdpi/menu_notifications_on.png | Bin 2758 -> 0 bytes .../drawable-xhdpi/menu_options_default.png | Bin 1397 -> 0 bytes .../menu_recordings_default.png | Bin 3142 -> 0 bytes .../res/drawable-xhdpi/menu_reply_default.png | Bin 12166 -> 0 bytes .../drawable-xhdpi/menu_security_default.png | Bin 14046 -> 0 bytes .../main/res/drawable-xhdpi/next_default.png | Bin 8512 -> 0 bytes app/src/main/res/drawable-xhdpi/numpad_0.png | Bin 3094 -> 0 bytes app/src/main/res/drawable-xhdpi/numpad_1.png | Bin 2960 -> 0 bytes app/src/main/res/drawable-xhdpi/numpad_2.png | Bin 2108 -> 0 bytes app/src/main/res/drawable-xhdpi/numpad_3.png | Bin 2450 -> 0 bytes app/src/main/res/drawable-xhdpi/numpad_4.png | Bin 1436 -> 0 bytes app/src/main/res/drawable-xhdpi/numpad_5.png | Bin 2253 -> 0 bytes app/src/main/res/drawable-xhdpi/numpad_6.png | Bin 3021 -> 0 bytes app/src/main/res/drawable-xhdpi/numpad_7.png | Bin 1694 -> 0 bytes app/src/main/res/drawable-xhdpi/numpad_8.png | Bin 3374 -> 0 bytes app/src/main/res/drawable-xhdpi/numpad_9.png | Bin 2954 -> 0 bytes .../main/res/drawable-xhdpi/numpad_hash.png | Bin 708 -> 0 bytes .../main/res/drawable-xhdpi/numpad_star.png | Bin 1583 -> 0 bytes .../main/res/drawable-xhdpi/quit_default.png | Bin 11802 -> 0 bytes .../record_audio_message_default.png | Bin 2579 -> 0 bytes .../drawable-xhdpi/record_pause_default.png | Bin 7268 -> 0 bytes .../drawable-xhdpi/record_play_default.png | Bin 8311 -> 0 bytes .../drawable-xhdpi/record_stop_default.png | Bin 915 -> 0 bytes .../replied_message_default.png | Bin 8230 -> 0 bytes .../resizable_assistant_button_default.9.png | Bin 538 -> 0 bytes .../resizable_assistant_button_disabled.9.png | Bin 419 -> 0 bytes .../resizable_assistant_button_over.9.png | Bin 506 -> 0 bytes .../drawable-xhdpi/resizable_text_field.9.png | Bin 223 -> 0 bytes .../scroll_to_bottom_default.png | Bin 15058 -> 0 bytes app/src/main/res/drawable-xhdpi/search.png | Bin 1941 -> 0 bytes .../drawable-xhdpi/security_1_indicator.png | Bin 2368 -> 0 bytes .../drawable-xhdpi/security_2_indicator.png | Bin 2388 -> 0 bytes .../security_alert_indicator.png | Bin 1299 -> 0 bytes .../main/res/drawable-xhdpi/security_ko.png | Bin 1009 -> 0 bytes .../main/res/drawable-xhdpi/security_ok.png | Bin 877 -> 0 bytes .../res/drawable-xhdpi/security_pending.png | Bin 882 -> 0 bytes .../drawable-xhdpi/security_post_quantum.png | Bin 27796 -> 0 bytes .../security_toggle_icon_green.png | Bin 1870 -> 0 bytes .../security_toggle_icon_grey.png | Bin 2420 -> 0 bytes .../res/drawable-xhdpi/select_all_default.png | Bin 1523 -> 0 bytes .../settings_advanced_default.png | Bin 2853 -> 0 bytes .../drawable-xhdpi/settings_audio_default.png | Bin 1750 -> 0 bytes .../drawable-xhdpi/settings_call_default.png | Bin 2378 -> 0 bytes .../drawable-xhdpi/settings_chat_default.png | Bin 2391 -> 0 bytes .../settings_conferences_default.png | Bin 12641 -> 0 bytes .../settings_contacts_default.png | Bin 2154 -> 0 bytes .../settings_network_default.png | Bin 2188 -> 0 bytes .../drawable-xhdpi/settings_video_default.png | Bin 1236 -> 0 bytes .../drawable-xhdpi/splashscreen_branding.png | Bin 11957 -> 0 bytes .../topbar_call_notification.png | Bin 1739 -> 0 bytes .../topbar_call_paused_notification.png | Bin 756 -> 0 bytes .../topbar_chat_notification.png | Bin 2032 -> 0 bytes .../topbar_missed_call_notification.png | Bin 2911 -> 0 bytes .../topbar_service_notification.png | Bin 5185 -> 0 bytes .../topbar_videocall_notification.png | Bin 1176 -> 0 bytes .../main/res/drawable-xhdpi/valid_default.png | Bin 1934 -> 0 bytes app/src/main/res/drawable-xhdpi/voicemail.png | Bin 1323 -> 0 bytes .../res/drawable-xhdpi/voip_audio_routes.png | Bin 3729 -> 0 bytes .../res/drawable-xhdpi/voip_bluetooth.png | Bin 2557 -> 0 bytes app/src/main/res/drawable-xhdpi/voip_call.png | Bin 3830 -> 0 bytes .../main/res/drawable-xhdpi/voip_call_add.png | Bin 14157 -> 0 bytes .../res/drawable-xhdpi/voip_call_chat.png | Bin 13663 -> 0 bytes .../res/drawable-xhdpi/voip_call_forward.png | Bin 14584 -> 0 bytes .../voip_call_header_active.png | Bin 1787 -> 0 bytes .../voip_call_header_incoming.png | Bin 1298 -> 0 bytes .../voip_call_header_outgoing.png | Bin 1274 -> 0 bytes .../voip_call_header_paused.png | Bin 1119 -> 0 bytes .../drawable-xhdpi/voip_call_list_menu.png | Bin 1895 -> 0 bytes .../res/drawable-xhdpi/voip_call_more.png | Bin 10488 -> 0 bytes .../res/drawable-xhdpi/voip_call_numpad.png | Bin 19610 -> 0 bytes .../drawable-xhdpi/voip_call_participants.png | Bin 15075 -> 0 bytes .../res/drawable-xhdpi/voip_call_record.png | Bin 3890 -> 0 bytes .../res/drawable-xhdpi/voip_call_stats.png | Bin 9380 -> 0 bytes .../res/drawable-xhdpi/voip_calls_list.png | Bin 14703 -> 0 bytes .../res/drawable-xhdpi/voip_camera_off.png | Bin 2971 -> 0 bytes .../res/drawable-xhdpi/voip_camera_on.png | Bin 1748 -> 0 bytes .../main/res/drawable-xhdpi/voip_cancel.png | Bin 1609 -> 0 bytes .../res/drawable-xhdpi/voip_change_camera.png | Bin 1831 -> 0 bytes .../drawable-xhdpi/voip_chat_rooms_list.png | Bin 4105 -> 0 bytes .../res/drawable-xhdpi/voip_conference.png | Bin 9289 -> 0 bytes .../voip_conference_active_speaker.png | Bin 7818 -> 0 bytes .../voip_conference_audio_only.png | Bin 1260 -> 0 bytes .../drawable-xhdpi/voip_conference_mosaic.png | Bin 8850 -> 0 bytes .../voip_conference_paused_big.png | Bin 2250 -> 0 bytes .../voip_conference_play_big.png | Bin 15790 -> 0 bytes app/src/main/res/drawable-xhdpi/voip_copy.png | Bin 1930 -> 0 bytes .../main/res/drawable-xhdpi/voip_delete.png | Bin 2223 -> 0 bytes .../main/res/drawable-xhdpi/voip_dropdown.png | Bin 1256 -> 0 bytes .../main/res/drawable-xhdpi/voip_earpiece.png | Bin 3857 -> 0 bytes app/src/main/res/drawable-xhdpi/voip_edit.png | Bin 1834 -> 0 bytes .../main/res/drawable-xhdpi/voip_export.png | Bin 2383 -> 0 bytes .../main/res/drawable-xhdpi/voip_hangup.png | Bin 3166 -> 0 bytes app/src/main/res/drawable-xhdpi/voip_info.png | Bin 13635 -> 0 bytes .../res/drawable-xhdpi/voip_mandatory.png | Bin 7410 -> 0 bytes .../drawable-xhdpi/voip_meeting_schedule.png | Bin 3841 -> 0 bytes .../res/drawable-xhdpi/voip_menu_more.png | Bin 1540 -> 0 bytes .../res/drawable-xhdpi/voip_merge_calls.png | Bin 4349 -> 0 bytes .../res/drawable-xhdpi/voip_micro_off.png | Bin 4208 -> 0 bytes .../main/res/drawable-xhdpi/voip_micro_on.png | Bin 3114 -> 0 bytes .../voip_multiple_contacts_avatar.png | Bin 3035 -> 0 bytes .../voip_multiple_contacts_avatar_alt.png | Bin 6666 -> 0 bytes .../main/res/drawable-xhdpi/voip_numpad_0.png | Bin 14607 -> 0 bytes .../main/res/drawable-xhdpi/voip_numpad_1.png | Bin 10598 -> 0 bytes .../main/res/drawable-xhdpi/voip_numpad_2.png | Bin 8603 -> 0 bytes .../main/res/drawable-xhdpi/voip_numpad_3.png | Bin 8978 -> 0 bytes .../main/res/drawable-xhdpi/voip_numpad_4.png | Bin 7692 -> 0 bytes .../main/res/drawable-xhdpi/voip_numpad_5.png | Bin 8747 -> 0 bytes .../main/res/drawable-xhdpi/voip_numpad_6.png | Bin 10150 -> 0 bytes .../main/res/drawable-xhdpi/voip_numpad_7.png | Bin 7606 -> 0 bytes .../main/res/drawable-xhdpi/voip_numpad_8.png | Bin 10857 -> 0 bytes .../main/res/drawable-xhdpi/voip_numpad_9.png | Bin 10035 -> 0 bytes .../res/drawable-xhdpi/voip_numpad_hash.png | Bin 12417 -> 0 bytes .../res/drawable-xhdpi/voip_numpad_star.png | Bin 10163 -> 0 bytes .../main/res/drawable-xhdpi/voip_pause.png | Bin 830 -> 0 bytes .../drawable-xhdpi/voip_remote_recording.png | Bin 2673 -> 0 bytes .../res/drawable-xhdpi/voip_screenshot.png | Bin 1757 -> 0 bytes .../voip_single_contact_avatar.png | Bin 1602 -> 0 bytes .../voip_single_contact_avatar_alt.png | Bin 4848 -> 0 bytes .../res/drawable-xhdpi/voip_speaker_off.png | Bin 3780 -> 0 bytes .../res/drawable-xhdpi/voip_speaker_on.png | Bin 2599 -> 0 bytes .../main/res/drawable-xhdpi/voip_spinner.png | Bin 2499 -> 0 bytes .../topbar_call_notification.png | Bin 2418 -> 0 bytes .../topbar_call_paused_notification.png | Bin 896 -> 0 bytes .../topbar_chat_notification.png | Bin 2818 -> 0 bytes .../topbar_missed_call_notification.png | Bin 4609 -> 0 bytes .../topbar_service_notification.png | Bin 7767 -> 0 bytes .../topbar_videocall_notification.png | Bin 1449 -> 0 bytes .../topbar_call_notification.png | Bin 2482 -> 0 bytes .../topbar_call_paused_notification.png | Bin 998 -> 0 bytes .../topbar_chat_notification.png | Bin 2835 -> 0 bytes .../topbar_missed_call_notification.png | Bin 4079 -> 0 bytes .../topbar_service_notification.png | Bin 8636 -> 0 bytes .../topbar_videocall_notification.png | Bin 1517 -> 0 bytes .../main/res/drawable/assistant_button.xml | 10 - .../drawable/assistant_button_text_color.xml | 7 - app/src/main/res/drawable/avatar_border.xml | 5 - app/src/main/res/drawable/back.xml | 15 - .../background_round_primary_color.xml | 5 - .../background_round_secondary_color.xml | 5 - app/src/main/res/drawable/backspace.xml | 15 - .../main/res/drawable/button_background.xml | 9 - .../res/drawable/button_background_dark.xml | 11 - .../res/drawable/button_background_light.xml | 11 - .../drawable/button_background_reverse.xml | 9 - .../button_call_answer_background.xml | 7 - .../button_call_context_menu_background.xml | 7 - .../button_call_numpad_background.xml | 7 - .../button_call_recording_background.xml | 9 - .../button_call_terminate_background.xml | 7 - .../res/drawable/button_conference_info.xml | 11 - .../res/drawable/button_green_background.xml | 9 - .../res/drawable/button_orange_background.xml | 9 - .../res/drawable/button_round_background.xml | 9 - .../res/drawable/button_toggle_background.xml | 11 - .../button_toggle_background_reverse.xml | 11 - app/src/main/res/drawable/call.xml | 7 - app/src/main/res/drawable/call_alt_start.xml | 15 - app/src/main/res/drawable/call_back.xml | 7 - app/src/main/res/drawable/camera_switch.xml | 20 - app/src/main/res/drawable/cancel.xml | 9 - app/src/main/res/drawable/cancel_shape.xml | 35 - .../drawable/chat_bubble_incoming_full.xml | 5 - .../chat_bubble_incoming_reactions.xml | 6 - .../drawable/chat_bubble_incoming_split_1.xml | 5 - .../drawable/chat_bubble_incoming_split_2.xml | 5 - .../drawable/chat_bubble_incoming_split_3.xml | 5 - .../drawable/chat_bubble_outgoing_full.xml | 5 - .../chat_bubble_outgoing_reactions.xml | 6 - .../drawable/chat_bubble_outgoing_split_1.xml | 5 - .../drawable/chat_bubble_outgoing_split_2.xml | 5 - .../drawable/chat_bubble_outgoing_split_3.xml | 5 - .../drawable/chat_bubble_reply_background.xml | 6 - .../chat_bubble_reply_file_background.xml | 7 - .../chat_bubble_reply_incoming_indicator.xml | 5 - .../chat_bubble_reply_outgoing_indicator.xml | 5 - app/src/main/res/drawable/chat_file.xml | 15 - app/src/main/res/drawable/chat_group_add.xml | 8 - app/src/main/res/drawable/chat_group_new.xml | 15 - ..._message_audio_record_preview_progress.xml | 25 - .../chat_message_audio_record_progress.xml | 26 - ...hat_message_voice_recording_background.xml | 6 - app/src/main/res/drawable/chat_new.xml | 15 - .../drawable/chat_room_menu_add_contact.xml | 7 - .../res/drawable/chat_room_menu_delete.xml | 7 - .../res/drawable/chat_room_menu_devices.xml | 11 - .../res/drawable/chat_room_menu_ephemeral.xml | 11 - .../drawable/chat_room_menu_group_info.xml | 7 - .../res/drawable/chat_room_menu_meeting.xml | 11 - .../chat_room_menu_mute_notifications.xml | 7 - .../chat_room_menu_unmute_notifications.xml | 7 - .../drawable/chat_room_menu_view_contact.xml | 7 - .../main/res/drawable/chat_send_message.xml | 16 - .../main/res/drawable/chevron_list_close.xml | 15 - .../main/res/drawable/chevron_list_open.xml | 15 - app/src/main/res/drawable/contact.xml | 15 - app/src/main/res/drawable/contact_add.xml | 15 - app/src/main/res/drawable/contacts_all.xml | 15 - app/src/main/res/drawable/contacts_sip.xml | 15 - app/src/main/res/drawable/delete.xml | 15 - app/src/main/res/drawable/deselect_all.xml | 15 - .../main/res/drawable/dialog_delete_icon.xml | 7 - app/src/main/res/drawable/divider.xml | 7 - app/src/main/res/drawable/edit.xml | 19 - app/src/main/res/drawable/emoji.xml | 19 - .../main/res/drawable/ephemeral_messages.xml | 7 - .../res/drawable/event_decoration_gray.xml | 5 - .../res/drawable/event_decoration_red.xml | 7 - app/src/main/res/drawable/field_add.xml | 10 - .../main/res/drawable/field_add_default.xml | 10 - .../main/res/drawable/field_add_disabled.xml | 10 - app/src/main/res/drawable/field_add_over.xml | 10 - app/src/main/res/drawable/field_add_shape.xml | 26 - app/src/main/res/drawable/field_clean.xml | 8 - .../main/res/drawable/field_clean_default.xml | 10 - .../main/res/drawable/field_clean_over.xml | 10 - .../main/res/drawable/field_clean_shape.xml | 31 - app/src/main/res/drawable/field_remove.xml | 10 - .../res/drawable/field_remove_default.xml | 10 - .../res/drawable/field_remove_disabled.xml | 10 - .../main/res/drawable/field_remove_over.xml | 10 - .../main/res/drawable/field_remove_shape.xml | 12 - app/src/main/res/drawable/file.xml | 11 - app/src/main/res/drawable/file_audio.xml | 11 - app/src/main/res/drawable/file_pdf.xml | 11 - app/src/main/res/drawable/file_picture.xml | 11 - app/src/main/res/drawable/file_video.xml | 11 - app/src/main/res/drawable/footer_button.xml | 9 - .../main/res/drawable/generated_avatar_bg.xml | 5 - .../hidden_unread_message_count_bg.xml | 5 - app/src/main/res/drawable/history_all.xml | 15 - .../drawable/history_detail_background.xml | 5 - .../history_detail_background_pressed.xml | 4 - app/src/main/res/drawable/history_missed.xml | 15 - app/src/main/res/drawable/icon_apply.xml | 15 - .../main/res/drawable/icon_audio_routes.xml | 7 - app/src/main/res/drawable/icon_bluetooth.xml | 7 - app/src/main/res/drawable/icon_call_add.xml | 15 - .../main/res/drawable/icon_call_answer.xml | 7 - .../res/drawable/icon_call_answer_video.xml | 7 - .../res/drawable/icon_call_camera_switch.xml | 7 - app/src/main/res/drawable/icon_call_chat.xml | 15 - .../res/drawable/icon_call_chat_rooms.xml | 7 - .../main/res/drawable/icon_call_forward.xml | 15 - .../main/res/drawable/icon_call_hangup.xml | 7 - app/src/main/res/drawable/icon_call_more.xml | 7 - .../main/res/drawable/icon_call_numpad.xml | 15 - .../res/drawable/icon_call_participants.xml | 15 - .../main/res/drawable/icon_call_record.xml | 7 - .../res/drawable/icon_call_screenshot.xml | 7 - app/src/main/res/drawable/icon_call_stats.xml | 15 - app/src/main/res/drawable/icon_calls_list.xml | 15 - .../res/drawable/icon_calls_list_menu.xml | 15 - app/src/main/res/drawable/icon_cancel.xml | 7 - app/src/main/res/drawable/icon_cancel_alt.xml | 7 - .../icon_conference_layout_active_speaker.xml | 15 - .../icon_conference_layout_audio_only.xml | 15 - .../drawable/icon_conference_layout_grid.xml | 15 - app/src/main/res/drawable/icon_copy.xml | 7 - app/src/main/res/drawable/icon_delete.xml | 7 - app/src/main/res/drawable/icon_earpiece.xml | 7 - app/src/main/res/drawable/icon_edit.xml | 7 - app/src/main/res/drawable/icon_edit_alt.xml | 7 - .../res/drawable/icon_ephemeral_message.xml | 7 - .../res/drawable/icon_forwarded_message.xml | 7 - app/src/main/res/drawable/icon_info.xml | 7 - .../main/res/drawable/icon_info_selected.xml | 7 - .../res/drawable/icon_meeting_schedule.xml | 7 - app/src/main/res/drawable/icon_menu_more.xml | 15 - .../icon_merge_calls_local_conference.xml | 7 - app/src/main/res/drawable/icon_mic_muted.xml | 7 - .../icon_multiple_contacts_avatar.xml | 7 - app/src/main/res/drawable/icon_next.xml | 15 - app/src/main/res/drawable/icon_pause.xml | 7 - .../res/drawable/icon_replied_message.xml | 7 - .../main/res/drawable/icon_schedule_date.xml | 7 - .../drawable/icon_schedule_participants.xml | 7 - .../main/res/drawable/icon_schedule_time.xml | 7 - app/src/main/res/drawable/icon_share.xml | 9 - .../drawable/icon_single_contact_avatar.xml | 7 - app/src/main/res/drawable/icon_speaker.xml | 7 - app/src/main/res/drawable/icon_spinner.xml | 7 - .../res/drawable/icon_spinner_background.xml | 24 - .../res/drawable/icon_spinner_rotating.xml | 6 - .../main/res/drawable/icon_toggle_camera.xml | 11 - app/src/main/res/drawable/icon_toggle_mic.xml | 11 - .../main/res/drawable/icon_toggle_speaker.xml | 11 - .../icon_video_conf_history_filter.xml | 15 - .../res/drawable/icon_video_conf_incoming.xml | 7 - .../main/res/drawable/icon_video_conf_new.xml | 15 - app/src/main/res/drawable/led_away.xml | 5 - app/src/main/res/drawable/led_background.xml | 5 - .../main/res/drawable/led_do_not_disturb.xml | 5 - app/src/main/res/drawable/led_error.xml | 5 - .../main/res/drawable/led_not_registered.xml | 5 - app/src/main/res/drawable/led_online.xml | 5 - app/src/main/res/drawable/led_registered.xml | 5 - .../drawable/led_registration_in_progress.xml | 5 - .../res/drawable/linphone_logo_tinted.xml | 7 - app/src/main/res/drawable/list_detail.xml | 7 - app/src/main/res/drawable/menu.xml | 11 - app/src/main/res/drawable/menu_about.xml | 15 - app/src/main/res/drawable/menu_assistant.xml | 15 - app/src/main/res/drawable/menu_background.xml | 7 - .../res/drawable/menu_background_color.xml | 7 - app/src/main/res/drawable/menu_copy_text.xml | 7 - app/src/main/res/drawable/menu_delete.xml | 7 - app/src/main/res/drawable/menu_forward.xml | 7 - app/src/main/res/drawable/menu_imdn_info.xml | 7 - app/src/main/res/drawable/menu_options.xml | 15 - .../menu_pressed_background_color.xml | 7 - app/src/main/res/drawable/menu_recordings.xml | 15 - app/src/main/res/drawable/menu_reply.xml | 7 - app/src/main/res/drawable/numpad_eight.xml | 12 - app/src/main/res/drawable/numpad_five.xml | 11 - app/src/main/res/drawable/numpad_four.xml | 11 - app/src/main/res/drawable/numpad_nine.xml | 11 - app/src/main/res/drawable/numpad_one.xml | 11 - app/src/main/res/drawable/numpad_seven.xml | 11 - app/src/main/res/drawable/numpad_sharp.xml | 11 - app/src/main/res/drawable/numpad_six.xml | 11 - .../main/res/drawable/numpad_star_digit.xml | 11 - app/src/main/res/drawable/numpad_three.xml | 11 - app/src/main/res/drawable/numpad_two.xml | 11 - app/src/main/res/drawable/numpad_zero.xml | 11 - .../res/drawable/record_audio_message.xml | 20 - .../main/res/drawable/record_pause_dark.xml | 8 - .../main/res/drawable/record_pause_light.xml | 8 - .../main/res/drawable/record_play_dark.xml | 8 - .../main/res/drawable/record_play_light.xml | 8 - .../main/res/drawable/record_stop_light.xml | 8 - .../res/drawable/recording_play_pause.xml | 11 - .../main/res/drawable/rect_orange_button.xml | 9 - .../drawable/resizable_assistant_button.xml | 9 - .../res/drawable/round_button_background.xml | 5 - .../round_button_background_default.xml | 5 - .../round_orange_button_background.xml | 9 - ...round_orange_button_background_default.xml | 5 - ...ound_orange_button_background_disabled.xml | 5 - .../round_orange_button_background_over.xml | 5 - ...round_recording_button_background_dark.xml | 5 - ...ound_recording_button_background_light.xml | 5 - .../main/res/drawable/scroll_to_bottom.xml | 20 - app/src/main/res/drawable/select_all.xml | 15 - .../main/res/drawable/settings_advanced.xml | 13 - app/src/main/res/drawable/settings_audio.xml | 13 - app/src/main/res/drawable/settings_call.xml | 13 - app/src/main/res/drawable/settings_chat.xml | 13 - .../res/drawable/settings_conferences.xml | 12 - .../main/res/drawable/settings_contacts.xml | 13 - .../main/res/drawable/settings_network.xml | 13 - app/src/main/res/drawable/settings_video.xml | 12 - .../shape_audio_only_me_background.xml | 5 - .../shape_audio_only_remote_background.xml | 5 - .../shape_audio_routes_background.xml | 5 - .../res/drawable/shape_button_background.xml | 5 - .../shape_button_disabled_background.xml | 5 - .../shape_button_pressed_background.xml | 5 - .../drawable/shape_call_answer_background.xml | 6 - .../shape_call_answer_pressed_background.xml | 6 - .../drawable/shape_call_numpad_background.xml | 5 - .../shape_call_numpad_pressed_background.xml | 5 - .../drawable/shape_call_popup_background.xml | 5 - .../shape_call_recording_off_background.xml | 5 - .../shape_call_recording_on_background.xml | 5 - .../shape_call_terminate_background.xml | 6 - ...hape_call_terminate_pressed_background.xml | 6 - ...shape_conference_active_speaker_border.xml | 5 - .../shape_conference_audio_only_border.xml | 5 - .../shape_conference_invite_background.xml | 5 - .../res/drawable/shape_dialog_background.xml | 5 - .../main/res/drawable/shape_dialog_handle.xml | 6 - .../drawable/shape_edittext_background.xml | 6 - .../shape_extra_buttons_background.xml | 5 - .../drawable/shape_form_field_background.xml | 5 - .../main/res/drawable/shape_me_background.xml | 5 - .../main/res/drawable/shape_orange_circle.xml | 6 - .../shape_own_reaction_background.xml | 5 - .../res/drawable/shape_rect_gray_button.xml | 6 - .../res/drawable/shape_rect_green_button.xml | 6 - .../drawable/shape_rect_light_gray_button.xml | 6 - .../res/drawable/shape_rect_orange_button.xml | 6 - .../shape_rect_orange_disabled_button.xml | 6 - .../res/drawable/shape_remote_background.xml | 5 - .../shape_remote_paused_background.xml | 5 - .../shape_remote_recording_background.xml | 5 - .../shape_remote_video_background.xml | 5 - .../main/res/drawable/shape_round_button.xml | 5 - .../drawable/shape_round_button_disabled.xml | 5 - .../drawable/shape_round_button_pressed.xml | 5 - .../shape_round_dark_gray_background.xml | 5 - ...ark_gray_background_with_orange_border.xml | 6 - .../drawable/shape_round_gray_background.xml | 5 - ...und_gray_background_with_orange_border.xml | 6 - .../drawable/shape_round_red_background.xml | 5 - ...ound_red_background_with_orange_border.xml | 6 - .../shape_toggle_disabled_background.xml | 5 - .../drawable/shape_toggle_off_background.xml | 5 - .../drawable/shape_toggle_on_background.xml | 5 - .../shape_toggle_pressed_background.xml | 5 - .../shape_transparent_reaction_background.xml | 4 - .../res/drawable/status_led_background.xml | 5 - .../res/drawable/unread_message_count_bg.xml | 5 - app/src/main/res/drawable/valid.xml | 15 - .../res/drawable/vector_linphone_logo.xml | 20 - .../main/res/layout-land/about_fragment.xml | 191 --- .../assistant_welcome_fragment.xml | 166 -- .../conference_waiting_room_fragment.xml | 312 ---- .../main/res/layout-land/dialer_fragment.xml | 141 -- .../res/layout-land/main_activity_content.xml | 40 - .../main/res/layout-land/tabs_fragment.xml | 152 -- .../voip_conference_active_speaker.xml | 363 ----- .../res/layout-land/voip_conference_grid.xml | 139 -- app/src/main/res/layout-land/voip_numpad.xml | 225 --- .../layout-sw533dp-land/dialer_fragment.xml | 182 --- .../res/layout-sw600dp/dialer_fragment.xml | 182 --- app/src/main/res/layout/about_fragment.xml | 197 --- .../assistant_account_login_fragment.xml | 282 ---- .../main/res/layout/assistant_activity.xml | 24 - .../layout/assistant_country_picker_cell.xml | 22 - .../assistant_country_picker_fragment.xml | 89 -- ...nt_echo_canceller_calibration_fragment.xml | 57 - ...istant_email_account_creation_fragment.xml | 169 -- ...tant_email_account_validation_fragment.xml | 88 -- ...sistant_generic_account_login_fragment.xml | 189 --- ...stant_generic_account_warning_fragment.xml | 65 - .../assistant_no_push_warning_fragment.xml | 58 - ...istant_phone_account_creation_fragment.xml | 262 ---- ...sistant_phone_account_linking_fragment.xml | 194 --- ...tant_phone_account_validation_fragment.xml | 126 -- .../res/layout/assistant_qr_code_fragment.xml | 55 - ...assistant_remote_provisioning_fragment.xml | 104 -- .../res/layout/assistant_top_bar_fragment.xml | 45 - .../res/layout/assistant_welcome_fragment.xml | 163 -- .../call_incoming_notification_heads_up.xml | 46 - app/src/main/res/layout/call_overlay.xml | 27 - .../main/res/layout/chat_bubble_activity.xml | 183 --- .../main/res/layout/chat_event_list_cell.xml | 66 - .../layout/chat_message_attachment_cell.xml | 94 -- ...age_conference_invitation_content_cell.xml | 150 -- .../res/layout/chat_message_content_cell.xml | 110 -- ...message_downloadable_file_content_cell.xml | 57 - ...chat_message_generic_file_content_cell.xml | 47 - .../chat_message_image_content_cell.xml | 42 - .../res/layout/chat_message_list_cell.xml | 247 --- .../layout/chat_message_long_press_menu.xml | 227 --- .../main/res/layout/chat_message_reaction.xml | 20 - .../chat_message_reactions_list_cell.xml | 53 - .../chat_message_reactions_list_dialog.xml | 60 - .../main/res/layout/chat_message_reply.xml | 91 -- .../res/layout/chat_message_reply_bubble.xml | 81 - .../chat_message_reply_content_cell.xml | 86 - ...hat_message_reply_preview_content_cell.xml | 85 - .../chat_message_video_content_cell.xml | 54 - ...chat_message_voice_record_content_cell.xml | 67 - .../layout/chat_message_voice_recording.xml | 124 -- .../layout/chat_room_creation_fragment.xml | 229 --- .../res/layout/chat_room_detail_fragment.xml | 235 --- .../layout/chat_room_devices_child_cell.xml | 47 - .../res/layout/chat_room_devices_fragment.xml | 62 - .../layout/chat_room_devices_group_cell.xml | 137 -- .../chat_room_ephemeral_duration_cell.xml | 48 - .../layout/chat_room_ephemeral_fragment.xml | 108 -- .../layout/chat_room_group_info_fragment.xml | 163 -- .../chat_room_group_info_participant_cell.xml | 161 -- .../res/layout/chat_room_imdn_fragment.xml | 71 - .../chat_room_imdn_participant_cell.xml | 81 - .../main/res/layout/chat_room_list_cell.xml | 220 --- .../res/layout/chat_room_master_fragment.xml | 166 -- app/src/main/res/layout/chat_room_menu.xml | 169 -- app/src/main/res/layout/chat_room_sending.xml | 271 ---- .../chat_unread_messages_list_header.xml | 34 - .../res/layout/conference_schedule_cell.xml | 384 ----- .../conference_schedule_list_header.xml | 26 - .../layout/conference_scheduling_fragment.xml | 396 ----- ...conference_scheduling_participant_cell.xml | 100 -- ..._scheduling_participants_list_fragment.xml | 134 -- ...conference_scheduling_summary_fragment.xml | 388 ----- .../conference_waiting_room_fragment.xml | 317 ---- .../layout/conferences_scheduled_fragment.xml | 127 -- .../main/res/layout/contact_detail_cell.xml | 145 -- .../res/layout/contact_detail_fragment.xml | 148 -- .../res/layout/contact_editor_fragment.xml | 213 --- app/src/main/res/layout/contact_list_cell.xml | 120 -- .../res/layout/contact_master_fragment.xml | 214 --- .../contact_number_address_editor_cell.xml | 48 - .../main/res/layout/contact_selected_cell.xml | 42 - .../res/layout/contact_selection_cell.xml | 133 -- .../contact_sync_account_picker_cell.xml | 23 - .../contact_sync_account_picker_fragment.xml | 53 - app/src/main/res/layout/dialer_fragment.xml | 140 -- app/src/main/res/layout/dialog.xml | 160 -- app/src/main/res/layout/empty_fragment.xml | 7 - .../res/layout/file_audio_viewer_fragment.xml | 33 - .../layout/file_config_viewer_fragment.xml | 71 - .../res/layout/file_image_viewer_fragment.xml | 44 - .../main/res/layout/file_pdf_viewer_cell.xml | 12 - .../res/layout/file_pdf_viewer_fragment.xml | 39 - .../res/layout/file_text_viewer_fragment.xml | 47 - .../res/layout/file_video_viewer_fragment.xml | 42 - .../layout/file_viewer_top_bar_fragment.xml | 50 - .../main/res/layout/generic_list_header.xml | 30 - .../layout/history_conf_detail_fragment.xml | 199 --- .../main/res/layout/history_detail_cell.xml | 35 - .../res/layout/history_detail_fragment.xml | 215 --- app/src/main/res/layout/history_list_cell.xml | 137 -- .../res/layout/history_master_fragment.xml | 186 --- app/src/main/res/layout/imdn_list_header.xml | 53 - .../res/layout/list_edit_top_bar_fragment.xml | 80 - app/src/main/res/layout/main_activity.xml | 74 - .../main/res/layout/main_activity_content.xml | 40 - app/src/main/res/layout/numpad.xml | 101 -- .../main/res/layout/recording_list_cell.xml | 136 -- .../main/res/layout/recordings_fragment.xml | 99 -- .../main/res/layout/settings_account_cell.xml | 64 - .../res/layout/settings_account_fragment.xml | 274 ---- .../res/layout/settings_advanced_fragment.xml | 206 --- .../res/layout/settings_audio_fragment.xml | 161 -- .../res/layout/settings_call_fragment.xml | 227 --- .../res/layout/settings_chat_fragment.xml | 161 -- .../layout/settings_conferences_fragment.xml | 88 -- .../res/layout/settings_contacts_fragment.xml | 147 -- app/src/main/res/layout/settings_fragment.xml | 211 --- .../main/res/layout/settings_ldap_cell.xml | 37 - .../res/layout/settings_ldap_fragment.xml | 234 --- .../res/layout/settings_network_fragment.xml | 101 -- .../res/layout/settings_tunnel_fragment.xml | 122 -- .../res/layout/settings_video_fragment.xml | 163 -- .../main/res/layout/settings_widget_basic.xml | 89 -- .../main/res/layout/settings_widget_list.xml | 89 -- .../res/layout/settings_widget_switch.xml | 88 -- .../settings_widget_switch_and_text.xml | 126 -- .../main/res/layout/settings_widget_text.xml | 80 - .../res/layout/side_menu_account_cell.xml | 48 - .../main/res/layout/side_menu_fragment.xml | 345 ---- app/src/main/res/layout/status_fragment.xml | 84 - app/src/main/res/layout/tabs_fragment.xml | 152 -- app/src/main/res/layout/video_play_button.xml | 9 - app/src/main/res/layout/voip_activity.xml | 40 - app/src/main/res/layout/voip_buttons.xml | 148 -- .../res/layout/voip_buttons_audio_routes.xml | 59 - .../main/res/layout/voip_buttons_extra.xml | 203 --- .../main/res/layout/voip_buttons_incoming.xml | 51 - .../main/res/layout/voip_buttons_outgoing.xml | 85 - .../res/layout/voip_call_context_menu.xml | 91 -- .../layout/voip_call_incoming_fragment.xml | 249 --- .../layout/voip_call_outgoing_fragment.xml | 170 -- app/src/main/res/layout/voip_call_paused.xml | 72 - .../res/layout/voip_call_paused_by_remote.xml | 56 - .../main/res/layout/voip_call_stat_cell.xml | 36 - app/src/main/res/layout/voip_call_stats.xml | 74 - .../main/res/layout/voip_call_stats_cell.xml | 79 - app/src/main/res/layout/voip_calls_cell.xml | 145 -- .../res/layout/voip_calls_list_fragment.xml | 144 -- .../main/res/layout/voip_chat_fragment.xml | 202 --- .../layout/voip_conference_active_speaker.xml | 358 ----- .../res/layout/voip_conference_audio_only.xml | 147 -- .../layout/voip_conference_call_fragment.xml | 174 --- ...onference_creation_pending_wait_layout.xml | 39 - .../main/res/layout/voip_conference_grid.xml | 139 -- ...p_conference_incoming_participant_cell.xml | 36 - .../voip_conference_layout_fragment.xml | 167 -- ..._conference_participant_broadcast_cell.xml | 100 -- .../voip_conference_participant_cell.xml | 136 -- ...cipant_remote_active_speaker_miniature.xml | 111 -- ...nference_participant_remote_audio_only.xml | 127 -- ...oip_conference_participant_remote_grid.xml | 134 -- ...p_conference_participants_add_fragment.xml | 117 -- .../voip_conference_participants_fragment.xml | 121 -- .../res/layout/voip_conference_paused.xml | 71 - app/src/main/res/layout/voip_dialog.xml | 227 --- app/src/main/res/layout/voip_dialog_info.xml | 70 - app/src/main/res/layout/voip_numpad.xml | 227 --- .../main/res/layout/voip_remote_recording.xml | 55 - .../res/layout/voip_single_call_fragment.xml | 355 ----- .../main/res/layout/voip_status_fragment.xml | 71 - app/src/main/res/layout/wait_layout.xml | 40 - .../res/mipmap-anydpi-v26/ic_launcher.xml | 6 - .../mipmap-anydpi-v26/ic_launcher_round.xml | 6 - app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 2132 -> 0 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 2343 -> 0 bytes .../linphone_launcher_icon_foreground.png | Bin 6534 -> 0 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 1417 -> 0 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 1467 -> 0 bytes .../linphone_launcher_icon_foreground.png | Bin 6048 -> 0 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 2880 -> 0 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 3178 -> 0 bytes .../linphone_launcher_icon_foreground.png | Bin 7517 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 4524 -> 0 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 5416 -> 0 bytes .../linphone_launcher_icon_foreground.png | Bin 9499 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 6221 -> 0 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 8083 -> 0 bytes .../linphone_launcher_icon_foreground.png | Bin 11701 -> 0 bytes .../res/navigation/assistant_nav_graph.xml | 173 -- .../main/res/navigation/call_nav_graph.xml | 118 -- .../main/res/navigation/chat_nav_graph.xml | 96 -- .../res/navigation/contacts_nav_graph.xml | 53 - .../main/res/navigation/history_nav_graph.xml | 36 - .../main/res/navigation/main_nav_graph.xml | 224 --- .../res/navigation/settings_nav_graph.xml | 154 -- app/src/main/res/values-bg/strings.xml | 814 ---------- app/src/main/res/values-bn-rBD/strings.xml | 16 - app/src/main/res/values-cs/strings.xml | 849 ---------- app/src/main/res/values-de/strings.xml | 298 ---- app/src/main/res/values-el/strings.xml | 2 - app/src/main/res/values-es/strings.xml | 581 ------- app/src/main/res/values-fi/strings.xml | 429 ----- app/src/main/res/values-fr-rCA/strings.xml | 2 - app/src/main/res/values-fr/strings.xml | 823 ---------- app/src/main/res/values-he/strings.xml | 303 ---- app/src/main/res/values-hu/strings.xml | 610 -------- app/src/main/res/values-it/strings.xml | 825 ---------- app/src/main/res/values-ja/strings.xml | 816 ---------- app/src/main/res/values-ka/strings.xml | 787 ---------- app/src/main/res/values-land/dimen.xml | 4 - app/src/main/res/values-lv/strings.xml | 8 - app/src/main/res/values-night-v27/styles.xml | 9 - app/src/main/res/values-night/styles.xml | 70 - app/src/main/res/values-nl/strings.xml | 151 -- app/src/main/res/values-pl/strings.xml | 12 - app/src/main/res/values-pt/strings.xml | 588 ------- app/src/main/res/values-ro/strings.xml | 98 -- app/src/main/res/values-ru-rUA/strings.xml | 14 - app/src/main/res/values-ru/strings.xml | 830 ---------- app/src/main/res/values-sw533dp/bools.xml | 4 - app/src/main/res/values-uk/strings.xml | 366 ----- app/src/main/res/values-v27/styles.xml | 9 - app/src/main/res/values-v31/styles.xml | 9 - app/src/main/res/values-w1280dp/dimen.xml | 4 - app/src/main/res/values-w600dp/dimen.xml | 4 - app/src/main/res/values-yo/strings.xml | 2 - app/src/main/res/values-zh-rCN/strings.xml | 756 --------- app/src/main/res/values-zh-rTW/strings.xml | 843 ---------- app/src/main/res/values/attrs.xml | 91 -- app/src/main/res/values/bools.xml | 4 - app/src/main/res/values/colors.xml | 70 - app/src/main/res/values/dimen.xml | 94 -- app/src/main/res/values/strings.xml | 967 ------------ app/src/main/res/values/styles.xml | 509 ------ app/src/main/res/xml/authenticator.xml | 7 - app/src/main/res/xml/automotive_app_desc.xml | 4 - app/src/main/res/xml/locales_config.xml | 25 - .../motion_main_activity_tabs_selector.xml | 73 - ...otion_main_activity_tabs_selector_land.xml | 73 - app/src/main/res/xml/provider_paths.xml | 9 - app/src/main/res/xml/shortcuts.xml | 10 - app/src/main/res/xml/sync_adapter.xml | 9 - build.gradle | 23 - gradle.properties | 23 - gradle/wrapper/gradle-wrapper.jar | Bin 55616 -> 0 bytes gradle/wrapper/gradle-wrapper.properties | 6 - gradlew | 188 --- gradlew.bat | 100 -- keystore.properties | 4 - settings.gradle | 29 - 1031 files changed, 87523 deletions(-) delete mode 100644 .gitignore delete mode 100644 .gitlab-ci-files/job-android.yml delete mode 100644 .gitlab-ci-files/job-upload.yml delete mode 100644 .gitlab-ci.yml delete mode 100644 app/.gitignore delete mode 100644 app/build.gradle delete mode 100644 app/contacts.xml delete mode 100644 app/google-services.json delete mode 100644 app/proguard-rules.pro delete mode 100644 app/src/main/AndroidManifest.xml delete mode 100644 app/src/main/assets/assistant_default_values delete mode 100644 app/src/main/assets/assistant_linphone_default_values delete mode 100644 app/src/main/assets/linphonerc_default delete mode 100644 app/src/main/assets/linphonerc_factory delete mode 100644 app/src/main/java/org/linphone/LinphoneApplication.kt delete mode 100644 app/src/main/java/org/linphone/activities/GenericActivity.kt delete mode 100644 app/src/main/java/org/linphone/activities/GenericFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/Navigation.kt delete mode 100644 app/src/main/java/org/linphone/activities/ProximitySensorActivity.kt delete mode 100644 app/src/main/java/org/linphone/activities/SnackBarActivity.kt delete mode 100644 app/src/main/java/org/linphone/activities/assistant/AssistantActivity.kt delete mode 100644 app/src/main/java/org/linphone/activities/assistant/adapters/CountryPickerAdapter.kt delete mode 100644 app/src/main/java/org/linphone/activities/assistant/fragments/AbstractPhoneFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/assistant/fragments/AccountLoginFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/assistant/fragments/CountryPickerFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/assistant/fragments/EchoCancellerCalibrationFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/assistant/fragments/EmailAccountCreationFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/assistant/fragments/EmailAccountValidationFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/assistant/fragments/GenericAccountLoginFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/assistant/fragments/GenericAccountWarningFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/assistant/fragments/NoPushWarningFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/assistant/fragments/PhoneAccountCreationFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/assistant/fragments/PhoneAccountLinkingFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/assistant/fragments/PhoneAccountValidationFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/assistant/fragments/QrCodeFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/assistant/fragments/RemoteProvisioningFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/assistant/fragments/TopBarFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/assistant/fragments/WelcomeFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/assistant/viewmodels/AbstractPhoneViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/assistant/viewmodels/AbstractPushTokenViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/assistant/viewmodels/AccountLoginViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/assistant/viewmodels/EchoCancellerCalibrationViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/assistant/viewmodels/EmailAccountCreationViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/assistant/viewmodels/EmailAccountValidationViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/assistant/viewmodels/GenericLoginViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/assistant/viewmodels/PhoneAccountCreationViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/assistant/viewmodels/PhoneAccountLinkingViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/assistant/viewmodels/PhoneAccountValidationViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/assistant/viewmodels/QrCodeViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/assistant/viewmodels/RemoteProvisioningViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/assistant/viewmodels/SharedAssistantViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/assistant/viewmodels/WelcomeViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/chat_bubble/ChatBubbleActivity.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/MainActivity.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/about/AboutFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/about/AboutViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/adapters/SelectionListAdapter.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/chat/ChatScrollListener.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/chat/GroupChatRoomMember.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/chat/adapters/ChatMessagesListAdapter.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/chat/adapters/ChatRoomsListAdapter.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/chat/adapters/GroupInfoParticipantsAdapter.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/chat/adapters/ImdnAdapter.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageAttachmentData.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageContentData.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageData.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageReactionData.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageReactionsListData.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/chat/data/ChatRoomData.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/chat/data/DevicesListChildData.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/chat/data/DevicesListGroupData.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/chat/data/EphemeralDurationData.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/chat/data/EventData.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/chat/data/EventLogData.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/chat/data/GroupInfoParticipantData.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/chat/data/ImdnParticipantData.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/chat/fragments/ChatMessageReactionsListDialogFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/chat/fragments/ChatRoomCreationFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/chat/fragments/DevicesFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/chat/fragments/EphemeralFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/chat/fragments/GroupInfoFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/chat/fragments/ImdnFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/chat/fragments/MasterChatRoomsFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/chat/receivers/RichContentReceiver.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatMessageSendingViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatMessagesListViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomCreationViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomsListViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/chat/viewmodels/DevicesListViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/chat/viewmodels/EphemeralViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/chat/viewmodels/GroupInfoViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/chat/viewmodels/ImdnViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/chat/views/MultiLineWrapContentWidthTextView.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/chat/views/RichEditText.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/conference/adapters/ScheduledConferencesAdapter.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/conference/data/ConferenceSchedulingParticipantData.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/conference/data/Duration.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/conference/data/ScheduledConferenceData.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/conference/data/TimeZoneData.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/conference/fragments/ConferenceSchedulingFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/conference/fragments/ConferenceSchedulingParticipantsListFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/conference/fragments/ConferenceSchedulingSummaryFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/conference/fragments/ConferenceWaitingRoomFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/conference/fragments/ScheduledConferencesFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/conference/viewmodels/ConferenceSchedulingViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/conference/viewmodels/ConferenceWaitingRoomViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/conference/viewmodels/ScheduledConferencesViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/contact/adapters/ContactsListAdapter.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/contact/adapters/SyncAccountAdapter.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/contact/data/ContactEditorData.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/contact/data/ContactNumberOrAddressData.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/contact/data/NumberOrAddressEditorData.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/contact/fragments/ContactEditorFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/contact/fragments/DetailContactFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/contact/fragments/MasterContactsFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/contact/fragments/SyncAccountPickerFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/contact/viewmodels/ContactViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/contact/viewmodels/ContactsListViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/dialer/NumpadDigitListener.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/dialer/fragments/ConfigViewerFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/dialer/fragments/DialerFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/dialer/viewmodels/ConfigFileViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/dialer/viewmodels/DialerViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/files/adapters/PdfPagesListAdapter.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/files/fragments/AudioViewerFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/files/fragments/GenericViewerFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/files/fragments/ImageViewerFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/files/fragments/PdfViewerFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/files/fragments/TextViewerFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/files/fragments/TopBarFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/files/fragments/VideoViewerFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/files/viewmodels/AudioFileViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/files/viewmodels/FileViewerViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/files/viewmodels/ImageFileViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/files/viewmodels/PdfFileViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/files/viewmodels/TextFileViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/files/viewmodels/VideoFileViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/fragments/EmptyFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/fragments/ListTopBarFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/fragments/MasterFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/fragments/SecureFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/fragments/StatusFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/fragments/TabsFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/history/adapters/CallLogsListAdapter.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/history/data/GroupedCallLogData.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/history/fragments/DetailCallLogFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/history/fragments/DetailConferenceCallLogFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/history/fragments/MasterCallLogsFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/history/viewmodels/CallLogViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/history/viewmodels/CallLogsListViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/recordings/adapters/RecordingsListAdapter.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/recordings/data/RecordingData.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/recordings/fragments/RecordingsFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/recordings/viewmodels/RecordingsViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/settings/SettingListener.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/settings/SettingListenerStub.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/settings/fragments/AccountSettingsFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/settings/fragments/AdvancedSettingsFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/settings/fragments/AudioSettingsFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/settings/fragments/CallSettingsFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/settings/fragments/ChatSettingsFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/settings/fragments/ConferencesSettingsFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/settings/fragments/ContactsSettingsFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/settings/fragments/GenericSettingFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/settings/fragments/LdapSettingsFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/settings/fragments/NetworkSettingsFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/settings/fragments/SettingsFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/settings/fragments/TunnelSettingsFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/settings/fragments/VideoSettingsFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/settings/viewmodels/AccountSettingsViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/settings/viewmodels/AdvancedSettingsViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/settings/viewmodels/AudioSettingsViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/settings/viewmodels/CallSettingsViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/settings/viewmodels/ChatSettingsViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/settings/viewmodels/ConferencesSettingsViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/settings/viewmodels/ContactsSettingsViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/settings/viewmodels/GenericSettingsViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/settings/viewmodels/LdapSettingsViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/settings/viewmodels/NetworkSettingsViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/settings/viewmodels/SettingsViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/settings/viewmodels/TunnelSettingsViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/settings/viewmodels/VideoSettingsViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/sidemenu/fragments/SideMenuFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/sidemenu/viewmodels/SideMenuViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/viewmodels/CallOverlayViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/viewmodels/DialogViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/viewmodels/ListTopBarViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/viewmodels/LogsUploadViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/viewmodels/MessageNotifierViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/viewmodels/SharedMainViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/viewmodels/StatusViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/main/viewmodels/TabsViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/voip/CallActivity.kt delete mode 100644 app/src/main/java/org/linphone/activities/voip/ConferenceDisplayMode.kt delete mode 100644 app/src/main/java/org/linphone/activities/voip/data/CallData.kt delete mode 100644 app/src/main/java/org/linphone/activities/voip/data/CallStatisticsData.kt delete mode 100644 app/src/main/java/org/linphone/activities/voip/data/ConferenceInfoParticipantData.kt delete mode 100644 app/src/main/java/org/linphone/activities/voip/data/ConferenceParticipantData.kt delete mode 100644 app/src/main/java/org/linphone/activities/voip/data/ConferenceParticipantDeviceData.kt delete mode 100644 app/src/main/java/org/linphone/activities/voip/data/StatItemData.kt delete mode 100644 app/src/main/java/org/linphone/activities/voip/fragments/CallsListFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/voip/fragments/ChatFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/voip/fragments/ConferenceAddParticipantsFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/voip/fragments/ConferenceCallFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/voip/fragments/ConferenceLayoutFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/voip/fragments/ConferenceParticipantsFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/voip/fragments/GenericVideoPreviewFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/voip/fragments/IncomingCallFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/voip/fragments/OutgoingCallFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/voip/fragments/SingleCallFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/voip/fragments/StatusFragment.kt delete mode 100644 app/src/main/java/org/linphone/activities/voip/viewmodels/CallsViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/voip/viewmodels/ConferenceParticipantsViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/voip/viewmodels/ConferenceViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/voip/viewmodels/ControlsViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/voip/viewmodels/StatisticsListViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/voip/viewmodels/StatusViewModel.kt delete mode 100644 app/src/main/java/org/linphone/activities/voip/views/GridBoxLayout.kt delete mode 100644 app/src/main/java/org/linphone/activities/voip/views/RoundCornersTextureView.kt delete mode 100644 app/src/main/java/org/linphone/activities/voip/views/ScrollDotsView.kt delete mode 100644 app/src/main/java/org/linphone/compatibility/Api23Compatibility.kt delete mode 100644 app/src/main/java/org/linphone/compatibility/Api25Compatibility.kt delete mode 100644 app/src/main/java/org/linphone/compatibility/Api26Compatibility.kt delete mode 100644 app/src/main/java/org/linphone/compatibility/Api27Compatibility.kt delete mode 100644 app/src/main/java/org/linphone/compatibility/Api28Compatibility.kt delete mode 100644 app/src/main/java/org/linphone/compatibility/Api29Compatibility.kt delete mode 100644 app/src/main/java/org/linphone/compatibility/Api30Compatibility.kt delete mode 100644 app/src/main/java/org/linphone/compatibility/Api31Compatibility.kt delete mode 100644 app/src/main/java/org/linphone/compatibility/Api33Compatibility.kt delete mode 100644 app/src/main/java/org/linphone/compatibility/Api34Compatibility.kt delete mode 100644 app/src/main/java/org/linphone/compatibility/Compatibility.kt delete mode 100644 app/src/main/java/org/linphone/compatibility/PhoneStateInterface.kt delete mode 100644 app/src/main/java/org/linphone/compatibility/PhoneStateListener.kt delete mode 100644 app/src/main/java/org/linphone/compatibility/TelephonyListener.kt delete mode 100644 app/src/main/java/org/linphone/compatibility/XiaomiCompatibility.kt delete mode 100644 app/src/main/java/org/linphone/contact/ContactAvatarGenerator.kt delete mode 100644 app/src/main/java/org/linphone/contact/ContactDataInterface.kt delete mode 100644 app/src/main/java/org/linphone/contact/ContactLoader.kt delete mode 100644 app/src/main/java/org/linphone/contact/ContactSelectionData.kt delete mode 100644 app/src/main/java/org/linphone/contact/ContactsManager.kt delete mode 100644 app/src/main/java/org/linphone/contact/ContactsSelectionAdapter.kt delete mode 100644 app/src/main/java/org/linphone/contact/ContactsSelectionViewModel.kt delete mode 100644 app/src/main/java/org/linphone/contact/DummyAuthenticationService.kt delete mode 100644 app/src/main/java/org/linphone/contact/DummySyncService.kt delete mode 100644 app/src/main/java/org/linphone/contact/NativeContactEditor.kt delete mode 100644 app/src/main/java/org/linphone/core/BootReceiver.kt delete mode 100644 app/src/main/java/org/linphone/core/CoreContext.kt delete mode 100644 app/src/main/java/org/linphone/core/CorePreferences.kt delete mode 100644 app/src/main/java/org/linphone/core/CorePushReceiver.kt delete mode 100644 app/src/main/java/org/linphone/core/CoreService.kt delete mode 100644 app/src/main/java/org/linphone/notifications/NotificationBroadcastReceiver.kt delete mode 100644 app/src/main/java/org/linphone/notifications/NotificationsManager.kt delete mode 100644 app/src/main/java/org/linphone/telecom/NativeCallWrapper.kt delete mode 100644 app/src/main/java/org/linphone/telecom/TelecomConnectionService.kt delete mode 100644 app/src/main/java/org/linphone/telecom/TelecomHelper.kt delete mode 100644 app/src/main/java/org/linphone/utils/ActivityMonitor.kt delete mode 100644 app/src/main/java/org/linphone/utils/AppUtils.kt delete mode 100644 app/src/main/java/org/linphone/utils/AudioRouteUtils.kt delete mode 100644 app/src/main/java/org/linphone/utils/ContactUtils.kt delete mode 100644 app/src/main/java/org/linphone/utils/DataBindingUtils.kt delete mode 100644 app/src/main/java/org/linphone/utils/DialogUtils.kt delete mode 100644 app/src/main/java/org/linphone/utils/Event.kt delete mode 100644 app/src/main/java/org/linphone/utils/FileUtils.kt delete mode 100644 app/src/main/java/org/linphone/utils/ImageUtils.kt delete mode 100644 app/src/main/java/org/linphone/utils/LinphoneUtils.kt delete mode 100644 app/src/main/java/org/linphone/utils/PatternClickableSpan.kt delete mode 100644 app/src/main/java/org/linphone/utils/PermissionHelper.kt delete mode 100644 app/src/main/java/org/linphone/utils/PhoneNumberUtils.kt delete mode 100644 app/src/main/java/org/linphone/utils/PowerManagerUtils.kt delete mode 100644 app/src/main/java/org/linphone/utils/RecyclerViewHeaderDecoration.kt delete mode 100644 app/src/main/java/org/linphone/utils/RecyclerViewSwipeUtils.kt delete mode 100644 app/src/main/java/org/linphone/utils/ShortcutsHelper.kt delete mode 100644 app/src/main/java/org/linphone/utils/SingletonHolder.kt delete mode 100644 app/src/main/java/org/linphone/utils/TimestampUtils.kt delete mode 100644 app/src/main/java/org/linphone/views/MarqueeTextView.kt delete mode 100644 app/src/main/java/org/linphone/views/SettingTextInputEditText.kt delete mode 100644 app/src/main/java/org/linphone/views/VoiceRecordProgressBar.kt delete mode 100644 app/src/main/res/color/dark_primary_text_color.xml delete mode 100644 app/src/main/res/color/light_primary_text_color.xml delete mode 100644 app/src/main/res/color/voip_extra_button_text_color.xml delete mode 100644 app/src/main/res/drawable-hdpi/topbar_call_notification.png delete mode 100644 app/src/main/res/drawable-hdpi/topbar_call_paused_notification.png delete mode 100644 app/src/main/res/drawable-hdpi/topbar_chat_notification.png delete mode 100644 app/src/main/res/drawable-hdpi/topbar_missed_call_notification.png delete mode 100644 app/src/main/res/drawable-hdpi/topbar_service_notification.png delete mode 100644 app/src/main/res/drawable-hdpi/topbar_videocall_notification.png delete mode 100644 app/src/main/res/drawable-mdpi/topbar_call_notification.png delete mode 100644 app/src/main/res/drawable-mdpi/topbar_call_paused_notification.png delete mode 100644 app/src/main/res/drawable-mdpi/topbar_chat_notification.png delete mode 100644 app/src/main/res/drawable-mdpi/topbar_missed_call_notification.png delete mode 100644 app/src/main/res/drawable-mdpi/topbar_service_notification.png delete mode 100644 app/src/main/res/drawable-mdpi/topbar_videocall_notification.png delete mode 100644 app/src/main/res/drawable-xhdpi/audio_recording_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/audio_recording_reply_preview_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/back_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/backspace_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/call_add.png delete mode 100644 app/src/main/res/drawable-xhdpi/call_alt_start_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/call_audio_start.png delete mode 100644 app/src/main/res/drawable-xhdpi/call_back_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/call_hangup.png delete mode 100644 app/src/main/res/drawable-xhdpi/call_incoming.png delete mode 100644 app/src/main/res/drawable-xhdpi/call_missed.png delete mode 100644 app/src/main/res/drawable-xhdpi/call_outgoing.png delete mode 100644 app/src/main/res/drawable-xhdpi/call_quality_indicator_0.png delete mode 100644 app/src/main/res/drawable-xhdpi/call_quality_indicator_1.png delete mode 100644 app/src/main/res/drawable-xhdpi/call_quality_indicator_2.png delete mode 100644 app/src/main/res/drawable-xhdpi/call_quality_indicator_3.png delete mode 100644 app/src/main/res/drawable-xhdpi/call_quality_indicator_4.png delete mode 100644 app/src/main/res/drawable-xhdpi/call_start_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/call_status_incoming.png delete mode 100644 app/src/main/res/drawable-xhdpi/call_status_missed.png delete mode 100644 app/src/main/res/drawable-xhdpi/call_status_outgoing.png delete mode 100644 app/src/main/res/drawable-xhdpi/call_transfer.png delete mode 100644 app/src/main/res/drawable-xhdpi/call_video_start.png delete mode 100644 app/src/main/res/drawable-xhdpi/camera_switch_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/chat_delivered.png delete mode 100644 app/src/main/res/drawable-xhdpi/chat_error.png delete mode 100644 app/src/main/res/drawable-xhdpi/chat_file_over.png delete mode 100644 app/src/main/res/drawable-xhdpi/chat_group_add_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/chat_group_delete.png delete mode 100644 app/src/main/res/drawable-xhdpi/chat_group_new_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/chat_new_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/chat_read.png delete mode 100644 app/src/main/res/drawable-xhdpi/chat_send_over.png delete mode 100644 app/src/main/res/drawable-xhdpi/chat_start_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/check_selected.png delete mode 100644 app/src/main/res/drawable-xhdpi/check_unselected.png delete mode 100644 app/src/main/res/drawable-xhdpi/chevron_list_close_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/chevron_list_open_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/collapse_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/conference_schedule_calendar_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/conference_schedule_participants_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/conference_schedule_time_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/contact_add_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/contact_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/contacts_all_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/contacts_sip_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/delete_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/deselect_all_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/dialer_background.png delete mode 100644 app/src/main/res/drawable-xhdpi/edit_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/emoji_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/ephemeral_messages_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/file_audio_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/file_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/file_pdf_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/file_picture_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/file_video_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/footer_chat.png delete mode 100644 app/src/main/res/drawable-xhdpi/footer_contacts.png delete mode 100644 app/src/main/res/drawable-xhdpi/footer_dialer.png delete mode 100644 app/src/main/res/drawable-xhdpi/footer_history.png delete mode 100644 app/src/main/res/drawable-xhdpi/forward_message_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/forwarded_message_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/fullscreen_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/history_all_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/history_missed_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/info.png delete mode 100644 app/src/main/res/drawable-xhdpi/linphone_app_name_logo.png delete mode 100644 app/src/main/res/drawable-xhdpi/linphone_logo.png delete mode 100644 app/src/main/res/drawable-xhdpi/list_details_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/menu_about_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/menu_add_contact.png delete mode 100644 app/src/main/res/drawable-xhdpi/menu_assistant_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/menu_contact.png delete mode 100644 app/src/main/res/drawable-xhdpi/menu_copy_text_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/menu_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/menu_delete_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/menu_ephemeral_messages_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/menu_forward_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/menu_group_info_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/menu_meeting_schedule.png delete mode 100644 app/src/main/res/drawable-xhdpi/menu_notifications_off.png delete mode 100644 app/src/main/res/drawable-xhdpi/menu_notifications_on.png delete mode 100644 app/src/main/res/drawable-xhdpi/menu_options_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/menu_recordings_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/menu_reply_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/menu_security_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/next_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/numpad_0.png delete mode 100644 app/src/main/res/drawable-xhdpi/numpad_1.png delete mode 100644 app/src/main/res/drawable-xhdpi/numpad_2.png delete mode 100644 app/src/main/res/drawable-xhdpi/numpad_3.png delete mode 100644 app/src/main/res/drawable-xhdpi/numpad_4.png delete mode 100644 app/src/main/res/drawable-xhdpi/numpad_5.png delete mode 100644 app/src/main/res/drawable-xhdpi/numpad_6.png delete mode 100644 app/src/main/res/drawable-xhdpi/numpad_7.png delete mode 100644 app/src/main/res/drawable-xhdpi/numpad_8.png delete mode 100644 app/src/main/res/drawable-xhdpi/numpad_9.png delete mode 100644 app/src/main/res/drawable-xhdpi/numpad_hash.png delete mode 100644 app/src/main/res/drawable-xhdpi/numpad_star.png delete mode 100644 app/src/main/res/drawable-xhdpi/quit_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/record_audio_message_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/record_pause_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/record_play_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/record_stop_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/replied_message_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/resizable_assistant_button_default.9.png delete mode 100644 app/src/main/res/drawable-xhdpi/resizable_assistant_button_disabled.9.png delete mode 100644 app/src/main/res/drawable-xhdpi/resizable_assistant_button_over.9.png delete mode 100644 app/src/main/res/drawable-xhdpi/resizable_text_field.9.png delete mode 100644 app/src/main/res/drawable-xhdpi/scroll_to_bottom_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/search.png delete mode 100644 app/src/main/res/drawable-xhdpi/security_1_indicator.png delete mode 100644 app/src/main/res/drawable-xhdpi/security_2_indicator.png delete mode 100644 app/src/main/res/drawable-xhdpi/security_alert_indicator.png delete mode 100644 app/src/main/res/drawable-xhdpi/security_ko.png delete mode 100644 app/src/main/res/drawable-xhdpi/security_ok.png delete mode 100644 app/src/main/res/drawable-xhdpi/security_pending.png delete mode 100644 app/src/main/res/drawable-xhdpi/security_post_quantum.png delete mode 100644 app/src/main/res/drawable-xhdpi/security_toggle_icon_green.png delete mode 100644 app/src/main/res/drawable-xhdpi/security_toggle_icon_grey.png delete mode 100644 app/src/main/res/drawable-xhdpi/select_all_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/settings_advanced_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/settings_audio_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/settings_call_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/settings_chat_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/settings_conferences_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/settings_contacts_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/settings_network_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/settings_video_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/splashscreen_branding.png delete mode 100644 app/src/main/res/drawable-xhdpi/topbar_call_notification.png delete mode 100644 app/src/main/res/drawable-xhdpi/topbar_call_paused_notification.png delete mode 100644 app/src/main/res/drawable-xhdpi/topbar_chat_notification.png delete mode 100644 app/src/main/res/drawable-xhdpi/topbar_missed_call_notification.png delete mode 100644 app/src/main/res/drawable-xhdpi/topbar_service_notification.png delete mode 100644 app/src/main/res/drawable-xhdpi/topbar_videocall_notification.png delete mode 100644 app/src/main/res/drawable-xhdpi/valid_default.png delete mode 100644 app/src/main/res/drawable-xhdpi/voicemail.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_audio_routes.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_bluetooth.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_call.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_call_add.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_call_chat.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_call_forward.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_call_header_active.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_call_header_incoming.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_call_header_outgoing.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_call_header_paused.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_call_list_menu.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_call_more.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_call_numpad.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_call_participants.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_call_record.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_call_stats.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_calls_list.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_camera_off.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_camera_on.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_cancel.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_change_camera.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_chat_rooms_list.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_conference.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_conference_active_speaker.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_conference_audio_only.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_conference_mosaic.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_conference_paused_big.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_conference_play_big.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_copy.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_delete.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_dropdown.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_earpiece.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_edit.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_export.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_hangup.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_info.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_mandatory.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_meeting_schedule.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_menu_more.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_merge_calls.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_micro_off.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_micro_on.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_multiple_contacts_avatar.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_multiple_contacts_avatar_alt.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_numpad_0.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_numpad_1.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_numpad_2.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_numpad_3.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_numpad_4.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_numpad_5.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_numpad_6.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_numpad_7.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_numpad_8.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_numpad_9.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_numpad_hash.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_numpad_star.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_pause.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_remote_recording.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_screenshot.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_single_contact_avatar.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_single_contact_avatar_alt.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_speaker_off.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_speaker_on.png delete mode 100644 app/src/main/res/drawable-xhdpi/voip_spinner.png delete mode 100644 app/src/main/res/drawable-xxhdpi/topbar_call_notification.png delete mode 100644 app/src/main/res/drawable-xxhdpi/topbar_call_paused_notification.png delete mode 100644 app/src/main/res/drawable-xxhdpi/topbar_chat_notification.png delete mode 100644 app/src/main/res/drawable-xxhdpi/topbar_missed_call_notification.png delete mode 100644 app/src/main/res/drawable-xxhdpi/topbar_service_notification.png delete mode 100644 app/src/main/res/drawable-xxhdpi/topbar_videocall_notification.png delete mode 100644 app/src/main/res/drawable-xxxhdpi/topbar_call_notification.png delete mode 100644 app/src/main/res/drawable-xxxhdpi/topbar_call_paused_notification.png delete mode 100644 app/src/main/res/drawable-xxxhdpi/topbar_chat_notification.png delete mode 100644 app/src/main/res/drawable-xxxhdpi/topbar_missed_call_notification.png delete mode 100644 app/src/main/res/drawable-xxxhdpi/topbar_service_notification.png delete mode 100644 app/src/main/res/drawable-xxxhdpi/topbar_videocall_notification.png delete mode 100644 app/src/main/res/drawable/assistant_button.xml delete mode 100644 app/src/main/res/drawable/assistant_button_text_color.xml delete mode 100644 app/src/main/res/drawable/avatar_border.xml delete mode 100644 app/src/main/res/drawable/back.xml delete mode 100644 app/src/main/res/drawable/background_round_primary_color.xml delete mode 100644 app/src/main/res/drawable/background_round_secondary_color.xml delete mode 100644 app/src/main/res/drawable/backspace.xml delete mode 100644 app/src/main/res/drawable/button_background.xml delete mode 100644 app/src/main/res/drawable/button_background_dark.xml delete mode 100644 app/src/main/res/drawable/button_background_light.xml delete mode 100644 app/src/main/res/drawable/button_background_reverse.xml delete mode 100644 app/src/main/res/drawable/button_call_answer_background.xml delete mode 100644 app/src/main/res/drawable/button_call_context_menu_background.xml delete mode 100644 app/src/main/res/drawable/button_call_numpad_background.xml delete mode 100644 app/src/main/res/drawable/button_call_recording_background.xml delete mode 100644 app/src/main/res/drawable/button_call_terminate_background.xml delete mode 100644 app/src/main/res/drawable/button_conference_info.xml delete mode 100644 app/src/main/res/drawable/button_green_background.xml delete mode 100644 app/src/main/res/drawable/button_orange_background.xml delete mode 100644 app/src/main/res/drawable/button_round_background.xml delete mode 100644 app/src/main/res/drawable/button_toggle_background.xml delete mode 100644 app/src/main/res/drawable/button_toggle_background_reverse.xml delete mode 100644 app/src/main/res/drawable/call.xml delete mode 100644 app/src/main/res/drawable/call_alt_start.xml delete mode 100644 app/src/main/res/drawable/call_back.xml delete mode 100644 app/src/main/res/drawable/camera_switch.xml delete mode 100644 app/src/main/res/drawable/cancel.xml delete mode 100644 app/src/main/res/drawable/cancel_shape.xml delete mode 100644 app/src/main/res/drawable/chat_bubble_incoming_full.xml delete mode 100644 app/src/main/res/drawable/chat_bubble_incoming_reactions.xml delete mode 100644 app/src/main/res/drawable/chat_bubble_incoming_split_1.xml delete mode 100644 app/src/main/res/drawable/chat_bubble_incoming_split_2.xml delete mode 100644 app/src/main/res/drawable/chat_bubble_incoming_split_3.xml delete mode 100644 app/src/main/res/drawable/chat_bubble_outgoing_full.xml delete mode 100644 app/src/main/res/drawable/chat_bubble_outgoing_reactions.xml delete mode 100644 app/src/main/res/drawable/chat_bubble_outgoing_split_1.xml delete mode 100644 app/src/main/res/drawable/chat_bubble_outgoing_split_2.xml delete mode 100644 app/src/main/res/drawable/chat_bubble_outgoing_split_3.xml delete mode 100644 app/src/main/res/drawable/chat_bubble_reply_background.xml delete mode 100644 app/src/main/res/drawable/chat_bubble_reply_file_background.xml delete mode 100644 app/src/main/res/drawable/chat_bubble_reply_incoming_indicator.xml delete mode 100644 app/src/main/res/drawable/chat_bubble_reply_outgoing_indicator.xml delete mode 100644 app/src/main/res/drawable/chat_file.xml delete mode 100644 app/src/main/res/drawable/chat_group_add.xml delete mode 100644 app/src/main/res/drawable/chat_group_new.xml delete mode 100644 app/src/main/res/drawable/chat_message_audio_record_preview_progress.xml delete mode 100644 app/src/main/res/drawable/chat_message_audio_record_progress.xml delete mode 100644 app/src/main/res/drawable/chat_message_voice_recording_background.xml delete mode 100644 app/src/main/res/drawable/chat_new.xml delete mode 100644 app/src/main/res/drawable/chat_room_menu_add_contact.xml delete mode 100644 app/src/main/res/drawable/chat_room_menu_delete.xml delete mode 100644 app/src/main/res/drawable/chat_room_menu_devices.xml delete mode 100644 app/src/main/res/drawable/chat_room_menu_ephemeral.xml delete mode 100644 app/src/main/res/drawable/chat_room_menu_group_info.xml delete mode 100644 app/src/main/res/drawable/chat_room_menu_meeting.xml delete mode 100644 app/src/main/res/drawable/chat_room_menu_mute_notifications.xml delete mode 100644 app/src/main/res/drawable/chat_room_menu_unmute_notifications.xml delete mode 100644 app/src/main/res/drawable/chat_room_menu_view_contact.xml delete mode 100644 app/src/main/res/drawable/chat_send_message.xml delete mode 100644 app/src/main/res/drawable/chevron_list_close.xml delete mode 100644 app/src/main/res/drawable/chevron_list_open.xml delete mode 100644 app/src/main/res/drawable/contact.xml delete mode 100644 app/src/main/res/drawable/contact_add.xml delete mode 100644 app/src/main/res/drawable/contacts_all.xml delete mode 100644 app/src/main/res/drawable/contacts_sip.xml delete mode 100644 app/src/main/res/drawable/delete.xml delete mode 100644 app/src/main/res/drawable/deselect_all.xml delete mode 100644 app/src/main/res/drawable/dialog_delete_icon.xml delete mode 100644 app/src/main/res/drawable/divider.xml delete mode 100644 app/src/main/res/drawable/edit.xml delete mode 100644 app/src/main/res/drawable/emoji.xml delete mode 100644 app/src/main/res/drawable/ephemeral_messages.xml delete mode 100644 app/src/main/res/drawable/event_decoration_gray.xml delete mode 100644 app/src/main/res/drawable/event_decoration_red.xml delete mode 100644 app/src/main/res/drawable/field_add.xml delete mode 100644 app/src/main/res/drawable/field_add_default.xml delete mode 100644 app/src/main/res/drawable/field_add_disabled.xml delete mode 100644 app/src/main/res/drawable/field_add_over.xml delete mode 100644 app/src/main/res/drawable/field_add_shape.xml delete mode 100644 app/src/main/res/drawable/field_clean.xml delete mode 100644 app/src/main/res/drawable/field_clean_default.xml delete mode 100644 app/src/main/res/drawable/field_clean_over.xml delete mode 100644 app/src/main/res/drawable/field_clean_shape.xml delete mode 100644 app/src/main/res/drawable/field_remove.xml delete mode 100644 app/src/main/res/drawable/field_remove_default.xml delete mode 100644 app/src/main/res/drawable/field_remove_disabled.xml delete mode 100644 app/src/main/res/drawable/field_remove_over.xml delete mode 100644 app/src/main/res/drawable/field_remove_shape.xml delete mode 100644 app/src/main/res/drawable/file.xml delete mode 100644 app/src/main/res/drawable/file_audio.xml delete mode 100644 app/src/main/res/drawable/file_pdf.xml delete mode 100644 app/src/main/res/drawable/file_picture.xml delete mode 100644 app/src/main/res/drawable/file_video.xml delete mode 100644 app/src/main/res/drawable/footer_button.xml delete mode 100644 app/src/main/res/drawable/generated_avatar_bg.xml delete mode 100644 app/src/main/res/drawable/hidden_unread_message_count_bg.xml delete mode 100644 app/src/main/res/drawable/history_all.xml delete mode 100644 app/src/main/res/drawable/history_detail_background.xml delete mode 100644 app/src/main/res/drawable/history_detail_background_pressed.xml delete mode 100644 app/src/main/res/drawable/history_missed.xml delete mode 100644 app/src/main/res/drawable/icon_apply.xml delete mode 100644 app/src/main/res/drawable/icon_audio_routes.xml delete mode 100644 app/src/main/res/drawable/icon_bluetooth.xml delete mode 100644 app/src/main/res/drawable/icon_call_add.xml delete mode 100644 app/src/main/res/drawable/icon_call_answer.xml delete mode 100644 app/src/main/res/drawable/icon_call_answer_video.xml delete mode 100644 app/src/main/res/drawable/icon_call_camera_switch.xml delete mode 100644 app/src/main/res/drawable/icon_call_chat.xml delete mode 100644 app/src/main/res/drawable/icon_call_chat_rooms.xml delete mode 100644 app/src/main/res/drawable/icon_call_forward.xml delete mode 100644 app/src/main/res/drawable/icon_call_hangup.xml delete mode 100644 app/src/main/res/drawable/icon_call_more.xml delete mode 100644 app/src/main/res/drawable/icon_call_numpad.xml delete mode 100644 app/src/main/res/drawable/icon_call_participants.xml delete mode 100644 app/src/main/res/drawable/icon_call_record.xml delete mode 100644 app/src/main/res/drawable/icon_call_screenshot.xml delete mode 100644 app/src/main/res/drawable/icon_call_stats.xml delete mode 100644 app/src/main/res/drawable/icon_calls_list.xml delete mode 100644 app/src/main/res/drawable/icon_calls_list_menu.xml delete mode 100644 app/src/main/res/drawable/icon_cancel.xml delete mode 100644 app/src/main/res/drawable/icon_cancel_alt.xml delete mode 100644 app/src/main/res/drawable/icon_conference_layout_active_speaker.xml delete mode 100644 app/src/main/res/drawable/icon_conference_layout_audio_only.xml delete mode 100644 app/src/main/res/drawable/icon_conference_layout_grid.xml delete mode 100644 app/src/main/res/drawable/icon_copy.xml delete mode 100644 app/src/main/res/drawable/icon_delete.xml delete mode 100644 app/src/main/res/drawable/icon_earpiece.xml delete mode 100644 app/src/main/res/drawable/icon_edit.xml delete mode 100644 app/src/main/res/drawable/icon_edit_alt.xml delete mode 100644 app/src/main/res/drawable/icon_ephemeral_message.xml delete mode 100644 app/src/main/res/drawable/icon_forwarded_message.xml delete mode 100644 app/src/main/res/drawable/icon_info.xml delete mode 100644 app/src/main/res/drawable/icon_info_selected.xml delete mode 100644 app/src/main/res/drawable/icon_meeting_schedule.xml delete mode 100644 app/src/main/res/drawable/icon_menu_more.xml delete mode 100644 app/src/main/res/drawable/icon_merge_calls_local_conference.xml delete mode 100644 app/src/main/res/drawable/icon_mic_muted.xml delete mode 100644 app/src/main/res/drawable/icon_multiple_contacts_avatar.xml delete mode 100644 app/src/main/res/drawable/icon_next.xml delete mode 100644 app/src/main/res/drawable/icon_pause.xml delete mode 100644 app/src/main/res/drawable/icon_replied_message.xml delete mode 100644 app/src/main/res/drawable/icon_schedule_date.xml delete mode 100644 app/src/main/res/drawable/icon_schedule_participants.xml delete mode 100644 app/src/main/res/drawable/icon_schedule_time.xml delete mode 100644 app/src/main/res/drawable/icon_share.xml delete mode 100644 app/src/main/res/drawable/icon_single_contact_avatar.xml delete mode 100644 app/src/main/res/drawable/icon_speaker.xml delete mode 100644 app/src/main/res/drawable/icon_spinner.xml delete mode 100644 app/src/main/res/drawable/icon_spinner_background.xml delete mode 100644 app/src/main/res/drawable/icon_spinner_rotating.xml delete mode 100644 app/src/main/res/drawable/icon_toggle_camera.xml delete mode 100644 app/src/main/res/drawable/icon_toggle_mic.xml delete mode 100644 app/src/main/res/drawable/icon_toggle_speaker.xml delete mode 100644 app/src/main/res/drawable/icon_video_conf_history_filter.xml delete mode 100644 app/src/main/res/drawable/icon_video_conf_incoming.xml delete mode 100644 app/src/main/res/drawable/icon_video_conf_new.xml delete mode 100644 app/src/main/res/drawable/led_away.xml delete mode 100644 app/src/main/res/drawable/led_background.xml delete mode 100644 app/src/main/res/drawable/led_do_not_disturb.xml delete mode 100644 app/src/main/res/drawable/led_error.xml delete mode 100644 app/src/main/res/drawable/led_not_registered.xml delete mode 100644 app/src/main/res/drawable/led_online.xml delete mode 100644 app/src/main/res/drawable/led_registered.xml delete mode 100644 app/src/main/res/drawable/led_registration_in_progress.xml delete mode 100644 app/src/main/res/drawable/linphone_logo_tinted.xml delete mode 100644 app/src/main/res/drawable/list_detail.xml delete mode 100644 app/src/main/res/drawable/menu.xml delete mode 100644 app/src/main/res/drawable/menu_about.xml delete mode 100644 app/src/main/res/drawable/menu_assistant.xml delete mode 100644 app/src/main/res/drawable/menu_background.xml delete mode 100644 app/src/main/res/drawable/menu_background_color.xml delete mode 100644 app/src/main/res/drawable/menu_copy_text.xml delete mode 100644 app/src/main/res/drawable/menu_delete.xml delete mode 100644 app/src/main/res/drawable/menu_forward.xml delete mode 100644 app/src/main/res/drawable/menu_imdn_info.xml delete mode 100644 app/src/main/res/drawable/menu_options.xml delete mode 100644 app/src/main/res/drawable/menu_pressed_background_color.xml delete mode 100644 app/src/main/res/drawable/menu_recordings.xml delete mode 100644 app/src/main/res/drawable/menu_reply.xml delete mode 100644 app/src/main/res/drawable/numpad_eight.xml delete mode 100644 app/src/main/res/drawable/numpad_five.xml delete mode 100644 app/src/main/res/drawable/numpad_four.xml delete mode 100644 app/src/main/res/drawable/numpad_nine.xml delete mode 100644 app/src/main/res/drawable/numpad_one.xml delete mode 100644 app/src/main/res/drawable/numpad_seven.xml delete mode 100644 app/src/main/res/drawable/numpad_sharp.xml delete mode 100644 app/src/main/res/drawable/numpad_six.xml delete mode 100644 app/src/main/res/drawable/numpad_star_digit.xml delete mode 100644 app/src/main/res/drawable/numpad_three.xml delete mode 100644 app/src/main/res/drawable/numpad_two.xml delete mode 100644 app/src/main/res/drawable/numpad_zero.xml delete mode 100644 app/src/main/res/drawable/record_audio_message.xml delete mode 100644 app/src/main/res/drawable/record_pause_dark.xml delete mode 100644 app/src/main/res/drawable/record_pause_light.xml delete mode 100644 app/src/main/res/drawable/record_play_dark.xml delete mode 100644 app/src/main/res/drawable/record_play_light.xml delete mode 100644 app/src/main/res/drawable/record_stop_light.xml delete mode 100644 app/src/main/res/drawable/recording_play_pause.xml delete mode 100644 app/src/main/res/drawable/rect_orange_button.xml delete mode 100644 app/src/main/res/drawable/resizable_assistant_button.xml delete mode 100644 app/src/main/res/drawable/round_button_background.xml delete mode 100644 app/src/main/res/drawable/round_button_background_default.xml delete mode 100644 app/src/main/res/drawable/round_orange_button_background.xml delete mode 100644 app/src/main/res/drawable/round_orange_button_background_default.xml delete mode 100644 app/src/main/res/drawable/round_orange_button_background_disabled.xml delete mode 100644 app/src/main/res/drawable/round_orange_button_background_over.xml delete mode 100644 app/src/main/res/drawable/round_recording_button_background_dark.xml delete mode 100644 app/src/main/res/drawable/round_recording_button_background_light.xml delete mode 100644 app/src/main/res/drawable/scroll_to_bottom.xml delete mode 100644 app/src/main/res/drawable/select_all.xml delete mode 100644 app/src/main/res/drawable/settings_advanced.xml delete mode 100644 app/src/main/res/drawable/settings_audio.xml delete mode 100644 app/src/main/res/drawable/settings_call.xml delete mode 100644 app/src/main/res/drawable/settings_chat.xml delete mode 100644 app/src/main/res/drawable/settings_conferences.xml delete mode 100644 app/src/main/res/drawable/settings_contacts.xml delete mode 100644 app/src/main/res/drawable/settings_network.xml delete mode 100644 app/src/main/res/drawable/settings_video.xml delete mode 100644 app/src/main/res/drawable/shape_audio_only_me_background.xml delete mode 100644 app/src/main/res/drawable/shape_audio_only_remote_background.xml delete mode 100644 app/src/main/res/drawable/shape_audio_routes_background.xml delete mode 100644 app/src/main/res/drawable/shape_button_background.xml delete mode 100644 app/src/main/res/drawable/shape_button_disabled_background.xml delete mode 100644 app/src/main/res/drawable/shape_button_pressed_background.xml delete mode 100644 app/src/main/res/drawable/shape_call_answer_background.xml delete mode 100644 app/src/main/res/drawable/shape_call_answer_pressed_background.xml delete mode 100644 app/src/main/res/drawable/shape_call_numpad_background.xml delete mode 100644 app/src/main/res/drawable/shape_call_numpad_pressed_background.xml delete mode 100644 app/src/main/res/drawable/shape_call_popup_background.xml delete mode 100644 app/src/main/res/drawable/shape_call_recording_off_background.xml delete mode 100644 app/src/main/res/drawable/shape_call_recording_on_background.xml delete mode 100644 app/src/main/res/drawable/shape_call_terminate_background.xml delete mode 100644 app/src/main/res/drawable/shape_call_terminate_pressed_background.xml delete mode 100644 app/src/main/res/drawable/shape_conference_active_speaker_border.xml delete mode 100644 app/src/main/res/drawable/shape_conference_audio_only_border.xml delete mode 100644 app/src/main/res/drawable/shape_conference_invite_background.xml delete mode 100644 app/src/main/res/drawable/shape_dialog_background.xml delete mode 100644 app/src/main/res/drawable/shape_dialog_handle.xml delete mode 100644 app/src/main/res/drawable/shape_edittext_background.xml delete mode 100644 app/src/main/res/drawable/shape_extra_buttons_background.xml delete mode 100644 app/src/main/res/drawable/shape_form_field_background.xml delete mode 100644 app/src/main/res/drawable/shape_me_background.xml delete mode 100644 app/src/main/res/drawable/shape_orange_circle.xml delete mode 100644 app/src/main/res/drawable/shape_own_reaction_background.xml delete mode 100644 app/src/main/res/drawable/shape_rect_gray_button.xml delete mode 100644 app/src/main/res/drawable/shape_rect_green_button.xml delete mode 100644 app/src/main/res/drawable/shape_rect_light_gray_button.xml delete mode 100644 app/src/main/res/drawable/shape_rect_orange_button.xml delete mode 100644 app/src/main/res/drawable/shape_rect_orange_disabled_button.xml delete mode 100644 app/src/main/res/drawable/shape_remote_background.xml delete mode 100644 app/src/main/res/drawable/shape_remote_paused_background.xml delete mode 100644 app/src/main/res/drawable/shape_remote_recording_background.xml delete mode 100644 app/src/main/res/drawable/shape_remote_video_background.xml delete mode 100644 app/src/main/res/drawable/shape_round_button.xml delete mode 100644 app/src/main/res/drawable/shape_round_button_disabled.xml delete mode 100644 app/src/main/res/drawable/shape_round_button_pressed.xml delete mode 100644 app/src/main/res/drawable/shape_round_dark_gray_background.xml delete mode 100644 app/src/main/res/drawable/shape_round_dark_gray_background_with_orange_border.xml delete mode 100644 app/src/main/res/drawable/shape_round_gray_background.xml delete mode 100644 app/src/main/res/drawable/shape_round_gray_background_with_orange_border.xml delete mode 100644 app/src/main/res/drawable/shape_round_red_background.xml delete mode 100644 app/src/main/res/drawable/shape_round_red_background_with_orange_border.xml delete mode 100644 app/src/main/res/drawable/shape_toggle_disabled_background.xml delete mode 100644 app/src/main/res/drawable/shape_toggle_off_background.xml delete mode 100644 app/src/main/res/drawable/shape_toggle_on_background.xml delete mode 100644 app/src/main/res/drawable/shape_toggle_pressed_background.xml delete mode 100644 app/src/main/res/drawable/shape_transparent_reaction_background.xml delete mode 100644 app/src/main/res/drawable/status_led_background.xml delete mode 100644 app/src/main/res/drawable/unread_message_count_bg.xml delete mode 100644 app/src/main/res/drawable/valid.xml delete mode 100644 app/src/main/res/drawable/vector_linphone_logo.xml delete mode 100644 app/src/main/res/layout-land/about_fragment.xml delete mode 100644 app/src/main/res/layout-land/assistant_welcome_fragment.xml delete mode 100644 app/src/main/res/layout-land/conference_waiting_room_fragment.xml delete mode 100644 app/src/main/res/layout-land/dialer_fragment.xml delete mode 100644 app/src/main/res/layout-land/main_activity_content.xml delete mode 100644 app/src/main/res/layout-land/tabs_fragment.xml delete mode 100644 app/src/main/res/layout-land/voip_conference_active_speaker.xml delete mode 100644 app/src/main/res/layout-land/voip_conference_grid.xml delete mode 100644 app/src/main/res/layout-land/voip_numpad.xml delete mode 100644 app/src/main/res/layout-sw533dp-land/dialer_fragment.xml delete mode 100644 app/src/main/res/layout-sw600dp/dialer_fragment.xml delete mode 100644 app/src/main/res/layout/about_fragment.xml delete mode 100644 app/src/main/res/layout/assistant_account_login_fragment.xml delete mode 100644 app/src/main/res/layout/assistant_activity.xml delete mode 100644 app/src/main/res/layout/assistant_country_picker_cell.xml delete mode 100644 app/src/main/res/layout/assistant_country_picker_fragment.xml delete mode 100644 app/src/main/res/layout/assistant_echo_canceller_calibration_fragment.xml delete mode 100644 app/src/main/res/layout/assistant_email_account_creation_fragment.xml delete mode 100644 app/src/main/res/layout/assistant_email_account_validation_fragment.xml delete mode 100644 app/src/main/res/layout/assistant_generic_account_login_fragment.xml delete mode 100644 app/src/main/res/layout/assistant_generic_account_warning_fragment.xml delete mode 100644 app/src/main/res/layout/assistant_no_push_warning_fragment.xml delete mode 100644 app/src/main/res/layout/assistant_phone_account_creation_fragment.xml delete mode 100644 app/src/main/res/layout/assistant_phone_account_linking_fragment.xml delete mode 100644 app/src/main/res/layout/assistant_phone_account_validation_fragment.xml delete mode 100644 app/src/main/res/layout/assistant_qr_code_fragment.xml delete mode 100644 app/src/main/res/layout/assistant_remote_provisioning_fragment.xml delete mode 100644 app/src/main/res/layout/assistant_top_bar_fragment.xml delete mode 100644 app/src/main/res/layout/assistant_welcome_fragment.xml delete mode 100644 app/src/main/res/layout/call_incoming_notification_heads_up.xml delete mode 100644 app/src/main/res/layout/call_overlay.xml delete mode 100644 app/src/main/res/layout/chat_bubble_activity.xml delete mode 100644 app/src/main/res/layout/chat_event_list_cell.xml delete mode 100644 app/src/main/res/layout/chat_message_attachment_cell.xml delete mode 100644 app/src/main/res/layout/chat_message_conference_invitation_content_cell.xml delete mode 100644 app/src/main/res/layout/chat_message_content_cell.xml delete mode 100644 app/src/main/res/layout/chat_message_downloadable_file_content_cell.xml delete mode 100644 app/src/main/res/layout/chat_message_generic_file_content_cell.xml delete mode 100644 app/src/main/res/layout/chat_message_image_content_cell.xml delete mode 100644 app/src/main/res/layout/chat_message_list_cell.xml delete mode 100644 app/src/main/res/layout/chat_message_long_press_menu.xml delete mode 100644 app/src/main/res/layout/chat_message_reaction.xml delete mode 100644 app/src/main/res/layout/chat_message_reactions_list_cell.xml delete mode 100644 app/src/main/res/layout/chat_message_reactions_list_dialog.xml delete mode 100644 app/src/main/res/layout/chat_message_reply.xml delete mode 100644 app/src/main/res/layout/chat_message_reply_bubble.xml delete mode 100644 app/src/main/res/layout/chat_message_reply_content_cell.xml delete mode 100644 app/src/main/res/layout/chat_message_reply_preview_content_cell.xml delete mode 100644 app/src/main/res/layout/chat_message_video_content_cell.xml delete mode 100644 app/src/main/res/layout/chat_message_voice_record_content_cell.xml delete mode 100644 app/src/main/res/layout/chat_message_voice_recording.xml delete mode 100644 app/src/main/res/layout/chat_room_creation_fragment.xml delete mode 100644 app/src/main/res/layout/chat_room_detail_fragment.xml delete mode 100644 app/src/main/res/layout/chat_room_devices_child_cell.xml delete mode 100644 app/src/main/res/layout/chat_room_devices_fragment.xml delete mode 100644 app/src/main/res/layout/chat_room_devices_group_cell.xml delete mode 100644 app/src/main/res/layout/chat_room_ephemeral_duration_cell.xml delete mode 100644 app/src/main/res/layout/chat_room_ephemeral_fragment.xml delete mode 100644 app/src/main/res/layout/chat_room_group_info_fragment.xml delete mode 100644 app/src/main/res/layout/chat_room_group_info_participant_cell.xml delete mode 100644 app/src/main/res/layout/chat_room_imdn_fragment.xml delete mode 100644 app/src/main/res/layout/chat_room_imdn_participant_cell.xml delete mode 100644 app/src/main/res/layout/chat_room_list_cell.xml delete mode 100644 app/src/main/res/layout/chat_room_master_fragment.xml delete mode 100644 app/src/main/res/layout/chat_room_menu.xml delete mode 100644 app/src/main/res/layout/chat_room_sending.xml delete mode 100644 app/src/main/res/layout/chat_unread_messages_list_header.xml delete mode 100644 app/src/main/res/layout/conference_schedule_cell.xml delete mode 100644 app/src/main/res/layout/conference_schedule_list_header.xml delete mode 100644 app/src/main/res/layout/conference_scheduling_fragment.xml delete mode 100644 app/src/main/res/layout/conference_scheduling_participant_cell.xml delete mode 100644 app/src/main/res/layout/conference_scheduling_participants_list_fragment.xml delete mode 100644 app/src/main/res/layout/conference_scheduling_summary_fragment.xml delete mode 100644 app/src/main/res/layout/conference_waiting_room_fragment.xml delete mode 100644 app/src/main/res/layout/conferences_scheduled_fragment.xml delete mode 100644 app/src/main/res/layout/contact_detail_cell.xml delete mode 100644 app/src/main/res/layout/contact_detail_fragment.xml delete mode 100644 app/src/main/res/layout/contact_editor_fragment.xml delete mode 100644 app/src/main/res/layout/contact_list_cell.xml delete mode 100644 app/src/main/res/layout/contact_master_fragment.xml delete mode 100644 app/src/main/res/layout/contact_number_address_editor_cell.xml delete mode 100644 app/src/main/res/layout/contact_selected_cell.xml delete mode 100644 app/src/main/res/layout/contact_selection_cell.xml delete mode 100644 app/src/main/res/layout/contact_sync_account_picker_cell.xml delete mode 100644 app/src/main/res/layout/contact_sync_account_picker_fragment.xml delete mode 100644 app/src/main/res/layout/dialer_fragment.xml delete mode 100644 app/src/main/res/layout/dialog.xml delete mode 100644 app/src/main/res/layout/empty_fragment.xml delete mode 100644 app/src/main/res/layout/file_audio_viewer_fragment.xml delete mode 100644 app/src/main/res/layout/file_config_viewer_fragment.xml delete mode 100644 app/src/main/res/layout/file_image_viewer_fragment.xml delete mode 100644 app/src/main/res/layout/file_pdf_viewer_cell.xml delete mode 100644 app/src/main/res/layout/file_pdf_viewer_fragment.xml delete mode 100644 app/src/main/res/layout/file_text_viewer_fragment.xml delete mode 100644 app/src/main/res/layout/file_video_viewer_fragment.xml delete mode 100644 app/src/main/res/layout/file_viewer_top_bar_fragment.xml delete mode 100644 app/src/main/res/layout/generic_list_header.xml delete mode 100644 app/src/main/res/layout/history_conf_detail_fragment.xml delete mode 100644 app/src/main/res/layout/history_detail_cell.xml delete mode 100644 app/src/main/res/layout/history_detail_fragment.xml delete mode 100644 app/src/main/res/layout/history_list_cell.xml delete mode 100644 app/src/main/res/layout/history_master_fragment.xml delete mode 100644 app/src/main/res/layout/imdn_list_header.xml delete mode 100644 app/src/main/res/layout/list_edit_top_bar_fragment.xml delete mode 100644 app/src/main/res/layout/main_activity.xml delete mode 100644 app/src/main/res/layout/main_activity_content.xml delete mode 100644 app/src/main/res/layout/numpad.xml delete mode 100644 app/src/main/res/layout/recording_list_cell.xml delete mode 100644 app/src/main/res/layout/recordings_fragment.xml delete mode 100644 app/src/main/res/layout/settings_account_cell.xml delete mode 100644 app/src/main/res/layout/settings_account_fragment.xml delete mode 100644 app/src/main/res/layout/settings_advanced_fragment.xml delete mode 100644 app/src/main/res/layout/settings_audio_fragment.xml delete mode 100644 app/src/main/res/layout/settings_call_fragment.xml delete mode 100644 app/src/main/res/layout/settings_chat_fragment.xml delete mode 100644 app/src/main/res/layout/settings_conferences_fragment.xml delete mode 100644 app/src/main/res/layout/settings_contacts_fragment.xml delete mode 100644 app/src/main/res/layout/settings_fragment.xml delete mode 100644 app/src/main/res/layout/settings_ldap_cell.xml delete mode 100644 app/src/main/res/layout/settings_ldap_fragment.xml delete mode 100644 app/src/main/res/layout/settings_network_fragment.xml delete mode 100644 app/src/main/res/layout/settings_tunnel_fragment.xml delete mode 100644 app/src/main/res/layout/settings_video_fragment.xml delete mode 100644 app/src/main/res/layout/settings_widget_basic.xml delete mode 100644 app/src/main/res/layout/settings_widget_list.xml delete mode 100644 app/src/main/res/layout/settings_widget_switch.xml delete mode 100644 app/src/main/res/layout/settings_widget_switch_and_text.xml delete mode 100644 app/src/main/res/layout/settings_widget_text.xml delete mode 100644 app/src/main/res/layout/side_menu_account_cell.xml delete mode 100644 app/src/main/res/layout/side_menu_fragment.xml delete mode 100644 app/src/main/res/layout/status_fragment.xml delete mode 100644 app/src/main/res/layout/tabs_fragment.xml delete mode 100644 app/src/main/res/layout/video_play_button.xml delete mode 100644 app/src/main/res/layout/voip_activity.xml delete mode 100644 app/src/main/res/layout/voip_buttons.xml delete mode 100644 app/src/main/res/layout/voip_buttons_audio_routes.xml delete mode 100644 app/src/main/res/layout/voip_buttons_extra.xml delete mode 100644 app/src/main/res/layout/voip_buttons_incoming.xml delete mode 100644 app/src/main/res/layout/voip_buttons_outgoing.xml delete mode 100644 app/src/main/res/layout/voip_call_context_menu.xml delete mode 100644 app/src/main/res/layout/voip_call_incoming_fragment.xml delete mode 100644 app/src/main/res/layout/voip_call_outgoing_fragment.xml delete mode 100644 app/src/main/res/layout/voip_call_paused.xml delete mode 100644 app/src/main/res/layout/voip_call_paused_by_remote.xml delete mode 100644 app/src/main/res/layout/voip_call_stat_cell.xml delete mode 100644 app/src/main/res/layout/voip_call_stats.xml delete mode 100644 app/src/main/res/layout/voip_call_stats_cell.xml delete mode 100644 app/src/main/res/layout/voip_calls_cell.xml delete mode 100644 app/src/main/res/layout/voip_calls_list_fragment.xml delete mode 100644 app/src/main/res/layout/voip_chat_fragment.xml delete mode 100644 app/src/main/res/layout/voip_conference_active_speaker.xml delete mode 100644 app/src/main/res/layout/voip_conference_audio_only.xml delete mode 100644 app/src/main/res/layout/voip_conference_call_fragment.xml delete mode 100644 app/src/main/res/layout/voip_conference_creation_pending_wait_layout.xml delete mode 100644 app/src/main/res/layout/voip_conference_grid.xml delete mode 100644 app/src/main/res/layout/voip_conference_incoming_participant_cell.xml delete mode 100644 app/src/main/res/layout/voip_conference_layout_fragment.xml delete mode 100644 app/src/main/res/layout/voip_conference_participant_broadcast_cell.xml delete mode 100644 app/src/main/res/layout/voip_conference_participant_cell.xml delete mode 100644 app/src/main/res/layout/voip_conference_participant_remote_active_speaker_miniature.xml delete mode 100644 app/src/main/res/layout/voip_conference_participant_remote_audio_only.xml delete mode 100644 app/src/main/res/layout/voip_conference_participant_remote_grid.xml delete mode 100644 app/src/main/res/layout/voip_conference_participants_add_fragment.xml delete mode 100644 app/src/main/res/layout/voip_conference_participants_fragment.xml delete mode 100644 app/src/main/res/layout/voip_conference_paused.xml delete mode 100644 app/src/main/res/layout/voip_dialog.xml delete mode 100644 app/src/main/res/layout/voip_dialog_info.xml delete mode 100644 app/src/main/res/layout/voip_numpad.xml delete mode 100644 app/src/main/res/layout/voip_remote_recording.xml delete mode 100644 app/src/main/res/layout/voip_single_call_fragment.xml delete mode 100644 app/src/main/res/layout/voip_status_fragment.xml delete mode 100644 app/src/main/res/layout/wait_layout.xml delete mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml delete mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml delete mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png delete mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png delete mode 100644 app/src/main/res/mipmap-hdpi/linphone_launcher_icon_foreground.png delete mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png delete mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png delete mode 100644 app/src/main/res/mipmap-mdpi/linphone_launcher_icon_foreground.png delete mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png delete mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png delete mode 100644 app/src/main/res/mipmap-xhdpi/linphone_launcher_icon_foreground.png delete mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png delete mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png delete mode 100644 app/src/main/res/mipmap-xxhdpi/linphone_launcher_icon_foreground.png delete mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png delete mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png delete mode 100644 app/src/main/res/mipmap-xxxhdpi/linphone_launcher_icon_foreground.png delete mode 100644 app/src/main/res/navigation/assistant_nav_graph.xml delete mode 100644 app/src/main/res/navigation/call_nav_graph.xml delete mode 100644 app/src/main/res/navigation/chat_nav_graph.xml delete mode 100644 app/src/main/res/navigation/contacts_nav_graph.xml delete mode 100644 app/src/main/res/navigation/history_nav_graph.xml delete mode 100644 app/src/main/res/navigation/main_nav_graph.xml delete mode 100644 app/src/main/res/navigation/settings_nav_graph.xml delete mode 100644 app/src/main/res/values-bg/strings.xml delete mode 100644 app/src/main/res/values-bn-rBD/strings.xml delete mode 100644 app/src/main/res/values-cs/strings.xml delete mode 100644 app/src/main/res/values-de/strings.xml delete mode 100644 app/src/main/res/values-el/strings.xml delete mode 100644 app/src/main/res/values-es/strings.xml delete mode 100644 app/src/main/res/values-fi/strings.xml delete mode 100644 app/src/main/res/values-fr-rCA/strings.xml delete mode 100644 app/src/main/res/values-fr/strings.xml delete mode 100644 app/src/main/res/values-he/strings.xml delete mode 100644 app/src/main/res/values-hu/strings.xml delete mode 100644 app/src/main/res/values-it/strings.xml delete mode 100644 app/src/main/res/values-ja/strings.xml delete mode 100644 app/src/main/res/values-ka/strings.xml delete mode 100644 app/src/main/res/values-land/dimen.xml delete mode 100644 app/src/main/res/values-lv/strings.xml delete mode 100644 app/src/main/res/values-night-v27/styles.xml delete mode 100644 app/src/main/res/values-night/styles.xml delete mode 100644 app/src/main/res/values-nl/strings.xml delete mode 100644 app/src/main/res/values-pl/strings.xml delete mode 100644 app/src/main/res/values-pt/strings.xml delete mode 100644 app/src/main/res/values-ro/strings.xml delete mode 100644 app/src/main/res/values-ru-rUA/strings.xml delete mode 100644 app/src/main/res/values-ru/strings.xml delete mode 100644 app/src/main/res/values-sw533dp/bools.xml delete mode 100644 app/src/main/res/values-uk/strings.xml delete mode 100644 app/src/main/res/values-v27/styles.xml delete mode 100644 app/src/main/res/values-v31/styles.xml delete mode 100644 app/src/main/res/values-w1280dp/dimen.xml delete mode 100644 app/src/main/res/values-w600dp/dimen.xml delete mode 100644 app/src/main/res/values-yo/strings.xml delete mode 100644 app/src/main/res/values-zh-rCN/strings.xml delete mode 100644 app/src/main/res/values-zh-rTW/strings.xml delete mode 100644 app/src/main/res/values/attrs.xml delete mode 100644 app/src/main/res/values/bools.xml delete mode 100644 app/src/main/res/values/colors.xml delete mode 100644 app/src/main/res/values/dimen.xml delete mode 100644 app/src/main/res/values/strings.xml delete mode 100644 app/src/main/res/values/styles.xml delete mode 100644 app/src/main/res/xml/authenticator.xml delete mode 100644 app/src/main/res/xml/automotive_app_desc.xml delete mode 100644 app/src/main/res/xml/locales_config.xml delete mode 100644 app/src/main/res/xml/motion_main_activity_tabs_selector.xml delete mode 100644 app/src/main/res/xml/motion_main_activity_tabs_selector_land.xml delete mode 100644 app/src/main/res/xml/provider_paths.xml delete mode 100644 app/src/main/res/xml/shortcuts.xml delete mode 100644 app/src/main/res/xml/sync_adapter.xml delete mode 100644 build.gradle delete mode 100644 gradle.properties delete mode 100644 gradle/wrapper/gradle-wrapper.jar delete mode 100644 gradle/wrapper/gradle-wrapper.properties delete mode 100755 gradlew delete mode 100644 gradlew.bat delete mode 100644 keystore.properties delete mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 6c785d36f..000000000 --- a/.gitignore +++ /dev/null @@ -1,29 +0,0 @@ -*.orig -*.rej -.DS_Store -.gradle -.idea -.settings -adb.pid -bc-android.keystore -build -*.iml -lint.xml -local.properties -res/.DS_Store -res/raw/lpconfig.xsd -.d -.*clang* -**/*.iml -**/.classpath -**/.project -**/*.kdev4 -**/.vscode -res/value-hi_IN -linphone-sdk-android/*.aar -app/debug -app/release -app/releaseAppBundle -app/releaseWithCrashlytics -keystore.properties -app/src/main/res/xml/contacts.xml diff --git a/.gitlab-ci-files/job-android.yml b/.gitlab-ci-files/job-android.yml deleted file mode 100644 index a66ce9e9f..000000000 --- a/.gitlab-ci-files/job-android.yml +++ /dev/null @@ -1,35 +0,0 @@ -job-android: - - stage: build - tags: [ "docker-android" ] - image: gitlab.linphone.org:4567/bc/public/linphone-android/bc-dev-android:20230414_bullseye_jdk_17_cleaned - - before_script: - - if ! [ -z ${SCP_PRIVATE_KEY+x} ]; then eval $(ssh-agent -s); fi - - if ! [ -z ${SCP_PRIVATE_KEY+x} ]; then echo "$SCP_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null; fi - - if ! [ -z ${ANDROID_SETTINGS_GRADLE+x} ]; then echo "$ANDROID_SETTINGS_GRADLE" > settings.gradle; fi - - git config --global --add safe.directory /builds/BC/public/linphone-android - - script: - - scp -oStrictHostKeyChecking=no $DEPLOY_SERVER:$ANDROID_KEYSTORE_PATH app/ - - scp -oStrictHostKeyChecking=no $DEPLOY_SERVER:$ANDROID_GOOGLE_SERVICES_PATH app/ - - echo storePassword=$ANDROID_KEYSTORE_PASSWORD > keystore.properties - - echo keyPassword=$ANDROID_KEYSTORE_KEY_PASSWORD >> keystore.properties - - echo keyAlias=$ANDROID_KEYSTORE_KEY_ALIAS >> keystore.properties - - echo storeFile=$ANDROID_KEYSTORE_FILE >> keystore.properties - - ./gradlew app:dependencies | grep org.linphone - - ./gradlew assembleDebug - - ./gradlew assembleRelease - - artifacts: - paths: - - ./app/build/outputs/apk/debug/linphone-android-debug-*.apk - - ./app/build/outputs/apk/release/linphone-android-release-*.apk - when: always - expire_in: 1 week - - -.scheduled-job-android: - extends: job-android - only: - - schedules diff --git a/.gitlab-ci-files/job-upload.yml b/.gitlab-ci-files/job-upload.yml deleted file mode 100644 index 31e1d1a50..000000000 --- a/.gitlab-ci-files/job-upload.yml +++ /dev/null @@ -1,12 +0,0 @@ -job-android-upload: - - stage: deploy - tags: [ "deploy" ] - - only: - - schedules - dependencies: - - job-android - - script: - - cd app/build/outputs/apk/ && rsync ./debug/*.apk $DEPLOY_SERVER:$ANDROID_DEPLOY_DIRECTORY \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 0e65baf26..000000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,19 +0,0 @@ -################################################# -# Base configuration -################################################# - - - -################################################# -# Platforms to test -################################################# - - -include: - - '.gitlab-ci-files/job-android.yml' - - '.gitlab-ci-files/job-upload.yml' - - -stages: - - build - - deploy diff --git a/app/.gitignore b/app/.gitignore deleted file mode 100644 index 796b96d1c..000000000 --- a/app/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build diff --git a/app/build.gradle b/app/build.gradle deleted file mode 100644 index 10c35e7a2..000000000 --- a/app/build.gradle +++ /dev/null @@ -1,285 +0,0 @@ -plugins { - id 'com.android.application' - id 'kotlin-android' - id 'kotlin-kapt' - id 'org.jlleitschuh.gradle.ktlint' version '11.3.1' - id 'org.jetbrains.kotlin.android' -} - -def appVersionName = "5.3.0" -def appVersionCode = 52000 - -def packageName = "org.linphone" - -def firebaseAvailable = new File(projectDir.absolutePath +'/google-services.json').exists() - -def crashlyticsAvailable = new File(projectDir.absolutePath +'/google-services.json').exists() && new File(LinphoneSdkBuildDir + '/libs/').exists() && new File(LinphoneSdkBuildDir + '/libs-debug/').exists() - -def extractNativeLibs = false - -if (firebaseAvailable) { - apply plugin: 'com.google.gms.google-services' -} - -def gitBranch = new ByteArrayOutputStream() -task getGitVersion() { - def gitVersion = appVersionName - def gitVersionStream = new ByteArrayOutputStream() - def gitCommitsCount = new ByteArrayOutputStream() - def gitCommitHash = new ByteArrayOutputStream() - - try { - exec { - executable "git" args "describe", "--abbrev=0" - standardOutput = gitVersionStream - } - exec { - executable "git" args "rev-list", gitVersionStream.toString().trim() + "..HEAD", "--count" - standardOutput = gitCommitsCount - } - exec { - executable "git" args "rev-parse", "--short", "HEAD" - standardOutput = gitCommitHash - } - exec { - executable "git" args "name-rev", "--name-only", "HEAD" - standardOutput = gitBranch - } - - if (gitCommitsCount.toString().toInteger() == 0) { - gitVersion = gitVersionStream.toString().trim() - } else { - gitVersion = gitVersionStream.toString().trim() + "." + gitCommitsCount.toString().trim() + "+" + gitCommitHash.toString().trim() - } - println("Git version: " + gitVersion + " (" + appVersionCode + ")") - } catch (ignored) { - println("Git not found, using " + gitVersion + " (" + appVersionCode + ")") - } - project.version = gitVersion -} - -configurations { - customImplementation.extendsFrom implementation -} - -task linphoneSdkSource() { - doLast { - configurations.customImplementation.getIncoming().each { - it.getResolutionResult().allComponents.each { - if (it.id.getDisplayName().contains("linphone-sdk-android")) { - println 'Linphone SDK used is ' + it.moduleVersion.version + ' from ' + it.properties["repositoryName"] - } - } - } - } -} - -project.tasks['preBuild'].dependsOn 'getGitVersion' -project.tasks['preBuild'].dependsOn 'linphoneSdkSource' - -android { - compileOptions { - sourceCompatibility = 17 - targetCompatibility = 17 - } - - compileSdkVersion 34 - defaultConfig { - minSdkVersion 23 - targetSdkVersion 34 - versionCode appVersionCode - versionName "${project.version}" - applicationId packageName - } - - applicationVariants.all { variant -> - variant.outputs.all { - outputFileName = "linphone-android-${variant.buildType.name}-${project.version}.apk" - } - - var enableFirebaseService = "false" - if (firebaseAvailable) { - enableFirebaseService = "true" - } - - // See https://developer.android.com/studio/releases/gradle-plugin#3-6-0-behavior for why extractNativeLibs is set to true in debug flavor - if (variant.buildType.name == "release" || variant.buildType.name == "releaseWithCrashlytics") { - variant.getMergedFlavor().manifestPlaceholders = [linphone_address_mime_type: "vnd.android.cursor.item/vnd." + packageName + ".provider.sip_address", - linphone_file_provider: packageName + ".fileprovider", - appLabel: "@string/app_name", - firebaseServiceEnabled: enableFirebaseService] - } else { - variant.getMergedFlavor().manifestPlaceholders = [linphone_address_mime_type: "vnd.android.cursor.item/vnd." + packageName + ".provider.sip_address", - linphone_file_provider: packageName + ".debug.fileprovider", - appLabel: "@string/app_name_debug", - firebaseServiceEnabled: enableFirebaseService] - extractNativeLibs = true - } - } - - def keystorePropertiesFile = rootProject.file("keystore.properties") - def keystoreProperties = new Properties() - keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) - - signingConfigs { - release { - storeFile file(keystoreProperties['storeFile']) - storePassword keystoreProperties['storePassword'] - keyAlias keystoreProperties['keyAlias'] - keyPassword keystoreProperties['keyPassword'] - } - } - - buildTypes { - release { - minifyEnabled true - signingConfig signingConfigs.release - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - - resValue "string", "linphone_app_branch", gitBranch.toString().trim() - resValue "string", "sync_account_type", packageName + ".sync" - resValue "string", "file_provider", packageName + ".fileprovider" - resValue "string", "linphone_address_mime_type", "vnd.android.cursor.item/vnd." + packageName + ".provider.sip_address" - - if (!firebaseAvailable) { - resValue "string", "gcm_defaultSenderId", "none" - } - - resValue "bool", "crashlytics_enabled", "false" - } - - releaseWithCrashlytics { - initWith release - - resValue "bool", "crashlytics_enabled", crashlyticsAvailable.toString() - - if (crashlyticsAvailable) { - apply plugin: 'com.google.firebase.crashlytics' - - firebaseCrashlytics { - nativeSymbolUploadEnabled true - unstrippedNativeLibsDir file(LinphoneSdkBuildDir + '/libs-debug/').toString() - } - } - } - - debug { - applicationIdSuffix ".debug" - debuggable true - jniDebuggable true - - resValue "string", "linphone_app_branch", gitBranch.toString().trim() - resValue "string", "sync_account_type", packageName + ".sync" - resValue "string", "file_provider", packageName + ".debug.fileprovider" - resValue "string", "linphone_address_mime_type", "vnd.android.cursor.item/vnd." + packageName + ".provider.sip_address" - resValue "bool", "crashlytics_enabled", crashlyticsAvailable.toString() - - if (!firebaseAvailable) { - resValue "string", "gcm_defaultSenderId", "none" - } - - if (crashlyticsAvailable) { - apply plugin: 'com.google.firebase.crashlytics' - - firebaseCrashlytics { - nativeSymbolUploadEnabled false - } - } - } - } - - buildFeatures { - dataBinding = true - } - - namespace 'org.linphone' - packagingOptions { - jniLibs { - useLegacyPackaging extractNativeLibs - } - } -} - -dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'androidx.appcompat:appcompat:1.6.1' - implementation 'androidx.core:core-ktx:1.12.0' - implementation 'androidx.core:core-splashscreen:1.0.1' - implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2' - implementation 'androidx.media:media:1.6.0' - implementation "androidx.security:security-crypto-ktx:1.1.0-alpha06" - implementation "androidx.window:window:1.2.0" - - def emoji_version = "1.4.0" - implementation "androidx.emoji2:emoji2:$emoji_version" - implementation "androidx.emoji2:emoji2-emojipicker:$emoji_version" - - def nav_version = "2.7.5" - implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" - implementation "androidx.navigation:navigation-ui-ktx:$nav_version" - - implementation "androidx.slidingpanelayout:slidingpanelayout:1.2.0" - implementation 'androidx.constraintlayout:constraintlayout:2.1.4' - implementation "androidx.gridlayout:gridlayout:1.0.0" - implementation 'androidx.recyclerview:recyclerview:1.3.2' - implementation 'androidx.drawerlayout:drawerlayout:1.2.0' - - // https://github.com/material-components/material-components-android/blob/master/LICENSE Apache v2.0 - implementation 'com.google.android.material:material:1.10.0' - // https://github.com/google/flexbox-layout/blob/main/LICENSE Apache v2.0 - implementation 'com.google.android.flexbox:flexbox:3.0.0' - - // https://github.com/coil-kt/coil/blob/main/LICENSE.txt Apache v2.0 - def coil_version = "2.4.0" - implementation("io.coil-kt:coil:$coil_version") - implementation("io.coil-kt:coil-gif:$coil_version") - implementation("io.coil-kt:coil-svg:$coil_version") - implementation("io.coil-kt:coil-video:$coil_version") - - // https://github.com/Baseflow/PhotoView/blob/master/LICENSE Apache v2.0 - implementation 'com.github.chrisbanes:PhotoView:2.3.0' - - implementation platform('com.google.firebase:firebase-bom:32.5.0') - if (crashlyticsAvailable) { - debugImplementation 'com.google.firebase:firebase-crashlytics-ndk' - releaseWithCrashlyticsImplementation 'com.google.firebase:firebase-crashlytics-ndk' - releaseCompileOnly 'com.google.firebase:firebase-crashlytics-ndk' - } else { - compileOnly 'com.google.firebase:firebase-crashlytics-ndk' - } - if (firebaseAvailable) { - implementation 'com.google.firebase:firebase-messaging' - } - - implementation 'org.linphone:linphone-sdk-android:5.4+' - - // Only enable leak canary prior to release - // debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10' -} - -task generateContactsXml(type: Copy) { - from 'contacts.xml' - into "src/main/res/xml/" - outputs.upToDateWhen { file('src/main/res/xml/contacts.xml').exists() } - filter { - line -> line - .replaceAll('%%AUTO_GENERATED%%', 'This file has been automatically generated, do not edit or commit !') - .replaceAll('%%PACKAGE_NAME%%', packageName) - - } -} -project.tasks['preBuild'].dependsOn 'generateContactsXml' - -ktlint { - android = true - ignoreFailures = true -} - -project.tasks['preBuild'].dependsOn 'ktlintFormat' - -if (crashlyticsAvailable) { - afterEvaluate { - assembleReleaseWithCrashlytics.finalizedBy(uploadCrashlyticsSymbolFileReleaseWithCrashlytics) - packageReleaseWithCrashlytics.finalizedBy(uploadCrashlyticsSymbolFileReleaseWithCrashlytics) - } -} diff --git a/app/contacts.xml b/app/contacts.xml deleted file mode 100644 index 07ea82c10..000000000 --- a/app/contacts.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - diff --git a/app/google-services.json b/app/google-services.json deleted file mode 100644 index c26aa7702..000000000 --- a/app/google-services.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "project_info": { - "project_number": "929724111839", - "firebase_url": "https://linphone-android-8a563.firebaseio.com", - "project_id": "linphone-android-8a563", - "storage_bucket": "linphone-android-8a563.appspot.com" - }, - "client": [ - { - "client_info": { - "mobilesdk_app_id": "1:929724111839:android:4662ea9a056188c4", - "android_client_info": { - "package_name": "org.linphone" - } - }, - "oauth_client": [ - { - "client_id": "929724111839-co5kffto4j7dets7oolvfv0056cvpfbl.apps.googleusercontent.com", - "client_type": 1, - "android_info": { - "package_name": "org.linphone", - "certificate_hash": "85463a95603f7b6331899b74b85d53d043dcd500" - } - }, - { - "client_id": "929724111839-v5so1tcd65iil7dd7sde8jgii44h8luf.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzaSyCKrwWhkbA7Iy3wpEI8_ZvKOMp5jf6vV6A" - } - ] - }, - { - "client_info": { - "mobilesdk_app_id": "1:929724111839:android:3cf90ee1d2f8fcb6", - "android_client_info": { - "package_name": "org.linphone.debug" - } - }, - "oauth_client": [ - { - "client_id": "929724111839-v5so1tcd65iil7dd7sde8jgii44h8luf.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzaSyCKrwWhkbA7Iy3wpEI8_ZvKOMp5jf6vV6A" - } - ] - } - ], - "configuration_version": "1" -} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro deleted file mode 100644 index e70e74af6..000000000 --- a/app/proguard-rules.pro +++ /dev/null @@ -1,41 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile - --keep public class * extends androidx.fragment.app.Fragment { *; } --dontwarn com.google.errorprone.annotations.Immutable - -# To prevent following errors: -#ERROR: Missing classes detected while running R8. Please add the missing classes or apply additional keep rules that are generated in /builds/BC/public/linphone-android/app/build/outputs/mapping/release/missing_rules.txt. -#ERROR: R8: Missing class org.bouncycastle.jsse.BCSSLParameters (referenced from: void okhttp3.internal.platform.BouncyCastlePlatform.configureTlsExtensions(javax.net.ssl.SSLSocket, java.lang.String, java.util.List) and 1 other context) -#Missing class org.bouncycastle.jsse.BCSSLSocket (referenced from: void okhttp3.internal.platform.BouncyCastlePlatform.configureTlsExtensions(javax.net.ssl.SSLSocket, java.lang.String, java.util.List) and 5 other contexts) -#Missing class org.bouncycastle.jsse.provider.BouncyCastleJsseProvider (referenced from: void okhttp3.internal.platform.BouncyCastlePlatform.()) -#Missing class org.conscrypt.Conscrypt$Version (referenced from: boolean okhttp3.internal.platform.ConscryptPlatform$Companion.atLeastVersion(int, int, int)) -#Missing class org.conscrypt.Conscrypt (referenced from: boolean okhttp3.internal.platform.ConscryptPlatform$Companion.atLeastVersion(int, int, int) and 4 other contexts) -#Missing class org.conscrypt.ConscryptHostnameVerifier (referenced from: okhttp3.internal.platform.ConscryptPlatform$DisabledHostnameVerifier) -#Missing class org.openjsse.javax.net.ssl.SSLParameters (referenced from: void okhttp3.internal.platform.OpenJSSEPlatform.configureTlsExtensions(javax.net.ssl.SSLSocket, java.lang.String, java.util.List)) -#Missing class org.openjsse.javax.net.ssl.SSLSocket (referenced from: void okhttp3.internal.platform.OpenJSSEPlatform.configureTlsExtensions(javax.net.ssl.SSLSocket, java.lang.String, java.util.List) and 1 other context) -#Missing class org.openjsse.net.ssl.OpenJSSE (referenced from: void okhttp3.internal.platform.OpenJSSEPlatform.()) -#> Task :app:lintVitalAnalyzeRelease -#FAILURE: Build failed with an exception. --dontwarn org.conscrypt.** --dontwarn org.bouncycastle.** --dontwarn org.openjsse.** \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml deleted file mode 100644 index c1b106232..000000000 --- a/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,256 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/assets/assistant_default_values b/app/src/main/assets/assistant_default_values deleted file mode 100644 index 5800203a4..000000000 --- a/app/src/main/assets/assistant_default_values +++ /dev/null @@ -1,42 +0,0 @@ - - -
- 0 - 0 - 0 - -1 - - 0 - 0 - 3600 - - - - 1 - - - - - 0 - 0 - 0 - -
-
- - -
-
- 0 -
-
- - MD5 - -1 - 0 - -1 - 128 - 1 - ^[a-zA-Z0-9+_.\-]*$ -
-
diff --git a/app/src/main/assets/assistant_linphone_default_values b/app/src/main/assets/assistant_linphone_default_values deleted file mode 100644 index 7a72cd0c8..000000000 --- a/app/src/main/assets/assistant_linphone_default_values +++ /dev/null @@ -1,42 +0,0 @@ - - -
- 1 - 0 - 1 - 120 - sip:voip-metrics@sip.linphone.org;transport=tls - 1 - 180 - 31536000 - sip:?@sip.linphone.org - <sip:sip.linphone.org;transport=tls> - <sip:sip.linphone.org;transport=tls> - 1 - nat_policy_default_values - sip.linphone.org - sip:conference-factory@sip.linphone.org - sip:videoconference-factory@sip.linphone.org - 1 - 1 - 1 - https://lime.linphone.org/lime-server/lime-server.php -
-
- stun.linphone.org - stun,ice -
-
- 1 -
-
- sip.linphone.org - SHA-256 - -1 - 1 - -1 - 64 - 1 - ^[a-z0-9+_.\-]*$ -
-
diff --git a/app/src/main/assets/linphonerc_default b/app/src/main/assets/linphonerc_default deleted file mode 100644 index bad1a1a76..000000000 --- a/app/src/main/assets/linphonerc_default +++ /dev/null @@ -1,44 +0,0 @@ - -## Start of default rc - -[sip] -contact="Linphone Android" -use_info=0 -use_ipv6=1 -keepalive_period=30000 -sip_port=-1 -sip_tcp_port=-1 -sip_tls_port=-1 -media_encryption=none -update_presence_model_timestamp_before_publish_expires_refresh=1 - -[net] -#Because dynamic bitrate adaption can increase bitrate, we must allow "no limit" -download_bw=0 -upload_bw=0 - -[video] -size=vga - -[app] -tunnel=disabled -auto_start=1 -record_aware=1 - -[tunnel] -host= -port=443 - -[misc] -log_collection_upload_server_url=https://www.linphone.org:444/lft.php -file_transfer_server_url=https://www.linphone.org:444/lft.php -version_check_url_root=https://www.linphone.org/releases -max_calls=10 -history_max_size=100 -conference_layout=1 - -[in-app-purchase] -server_url=https://subscribe.linphone.org:444/inapp.php -purchasable_items_ids=test_account_subscription - -## End of default rc diff --git a/app/src/main/assets/linphonerc_factory b/app/src/main/assets/linphonerc_factory deleted file mode 100644 index 23e6571e4..000000000 --- a/app/src/main/assets/linphonerc_factory +++ /dev/null @@ -1,54 +0,0 @@ - -## Start of factory rc - -# This file shall not contain path referencing package name, in order to be portable when app is renamed. -# Paths to resources must be set from LinphoneManager, after creating LinphoneCore. - -[net] -mtu=1300 -force_ice_disablement=0 - -[rtp] -accept_any_encryption=1 - -[sip] -guess_hostname=1 -register_only_when_network_is_up=1 -auto_net_state_mon=1 -auto_answer_replacing_calls=1 -ping_with_options=0 -use_cpim=1 -zrtp_key_agreements_suites=MS_ZRTP_KEY_AGREEMENT_K255_KYB512 -chat_messages_aggregation_delay=1000 -chat_messages_aggregation=1 - -[sound] -#remove this property for any application that is not Linphone public version itself -ec_calibrator_cool_tones=1 - -[video] -displaytype=MSAndroidTextureDisplay -auto_resize_preview_to_keep_ratio=1 -max_conference_size=vga - -[misc] -enable_basic_to_client_group_chat_room_migration=0 -enable_simple_group_chat_message_state=0 -aggregate_imdn=1 -notify_each_friend_individually_when_presence_received=0 - -[app] -activation_code_length=4 -prefer_basic_chat_room=1 -record_aware=1 - -[account_creator] -backend=1 -# 1 means FlexiAPI, 0 is XMLRPC -url=https://subscribe.linphone.org/api/ -# replace above URL by https://staging-subscribe.linphone.org/api/ for testing - -[lime] -lime_update_threshold=86400 - -## End of factory rc diff --git a/app/src/main/java/org/linphone/LinphoneApplication.kt b/app/src/main/java/org/linphone/LinphoneApplication.kt deleted file mode 100644 index 2a08ce958..000000000 --- a/app/src/main/java/org/linphone/LinphoneApplication.kt +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone - -import android.annotation.SuppressLint -import android.app.Application -import android.content.Context -import coil.ImageLoader -import coil.ImageLoaderFactory -import coil.decode.GifDecoder -import coil.decode.ImageDecoderDecoder -import coil.decode.SvgDecoder -import coil.decode.VideoFrameDecoder -import coil.disk.DiskCache -import coil.memory.MemoryCache -import org.linphone.core.* -import org.linphone.core.tools.Log -import org.linphone.mediastream.Version - -class LinphoneApplication : Application(), ImageLoaderFactory { - companion object { - @SuppressLint("StaticFieldLeak") - lateinit var corePreferences: CorePreferences - - @SuppressLint("StaticFieldLeak") - lateinit var coreContext: CoreContext - - private fun createConfig(context: Context) { - if (::corePreferences.isInitialized) { - return - } - - Factory.instance().setLogCollectionPath(context.filesDir.absolutePath) - Factory.instance().enableLogCollection(LogCollectionState.Enabled) - - // For VFS - Factory.instance().setCacheDir(context.cacheDir.absolutePath) - - corePreferences = CorePreferences(context) - corePreferences.copyAssetsFromPackage() - - if (corePreferences.vfsEnabled) { - CoreContext.activateVFS() - } - - val config = Factory.instance().createConfigWithFactory( - corePreferences.configPath, - corePreferences.factoryConfigPath - ) - corePreferences.config = config - - val appName = context.getString(R.string.app_name) - Factory.instance().setLoggerDomain(appName) - Factory.instance().enableLogcatLogs(corePreferences.logcatLogsOutput) - if (corePreferences.debugLogs) { - Factory.instance().loggingService.setLogLevel(LogLevel.Message) - } - - Log.i("[Application] Core config & preferences created") - } - - fun ensureCoreExists( - context: Context, - pushReceived: Boolean = false, - service: CoreService? = null, - useAutoStartDescription: Boolean = false, - skipCoreStart: Boolean = false - ): Boolean { - if (::coreContext.isInitialized && !coreContext.stopped) { - Log.d("[Application] Skipping Core creation (push received? $pushReceived)") - return false - } - - Log.i( - "[Application] Core context is being created ${if (pushReceived) "from push" else ""}" - ) - coreContext = CoreContext( - context, - corePreferences.config, - service, - useAutoStartDescription - ) - if (!skipCoreStart) { - coreContext.start() - } - return true - } - - fun contextExists(): Boolean { - return ::coreContext.isInitialized - } - } - - override fun onCreate() { - super.onCreate() - val appName = getString(R.string.app_name) - android.util.Log.i("[$appName]", "Application is being created") - createConfig(applicationContext) - Log.i("[Application] Created") - } - - override fun newImageLoader(): ImageLoader { - return ImageLoader.Builder(this) - .components { - add(VideoFrameDecoder.Factory()) - add(SvgDecoder.Factory()) - if (Version.sdkAboveOrEqual(Version.API28_PIE_90)) { - add(ImageDecoderDecoder.Factory()) - } else { - add(GifDecoder.Factory()) - } - } - .memoryCache { - MemoryCache.Builder(this) - .maxSizePercent(0.25) - .build() - } - .diskCache { - DiskCache.Builder() - .directory(cacheDir.resolve("image_cache")) - .maxSizePercent(0.02) - .build() - } - .build() - } -} diff --git a/app/src/main/java/org/linphone/activities/GenericActivity.kt b/app/src/main/java/org/linphone/activities/GenericActivity.kt deleted file mode 100644 index 4498c8696..000000000 --- a/app/src/main/java/org/linphone/activities/GenericActivity.kt +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities - -import android.annotation.SuppressLint -import android.content.pm.ActivityInfo -import android.content.res.Configuration -import android.os.Bundle -import android.util.DisplayMetrics -import android.view.Display -import androidx.appcompat.app.AppCompatActivity -import androidx.appcompat.app.AppCompatDelegate -import androidx.lifecycle.lifecycleScope -import androidx.navigation.ActivityNavigator -import androidx.window.layout.FoldingFeature -import androidx.window.layout.WindowInfoTracker -import androidx.window.layout.WindowLayoutInfo -import java.util.* -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.LinphoneApplication.Companion.ensureCoreExists -import org.linphone.R -import org.linphone.core.tools.Log - -abstract class GenericActivity : AppCompatActivity() { - private var timer: Timer? = null - private var _isDestructionPending = false - val isDestructionPending: Boolean - get() = _isDestructionPending - - open fun onLayoutChanges(foldingFeature: FoldingFeature?) { } - - @SuppressLint("SourceLockedOrientationActivity") - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - Log.i("[Generic Activity] Ensuring Core exists") - ensureCoreExists(applicationContext) - - lifecycleScope.launch(Dispatchers.Main) { - WindowInfoTracker - .getOrCreate(this@GenericActivity) - .windowLayoutInfo(this@GenericActivity) - .collect { newLayoutInfo -> - updateCurrentLayout(newLayoutInfo) - } - } - - requestedOrientation = if (corePreferences.forcePortrait) { - ActivityInfo.SCREEN_ORIENTATION_PORTRAIT - } else { - ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED - } - - _isDestructionPending = false - val nightMode = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK - val darkModeEnabled = corePreferences.darkMode - when (nightMode) { - Configuration.UI_MODE_NIGHT_NO, Configuration.UI_MODE_NIGHT_UNDEFINED -> { - if (darkModeEnabled == 1) { - // Force dark mode - Log.w("[Generic Activity] Forcing night mode") - AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) - _isDestructionPending = true - } - } - Configuration.UI_MODE_NIGHT_YES -> { - if (darkModeEnabled == 0) { - // Force light mode - Log.w("[Generic Activity] Forcing day mode") - AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) - _isDestructionPending = true - } - } - } - - updateScreenSize() - } - - override fun onResume() { - super.onResume() - - // Remove service notification if it has been started by device boot - coreContext.notificationsManager.stopForegroundNotificationIfPossible() - } - - override fun finish() { - super.finish() - ActivityNavigator.applyPopAnimationsToPendingTransition(this) - } - - fun isTablet(): Boolean { - return resources.getBoolean(R.bool.isTablet) - } - - private fun updateScreenSize() { - val metrics = DisplayMetrics() - val display: Display = windowManager.defaultDisplay - display.getRealMetrics(metrics) - val screenWidth = metrics.widthPixels.toFloat() - val screenHeight = metrics.heightPixels.toFloat() - coreContext.screenWidth = screenWidth - coreContext.screenHeight = screenHeight - } - - private fun updateCurrentLayout(newLayoutInfo: WindowLayoutInfo) { - if (newLayoutInfo.displayFeatures.isEmpty()) { - onLayoutChanges(null) - } else { - for (feature in newLayoutInfo.displayFeatures) { - val foldingFeature = feature as? FoldingFeature - if (foldingFeature != null) { - onLayoutChanges(foldingFeature) - } - } - } - } -} diff --git a/app/src/main/java/org/linphone/activities/GenericFragment.kt b/app/src/main/java/org/linphone/activities/GenericFragment.kt deleted file mode 100644 index ab8db20ef..000000000 --- a/app/src/main/java/org/linphone/activities/GenericFragment.kt +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.ImageView -import androidx.activity.OnBackPressedCallback -import androidx.core.view.doOnPreDraw -import androidx.databinding.DataBindingUtil -import androidx.databinding.ViewDataBinding -import androidx.fragment.app.Fragment -import androidx.lifecycle.ViewModelProvider -import androidx.navigation.fragment.findNavController -import com.google.android.material.transition.MaterialSharedAxis -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.main.viewmodels.SharedMainViewModel -import org.linphone.core.tools.Log - -abstract class GenericFragment : Fragment() { - companion object { - val emptyFragmentsIds = arrayListOf( - R.id.emptyChatFragment, - R.id.emptyContactFragment, - R.id.emptySettingsFragment, - R.id.emptyCallHistoryFragment - ) - } - - private var _binding: T? = null - protected val binding get() = _binding!! - - protected var useMaterialSharedAxisXForwardAnimation = true - - protected lateinit var sharedViewModel: SharedMainViewModel - - protected fun isSharedViewModelInitialized(): Boolean { - return ::sharedViewModel.isInitialized - } - - protected fun isBindingAvailable(): Boolean { - return _binding != null - } - - private fun getFragmentRealClassName(): String { - return this.javaClass.name - } - - private val onBackPressedCallback = object : OnBackPressedCallback(false) { - override fun handleOnBackPressed() { - try { - val navController = findNavController() - Log.d("[Generic Fragment] ${getFragmentRealClassName()} handleOnBackPressed") - if (!navController.popBackStack()) { - Log.d("[Generic Fragment] ${getFragmentRealClassName()} couldn't pop") - if (!navController.navigateUp()) { - Log.d( - "[Generic Fragment] ${getFragmentRealClassName()} couldn't navigate up" - ) - // Disable this callback & start a new back press event - isEnabled = false - goBack() - } - } - } catch (ise: IllegalStateException) { - Log.e( - "[Generic Fragment] ${getFragmentRealClassName()}.handleOnBackPressed() Can't go back: $ise" - ) - } - } - } - - abstract fun getLayoutId(): Int - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - sharedViewModel = requireActivity().run { - ViewModelProvider(this)[SharedMainViewModel::class.java] - } - - sharedViewModel.isSlidingPaneSlideable.observe(viewLifecycleOwner) { - Log.d( - "[Generic Fragment] ${getFragmentRealClassName()} shared main VM sliding pane has changed" - ) - onBackPressedCallback.isEnabled = backPressedCallBackEnabled() - } - - _binding = DataBindingUtil.inflate(inflater, getLayoutId(), container, false) - return _binding!!.root - } - - override fun onStart() { - super.onStart() - - if (useMaterialSharedAxisXForwardAnimation && corePreferences.enableAnimations) { - enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) - reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) - returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) - exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) - - postponeEnterTransition() - binding.root.doOnPreDraw { startPostponedEnterTransition() } - } - - setupBackPressCallback() - } - - override fun onDestroyView() { - super.onDestroyView() - - onBackPressedCallback.remove() - _binding = null - } - - protected fun goBack() { - try { - requireActivity().onBackPressedDispatcher.onBackPressed() - } catch (ise: IllegalStateException) { - Log.w("[Generic Fragment] ${getFragmentRealClassName()}.goBack() can't go back: $ise") - onBackPressedCallback.handleOnBackPressed() - } - } - - private fun setupBackPressCallback() { - Log.d("[Generic Fragment] ${getFragmentRealClassName()} setupBackPressCallback") - - val backButton = binding.root.findViewById(R.id.back) - if (backButton != null) { - Log.d("[Generic Fragment] ${getFragmentRealClassName()} found back button") - // If popping navigation back stack entry would bring us to an "empty" fragment - // then don't do it if sliding pane layout isn't "flat" - onBackPressedCallback.isEnabled = backPressedCallBackEnabled() - backButton.setOnClickListener { goBack() } - } else { - onBackPressedCallback.isEnabled = false - } - - requireActivity().onBackPressedDispatcher.addCallback( - viewLifecycleOwner, - onBackPressedCallback - ) - } - - private fun backPressedCallBackEnabled(): Boolean { - // This allow to navigate a SlidingPane child nav graph. - // This only concerns fragments for which the nav graph is inside a SlidingPane layout. - // In our case it's all graphs except the main one. - if (findNavController().graph.id == R.id.main_nav_graph_xml) return false - - val isSlidingPaneFlat = sharedViewModel.isSlidingPaneSlideable.value == false - Log.d( - "[Generic Fragment] ${getFragmentRealClassName()} isSlidingPaneFlat ? $isSlidingPaneFlat" - ) - val isPreviousFragmentEmpty = findNavController().previousBackStackEntry?.destination?.id in emptyFragmentsIds - Log.d( - "[Generic Fragment] ${getFragmentRealClassName()} isPreviousFragmentEmpty ? $isPreviousFragmentEmpty" - ) - val popBackStack = isSlidingPaneFlat || !isPreviousFragmentEmpty - Log.d("[Generic Fragment] ${getFragmentRealClassName()} popBackStack ? $popBackStack") - return popBackStack - } -} diff --git a/app/src/main/java/org/linphone/activities/Navigation.kt b/app/src/main/java/org/linphone/activities/Navigation.kt deleted file mode 100644 index c03d854d1..000000000 --- a/app/src/main/java/org/linphone/activities/Navigation.kt +++ /dev/null @@ -1,1222 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities - -import android.net.Uri -import android.os.Bundle -import androidx.core.os.bundleOf -import androidx.fragment.app.Fragment -import androidx.navigation.NavController -import androidx.navigation.NavOptions -import androidx.navigation.findNavController -import androidx.navigation.fragment.NavHostFragment -import androidx.navigation.fragment.findNavController -import androidx.slidingpanelayout.widget.SlidingPaneLayout -import org.linphone.R -import org.linphone.activities.assistant.fragments.* -import org.linphone.activities.main.MainActivity -import org.linphone.activities.main.chat.fragments.ChatRoomCreationFragment -import org.linphone.activities.main.chat.fragments.DetailChatRoomFragment -import org.linphone.activities.main.chat.fragments.GroupInfoFragment -import org.linphone.activities.main.chat.fragments.MasterChatRoomsFragment -import org.linphone.activities.main.conference.fragments.* -import org.linphone.activities.main.contact.fragments.ContactEditorFragment -import org.linphone.activities.main.contact.fragments.DetailContactFragment -import org.linphone.activities.main.contact.fragments.MasterContactsFragment -import org.linphone.activities.main.dialer.fragments.DialerFragment -import org.linphone.activities.main.fragments.TabsFragment -import org.linphone.activities.main.history.fragments.DetailCallLogFragment -import org.linphone.activities.main.history.fragments.MasterCallLogsFragment -import org.linphone.activities.main.settings.fragments.* -import org.linphone.activities.main.sidemenu.fragments.SideMenuFragment -import org.linphone.activities.voip.CallActivity -import org.linphone.activities.voip.fragments.* -import org.linphone.core.tools.Log - -internal fun Fragment.findMasterNavController(): NavController { - return parentFragment?.parentFragment?.findNavController() ?: findNavController() -} - -fun popupTo( - popUpTo: Int = -1, - popUpInclusive: Boolean = false, - singleTop: Boolean = true -): NavOptions { - val builder = NavOptions.Builder() - builder.setPopUpTo(popUpTo, popUpInclusive).setLaunchSingleTop(singleTop) - return builder.build() -} - -/* Main activity related */ - -internal fun MainActivity.navigateToDialer(args: Bundle? = null) { - findNavController(R.id.nav_host_fragment).navigate( - R.id.action_global_dialerFragment, - args, - popupTo(R.id.dialerFragment, true) - ) -} - -internal fun MainActivity.navigateToChatRooms(args: Bundle? = null) { - findNavController(R.id.nav_host_fragment).navigate( - R.id.action_global_masterChatRoomsFragment, - args, - popupTo(R.id.masterChatRoomsFragment, true) - ) -} - -internal fun MainActivity.navigateToChatRoom(localAddress: String?, peerAddress: String?) { - val deepLink = "linphone-android://chat-room/$localAddress/$peerAddress" - findNavController(R.id.nav_host_fragment).navigate( - Uri.parse(deepLink), - popupTo(R.id.masterChatRoomsFragment, true) - ) -} - -internal fun MainActivity.navigateToContacts() { - findNavController(R.id.nav_host_fragment).navigate( - R.id.action_global_masterContactsFragment, - null, - popupTo(R.id.masterContactsFragment, true) - ) -} - -internal fun MainActivity.navigateToContact(contactId: String?) { - val deepLink = "linphone-android://contact/view/$contactId" - findNavController(R.id.nav_host_fragment).navigate( - Uri.parse(deepLink), - popupTo(R.id.masterContactsFragment, true) - ) -} - -/* Tabs fragment related */ - -internal fun TabsFragment.navigateToCallHistory() { - val action = when (findNavController().currentDestination?.id) { - R.id.masterContactsFragment -> R.id.action_masterContactsFragment_to_masterCallLogsFragment - R.id.dialerFragment -> R.id.action_dialerFragment_to_masterCallLogsFragment - R.id.masterChatRoomsFragment -> R.id.action_masterChatRoomsFragment_to_masterCallLogsFragment - else -> R.id.action_global_masterCallLogsFragment - } - findNavController().navigate( - action, - null, - popupTo(R.id.masterCallLogsFragment, true) - ) -} - -internal fun TabsFragment.navigateToContacts() { - val action = when (findNavController().currentDestination?.id) { - R.id.masterCallLogsFragment -> R.id.action_masterCallLogsFragment_to_masterContactsFragment - R.id.dialerFragment -> R.id.action_dialerFragment_to_masterContactsFragment - R.id.masterChatRoomsFragment -> R.id.action_masterChatRoomsFragment_to_masterContactsFragment - else -> R.id.action_global_masterContactsFragment - } - findNavController().navigate( - action, - null, - popupTo(R.id.masterContactsFragment, true) - ) -} - -internal fun TabsFragment.navigateToDialer() { - val action = when (findNavController().currentDestination?.id) { - R.id.masterCallLogsFragment -> R.id.action_masterCallLogsFragment_to_dialerFragment - R.id.masterContactsFragment -> R.id.action_masterContactsFragment_to_dialerFragment - R.id.masterChatRoomsFragment -> R.id.action_masterChatRoomsFragment_to_dialerFragment - else -> R.id.action_global_dialerFragment - } - findNavController().navigate( - action, - null, - popupTo(R.id.dialerFragment, true) - ) -} - -internal fun TabsFragment.navigateToChatRooms() { - val action = when (findNavController().currentDestination?.id) { - R.id.masterCallLogsFragment -> R.id.action_masterCallLogsFragment_to_masterChatRoomsFragment - R.id.masterContactsFragment -> R.id.action_masterContactsFragment_to_masterChatRoomsFragment - R.id.dialerFragment -> R.id.action_dialerFragment_to_masterChatRoomsFragment - else -> R.id.action_global_masterChatRoomsFragment - } - findNavController().navigate( - action, - null, - popupTo(R.id.masterChatRoomsFragment, true) - ) -} - -/* Dialer related */ - -internal fun DialerFragment.navigateToContacts(uriToAdd: String?) { - if (uriToAdd.isNullOrEmpty()) { - Log.e("[Navigation] SIP URI to add to contact is null or empty!") - return - } - - val deepLink = "linphone-android://contact/new/$uriToAdd" - findNavController().navigate( - Uri.parse(deepLink), - popupTo(R.id.masterContactsFragment, true) - ) -} - -internal fun DialerFragment.navigateToConfigFileViewer() { - val bundle = bundleOf("Secure" to true) - findMasterNavController().navigate( - R.id.action_global_configViewerFragment, - bundle, - popupTo() - ) -} - -internal fun DialerFragment.navigateToConferenceScheduling() { - findMasterNavController().navigate( - R.id.action_global_conferenceSchedulingFragment, - null, - popupTo() - ) -} - -/* Conference scheduling related */ - -internal fun ConferenceSchedulingFragment.navigateToParticipantsList() { - if (findNavController().currentDestination?.id == R.id.conferenceSchedulingFragment) { - findNavController().navigate( - R.id.action_conferenceSchedulingFragment_to_conferenceSchedulingParticipantsListFragment, - null, - popupTo(R.id.conferenceSchedulingParticipantsListFragment, true) - ) - } -} - -internal fun ConferenceSchedulingParticipantsListFragment.navigateToSummary() { - if (findNavController().currentDestination?.id == R.id.conferenceSchedulingParticipantsListFragment) { - findNavController().navigate( - R.id.action_conferenceSchedulingParticipantsListFragment_to_conferenceSchedulingSummaryFragment, - null, - popupTo(R.id.conferenceSchedulingSummaryFragment, true) - ) - } -} - -internal fun ConferenceSchedulingSummaryFragment.navigateToScheduledConferences() { - if (findNavController().currentDestination?.id == R.id.conferenceSchedulingSummaryFragment) { - findNavController().navigate( - R.id.action_global_scheduledConferencesFragment, - null, - popupTo(R.id.dialerFragment, false) - ) - } -} - -internal fun ConferenceSchedulingSummaryFragment.navigateToDialer() { - val bundle = Bundle() - findMasterNavController().navigate( - R.id.action_global_dialerFragment, - bundle, - popupTo(R.id.dialerFragment, false) - ) -} - -internal fun DetailChatRoomFragment.navigateToConferenceWaitingRoom( - address: String, - subject: String? -) { - val bundle = Bundle() - bundle.putString("Address", address) - bundle.putString("Subject", subject) - findMasterNavController().navigate( - R.id.action_global_conferenceWaitingRoomFragment, - bundle, - popupTo(R.id.conferenceWaitingRoomFragment, true) - ) -} - -internal fun ScheduledConferencesFragment.navigateToConferenceWaitingRoom( - address: String, - subject: String? -) { - val bundle = Bundle() - bundle.putString("Address", address) - bundle.putString("Subject", subject) - findMasterNavController().navigate( - R.id.action_global_conferenceWaitingRoomFragment, - bundle, - popupTo(R.id.conferenceWaitingRoomFragment, true) - ) -} - -internal fun ScheduledConferencesFragment.navigateToConferenceScheduling() { - findMasterNavController().navigate( - R.id.action_global_conferenceSchedulingFragment, - null, - popupTo(R.id.conferenceSchedulingFragment, true) - ) -} - -/* Chat related */ - -internal fun MasterChatRoomsFragment.navigateToChatRoom(args: Bundle) { - val navHostFragment = - childFragmentManager.findFragmentById(R.id.chat_nav_container) as NavHostFragment - navHostFragment.navController.navigate( - R.id.action_global_detailChatRoomFragment, - args, - popupTo(R.id.emptyChatFragment, false) - ) -} - -internal fun MasterChatRoomsFragment.navigateToChatRoomCreation( - createGroupChatRoom: Boolean = false, - slidingPane: SlidingPaneLayout -) { - val bundle = bundleOf("createGroup" to createGroupChatRoom) - val navHostFragment = - childFragmentManager.findFragmentById(R.id.chat_nav_container) as NavHostFragment - navHostFragment.navController.navigate( - R.id.action_global_chatRoomCreationFragment, - bundle, - popupTo(R.id.emptyChatFragment, false) - ) - if (!slidingPane.isOpen) slidingPane.openPane() -} - -internal fun MasterChatRoomsFragment.clearDisplayedChatRoom() { - if (findNavController().currentDestination?.id == R.id.masterChatRoomsFragment) { - val navHostFragment = - childFragmentManager.findFragmentById(R.id.chat_nav_container) as NavHostFragment - navHostFragment.navController.navigate( - R.id.action_global_emptyChatFragment, - null, - popupTo(R.id.emptyChatFragment, true) - ) - } -} - -internal fun DetailChatRoomFragment.navigateToContacts(sipUriToAdd: String) { - if (sipUriToAdd.isEmpty()) { - Log.e("[Navigation] SIP URI to add to contact is empty!") - return - } - - val deepLink = "linphone-android://contact/new/$sipUriToAdd" - findMasterNavController().navigate(Uri.parse(deepLink)) -} - -internal fun DetailChatRoomFragment.navigateToNativeContact(id: String) { - val deepLink = "linphone-android://contact/view/$id" - findMasterNavController().navigate(Uri.parse(deepLink)) -} - -internal fun DetailChatRoomFragment.navigateToFriend(address: String) { - val deepLink = "linphone-android://contact/view-friend/$address" - findMasterNavController().navigate(Uri.parse(deepLink)) -} - -internal fun DetailChatRoomFragment.navigateToImdn(args: Bundle?) { - if (findNavController().currentDestination?.id == R.id.detailChatRoomFragment) { - findNavController().navigate( - R.id.action_detailChatRoomFragment_to_imdnFragment, - args, - popupTo() - ) - } -} - -internal fun DetailChatRoomFragment.navigateToDevices() { - if (findNavController().currentDestination?.id == R.id.detailChatRoomFragment) { - findNavController().navigate( - R.id.action_detailChatRoomFragment_to_devicesFragment, - null, - popupTo() - ) - } -} - -internal fun DetailChatRoomFragment.navigateToGroupInfo() { - if (findNavController().currentDestination?.id == R.id.detailChatRoomFragment) { - findNavController().navigate( - R.id.action_detailChatRoomFragment_to_groupInfoFragment, - null, - popupTo(R.id.groupInfoFragment, true) - ) - } -} - -internal fun DetailChatRoomFragment.navigateToEphemeralInfo() { - if (findNavController().currentDestination?.id == R.id.detailChatRoomFragment) { - findNavController().navigate( - R.id.action_detailChatRoomFragment_to_ephemeralFragment, - null, - popupTo() - ) - } -} - -internal fun DetailChatRoomFragment.navigateToTextFileViewer(secure: Boolean) { - val bundle = bundleOf("Secure" to secure) - findMasterNavController().navigate( - R.id.action_global_textViewerFragment, - bundle, - popupTo() - ) -} - -internal fun DetailChatRoomFragment.navigateToPdfFileViewer(secure: Boolean) { - val bundle = bundleOf("Secure" to secure) - findMasterNavController().navigate( - R.id.action_global_pdfViewerFragment, - bundle, - popupTo() - ) -} - -internal fun DetailChatRoomFragment.navigateToImageFileViewer(secure: Boolean) { - val bundle = bundleOf("Secure" to secure) - findMasterNavController().navigate( - R.id.action_global_imageViewerFragment, - bundle, - popupTo() - ) -} - -internal fun DetailChatRoomFragment.navigateToVideoFileViewer(secure: Boolean) { - val bundle = bundleOf("Secure" to secure) - findMasterNavController().navigate( - R.id.action_global_videoViewerFragment, - bundle, - popupTo() - ) -} - -internal fun DetailChatRoomFragment.navigateToAudioFileViewer(secure: Boolean) { - val bundle = bundleOf("Secure" to secure) - findMasterNavController().navigate( - R.id.action_global_audioViewerFragment, - bundle, - popupTo() - ) -} - -internal fun DetailChatRoomFragment.navigateToEmptyChatRoom() { - findNavController().navigate( - R.id.action_global_emptyChatFragment, - null, - popupTo(R.id.detailChatRoomFragment, true) - ) -} - -internal fun DetailChatRoomFragment.navigateToDialer(args: Bundle?) { - findMasterNavController().navigate( - R.id.action_global_dialerFragment, - args, - popupTo(R.id.dialerFragment, true) - ) -} - -internal fun DetailChatRoomFragment.navigateToConferenceScheduling() { - findMasterNavController().navigate( - R.id.action_global_conferenceSchedulingFragment, - null, - popupTo() - ) -} - -internal fun ChatRoomCreationFragment.navigateToGroupInfo() { - if (findNavController().currentDestination?.id == R.id.chatRoomCreationFragment) { - findNavController().navigate( - R.id.action_chatRoomCreationFragment_to_groupInfoFragment, - null, - popupTo(R.id.groupInfoFragment, true) - ) - } -} - -internal fun ChatRoomCreationFragment.navigateToChatRoom(args: Bundle) { - if (findNavController().currentDestination?.id == R.id.chatRoomCreationFragment) { - findNavController().navigate( - R.id.action_chatRoomCreationFragment_to_detailChatRoomFragment, - args, - popupTo(R.id.emptyChatFragment, false) - ) - } -} - -internal fun GroupInfoFragment.navigateToChatRoomCreation(args: Bundle?) { - if (findNavController().currentDestination?.id == R.id.groupInfoFragment) { - findNavController().navigate( - R.id.action_groupInfoFragment_to_chatRoomCreationFragment, - args, - popupTo(R.id.chatRoomCreationFragment, true) - ) - } -} - -internal fun GroupInfoFragment.navigateToChatRoom(args: Bundle?, created: Boolean) { - if (findNavController().currentDestination?.id == R.id.groupInfoFragment) { - val popUpToFragmentId = if (created) { // To remove all creation fragments from back stack - R.id.chatRoomCreationFragment - } else { - R.id.detailChatRoomFragment - } - findNavController().navigate( - R.id.action_groupInfoFragment_to_detailChatRoomFragment, - args, - popupTo(popUpToFragmentId, true) - ) - } -} - -/* Contacts related */ - -internal fun MasterContactsFragment.navigateToContact() { - if (findNavController().currentDestination?.id == R.id.masterContactsFragment) { - val navHostFragment = - childFragmentManager.findFragmentById(R.id.contacts_nav_container) as NavHostFragment - navHostFragment.navController.navigate( - R.id.action_global_detailContactFragment, - null, - popupTo(R.id.emptyContactFragment, false) - ) - } -} - -internal fun MasterContactsFragment.navigateToContactEditor( - sipUriToAdd: String? = null, - slidingPane: SlidingPaneLayout -) { - if (findNavController().currentDestination?.id == R.id.masterContactsFragment) { - val bundle = if (sipUriToAdd != null) bundleOf("SipUri" to sipUriToAdd) else Bundle() - val navHostFragment = - childFragmentManager.findFragmentById(R.id.contacts_nav_container) as NavHostFragment - navHostFragment.navController.navigate( - R.id.action_global_contactEditorFragment, - bundle, - popupTo(R.id.emptyContactFragment, false) - ) - if (!slidingPane.isOpen) slidingPane.openPane() - } -} - -internal fun MasterContactsFragment.clearDisplayedContact() { - if (findNavController().currentDestination?.id == R.id.masterContactsFragment) { - val navHostFragment = - childFragmentManager.findFragmentById(R.id.contacts_nav_container) as NavHostFragment - navHostFragment.navController.navigate( - R.id.action_global_emptyContactFragment, - null, - popupTo(R.id.emptyContactFragment, true) - ) - } -} - -internal fun ContactEditorFragment.navigateToContact(id: String) { - if (findNavController().currentDestination?.id == R.id.contactEditorFragment) { - val bundle = Bundle() - bundle.putString("id", id) - findNavController().navigate( - R.id.action_contactEditorFragment_to_detailContactFragment, - bundle, - popupTo(R.id.contactEditorFragment, true) - ) - } -} - -internal fun DetailContactFragment.navigateToChatRoom(args: Bundle?) { - findMasterNavController().navigate( - R.id.action_global_masterChatRoomsFragment, - args, - popupTo(R.id.masterChatRoomsFragment, true) - ) -} - -internal fun DetailContactFragment.navigateToDialer(args: Bundle?) { - findMasterNavController().navigate( - R.id.action_global_dialerFragment, - args, - popupTo(R.id.dialerFragment, true) - ) -} - -internal fun DetailContactFragment.navigateToContactEditor() { - if (findNavController().currentDestination?.id == R.id.detailContactFragment) { - findNavController().navigate( - R.id.action_detailContactFragment_to_contactEditorFragment, - null, - popupTo(R.id.contactEditorFragment, true) - ) - } -} - -/* History related */ - -internal fun MasterCallLogsFragment.navigateToCallHistory(slidingPane: SlidingPaneLayout) { - if (findNavController().currentDestination?.id == R.id.masterCallLogsFragment) { - val navHostFragment = - childFragmentManager.findFragmentById(R.id.history_nav_container) as NavHostFragment - navHostFragment.navController.navigate( - R.id.action_global_detailCallLogFragment, - null, - popupTo(R.id.emptyCallHistoryFragment, false) - ) - if (!slidingPane.isOpen) slidingPane.openPane() - } -} - -internal fun MasterCallLogsFragment.navigateToConferenceCallHistory(slidingPane: SlidingPaneLayout) { - if (findNavController().currentDestination?.id == R.id.masterCallLogsFragment) { - val navHostFragment = - childFragmentManager.findFragmentById(R.id.history_nav_container) as NavHostFragment - navHostFragment.navController.navigate( - R.id.action_global_detailConferenceCallLogFragment, - null, - popupTo(R.id.emptyCallHistoryFragment, false) - ) - if (!slidingPane.isOpen) slidingPane.openPane() - } -} - -internal fun MasterCallLogsFragment.clearDisplayedCallHistory() { - if (findNavController().currentDestination?.id == R.id.masterCallLogsFragment) { - val navHostFragment = - childFragmentManager.findFragmentById(R.id.history_nav_container) as NavHostFragment - navHostFragment.navController.navigate( - R.id.action_global_emptyFragment, - null, - popupTo(R.id.emptyCallHistoryFragment, true) - ) - } -} - -internal fun MasterCallLogsFragment.navigateToDialer(args: Bundle?) { - findNavController().navigate( - R.id.action_global_dialerFragment, - args, - popupTo(R.id.dialerFragment, true) - ) -} - -internal fun MasterCallLogsFragment.navigateToConferenceWaitingRoom( - address: String, - subject: String? -) { - val bundle = Bundle() - bundle.putString("Address", address) - bundle.putString("Subject", subject) - findMasterNavController().navigate( - R.id.action_global_conferenceWaitingRoomFragment, - bundle, - popupTo(R.id.conferenceWaitingRoomFragment, true) - ) -} - -internal fun DetailCallLogFragment.navigateToContacts(sipUriToAdd: String) { - if (sipUriToAdd.isEmpty()) { - Log.e("[Navigation] SIP URI to add to contact is empty!") - return - } - - val deepLink = "linphone-android://contact/new/$sipUriToAdd" - findMasterNavController().navigate(Uri.parse(deepLink)) -} - -internal fun DetailCallLogFragment.navigateToNativeContact(id: String) { - val deepLink = "linphone-android://contact/view/$id" - findMasterNavController().navigate(Uri.parse(deepLink)) -} - -internal fun DetailCallLogFragment.navigateToFriend(address: String) { - val deepLink = "linphone-android://contact/view-friend/$address" - findMasterNavController().navigate(Uri.parse(deepLink)) -} - -internal fun DetailCallLogFragment.navigateToChatRoom(args: Bundle?) { - if (findNavController().currentDestination?.id == R.id.detailCallLogFragment) { - findMasterNavController().navigate( - R.id.action_global_masterChatRoomsFragment, - args, - popupTo(R.id.masterChatRoomsFragment, true) - ) - } -} - -internal fun DetailCallLogFragment.navigateToDialer(args: Bundle?) { - if (findNavController().currentDestination?.id == R.id.detailCallLogFragment) { - findMasterNavController().navigate( - R.id.action_global_dialerFragment, - args, - popupTo(R.id.dialerFragment, true) - ) - } -} - -/* Settings related */ - -internal fun SettingsFragment.navigateToAccountSettings(identity: String) { - if (findNavController().currentDestination?.id == R.id.settingsFragment) { - val bundle = bundleOf("Identity" to identity) - val navHostFragment = - childFragmentManager.findFragmentById(R.id.settings_nav_container) as NavHostFragment - navHostFragment.navController.navigate( - R.id.action_global_accountSettingsFragment, - bundle, - popupTo(R.id.emptySettingsFragment, false) - ) - } -} - -internal fun SettingsFragment.navigateToTunnelSettings(slidingPane: SlidingPaneLayout) { - if (findNavController().currentDestination?.id == R.id.settingsFragment) { - val navHostFragment = - childFragmentManager.findFragmentById(R.id.settings_nav_container) as NavHostFragment - navHostFragment.navController.navigate( - R.id.action_global_tunnelSettingsFragment, - null, - popupTo(R.id.emptySettingsFragment, false) - ) - if (!slidingPane.isOpen) slidingPane.openPane() - } -} - -internal fun SettingsFragment.navigateToAudioSettings(slidingPane: SlidingPaneLayout) { - if (findNavController().currentDestination?.id == R.id.settingsFragment) { - val navHostFragment = - childFragmentManager.findFragmentById(R.id.settings_nav_container) as NavHostFragment - navHostFragment.navController.navigate( - R.id.action_global_audioSettingsFragment, - null, - popupTo(R.id.emptySettingsFragment, false) - ) - if (!slidingPane.isOpen) slidingPane.openPane() - } -} - -internal fun SettingsFragment.navigateToVideoSettings(slidingPane: SlidingPaneLayout) { - if (findNavController().currentDestination?.id == R.id.settingsFragment) { - val navHostFragment = - childFragmentManager.findFragmentById(R.id.settings_nav_container) as NavHostFragment - navHostFragment.navController.navigate( - R.id.action_global_videoSettingsFragment, - null, - popupTo(R.id.emptySettingsFragment, false) - ) - if (!slidingPane.isOpen) slidingPane.openPane() - } -} - -internal fun SettingsFragment.navigateToCallSettings(slidingPane: SlidingPaneLayout) { - if (findNavController().currentDestination?.id == R.id.settingsFragment) { - val navHostFragment = - childFragmentManager.findFragmentById(R.id.settings_nav_container) as NavHostFragment - navHostFragment.navController.navigate( - R.id.action_global_callSettingsFragment, - null, - popupTo(R.id.emptySettingsFragment, false) - ) - if (!slidingPane.isOpen) slidingPane.openPane() - } -} - -internal fun SettingsFragment.navigateToChatSettings(slidingPane: SlidingPaneLayout) { - if (findNavController().currentDestination?.id == R.id.settingsFragment) { - val navHostFragment = - childFragmentManager.findFragmentById(R.id.settings_nav_container) as NavHostFragment - navHostFragment.navController.navigate( - R.id.action_global_chatSettingsFragment, - null, - popupTo(R.id.emptySettingsFragment, false) - ) - if (!slidingPane.isOpen) slidingPane.openPane() - } -} - -internal fun SettingsFragment.navigateToNetworkSettings(slidingPane: SlidingPaneLayout) { - if (findNavController().currentDestination?.id == R.id.settingsFragment) { - val navHostFragment = - childFragmentManager.findFragmentById(R.id.settings_nav_container) as NavHostFragment - navHostFragment.navController.navigate( - R.id.action_global_networkSettingsFragment, - null, - popupTo(R.id.emptySettingsFragment, false) - ) - if (!slidingPane.isOpen) slidingPane.openPane() - } -} - -internal fun SettingsFragment.navigateToContactsSettings(slidingPane: SlidingPaneLayout) { - if (findNavController().currentDestination?.id == R.id.settingsFragment) { - val navHostFragment = - childFragmentManager.findFragmentById(R.id.settings_nav_container) as NavHostFragment - navHostFragment.navController.navigate( - R.id.action_global_contactsSettingsFragment, - null, - popupTo(R.id.emptySettingsFragment, false) - ) - if (!slidingPane.isOpen) slidingPane.openPane() - } -} - -internal fun SettingsFragment.navigateToAdvancedSettings(slidingPane: SlidingPaneLayout) { - if (findNavController().currentDestination?.id == R.id.settingsFragment) { - val navHostFragment = - childFragmentManager.findFragmentById(R.id.settings_nav_container) as NavHostFragment - navHostFragment.navController.navigate( - R.id.action_global_advancedSettingsFragment, - null, - popupTo(R.id.emptySettingsFragment, false) - ) - if (!slidingPane.isOpen) slidingPane.openPane() - } -} - -internal fun SettingsFragment.navigateToConferencesSettings(slidingPane: SlidingPaneLayout) { - if (findNavController().currentDestination?.id == R.id.settingsFragment) { - val navHostFragment = - childFragmentManager.findFragmentById(R.id.settings_nav_container) as NavHostFragment - navHostFragment.navController.navigate( - R.id.action_global_conferencesSettingsFragment, - null, - popupTo(R.id.emptySettingsFragment, false) - ) - if (!slidingPane.isOpen) slidingPane.openPane() - } -} - -internal fun AccountSettingsFragment.navigateToPhoneLinking(args: Bundle?) { - if (findNavController().currentDestination?.id == R.id.accountSettingsFragment) { - findNavController().navigate( - R.id.action_accountSettingsFragment_to_phoneAccountLinkingFragment, - args, - popupTo() - ) - } -} - -internal fun PhoneAccountLinkingFragment.navigateToPhoneAccountValidation(args: Bundle?) { - if (findNavController().currentDestination?.id == R.id.phoneAccountLinkingFragment) { - findNavController().navigate( - R.id.action_phoneAccountLinkingFragment_to_phoneAccountValidationFragment, - args, - popupTo() - ) - } -} - -internal fun navigateToEmptySetting(navController: NavController) { - navController.navigate( - R.id.action_global_emptySettingsFragment, - null, - popupTo(R.id.emptySettingsFragment, true) - ) -} - -internal fun ContactsSettingsFragment.navigateToLdapSettings(configIndex: Int) { - if (findNavController().currentDestination?.id == R.id.contactsSettingsFragment) { - val bundle = bundleOf("LdapConfigIndex" to configIndex) - findNavController().navigate( - R.id.action_contactsSettingsFragment_to_ldapSettingsFragment, - bundle, - popupTo() - ) - } -} - -/* Side menu related */ - -internal fun SideMenuFragment.navigateToAccountSettings(identity: String) { - val deepLink = "linphone-android://settings/$identity" - try { - findNavController().navigate(Uri.parse(deepLink)) - } catch (iae: IllegalArgumentException) { - Log.e("[Navigation] Failed to navigate to deeplink [$deepLink]") - } -} - -internal fun SideMenuFragment.navigateToSettings() { - findNavController().navigate( - R.id.action_global_settingsFragment, - null, - popupTo(R.id.settingsFragment, true) - ) -} - -internal fun SideMenuFragment.navigateToAbout() { - findNavController().navigate( - R.id.action_global_aboutFragment, - null, - popupTo(R.id.aboutFragment, true) - ) -} - -internal fun SideMenuFragment.navigateToRecordings() { - findNavController().navigate( - R.id.action_global_recordingsFragment, - null, - popupTo(R.id.recordingsFragment, true) - ) -} - -internal fun SideMenuFragment.navigateToScheduledConferences() { - findNavController().navigate( - R.id.action_global_scheduledConferencesFragment, - null, - popupTo(R.id.scheduledConferencesFragment, true) - ) -} - -/* Calls related */ - -internal fun CallActivity.navigateToActiveCall() { - if (findNavController(R.id.nav_host_fragment).currentDestination?.id != R.id.singleCallFragment) { - findNavController(R.id.nav_host_fragment).navigate( - R.id.action_global_singleCallFragment, - null, - popupTo(R.id.conferenceCallFragment, true) - ) - } -} - -internal fun CallActivity.navigateToConferenceCall() { - if (findNavController(R.id.nav_host_fragment).currentDestination?.id != R.id.conferenceCallFragment) { - findNavController(R.id.nav_host_fragment).navigate( - R.id.action_global_conferenceCallFragment, - null, - popupTo(R.id.singleCallFragment, true) - ) - } -} - -internal fun CallActivity.navigateToOutgoingCall() { - findNavController(R.id.nav_host_fragment).navigate( - R.id.action_global_outgoingCallFragment, - null, - popupTo(R.id.singleCallFragment, true) - ) -} - -internal fun CallActivity.navigateToIncomingCall(earlyMediaVideoEnabled: Boolean) { - val args = Bundle() - args.putBoolean("earlyMediaVideo", earlyMediaVideoEnabled) - findNavController(R.id.nav_host_fragment).navigate( - R.id.action_global_incomingCallFragment, - args, - popupTo(R.id.singleCallFragment, true) - ) -} - -internal fun OutgoingCallFragment.navigateToActiveCall() { - findNavController().navigate( - R.id.action_global_singleCallFragment, - null, - popupTo(R.id.outgoingCallFragment, true) - ) -} - -internal fun IncomingCallFragment.navigateToActiveCall() { - findNavController().navigate( - R.id.action_global_singleCallFragment, - null, - popupTo(R.id.incomingCallFragment, true) - ) -} - -internal fun SingleCallFragment.navigateToCallsList() { - if (findNavController().currentDestination?.id == R.id.singleCallFragment) { - findNavController().navigate( - R.id.action_singleCallFragment_to_callsListFragment, - null, - popupTo() - ) - } -} - -internal fun SingleCallFragment.navigateToConferenceParticipants() { - if (findNavController().currentDestination?.id == R.id.singleCallFragment) { - findNavController().navigate( - R.id.action_singleCallFragment_to_conferenceParticipantsFragment, - null, - popupTo() - ) - } -} - -internal fun SingleCallFragment.navigateToConferenceLayout() { - if (findNavController().currentDestination?.id == R.id.singleCallFragment) { - findNavController().navigate( - R.id.action_singleCallFragment_to_conferenceLayoutFragment, - null, - popupTo() - ) - } -} - -internal fun SingleCallFragment.navigateToIncomingCall() { - if (findNavController().currentDestination?.id == R.id.singleCallFragment) { - findNavController().navigate( - R.id.action_global_incomingCallFragment, - null, - popupTo(R.id.singleCallFragment, true) - ) - } -} - -internal fun SingleCallFragment.navigateToOutgoingCall() { - if (findNavController().currentDestination?.id == R.id.singleCallFragment) { - findNavController().navigate( - R.id.action_global_outgoingCallFragment, - null, - popupTo(R.id.singleCallFragment, true) - ) - } -} - -internal fun ConferenceCallFragment.navigateToCallsList() { - if (findNavController().currentDestination?.id == R.id.conferenceCallFragment) { - findNavController().navigate( - R.id.action_conferenceCallFragment_to_callsListFragment, - null, - popupTo() - ) - } -} - -internal fun ConferenceCallFragment.navigateToConferenceParticipants() { - if (findNavController().currentDestination?.id == R.id.conferenceCallFragment) { - findNavController().navigate( - R.id.action_conferenceCallFragment_to_conferenceParticipantsFragment, - null, - popupTo() - ) - } -} - -internal fun ConferenceCallFragment.navigateToConferenceLayout() { - if (findNavController().currentDestination?.id == R.id.conferenceCallFragment) { - findNavController().navigate( - R.id.action_conferenceCallFragment_to_conferenceLayoutFragment, - null, - popupTo() - ) - } -} - -internal fun ConferenceCallFragment.refreshConferenceFragment() { - if (findNavController().currentDestination?.id == R.id.conferenceCallFragment) { - findNavController().navigate( - R.id.action_global_conferenceCallFragment, - null, - popupTo(R.id.conferenceCallFragment, true) - ) - } -} - -internal fun ConferenceParticipantsFragment.navigateToAddParticipants() { - if (findNavController().currentDestination?.id == R.id.conferenceParticipantsFragment) { - findNavController().navigate( - R.id.action_conferenceParticipantsFragment_to_conferenceAddParticipantsFragment, - null, - popupTo(R.id.conferenceAddParticipantsFragment, true) - ) - } -} - -/* Assistant related */ - -internal fun WelcomeFragment.navigateToEmailAccountCreation() { - if (findNavController().currentDestination?.id == R.id.welcomeFragment) { - findNavController().navigate( - R.id.action_welcomeFragment_to_emailAccountCreationFragment, - null, - popupTo() - ) - } -} - -internal fun WelcomeFragment.navigateToPhoneAccountCreation() { - if (findNavController().currentDestination?.id == R.id.welcomeFragment) { - findNavController().navigate( - R.id.action_welcomeFragment_to_phoneAccountCreationFragment, - null, - popupTo() - ) - } -} - -internal fun WelcomeFragment.navigateToNoPushWarning() { - if (findNavController().currentDestination?.id == R.id.welcomeFragment) { - findNavController().navigate( - R.id.action_welcomeFragment_to_noPushWarningFragment, - null, - popupTo() - ) - } -} - -internal fun WelcomeFragment.navigateToAccountLogin() { - if (findNavController().currentDestination?.id == R.id.welcomeFragment) { - findNavController().navigate( - R.id.action_welcomeFragment_to_accountLoginFragment, - null, - popupTo() - ) - } -} - -internal fun WelcomeFragment.navigateToGenericLoginWarning() { - if (findNavController().currentDestination?.id == R.id.welcomeFragment) { - findNavController().navigate( - R.id.action_welcomeFragment_to_genericAccountWarningFragment, - null, - popupTo() - ) - } -} - -internal fun WelcomeFragment.navigateToRemoteProvisioning() { - if (findNavController().currentDestination?.id == R.id.welcomeFragment) { - findNavController().navigate( - R.id.action_welcomeFragment_to_remoteProvisioningFragment, - null, - popupTo() - ) - } -} - -internal fun AccountLoginFragment.navigateToEchoCancellerCalibration() { - if (findNavController().currentDestination?.id == R.id.accountLoginFragment) { - findNavController().navigate( - R.id.action_accountLoginFragment_to_echoCancellerCalibrationFragment, - null, - popupTo() - ) - } -} - -internal fun AccountLoginFragment.navigateToPhoneAccountValidation(args: Bundle?) { - if (findNavController().currentDestination?.id == R.id.accountLoginFragment) { - findNavController().navigate( - R.id.action_accountLoginFragment_to_phoneAccountValidationFragment, - args, - popupTo() - ) - } -} - -internal fun GenericAccountWarningFragment.navigateToGenericLogin() { - if (findNavController().currentDestination?.id == R.id.genericAccountWarningFragment) { - findNavController().navigate( - R.id.action_genericAccountWarningFragment_to_genericAccountLoginFragment, - null, - popupTo(R.id.welcomeFragment, popUpInclusive = false) - ) - } -} - -internal fun GenericAccountLoginFragment.navigateToEchoCancellerCalibration() { - if (findNavController().currentDestination?.id == R.id.genericAccountLoginFragment) { - findNavController().navigate( - R.id.action_genericAccountLoginFragment_to_echoCancellerCalibrationFragment, - null, - popupTo() - ) - } -} - -internal fun RemoteProvisioningFragment.navigateToQrCode() { - if (findNavController().currentDestination?.id == R.id.remoteProvisioningFragment) { - findNavController().navigate( - R.id.action_remoteProvisioningFragment_to_qrCodeFragment, - null, - popupTo() - ) - } -} - -internal fun RemoteProvisioningFragment.navigateToEchoCancellerCalibration() { - if (findNavController().currentDestination?.id == R.id.remoteProvisioningFragment) { - findNavController().navigate( - R.id.action_remoteProvisioningFragment_to_echoCancellerCalibrationFragment, - null, - popupTo() - ) - } -} - -internal fun EmailAccountCreationFragment.navigateToEmailAccountValidation() { - if (findNavController().currentDestination?.id == R.id.emailAccountCreationFragment) { - findNavController().navigate( - R.id.action_emailAccountCreationFragment_to_emailAccountValidationFragment, - null, - popupTo() - ) - } -} - -internal fun EmailAccountValidationFragment.navigateToAccountLinking(args: Bundle?) { - if (findNavController().currentDestination?.id == R.id.emailAccountValidationFragment) { - findNavController().navigate( - R.id.action_emailAccountValidationFragment_to_phoneAccountLinkingFragment, - args, - popupTo() - ) - } -} - -internal fun PhoneAccountCreationFragment.navigateToPhoneAccountValidation(args: Bundle?) { - if (findNavController().currentDestination?.id == R.id.phoneAccountCreationFragment) { - findNavController().navigate( - R.id.action_phoneAccountCreationFragment_to_phoneAccountValidationFragment, - args, - popupTo() - ) - } -} - -internal fun PhoneAccountValidationFragment.navigateToAccountSettings(args: Bundle?) { - if (findNavController().currentDestination?.id == R.id.phoneAccountValidationFragment) { - findNavController().navigate( - R.id.action_phoneAccountValidationFragment_to_accountSettingsFragment, - args, - popupTo(R.id.accountSettingsFragment, true) - ) - } -} - -internal fun PhoneAccountValidationFragment.navigateToEchoCancellerCalibration() { - if (findNavController().currentDestination?.id == R.id.phoneAccountValidationFragment) { - findNavController().navigate( - R.id.action_phoneAccountValidationFragment_to_echoCancellerCalibrationFragment, - null, - popupTo() - ) - } -} - -internal fun PhoneAccountLinkingFragment.navigateToEchoCancellerCalibration() { - if (findNavController().currentDestination?.id == R.id.phoneAccountLinkingFragment) { - findNavController().navigate( - R.id.action_phoneAccountLinkingFragment_to_echoCancellerCalibrationFragment, - null, - popupTo() - ) - } -} diff --git a/app/src/main/java/org/linphone/activities/ProximitySensorActivity.kt b/app/src/main/java/org/linphone/activities/ProximitySensorActivity.kt deleted file mode 100644 index 605af6df8..000000000 --- a/app/src/main/java/org/linphone/activities/ProximitySensorActivity.kt +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities - -import android.content.Context -import android.os.Bundle -import android.os.PowerManager -import android.os.PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY -import org.linphone.core.tools.Log - -abstract class ProximitySensorActivity : GenericActivity() { - private lateinit var proximityWakeLock: PowerManager.WakeLock - private var proximitySensorEnabled = false - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - val powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager - if (!powerManager.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) { - Log.w( - "[Proximity Sensor Activity] PROXIMITY_SCREEN_OFF_WAKE_LOCK isn't supported on this device!" - ) - } - - proximityWakeLock = powerManager.newWakeLock( - PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, - "$packageName;proximity_sensor" - ) - } - - override fun onPause() { - enableProximitySensor(false) - - super.onPause() - } - - override fun onDestroy() { - enableProximitySensor(false) - - super.onDestroy() - } - - protected fun enableProximitySensor(enable: Boolean) { - if (enable) { - if (!proximitySensorEnabled) { - Log.i( - "[Proximity Sensor Activity] Enabling proximity sensor (turning screen OFF when wake lock is acquired)" - ) - if (!proximityWakeLock.isHeld) { - Log.i("[Proximity Sensor Activity] Acquiring PROXIMITY_SCREEN_OFF_WAKE_LOCK") - proximityWakeLock.acquire() - } - proximitySensorEnabled = true - } - } else { - if (proximitySensorEnabled) { - Log.i( - "[Proximity Sensor Activity] Disabling proximity sensor (turning screen ON when wake lock is released)" - ) - if (proximityWakeLock.isHeld) { - Log.i( - "[Proximity Sensor Activity] Asking to release PROXIMITY_SCREEN_OFF_WAKE_LOCK next time sensor detects no proximity" - ) - proximityWakeLock.release(RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY) - } - proximitySensorEnabled = false - } - } - } -} diff --git a/app/src/main/java/org/linphone/activities/SnackBarActivity.kt b/app/src/main/java/org/linphone/activities/SnackBarActivity.kt deleted file mode 100644 index c9995d68c..000000000 --- a/app/src/main/java/org/linphone/activities/SnackBarActivity.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities - -import androidx.annotation.StringRes - -interface SnackBarActivity { - fun showSnackBar(@StringRes resourceId: Int) - fun showSnackBar(@StringRes resourceId: Int, action: Int, listener: () -> Unit) - fun showSnackBar(message: String) -} diff --git a/app/src/main/java/org/linphone/activities/assistant/AssistantActivity.kt b/app/src/main/java/org/linphone/activities/assistant/AssistantActivity.kt deleted file mode 100644 index 1b1b8321e..000000000 --- a/app/src/main/java/org/linphone/activities/assistant/AssistantActivity.kt +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.assistant - -import android.os.Bundle -import androidx.annotation.StringRes -import androidx.coordinatorlayout.widget.CoordinatorLayout -import androidx.lifecycle.ViewModelProvider -import com.google.android.material.snackbar.Snackbar -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.GenericActivity -import org.linphone.activities.SnackBarActivity -import org.linphone.activities.assistant.viewmodels.SharedAssistantViewModel - -class AssistantActivity : GenericActivity(), SnackBarActivity { - private lateinit var sharedViewModel: SharedAssistantViewModel - private lateinit var coordinator: CoordinatorLayout - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - setContentView(R.layout.assistant_activity) - - sharedViewModel = ViewModelProvider(this)[SharedAssistantViewModel::class.java] - - coordinator = findViewById(R.id.coordinator) - - corePreferences.firstStart = false - } - - override fun showSnackBar(@StringRes resourceId: Int) { - Snackbar.make(coordinator, resourceId, Snackbar.LENGTH_LONG).show() - } - - override fun showSnackBar(@StringRes resourceId: Int, action: Int, listener: () -> Unit) { - Snackbar - .make(findViewById(R.id.coordinator), resourceId, Snackbar.LENGTH_LONG) - .setAction(action) { - listener() - } - .show() - } - - override fun showSnackBar(message: String) { - Snackbar.make(coordinator, message, Snackbar.LENGTH_LONG).show() - } -} diff --git a/app/src/main/java/org/linphone/activities/assistant/adapters/CountryPickerAdapter.kt b/app/src/main/java/org/linphone/activities/assistant/adapters/CountryPickerAdapter.kt deleted file mode 100644 index 47220e0a7..000000000 --- a/app/src/main/java/org/linphone/activities/assistant/adapters/CountryPickerAdapter.kt +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.assistant.adapters - -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.BaseAdapter -import android.widget.Filter -import android.widget.Filterable -import android.widget.TextView -import kotlin.collections.ArrayList -import org.linphone.R -import org.linphone.core.DialPlan -import org.linphone.core.Factory - -class CountryPickerAdapter : BaseAdapter(), Filterable { - private var countries: ArrayList - - init { - val dialPlans = Factory.instance().dialPlans - countries = arrayListOf() - countries.addAll(dialPlans) - } - - override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { - val view: View = convertView ?: LayoutInflater.from(parent.context).inflate( - R.layout.assistant_country_picker_cell, - parent, - false - ) - val dialPlan: DialPlan = countries[position] - - val name = view.findViewById(R.id.country_name) - name.text = dialPlan.country - - val dialCode = view.findViewById(R.id.country_prefix) - dialCode.text = String.format("(%s)", dialPlan.countryCallingCode) - - view.tag = dialPlan - return view - } - - override fun getItem(position: Int): DialPlan { - return countries[position] - } - - override fun getItemId(position: Int): Long { - return position.toLong() - } - - override fun getCount(): Int { - return countries.size - } - - override fun getFilter(): Filter { - return object : Filter() { - override fun performFiltering(constraint: CharSequence): FilterResults { - val filteredCountries = arrayListOf() - for (dialPlan in Factory.instance().dialPlans) { - if (dialPlan.country.contains(constraint, ignoreCase = true) || - dialPlan.countryCallingCode.contains(constraint) - ) { - filteredCountries.add(dialPlan) - } - } - val filterResults = FilterResults() - filterResults.values = filteredCountries - return filterResults - } - - @Suppress("UNCHECKED_CAST") - override fun publishResults( - constraint: CharSequence, - results: FilterResults - ) { - countries = results.values as ArrayList - notifyDataSetChanged() - } - } - } -} diff --git a/app/src/main/java/org/linphone/activities/assistant/fragments/AbstractPhoneFragment.kt b/app/src/main/java/org/linphone/activities/assistant/fragments/AbstractPhoneFragment.kt deleted file mode 100644 index 01c33748a..000000000 --- a/app/src/main/java/org/linphone/activities/assistant/fragments/AbstractPhoneFragment.kt +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ - -package org.linphone.activities.assistant.fragments - -import android.content.pm.PackageManager -import androidx.databinding.ViewDataBinding -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import org.linphone.R -import org.linphone.activities.GenericFragment -import org.linphone.activities.assistant.viewmodels.AbstractPhoneViewModel -import org.linphone.compatibility.Compatibility -import org.linphone.core.tools.Log -import org.linphone.utils.LinphoneUtils -import org.linphone.utils.PermissionHelper -import org.linphone.utils.PhoneNumberUtils - -abstract class AbstractPhoneFragment : GenericFragment() { - companion object { - const val READ_PHONE_STATE_PERMISSION_REQUEST_CODE = 0 - } - - abstract val viewModel: AbstractPhoneViewModel - - @Deprecated("Deprecated in Java") - override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray - ) { - if (requestCode == READ_PHONE_STATE_PERMISSION_REQUEST_CODE) { - if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - Log.i("[Assistant] READ_PHONE_STATE/READ_PHONE_NUMBERS permission granted") - updateFromDeviceInfo() - } else { - Log.w("[Assistant] READ_PHONE_STATE/READ_PHONE_NUMBERS permission denied") - } - } - } - - protected fun checkPermissions() { - // Only ask for phone number related permission on devices that have TELEPHONY feature && if push notifications are available - if (requireContext().packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) && LinphoneUtils.isPushNotificationAvailable()) { - if (!PermissionHelper.get().hasReadPhoneStateOrPhoneNumbersPermission()) { - Log.i("[Assistant] Asking for READ_PHONE_STATE/READ_PHONE_NUMBERS permission") - Compatibility.requestReadPhoneStateOrNumbersPermission( - this, - READ_PHONE_STATE_PERMISSION_REQUEST_CODE - ) - } else { - updateFromDeviceInfo() - } - } - } - - private fun updateFromDeviceInfo() { - val phoneNumber = PhoneNumberUtils.getDevicePhoneNumber(requireContext()) - val dialPlan = PhoneNumberUtils.getDialPlanForCurrentCountry(requireContext()) - viewModel.updateFromPhoneNumberAndOrDialPlan(phoneNumber, dialPlan) - } - - protected fun showPhoneNumberInfoDialog() { - MaterialAlertDialogBuilder(requireContext()) - .setTitle(getString(R.string.assistant_phone_number_info_title)) - .setMessage( - getString(R.string.assistant_phone_number_link_info_content) + "\n" + - getString( - R.string.assistant_phone_number_link_info_content_already_account - ) - ) - .setNegativeButton(getString(R.string.dialog_ok), null) - .show() - } -} diff --git a/app/src/main/java/org/linphone/activities/assistant/fragments/AccountLoginFragment.kt b/app/src/main/java/org/linphone/activities/assistant/fragments/AccountLoginFragment.kt deleted file mode 100644 index e9a35b94b..000000000 --- a/app/src/main/java/org/linphone/activities/assistant/fragments/AccountLoginFragment.kt +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.assistant.fragments - -import android.app.Dialog -import android.content.Intent -import android.net.Uri -import android.os.Bundle -import android.view.View -import androidx.lifecycle.ViewModelProvider -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R -import org.linphone.activities.assistant.AssistantActivity -import org.linphone.activities.assistant.viewmodels.AccountLoginViewModel -import org.linphone.activities.assistant.viewmodels.AccountLoginViewModelFactory -import org.linphone.activities.assistant.viewmodels.SharedAssistantViewModel -import org.linphone.activities.main.viewmodels.DialogViewModel -import org.linphone.activities.navigateToEchoCancellerCalibration -import org.linphone.activities.navigateToPhoneAccountValidation -import org.linphone.databinding.AssistantAccountLoginFragmentBinding -import org.linphone.utils.DialogUtils - -class AccountLoginFragment : AbstractPhoneFragment() { - override lateinit var viewModel: AccountLoginViewModel - private lateinit var sharedAssistantViewModel: SharedAssistantViewModel - - override fun getLayoutId(): Int = R.layout.assistant_account_login_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - sharedAssistantViewModel = requireActivity().run { - ViewModelProvider(this)[SharedAssistantViewModel::class.java] - } - - viewModel = ViewModelProvider( - this, - AccountLoginViewModelFactory(sharedAssistantViewModel.getAccountCreator()) - )[AccountLoginViewModel::class.java] - binding.viewModel = viewModel - - binding.setInfoClickListener { - showPhoneNumberInfoDialog() - } - - binding.setSelectCountryClickListener { - val countryPickerFragment = CountryPickerFragment() - countryPickerFragment.listener = viewModel - countryPickerFragment.show(childFragmentManager, "CountryPicker") - } - - binding.setForgotPasswordClickListener { - val intent = Intent(Intent.ACTION_VIEW) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - intent.data = Uri.parse(getString(R.string.assistant_forgotten_password_link)) - startActivity(intent) - } - - viewModel.prefix.observe(viewLifecycleOwner) { internationalPrefix -> - viewModel.getCountryNameFromPrefix(internationalPrefix) - } - - viewModel.goToSmsValidationEvent.observe( - viewLifecycleOwner - ) { - it.consume { - val args = Bundle() - args.putBoolean("IsLogin", true) - args.putString("PhoneNumber", viewModel.accountCreator.phoneNumber) - navigateToPhoneAccountValidation(args) - } - } - - viewModel.leaveAssistantEvent.observe( - viewLifecycleOwner - ) { - it.consume { - coreContext.newAccountConfigured(true) - - if (coreContext.core.isEchoCancellerCalibrationRequired) { - navigateToEchoCancellerCalibration() - } else { - requireActivity().finish() - } - } - } - - viewModel.invalidCredentialsEvent.observe( - viewLifecycleOwner - ) { - it.consume { - val dialogViewModel = - DialogViewModel(getString(R.string.assistant_error_invalid_credentials)) - val dialog: Dialog = DialogUtils.getDialog(requireContext(), dialogViewModel) - - dialogViewModel.showCancelButton { - viewModel.removeInvalidProxyConfig() - dialog.dismiss() - } - - dialogViewModel.showDeleteButton( - { - viewModel.continueEvenIfInvalidCredentials() - dialog.dismiss() - }, - getString(R.string.assistant_continue_even_if_credentials_invalid) - ) - - dialog.show() - } - } - - viewModel.onErrorEvent.observe( - viewLifecycleOwner - ) { - it.consume { message -> - (requireActivity() as AssistantActivity).showSnackBar(message) - } - } - - checkPermissions() - } -} diff --git a/app/src/main/java/org/linphone/activities/assistant/fragments/CountryPickerFragment.kt b/app/src/main/java/org/linphone/activities/assistant/fragments/CountryPickerFragment.kt deleted file mode 100644 index 2e3d4dc72..000000000 --- a/app/src/main/java/org/linphone/activities/assistant/fragments/CountryPickerFragment.kt +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.assistant.fragments - -import android.os.Bundle -import android.text.Editable -import android.text.TextWatcher -import android.view.* -import androidx.fragment.app.DialogFragment -import org.linphone.R -import org.linphone.activities.assistant.adapters.CountryPickerAdapter -import org.linphone.core.DialPlan -import org.linphone.databinding.AssistantCountryPickerFragmentBinding - -class CountryPickerFragment : DialogFragment() { - private var _binding: AssistantCountryPickerFragmentBinding? = null - private val binding get() = _binding!! - private lateinit var adapter: CountryPickerAdapter - - var listener: CountryPickedListener? = null - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, R.style.assistant_country_dialog_style) - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - _binding = AssistantCountryPickerFragmentBinding.inflate(inflater, container, false) - - adapter = CountryPickerAdapter() - binding.countryList.adapter = adapter - - binding.countryList.setOnItemClickListener { _, _, position, _ -> - if (position >= 0 && position < adapter.count) { - val dialPlan = adapter.getItem(position) - listener?.onCountryClicked(dialPlan) - } - dismiss() - } - - binding.searchCountry.addTextChangedListener(object : TextWatcher { - override fun afterTextChanged(s: Editable?) { - adapter.filter.filter(s) - } - - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { } - - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { } - }) - - binding.setCancelClickListener { - dismiss() - } - - return binding.root - } - - interface CountryPickedListener { - fun onCountryClicked(dialPlan: DialPlan) - } -} diff --git a/app/src/main/java/org/linphone/activities/assistant/fragments/EchoCancellerCalibrationFragment.kt b/app/src/main/java/org/linphone/activities/assistant/fragments/EchoCancellerCalibrationFragment.kt deleted file mode 100644 index 9c48cb0d0..000000000 --- a/app/src/main/java/org/linphone/activities/assistant/fragments/EchoCancellerCalibrationFragment.kt +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.assistant.fragments - -import android.content.pm.PackageManager -import android.os.Bundle -import android.view.View -import androidx.lifecycle.ViewModelProvider -import org.linphone.R -import org.linphone.activities.GenericFragment -import org.linphone.activities.assistant.viewmodels.EchoCancellerCalibrationViewModel -import org.linphone.core.tools.Log -import org.linphone.databinding.AssistantEchoCancellerCalibrationFragmentBinding -import org.linphone.utils.PermissionHelper - -class EchoCancellerCalibrationFragment : GenericFragment() { - companion object { - const val RECORD_AUDIO_PERMISSION_REQUEST_CODE = 0 - } - - private lateinit var viewModel: EchoCancellerCalibrationViewModel - - override fun getLayoutId(): Int = R.layout.assistant_echo_canceller_calibration_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - viewModel = ViewModelProvider(this)[EchoCancellerCalibrationViewModel::class.java] - binding.viewModel = viewModel - - viewModel.echoCalibrationTerminated.observe( - viewLifecycleOwner - ) { - it.consume { - requireActivity().finish() - } - } - - if (!PermissionHelper.required(requireContext()).hasRecordAudioPermission()) { - Log.i("[Echo Canceller Calibration] Asking for RECORD_AUDIO permission") - requestPermissions( - arrayOf(android.Manifest.permission.RECORD_AUDIO), - RECORD_AUDIO_PERMISSION_REQUEST_CODE - ) - } else { - viewModel.startEchoCancellerCalibration() - } - } - - @Deprecated("Deprecated in Java") - override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray - ) { - if (requestCode == RECORD_AUDIO_PERMISSION_REQUEST_CODE) { - val granted = - grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED - if (granted) { - Log.i("[Echo Canceller Calibration] RECORD_AUDIO permission granted") - viewModel.startEchoCancellerCalibration() - } else { - Log.w("[Echo Canceller Calibration] RECORD_AUDIO permission denied") - requireActivity().finish() - } - } - } -} diff --git a/app/src/main/java/org/linphone/activities/assistant/fragments/EmailAccountCreationFragment.kt b/app/src/main/java/org/linphone/activities/assistant/fragments/EmailAccountCreationFragment.kt deleted file mode 100644 index 027636460..000000000 --- a/app/src/main/java/org/linphone/activities/assistant/fragments/EmailAccountCreationFragment.kt +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.assistant.fragments - -import android.os.Bundle -import android.view.View -import androidx.lifecycle.ViewModelProvider -import org.linphone.R -import org.linphone.activities.GenericFragment -import org.linphone.activities.assistant.AssistantActivity -import org.linphone.activities.assistant.viewmodels.EmailAccountCreationViewModel -import org.linphone.activities.assistant.viewmodels.EmailAccountCreationViewModelFactory -import org.linphone.activities.assistant.viewmodels.SharedAssistantViewModel -import org.linphone.activities.navigateToEmailAccountValidation -import org.linphone.databinding.AssistantEmailAccountCreationFragmentBinding - -class EmailAccountCreationFragment : GenericFragment() { - private lateinit var sharedAssistantViewModel: SharedAssistantViewModel - private lateinit var viewModel: EmailAccountCreationViewModel - - override fun getLayoutId(): Int = R.layout.assistant_email_account_creation_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - sharedAssistantViewModel = requireActivity().run { - ViewModelProvider(this)[SharedAssistantViewModel::class.java] - } - - viewModel = ViewModelProvider( - this, - EmailAccountCreationViewModelFactory(sharedAssistantViewModel.getAccountCreator()) - )[EmailAccountCreationViewModel::class.java] - binding.viewModel = viewModel - - viewModel.goToEmailValidationEvent.observe( - viewLifecycleOwner - ) { - it.consume { - navigateToEmailAccountValidation() - } - } - - viewModel.onErrorEvent.observe( - viewLifecycleOwner - ) { - it.consume { message -> - (requireActivity() as AssistantActivity).showSnackBar(message) - } - } - } -} diff --git a/app/src/main/java/org/linphone/activities/assistant/fragments/EmailAccountValidationFragment.kt b/app/src/main/java/org/linphone/activities/assistant/fragments/EmailAccountValidationFragment.kt deleted file mode 100644 index 98b5548bd..000000000 --- a/app/src/main/java/org/linphone/activities/assistant/fragments/EmailAccountValidationFragment.kt +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.assistant.fragments - -import android.os.Bundle -import android.view.View -import androidx.lifecycle.ViewModelProvider -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.GenericFragment -import org.linphone.activities.assistant.AssistantActivity -import org.linphone.activities.assistant.viewmodels.* -import org.linphone.activities.navigateToAccountLinking -import org.linphone.databinding.AssistantEmailAccountValidationFragmentBinding - -class EmailAccountValidationFragment : GenericFragment() { - private lateinit var sharedAssistantViewModel: SharedAssistantViewModel - private lateinit var viewModel: EmailAccountValidationViewModel - - override fun getLayoutId(): Int = R.layout.assistant_email_account_validation_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - sharedAssistantViewModel = requireActivity().run { - ViewModelProvider(this)[SharedAssistantViewModel::class.java] - } - - viewModel = ViewModelProvider( - this, - EmailAccountValidationViewModelFactory(sharedAssistantViewModel.getAccountCreator()) - )[EmailAccountValidationViewModel::class.java] - binding.viewModel = viewModel - - viewModel.leaveAssistantEvent.observe( - viewLifecycleOwner - ) { - it.consume { - coreContext.newAccountConfigured(true) - - if (!corePreferences.hideLinkPhoneNumber) { - val args = Bundle() - args.putBoolean("AllowSkip", true) - args.putString("Username", viewModel.accountCreator.username) - args.putString("Password", viewModel.accountCreator.password) - navigateToAccountLinking(args) - } else { - requireActivity().finish() - } - } - } - - viewModel.onErrorEvent.observe( - viewLifecycleOwner - ) { - it.consume { message -> - (requireActivity() as AssistantActivity).showSnackBar(message) - } - } - } -} diff --git a/app/src/main/java/org/linphone/activities/assistant/fragments/GenericAccountLoginFragment.kt b/app/src/main/java/org/linphone/activities/assistant/fragments/GenericAccountLoginFragment.kt deleted file mode 100644 index a93eea348..000000000 --- a/app/src/main/java/org/linphone/activities/assistant/fragments/GenericAccountLoginFragment.kt +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.assistant.fragments - -import android.app.Dialog -import android.os.Bundle -import android.view.View -import androidx.lifecycle.ViewModelProvider -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.GenericFragment -import org.linphone.activities.assistant.AssistantActivity -import org.linphone.activities.assistant.viewmodels.GenericLoginViewModel -import org.linphone.activities.assistant.viewmodels.GenericLoginViewModelFactory -import org.linphone.activities.assistant.viewmodels.SharedAssistantViewModel -import org.linphone.activities.main.viewmodels.DialogViewModel -import org.linphone.activities.navigateToEchoCancellerCalibration -import org.linphone.databinding.AssistantGenericAccountLoginFragmentBinding -import org.linphone.utils.DialogUtils - -class GenericAccountLoginFragment : GenericFragment() { - private lateinit var sharedAssistantViewModel: SharedAssistantViewModel - private lateinit var viewModel: GenericLoginViewModel - - override fun getLayoutId(): Int = R.layout.assistant_generic_account_login_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - sharedAssistantViewModel = requireActivity().run { - ViewModelProvider(this)[SharedAssistantViewModel::class.java] - } - - viewModel = ViewModelProvider( - this, - GenericLoginViewModelFactory(sharedAssistantViewModel.getAccountCreator(true)) - )[GenericLoginViewModel::class.java] - binding.viewModel = viewModel - - viewModel.leaveAssistantEvent.observe( - viewLifecycleOwner - ) { - it.consume { - val isLinphoneAccount = viewModel.domain.value.orEmpty() == corePreferences.defaultDomain - coreContext.newAccountConfigured(isLinphoneAccount) - - if (coreContext.core.isEchoCancellerCalibrationRequired) { - navigateToEchoCancellerCalibration() - } else { - requireActivity().finish() - } - } - } - - viewModel.invalidCredentialsEvent.observe( - viewLifecycleOwner - ) { - it.consume { - val dialogViewModel = - DialogViewModel(getString(R.string.assistant_error_invalid_credentials)) - val dialog: Dialog = DialogUtils.getDialog(requireContext(), dialogViewModel) - - dialogViewModel.showCancelButton { - viewModel.removeInvalidProxyConfig() - dialog.dismiss() - } - - dialogViewModel.showDeleteButton( - { - viewModel.continueEvenIfInvalidCredentials() - dialog.dismiss() - }, - getString(R.string.assistant_continue_even_if_credentials_invalid) - ) - - dialog.show() - } - } - - viewModel.onErrorEvent.observe( - viewLifecycleOwner - ) { - it.consume { message -> - (requireActivity() as AssistantActivity).showSnackBar(message) - } - } - } -} diff --git a/app/src/main/java/org/linphone/activities/assistant/fragments/GenericAccountWarningFragment.kt b/app/src/main/java/org/linphone/activities/assistant/fragments/GenericAccountWarningFragment.kt deleted file mode 100644 index 8c41c0653..000000000 --- a/app/src/main/java/org/linphone/activities/assistant/fragments/GenericAccountWarningFragment.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2010-2022 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.assistant.fragments - -import android.os.Bundle -import android.view.View -import org.linphone.R -import org.linphone.activities.GenericFragment -import org.linphone.activities.navigateToGenericLogin -import org.linphone.databinding.AssistantGenericAccountWarningFragmentBinding - -class GenericAccountWarningFragment : GenericFragment() { - override fun getLayoutId(): Int = R.layout.assistant_generic_account_warning_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - binding.setUnderstoodClickListener { - navigateToGenericLogin() - } - } -} diff --git a/app/src/main/java/org/linphone/activities/assistant/fragments/NoPushWarningFragment.kt b/app/src/main/java/org/linphone/activities/assistant/fragments/NoPushWarningFragment.kt deleted file mode 100644 index 1671474bd..000000000 --- a/app/src/main/java/org/linphone/activities/assistant/fragments/NoPushWarningFragment.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2010-2023 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.assistant.fragments - -import android.os.Bundle -import android.view.View -import org.linphone.R -import org.linphone.activities.GenericFragment -import org.linphone.databinding.AssistantNoPushWarningFragmentBinding - -class NoPushWarningFragment : GenericFragment() { - override fun getLayoutId(): Int = R.layout.assistant_no_push_warning_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - } -} diff --git a/app/src/main/java/org/linphone/activities/assistant/fragments/PhoneAccountCreationFragment.kt b/app/src/main/java/org/linphone/activities/assistant/fragments/PhoneAccountCreationFragment.kt deleted file mode 100644 index 4f285e8b5..000000000 --- a/app/src/main/java/org/linphone/activities/assistant/fragments/PhoneAccountCreationFragment.kt +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.assistant.fragments - -import android.os.Bundle -import android.view.View -import androidx.lifecycle.ViewModelProvider -import org.linphone.R -import org.linphone.activities.assistant.AssistantActivity -import org.linphone.activities.assistant.viewmodels.PhoneAccountCreationViewModel -import org.linphone.activities.assistant.viewmodels.PhoneAccountCreationViewModelFactory -import org.linphone.activities.assistant.viewmodels.SharedAssistantViewModel -import org.linphone.activities.navigateToPhoneAccountValidation -import org.linphone.databinding.AssistantPhoneAccountCreationFragmentBinding - -class PhoneAccountCreationFragment : - AbstractPhoneFragment() { - private lateinit var sharedAssistantViewModel: SharedAssistantViewModel - override lateinit var viewModel: PhoneAccountCreationViewModel - - override fun getLayoutId(): Int = R.layout.assistant_phone_account_creation_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - sharedAssistantViewModel = requireActivity().run { - ViewModelProvider(this)[SharedAssistantViewModel::class.java] - } - - viewModel = ViewModelProvider( - this, - PhoneAccountCreationViewModelFactory(sharedAssistantViewModel.getAccountCreator()) - )[PhoneAccountCreationViewModel::class.java] - binding.viewModel = viewModel - - binding.setInfoClickListener { - showPhoneNumberInfoDialog() - } - - binding.setSelectCountryClickListener { - val countryPickerFragment = CountryPickerFragment() - countryPickerFragment.listener = viewModel - countryPickerFragment.show(childFragmentManager, "CountryPicker") - } - - viewModel.prefix.observe(viewLifecycleOwner) { internationalPrefix -> - viewModel.getCountryNameFromPrefix(internationalPrefix) - } - - viewModel.goToSmsValidationEvent.observe( - viewLifecycleOwner - ) { - it.consume { - val args = Bundle() - args.putBoolean("IsCreation", true) - args.putString("PhoneNumber", viewModel.accountCreator.phoneNumber) - navigateToPhoneAccountValidation(args) - } - } - - viewModel.onErrorEvent.observe( - viewLifecycleOwner - ) { - it.consume { message -> - (requireActivity() as AssistantActivity).showSnackBar(message) - } - } - - checkPermissions() - } -} diff --git a/app/src/main/java/org/linphone/activities/assistant/fragments/PhoneAccountLinkingFragment.kt b/app/src/main/java/org/linphone/activities/assistant/fragments/PhoneAccountLinkingFragment.kt deleted file mode 100644 index d8fd87e22..000000000 --- a/app/src/main/java/org/linphone/activities/assistant/fragments/PhoneAccountLinkingFragment.kt +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.assistant.fragments - -import android.os.Bundle -import android.view.View -import androidx.lifecycle.ViewModelProvider -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R -import org.linphone.activities.SnackBarActivity -import org.linphone.activities.assistant.viewmodels.* -import org.linphone.activities.navigateToEchoCancellerCalibration -import org.linphone.activities.navigateToPhoneAccountValidation -import org.linphone.core.tools.Log -import org.linphone.databinding.AssistantPhoneAccountLinkingFragmentBinding - -class PhoneAccountLinkingFragment : AbstractPhoneFragment() { - private lateinit var sharedAssistantViewModel: SharedAssistantViewModel - override lateinit var viewModel: PhoneAccountLinkingViewModel - - override fun getLayoutId(): Int = R.layout.assistant_phone_account_linking_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - sharedAssistantViewModel = requireActivity().run { - ViewModelProvider(this)[SharedAssistantViewModel::class.java] - } - - val accountCreator = sharedAssistantViewModel.getAccountCreator() - viewModel = ViewModelProvider(this, PhoneAccountLinkingViewModelFactory(accountCreator))[PhoneAccountLinkingViewModel::class.java] - binding.viewModel = viewModel - - val username = arguments?.getString("Username") - Log.i("[Phone Account Linking] username to link is $username") - viewModel.username.value = username - - val password = arguments?.getString("Password") - accountCreator.password = password - - val ha1 = arguments?.getString("HA1") - accountCreator.ha1 = ha1 - - val allowSkip = arguments?.getBoolean("AllowSkip", false) - viewModel.allowSkip.value = allowSkip - - binding.setInfoClickListener { - showPhoneNumberInfoDialog() - } - - binding.setSelectCountryClickListener { - val countryPickerFragment = CountryPickerFragment() - countryPickerFragment.listener = viewModel - countryPickerFragment.show(childFragmentManager, "CountryPicker") - } - - viewModel.prefix.observe(viewLifecycleOwner) { internationalPrefix -> - viewModel.getCountryNameFromPrefix(internationalPrefix) - } - - viewModel.goToSmsValidationEvent.observe( - viewLifecycleOwner - ) { - it.consume { - val args = Bundle() - args.putBoolean("IsLinking", true) - args.putString("PhoneNumber", viewModel.accountCreator.phoneNumber) - navigateToPhoneAccountValidation(args) - } - } - - viewModel.leaveAssistantEvent.observe( - viewLifecycleOwner - ) { - it.consume { - if (coreContext.core.isEchoCancellerCalibrationRequired) { - navigateToEchoCancellerCalibration() - } else { - requireActivity().finish() - } - } - } - - viewModel.onErrorEvent.observe( - viewLifecycleOwner - ) { - it.consume { message -> - (requireActivity() as SnackBarActivity).showSnackBar(message) - } - } - - checkPermissions() - } -} diff --git a/app/src/main/java/org/linphone/activities/assistant/fragments/PhoneAccountValidationFragment.kt b/app/src/main/java/org/linphone/activities/assistant/fragments/PhoneAccountValidationFragment.kt deleted file mode 100644 index edcf18210..000000000 --- a/app/src/main/java/org/linphone/activities/assistant/fragments/PhoneAccountValidationFragment.kt +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.assistant.fragments - -import android.content.ClipboardManager -import android.content.Context.CLIPBOARD_SERVICE -import android.os.Bundle -import android.view.View -import androidx.lifecycle.ViewModelProvider -import androidx.navigation.fragment.findNavController -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R -import org.linphone.activities.GenericFragment -import org.linphone.activities.SnackBarActivity -import org.linphone.activities.assistant.viewmodels.PhoneAccountValidationViewModel -import org.linphone.activities.assistant.viewmodels.PhoneAccountValidationViewModelFactory -import org.linphone.activities.assistant.viewmodels.SharedAssistantViewModel -import org.linphone.activities.navigateToAccountSettings -import org.linphone.activities.navigateToEchoCancellerCalibration -import org.linphone.compatibility.Compatibility -import org.linphone.core.tools.Log -import org.linphone.databinding.AssistantPhoneAccountValidationFragmentBinding - -class PhoneAccountValidationFragment : GenericFragment() { - private lateinit var sharedAssistantViewModel: SharedAssistantViewModel - private lateinit var viewModel: PhoneAccountValidationViewModel - - override fun getLayoutId(): Int = R.layout.assistant_phone_account_validation_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - sharedAssistantViewModel = requireActivity().run { - ViewModelProvider(this)[SharedAssistantViewModel::class.java] - } - - viewModel = ViewModelProvider( - this, - PhoneAccountValidationViewModelFactory(sharedAssistantViewModel.getAccountCreator()) - )[PhoneAccountValidationViewModel::class.java] - binding.viewModel = viewModel - - viewModel.phoneNumber.value = arguments?.getString("PhoneNumber") - viewModel.isLogin.value = arguments?.getBoolean("IsLogin", false) - viewModel.isCreation.value = arguments?.getBoolean("IsCreation", false) - viewModel.isLinking.value = arguments?.getBoolean("IsLinking", false) - - viewModel.leaveAssistantEvent.observe( - viewLifecycleOwner - ) { - it.consume { - when { - viewModel.isLogin.value == true || viewModel.isCreation.value == true -> { - coreContext.newAccountConfigured(true) - - if (coreContext.core.isEchoCancellerCalibrationRequired) { - navigateToEchoCancellerCalibration() - } else { - requireActivity().finish() - } - } - viewModel.isLinking.value == true -> { - if (findNavController().graph.id == R.id.settings_nav_graph_xml) { - val args = Bundle() - args.putString( - "Identity", - "sip:${viewModel.accountCreator.username}@${viewModel.accountCreator.domain}" - ) - navigateToAccountSettings(args) - } else { - requireActivity().finish() - } - } - } - } - } - - viewModel.onErrorEvent.observe( - viewLifecycleOwner - ) { - it.consume { message -> - (requireActivity() as SnackBarActivity).showSnackBar(message) - } - } - - // This won't work starting Android 10 as clipboard access is denied unless app has focus, - // which won't be the case when the SMS arrives unless it is added into clipboard from a notification - val clipboard = requireContext().getSystemService(CLIPBOARD_SERVICE) as ClipboardManager - clipboard.addPrimaryClipChangedListener { - val data = clipboard.primaryClip - if (data != null && data.itemCount > 0) { - val clip = data.getItemAt(0).text.toString() - if (clip.length == 4) { - Log.i( - "[Assistant] [Phone Account Validation] Found 4 digits as primary clip in clipboard, using it and clear it" - ) - viewModel.code.value = clip - Compatibility.clearClipboard(clipboard) - } - } - } - } -} diff --git a/app/src/main/java/org/linphone/activities/assistant/fragments/QrCodeFragment.kt b/app/src/main/java/org/linphone/activities/assistant/fragments/QrCodeFragment.kt deleted file mode 100644 index 0fe20d93e..000000000 --- a/app/src/main/java/org/linphone/activities/assistant/fragments/QrCodeFragment.kt +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.assistant.fragments - -import android.content.pm.PackageManager -import android.os.Bundle -import android.view.View -import androidx.lifecycle.ViewModelProvider -import androidx.navigation.fragment.findNavController -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R -import org.linphone.activities.GenericFragment -import org.linphone.activities.assistant.viewmodels.QrCodeViewModel -import org.linphone.activities.assistant.viewmodels.SharedAssistantViewModel -import org.linphone.core.tools.Log -import org.linphone.databinding.AssistantQrCodeFragmentBinding -import org.linphone.utils.PermissionHelper - -class QrCodeFragment : GenericFragment() { - companion object { - const val CAMERA_PERMISSION_REQUEST_CODE = 0 - } - - private lateinit var sharedAssistantViewModel: SharedAssistantViewModel - private lateinit var viewModel: QrCodeViewModel - - override fun getLayoutId(): Int = R.layout.assistant_qr_code_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - sharedAssistantViewModel = requireActivity().run { - ViewModelProvider(this)[SharedAssistantViewModel::class.java] - } - - viewModel = ViewModelProvider(this)[QrCodeViewModel::class.java] - binding.viewModel = viewModel - - viewModel.qrCodeFoundEvent.observe( - viewLifecycleOwner - ) { - it.consume { url -> - sharedAssistantViewModel.remoteProvisioningUrl.value = url - findNavController().navigateUp() - } - } - viewModel.setBackCamera() - - if (!PermissionHelper.required(requireContext()).hasCameraPermission()) { - Log.i("[QR Code] Asking for CAMERA permission") - requestPermissions( - arrayOf(android.Manifest.permission.CAMERA), - CAMERA_PERMISSION_REQUEST_CODE - ) - } - } - - override fun onResume() { - super.onResume() - - coreContext.core.nativePreviewWindowId = binding.qrCodeCaptureTexture - coreContext.core.isQrcodeVideoPreviewEnabled = true - coreContext.core.isVideoPreviewEnabled = true - } - - override fun onPause() { - coreContext.core.nativePreviewWindowId = null - coreContext.core.isQrcodeVideoPreviewEnabled = false - coreContext.core.isVideoPreviewEnabled = false - - super.onPause() - } - - @Deprecated("Deprecated in Java") - override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray - ) { - if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) { - val granted = - grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED - if (granted) { - Log.i("[QR Code] CAMERA permission granted") - coreContext.core.reloadVideoDevices() - viewModel.setBackCamera() - } else { - Log.w("[QR Code] CAMERA permission denied") - findNavController().navigateUp() - } - } - } -} diff --git a/app/src/main/java/org/linphone/activities/assistant/fragments/RemoteProvisioningFragment.kt b/app/src/main/java/org/linphone/activities/assistant/fragments/RemoteProvisioningFragment.kt deleted file mode 100644 index 5889ba271..000000000 --- a/app/src/main/java/org/linphone/activities/assistant/fragments/RemoteProvisioningFragment.kt +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.assistant.fragments - -import android.os.Bundle -import android.view.View -import androidx.lifecycle.ViewModelProvider -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R -import org.linphone.activities.GenericFragment -import org.linphone.activities.assistant.AssistantActivity -import org.linphone.activities.assistant.viewmodels.RemoteProvisioningViewModel -import org.linphone.activities.assistant.viewmodels.SharedAssistantViewModel -import org.linphone.activities.navigateToEchoCancellerCalibration -import org.linphone.activities.navigateToQrCode -import org.linphone.databinding.AssistantRemoteProvisioningFragmentBinding - -class RemoteProvisioningFragment : GenericFragment() { - private lateinit var sharedAssistantViewModel: SharedAssistantViewModel - private lateinit var viewModel: RemoteProvisioningViewModel - - override fun getLayoutId(): Int = R.layout.assistant_remote_provisioning_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - sharedAssistantViewModel = requireActivity().run { - ViewModelProvider(this)[SharedAssistantViewModel::class.java] - } - - viewModel = ViewModelProvider(this)[RemoteProvisioningViewModel::class.java] - binding.viewModel = viewModel - - binding.setQrCodeClickListener { - navigateToQrCode() - } - - viewModel.fetchSuccessfulEvent.observe( - viewLifecycleOwner - ) { - it.consume { success -> - if (success) { - if (coreContext.core.isEchoCancellerCalibrationRequired) { - navigateToEchoCancellerCalibration() - } else { - requireActivity().finish() - } - } else { - val activity = requireActivity() as AssistantActivity - activity.showSnackBar(R.string.assistant_remote_provisioning_failure) - } - } - } - - viewModel.urlToFetch.value = sharedAssistantViewModel.remoteProvisioningUrl.value ?: coreContext.core.provisioningUri - } - - override fun onDestroy() { - super.onDestroy() - - if (::sharedAssistantViewModel.isInitialized) { - sharedAssistantViewModel.remoteProvisioningUrl.value = null - } - } -} diff --git a/app/src/main/java/org/linphone/activities/assistant/fragments/TopBarFragment.kt b/app/src/main/java/org/linphone/activities/assistant/fragments/TopBarFragment.kt deleted file mode 100644 index e1e8ef074..000000000 --- a/app/src/main/java/org/linphone/activities/assistant/fragments/TopBarFragment.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.assistant.fragments - -import android.os.Bundle -import android.view.View -import org.linphone.R -import org.linphone.activities.GenericFragment -import org.linphone.databinding.AssistantTopBarFragmentBinding - -class TopBarFragment : GenericFragment() { - override fun getLayoutId(): Int = R.layout.assistant_top_bar_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - useMaterialSharedAxisXForwardAnimation = false - } -} diff --git a/app/src/main/java/org/linphone/activities/assistant/fragments/WelcomeFragment.kt b/app/src/main/java/org/linphone/activities/assistant/fragments/WelcomeFragment.kt deleted file mode 100644 index 79c54f64d..000000000 --- a/app/src/main/java/org/linphone/activities/assistant/fragments/WelcomeFragment.kt +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.assistant.fragments - -import android.content.Intent -import android.content.pm.PackageManager -import android.net.Uri -import android.os.Bundle -import android.text.SpannableString -import android.text.Spanned -import android.text.method.LinkMovementMethod -import android.text.style.ClickableSpan -import android.view.View -import androidx.lifecycle.ViewModelProvider -import java.util.UnknownFormatConversionException -import java.util.regex.Pattern -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.* -import org.linphone.activities.assistant.viewmodels.WelcomeViewModel -import org.linphone.activities.navigateToAccountLogin -import org.linphone.activities.navigateToEmailAccountCreation -import org.linphone.activities.navigateToRemoteProvisioning -import org.linphone.core.tools.Log -import org.linphone.databinding.AssistantWelcomeFragmentBinding -import org.linphone.utils.LinphoneUtils - -class WelcomeFragment : GenericFragment() { - private lateinit var viewModel: WelcomeViewModel - - override fun getLayoutId(): Int = R.layout.assistant_welcome_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - viewModel = ViewModelProvider(this)[WelcomeViewModel::class.java] - binding.viewModel = viewModel - - binding.setCreateAccountClickListener { - if (LinphoneUtils.isPushNotificationAvailable()) { - Log.i("[Assistant] Core says push notifications are available") - val deviceHasTelephonyFeature = coreContext.context.packageManager.hasSystemFeature( - PackageManager.FEATURE_TELEPHONY - ) - if (!deviceHasTelephonyFeature) { - Log.i( - "[Assistant] Device doesn't have TELEPHONY feature, showing email based account creation" - ) - navigateToEmailAccountCreation() - } else { - Log.i( - "[Assistant] Device has TELEPHONY feature, showing phone based account creation" - ) - navigateToPhoneAccountCreation() - } - } else { - Log.w( - "[Assistant] Failed to get push notification info, showing warning instead of phone based account creation" - ) - navigateToNoPushWarning() - } - } - - binding.setAccountLoginClickListener { - navigateToAccountLogin() - } - - binding.setGenericAccountLoginClickListener { - navigateToGenericLoginWarning() - } - - binding.setRemoteProvisioningClickListener { - navigateToRemoteProvisioning() - } - - viewModel.termsAndPrivacyAccepted.observe( - viewLifecycleOwner - ) { - if (it) corePreferences.readAndAgreeTermsAndPrivacy = true - } - - setUpTermsAndPrivacyLinks() - } - - private fun setUpTermsAndPrivacyLinks() { - val terms = getString(R.string.assistant_general_terms) - val privacy = getString(R.string.assistant_privacy_policy) - - val label = try { - getString( - R.string.assistant_read_and_agree_terms, - terms, - privacy - ) - } catch (e: UnknownFormatConversionException) { - Log.e("[Welcome] Wrong R.string.assistant_read_and_agree_terms format!") - "I accept Belledonne Communications' terms of use and privacy policy" - } - val spannable = SpannableString(label) - - val termsMatcher = Pattern.compile(terms).matcher(label) - if (termsMatcher.find()) { - val clickableSpan: ClickableSpan = object : ClickableSpan() { - override fun onClick(widget: View) { - val browserIntent = Intent( - Intent.ACTION_VIEW, - Uri.parse(getString(R.string.assistant_general_terms_link)) - ) - try { - startActivity(browserIntent) - } catch (e: Exception) { - Log.e("[Welcome] Can't start activity: $e") - } - } - } - spannable.setSpan( - clickableSpan, - termsMatcher.start(0), - termsMatcher.end(), - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE - ) - } - - val policyMatcher = Pattern.compile(privacy).matcher(label) - if (policyMatcher.find()) { - val clickableSpan: ClickableSpan = object : ClickableSpan() { - override fun onClick(widget: View) { - val browserIntent = Intent( - Intent.ACTION_VIEW, - Uri.parse(getString(R.string.assistant_privacy_policy_link)) - ) - try { - startActivity(browserIntent) - } catch (e: Exception) { - Log.e("[Welcome] Can't start activity: $e") - } - } - } - spannable.setSpan( - clickableSpan, - policyMatcher.start(0), - policyMatcher.end(), - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE - ) - } - - binding.termsAndPrivacy.text = spannable - binding.termsAndPrivacy.movementMethod = LinkMovementMethod.getInstance() - } -} diff --git a/app/src/main/java/org/linphone/activities/assistant/viewmodels/AbstractPhoneViewModel.kt b/app/src/main/java/org/linphone/activities/assistant/viewmodels/AbstractPhoneViewModel.kt deleted file mode 100644 index 605b0c5ce..000000000 --- a/app/src/main/java/org/linphone/activities/assistant/viewmodels/AbstractPhoneViewModel.kt +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ - -package org.linphone.activities.assistant.viewmodels - -import androidx.lifecycle.MutableLiveData -import org.linphone.activities.assistant.fragments.CountryPickerFragment -import org.linphone.core.AccountCreator -import org.linphone.core.DialPlan -import org.linphone.core.tools.Log -import org.linphone.utils.PhoneNumberUtils - -abstract class AbstractPhoneViewModel(accountCreator: AccountCreator) : - AbstractPushTokenViewModel(accountCreator), - CountryPickerFragment.CountryPickedListener { - - val prefix = MutableLiveData() - val prefixError = MutableLiveData() - - val phoneNumber = MutableLiveData() - val phoneNumberError = MutableLiveData() - - val countryName = MutableLiveData() - - init { - prefix.value = "+" - } - - override fun onCountryClicked(dialPlan: DialPlan) { - prefix.value = "+${dialPlan.countryCallingCode}" - countryName.value = dialPlan.country - } - - fun isPhoneNumberOk(): Boolean { - return prefix.value.orEmpty().length > 1 && // Not just '+' character - prefixError.value.orEmpty().isEmpty() && - phoneNumber.value.orEmpty().isNotEmpty() && - phoneNumberError.value.orEmpty().isEmpty() - } - - fun updateFromPhoneNumberAndOrDialPlan(number: String?, dialPlan: DialPlan?) { - val internationalPrefix = "+${dialPlan?.countryCallingCode}" - if (dialPlan != null) { - Log.i("[Assistant] Found prefix from dial plan: ${dialPlan.countryCallingCode}") - prefix.value = internationalPrefix - getCountryNameFromPrefix(internationalPrefix) - } - - if (number != null) { - Log.i("[Assistant] Found phone number: $number") - phoneNumber.value = if (number.startsWith(internationalPrefix)) { - number.substring(internationalPrefix.length) - } else { - number - } - } - } - - fun getCountryNameFromPrefix(prefix: String?) { - if (!prefix.isNullOrEmpty()) { - val countryCode = if (prefix.first() == '+') prefix.substring(1) else prefix - val dialPlan = PhoneNumberUtils.getDialPlanFromCountryCallingPrefix(countryCode) - Log.i("[Assistant] Found dial plan $dialPlan from country code: $countryCode") - countryName.value = dialPlan?.country - } - } -} diff --git a/app/src/main/java/org/linphone/activities/assistant/viewmodels/AbstractPushTokenViewModel.kt b/app/src/main/java/org/linphone/activities/assistant/viewmodels/AbstractPushTokenViewModel.kt deleted file mode 100644 index 363475e63..000000000 --- a/app/src/main/java/org/linphone/activities/assistant/viewmodels/AbstractPushTokenViewModel.kt +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (c) 2010-2023 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.assistant.viewmodels - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import org.json.JSONException -import org.json.JSONObject -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.core.AccountCreator -import org.linphone.core.Core -import org.linphone.core.CoreListenerStub -import org.linphone.core.tools.Log - -abstract class AbstractPushTokenViewModel(val accountCreator: AccountCreator) : ViewModel() { - private var waitingForPushToken = false - private var waitForPushJob: Job? = null - - private val coreListener = object : CoreListenerStub() { - override fun onPushNotificationReceived(core: Core, payload: String?) { - Log.i("[Assistant] Push received: [$payload]") - - val data = payload.orEmpty() - if (data.isNotEmpty()) { - try { - // This is because JSONObject.toString() done by the SDK will result in payload looking like {"custom-payload":"{\"token\":\"value\"}"} - val cleanPayload = data.replace("\\\"", "\"").replace("\"{", "{").replace( - "}\"", - "}" - ) - Log.i("[Assistant] Cleaned payload is: [$cleanPayload]") - val json = JSONObject(cleanPayload) - val customPayload = json.getJSONObject("custom-payload") - if (customPayload.has("token")) { - waitForPushJob?.cancel() - waitingForPushToken = false - - val token = customPayload.getString("token") - if (token.isNotEmpty()) { - Log.i("[Assistant] Extracted token [$token] from push payload") - accountCreator.token = token - onFlexiApiTokenReceived() - } else { - Log.e("[Assistant] Push payload JSON object has an empty 'token'!") - onFlexiApiTokenRequestError() - } - } else { - Log.e("[Assistant] Push payload JSON object has no 'token' key!") - onFlexiApiTokenRequestError() - } - } catch (e: JSONException) { - Log.e("[Assistant] Exception trying to parse push payload as JSON: [$e]") - onFlexiApiTokenRequestError() - } - } else { - Log.e("[Assistant] Push payload is null or empty, can't extract auth token!") - onFlexiApiTokenRequestError() - } - } - } - - init { - coreContext.core.addListener(coreListener) - } - - override fun onCleared() { - coreContext.core.removeListener(coreListener) - waitForPushJob?.cancel() - } - - abstract fun onFlexiApiTokenReceived() - abstract fun onFlexiApiTokenRequestError() - - protected fun requestFlexiApiToken() { - if (!coreContext.core.isPushNotificationAvailable) { - Log.e( - "[Assistant] Core says push notification aren't available, can't request a token from FlexiAPI" - ) - onFlexiApiTokenRequestError() - return - } - - val pushConfig = coreContext.core.pushNotificationConfig - if (pushConfig != null) { - Log.i( - "[Assistant] Found push notification info: provider [${pushConfig.provider}], param [${pushConfig.param}] and prid [${pushConfig.prid}]" - ) - accountCreator.pnProvider = pushConfig.provider - accountCreator.pnParam = pushConfig.param - accountCreator.pnPrid = pushConfig.prid - - // Request an auth token, will be sent by push - val result = accountCreator.requestAuthToken() - if (result == AccountCreator.Status.RequestOk) { - val waitFor = 5000 - waitingForPushToken = true - waitForPushJob?.cancel() - - Log.i("[Assistant] Waiting push with auth token for $waitFor ms") - waitForPushJob = viewModelScope.launch { - withContext(Dispatchers.IO) { - delay(waitFor.toLong()) - } - withContext(Dispatchers.Main) { - if (waitingForPushToken) { - waitingForPushToken = false - Log.e("[Assistant] Auth token wasn't received by push in $waitFor ms") - onFlexiApiTokenRequestError() - } - } - } - } else { - Log.e("[Assistant] Failed to require a push with an auth token: [$result]") - onFlexiApiTokenRequestError() - } - } else { - Log.e("[Assistant] No push configuration object in Core, shouldn't happen!") - onFlexiApiTokenRequestError() - } - } -} diff --git a/app/src/main/java/org/linphone/activities/assistant/viewmodels/AccountLoginViewModel.kt b/app/src/main/java/org/linphone/activities/assistant/viewmodels/AccountLoginViewModel.kt deleted file mode 100644 index 579a76eb7..000000000 --- a/app/src/main/java/org/linphone/activities/assistant/viewmodels/AccountLoginViewModel.kt +++ /dev/null @@ -1,303 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.assistant.viewmodels - -import android.content.pm.PackageManager -import androidx.lifecycle.* -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.core.* -import org.linphone.core.tools.Log -import org.linphone.utils.Event -import org.linphone.utils.LinphoneUtils -import org.linphone.utils.PhoneNumberUtils - -class AccountLoginViewModelFactory(private val accountCreator: AccountCreator) : - ViewModelProvider.NewInstanceFactory() { - - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - return AccountLoginViewModel(accountCreator) as T - } -} - -class AccountLoginViewModel(accountCreator: AccountCreator) : AbstractPhoneViewModel(accountCreator) { - val loginWithUsernamePassword = MutableLiveData() - - val username = MutableLiveData() - val usernameError = MutableLiveData() - - val password = MutableLiveData() - val passwordError = MutableLiveData() - - val loginEnabled = MediatorLiveData() - - val waitForServerAnswer = MutableLiveData() - - val displayName = MutableLiveData() - - val forceLoginUsingUsernameAndPassword = MutableLiveData() - - val leaveAssistantEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val invalidCredentialsEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val goToSmsValidationEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val onErrorEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - private val listener = object : AccountCreatorListenerStub() { - override fun onRecoverAccount( - creator: AccountCreator, - status: AccountCreator.Status, - response: String? - ) { - Log.i("[Assistant] [Account Login] Recover account status is $status") - waitForServerAnswer.value = false - - if (status == AccountCreator.Status.RequestOk) { - goToSmsValidationEvent.value = Event(true) - } else { - onErrorEvent.value = Event("Error: ${status.name}") - } - } - } - - private var accountToCheck: Account? = null - - private val coreListener = object : CoreListenerStub() { - override fun onAccountRegistrationStateChanged( - core: Core, - account: Account, - state: RegistrationState?, - message: String - ) { - if (account == accountToCheck) { - Log.i("[Assistant] [Account Login] Registration state is $state: $message") - if (state == RegistrationState.Ok) { - waitForServerAnswer.value = false - leaveAssistantEvent.value = Event(true) - core.removeListener(this) - } else if (state == RegistrationState.Failed) { - waitForServerAnswer.value = false - invalidCredentialsEvent.value = Event(true) - core.removeListener(this) - } - } - } - } - - init { - accountCreator.addListener(listener) - - val pushAvailable = LinphoneUtils.isPushNotificationAvailable() - val deviceHasTelephonyFeature = coreContext.context.packageManager.hasSystemFeature( - PackageManager.FEATURE_TELEPHONY - ) - loginWithUsernamePassword.value = !deviceHasTelephonyFeature || !pushAvailable - forceLoginUsingUsernameAndPassword.value = !pushAvailable - - loginEnabled.value = false - loginEnabled.addSource(prefix) { - loginEnabled.value = isLoginButtonEnabled() - } - loginEnabled.addSource(phoneNumber) { - loginEnabled.value = isLoginButtonEnabled() - } - loginEnabled.addSource(username) { - loginEnabled.value = isLoginButtonEnabled() - } - loginEnabled.addSource(password) { - loginEnabled.value = isLoginButtonEnabled() - } - loginEnabled.addSource(loginWithUsernamePassword) { - loginEnabled.value = isLoginButtonEnabled() - } - loginEnabled.addSource(phoneNumberError) { - loginEnabled.value = isLoginButtonEnabled() - } - loginEnabled.addSource(prefixError) { - loginEnabled.value = isLoginButtonEnabled() - } - } - - override fun onCleared() { - accountCreator.removeListener(listener) - super.onCleared() - } - - override fun onFlexiApiTokenReceived() { - Log.i("[Assistant] [Account Login] Using FlexiAPI auth token [${accountCreator.token}]") - waitForServerAnswer.value = false - loginWithPhoneNumber() - } - - override fun onFlexiApiTokenRequestError() { - Log.e("[Assistant] [Account Login] Failed to get an auth token from FlexiAPI") - waitForServerAnswer.value = false - onErrorEvent.value = Event("Error: Failed to get an auth token from account manager server") - } - - fun removeInvalidProxyConfig() { - val account = accountToCheck - account ?: return - - val core = coreContext.core - val authInfo = account.findAuthInfo() - if (authInfo != null) core.removeAuthInfo(authInfo) - core.removeAccount(account) - accountToCheck = null - - // Make sure there is a valid default account - val accounts = core.accountList - if (accounts.isNotEmpty() && core.defaultAccount == null) { - core.defaultAccount = accounts.first() - core.refreshRegisters() - } - } - - fun continueEvenIfInvalidCredentials() { - leaveAssistantEvent.value = Event(true) - } - - private fun loginWithUsername() { - val result = accountCreator.setUsername(username.value) - if (result != AccountCreator.UsernameStatus.Ok) { - Log.e( - "[Assistant] [Account Login] Error [${result.name}] setting the username: ${username.value}" - ) - usernameError.value = result.name - return - } - Log.i("[Assistant] [Account Login] Username is ${accountCreator.username}") - - val result2 = accountCreator.setPassword(password.value) - if (result2 != AccountCreator.PasswordStatus.Ok) { - Log.e("[Assistant] [Account Login] Error [${result2.name}] setting the password") - passwordError.value = result2.name - return - } - - waitForServerAnswer.value = true - coreContext.core.addListener(coreListener) - if (!createAccountAndAuthInfo()) { - waitForServerAnswer.value = false - coreContext.core.removeListener(coreListener) - onErrorEvent.value = Event("Error: Failed to create account object") - } - } - - private fun loginWithPhoneNumber() { - val result = AccountCreator.PhoneNumberStatus.fromInt( - accountCreator.setPhoneNumber(phoneNumber.value, prefix.value) - ) - if (result != AccountCreator.PhoneNumberStatus.Ok) { - Log.e( - "[Assistant] [Account Login] Error [$result] setting the phone number: ${phoneNumber.value} with prefix: ${prefix.value}" - ) - phoneNumberError.value = result.name - return - } - Log.i("[Assistant] [Account Login] Phone number is ${accountCreator.phoneNumber}") - - val result2 = accountCreator.setUsername(accountCreator.phoneNumber) - if (result2 != AccountCreator.UsernameStatus.Ok) { - Log.e( - "[Assistant] [Account Login] Error [${result2.name}] setting the username: ${accountCreator.phoneNumber}" - ) - usernameError.value = result2.name - return - } - Log.i("[Assistant] [Account Login] Username is ${accountCreator.username}") - - waitForServerAnswer.value = true - val status = accountCreator.recoverAccount() - Log.i("[Assistant] [Account Login] Recover account returned $status") - if (status != AccountCreator.Status.RequestOk) { - waitForServerAnswer.value = false - onErrorEvent.value = Event("Error: ${status.name}") - } - } - - fun login() { - accountCreator.displayName = displayName.value - - if (loginWithUsernamePassword.value == true) { - loginWithUsername() - } else { - val token = accountCreator.token.orEmpty() - if (token.isNotEmpty()) { - Log.i( - "[Assistant] [Account Login] We already have an auth token from FlexiAPI [$token], continue" - ) - onFlexiApiTokenReceived() - } else { - Log.i("[Assistant] [Account Login] Requesting an auth token from FlexiAPI") - waitForServerAnswer.value = true - requestFlexiApiToken() - } - } - } - - private fun isLoginButtonEnabled(): Boolean { - return if (loginWithUsernamePassword.value == true) { - username.value.orEmpty().isNotEmpty() && password.value.orEmpty().isNotEmpty() - } else { - isPhoneNumberOk() - } - } - - private fun createAccountAndAuthInfo(): Boolean { - val account = accountCreator.createAccountInCore() - accountToCheck = account - - if (account == null) { - Log.e("[Assistant] [Account Login] Account creator couldn't create account") - onErrorEvent.value = Event("Error: Failed to create account object") - return false - } - - val params = account.params.clone() - params.pushNotificationAllowed = true - - if (params.internationalPrefix.isNullOrEmpty()) { - val dialPlan = PhoneNumberUtils.getDialPlanForCurrentCountry(coreContext.context) - if (dialPlan != null) { - Log.i( - "[Assistant] [Account Login] Found dial plan country ${dialPlan.country} with international prefix ${dialPlan.countryCallingCode}" - ) - params.internationalPrefix = dialPlan.countryCallingCode - } else { - Log.w("[Assistant] [Account Login] Failed to find dial plan") - } - } - - account.params = params - Log.i("[Assistant] [Account Login] Account created") - return true - } -} diff --git a/app/src/main/java/org/linphone/activities/assistant/viewmodels/EchoCancellerCalibrationViewModel.kt b/app/src/main/java/org/linphone/activities/assistant/viewmodels/EchoCancellerCalibrationViewModel.kt deleted file mode 100644 index 09d01c069..000000000 --- a/app/src/main/java/org/linphone/activities/assistant/viewmodels/EchoCancellerCalibrationViewModel.kt +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ - -package org.linphone.activities.assistant.viewmodels - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.core.Core -import org.linphone.core.CoreListenerStub -import org.linphone.core.EcCalibratorStatus -import org.linphone.core.tools.Log -import org.linphone.utils.Event - -class EchoCancellerCalibrationViewModel : ViewModel() { - val echoCalibrationTerminated = MutableLiveData>() - - private val listener = object : CoreListenerStub() { - override fun onEcCalibrationResult(core: Core, status: EcCalibratorStatus, delayMs: Int) { - if (status == EcCalibratorStatus.InProgress) return - echoCancellerCalibrationFinished(status, delayMs) - } - } - - init { - coreContext.core.addListener(listener) - } - - fun startEchoCancellerCalibration() { - coreContext.core.startEchoCancellerCalibration() - } - - fun echoCancellerCalibrationFinished(status: EcCalibratorStatus, delay: Int) { - coreContext.core.removeListener(listener) - when (status) { - EcCalibratorStatus.DoneNoEcho -> { - Log.i("[Assistant] [Echo Canceller Calibration] Done, no echo") - } - EcCalibratorStatus.Done -> { - Log.i("[Assistant] [Echo Canceller Calibration] Done, delay is ${delay}ms") - } - EcCalibratorStatus.Failed -> { - Log.w("[Assistant] [Echo Canceller Calibration] Failed") - } - EcCalibratorStatus.InProgress -> { - Log.i("[Assistant] [Echo Canceller Calibration] In progress") - } - } - echoCalibrationTerminated.value = Event(true) - } -} diff --git a/app/src/main/java/org/linphone/activities/assistant/viewmodels/EmailAccountCreationViewModel.kt b/app/src/main/java/org/linphone/activities/assistant/viewmodels/EmailAccountCreationViewModel.kt deleted file mode 100644 index dea25074d..000000000 --- a/app/src/main/java/org/linphone/activities/assistant/viewmodels/EmailAccountCreationViewModel.kt +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.assistant.viewmodels - -import androidx.lifecycle.MediatorLiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import org.linphone.R -import org.linphone.core.AccountCreator -import org.linphone.core.AccountCreatorListenerStub -import org.linphone.core.tools.Log -import org.linphone.utils.AppUtils -import org.linphone.utils.Event - -class EmailAccountCreationViewModelFactory(private val accountCreator: AccountCreator) : - ViewModelProvider.NewInstanceFactory() { - - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - return EmailAccountCreationViewModel(accountCreator) as T - } -} - -class EmailAccountCreationViewModel(accountCreator: AccountCreator) : AbstractPushTokenViewModel( - accountCreator -) { - val username = MutableLiveData() - val usernameError = MutableLiveData() - - val email = MutableLiveData() - val emailError = MutableLiveData() - - val password = MutableLiveData() - val passwordError = MutableLiveData() - - val passwordConfirmation = MutableLiveData() - val passwordConfirmationError = MutableLiveData() - - val displayName = MutableLiveData() - - val createEnabled: MediatorLiveData = MediatorLiveData() - - val waitForServerAnswer = MutableLiveData() - - val goToEmailValidationEvent = MutableLiveData>() - - val onErrorEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - private val listener = object : AccountCreatorListenerStub() { - override fun onIsAccountExist( - creator: AccountCreator, - status: AccountCreator.Status, - response: String? - ) { - Log.i("[Assistant] [Account Creation] onIsAccountExist status is $status") - when (status) { - AccountCreator.Status.AccountExist, AccountCreator.Status.AccountExistWithAlias -> { - waitForServerAnswer.value = false - usernameError.value = AppUtils.getString( - R.string.assistant_error_username_already_exists - ) - } - AccountCreator.Status.AccountNotExist -> { - val createAccountStatus = creator.createAccount() - if (createAccountStatus != AccountCreator.Status.RequestOk) { - waitForServerAnswer.value = false - onErrorEvent.value = Event("Error: ${status.name}") - } - } - else -> { - waitForServerAnswer.value = false - onErrorEvent.value = Event("Error: ${status.name}") - } - } - } - - override fun onCreateAccount( - creator: AccountCreator, - status: AccountCreator.Status, - response: String? - ) { - Log.i("[Assistant] [Account Creation] onCreateAccount status is $status") - waitForServerAnswer.value = false - - when (status) { - AccountCreator.Status.AccountCreated -> { - goToEmailValidationEvent.value = Event(true) - } - else -> { - onErrorEvent.value = Event("Error: ${status.name}") - } - } - } - } - - init { - accountCreator.addListener(listener) - - createEnabled.value = false - createEnabled.addSource(username) { - createEnabled.value = isCreateButtonEnabled() - } - createEnabled.addSource(usernameError) { - createEnabled.value = isCreateButtonEnabled() - } - createEnabled.addSource(email) { - createEnabled.value = isCreateButtonEnabled() - } - createEnabled.addSource(emailError) { - createEnabled.value = isCreateButtonEnabled() - } - createEnabled.addSource(password) { - createEnabled.value = isCreateButtonEnabled() - } - createEnabled.addSource(passwordError) { - createEnabled.value = isCreateButtonEnabled() - } - createEnabled.addSource(passwordConfirmation) { - createEnabled.value = isCreateButtonEnabled() - } - createEnabled.addSource(passwordConfirmationError) { - createEnabled.value = isCreateButtonEnabled() - } - } - - override fun onCleared() { - accountCreator.removeListener(listener) - super.onCleared() - } - - override fun onFlexiApiTokenReceived() { - Log.i("[Assistant] [Account Creation] Using FlexiAPI auth token [${accountCreator.token}]") - - waitForServerAnswer.value = true - val status = accountCreator.isAccountExist - Log.i("[Assistant] [Account Creation] Account exists returned $status") - if (status != AccountCreator.Status.RequestOk) { - waitForServerAnswer.value = false - onErrorEvent.value = Event("Error: ${status.name}") - } - } - - override fun onFlexiApiTokenRequestError() { - Log.e("[Assistant] [Account Creation] Failed to get an auth token from FlexiAPI") - waitForServerAnswer.value = false - onErrorEvent.value = Event("Error: Failed to get an auth token from account manager server") - } - - fun create() { - accountCreator.username = username.value - accountCreator.password = password.value - accountCreator.email = email.value - accountCreator.displayName = displayName.value - - val token = accountCreator.token.orEmpty() - if (token.isNotEmpty()) { - Log.i( - "[Assistant] [Account Creation] We already have an auth token from FlexiAPI [$token], continue" - ) - onFlexiApiTokenReceived() - } else { - Log.i("[Assistant] [Account Creation] Requesting an auth token from FlexiAPI") - waitForServerAnswer.value = true - requestFlexiApiToken() - } - } - - private fun isCreateButtonEnabled(): Boolean { - return username.value.orEmpty().isNotEmpty() && - email.value.orEmpty().isNotEmpty() && - password.value.orEmpty().isNotEmpty() && - passwordConfirmation.value.orEmpty().isNotEmpty() && - password.value == passwordConfirmation.value && - usernameError.value.orEmpty().isEmpty() && - emailError.value.orEmpty().isEmpty() && - passwordError.value.orEmpty().isEmpty() && - passwordConfirmationError.value.orEmpty().isEmpty() - } -} diff --git a/app/src/main/java/org/linphone/activities/assistant/viewmodels/EmailAccountValidationViewModel.kt b/app/src/main/java/org/linphone/activities/assistant/viewmodels/EmailAccountValidationViewModel.kt deleted file mode 100644 index bc72fbc90..000000000 --- a/app/src/main/java/org/linphone/activities/assistant/viewmodels/EmailAccountValidationViewModel.kt +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.assistant.viewmodels - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R -import org.linphone.core.AccountCreator -import org.linphone.core.AccountCreatorListenerStub -import org.linphone.core.tools.Log -import org.linphone.utils.AppUtils -import org.linphone.utils.Event -import org.linphone.utils.PhoneNumberUtils - -class EmailAccountValidationViewModelFactory(private val accountCreator: AccountCreator) : - ViewModelProvider.NewInstanceFactory() { - - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - return EmailAccountValidationViewModel(accountCreator) as T - } -} - -class EmailAccountValidationViewModel(val accountCreator: AccountCreator) : ViewModel() { - val email = MutableLiveData() - - val waitForServerAnswer = MutableLiveData() - - val leaveAssistantEvent = MutableLiveData>() - - val onErrorEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - private val listener = object : AccountCreatorListenerStub() { - override fun onIsAccountActivated( - creator: AccountCreator, - status: AccountCreator.Status, - response: String? - ) { - Log.i("[Assistant] [Account Validation] onIsAccountActivated status is $status") - waitForServerAnswer.value = false - - when (status) { - AccountCreator.Status.AccountActivated -> { - if (createAccountAndAuthInfo()) { - leaveAssistantEvent.value = Event(true) - } else { - onErrorEvent.value = Event("Error: ${status.name}") - } - } - AccountCreator.Status.AccountNotActivated -> { - onErrorEvent.value = Event( - AppUtils.getString(R.string.assistant_create_email_account_not_validated) - ) - } - else -> { - onErrorEvent.value = Event("Error: ${status.name}") - } - } - } - } - - init { - accountCreator.addListener(listener) - email.value = accountCreator.email - } - - override fun onCleared() { - accountCreator.removeListener(listener) - super.onCleared() - } - - fun finish() { - waitForServerAnswer.value = true - val status = accountCreator.isAccountActivated - Log.i("[Assistant] [Account Validation] Account exists returned $status") - if (status != AccountCreator.Status.RequestOk) { - waitForServerAnswer.value = false - onErrorEvent.value = Event("Error: ${status.name}") - } - } - - private fun createAccountAndAuthInfo(): Boolean { - val account = accountCreator.createAccountInCore() - - if (account == null) { - Log.e("[Assistant] [Account Validation] Account creator couldn't create account") - onErrorEvent.value = Event("Error: Failed to create account object") - return false - } - - val params = account.params.clone() - params.pushNotificationAllowed = true - - if (params.internationalPrefix.isNullOrEmpty()) { - val dialPlan = PhoneNumberUtils.getDialPlanForCurrentCountry(coreContext.context) - if (dialPlan != null) { - Log.i( - "[Assistant] [Account Validation] Found dial plan country ${dialPlan.country} with international prefix ${dialPlan.countryCallingCode}" - ) - params.internationalPrefix = dialPlan.countryCallingCode - } else { - Log.w("[Assistant] [Account Validation] Failed to find dial plan") - } - } - - account.params = params - - Log.i("[Assistant] [Account Validation] Account created") - return true - } -} diff --git a/app/src/main/java/org/linphone/activities/assistant/viewmodels/GenericLoginViewModel.kt b/app/src/main/java/org/linphone/activities/assistant/viewmodels/GenericLoginViewModel.kt deleted file mode 100644 index 5e0e50dfe..000000000 --- a/app/src/main/java/org/linphone/activities/assistant/viewmodels/GenericLoginViewModel.kt +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.assistant.viewmodels - -import androidx.lifecycle.MediatorLiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.core.* -import org.linphone.core.tools.Log -import org.linphone.utils.Event - -class GenericLoginViewModelFactory(private val accountCreator: AccountCreator) : - ViewModelProvider.NewInstanceFactory() { - - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - return GenericLoginViewModel(accountCreator) as T - } -} - -class GenericLoginViewModel(private val accountCreator: AccountCreator) : ViewModel() { - val username = MutableLiveData() - - val password = MutableLiveData() - - val domain = MutableLiveData() - - val displayName = MutableLiveData() - - val transport = MutableLiveData() - - val loginEnabled: MediatorLiveData = MediatorLiveData() - - val waitForServerAnswer = MutableLiveData() - - val leaveAssistantEvent = MutableLiveData>() - - val invalidCredentialsEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val onErrorEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - private var accountToCheck: Account? = null - - private val coreListener = object : CoreListenerStub() { - override fun onAccountRegistrationStateChanged( - core: Core, - account: Account, - state: RegistrationState?, - message: String - ) { - if (account == accountToCheck) { - Log.i("[Assistant] [Generic Login] Registration state is $state: $message") - if (state == RegistrationState.Ok) { - waitForServerAnswer.value = false - leaveAssistantEvent.value = Event(true) - core.removeListener(this) - } else if (state == RegistrationState.Failed) { - waitForServerAnswer.value = false - invalidCredentialsEvent.value = Event(true) - core.removeListener(this) - } - } - } - } - - init { - transport.value = TransportType.Tls - - loginEnabled.value = false - loginEnabled.addSource(username) { - loginEnabled.value = isLoginButtonEnabled() - } - loginEnabled.addSource(password) { - loginEnabled.value = isLoginButtonEnabled() - } - loginEnabled.addSource(domain) { - loginEnabled.value = isLoginButtonEnabled() - } - } - - fun setTransport(transportType: TransportType) { - transport.value = transportType - } - - fun removeInvalidProxyConfig() { - val account = accountToCheck - account ?: return - - val core = coreContext.core - val authInfo = account.findAuthInfo() - if (authInfo != null) core.removeAuthInfo(authInfo) - core.removeAccount(account) - accountToCheck = null - - // Make sure there is a valid default account - val accounts = core.accountList - if (accounts.isNotEmpty() && core.defaultAccount == null) { - core.defaultAccount = accounts.first() - core.refreshRegisters() - } - } - - fun continueEvenIfInvalidCredentials() { - leaveAssistantEvent.value = Event(true) - } - - fun createAccountAndAuthInfo() { - waitForServerAnswer.value = true - coreContext.core.addListener(coreListener) - - accountCreator.username = username.value - accountCreator.password = password.value - accountCreator.domain = domain.value - accountCreator.displayName = displayName.value - accountCreator.transport = transport.value - - val account = accountCreator.createAccountInCore() - accountToCheck = account - - if (account == null) { - Log.e("[Assistant] [Generic Login] Account creator couldn't create account") - coreContext.core.removeListener(coreListener) - onErrorEvent.value = Event("Error: Failed to create account object") - waitForServerAnswer.value = false - return - } - - Log.i("[Assistant] [Generic Login] Account created") - } - - private fun isLoginButtonEnabled(): Boolean { - return username.value.orEmpty().isNotEmpty() && - domain.value.orEmpty().isNotEmpty() && - password.value.orEmpty().isNotEmpty() - } -} diff --git a/app/src/main/java/org/linphone/activities/assistant/viewmodels/PhoneAccountCreationViewModel.kt b/app/src/main/java/org/linphone/activities/assistant/viewmodels/PhoneAccountCreationViewModel.kt deleted file mode 100644 index 03b6face3..000000000 --- a/app/src/main/java/org/linphone/activities/assistant/viewmodels/PhoneAccountCreationViewModel.kt +++ /dev/null @@ -1,271 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ - -package org.linphone.activities.assistant.viewmodels - -import androidx.lifecycle.MediatorLiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.core.AccountCreator -import org.linphone.core.AccountCreatorListenerStub -import org.linphone.core.tools.Log -import org.linphone.utils.AppUtils -import org.linphone.utils.Event - -class PhoneAccountCreationViewModelFactory(private val accountCreator: AccountCreator) : - ViewModelProvider.NewInstanceFactory() { - - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - return PhoneAccountCreationViewModel(accountCreator) as T - } -} - -class PhoneAccountCreationViewModel(accountCreator: AccountCreator) : AbstractPhoneViewModel( - accountCreator -) { - val username = MutableLiveData() - val useUsername = MutableLiveData() - val usernameError = MutableLiveData() - - val displayName = MutableLiveData() - - val createEnabled: MediatorLiveData = MediatorLiveData() - - val waitForServerAnswer = MutableLiveData() - - val goToSmsValidationEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val onErrorEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - private val listener = object : AccountCreatorListenerStub() { - override fun onIsAccountExist( - creator: AccountCreator, - status: AccountCreator.Status, - response: String? - ) { - Log.i("[Assistant] [Phone Account Creation] onIsAccountExist status is $status") - when (status) { - AccountCreator.Status.AccountExist, AccountCreator.Status.AccountExistWithAlias -> { - waitForServerAnswer.value = false - usernameError.value = AppUtils.getString( - R.string.assistant_error_username_already_exists - ) - } - AccountCreator.Status.AccountNotExist -> { - waitForServerAnswer.value = false - checkPhoneNumber() - } - else -> { - waitForServerAnswer.value = false - onErrorEvent.value = Event("Error: ${status.name}") - } - } - } - - override fun onIsAliasUsed( - creator: AccountCreator, - status: AccountCreator.Status, - response: String? - ) { - Log.i("[Assistant] [Phone Account Creation] onIsAliasUsed status is $status") - when (status) { - AccountCreator.Status.AliasExist -> { - waitForServerAnswer.value = false - phoneNumberError.value = AppUtils.getString( - R.string.assistant_error_phone_number_already_exists - ) - } - AccountCreator.Status.AliasIsAccount -> { - waitForServerAnswer.value = false - if (useUsername.value == true) { - usernameError.value = AppUtils.getString( - R.string.assistant_error_username_already_exists - ) - } else { - phoneNumberError.value = AppUtils.getString( - R.string.assistant_error_phone_number_already_exists - ) - } - } - AccountCreator.Status.AliasNotExist -> { - val createAccountStatus = creator.createAccount() - Log.i( - "[Assistant] [Phone Account Creation] createAccount returned $createAccountStatus" - ) - if (createAccountStatus != AccountCreator.Status.RequestOk) { - waitForServerAnswer.value = false - onErrorEvent.value = Event("Error: ${status.name}") - } - } - else -> { - waitForServerAnswer.value = false - onErrorEvent.value = Event("Error: ${status.name}") - } - } - } - - override fun onCreateAccount( - creator: AccountCreator, - status: AccountCreator.Status, - response: String? - ) { - Log.i("[Assistant] [Phone Account Creation] onCreateAccount status is $status") - waitForServerAnswer.value = false - when (status) { - AccountCreator.Status.AccountCreated -> { - goToSmsValidationEvent.value = Event(true) - } - AccountCreator.Status.AccountExistWithAlias -> { - phoneNumberError.value = AppUtils.getString( - R.string.assistant_error_phone_number_already_exists - ) - } - else -> { - onErrorEvent.value = Event("Error: ${status.name}") - } - } - } - } - - init { - useUsername.value = false - accountCreator.addListener(listener) - - createEnabled.value = false - createEnabled.addSource(prefix) { - createEnabled.value = isCreateButtonEnabled() - } - createEnabled.addSource(phoneNumber) { - createEnabled.value = isCreateButtonEnabled() - } - createEnabled.addSource(useUsername) { - createEnabled.value = isCreateButtonEnabled() - } - createEnabled.addSource(username) { - createEnabled.value = isCreateButtonEnabled() - } - createEnabled.addSource(usernameError) { - createEnabled.value = isCreateButtonEnabled() - } - createEnabled.addSource(phoneNumberError) { - createEnabled.value = isCreateButtonEnabled() - } - createEnabled.addSource(prefixError) { - createEnabled.value = isCreateButtonEnabled() - } - } - - override fun onCleared() { - accountCreator.removeListener(listener) - super.onCleared() - } - - override fun onFlexiApiTokenReceived() { - Log.i( - "[Assistant] [Phone Account Creation] Using FlexiAPI auth token [${accountCreator.token}]" - ) - accountCreator.displayName = displayName.value - - val result = AccountCreator.PhoneNumberStatus.fromInt( - accountCreator.setPhoneNumber(phoneNumber.value, prefix.value) - ) - if (result != AccountCreator.PhoneNumberStatus.Ok) { - Log.e( - "[Assistant] [Phone Account Creation] Error [$result] setting the phone number: ${phoneNumber.value} with prefix: ${prefix.value}" - ) - phoneNumberError.value = result.name - return - } - Log.i("[Assistant] [Phone Account Creation] Phone number is ${accountCreator.phoneNumber}") - - if (useUsername.value == true) { - accountCreator.username = username.value - } else { - accountCreator.username = accountCreator.phoneNumber - } - - if (useUsername.value == true) { - checkUsername() - } else { - checkPhoneNumber() - } - } - - override fun onFlexiApiTokenRequestError() { - Log.e("[Assistant] [Phone Account Creation] Failed to get an auth token from FlexiAPI") - waitForServerAnswer.value = false - onErrorEvent.value = Event("Error: Failed to get an auth token from account manager server") - } - - private fun checkUsername() { - val status = accountCreator.isAccountExist - Log.i("[Assistant] [Phone Account Creation] isAccountExist returned $status") - if (status != AccountCreator.Status.RequestOk) { - waitForServerAnswer.value = false - onErrorEvent.value = Event("Error: ${status.name}") - } - } - - private fun checkPhoneNumber() { - val status = accountCreator.isAliasUsed - Log.i("[Assistant] [Phone Account Creation] isAliasUsed returned $status") - if (status != AccountCreator.Status.RequestOk) { - waitForServerAnswer.value = false - onErrorEvent.value = Event("Error: ${status.name}") - } - } - - fun create() { - val token = accountCreator.token.orEmpty() - if (token.isNotEmpty()) { - Log.i( - "[Assistant] [Phone Account Creation] We already have an auth token from FlexiAPI [$token], continue" - ) - onFlexiApiTokenReceived() - } else { - Log.i("[Assistant] [Phone Account Creation] Requesting an auth token from FlexiAPI") - waitForServerAnswer.value = true - requestFlexiApiToken() - } - } - - private fun isCreateButtonEnabled(): Boolean { - val usernameRegexp = corePreferences.config.getString( - "assistant", - "username_regex", - "^[a-z0-9+_.\\-]*\$" - ) - return isPhoneNumberOk() && usernameRegexp != null && - ( - useUsername.value == false || - username.value.orEmpty().matches(Regex(usernameRegexp)) && - username.value.orEmpty().isNotEmpty() && - usernameError.value.orEmpty().isEmpty() - ) - } -} diff --git a/app/src/main/java/org/linphone/activities/assistant/viewmodels/PhoneAccountLinkingViewModel.kt b/app/src/main/java/org/linphone/activities/assistant/viewmodels/PhoneAccountLinkingViewModel.kt deleted file mode 100644 index 78c11c04e..000000000 --- a/app/src/main/java/org/linphone/activities/assistant/viewmodels/PhoneAccountLinkingViewModel.kt +++ /dev/null @@ -1,157 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ - -package org.linphone.activities.assistant.viewmodels - -import androidx.lifecycle.* -import org.linphone.core.AccountCreator -import org.linphone.core.AccountCreatorListenerStub -import org.linphone.core.tools.Log -import org.linphone.utils.Event - -class PhoneAccountLinkingViewModelFactory(private val accountCreator: AccountCreator) : - ViewModelProvider.NewInstanceFactory() { - - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - return PhoneAccountLinkingViewModel(accountCreator) as T - } -} - -class PhoneAccountLinkingViewModel(accountCreator: AccountCreator) : AbstractPhoneViewModel( - accountCreator -) { - val username = MutableLiveData() - - val allowSkip = MutableLiveData() - - val linkEnabled: MediatorLiveData = MediatorLiveData() - - val waitForServerAnswer = MutableLiveData() - - val leaveAssistantEvent = MutableLiveData>() - - val goToSmsValidationEvent = MutableLiveData>() - - val onErrorEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - private val listener = object : AccountCreatorListenerStub() { - override fun onIsAliasUsed( - creator: AccountCreator, - status: AccountCreator.Status, - response: String? - ) { - Log.i("[Assistant] [Phone Account Linking] onIsAliasUsed status is $status") - - when (status) { - AccountCreator.Status.AliasNotExist -> { - if (creator.linkAccount() != AccountCreator.Status.RequestOk) { - Log.e("[Assistant] [Phone Account Linking] linkAccount status is $status") - waitForServerAnswer.value = false - onErrorEvent.value = Event("Error: ${status.name}") - } - } - AccountCreator.Status.AliasExist, AccountCreator.Status.AliasIsAccount -> { - waitForServerAnswer.value = false - onErrorEvent.value = Event("Error: ${status.name}") - } - else -> { - waitForServerAnswer.value = false - onErrorEvent.value = Event("Error: ${status.name}") - } - } - } - - override fun onLinkAccount( - creator: AccountCreator, - status: AccountCreator.Status, - response: String? - ) { - Log.i("[Assistant] [Phone Account Linking] onLinkAccount status is $status") - waitForServerAnswer.value = false - - when (status) { - AccountCreator.Status.RequestOk -> { - goToSmsValidationEvent.value = Event(true) - } - else -> { - onErrorEvent.value = Event("Error: ${status.name}") - } - } - } - } - - init { - accountCreator.addListener(listener) - - linkEnabled.value = false - linkEnabled.addSource(prefix) { - linkEnabled.value = isLinkButtonEnabled() - } - linkEnabled.addSource(phoneNumber) { - linkEnabled.value = isLinkButtonEnabled() - } - linkEnabled.addSource(phoneNumberError) { - linkEnabled.value = isLinkButtonEnabled() - } - linkEnabled.addSource(prefixError) { - linkEnabled.value = isLinkButtonEnabled() - } - } - - override fun onCleared() { - accountCreator.removeListener(listener) - super.onCleared() - } - - override fun onFlexiApiTokenReceived() { - accountCreator.setPhoneNumber(phoneNumber.value, prefix.value) - accountCreator.username = username.value - Log.i("[Assistant] [Phone Account Linking] Phone number is ${accountCreator.phoneNumber}") - - val status: AccountCreator.Status = accountCreator.isAliasUsed - Log.i("[Assistant] [Phone Account Linking] isAliasUsed returned $status") - if (status != AccountCreator.Status.RequestOk) { - waitForServerAnswer.value = false - onErrorEvent.value = Event("Error: ${status.name}") - } - } - - override fun onFlexiApiTokenRequestError() { - Log.e("[Assistant] [Phone Account Linking] Failed to get an auth token from FlexiAPI") - waitForServerAnswer.value = false - } - - fun link() { - Log.i("[Assistant] [Phone Account Linking] Requesting an auth token from FlexiAPI") - waitForServerAnswer.value = true - requestFlexiApiToken() - } - - fun skip() { - leaveAssistantEvent.value = Event(true) - } - - private fun isLinkButtonEnabled(): Boolean { - return isPhoneNumberOk() - } -} diff --git a/app/src/main/java/org/linphone/activities/assistant/viewmodels/PhoneAccountValidationViewModel.kt b/app/src/main/java/org/linphone/activities/assistant/viewmodels/PhoneAccountValidationViewModel.kt deleted file mode 100644 index cf1f3f787..000000000 --- a/app/src/main/java/org/linphone/activities/assistant/viewmodels/PhoneAccountValidationViewModel.kt +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.assistant.viewmodels - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import org.linphone.core.AccountCreator -import org.linphone.core.AccountCreatorListenerStub -import org.linphone.core.tools.Log -import org.linphone.utils.Event - -class PhoneAccountValidationViewModelFactory(private val accountCreator: AccountCreator) : - ViewModelProvider.NewInstanceFactory() { - - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - return PhoneAccountValidationViewModel(accountCreator) as T - } -} - -class PhoneAccountValidationViewModel(val accountCreator: AccountCreator) : ViewModel() { - val phoneNumber = MutableLiveData() - - val code = MutableLiveData() - - val isLogin = MutableLiveData() - - val isCreation = MutableLiveData() - - val isLinking = MutableLiveData() - - val waitForServerAnswer = MutableLiveData() - - val leaveAssistantEvent = MutableLiveData>() - - val onErrorEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val listener = object : AccountCreatorListenerStub() { - override fun onLoginLinphoneAccount( - creator: AccountCreator, - status: AccountCreator.Status, - response: String? - ) { - Log.i("[Assistant] [Phone Account Validation] onLoginLinphoneAccount status is $status") - waitForServerAnswer.value = false - - if (status == AccountCreator.Status.RequestOk) { - if (createAccountAndAuthInfo()) { - leaveAssistantEvent.value = Event(true) - } else { - onErrorEvent.value = Event("Error: Failed to create account object") - } - } else { - onErrorEvent.value = Event("Error: ${status.name}") - } - } - - override fun onActivateAlias( - creator: AccountCreator, - status: AccountCreator.Status, - response: String? - ) { - Log.i("[Assistant] [Phone Account Validation] onActivateAlias status is $status") - waitForServerAnswer.value = false - - when (status) { - AccountCreator.Status.AccountActivated -> { - leaveAssistantEvent.value = Event(true) - } - else -> { - onErrorEvent.value = Event("Error: ${status.name}") - } - } - } - - override fun onActivateAccount( - creator: AccountCreator, - status: AccountCreator.Status, - response: String? - ) { - Log.i("[Assistant] [Phone Account Validation] onActivateAccount status is $status") - waitForServerAnswer.value = false - - if (status == AccountCreator.Status.AccountActivated) { - if (createAccountAndAuthInfo()) { - leaveAssistantEvent.value = Event(true) - } else { - onErrorEvent.value = Event("Error: Failed to create account object") - } - } else { - onErrorEvent.value = Event("Error: ${status.name}") - } - } - } - - init { - accountCreator.addListener(listener) - } - - override fun onCleared() { - accountCreator.removeListener(listener) - super.onCleared() - } - - fun finish() { - accountCreator.activationCode = code.value.orEmpty() - Log.i( - "[Assistant] [Phone Account Validation] Phone number is ${accountCreator.phoneNumber} and activation code is ${accountCreator.activationCode}" - ) - waitForServerAnswer.value = true - - val status = when { - isLogin.value == true -> accountCreator.loginLinphoneAccount() - isCreation.value == true -> accountCreator.activateAccount() - isLinking.value == true -> accountCreator.activateAlias() - else -> AccountCreator.Status.UnexpectedError - } - Log.i("[Assistant] [Phone Account Validation] Code validation result is $status") - if (status != AccountCreator.Status.RequestOk) { - waitForServerAnswer.value = false - onErrorEvent.value = Event("Error: ${status.name}") - } - } - - private fun createAccountAndAuthInfo(): Boolean { - val account = accountCreator.createAccountInCore() - - if (account == null) { - Log.e( - "[Assistant] [Phone Account Validation] Account creator couldn't create account" - ) - return false - } - - val params = account.params.clone() - params.pushNotificationAllowed = true - account.params = params - - Log.i("[Assistant] [Phone Account Validation] Account created") - return true - } -} diff --git a/app/src/main/java/org/linphone/activities/assistant/viewmodels/QrCodeViewModel.kt b/app/src/main/java/org/linphone/activities/assistant/viewmodels/QrCodeViewModel.kt deleted file mode 100644 index c0f988d0f..000000000 --- a/app/src/main/java/org/linphone/activities/assistant/viewmodels/QrCodeViewModel.kt +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.assistant.viewmodels - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.core.Core -import org.linphone.core.CoreListenerStub -import org.linphone.core.tools.Log -import org.linphone.utils.Event - -class QrCodeViewModel : ViewModel() { - val qrCodeFoundEvent = MutableLiveData>() - - val showSwitchCamera = MutableLiveData() - - private val listener = object : CoreListenerStub() { - override fun onQrcodeFound(core: Core, result: String?) { - Log.i("[Assistant] [QR Code] Found [$result]") - if (result != null) qrCodeFoundEvent.postValue(Event(result)) - } - } - - init { - coreContext.core.addListener(listener) - showSwitchCamera.value = coreContext.showSwitchCameraButton() - } - - override fun onCleared() { - coreContext.core.removeListener(listener) - super.onCleared() - } - - fun setBackCamera() { - showSwitchCamera.value = coreContext.showSwitchCameraButton() - - for (camera in coreContext.core.videoDevicesList) { - if (camera.contains("Back")) { - Log.i("[Assistant] [QR Code] Found back facing camera: $camera") - coreContext.core.videoDevice = camera - return - } - } - - val first = coreContext.core.videoDevicesList.firstOrNull() - if (first != null) { - Log.i("[Assistant] [QR Code] Using first camera found: $first") - coreContext.core.videoDevice = first - } - } - - fun switchCamera() { - coreContext.switchCamera() - } -} diff --git a/app/src/main/java/org/linphone/activities/assistant/viewmodels/RemoteProvisioningViewModel.kt b/app/src/main/java/org/linphone/activities/assistant/viewmodels/RemoteProvisioningViewModel.kt deleted file mode 100644 index 2f8d75b90..000000000 --- a/app/src/main/java/org/linphone/activities/assistant/viewmodels/RemoteProvisioningViewModel.kt +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.assistant.viewmodels - -import androidx.lifecycle.MediatorLiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.core.ConfiguringState -import org.linphone.core.Core -import org.linphone.core.CoreListenerStub -import org.linphone.core.tools.Log -import org.linphone.utils.Event - -class RemoteProvisioningViewModel : ViewModel() { - val urlToFetch = MutableLiveData() - val urlError = MutableLiveData() - - val fetchEnabled: MediatorLiveData = MediatorLiveData() - val fetchInProgress = MutableLiveData() - val fetchSuccessfulEvent = MutableLiveData>() - - private val listener = object : CoreListenerStub() { - override fun onConfiguringStatus( - core: Core, - status: ConfiguringState, - message: String? - ) { - fetchInProgress.value = false - when (status) { - ConfiguringState.Successful -> { - fetchSuccessfulEvent.value = Event(true) - } - ConfiguringState.Failed -> { - fetchSuccessfulEvent.value = Event(false) - } - else -> {} - } - } - } - - init { - fetchInProgress.value = false - coreContext.core.addListener(listener) - - fetchEnabled.value = false - fetchEnabled.addSource(urlToFetch) { - fetchEnabled.value = isFetchEnabled() - } - fetchEnabled.addSource(urlError) { - fetchEnabled.value = isFetchEnabled() - } - } - - override fun onCleared() { - coreContext.core.removeListener(listener) - super.onCleared() - } - - fun fetchAndApply() { - val url = urlToFetch.value.orEmpty() - coreContext.core.provisioningUri = url - Log.w("[Assistant] [Remote Provisioning] Url set to [$url], restarting Core") - fetchInProgress.value = true - coreContext.core.stop() - coreContext.core.start() - } - - private fun isFetchEnabled(): Boolean { - return urlToFetch.value.orEmpty().isNotEmpty() && urlError.value.orEmpty().isEmpty() - } -} diff --git a/app/src/main/java/org/linphone/activities/assistant/viewmodels/SharedAssistantViewModel.kt b/app/src/main/java/org/linphone/activities/assistant/viewmodels/SharedAssistantViewModel.kt deleted file mode 100644 index d97bdf1b7..000000000 --- a/app/src/main/java/org/linphone/activities/assistant/viewmodels/SharedAssistantViewModel.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.assistant.viewmodels - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import java.util.* -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.core.* -import org.linphone.core.tools.Log - -class SharedAssistantViewModel : ViewModel() { - val remoteProvisioningUrl = MutableLiveData() - - private var accountCreator: AccountCreator - private var useGenericSipAccount: Boolean = false - - init { - Log.i("[Assistant] Loading linphone default values") - coreContext.core.loadConfigFromXml(corePreferences.linphoneDefaultValuesPath) - accountCreator = coreContext.core.createAccountCreator(corePreferences.xmlRpcServerUrl) - accountCreator.language = Locale.getDefault().language - } - - fun getAccountCreator(genericAccountCreator: Boolean = false): AccountCreator { - if (genericAccountCreator != useGenericSipAccount) { - accountCreator.reset() - accountCreator.language = Locale.getDefault().language - - if (genericAccountCreator) { - Log.i("[Assistant] Loading default values") - coreContext.core.loadConfigFromXml(corePreferences.defaultValuesPath) - } else { - Log.i("[Assistant] Loading linphone default values") - coreContext.core.loadConfigFromXml(corePreferences.linphoneDefaultValuesPath) - } - useGenericSipAccount = genericAccountCreator - } - return accountCreator - } -} diff --git a/app/src/main/java/org/linphone/activities/assistant/viewmodels/WelcomeViewModel.kt b/app/src/main/java/org/linphone/activities/assistant/viewmodels/WelcomeViewModel.kt deleted file mode 100644 index d31a2ba89..000000000 --- a/app/src/main/java/org/linphone/activities/assistant/viewmodels/WelcomeViewModel.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.assistant.viewmodels - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import org.linphone.LinphoneApplication.Companion.corePreferences - -class WelcomeViewModel : ViewModel() { - val showCreateAccount: Boolean = corePreferences.showCreateAccount - val showLinphoneLogin: Boolean = corePreferences.showLinphoneLogin - val showGenericLogin: Boolean = corePreferences.showGenericLogin - val showRemoteProvisioning: Boolean = corePreferences.showRemoteProvisioning - - val termsAndPrivacyAccepted = MutableLiveData() - - init { - termsAndPrivacyAccepted.value = corePreferences.readAndAgreeTermsAndPrivacy - } -} diff --git a/app/src/main/java/org/linphone/activities/chat_bubble/ChatBubbleActivity.kt b/app/src/main/java/org/linphone/activities/chat_bubble/ChatBubbleActivity.kt deleted file mode 100644 index ddcdefa5b..000000000 --- a/app/src/main/java/org/linphone/activities/chat_bubble/ChatBubbleActivity.kt +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.chat_bubble - -import android.content.Intent -import android.os.Bundle -import android.widget.Toast -import androidx.databinding.DataBindingUtil -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.lifecycleScope -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R -import org.linphone.activities.GenericActivity -import org.linphone.activities.main.MainActivity -import org.linphone.activities.main.chat.adapters.ChatMessagesListAdapter -import org.linphone.activities.main.chat.viewmodels.* -import org.linphone.activities.main.viewmodels.ListTopBarViewModel -import org.linphone.core.ChatRoom -import org.linphone.core.ChatRoomListenerStub -import org.linphone.core.EventLog -import org.linphone.core.Factory -import org.linphone.core.tools.Log -import org.linphone.databinding.ChatBubbleActivityBinding -import org.linphone.utils.FileUtils - -class ChatBubbleActivity : GenericActivity() { - private lateinit var binding: ChatBubbleActivityBinding - private lateinit var viewModel: ChatRoomViewModel - private lateinit var listViewModel: ChatMessagesListViewModel - private lateinit var chatSendingViewModel: ChatMessageSendingViewModel - private lateinit var adapter: ChatMessagesListAdapter - - private val observer = object : RecyclerView.AdapterDataObserver() { - override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { - if (positionStart == adapter.itemCount - itemCount) { - adapter.notifyItemChanged(positionStart - 1) // For grouping purposes - scrollToBottom() - } - } - } - - private val listener = object : ChatRoomListenerStub() { - override fun onChatMessagesReceived(chatRoom: ChatRoom, eventLogs: Array) { - chatRoom.markAsRead() - } - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - binding = DataBindingUtil.setContentView(this, R.layout.chat_bubble_activity) - binding.lifecycleOwner = this - - val localSipUri = intent.getStringExtra("LocalSipUri") - val remoteSipUri = intent.getStringExtra("RemoteSipUri") - var chatRoom: ChatRoom? = null - - if (localSipUri != null && remoteSipUri != null) { - Log.i( - "[Chat Bubble] Found local [$localSipUri] & remote [$remoteSipUri] addresses in arguments" - ) - val localAddress = Factory.instance().createAddress(localSipUri) - val remoteSipAddress = Factory.instance().createAddress(remoteSipUri) - chatRoom = coreContext.core.searchChatRoom( - null, - localAddress, - remoteSipAddress, - arrayOfNulls( - 0 - ) - ) - } - - if (chatRoom == null) { - Log.e("[Chat Bubble] Chat room is null, aborting!") - finish() - return - } - - viewModel = ViewModelProvider( - this, - ChatRoomViewModelFactory(chatRoom) - )[ChatRoomViewModel::class.java] - binding.viewModel = viewModel - - listViewModel = ViewModelProvider( - this, - ChatMessagesListViewModelFactory(chatRoom) - )[ChatMessagesListViewModel::class.java] - - chatSendingViewModel = ViewModelProvider( - this, - ChatMessageSendingViewModelFactory(chatRoom) - )[ChatMessageSendingViewModel::class.java] - binding.chatSendingViewModel = chatSendingViewModel - - val listSelectionViewModel = ViewModelProvider(this)[ListTopBarViewModel::class.java] - adapter = ChatMessagesListAdapter(listSelectionViewModel, this) - // SubmitList is done on a background thread - // We need this adapter data observer to know when to scroll - binding.chatMessagesList.adapter = adapter - adapter.registerAdapterDataObserver(observer) - - // Disable context menu on each message - adapter.disableAdvancedContextMenuOptions() - - adapter.openContentEvent.observe( - this - ) { - it.consume { content -> - if (content.isFileEncrypted) { - Toast.makeText( - this, - R.string.chat_bubble_cant_open_enrypted_file, - Toast.LENGTH_LONG - ).show() - } else { - FileUtils.openFileInThirdPartyApp(this, content.filePath.orEmpty(), true) - } - } - } - - val layoutManager = LinearLayoutManager(this) - layoutManager.stackFromEnd = true - binding.chatMessagesList.layoutManager = layoutManager - - listViewModel.events.observe( - this - ) { events -> - adapter.submitList(events) - } - - chatSendingViewModel.textToSend.observe( - this - ) { - chatSendingViewModel.onTextToSendChanged(it) - } - - binding.setOpenAppClickListener { - coreContext.notificationsManager.currentlyDisplayedChatRoomAddress = null - coreContext.notificationsManager.changeDismissNotificationUponReadForChatRoom( - viewModel.chatRoom, - false - ) - - val intent = Intent(this, MainActivity::class.java) - intent.putExtra("RemoteSipUri", remoteSipUri) - intent.putExtra("LocalSipUri", localSipUri) - intent.putExtra("Chat", true) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK and Intent.FLAG_ACTIVITY_CLEAR_TASK) - startActivity(intent) - } - - binding.setCloseBubbleClickListener { - coreContext.notificationsManager.dismissChatNotification(viewModel.chatRoom) - } - - binding.setSendMessageClickListener { - chatSendingViewModel.sendMessage() - binding.message.text?.clear() - } - } - - override fun onResume() { - super.onResume() - - viewModel.chatRoom.addListener(listener) - - // Workaround for the removed notification when a chat room is marked as read - coreContext.notificationsManager.changeDismissNotificationUponReadForChatRoom( - viewModel.chatRoom, - true - ) - viewModel.chatRoom.markAsRead() - - val peerAddress = viewModel.chatRoom.peerAddress.asStringUriOnly() - coreContext.notificationsManager.currentlyDisplayedChatRoomAddress = peerAddress - coreContext.notificationsManager.resetChatNotificationCounterForSipUri(peerAddress) - - lifecycleScope.launch { - // Without the delay the scroll to bottom doesn't happen... - delay(100) - scrollToBottom() - } - } - - override fun onPause() { - viewModel.chatRoom.removeListener(listener) - - coreContext.notificationsManager.currentlyDisplayedChatRoomAddress = null - coreContext.notificationsManager.changeDismissNotificationUponReadForChatRoom( - viewModel.chatRoom, - false - ) - - super.onPause() - } - - private fun scrollToBottom() { - if (adapter.itemCount > 0) { - binding.chatMessagesList.scrollToPosition(adapter.itemCount - 1) - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/MainActivity.kt b/app/src/main/java/org/linphone/activities/main/MainActivity.kt deleted file mode 100644 index 78798c4c0..000000000 --- a/app/src/main/java/org/linphone/activities/main/MainActivity.kt +++ /dev/null @@ -1,740 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main - -import android.app.Dialog -import android.content.ComponentCallbacks2 -import android.content.Context -import android.content.Intent -import android.content.res.Configuration -import android.net.Uri -import android.os.Bundle -import android.os.Parcelable -import android.view.Gravity -import android.view.MotionEvent -import android.view.View -import android.view.inputmethod.InputMethodManager -import androidx.annotation.StringRes -import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen -import androidx.core.view.doOnAttach -import androidx.databinding.DataBindingUtil -import androidx.fragment.app.FragmentContainerView -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.lifecycleScope -import androidx.navigation.NavController -import androidx.navigation.NavDestination -import androidx.navigation.findNavController -import androidx.window.layout.FoldingFeature -import coil.imageLoader -import com.google.android.material.snackbar.Snackbar -import java.io.UnsupportedEncodingException -import java.net.URLDecoder -import kotlin.math.abs -import kotlinx.coroutines.* -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.* -import org.linphone.activities.assistant.AssistantActivity -import org.linphone.activities.main.viewmodels.CallOverlayViewModel -import org.linphone.activities.main.viewmodels.DialogViewModel -import org.linphone.activities.main.viewmodels.SharedMainViewModel -import org.linphone.activities.navigateToDialer -import org.linphone.compatibility.Compatibility -import org.linphone.contact.ContactsUpdatedListenerStub -import org.linphone.core.AuthInfo -import org.linphone.core.AuthMethod -import org.linphone.core.Core -import org.linphone.core.CoreListenerStub -import org.linphone.core.CorePreferences -import org.linphone.core.tools.Log -import org.linphone.databinding.MainActivityBinding -import org.linphone.utils.* - -class MainActivity : GenericActivity(), SnackBarActivity, NavController.OnDestinationChangedListener { - private lateinit var binding: MainActivityBinding - private lateinit var sharedViewModel: SharedMainViewModel - private lateinit var callOverlayViewModel: CallOverlayViewModel - - private val listener = object : ContactsUpdatedListenerStub() { - override fun onContactsUpdated() { - Log.i("[Main Activity] Contact(s) updated, update shortcuts") - if (corePreferences.contactsShortcuts) { - ShortcutsHelper.createShortcutsToContacts(this@MainActivity) - } else if (corePreferences.chatRoomShortcuts) { - ShortcutsHelper.createShortcutsToChatRooms(this@MainActivity) - } - } - } - - private lateinit var tabsFragment: FragmentContainerView - private lateinit var statusFragment: FragmentContainerView - - private var overlayX = 0f - private var overlayY = 0f - private var initPosX = 0f - private var initPosY = 0f - private var overlay: View? = null - - private val componentCallbacks = object : ComponentCallbacks2 { - override fun onConfigurationChanged(newConfig: Configuration) { } - - override fun onLowMemory() { - Log.w("[Main Activity] onLowMemory !") - } - - override fun onTrimMemory(level: Int) { - Log.w("[Main Activity] onTrimMemory called with level $level !") - applicationContext.imageLoader.memoryCache?.clear() - } - } - - override fun onLayoutChanges(foldingFeature: FoldingFeature?) { - sharedViewModel.layoutChangedEvent.value = Event(true) - } - - private var shouldTabsBeVisibleDependingOnDestination = true - private var shouldTabsBeVisibleDueToOrientationAndKeyboard = true - - private val authenticationRequestedEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - private var authenticationRequiredDialog: Dialog? = null - - private val coreListener: CoreListenerStub = object : CoreListenerStub() { - override fun onAuthenticationRequested(core: Core, authInfo: AuthInfo, method: AuthMethod) { - if (authInfo.username == null || authInfo.domain == null || authInfo.realm == null) { - return - } - - Log.w( - "[Main Activity] Authentication requested for account [${authInfo.username}@${authInfo.domain}] with realm [${authInfo.realm}] using method [$method]" - ) - authenticationRequestedEvent.value = Event(authInfo) - } - } - - private val keyboardVisibilityListeners = arrayListOf() - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - // Must be done before the setContentView - installSplashScreen() - - binding = DataBindingUtil.setContentView(this, R.layout.main_activity) - binding.lifecycleOwner = this - - sharedViewModel = ViewModelProvider(this)[SharedMainViewModel::class.java] - binding.viewModel = sharedViewModel - - callOverlayViewModel = ViewModelProvider(this)[CallOverlayViewModel::class.java] - binding.callOverlayViewModel = callOverlayViewModel - - sharedViewModel.toggleDrawerEvent.observe( - this - ) { - it.consume { - if (binding.sideMenu.isDrawerOpen(Gravity.LEFT)) { - binding.sideMenu.closeDrawer(binding.sideMenuContent, true) - } else { - binding.sideMenu.openDrawer(binding.sideMenuContent, true) - } - } - } - - coreContext.callErrorMessageResourceId.observe( - this - ) { - it.consume { message -> - showSnackBar(message) - } - } - - authenticationRequestedEvent.observe( - this - ) { - it.consume { authInfo -> - showAuthenticationRequestedDialog(authInfo) - } - } - - if (coreContext.core.accountList.isEmpty()) { - if (corePreferences.firstStart) { - startActivity(Intent(this, AssistantActivity::class.java)) - } - } - - tabsFragment = findViewById(R.id.tabs_fragment) - statusFragment = findViewById(R.id.status_fragment) - - binding.root.doOnAttach { - Log.i("[Main Activity] Report UI has been fully drawn (TTFD)") - try { - reportFullyDrawn() - } catch (se: SecurityException) { - Log.e("[Main Activity] Security exception when doing reportFullyDrawn(): $se") - } - } - } - - override fun onNewIntent(intent: Intent?) { - super.onNewIntent(intent) - - if (intent != null) { - Log.d("[Main Activity] Found new intent") - handleIntentParams(intent) - } - } - - override fun onResume() { - super.onResume() - coreContext.contactsManager.addListener(listener) - coreContext.core.addListener(coreListener) - } - - override fun onPause() { - coreContext.core.removeListener(coreListener) - coreContext.contactsManager.removeListener(listener) - super.onPause() - } - - override fun showSnackBar(@StringRes resourceId: Int) { - Snackbar.make(findViewById(R.id.coordinator), resourceId, Snackbar.LENGTH_LONG).show() - } - - override fun showSnackBar(@StringRes resourceId: Int, action: Int, listener: () -> Unit) { - Snackbar - .make(findViewById(R.id.coordinator), resourceId, Snackbar.LENGTH_LONG) - .setAction(action) { - Log.i("[Snack Bar] Action listener triggered") - listener() - } - .show() - } - - override fun showSnackBar(message: String) { - Snackbar.make(findViewById(R.id.coordinator), message, Snackbar.LENGTH_LONG).show() - } - - override fun onPostCreate(savedInstanceState: Bundle?) { - super.onPostCreate(savedInstanceState) - - registerComponentCallbacks(componentCallbacks) - findNavController(R.id.nav_host_fragment).addOnDestinationChangedListener(this) - - binding.rootCoordinatorLayout.setKeyboardInsetListener { keyboardVisible -> - val portraitOrientation = resources.configuration.orientation != Configuration.ORIENTATION_LANDSCAPE - Log.i( - "[Main Activity] Keyboard is ${if (keyboardVisible) "visible" else "invisible"}, orientation is ${if (portraitOrientation) "portrait" else "landscape"}" - ) - shouldTabsBeVisibleDueToOrientationAndKeyboard = !portraitOrientation || !keyboardVisible - updateTabsFragmentVisibility() - - for (listener in keyboardVisibilityListeners) { - listener.onKeyboardVisibilityChanged(keyboardVisible) - } - } - - initOverlay() - - if (intent != null) { - Log.d("[Main Activity] Found post create intent") - handleIntentParams(intent) - } - } - - override fun onDestroy() { - findNavController(R.id.nav_host_fragment).removeOnDestinationChangedListener(this) - unregisterComponentCallbacks(componentCallbacks) - super.onDestroy() - } - - override fun onDestinationChanged( - controller: NavController, - destination: NavDestination, - arguments: Bundle? - ) { - hideKeyboard() - if (statusFragment.visibility == View.GONE) { - statusFragment.visibility = View.VISIBLE - } - - shouldTabsBeVisibleDependingOnDestination = when (destination.id) { - R.id.masterCallLogsFragment, R.id.masterContactsFragment, R.id.dialerFragment, R.id.masterChatRoomsFragment -> - true - else -> false - } - updateTabsFragmentVisibility() - } - - fun addKeyboardVisibilityListener(listener: AppUtils.KeyboardVisibilityListener) { - keyboardVisibilityListeners.add(listener) - } - - fun removeKeyboardVisibilityListener(listener: AppUtils.KeyboardVisibilityListener) { - keyboardVisibilityListeners.remove(listener) - } - - fun hideKeyboard() { - currentFocus?.hideKeyboard() - } - - fun showKeyboard() { - // Requires a text field to have the focus - if (currentFocus != null) { - (getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager) - .showSoftInput(currentFocus, 0) - } else { - Log.w("[Main Activity] Can't show the keyboard, no focused view") - } - } - - fun hideStatusFragment(hide: Boolean) { - statusFragment.visibility = if (hide) View.GONE else View.VISIBLE - } - - private fun updateTabsFragmentVisibility() { - tabsFragment.visibility = if (shouldTabsBeVisibleDependingOnDestination && shouldTabsBeVisibleDueToOrientationAndKeyboard) View.VISIBLE else View.GONE - } - - private fun handleIntentParams(intent: Intent) { - Log.i( - "[Main Activity] Handling intent with action [${intent.action}], type [${intent.type}] and data [${intent.data}]" - ) - - when (intent.action) { - Intent.ACTION_MAIN -> handleMainIntent(intent) - Intent.ACTION_SEND, Intent.ACTION_SENDTO -> { - if (intent.type == "text/plain") { - handleSendText(intent) - } else { - lifecycleScope.launch { - handleSendFile(intent) - } - } - } - Intent.ACTION_SEND_MULTIPLE -> { - lifecycleScope.launch { - handleSendMultipleFiles(intent) - } - } - Intent.ACTION_VIEW -> { - val uri = intent.data - if (uri != null) { - if ( - intent.type == AppUtils.getString(R.string.linphone_address_mime_type) && - PermissionHelper.get().hasReadContactsPermission() - ) { - val contactId = - coreContext.contactsManager.getAndroidContactIdFromUri(uri) - if (contactId != null) { - Log.i("[Main Activity] Found contact URI parameter in intent: $uri") - navigateToContact(contactId) - } - } else { - val stringUri = uri.toString() - if (stringUri.startsWith("linphone-config:")) { - val remoteConfigUri = stringUri.substring("linphone-config:".length) - if (corePreferences.autoRemoteProvisioningOnConfigUriHandler) { - Log.w( - "[Main Activity] Remote provisioning URL set to [$remoteConfigUri], restarting Core now" - ) - applyRemoteProvisioning(remoteConfigUri) - } else { - Log.i( - "[Main Activity] Remote provisioning URL found [$remoteConfigUri], asking for user validation" - ) - showAcceptRemoteConfigurationDialog(remoteConfigUri) - } - } else { - handleTelOrSipUri(uri) - } - } - } - } - Intent.ACTION_DIAL, Intent.ACTION_CALL -> { - val uri = intent.data - if (uri != null) { - handleTelOrSipUri(uri) - } - } - Intent.ACTION_VIEW_LOCUS -> { - if (corePreferences.disableChat) return - val locus = Compatibility.extractLocusIdFromIntent(intent) - if (locus != null) { - Log.i("[Main Activity] Found chat room locus intent extra: $locus") - handleLocusOrShortcut(locus) - } - } - else -> handleMainIntent(intent) - } - - // Prevent this intent to be processed again - intent.action = null - intent.data = null - val extras = intent.extras - if (extras != null) { - for (key in extras.keySet()) { - intent.removeExtra(key) - } - } - } - - private fun handleMainIntent(intent: Intent) { - when { - intent.hasExtra("ContactId") -> { - val id = intent.getStringExtra("ContactId") - Log.i("[Main Activity] Found contact ID in extras: $id") - navigateToContact(id) - } - intent.hasExtra("Chat") -> { - if (corePreferences.disableChat) return - - if (intent.hasExtra("RemoteSipUri") && intent.hasExtra("LocalSipUri")) { - val peerAddress = intent.getStringExtra("RemoteSipUri") - val localAddress = intent.getStringExtra("LocalSipUri") - Log.i( - "[Main Activity] Found chat room intent extra: local SIP URI=[$localAddress], peer SIP URI=[$peerAddress]" - ) - navigateToChatRoom(localAddress, peerAddress) - } else { - Log.i("[Main Activity] Found chat intent extra, go to chat rooms list") - navigateToChatRooms() - } - } - intent.hasExtra("Dialer") -> { - Log.i("[Main Activity] Found dialer intent extra, go to dialer") - val isTransfer = intent.getBooleanExtra("Transfer", false) - sharedViewModel.pendingCallTransfer = isTransfer - navigateToDialer() - } - intent.hasExtra("Contacts") -> { - Log.i("[Main Activity] Found contacts intent extra, go to contacts list") - val isTransfer = intent.getBooleanExtra("Transfer", false) - sharedViewModel.pendingCallTransfer = isTransfer - navigateToContacts() - } - else -> { - val core = coreContext.core - val call = core.currentCall ?: core.calls.firstOrNull() - if (call != null) { - Log.i( - "[Main Activity] Launcher clicked while there is at least one active call, go to CallActivity" - ) - val callIntent = Intent( - this, - org.linphone.activities.voip.CallActivity::class.java - ) - callIntent.addFlags( - Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_REORDER_TO_FRONT - ) - startActivity(callIntent) - } - } - } - } - - private fun handleTelOrSipUri(uri: Uri) { - Log.i("[Main Activity] Found uri: $uri to call") - val stringUri = uri.toString() - var addressToCall: String = stringUri - - when { - addressToCall.startsWith("tel:") -> { - Log.i("[Main Activity] Removing tel: prefix") - addressToCall = addressToCall.substring("tel:".length) - } - addressToCall.startsWith("linphone:") -> { - Log.i("[Main Activity] Removing linphone: prefix") - addressToCall = addressToCall.substring("linphone:".length) - } - addressToCall.startsWith("sip-linphone:") -> { - Log.i("[Main Activity] Removing linphone: sip-linphone") - addressToCall = addressToCall.substring("sip-linphone:".length) - } - } - - addressToCall = addressToCall.replace("%40", "@") - - val address = coreContext.core.interpretUrl( - addressToCall, - LinphoneUtils.applyInternationalPrefix() - ) - if (address != null) { - addressToCall = address.asStringUriOnly() - } - - Log.i("[Main Activity] Starting dialer with pre-filled URI $addressToCall") - val args = Bundle() - args.putString("URI", addressToCall) - navigateToDialer(args) - } - - private fun handleSendText(intent: Intent) { - if (corePreferences.disableChat) return - - intent.getStringExtra(Intent.EXTRA_TEXT)?.let { - sharedViewModel.textToShare.value = it - } - - handleSendChatRoom(intent) - } - - private suspend fun handleSendFile(intent: Intent) { - if (corePreferences.disableChat) return - - Log.i("[Main Activity] Found single file to share with type ${intent.type}") - - (intent.getParcelableExtra(Intent.EXTRA_STREAM) as? Uri)?.let { - val list = arrayListOf() - coroutineScope { - val deferred = async { - FileUtils.getFilePath(this@MainActivity, it) - } - val path = deferred.await() - if (path != null) { - list.add(path) - Log.i("[Main Activity] Found single file to share: $path") - } - } - sharedViewModel.filesToShare.value = list - } - - // Check that the current fragment hasn't already handled the event on filesToShare - // If it has, don't go further. - // For example this may happen when picking a GIF from the keyboard while inside a chat room - if (!sharedViewModel.filesToShare.value.isNullOrEmpty()) { - handleSendChatRoom(intent) - } - } - - private suspend fun handleSendMultipleFiles(intent: Intent) { - if (corePreferences.disableChat) return - - intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM)?.let { - val list = arrayListOf() - coroutineScope { - val deferred = arrayListOf>() - for (parcelable in it) { - val uri = parcelable as Uri - deferred.add(async { FileUtils.getFilePath(this@MainActivity, uri) }) - } - val paths = deferred.awaitAll() - for (path in paths) { - Log.i("[Main Activity] Found file to share: $path") - if (path != null) list.add(path) - } - } - sharedViewModel.filesToShare.value = list - } - - handleSendChatRoom(intent) - } - - private fun handleSendChatRoom(intent: Intent) { - if (corePreferences.disableChat) return - - val uri = intent.data - if (uri != null) { - Log.i("[Main Activity] Found uri: $uri to send a message to") - val stringUri = uri.toString() - var addressToIM: String = stringUri - try { - addressToIM = URLDecoder.decode(stringUri, "UTF-8") - } catch (e: UnsupportedEncodingException) { - Log.e("[Main Activity] UnsupportedEncodingException: $e") - } - - when { - addressToIM.startsWith("sms:") -> - addressToIM = addressToIM.substring("sms:".length) - addressToIM.startsWith("smsto:") -> - addressToIM = addressToIM.substring("smsto:".length) - addressToIM.startsWith("mms:") -> - addressToIM = addressToIM.substring("mms:".length) - addressToIM.startsWith("mmsto:") -> - addressToIM = addressToIM.substring("mmsto:".length) - } - - val localAddress = - coreContext.core.defaultAccount?.params?.identityAddress?.asStringUriOnly() - val peerAddress = coreContext.core.interpretUrl( - addressToIM, - LinphoneUtils.applyInternationalPrefix() - )?.asStringUriOnly() - Log.i( - "[Main Activity] Navigating to chat room with local [$localAddress] and peer [$peerAddress] addresses" - ) - navigateToChatRoom(localAddress, peerAddress) - } else { - val shortcutId = intent.getStringExtra("android.intent.extra.shortcut.ID") // Intent.EXTRA_SHORTCUT_ID - if (shortcutId != null) { - Log.i("[Main Activity] Found shortcut ID: $shortcutId") - handleLocusOrShortcut(shortcutId) - } else { - Log.i("[Main Activity] Going into chat rooms list") - navigateToChatRooms() - } - } - } - - private fun handleLocusOrShortcut(id: String) { - val split = id.split("~") - if (split.size == 2) { - val localAddress = split[0] - val peerAddress = split[1] - Log.i( - "[Main Activity] Navigating to chat room with local [$localAddress] and peer [$peerAddress] addresses, computed from shortcut/locus id" - ) - navigateToChatRoom(localAddress, peerAddress) - } else { - Log.e( - "[Main Activity] Failed to parse shortcut/locus id: $id, going to chat rooms list" - ) - navigateToChatRooms() - } - } - - private fun initOverlay() { - overlay = binding.root.findViewById(R.id.call_overlay) - val callOverlay = overlay - callOverlay ?: return - - callOverlay.setOnTouchListener { view, event -> - when (event.action) { - MotionEvent.ACTION_DOWN -> { - overlayX = view.x - event.rawX - overlayY = view.y - event.rawY - initPosX = view.x - initPosY = view.y - } - MotionEvent.ACTION_MOVE -> { - view.animate() - .x(event.rawX + overlayX) - .y(event.rawY + overlayY) - .setDuration(0) - .start() - } - MotionEvent.ACTION_UP -> { - if (abs(initPosX - view.x) < CorePreferences.OVERLAY_CLICK_SENSITIVITY && - abs(initPosY - view.y) < CorePreferences.OVERLAY_CLICK_SENSITIVITY - ) { - view.performClick() - } - } - else -> return@setOnTouchListener false - } - true - } - - callOverlay.setOnClickListener { - coreContext.onCallOverlayClick() - } - } - - private fun applyRemoteProvisioning(remoteConfigUri: String) { - coreContext.core.provisioningUri = remoteConfigUri - coreContext.core.stop() - coreContext.core.start() - } - - private fun showAcceptRemoteConfigurationDialog(remoteConfigUri: String) { - val dialogViewModel = DialogViewModel( - remoteConfigUri, - getString(R.string.dialog_apply_remote_provisioning_title) - ) - val dialog = DialogUtils.getDialog(this, dialogViewModel) - - dialogViewModel.showCancelButton { - Log.i("[Main Activity] User cancelled remote provisioning config") - dialog.dismiss() - } - - val okLabel = getString( - R.string.dialog_apply_remote_provisioning_button - ) - dialogViewModel.showOkButton( - { - Log.w( - "[Main Activity] Remote provisioning URL set to [$remoteConfigUri], restarting Core now" - ) - applyRemoteProvisioning(remoteConfigUri) - dialog.dismiss() - }, - okLabel - ) - - dialog.show() - } - - private fun showAuthenticationRequestedDialog( - authInfo: AuthInfo - ) { - authenticationRequiredDialog?.dismiss() - - val accountFound = coreContext.core.accountList.find { - it.params.identityAddress?.username == authInfo.username && it.params.identityAddress?.domain == authInfo.domain - } - if (accountFound == null) { - Log.w("[Main Activity] Failed to find account matching auth info, aborting auth dialog") - return - } - - val identity = "${authInfo.username}@${authInfo.domain}" - Log.i("[Main Activity] Showing authentication required dialog for account [$identity]") - - val dialogViewModel = DialogViewModel( - getString(R.string.dialog_authentication_required_message, identity), - getString(R.string.dialog_authentication_required_title) - ) - dialogViewModel.showPassword = true - dialogViewModel.passwordTitle = getString( - R.string.settings_password_protection_dialog_input_hint - ) - val dialog = DialogUtils.getDialog(this, dialogViewModel) - - dialogViewModel.showCancelButton { - dialog.dismiss() - authenticationRequiredDialog = null - } - - dialogViewModel.showOkButton( - { - Log.i( - "[Main Activity] Updating password for account [$identity] using auth info [$authInfo]" - ) - val newPassword = dialogViewModel.password - authInfo.password = newPassword - coreContext.core.addAuthInfo(authInfo) - - coreContext.core.refreshRegisters() - - dialog.dismiss() - authenticationRequiredDialog = null - }, - getString(R.string.dialog_authentication_required_change_password_label) - ) - - dialog.show() - authenticationRequiredDialog = dialog - } -} diff --git a/app/src/main/java/org/linphone/activities/main/about/AboutFragment.kt b/app/src/main/java/org/linphone/activities/main/about/AboutFragment.kt deleted file mode 100644 index e81703192..000000000 --- a/app/src/main/java/org/linphone/activities/main/about/AboutFragment.kt +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.about - -import android.content.* -import android.net.Uri -import android.os.Bundle -import android.view.View -import androidx.lifecycle.ViewModelProvider -import org.linphone.R -import org.linphone.activities.main.fragments.SecureFragment -import org.linphone.core.tools.Log -import org.linphone.databinding.AboutFragmentBinding - -class AboutFragment : SecureFragment() { - private lateinit var viewModel: AboutViewModel - - override fun getLayoutId(): Int = R.layout.about_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - viewModel = ViewModelProvider(this)[AboutViewModel::class.java] - binding.viewModel = viewModel - - binding.setPrivacyPolicyClickListener { - val browserIntent = Intent( - Intent.ACTION_VIEW, - Uri.parse(getString(R.string.about_privacy_policy_link)) - ) - try { - startActivity(browserIntent) - } catch (se: SecurityException) { - Log.e("[About] Failed to start browser intent, $se") - } - } - - binding.setLicenseClickListener { - val browserIntent = Intent( - Intent.ACTION_VIEW, - Uri.parse(getString(R.string.about_license_link)) - ) - try { - startActivity(browserIntent) - } catch (se: SecurityException) { - Log.e("[About] Failed to start browser intent, $se") - } - } - - binding.setWeblateClickListener { - val browserIntent = Intent( - Intent.ACTION_VIEW, - Uri.parse(getString(R.string.about_weblate_link)) - ) - try { - startActivity(browserIntent) - } catch (se: SecurityException) { - Log.e("[About] Failed to start browser intent, $se") - } - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/about/AboutViewModel.kt b/app/src/main/java/org/linphone/activities/main/about/AboutViewModel.kt deleted file mode 100644 index 2961af522..000000000 --- a/app/src/main/java/org/linphone/activities/main/about/AboutViewModel.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.about - -import androidx.lifecycle.ViewModel -import org.linphone.LinphoneApplication.Companion.coreContext - -class AboutViewModel : ViewModel() { - val appVersion: String = coreContext.appVersion - - val sdkVersion: String = coreContext.sdkVersion -} diff --git a/app/src/main/java/org/linphone/activities/main/adapters/SelectionListAdapter.kt b/app/src/main/java/org/linphone/activities/main/adapters/SelectionListAdapter.kt deleted file mode 100644 index e02070b29..000000000 --- a/app/src/main/java/org/linphone/activities/main/adapters/SelectionListAdapter.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.adapters - -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.ListAdapter -import androidx.recyclerview.widget.RecyclerView -import org.linphone.activities.main.viewmodels.ListTopBarViewModel - -abstract class SelectionListAdapter( - selectionVM: ListTopBarViewModel, - diff: DiffUtil.ItemCallback -) : - ListAdapter(diff) { - - private var _selectionViewModel: ListTopBarViewModel? = selectionVM - protected val selectionViewModel get() = _selectionViewModel!! - - override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) { - super.onDetachedFromRecyclerView(recyclerView) - _selectionViewModel = null - } -} diff --git a/app/src/main/java/org/linphone/activities/main/chat/ChatScrollListener.kt b/app/src/main/java/org/linphone/activities/main/chat/ChatScrollListener.kt deleted file mode 100644 index 795be99ed..000000000 --- a/app/src/main/java/org/linphone/activities/main/chat/ChatScrollListener.kt +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.chat - -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView - -internal abstract class ChatScrollListener(private val mLayoutManager: LinearLayoutManager) : - RecyclerView.OnScrollListener() { - // The total number of items in the data set after the last load - private var previousTotalItemCount = 0 - - // True if we are still waiting for the last set of data to load. - private var loading = true - - private var userHasScrolledUp: Boolean = false - - // This happens many times a second during a scroll, so be wary of the code you place here. - // We are given a few useful parameters to help us work out if we need to load some more data, - // but first we check if we are waiting for the previous load to finish. - override fun onScrolled(view: RecyclerView, dx: Int, dy: Int) { - val totalItemCount = mLayoutManager.itemCount - val firstVisibleItemPosition: Int = mLayoutManager.findFirstVisibleItemPosition() - val lastVisibleItemPosition: Int = mLayoutManager.findLastVisibleItemPosition() - - // If the total item count is zero and the previous isn't, assume the - // list is invalidated and should be reset back to initial state - if (totalItemCount < previousTotalItemCount) { - previousTotalItemCount = totalItemCount - if (totalItemCount == 0) { - loading = true - } - } - - // If it’s still loading, we check to see if the data set count has - // changed, if so we conclude it has finished loading and update the current page - // number and total item count. - if (loading && totalItemCount > previousTotalItemCount) { - loading = false - previousTotalItemCount = totalItemCount - } - - userHasScrolledUp = lastVisibleItemPosition != totalItemCount - 1 - if (userHasScrolledUp) { - onScrolledUp() - } else { - onScrolledToEnd() - } - - // If it isn’t currently loading, we check to see if we have breached - // the mVisibleThreshold and need to reload more data. - // If we do need to reload some more data, we execute onLoadMore to fetch the data. - // threshold should reflect how many total columns there are too - if (!loading && - firstVisibleItemPosition < mVisibleThreshold && - firstVisibleItemPosition >= 0 && - lastVisibleItemPosition < totalItemCount - mVisibleThreshold - ) { - onLoadMore(totalItemCount) - loading = true - } - } - - // Defines the process for actually loading more data based on page - protected abstract fun onLoadMore(totalItemsCount: Int) - - // Called when user has started to scroll up, opposed to onScrolledToEnd() - protected abstract fun onScrolledUp() - - // Called when user has scrolled and reached the end of the items - protected abstract fun onScrolledToEnd() - - companion object { - // The minimum amount of items to have below your current scroll position - // before loading more. - private const val mVisibleThreshold = 5 - } -} diff --git a/app/src/main/java/org/linphone/activities/main/chat/GroupChatRoomMember.kt b/app/src/main/java/org/linphone/activities/main/chat/GroupChatRoomMember.kt deleted file mode 100644 index 537b8017c..000000000 --- a/app/src/main/java/org/linphone/activities/main/chat/GroupChatRoomMember.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.chat - -import org.linphone.core.Address -import org.linphone.core.ChatRoom - -data class GroupChatRoomMember( - val address: Address, - var isAdmin: Boolean = false, - val securityLevel: ChatRoom.SecurityLevel = ChatRoom.SecurityLevel.ClearText, - val hasLimeX3DHCapability: Boolean = false, - // A participant not yet added to a group can't be set admin at the same time it's added - val canBeSetAdmin: Boolean = false -) diff --git a/app/src/main/java/org/linphone/activities/main/chat/adapters/ChatMessagesListAdapter.kt b/app/src/main/java/org/linphone/activities/main/chat/adapters/ChatMessagesListAdapter.kt deleted file mode 100644 index d922ccd93..000000000 --- a/app/src/main/java/org/linphone/activities/main/chat/adapters/ChatMessagesListAdapter.kt +++ /dev/null @@ -1,656 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.chat.adapters - -import android.content.ClipData -import android.content.ClipboardManager -import android.content.Context -import android.view.Gravity -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.PopupWindow -import android.widget.TextView -import androidx.databinding.DataBindingUtil -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.MutableLiveData -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.RecyclerView -import kotlin.math.abs -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.main.adapters.SelectionListAdapter -import org.linphone.activities.main.chat.data.ChatMessageData -import org.linphone.activities.main.chat.data.EventData -import org.linphone.activities.main.chat.data.EventLogData -import org.linphone.activities.main.chat.data.OnContentClickedListener -import org.linphone.activities.main.viewmodels.ListTopBarViewModel -import org.linphone.core.* -import org.linphone.core.tools.Log -import org.linphone.databinding.ChatEventListCellBinding -import org.linphone.databinding.ChatMessageListCellBinding -import org.linphone.databinding.ChatMessageLongPressMenuBindingImpl -import org.linphone.databinding.ChatUnreadMessagesListHeaderBinding -import org.linphone.utils.AppUtils -import org.linphone.utils.Event -import org.linphone.utils.HeaderAdapter - -class ChatMessagesListAdapter( - selectionVM: ListTopBarViewModel, - private val viewLifecycleOwner: LifecycleOwner -) : SelectionListAdapter( - selectionVM, - ChatMessageDiffCallback() -), - HeaderAdapter { - companion object { - const val MAX_TIME_TO_GROUP_MESSAGES = 60 // 1 minute - } - - val resendMessageEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val deleteMessageEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val forwardMessageEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val replyMessageEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val showImdnForMessageEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val addSipUriToContactEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val openContentEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val urlClickEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val sipUriClickedEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val callConferenceEvent: MutableLiveData>> by lazy { - MutableLiveData>>() - } - - val scrollToChatMessageEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val showReactionsListEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val errorEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - private val contentClickedListener = object : OnContentClickedListener { - override fun onContentClicked(content: Content) { - openContentEvent.value = Event(content) - } - - override fun onWebUrlClicked(url: String) { - if (popup?.isShowing == true) { - Log.w( - "[Chat Message Data] Long press that displayed context menu detected, aborting click on URL [$url]" - ) - return - } - val urlWithScheme = if (!url.startsWith("http")) "http://$url" else url - urlClickEvent.value = Event(urlWithScheme) - } - - override fun onSipAddressClicked(sipUri: String) { - if (popup?.isShowing == true) { - Log.w( - "[Chat Message Data] Long press that displayed context menu detected, aborting click on SIP URI [$sipUri]" - ) - return - } - sipUriClickedEvent.value = Event(sipUri) - } - - override fun onEmailAddressClicked(email: String) { - if (popup?.isShowing == true) { - Log.w( - "[Chat Message Data] Long press that displayed context menu detected, aborting click on email address [$email]" - ) - return - } - val urlWithScheme = if (!email.startsWith("mailto:")) "mailto:$email" else email - urlClickEvent.value = Event(urlWithScheme) - } - - override fun onCallConference(address: String, subject: String?) { - callConferenceEvent.value = Event(Pair(address, subject)) - } - - override fun onShowReactionsList(chatMessage: ChatMessage) { - showReactionsListEvent.value = Event(chatMessage) - } - - override fun onError(messageId: Int) { - errorEvent.value = Event(messageId) - } - } - - private var advancedContextMenuOptionsDisabled: Boolean = false - private var popup: PopupWindow? = null - - private var unreadMessagesCount: Int = 0 - private var firstUnreadMessagePosition: Int = -1 - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { - return when (viewType) { - EventLog.Type.ConferenceChatMessage.toInt() -> createChatMessageViewHolder(parent) - else -> createEventViewHolder(parent) - } - } - - private fun createChatMessageViewHolder(parent: ViewGroup): ChatMessageViewHolder { - val binding: ChatMessageListCellBinding = DataBindingUtil.inflate( - LayoutInflater.from(parent.context), - R.layout.chat_message_list_cell, - parent, - false - ) - return ChatMessageViewHolder(binding) - } - - private fun createEventViewHolder(parent: ViewGroup): EventViewHolder { - val binding: ChatEventListCellBinding = DataBindingUtil.inflate( - LayoutInflater.from(parent.context), - R.layout.chat_event_list_cell, - parent, - false - ) - return EventViewHolder(binding) - } - - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - val eventLog = getItem(position) - when (holder) { - is ChatMessageViewHolder -> holder.bind(eventLog) - is EventViewHolder -> holder.bind(eventLog) - } - } - - override fun getItemViewType(position: Int): Int { - val eventLog = getItem(position) - return eventLog.eventLog.type.toInt() - } - - override fun onCurrentListChanged( - previousList: MutableList, - currentList: MutableList - ) { - Log.i( - "[Chat Messages Adapter] List has changed, clearing previous first unread message position" - ) - // Need to wait for messages to be added before computing new first unread message position - firstUnreadMessagePosition = -1 - } - - override fun displayHeaderForPosition(position: Int): Boolean { - Log.i( - "[Chat Messages Adapter] Unread message count is [$unreadMessagesCount], first unread message position is [$firstUnreadMessagePosition]" - ) - if (unreadMessagesCount > 0 && firstUnreadMessagePosition == -1) { - computeFirstUnreadMessagePosition() - } - return position == firstUnreadMessagePosition - } - - override fun getHeaderViewForPosition(context: Context, position: Int): View { - val binding: ChatUnreadMessagesListHeaderBinding = DataBindingUtil.inflate( - LayoutInflater.from(context), - R.layout.chat_unread_messages_list_header, - null, - false - ) - binding.title = AppUtils.getStringWithPlural( - R.plurals.chat_room_unread_messages_event, - unreadMessagesCount - ) - binding.executePendingBindings() - return binding.root - } - - fun disableAdvancedContextMenuOptions() { - advancedContextMenuOptionsDisabled = true - } - - fun setUnreadMessageCount(count: Int, forceUpdate: Boolean) { - Log.i("[Chat Messages Adapter] [$count] unread message in chat room") - // Once list has been filled once, don't show the unread message header - // when new messages are added to the history whilst it is visible - unreadMessagesCount = if (itemCount == 0 || forceUpdate) count else 0 - firstUnreadMessagePosition = -1 - Log.i( - "[Chat Messages Adapter] Set [$unreadMessagesCount] unread message(s) for current chat room" - ) - } - - fun getFirstUnreadMessagePosition(): Int { - Log.i( - "[Chat Messages Adapter] First unread message position is [$firstUnreadMessagePosition]" - ) - return firstUnreadMessagePosition - } - - private fun computeFirstUnreadMessagePosition() { - Log.i( - "[Chat Messages Adapter] [$unreadMessagesCount] unread message(s) for current chat room" - ) - if (unreadMessagesCount > 0) { - Log.i("[Chat Messages Adapter] Computing first unread message position") - var messageCount = 0 - for (position in itemCount - 1 downTo 0) { - val eventLog = getItem(position) - val data = eventLog.data - if (data is ChatMessageData) { - messageCount += 1 - if (messageCount == unreadMessagesCount) { - firstUnreadMessagePosition = position - Log.i( - "[Chat Messages Adapter] First unread message position found [$firstUnreadMessagePosition]" - ) - break - } - } - } - } - } - - inner class ChatMessageViewHolder( - val binding: ChatMessageListCellBinding - ) : RecyclerView.ViewHolder(binding.root) { - fun bind(eventLog: EventLogData) { - with(binding) { - if (eventLog.eventLog.type == EventLog.Type.ConferenceChatMessage) { - val chatMessageData = eventLog.data as ChatMessageData - chatMessageData.setContentClickListener(contentClickedListener) - - val chatMessage = chatMessageData.chatMessage - data = chatMessageData - - chatMessageData.contactNewlyFoundEvent.observe(viewLifecycleOwner) { - it.consume { - // Post to prevent IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling - binding.root.post { - try { - notifyItemChanged(bindingAdapterPosition) - } catch (e: Exception) { - Log.e( - "[Chat Messages Adapter] Can't notify item [$bindingAdapterPosition] has changed: $e" - ) - } - } - } - } - - lifecycleOwner = viewLifecycleOwner - - // This is for item selection through ListTopBarFragment - selectionListViewModel = selectionViewModel - selectionViewModel.isEditionEnabled.observe( - viewLifecycleOwner - ) { - position = bindingAdapterPosition - } - - setClickListener { - if (selectionViewModel.isEditionEnabled.value == true) { - selectionViewModel.onToggleSelect(bindingAdapterPosition) - } - } - - setReplyClickListener { - val reply = chatMessageData.replyData.value?.chatMessage - if (reply != null) { - scrollToChatMessageEvent.value = Event(reply) - } - } - - // Grouping - var hasPrevious = false - var hasNext = false - - if (bindingAdapterPosition > 0) { - val previousItem = getItem(bindingAdapterPosition - 1) - if (previousItem.eventLog.type == EventLog.Type.ConferenceChatMessage) { - val previousMessage = previousItem.eventLog.chatMessage - if (previousMessage != null && previousMessage.fromAddress.weakEqual( - chatMessage.fromAddress - ) - ) { - if (abs(chatMessage.time - previousMessage.time) < MAX_TIME_TO_GROUP_MESSAGES) { - hasPrevious = true - } - } - } - } - - if (bindingAdapterPosition >= 0 && bindingAdapterPosition < itemCount - 1) { - val nextItem = getItem(bindingAdapterPosition + 1) - if (nextItem.eventLog.type == EventLog.Type.ConferenceChatMessage) { - val nextMessage = nextItem.eventLog.chatMessage - if (nextMessage != null && nextMessage.fromAddress.weakEqual( - chatMessage.fromAddress - ) - ) { - if (abs(nextMessage.time - chatMessage.time) < MAX_TIME_TO_GROUP_MESSAGES) { - hasNext = true - } - } - } - } - - chatMessageData.updateBubbleBackground(hasPrevious, hasNext) - - executePendingBindings() - - setContextMenuClickListener { - val popupView: ChatMessageLongPressMenuBindingImpl = DataBindingUtil.inflate( - LayoutInflater.from(root.context), - R.layout.chat_message_long_press_menu, - null, - false - ) - - val itemSize = AppUtils.getDimension(R.dimen.chat_message_popup_item_height).toInt() - var totalSize = itemSize * 8 - if (chatMessage.chatRoom.hasCapability( - ChatRoom.Capabilities.OneToOne.toInt() - ) - ) { - // No message id - popupView.imdnHidden = true - totalSize -= itemSize - } - if (chatMessage.state != ChatMessage.State.NotDelivered) { - popupView.resendHidden = true - totalSize -= itemSize - } - if (chatMessage.contents.find { content -> content.isText } == null) { - popupView.copyTextHidden = true - totalSize -= itemSize - } - if (chatMessage.isOutgoing || - chatMessageData.contact.value != null || - advancedContextMenuOptionsDisabled || - corePreferences.readOnlyNativeContacts - ) { - popupView.addToContactsHidden = true - totalSize -= itemSize - } - if (chatMessage.chatRoom.isReadOnly) { - popupView.replyHidden = true - totalSize -= itemSize - } - if (advancedContextMenuOptionsDisabled) { - popupView.forwardHidden = true - totalSize -= itemSize - } - - val reaction = chatMessage.ownReaction - if (reaction != null) { - when (reaction.body) { - AppUtils.getString(R.string.emoji_love) -> { - popupView.heartSelected = true - } - AppUtils.getString(R.string.emoji_laughing) -> { - popupView.laughingSelected = true - } - AppUtils.getString(R.string.emoji_surprised) -> { - popupView.surprisedSelected = true - } - AppUtils.getString(R.string.emoji_thumbs_up) -> { - popupView.thumbsUpSelected = true - } - AppUtils.getString(R.string.emoji_tear) -> { - popupView.cryingSelected = true - } - } - } - - // When using WRAP_CONTENT instead of real size, fails to place the - // popup window above if not enough space is available below - val popupWindow = PopupWindow( - popupView.root, - AppUtils.getDimension(R.dimen.chat_message_popup_width).toInt(), - totalSize, - true - ) - popup = popupWindow - - // Elevation is for showing a shadow around the popup - popupWindow.elevation = 20f - - popupView.setEmojiClickListener { - val emoji = it as? TextView - if (emoji != null) { - reactToMessage(emoji.text.toString()) - popupWindow.dismiss() - } - } - popupView.setResendClickListener { - resendMessage() - popupWindow.dismiss() - } - popupView.setCopyTextClickListener { - copyTextToClipboard() - popupWindow.dismiss() - } - popupView.setForwardClickListener { - forwardMessage() - popupWindow.dismiss() - } - popupView.setReplyClickListener { - replyMessage() - popupWindow.dismiss() - } - popupView.setImdnClickListener { - showImdnDeliveryFragment() - popupWindow.dismiss() - } - popupView.setAddToContactsClickListener { - addSenderToContacts() - popupWindow.dismiss() - } - popupView.setDeleteClickListener { - deleteMessage() - popupWindow.dismiss() - } - - val gravity = if (chatMessage.isOutgoing) Gravity.END else Gravity.START - popupWindow.showAsDropDown(background, 0, 0, gravity or Gravity.TOP) - - true - } - } - } - } - - private fun reactToMessage(reaction: String) { - val chatMessage = binding.data?.chatMessage - if (chatMessage != null) { - val ownReaction = chatMessage.ownReaction - if (ownReaction != null && ownReaction.body == reaction) { - Log.i( - "[Chat Message Data] Removing our reaction to message [$chatMessage] (previously [$reaction])" - ) - // Empty string means remove existing reaction - val reactionMessage = chatMessage.createReaction("") - reactionMessage.send() - } else { - Log.i( - "[Chat Message Data] Reacting to message [$chatMessage] with [$reaction] emoji" - ) - val reactionMessage = chatMessage.createReaction(reaction) - reactionMessage.send() - } - } - } - - private fun resendMessage() { - val chatMessage = binding.data?.chatMessage - if (chatMessage != null) { - chatMessage.userData = bindingAdapterPosition - resendMessageEvent.value = Event(chatMessage) - } - } - - private fun copyTextToClipboard() { - val chatMessage = binding.data?.chatMessage - if (chatMessage != null) { - val content = chatMessage.contents.find { content -> content.isText } - if (content != null) { - val clipboard: ClipboardManager = - coreContext.context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - val clip = ClipData.newPlainText("Message", content.utf8Text) - clipboard.setPrimaryClip(clip) - } - } - } - - private fun forwardMessage() { - val chatMessage = binding.data?.chatMessage - if (chatMessage != null) { - forwardMessageEvent.value = Event(chatMessage) - } - } - - private fun replyMessage() { - val chatMessage = binding.data?.chatMessage - if (chatMessage != null) { - replyMessageEvent.value = Event(chatMessage) - } - } - - private fun showImdnDeliveryFragment() { - val chatMessage = binding.data?.chatMessage - if (chatMessage != null) { - showImdnForMessageEvent.value = Event(chatMessage) - } - } - - private fun deleteMessage() { - val chatMessage = binding.data?.chatMessage - if (chatMessage != null) { - chatMessage.userData = bindingAdapterPosition - deleteMessageEvent.value = Event(chatMessage) - } - } - - private fun addSenderToContacts() { - val chatMessage = binding.data?.chatMessage - if (chatMessage != null) { - val copy = chatMessage.fromAddress.clone() - copy.clean() // To remove gruu if any - addSipUriToContactEvent.value = Event(copy.asStringUriOnly()) - } - } - } - - inner class EventViewHolder( - private val binding: ChatEventListCellBinding - ) : RecyclerView.ViewHolder(binding.root) { - fun bind(eventLog: EventLogData) { - with(binding) { - val eventViewModel = eventLog.data as EventData - data = eventViewModel - - binding.lifecycleOwner = viewLifecycleOwner - - // This is for item selection through ListTopBarFragment - selectionListViewModel = selectionViewModel - selectionViewModel.isEditionEnabled.observe( - viewLifecycleOwner - ) { - position = bindingAdapterPosition - } - - binding.setClickListener { - if (selectionViewModel.isEditionEnabled.value == true) { - selectionViewModel.onToggleSelect(bindingAdapterPosition) - } - } - - executePendingBindings() - } - } - } -} - -private class ChatMessageDiffCallback : DiffUtil.ItemCallback() { - override fun areItemsTheSame( - oldItem: EventLogData, - newItem: EventLogData - ): Boolean { - return if (oldItem.type == EventLog.Type.ConferenceChatMessage && - newItem.type == EventLog.Type.ConferenceChatMessage - ) { - val oldData = (oldItem.data as ChatMessageData) - val newData = (newItem.data as ChatMessageData) - - oldData.time.value == newData.time.value && - oldData.isOutgoing == newData.isOutgoing - } else { - oldItem.notifyId == newItem.notifyId - } - } - - override fun areContentsTheSame( - oldItem: EventLogData, - newItem: EventLogData - ): Boolean { - return if (oldItem.type == EventLog.Type.ConferenceChatMessage && - newItem.type == EventLog.Type.ConferenceChatMessage - ) { - val oldData = (oldItem.data as ChatMessageData) - val newData = (newItem.data as ChatMessageData) - - val previous = oldData.hasPreviousMessage == newData.hasPreviousMessage - val next = oldData.hasNextMessage == newData.hasNextMessage - val isDisplayed = newData.isDisplayed.value == true - isDisplayed && previous && next - } else { - oldItem.type != EventLog.Type.ConferenceChatMessage && - newItem.type != EventLog.Type.ConferenceChatMessage - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/chat/adapters/ChatRoomsListAdapter.kt b/app/src/main/java/org/linphone/activities/main/chat/adapters/ChatRoomsListAdapter.kt deleted file mode 100644 index 12e6c7157..000000000 --- a/app/src/main/java/org/linphone/activities/main/chat/adapters/ChatRoomsListAdapter.kt +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.chat.adapters - -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.databinding.DataBindingUtil -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.MutableLiveData -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.RecyclerView -import org.linphone.R -import org.linphone.activities.main.adapters.SelectionListAdapter -import org.linphone.activities.main.chat.data.ChatRoomData -import org.linphone.activities.main.viewmodels.ListTopBarViewModel -import org.linphone.core.ChatRoom -import org.linphone.core.tools.Log -import org.linphone.databinding.ChatRoomListCellBinding -import org.linphone.utils.Event - -class ChatRoomsListAdapter( - selectionVM: ListTopBarViewModel, - private val viewLifecycleOwner: LifecycleOwner -) : SelectionListAdapter(selectionVM, ChatRoomDiffCallback()) { - val selectedChatRoomEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - private var isForwardPending = false - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - val binding: ChatRoomListCellBinding = DataBindingUtil.inflate( - LayoutInflater.from(parent.context), - R.layout.chat_room_list_cell, - parent, - false - ) - return ViewHolder(binding) - } - - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - (holder as ViewHolder).bind(getItem(position)) - } - - fun forwardPending(pending: Boolean) { - isForwardPending = pending - notifyItemRangeChanged(0, itemCount) - } - - inner class ViewHolder( - private val binding: ChatRoomListCellBinding - ) : RecyclerView.ViewHolder(binding.root) { - fun bind(chatRoomData: ChatRoomData) { - with(binding) { - chatRoomData.update() - data = chatRoomData - - chatRoomData.contactNewlyFoundEvent.observe(viewLifecycleOwner) { - it.consume { - // Post to prevent IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling - binding.root.post { - try { - notifyItemChanged(bindingAdapterPosition) - } catch (e: Exception) { - Log.e( - "[Chat Rooms Adapter] Can't notify item [$bindingAdapterPosition] has changed: $e" - ) - } - } - } - } - - lifecycleOwner = viewLifecycleOwner - - // This is for item selection through ListTopBarFragment - selectionListViewModel = selectionViewModel - selectionViewModel.isEditionEnabled.observe( - viewLifecycleOwner - ) { - position = bindingAdapterPosition - } - - forwardPending = isForwardPending - - setClickListener { - if (selectionViewModel.isEditionEnabled.value == true) { - selectionViewModel.onToggleSelect(bindingAdapterPosition) - } else { - selectedChatRoomEvent.value = Event(chatRoomData.chatRoom) - } - } - - setLongClickListener { - if (selectionViewModel.isEditionEnabled.value == false) { - selectionViewModel.isEditionEnabled.value = true - // Selection will be handled by click listener - true - } - false - } - - executePendingBindings() - } - } - } -} - -private class ChatRoomDiffCallback : DiffUtil.ItemCallback() { - override fun areItemsTheSame( - oldItem: ChatRoomData, - newItem: ChatRoomData - ): Boolean { - return oldItem.id == newItem.id - } - - override fun areContentsTheSame( - oldItem: ChatRoomData, - newItem: ChatRoomData - ): Boolean { - return false // To force redraw - } -} diff --git a/app/src/main/java/org/linphone/activities/main/chat/adapters/GroupInfoParticipantsAdapter.kt b/app/src/main/java/org/linphone/activities/main/chat/adapters/GroupInfoParticipantsAdapter.kt deleted file mode 100644 index b9d13bc60..000000000 --- a/app/src/main/java/org/linphone/activities/main/chat/adapters/GroupInfoParticipantsAdapter.kt +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.chat.adapters - -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.databinding.DataBindingUtil -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.MutableLiveData -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.ListAdapter -import androidx.recyclerview.widget.RecyclerView -import org.linphone.R -import org.linphone.activities.main.chat.GroupChatRoomMember -import org.linphone.activities.main.chat.data.GroupInfoParticipantData -import org.linphone.databinding.ChatRoomGroupInfoParticipantCellBinding -import org.linphone.utils.Event - -class GroupInfoParticipantsAdapter( - private val viewLifecycleOwner: LifecycleOwner, - private val isEncryptionEnabled: Boolean -) : ListAdapter(ParticipantDiffCallback()) { - private var showAdmin: Boolean = false - - val participantRemovedEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - val binding: ChatRoomGroupInfoParticipantCellBinding = DataBindingUtil.inflate( - LayoutInflater.from(parent.context), - R.layout.chat_room_group_info_participant_cell, - parent, - false - ) - return ViewHolder(binding) - } - - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - (holder as ViewHolder).bind(getItem(position)) - } - - fun showAdminControls(show: Boolean) { - showAdmin = show - notifyItemRangeChanged(0, itemCount) - } - - inner class ViewHolder( - val binding: ChatRoomGroupInfoParticipantCellBinding - ) : RecyclerView.ViewHolder(binding.root) { - fun bind(participantViewModel: GroupInfoParticipantData) { - with(binding) { - participantViewModel.showAdminControls.value = showAdmin - data = participantViewModel - - lifecycleOwner = viewLifecycleOwner - - setRemoveClickListener { - participantRemovedEvent.value = Event(participantViewModel.participant) - } - isEncrypted = isEncryptionEnabled - - executePendingBindings() - } - } - } -} - -private class ParticipantDiffCallback : DiffUtil.ItemCallback() { - override fun areItemsTheSame( - oldItem: GroupInfoParticipantData, - newItem: GroupInfoParticipantData - ): Boolean { - return oldItem.sipUri == newItem.sipUri - } - - override fun areContentsTheSame( - oldItem: GroupInfoParticipantData, - newItem: GroupInfoParticipantData - ): Boolean { - return false - } -} diff --git a/app/src/main/java/org/linphone/activities/main/chat/adapters/ImdnAdapter.kt b/app/src/main/java/org/linphone/activities/main/chat/adapters/ImdnAdapter.kt deleted file mode 100644 index 3226780ea..000000000 --- a/app/src/main/java/org/linphone/activities/main/chat/adapters/ImdnAdapter.kt +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.chat.adapters - -import android.content.Context -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.databinding.DataBindingUtil -import androidx.lifecycle.LifecycleOwner -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.ListAdapter -import androidx.recyclerview.widget.RecyclerView -import org.linphone.R -import org.linphone.activities.main.chat.data.ImdnParticipantData -import org.linphone.core.ChatMessage -import org.linphone.databinding.ChatRoomImdnParticipantCellBinding -import org.linphone.databinding.ImdnListHeaderBinding -import org.linphone.utils.HeaderAdapter - -class ImdnAdapter( - private val viewLifecycleOwner: LifecycleOwner -) : ListAdapter(ParticipantImdnStateDiffCallback()), HeaderAdapter { - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - val binding: ChatRoomImdnParticipantCellBinding = DataBindingUtil.inflate( - LayoutInflater.from(parent.context), - R.layout.chat_room_imdn_participant_cell, - parent, - false - ) - return ViewHolder(binding) - } - - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - (holder as ViewHolder).bind(getItem(position)) - } - - inner class ViewHolder( - val binding: ChatRoomImdnParticipantCellBinding - ) : RecyclerView.ViewHolder(binding.root) { - fun bind(participantImdnData: ImdnParticipantData) { - with(binding) { - data = participantImdnData - - lifecycleOwner = viewLifecycleOwner - - executePendingBindings() - } - } - } - - override fun displayHeaderForPosition(position: Int): Boolean { - if (position >= itemCount) return false - val participantImdnState = getItem(position) - val previousPosition = position - 1 - return if (previousPosition >= 0) { - getItem(previousPosition).imdnState.state != participantImdnState.imdnState.state - } else { - true - } - } - - override fun getHeaderViewForPosition(context: Context, position: Int): View { - val participantImdnState = getItem(position).imdnState - val binding: ImdnListHeaderBinding = DataBindingUtil.inflate( - LayoutInflater.from(context), - R.layout.imdn_list_header, - null, - false - ) - when (participantImdnState.state) { - ChatMessage.State.Displayed -> { - binding.title = R.string.chat_message_imdn_displayed - binding.textColor = R.color.imdn_read_color - binding.icon = R.drawable.chat_read - } - ChatMessage.State.DeliveredToUser -> { - binding.title = R.string.chat_message_imdn_delivered - binding.textColor = R.color.grey_color - binding.icon = R.drawable.chat_delivered - } - ChatMessage.State.Delivered -> { - binding.title = R.string.chat_message_imdn_sent - binding.textColor = R.color.grey_color - binding.icon = R.drawable.chat_delivered - } - ChatMessage.State.NotDelivered -> { - binding.title = R.string.chat_message_imdn_undelivered - binding.textColor = R.color.red_color - binding.icon = R.drawable.chat_error - } - else -> {} - } - binding.executePendingBindings() - return binding.root - } -} - -private class ParticipantImdnStateDiffCallback : DiffUtil.ItemCallback() { - override fun areItemsTheSame( - oldItem: ImdnParticipantData, - newItem: ImdnParticipantData - ): Boolean { - return oldItem.sipUri == newItem.sipUri - } - - override fun areContentsTheSame( - oldItem: ImdnParticipantData, - newItem: ImdnParticipantData - ): Boolean { - return false - } -} diff --git a/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageAttachmentData.kt b/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageAttachmentData.kt deleted file mode 100644 index fc66b68a5..000000000 --- a/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageAttachmentData.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.chat.data - -import android.webkit.MimeTypeMap -import org.linphone.utils.FileUtils - -class ChatMessageAttachmentData( - val path: String, - private val deleteCallback: (attachment: ChatMessageAttachmentData) -> Unit -) { - val fileName: String = FileUtils.getNameFromFilePath(path) - val isImage: Boolean - val isVideo: Boolean - val isAudio: Boolean - val isPdf: Boolean - - init { - val extension = FileUtils.getExtensionFromFileName(path) - val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) - val mimeType = FileUtils.getMimeType(mime) - isImage = mimeType == FileUtils.MimeType.Image - isVideo = mimeType == FileUtils.MimeType.Video - isAudio = mimeType == FileUtils.MimeType.Audio - isPdf = mimeType == FileUtils.MimeType.Pdf - } - - fun delete() { - deleteCallback(this) - } -} diff --git a/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageContentData.kt b/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageContentData.kt deleted file mode 100644 index 451a89eb7..000000000 --- a/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageContentData.kt +++ /dev/null @@ -1,560 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.chat.data - -import android.text.Spannable -import android.text.SpannableString -import android.text.Spanned -import android.text.style.UnderlineSpan -import android.webkit.MimeTypeMap -import android.widget.Toast -import androidx.lifecycle.MutableLiveData -import androidx.media.AudioFocusRequestCompat -import java.io.BufferedReader -import java.io.FileReader -import java.lang.StringBuilder -import java.text.SimpleDateFormat -import java.util.* -import java.util.concurrent.TimeUnit -import kotlinx.coroutines.* -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R -import org.linphone.core.* -import org.linphone.core.tools.Log -import org.linphone.utils.AppUtils -import org.linphone.utils.AudioRouteUtils -import org.linphone.utils.FileUtils -import org.linphone.utils.TimestampUtils - -class ChatMessageContentData( - private val chatMessage: ChatMessage, - private val contentIndex: Int -) { - var listener: OnContentClickedListener? = null - - val isOutgoing = chatMessage.isOutgoing - - val isImage = MutableLiveData() - val isVideo = MutableLiveData() - val isAudio = MutableLiveData() - val isPdf = MutableLiveData() - val isGenericFile = MutableLiveData() - val isVoiceRecording = MutableLiveData() - val isConferenceSchedule = MutableLiveData() - val isConferenceUpdated = MutableLiveData() - val isConferenceCancelled = MutableLiveData() - val isBroadcast = MutableLiveData() - val isSpeaker = MutableLiveData() - - val fileName = MutableLiveData() - val filePath = MutableLiveData() - - val downloadable = MutableLiveData() - val fileTransferProgress = MutableLiveData() - val fileTransferProgressInt = MutableLiveData() - val downloadLabel = MutableLiveData() - - val voiceRecordDuration = MutableLiveData() - val formattedDuration = MutableLiveData() - val voiceRecordPlayingPosition = MutableLiveData() - val isVoiceRecordPlaying = MutableLiveData() - - val conferenceSubject = MutableLiveData() - val conferenceDescription = MutableLiveData() - val conferenceParticipantCount = MutableLiveData() - val conferenceDate = MutableLiveData() - val conferenceTime = MutableLiveData() - val conferenceDuration = MutableLiveData() - val showDuration = MutableLiveData() - - val isAlone: Boolean - get() { - var count = 0 - for (content in chatMessage.contents) { - if (content.isFileTransfer || content.isFile) { - count += 1 - } - } - return count == 1 - } - - private var isFileEncrypted: Boolean = false - - private var voiceRecordAudioFocusRequest: AudioFocusRequestCompat? = null - - private lateinit var voiceRecordingPlayer: Player - private val playerListener = PlayerListener { - Log.i("[Voice Recording] End of file reached") - stopVoiceRecording() - } - - private var conferenceAddress: String? = null - - private fun getContent(): Content { - return chatMessage.contents[contentIndex] - } - - private val chatMessageListener: ChatMessageListenerStub = object : ChatMessageListenerStub() { - override fun onFileTransferProgressIndication( - message: ChatMessage, - c: Content, - offset: Int, - total: Int - ) { - if (c.filePath == getContent().filePath) { - if (fileTransferProgress.value == false) { - fileTransferProgress.value = true - } - val percent = ((offset * 100.0) / total).toInt() // Conversion from int to double and back to int is required - Log.d("[Content] Transfer progress is: $offset / $total -> $percent%") - fileTransferProgressInt.value = percent - } - } - - override fun onMsgStateChanged(message: ChatMessage, state: ChatMessage.State) { - if (state == ChatMessage.State.FileTransferDone || state == ChatMessage.State.FileTransferError) { - fileTransferProgress.value = false - updateContent() - - if (state == ChatMessage.State.FileTransferDone) { - Log.i("[Chat Message] File transfer done") - if (!message.isOutgoing && !message.isEphemeral) { - Log.i("[Chat Message] Adding content to media store") - coreContext.addContentToMediaStore(getContent()) - } - } - } - } - } - - private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) - - init { - isVoiceRecordPlaying.value = false - voiceRecordDuration.value = 0 - voiceRecordPlayingPosition.value = 0 - fileTransferProgress.value = false - fileTransferProgressInt.value = 0 - - updateContent() - chatMessage.addListener(chatMessageListener) - } - - fun destroy() { - scope.cancel() - - deletePlainFilePath() - chatMessage.removeListener(chatMessageListener) - - if (this::voiceRecordingPlayer.isInitialized) { - Log.i("[Voice Recording] Destroying voice record") - stopVoiceRecording() - voiceRecordingPlayer.removeListener(playerListener) - } - } - - fun download() { - if (chatMessage.isFileTransferInProgress) { - Log.w( - "[Content] Another FileTransfer content for this message is currently being downloaded, can't start another one for now" - ) - listener?.onError(R.string.chat_message_download_already_in_progress) - return - } - - val content = getContent() - val filePath = content.filePath - if (content.isFileTransfer) { - if (filePath.isNullOrEmpty()) { - val contentName = content.name - if (contentName != null) { - val file = FileUtils.getFileStoragePath(contentName) - content.filePath = file.path - Log.i("[Content] Started downloading $contentName into ${content.filePath}") - } else { - Log.e("[Content] Content name is null, can't download it!") - return - } - } else { - Log.w( - "[Content] File path already set [$filePath] using it (auto download that failed probably)" - ) - } - - if (!chatMessage.downloadContent(content)) { - Log.e("[Content] Failed to start content download!") - } - } else { - Log.e("[Content] Content is not a FileTransfer, can't download it!") - } - } - - fun openFile() { - listener?.onContentClicked(getContent()) - } - - private fun deletePlainFilePath() { - val path = filePath.value.orEmpty() - if (path.isNotEmpty() && isFileEncrypted) { - Log.i("[Content] [VFS] Deleting file used for preview: $path") - FileUtils.deleteFile(path) - filePath.value = "" - } - } - - private fun updateContent() { - Log.i("[Content] Updating content") - deletePlainFilePath() - - val content = getContent() - isFileEncrypted = content.isFileEncrypted - Log.i( - "[Content] Is ${if (content.isFile) "file" else "file transfer"} content encrypted ? $isFileEncrypted" - ) - - val contentName = content.name - val contentFilePath = content.filePath - val name = if (contentName.isNullOrEmpty()) { - if (!contentFilePath.isNullOrEmpty()) { - FileUtils.getNameFromFilePath(contentFilePath) - } else { - "" - } - } else { - contentName - } - fileName.value = name - filePath.value = "" - - // Display download size and underline text - val fileSize = AppUtils.bytesToDisplayableSize(content.fileSize.toLong()) - val spannable = SpannableString( - "${AppUtils.getString(R.string.chat_message_download_file)} ($fileSize)" - ) - spannable.setSpan(UnderlineSpan(), 0, spannable.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - downloadLabel.value = spannable - - isImage.value = false - isVideo.value = false - isAudio.value = false - isPdf.value = false - isVoiceRecording.value = false - isConferenceSchedule.value = false - isConferenceUpdated.value = false - isConferenceCancelled.value = false - - if (content.isFile || (content.isFileTransfer && chatMessage.isOutgoing)) { - val path = if (isFileEncrypted) { - Log.i( - "[Content] [VFS] Content is encrypted, requesting plain file path for file [${content.filePath}]" - ) - content.exportPlainFile() - } else { - content.filePath ?: "" - } - downloadable.value = content.filePath.orEmpty().isEmpty() - - val isVoiceRecord = content.isVoiceRecording - isVoiceRecording.value = isVoiceRecord - - val isConferenceIcs = content.isIcalendar - isConferenceSchedule.value = isConferenceIcs - - if (path.isNotEmpty()) { - filePath.value = path - val extension = FileUtils.getExtensionFromFileName(path) - val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) - val type = when (FileUtils.getMimeType(mime)) { - FileUtils.MimeType.Image -> { - isImage.value = true - "image" - } - FileUtils.MimeType.Video -> { - isVideo.value = !isVoiceRecord - if (isVoiceRecord) "voice recording" else "video" - } - FileUtils.MimeType.Audio -> { - isAudio.value = !isVoiceRecord - if (isVoiceRecord) "voice recording" else "audio" - } - FileUtils.MimeType.Pdf -> { - isPdf.value = true - "pdf" - } - else -> { - if (isConferenceIcs) "conference invitation" else "unknown" - } - } - Log.i( - "[Content] Extension for file [$path] is [$extension], deduced type from MIME is [$type]" - ) - - if (isVoiceRecord) { - val duration = content.fileDuration // duration is in ms - voiceRecordDuration.value = duration - formattedDuration.value = SimpleDateFormat("mm:ss", Locale.getDefault()).format( - duration - ) - Log.i( - "[Content] Voice recording duration is ${voiceRecordDuration.value} ($duration)" - ) - } else if (isConferenceIcs) { - parseConferenceInvite(content) - } - } else if (isConferenceIcs) { - Log.i("[Content] Found content with icalendar file") - parseConferenceInvite(content) - } else { - Log.w( - "[Content] Found ${if (content.isFile) "file" else "file transfer"} content with empty path..." - ) - } - } else if (content.isFileTransfer) { - downloadable.value = true - val extension = FileUtils.getExtensionFromFileName(name) - val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) - when (FileUtils.getMimeType(mime)) { - FileUtils.MimeType.Image -> { - isImage.value = true - } - FileUtils.MimeType.Video -> { - isVideo.value = true - } - FileUtils.MimeType.Audio -> { - isAudio.value = true - } - FileUtils.MimeType.Pdf -> { - isPdf.value = true - } - else -> {} - } - } else if (content.isIcalendar) { - Log.i("[Content] Found content with icalendar body") - isConferenceSchedule.value = true - parseConferenceInvite(content) - } else { - Log.w("[Content] Found content that's neither a file or a file transfer") - } - - isGenericFile.value = !isPdf.value!! && !isAudio.value!! && !isVideo.value!! && !isImage.value!! && !isVoiceRecording.value!! && !isConferenceSchedule.value!! - } - - private fun parseConferenceInvite(content: Content) { - val conferenceInfo = Factory.instance().createConferenceInfoFromIcalendarContent(content) - val conferenceUri = conferenceInfo?.uri?.asStringUriOnly() ?: "" - if (conferenceInfo != null && conferenceUri.isNotEmpty()) { - conferenceAddress = conferenceUri - Log.i( - "[Content] Created conference info from ICS with address $conferenceAddress" - ) - conferenceSubject.value = conferenceInfo.subject - conferenceDescription.value = conferenceInfo.description - - val state = conferenceInfo.state - isConferenceUpdated.value = state == ConferenceInfo.State.Updated - isConferenceCancelled.value = state == ConferenceInfo.State.Cancelled - - conferenceDate.value = TimestampUtils.dateToString(conferenceInfo.dateTime) - conferenceTime.value = TimestampUtils.timeToString(conferenceInfo.dateTime) - - val minutes = conferenceInfo.duration - val hours = TimeUnit.MINUTES.toHours(minutes.toLong()) - val remainMinutes = minutes - TimeUnit.HOURS.toMinutes(hours).toInt() - conferenceDuration.value = TimestampUtils.durationToString(hours.toInt(), remainMinutes) - showDuration.value = minutes > 0 - - // Check if organizer is part of participants list - var participantsCount = conferenceInfo.participants.size - val organizer = conferenceInfo.organizer - var organizerFound = false - var allSpeaker = true - isSpeaker.value = true - for (info in conferenceInfo.participantInfos) { - val participant = info.address - if (participant.weakEqual(chatMessage.chatRoom.localAddress)) { - isSpeaker.value = info.role == Participant.Role.Speaker - } - - if (info.role == Participant.Role.Listener) { - allSpeaker = false - } - - if (organizer != null) { - if (participant.weakEqual(organizer)) { - organizerFound = true - } - } - } - isBroadcast.value = allSpeaker == false - - if (!organizerFound) participantsCount += 1 // +1 for organizer - conferenceParticipantCount.value = String.format( - AppUtils.getString(R.string.conference_invite_participants_count), - participantsCount - ) - } else if (conferenceInfo == null) { - if (content.filePath != null) { - try { - val br = BufferedReader(FileReader(content.filePath)) - var line: String? - val textBuilder = StringBuilder() - while (br.readLine().also { line = it } != null) { - textBuilder.append(line) - textBuilder.append('\n') - } - br.close() - Log.e( - "[Content] Failed to create conference info from ICS file [${content.filePath}]: $textBuilder" - ) - } catch (e: Exception) { - Log.e("[Content] Failed to read content of ICS file [${content.filePath}]: $e") - } - } else { - Log.e("[Content] Failed to create conference info from ICS: ${content.utf8Text}") - } - } else if (conferenceInfo.uri == null) { - Log.e( - "[Content] Failed to find the conference URI in conference info [$conferenceInfo]" - ) - } - } - - fun callConferenceAddress() { - val address = conferenceAddress - if (address == null) { - Log.e("[Content] Can't call null conference address!") - return - } - listener?.onCallConference(address, conferenceSubject.value) - } - - /** Voice recording specifics */ - - fun playVoiceRecording() { - Log.i("[Voice Recording] Playing voice record") - if (isPlayerClosed()) { - Log.w("[Voice Recording] Player closed, let's open it first") - initVoiceRecordPlayer() - } - - if (AppUtils.isMediaVolumeLow(coreContext.context)) { - Toast.makeText( - coreContext.context, - R.string.chat_message_voice_recording_playback_low_volume, - Toast.LENGTH_LONG - ).show() - } - - if (voiceRecordAudioFocusRequest == null) { - voiceRecordAudioFocusRequest = AppUtils.acquireAudioFocusForVoiceRecordingOrPlayback( - coreContext.context - ) - } - voiceRecordingPlayer.start() - isVoiceRecordPlaying.value = true - tickerFlow().onEach { - withContext(Dispatchers.Main) { - voiceRecordPlayingPosition.value = voiceRecordingPlayer.currentPosition - } - }.launchIn(scope) - } - - fun pauseVoiceRecording() { - Log.i("[Voice Recording] Pausing voice record") - if (!isPlayerClosed()) { - voiceRecordingPlayer.pause() - } - - val request = voiceRecordAudioFocusRequest - if (request != null) { - AppUtils.releaseAudioFocusForVoiceRecordingOrPlayback(coreContext.context, request) - voiceRecordAudioFocusRequest = null - } - - isVoiceRecordPlaying.value = false - } - - private fun tickerFlow() = flow { - while (isVoiceRecordPlaying.value == true) { - emit(Unit) - delay(100) - } - } - - private fun initVoiceRecordPlayer() { - Log.i("[Voice Recording] Creating player for voice record") - val playbackSoundCard = AudioRouteUtils.getAudioPlaybackDeviceIdForCallRecordingOrVoiceMessage() - Log.i( - "[Voice Recording] Using device $playbackSoundCard to make the voice message playback" - ) - - val localPlayer = coreContext.core.createLocalPlayer(playbackSoundCard, null, null) - if (localPlayer != null) { - voiceRecordingPlayer = localPlayer - } else { - Log.e("[Voice Recording] Couldn't create local player!") - return - } - voiceRecordingPlayer.addListener(playerListener) - - val path = filePath.value - voiceRecordingPlayer.open(path.orEmpty()) - voiceRecordDuration.value = voiceRecordingPlayer.duration - formattedDuration.value = SimpleDateFormat("mm:ss", Locale.getDefault()).format( - voiceRecordingPlayer.duration - ) // is already in milliseconds - Log.i( - "[Voice Recording] Duration is ${voiceRecordDuration.value} (${voiceRecordingPlayer.duration})" - ) - } - - private fun stopVoiceRecording() { - if (!isPlayerClosed()) { - Log.i("[Voice Recording] Stopping voice record") - pauseVoiceRecording() - voiceRecordingPlayer.seek(0) - voiceRecordPlayingPosition.value = 0 - voiceRecordingPlayer.close() - } - } - - private fun isPlayerClosed(): Boolean { - return !this::voiceRecordingPlayer.isInitialized || voiceRecordingPlayer.state == Player.State.Closed - } -} - -interface OnContentClickedListener { - fun onContentClicked(content: Content) - - fun onSipAddressClicked(sipUri: String) - - fun onEmailAddressClicked(email: String) - - fun onWebUrlClicked(url: String) - - fun onCallConference(address: String, subject: String?) - - fun onShowReactionsList(chatMessage: ChatMessage) - - fun onError(messageId: Int) -} diff --git a/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageData.kt b/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageData.kt deleted file mode 100644 index f4d27b5dc..000000000 --- a/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageData.kt +++ /dev/null @@ -1,351 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.chat.data - -import android.os.CountDownTimer -import android.text.Spannable -import android.util.Patterns -import androidx.lifecycle.MutableLiveData -import java.util.regex.Pattern -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R -import org.linphone.contact.ContactsUpdatedListenerStub -import org.linphone.contact.GenericContactData -import org.linphone.core.Address -import org.linphone.core.ChatMessage -import org.linphone.core.ChatMessageListenerStub -import org.linphone.core.ChatMessageReaction -import org.linphone.core.tools.Log -import org.linphone.utils.AppUtils -import org.linphone.utils.Event -import org.linphone.utils.PatternClickableSpan -import org.linphone.utils.TimestampUtils - -class ChatMessageData(val chatMessage: ChatMessage) : GenericContactData(chatMessage.fromAddress) { - private var contentListener: OnContentClickedListener? = null - - val sendInProgress = MutableLiveData() - - val showImdn = MutableLiveData() - - val imdnIcon = MutableLiveData() - - val backgroundRes = MutableLiveData() - - val hideAvatar = MutableLiveData() - - val hideTime = MutableLiveData() - - val contents = MutableLiveData>() - - val time = MutableLiveData() - - val ephemeralLifetime = MutableLiveData() - - val text = MutableLiveData() - - val isTextEmoji = MutableLiveData() - - val replyData = MutableLiveData() - - val isDisplayed = MutableLiveData() - - val isOutgoing = chatMessage.isOutgoing - - val contactNewlyFoundEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val reactions = MutableLiveData>() - - var hasPreviousMessage = false - var hasNextMessage = false - - private var countDownTimer: CountDownTimer? = null - - private val listener = object : ChatMessageListenerStub() { - override fun onMsgStateChanged(message: ChatMessage, state: ChatMessage.State) { - time.value = TimestampUtils.toString(chatMessage.time) - updateChatMessageState(state) - } - - override fun onEphemeralMessageTimerStarted(message: ChatMessage) { - updateEphemeralTimer() - } - - override fun onNewMessageReaction(message: ChatMessage, reaction: ChatMessageReaction) { - Log.i( - "[Chat Message Data] New reaction to display [${reaction.body}] from [${reaction.fromAddress.asStringUriOnly()}]" - ) - updateReactionsList() - } - - override fun onReactionRemoved(message: ChatMessage, address: Address) { - Log.i( - "[Chat Message Data] [${address.asStringUriOnly()}] removed it's previous reaction" - ) - updateReactionsList() - } - } - - private val contactsListener = object : ContactsUpdatedListenerStub() { - override fun onContactsUpdated() { - contactLookup() - if (contact.value != null) { - coreContext.contactsManager.removeListener(this) - contactNewlyFoundEvent.value = Event(true) - } - } - } - - init { - chatMessage.addListener(listener) - - backgroundRes.value = if (chatMessage.isOutgoing) R.drawable.chat_bubble_outgoing_full else R.drawable.chat_bubble_incoming_full - hideAvatar.value = false - - if (chatMessage.isReply) { - val reply = chatMessage.replyMessage - if (reply != null) { - Log.i( - "[Chat Message Data] Message is a reply of message id [${chatMessage.replyMessageId}] sent by [${chatMessage.replyMessageSenderAddress?.asStringUriOnly()}]" - ) - replyData.value = ChatMessageData(reply) - } - } - - time.value = TimestampUtils.toString(chatMessage.time) - updateEphemeralTimer() - - updateChatMessageState(chatMessage.state) - updateContentsList() - - if (contact.value == null) { - coreContext.contactsManager.addListener(contactsListener) - } - - updateReactionsList() - } - - override fun destroy() { - super.destroy() - - if (chatMessage.isReply) { - replyData.value?.destroy() - } - - contents.value.orEmpty().forEach(ChatMessageContentData::destroy) - chatMessage.removeListener(listener) - contentListener = null - } - - fun updateBubbleBackground(hasPrevious: Boolean, hasNext: Boolean) { - hasPreviousMessage = hasPrevious - hasNextMessage = hasNext - hideTime.value = false - hideAvatar.value = false - - if (hasPrevious) { - hideTime.value = true - } - - if (chatMessage.isOutgoing) { - if (hasNext && hasPrevious) { - backgroundRes.value = R.drawable.chat_bubble_outgoing_split_2 - } else if (hasNext) { - backgroundRes.value = R.drawable.chat_bubble_outgoing_split_1 - } else if (hasPrevious) { - backgroundRes.value = R.drawable.chat_bubble_outgoing_split_3 - } else { - backgroundRes.value = R.drawable.chat_bubble_outgoing_full - } - } else { - if (hasNext && hasPrevious) { - hideAvatar.value = true - backgroundRes.value = R.drawable.chat_bubble_incoming_split_2 - } else if (hasNext) { - backgroundRes.value = R.drawable.chat_bubble_incoming_split_1 - } else if (hasPrevious) { - hideAvatar.value = true - backgroundRes.value = R.drawable.chat_bubble_incoming_split_3 - } else { - backgroundRes.value = R.drawable.chat_bubble_incoming_full - } - } - } - - fun setContentClickListener(listener: OnContentClickedListener) { - contentListener = listener - - for (data in contents.value.orEmpty()) { - data.listener = listener - } - } - - fun showReactionsList() { - contentListener?.onShowReactionsList(chatMessage) - } - - private fun updateChatMessageState(state: ChatMessage.State) { - sendInProgress.value = when (state) { - ChatMessage.State.InProgress, ChatMessage.State.FileTransferInProgress, ChatMessage.State.FileTransferDone -> true - else -> false - } - - showImdn.value = when (state) { - ChatMessage.State.DeliveredToUser, ChatMessage.State.Displayed, - ChatMessage.State.NotDelivered, ChatMessage.State.FileTransferError -> true - else -> false - } - - imdnIcon.value = when (state) { - ChatMessage.State.DeliveredToUser -> R.drawable.chat_delivered - ChatMessage.State.Displayed -> R.drawable.chat_read - ChatMessage.State.FileTransferError, ChatMessage.State.NotDelivered -> R.drawable.chat_error - else -> R.drawable.chat_error - } - - isDisplayed.value = state == ChatMessage.State.Displayed - } - - private fun updateContentsList() { - contents.value.orEmpty().forEach(ChatMessageContentData::destroy) - val list = arrayListOf() - - val contentsList = chatMessage.contents - for (index in contentsList.indices) { - val content = contentsList[index] - if (content.isFileTransfer || content.isFile || content.isIcalendar) { - val data = ChatMessageContentData(chatMessage, index) - data.listener = contentListener - list.add(data) - } else if (content.isText) { - val textContent = content.utf8Text.orEmpty().trim() - val spannable = Spannable.Factory.getInstance().newSpannable(textContent) - text.value = PatternClickableSpan() - .add( - Pattern.compile( - "(?:)?" - ), - object : PatternClickableSpan.SpannableClickedListener { - override fun onSpanClicked(text: String) { - Log.i("[Chat Message Data] Clicked on SIP URI: $text") - contentListener?.onSipAddressClicked(text) - } - } - ) - .add( - Patterns.EMAIL_ADDRESS, - object : PatternClickableSpan.SpannableClickedListener { - override fun onSpanClicked(text: String) { - Log.i("[Chat Message Data] Clicked on email address: $text") - contentListener?.onEmailAddressClicked(text) - } - } - ) - .add( - Patterns.PHONE, - object : PatternClickableSpan.SpannableClickedListener { - override fun onSpanClicked(text: String) { - Log.i("[Chat Message Data] Clicked on phone number: $text") - contentListener?.onSipAddressClicked(text) - } - } - ) - .add( - Patterns.WEB_URL, - object : PatternClickableSpan.SpannableClickedListener { - override fun onSpanClicked(text: String) { - Log.i("[Chat Message Data] Clicked on web URL: $text") - contentListener?.onWebUrlClicked(text) - } - } - ).build(spannable) - isTextEmoji.value = AppUtils.isTextOnlyContainingEmoji(textContent) - } else { - Log.e( - "[Chat Message Data] Unexpected content with type: ${content.type}/${content.subtype}" - ) - } - } - - contents.value = list - } - - fun updateReactionsList() { - val reactionsList = arrayListOf() - val allReactions = chatMessage.reactions - - var sameReactionTwiceOrMore = false - if (allReactions.isNotEmpty()) { - for (reaction in allReactions) { - val body = reaction.body - if (!reactionsList.contains(body)) { - reactionsList.add(body) - } else { - sameReactionTwiceOrMore = true - } - } - - if (sameReactionTwiceOrMore) { - reactionsList.add(allReactions.size.toString()) - } - } - - reactions.value = reactionsList - } - - private fun updateEphemeralTimer() { - if (chatMessage.isEphemeral) { - if (chatMessage.ephemeralExpireTime == 0L) { - // This means the message hasn't been read by all participants yet, so the countdown hasn't started - // In this case we simply display the configured value for lifetime - ephemeralLifetime.value = formatLifetime(chatMessage.ephemeralLifetime) - } else { - // Countdown has started, display remaining time - val remaining = chatMessage.ephemeralExpireTime - (System.currentTimeMillis() / 1000) - ephemeralLifetime.value = formatLifetime(remaining) - if (countDownTimer == null) { - countDownTimer = object : CountDownTimer(remaining * 1000, 1000) { - override fun onFinish() {} - - override fun onTick(millisUntilFinished: Long) { - ephemeralLifetime.postValue(formatLifetime(millisUntilFinished / 1000)) - } - } - countDownTimer?.start() - } - } - } - } - - private fun formatLifetime(seconds: Long): String { - val days = seconds / 86400 - return when { - days >= 1L -> AppUtils.getStringWithPlural(R.plurals.days, days.toInt()) - else -> String.format( - "%02d:%02d:%02d", - seconds / 3600, - (seconds % 3600) / 60, - (seconds % 60) - ) - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageReactionData.kt b/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageReactionData.kt deleted file mode 100644 index c73b423d8..000000000 --- a/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageReactionData.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2010-2022 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.chat.data - -import androidx.lifecycle.MutableLiveData -import org.linphone.contact.GenericContactData -import org.linphone.core.ChatMessageReaction - -class ChatMessageReactionData( - chatMessageReaction: ChatMessageReaction -) : GenericContactData(chatMessageReaction.fromAddress) { - - val reaction = MutableLiveData() - - init { - reaction.value = chatMessageReaction.body - } -} diff --git a/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageReactionsListData.kt b/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageReactionsListData.kt deleted file mode 100644 index 820195087..000000000 --- a/app/src/main/java/org/linphone/activities/main/chat/data/ChatMessageReactionsListData.kt +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2010-2022 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.chat.data - -import androidx.lifecycle.MutableLiveData -import org.linphone.core.Address -import org.linphone.core.ChatMessage -import org.linphone.core.ChatMessageListenerStub -import org.linphone.core.ChatMessageReaction -import org.linphone.core.tools.Log - -class ChatMessageReactionsListData(private val chatMessage: ChatMessage) { - val reactions = MutableLiveData>() - - val filteredReactions = MutableLiveData>() - - val reactionsMap = HashMap() - - val listener = object : ChatMessageListenerStub() { - override fun onNewMessageReaction(message: ChatMessage, reaction: ChatMessageReaction) { - val address = reaction.fromAddress - Log.i( - "[Chat Message Reactions List] Reaction received [${reaction.body}] from [${address.asStringUriOnly()}]" - ) - updateReactionsList(message) - } - - override fun onReactionRemoved(message: ChatMessage, address: Address) { - Log.i( - "[Chat Message Reactions List] Reaction removed by [${address.asStringUriOnly()}]" - ) - updateReactionsList(message) - } - } - - private var filter = "" - - init { - chatMessage.addListener(listener) - - updateReactionsList(chatMessage) - } - - fun onDestroy() { - chatMessage.removeListener(listener) - } - - fun updateFilteredReactions(newFilter: String) { - filter = newFilter - filteredReactions.value.orEmpty().forEach(ChatMessageReactionData::destroy) - - val reactionsList = arrayListOf() - for (reaction in reactions.value.orEmpty()) { - if (filter.isEmpty() || filter == reaction.body) { - val data = ChatMessageReactionData(reaction) - reactionsList.add(data) - } - } - filteredReactions.value = reactionsList - } - - private fun updateReactionsList(chatMessage: ChatMessage) { - reactionsMap.clear() - - val reactionsList = arrayListOf() - for (reaction in chatMessage.reactions) { - val body = reaction.body - val count = if (reactionsMap.containsKey(body)) { - reactionsMap[body] ?: 0 - } else { - 0 - } - // getOrDefault isn't available for API 23 :'( - reactionsMap[body] = count + 1 - reactionsList.add(reaction) - } - reactions.value = reactionsList - - updateFilteredReactions(filter) - } -} diff --git a/app/src/main/java/org/linphone/activities/main/chat/data/ChatRoomData.kt b/app/src/main/java/org/linphone/activities/main/chat/data/ChatRoomData.kt deleted file mode 100644 index 233ae0985..000000000 --- a/app/src/main/java/org/linphone/activities/main/chat/data/ChatRoomData.kt +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Copyright (c) 2010-2022 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.chat.data - -import android.graphics.Typeface -import android.text.SpannableStringBuilder -import android.text.style.StyleSpan -import androidx.lifecycle.MutableLiveData -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R -import org.linphone.contact.ContactDataInterface -import org.linphone.contact.ContactsUpdatedListenerStub -import org.linphone.core.* -import org.linphone.core.tools.Log -import org.linphone.utils.AppUtils -import org.linphone.utils.Event -import org.linphone.utils.LinphoneUtils -import org.linphone.utils.TimestampUtils - -class ChatRoomData(val chatRoom: ChatRoom) : ContactDataInterface { - override val contact: MutableLiveData = MutableLiveData() - override val displayName: MutableLiveData = MutableLiveData() - override val securityLevel: MutableLiveData = MutableLiveData() - override val showGroupChatAvatar: Boolean - get() = !oneToOneChatRoom - override val presenceStatus: MutableLiveData = MutableLiveData() - override val coroutineScope: CoroutineScope = coreContext.coroutineScope - - val id = LinphoneUtils.getChatRoomId(chatRoom) - - val unreadMessagesCount = MutableLiveData() - - val subject = MutableLiveData() - - val securityLevelIcon = MutableLiveData() - - val securityLevelContentDescription = MutableLiveData() - - val ephemeralEnabled = MutableLiveData() - - val lastUpdate = MutableLiveData() - - val lastMessageText = MutableLiveData() - - val showLastMessageImdnIcon = MutableLiveData() - - val lastMessageImdnIcon = MutableLiveData() - - val notificationsMuted = MutableLiveData() - - private val basicChatRoom: Boolean by lazy { - chatRoom.hasCapability(ChatRoom.Capabilities.Basic.toInt()) - } - - val oneToOneChatRoom: Boolean by lazy { - chatRoom.hasCapability(ChatRoom.Capabilities.OneToOne.toInt()) - } - - val encryptedChatRoom: Boolean by lazy { - chatRoom.hasCapability(ChatRoom.Capabilities.Encrypted.toInt()) - } - - val contactNewlyFoundEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - private val contactsListener = object : ContactsUpdatedListenerStub() { - override fun onContactsUpdated() { - if (contact.value == null && oneToOneChatRoom) { - searchMatchingContact() - } - if (!oneToOneChatRoom) { - formatLastMessage(chatRoom.lastMessageInHistory) - } - } - } - - init { - coreContext.contactsManager.addListener(contactsListener) - - lastUpdate.value = "00:00" - presenceStatus.value = ConsolidatedPresence.Offline - } - - fun destroy() { - coreContext.contactsManager.removeListener(contactsListener) - } - - fun update() { - unreadMessagesCount.value = chatRoom.unreadMessagesCount - - subject.value = chatRoom.subject - updateSecurityIcon() - ephemeralEnabled.value = chatRoom.isEphemeralEnabled - - contactLookup() - formatLastMessage(chatRoom.lastMessageInHistory) - - notificationsMuted.value = areNotificationsMuted() - } - - fun markAsRead() { - chatRoom.markAsRead() - unreadMessagesCount.value = 0 - } - - private fun updateSecurityIcon() { - val level = chatRoom.securityLevel - securityLevel.value = level - - securityLevelIcon.value = when (level) { - ChatRoom.SecurityLevel.Safe -> R.drawable.security_2_indicator - ChatRoom.SecurityLevel.Encrypted -> R.drawable.security_1_indicator - else -> R.drawable.security_alert_indicator - } - securityLevelContentDescription.value = when (level) { - ChatRoom.SecurityLevel.Safe -> R.string.content_description_security_level_safe - ChatRoom.SecurityLevel.Encrypted -> R.string.content_description_security_level_encrypted - else -> R.string.content_description_security_level_unsafe - } - } - - private fun contactLookup() { - if (oneToOneChatRoom) { - searchMatchingContact() - } else { - displayName.value = chatRoom.subject ?: chatRoom.peerAddress.asStringUriOnly() - } - } - - private fun searchMatchingContact() { - val remoteAddress = if (basicChatRoom) { - chatRoom.peerAddress - } else { - val participants = chatRoom.participants - if (participants.isNotEmpty()) { - participants.first().address - } else { - Log.e( - "[Chat Room] ${chatRoom.peerAddress} doesn't have any participant (state ${chatRoom.state})!" - ) - null - } - } - - if (remoteAddress != null) { - val friend = coreContext.contactsManager.findContactByAddress(remoteAddress) - if (friend != null) { - val newlyFound = contact.value == null - - contact.value = friend!! - presenceStatus.value = friend.consolidatedPresence - friend.addListener { - presenceStatus.value = it.consolidatedPresence - } - - if (newlyFound) { - contactNewlyFoundEvent.value = Event(true) - } - } else { - displayName.value = LinphoneUtils.getDisplayName(remoteAddress) - } - } else { - displayName.value = chatRoom.peerAddress.asStringUriOnly() - } - } - - private fun formatLastMessage(msg: ChatMessage?) { - val lastUpdateTime = chatRoom.lastUpdateTime - coroutineScope.launch { - withContext(Dispatchers.IO) { - lastUpdate.postValue(TimestampUtils.toString(lastUpdateTime, true)) - } - } - - val builder = SpannableStringBuilder() - if (msg == null) { - lastMessageText.value = builder - showLastMessageImdnIcon.value = false - return - } - - if (msg.isOutgoing && msg.state != ChatMessage.State.Displayed) { - msg.addListener(object : ChatMessageListenerStub() { - override fun onMsgStateChanged(message: ChatMessage, state: ChatMessage.State) { - computeLastMessageImdnIcon(message) - } - }) - } - computeLastMessageImdnIcon(msg) - - if (!oneToOneChatRoom) { - val sender: String = - coreContext.contactsManager.findContactByAddress(msg.fromAddress)?.name - ?: LinphoneUtils.getDisplayName(msg.fromAddress) - builder.append( - coreContext.context.getString(R.string.chat_room_last_message_sender_format, sender) - ) - builder.append(" ") - } - - for (content in msg.contents) { - if (content.isIcalendar) { - val body = AppUtils.getString(R.string.conference_invitation) - builder.append(body) - builder.setSpan( - StyleSpan(Typeface.ITALIC), - builder.length - body.length, - builder.length, - 0 - ) - break - } else if (content.isVoiceRecording) { - val body = AppUtils.getString(R.string.chat_message_voice_recording) - builder.append(body) - builder.setSpan( - StyleSpan(Typeface.ITALIC), - builder.length - body.length, - builder.length, - 0 - ) - break - } else if (content.isFile || content.isFileTransfer) { - builder.append(content.name + " ") - } else if (content.isText) { - builder.append(content.utf8Text + " ") - } - } - - builder.trim() - lastMessageText.value = builder - } - - private fun computeLastMessageImdnIcon(msg: ChatMessage) { - val state = msg.state - showLastMessageImdnIcon.value = if (msg.isOutgoing) { - when (state) { - ChatMessage.State.DeliveredToUser, ChatMessage.State.Displayed, - ChatMessage.State.NotDelivered, ChatMessage.State.FileTransferError -> true - else -> false - } - } else { - false - } - lastMessageImdnIcon.value = when (state) { - ChatMessage.State.DeliveredToUser -> R.drawable.chat_delivered - ChatMessage.State.Displayed -> R.drawable.chat_read - ChatMessage.State.FileTransferError, ChatMessage.State.NotDelivered -> R.drawable.chat_error - else -> R.drawable.chat_error - } - } - - private fun areNotificationsMuted(): Boolean { - return chatRoom.muted - } -} diff --git a/app/src/main/java/org/linphone/activities/main/chat/data/DevicesListChildData.kt b/app/src/main/java/org/linphone/activities/main/chat/data/DevicesListChildData.kt deleted file mode 100644 index e7216c946..000000000 --- a/app/src/main/java/org/linphone/activities/main/chat/data/DevicesListChildData.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.chat.data - -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R -import org.linphone.core.ChatRoom -import org.linphone.core.ParticipantDevice - -class DevicesListChildData(private val device: ParticipantDevice) { - val deviceName: String = device.name.orEmpty() - - val securityLevelIcon: Int by lazy { - when (device.securityLevel) { - ChatRoom.SecurityLevel.Safe -> R.drawable.security_2_indicator - ChatRoom.SecurityLevel.Encrypted -> R.drawable.security_1_indicator - else -> R.drawable.security_alert_indicator - } - } - - val securityContentDescription: Int by lazy { - when (device.securityLevel) { - ChatRoom.SecurityLevel.Safe -> R.string.content_description_security_level_safe - ChatRoom.SecurityLevel.Encrypted -> R.string.content_description_security_level_encrypted - else -> R.string.content_description_security_level_unsafe - } - } - - fun onClick() { - coreContext.startCall(device.address, forceZRTP = true) - } -} diff --git a/app/src/main/java/org/linphone/activities/main/chat/data/DevicesListGroupData.kt b/app/src/main/java/org/linphone/activities/main/chat/data/DevicesListGroupData.kt deleted file mode 100644 index c8620c03b..000000000 --- a/app/src/main/java/org/linphone/activities/main/chat/data/DevicesListGroupData.kt +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.chat.data - -import androidx.lifecycle.MutableLiveData -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R -import org.linphone.contact.GenericContactData -import org.linphone.core.ChatRoom -import org.linphone.core.Participant -import org.linphone.utils.LinphoneUtils - -class DevicesListGroupData(private val participant: Participant) : GenericContactData( - participant.address -) { - val securityLevelIcon: Int by lazy { - when (participant.securityLevel) { - ChatRoom.SecurityLevel.Safe -> R.drawable.security_2_indicator - ChatRoom.SecurityLevel.Encrypted -> R.drawable.security_1_indicator - else -> R.drawable.security_alert_indicator - } - } - - val securityLevelContentDescription: Int by lazy { - when (participant.securityLevel) { - ChatRoom.SecurityLevel.Safe -> R.string.content_description_security_level_safe - ChatRoom.SecurityLevel.Encrypted -> R.string.content_description_security_level_encrypted - else -> R.string.content_description_security_level_unsafe - } - } - - val sipUri: String get() = LinphoneUtils.getDisplayableAddress(participant.address) - - val isExpanded = MutableLiveData() - - val devices = MutableLiveData>() - - init { - securityLevel.value = participant.securityLevel - isExpanded.value = false - - val list = arrayListOf() - for (device in participant.devices) { - list.add(DevicesListChildData((device))) - } - devices.value = list - } - - fun toggleExpanded() { - isExpanded.value = isExpanded.value != true - } - - fun onClick() { - val device = if (participant.devices.isEmpty()) null else participant.devices.first() - if (device?.address != null) { - coreContext.startCall(device.address, forceZRTP = true) - } else { - coreContext.startCall(participant.address, forceZRTP = true) - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/chat/data/EphemeralDurationData.kt b/app/src/main/java/org/linphone/activities/main/chat/data/EphemeralDurationData.kt deleted file mode 100644 index ddf6b1d68..000000000 --- a/app/src/main/java/org/linphone/activities/main/chat/data/EphemeralDurationData.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.chat.data - -class EphemeralDurationData( - val textResource: Int, - selectedDuration: Long, - private val duration: Long, - private val listener: DurationItemClicked -) { - val selected: Boolean = selectedDuration == duration - - fun setSelected() { - listener.onDurationValueChanged(duration) - } -} - -interface DurationItemClicked { - fun onDurationValueChanged(duration: Long) -} diff --git a/app/src/main/java/org/linphone/activities/main/chat/data/EventData.kt b/app/src/main/java/org/linphone/activities/main/chat/data/EventData.kt deleted file mode 100644 index 4c9f08ec8..000000000 --- a/app/src/main/java/org/linphone/activities/main/chat/data/EventData.kt +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.chat.data - -import android.content.Context -import androidx.lifecycle.MutableLiveData -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R -import org.linphone.contact.GenericContactData -import org.linphone.core.EventLog - -class EventData(private val eventLog: EventLog) : GenericContactData( - if (eventLog.type == EventLog.Type.ConferenceSecurityEvent) { - eventLog.securityEventFaultyDeviceAddress!! - } else { - if (eventLog.participantAddress == null) { - eventLog.peerAddress!! - } else { - eventLog.participantAddress!! - } - } -) { - val text = MutableLiveData() - - val isSecurity: Boolean by lazy { - when (eventLog.type) { - EventLog.Type.ConferenceSecurityEvent -> true - else -> false - } - } - - val isGroupLeft: Boolean by lazy { - when (eventLog.type) { - EventLog.Type.ConferenceTerminated -> true - else -> false - } - } - - init { - updateEventText() - } - - private fun getName(): String { - return contact.value?.name ?: displayName.value ?: "" - } - - private fun updateEventText() { - val context: Context = coreContext.context - - text.value = when (eventLog.type) { - EventLog.Type.ConferenceCreated -> context.getString( - R.string.chat_event_conference_created - ) - EventLog.Type.ConferenceTerminated -> context.getString( - R.string.chat_event_conference_destroyed - ) - EventLog.Type.ConferenceParticipantAdded -> context.getString( - R.string.chat_event_participant_added - ).format(getName()) - EventLog.Type.ConferenceParticipantRemoved -> context.getString( - R.string.chat_event_participant_removed - ).format(getName()) - EventLog.Type.ConferenceSubjectChanged -> context.getString( - R.string.chat_event_subject_changed - ).format(eventLog.subject) - EventLog.Type.ConferenceParticipantSetAdmin -> context.getString( - R.string.chat_event_admin_set - ).format(getName()) - EventLog.Type.ConferenceParticipantUnsetAdmin -> context.getString( - R.string.chat_event_admin_unset - ).format(getName()) - EventLog.Type.ConferenceParticipantDeviceAdded -> context.getString( - R.string.chat_event_device_added - ).format(getName()) - EventLog.Type.ConferenceParticipantDeviceRemoved -> context.getString( - R.string.chat_event_device_removed - ).format(getName()) - EventLog.Type.ConferenceSecurityEvent -> { - val name = getName() - when (eventLog.securityEventType) { - EventLog.SecurityEventType.EncryptionIdentityKeyChanged -> context.getString( - R.string.chat_security_event_lime_identity_key_changed - ).format(name) - EventLog.SecurityEventType.ManInTheMiddleDetected -> context.getString( - R.string.chat_security_event_man_in_the_middle_detected - ).format(name) - EventLog.SecurityEventType.SecurityLevelDowngraded -> context.getString( - R.string.chat_security_event_security_level_downgraded - ).format(name) - EventLog.SecurityEventType.ParticipantMaxDeviceCountExceeded -> context.getString( - R.string.chat_security_event_participant_max_count_exceeded - ).format(name) - else -> "Unexpected security event for $name: ${eventLog.securityEventType}" - } - } - EventLog.Type.ConferenceEphemeralMessageDisabled -> context.getString( - R.string.chat_event_ephemeral_disabled - ) - EventLog.Type.ConferenceEphemeralMessageEnabled -> context.getString( - R.string.chat_event_ephemeral_enabled - ).format(formatEphemeralExpiration(context, eventLog.ephemeralMessageLifetime)) - EventLog.Type.ConferenceEphemeralMessageLifetimeChanged -> context.getString( - R.string.chat_event_ephemeral_lifetime_changed - ).format(formatEphemeralExpiration(context, eventLog.ephemeralMessageLifetime)) - else -> "Unexpected event: ${eventLog.type}" - } - } - - private fun formatEphemeralExpiration(context: Context, duration: Long): String { - return when (duration) { - 0L -> context.getString(R.string.chat_room_ephemeral_message_disabled) - 60L -> context.getString(R.string.chat_room_ephemeral_message_one_minute) - 3600L -> context.getString(R.string.chat_room_ephemeral_message_one_hour) - 86400L -> context.getString(R.string.chat_room_ephemeral_message_one_day) - 259200L -> context.getString(R.string.chat_room_ephemeral_message_three_days) - 604800L -> context.getString(R.string.chat_room_ephemeral_message_one_week) - else -> "Unexpected duration" - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/chat/data/EventLogData.kt b/app/src/main/java/org/linphone/activities/main/chat/data/EventLogData.kt deleted file mode 100644 index a20c19a94..000000000 --- a/app/src/main/java/org/linphone/activities/main/chat/data/EventLogData.kt +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.chat.data - -import org.linphone.contact.GenericContactData -import org.linphone.core.EventLog - -class EventLogData(val eventLog: EventLog) { - val type: EventLog.Type = eventLog.type - - val notifyId = eventLog.notifyId - - val data: GenericContactData = if (type == EventLog.Type.ConferenceChatMessage) { - ChatMessageData(eventLog.chatMessage!!) - } else { - EventData(eventLog) - } - - fun destroy() { - data.destroy() - } -} diff --git a/app/src/main/java/org/linphone/activities/main/chat/data/GroupInfoParticipantData.kt b/app/src/main/java/org/linphone/activities/main/chat/data/GroupInfoParticipantData.kt deleted file mode 100644 index 977fca139..000000000 --- a/app/src/main/java/org/linphone/activities/main/chat/data/GroupInfoParticipantData.kt +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.chat.data - -import androidx.lifecycle.MutableLiveData -import org.linphone.R -import org.linphone.activities.main.chat.GroupChatRoomMember -import org.linphone.contact.GenericContactData -import org.linphone.core.ChatRoom -import org.linphone.utils.LinphoneUtils - -class GroupInfoParticipantData(val participant: GroupChatRoomMember) : GenericContactData( - participant.address -) { - val sipUri: String get() = LinphoneUtils.getDisplayableAddress(participant.address) - - val isAdmin = MutableLiveData() - - val showAdminControls = MutableLiveData() - - // A participant not yet added to a group can't be set admin at the same time it's added - val canBeSetAdmin = MutableLiveData() - - val securityLevelIcon: Int by lazy { - when (participant.securityLevel) { - ChatRoom.SecurityLevel.Safe -> R.drawable.security_2_indicator - ChatRoom.SecurityLevel.Encrypted -> R.drawable.security_1_indicator - else -> R.drawable.security_alert_indicator - } - } - - val securityLevelContentDescription: Int by lazy { - when (participant.securityLevel) { - ChatRoom.SecurityLevel.Safe -> R.string.content_description_security_level_safe - ChatRoom.SecurityLevel.Encrypted -> R.string.content_description_security_level_encrypted - else -> R.string.content_description_security_level_unsafe - } - } - - init { - securityLevel.value = participant.securityLevel - isAdmin.value = participant.isAdmin - showAdminControls.value = false - canBeSetAdmin.value = participant.canBeSetAdmin - } - - fun setAdmin() { - isAdmin.value = true - participant.isAdmin = true - } - - fun unSetAdmin() { - isAdmin.value = false - participant.isAdmin = false - } -} diff --git a/app/src/main/java/org/linphone/activities/main/chat/data/ImdnParticipantData.kt b/app/src/main/java/org/linphone/activities/main/chat/data/ImdnParticipantData.kt deleted file mode 100644 index 4d93970c3..000000000 --- a/app/src/main/java/org/linphone/activities/main/chat/data/ImdnParticipantData.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.chat.data - -import org.linphone.contact.GenericContactData -import org.linphone.core.ParticipantImdnState -import org.linphone.utils.TimestampUtils - -class ImdnParticipantData(val imdnState: ParticipantImdnState) : GenericContactData( - imdnState.participant.address -) { - val sipUri: String = imdnState.participant.address.asStringUriOnly() - - val time: String = TimestampUtils.toString(imdnState.stateChangeTime) -} diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/ChatMessageReactionsListDialogFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/ChatMessageReactionsListDialogFragment.kt deleted file mode 100644 index 099981e84..000000000 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/ChatMessageReactionsListDialogFragment.kt +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (c) 2010-2022 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.chat.fragments - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import com.google.android.material.bottomsheet.BottomSheetDialogFragment -import com.google.android.material.tabs.TabLayout -import org.linphone.R -import org.linphone.activities.main.chat.data.ChatMessageReactionsListData -import org.linphone.core.ChatMessage -import org.linphone.core.tools.Log -import org.linphone.databinding.ChatMessageReactionsListDialogBinding -import org.linphone.utils.AppUtils - -class ChatMessageReactionsListDialogFragment() : BottomSheetDialogFragment() { - companion object { - const val TAG = "ChatMessageReactionsListDialogFragment" - } - - private lateinit var binding: ChatMessageReactionsListDialogBinding - - private lateinit var data: ChatMessageReactionsListData - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - binding = ChatMessageReactionsListDialogBinding.inflate(layoutInflater) - binding.lifecycleOwner = viewLifecycleOwner - if (::data.isInitialized) { - binding.data = data - - data.reactions.observe(viewLifecycleOwner) { - binding.tabs.removeAllTabs() - binding.tabs.addTab( - binding.tabs.newTab().setText( - AppUtils.getStringWithPlural( - R.plurals.chat_message_reactions_count, - it.orEmpty().size - ) - ).setId(0) - ) - - var index = 1 - data.reactionsMap.forEach { (key, value) -> - binding.tabs.addTab( - binding.tabs.newTab().setText("$key $value").setId(index).setTag(key) - ) - index += 1 - } - } - } else { - Log.w("$TAG View created but no message has been set, dismissing...") - dismiss() - } - - binding.tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { - override fun onTabSelected(tab: TabLayout.Tab) { - if (::data.isInitialized) { - if (tab.id == 0) { - data.updateFilteredReactions("") - } else { - data.updateFilteredReactions(tab.tag.toString()) - } - } - } - - override fun onTabUnselected(tab: TabLayout.Tab) { - } - - override fun onTabReselected(tab: TabLayout.Tab) { - } - }) - - return binding.root - } - - fun setMessage(chatMessage: ChatMessage) { - data = ChatMessageReactionsListData(chatMessage) - } - - override fun onDestroy() { - if (::data.isInitialized) { - data.onDestroy() - } - super.onDestroy() - } -} diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/ChatRoomCreationFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/ChatRoomCreationFragment.kt deleted file mode 100644 index 59c45515b..000000000 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/ChatRoomCreationFragment.kt +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.chat.fragments - -import android.content.pm.PackageManager -import android.os.Bundle -import android.view.View -import androidx.lifecycle.ViewModelProvider -import androidx.recyclerview.widget.LinearLayoutManager -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.GenericActivity -import org.linphone.activities.main.MainActivity -import org.linphone.activities.main.chat.viewmodels.ChatRoomCreationViewModel -import org.linphone.activities.main.fragments.SecureFragment -import org.linphone.activities.navigateToChatRoom -import org.linphone.activities.navigateToGroupInfo -import org.linphone.contact.ContactsSelectionAdapter -import org.linphone.core.tools.Log -import org.linphone.databinding.ChatRoomCreationFragmentBinding -import org.linphone.utils.AppUtils -import org.linphone.utils.LinphoneUtils -import org.linphone.utils.PermissionHelper - -class ChatRoomCreationFragment : SecureFragment() { - private lateinit var viewModel: ChatRoomCreationViewModel - private lateinit var adapter: ContactsSelectionAdapter - - override fun getLayoutId(): Int = R.layout.chat_room_creation_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - useMaterialSharedAxisXForwardAnimation = sharedViewModel.isSlidingPaneSlideable.value == false - - val createGroup = arguments?.getBoolean("createGroup") ?: false - - viewModel = ViewModelProvider(this)[ChatRoomCreationViewModel::class.java] - viewModel.createGroupChat.value = createGroup - - viewModel.isEncrypted.value = sharedViewModel.createEncryptedChatRoom - - binding.viewModel = viewModel - - adapter = ContactsSelectionAdapter(viewLifecycleOwner) - adapter.setGroupChatCapabilityRequired(viewModel.createGroupChat.value == true) - adapter.setLimeCapabilityRequired(viewModel.isEncrypted.value == true) - binding.contactsList.adapter = adapter - - val layoutManager = LinearLayoutManager(requireContext()) - binding.contactsList.layoutManager = layoutManager - - // Divider between items - binding.contactsList.addItemDecoration( - AppUtils.getDividerDecoration(requireContext(), layoutManager) - ) - - binding.back.visibility = if ((requireActivity() as GenericActivity).isTablet()) View.INVISIBLE else View.VISIBLE - - binding.setAllContactsToggleClickListener { - viewModel.sipContactsSelected.value = false - } - - binding.setSipContactsToggleClickListener { - viewModel.sipContactsSelected.value = true - } - - viewModel.contactsList.observe( - viewLifecycleOwner - ) { - adapter.submitList(it) - } - - viewModel.isEncrypted.observe( - viewLifecycleOwner - ) { - adapter.setLimeCapabilityRequired(it) - } - - viewModel.sipContactsSelected.observe( - viewLifecycleOwner - ) { - viewModel.applyFilter() - } - - viewModel.selectedAddresses.observe( - viewLifecycleOwner - ) { - adapter.updateSelectedAddresses(it) - } - - viewModel.chatRoomCreatedEvent.observe( - viewLifecycleOwner - ) { - it.consume { chatRoom -> - sharedViewModel.selectedChatRoom.value = chatRoom - navigateToChatRoom(AppUtils.createBundleWithSharedTextAndFiles(sharedViewModel)) - } - } - - viewModel.filter.observe( - viewLifecycleOwner - ) { - viewModel.applyFilter() - } - - adapter.selectedContact.observe( - viewLifecycleOwner - ) { - it.consume { searchResult -> - if (createGroup) { - viewModel.toggleSelectionForSearchResult(searchResult) - } else { - viewModel.createOneToOneChat(searchResult) - } - } - } - - addParticipantsFromSharedViewModel() - - // Next button is only used to go to group chat info fragment - binding.setNextClickListener { - sharedViewModel.createEncryptedChatRoom = viewModel.isEncrypted.value == true - sharedViewModel.chatRoomParticipants.value = viewModel.selectedAddresses.value - navigateToGroupInfo() - } - - viewModel.onMessageToNotifyEvent.observe( - viewLifecycleOwner - ) { - it.consume { messageResourceId -> - (activity as MainActivity).showSnackBar(messageResourceId) - } - } - - if (corePreferences.enableNativeAddressBookIntegration) { - if (!PermissionHelper.get().hasReadContactsPermission()) { - Log.i("[Chat Room Creation] Asking for READ_CONTACTS permission") - requestPermissions(arrayOf(android.Manifest.permission.READ_CONTACTS), 0) - } - } - } - - @Deprecated("Deprecated in Java") - override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray - ) { - if (requestCode == 0) { - val granted = grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED - if (granted) { - Log.i("[Chat Room Creation] READ_CONTACTS permission granted") - coreContext.fetchContacts() - } else { - Log.w("[Chat Room Creation] READ_CONTACTS permission denied") - } - } - } - - override fun onResume() { - super.onResume() - - viewModel.secureChatAvailable.value = LinphoneUtils.isEndToEndEncryptedChatAvailable() - } - - private fun addParticipantsFromSharedViewModel() { - val participants = sharedViewModel.chatRoomParticipants.value - if (participants != null && participants.size > 0) { - viewModel.selectedAddresses.value = participants - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt deleted file mode 100644 index 8b105a700..000000000 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/DetailChatRoomFragment.kt +++ /dev/null @@ -1,1369 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.chat.fragments - -import android.app.Activity -import android.app.Dialog -import android.content.* -import android.content.pm.PackageManager -import android.net.Uri -import android.os.Bundle -import android.os.Parcelable -import android.provider.MediaStore -import android.view.* -import android.view.ViewTreeObserver.OnGlobalLayoutListener -import android.webkit.MimeTypeMap -import android.widget.PopupWindow -import androidx.core.content.ContextCompat -import androidx.core.content.FileProvider -import androidx.databinding.DataBindingUtil -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.lifecycleScope -import androidx.recyclerview.widget.ItemTouchHelper -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import java.io.File -import kotlinx.coroutines.* -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.* -import org.linphone.activities.main.MainActivity -import org.linphone.activities.main.chat.ChatScrollListener -import org.linphone.activities.main.chat.adapters.ChatMessagesListAdapter -import org.linphone.activities.main.chat.data.ChatMessageData -import org.linphone.activities.main.chat.data.EventLogData -import org.linphone.activities.main.chat.viewmodels.* -import org.linphone.activities.main.chat.views.RichEditTextSendListener -import org.linphone.activities.main.fragments.MasterFragment -import org.linphone.activities.main.viewmodels.DialogViewModel -import org.linphone.compatibility.Compatibility -import org.linphone.core.* -import org.linphone.core.tools.Log -import org.linphone.databinding.ChatRoomDetailFragmentBinding -import org.linphone.databinding.ChatRoomMenuBindingImpl -import org.linphone.utils.* -import org.linphone.utils.Event - -class DetailChatRoomFragment : MasterFragment() { - private lateinit var viewModel: ChatRoomViewModel - private lateinit var chatSendingViewModel: ChatMessageSendingViewModel - private lateinit var listViewModel: ChatMessagesListViewModel - - private val observer = object : RecyclerView.AdapterDataObserver() { - override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { - adapter.notifyItemChanged(positionStart - 1) // For grouping purposes - - if (positionStart == 0 && adapter.itemCount == itemCount) { - // First time we fill the list with messages - Log.i("[Chat Room] History first $itemCount messages loaded") - } else { - // Scroll to newly added messages automatically only if user hasn't initiated a scroll up in the messages history - if (viewModel.isUserScrollingUp.value == false) { - scrollToFirstUnreadMessageOrBottom(false) - } else { - Log.d( - "[Chat Room] User has scrolled up manually in the messages history, don't scroll to the newly added message at the bottom & don't mark the chat room as read" - ) - } - } - } - } - - private val globalLayoutLayout = object : OnGlobalLayoutListener { - override fun onGlobalLayout() { - if (isBindingAvailable()) { - binding.chatMessagesList - .viewTreeObserver - .removeOnGlobalLayoutListener(this) - - if (::chatScrollListener.isInitialized) { - binding.chatMessagesList.addOnScrollListener(chatScrollListener) - } - - if (viewModel.chatRoom.unreadMessagesCount > 0) { - Log.i("[Chat Room] Messages have been displayed, scrolling to first unread") - val notAllMessagesDisplayed = scrollToFirstUnreadMessageOrBottom(false) - if (notAllMessagesDisplayed) { - Log.w( - "[Chat Room] More unread messages than the screen can display, do not mark chat room as read now, wait for user to scroll to bottom" - ) - } else { - // Consider user as scrolled to the end when marking chat room as read - viewModel.isUserScrollingUp.value = false - Log.i("[Chat Room] Marking chat room as read") - viewModel.chatRoom.markAsRead() - } - } - } else { - Log.e("[Chat Room] Binding not available in onGlobalLayout callback!") - } - } - } - - private val keyboardVisibilityListener = object : AppUtils.KeyboardVisibilityListener { - override fun onKeyboardVisibilityChanged(visible: Boolean) { - if (visible && chatSendingViewModel.isEmojiPickerOpen.value == true) { - Log.d( - "[Chat Room] Emoji picker is opened, closing it because keyboard is now visible" - ) - chatSendingViewModel.isEmojiPickerOpen.value = false - } - } - } - - private lateinit var chatScrollListener: ChatScrollListener - - override fun getLayoutId(): Int { - return R.layout.chat_room_detail_fragment - } - - override fun onDestroyView() { - binding.chatMessagesList.adapter = null - - super.onDestroyView() - } - - override fun onSaveInstanceState(outState: Bundle) { - if (isSharedViewModelInitialized()) { - val chatRoom = sharedViewModel.selectedChatRoom.value - if (chatRoom != null) { - outState.putString("LocalSipUri", chatRoom.localAddress.asStringUriOnly()) - outState.putString("RemoteSipUri", chatRoom.peerAddress.asStringUriOnly()) - Log.i( - "[Chat Room] Saving current chat room local & remote addresses in save instance state" - ) - } - } else { - Log.w( - "[Chat Room] Can't save instance state, sharedViewModel hasn't been initialized yet" - ) - } - super.onSaveInstanceState(outState) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - postponeEnterTransition() - - binding.lifecycleOwner = viewLifecycleOwner - - binding.sharedMainViewModel = sharedViewModel - - useMaterialSharedAxisXForwardAnimation = sharedViewModel.isSlidingPaneSlideable.value == false - - val localSipUri = arguments?.getString("LocalSipUri") ?: savedInstanceState?.getString( - "LocalSipUri" - ) - val remoteSipUri = arguments?.getString("RemoteSipUri") ?: savedInstanceState?.getString( - "RemoteSipUri" - ) - - val textToShare = arguments?.getString("TextToShare") - val filesToShare = arguments?.getStringArrayList("FilesToShare") - - if (remoteSipUri != null && arguments?.getString("RemoteSipUri") == null) { - Log.w("[Chat Room] Chat room will be restored from saved instance state") - } - arguments?.clear() - if (localSipUri != null && remoteSipUri != null) { - Log.i( - "[Chat Room] Found local [$localSipUri] & remote [$remoteSipUri] addresses in arguments or saved instance state" - ) - - val localAddress = Factory.instance().createAddress(localSipUri) - val remoteSipAddress = Factory.instance().createAddress(remoteSipUri) - sharedViewModel.selectedChatRoom.value = coreContext.core.searchChatRoom( - null, - localAddress, - remoteSipAddress, - arrayOfNulls( - 0 - ) - ) - } - - val chatRoom = sharedViewModel.selectedChatRoom.value - if (chatRoom == null) { - Log.e("[Chat Room] Chat room is null, aborting!") - goBack() - return - } - - Compatibility.setLocusIdInContentCaptureSession(binding.root, chatRoom) - - isSecure = chatRoom.currentParams.isEncryptionEnabled - - viewModel = ViewModelProvider( - this, - ChatRoomViewModelFactory(chatRoom) - )[ChatRoomViewModel::class.java] - - binding.viewModel = viewModel - - chatSendingViewModel = ViewModelProvider( - this, - ChatMessageSendingViewModelFactory(chatRoom) - )[ChatMessageSendingViewModel::class.java] - binding.chatSendingViewModel = chatSendingViewModel - - listViewModel = ViewModelProvider( - this, - ChatMessagesListViewModelFactory(chatRoom) - )[ChatMessagesListViewModel::class.java] - - _adapter = ChatMessagesListAdapter(listSelectionViewModel, viewLifecycleOwner) - // SubmitList is done on a background thread - // We need this adapter data observer to know when to scroll - binding.chatMessagesList.adapter = adapter - - val layoutManager = LinearLayoutManager(requireContext()) - layoutManager.stackFromEnd = true - binding.chatMessagesList.layoutManager = layoutManager - - // Displays unread messages header - val headerItemDecoration = RecyclerViewHeaderDecoration(requireContext(), adapter) - binding.chatMessagesList.addItemDecoration(headerItemDecoration) - - // Swipe action - val swipeConfiguration = RecyclerViewSwipeConfiguration() - // Reply action can only be done on a ChatMessageEventLog - swipeConfiguration.leftToRightAction = RecyclerViewSwipeConfiguration.Action( - text = requireContext().getString(R.string.chat_message_context_menu_reply), - backgroundColor = ContextCompat.getColor(requireContext(), R.color.light_grey_color), - preventFor = ChatMessagesListAdapter.EventViewHolder::class.java - ) - // Delete action can be done on any EventLog - swipeConfiguration.rightToLeftAction = RecyclerViewSwipeConfiguration.Action( - text = requireContext().getString(R.string.chat_message_context_menu_delete), - backgroundColor = ContextCompat.getColor(requireContext(), R.color.red_color) - ) - val swipeListener = object : RecyclerViewSwipeListener { - override fun onLeftToRightSwipe(viewHolder: RecyclerView.ViewHolder) { - val index = viewHolder.bindingAdapterPosition - if (index < 0 || index >= adapter.currentList.size) { - Log.e("[Chat Room] Index is out of bound, can't reply to chat message") - } else { - adapter.notifyItemChanged(index) - - val chatMessageEventLog = adapter.currentList[index] - val chatMessage = chatMessageEventLog.eventLog.chatMessage - if (chatMessage != null) { - replyToChatMessage(chatMessage) - } - } - } - - override fun onRightToLeftSwipe(viewHolder: RecyclerView.ViewHolder) { - val index = viewHolder.bindingAdapterPosition - if (index < 0 || index >= adapter.currentList.size) { - Log.e("[Chat Room] Index is out of bound, can't delete chat message") - } else { - // adapter.notifyItemRemoved(index) - val eventLog = adapter.currentList[index] - addDeleteMessageTaskToQueue(eventLog, index) - } - } - } - RecyclerViewSwipeUtils( - ItemTouchHelper.RIGHT or ItemTouchHelper.LEFT, - swipeConfiguration, - swipeListener - ) - .attachToRecyclerView(binding.chatMessagesList) - - chatScrollListener = object : ChatScrollListener(layoutManager) { - override fun onLoadMore(totalItemsCount: Int) { - Log.i( - "[Chat Room] User has scrolled up far enough, load more items from history (currently there are $totalItemsCount messages displayed)" - ) - listViewModel.loadMoreData(totalItemsCount) - } - - override fun onScrolledUp() { - viewModel.isUserScrollingUp.value = true - } - - override fun onScrolledToEnd() { - viewModel.isUserScrollingUp.value = false - - val peerAddress = viewModel.chatRoom.peerAddress.asStringUriOnly() - if (viewModel.unreadMessagesCount.value != 0 && - coreContext.notificationsManager.currentlyDisplayedChatRoomAddress == peerAddress - ) { - Log.i( - "[Chat Room] User has scrolled to the latest message, mark chat room as read" - ) - viewModel.chatRoom.markAsRead() - } - } - } - - chatSendingViewModel.textToSend.observe( - viewLifecycleOwner - ) { - chatSendingViewModel.onTextToSendChanged(it) - } - - chatSendingViewModel.isVoiceRecording.observe( - viewLifecycleOwner - ) { voiceRecording -> - // Keep screen on while recording voice message - if (voiceRecording) { - requireActivity().window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) - } else { - requireActivity().window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) - } - } - - chatSendingViewModel.requestRecordAudioPermissionEvent.observe( - viewLifecycleOwner - ) { - it.consume { - Log.i("[Chat Room] Asking for RECORD_AUDIO permission") - requestPermissions(arrayOf(android.Manifest.permission.RECORD_AUDIO), 2) - } - } - - chatSendingViewModel.messageSentEvent.observe( - viewLifecycleOwner - ) { - it.consume { - Log.i("[Chat Room] Message sent") - // Reset this to ensure sent message will be visible - viewModel.isUserScrollingUp.value = false - } - } - - chatSendingViewModel.requestKeyboardHidingEvent.observe( - viewLifecycleOwner - ) { - it.consume { - (requireActivity() as MainActivity).hideKeyboard() - } - } - - listViewModel.events.observe( - viewLifecycleOwner - ) { events -> - adapter.setUnreadMessageCount( - viewModel.chatRoom.unreadMessagesCount, - viewModel.isUserScrollingUp.value == true - ) - adapter.submitList(events) - } - - listViewModel.messageUpdatedEvent.observe( - viewLifecycleOwner - ) { - it.consume { position -> - adapter.notifyItemChanged(position) - } - } - - listViewModel.requestWriteExternalStoragePermissionEvent.observe( - viewLifecycleOwner - ) { - it.consume { - requestPermissions(arrayOf(android.Manifest.permission.WRITE_EXTERNAL_STORAGE), 1) - } - } - - adapter.deleteMessageEvent.observe( - viewLifecycleOwner - ) { - it.consume { chatMessage -> - listViewModel.deleteMessage(chatMessage) - sharedViewModel.refreshChatRoomInListEvent.value = Event(true) - } - } - - adapter.resendMessageEvent.observe( - viewLifecycleOwner - ) { - it.consume { chatMessage -> - listViewModel.resendMessage(chatMessage) - } - } - - adapter.forwardMessageEvent.observe( - viewLifecycleOwner - ) { - it.consume { chatMessage -> - // Remove observer before setting the message to forward - // as we don't want to forward it in this chat room - sharedViewModel.messageToForwardEvent.removeObservers(viewLifecycleOwner) - sharedViewModel.messageToForwardEvent.value = Event(chatMessage) - sharedViewModel.isPendingMessageForward.value = true - - if (sharedViewModel.isSlidingPaneSlideable.value == true) { - Log.i("[Chat Room] Forwarding message, going to chat rooms list") - goBack() - } else { - navigateToEmptyChatRoom() - } - } - } - - adapter.replyMessageEvent.observe( - viewLifecycleOwner - ) { - it.consume { chatMessage -> - replyToChatMessage(chatMessage) - } - } - - adapter.showImdnForMessageEvent.observe( - viewLifecycleOwner - ) { - it.consume { chatMessage -> - val args = Bundle() - args.putString("MessageId", chatMessage.messageId) - navigateToImdn(args) - } - } - - adapter.addSipUriToContactEvent.observe( - viewLifecycleOwner - ) { - it.consume { sipUri -> - Log.i("[Chat Room] Going to contacts list with SIP URI to add: $sipUri") - sharedViewModel.updateContactsAnimationsBasedOnDestination.value = - Event(R.id.masterChatRoomsFragment) - navigateToContacts(sipUri) - } - } - - adapter.openContentEvent.observe( - viewLifecycleOwner - ) { - it.consume { content -> - var path = content.filePath.orEmpty() - - if (path.isNotEmpty() && !File(path).exists()) { - Log.e("[Chat Room] File not found: $path") - (requireActivity() as MainActivity).showSnackBar( - R.string.chat_room_file_not_found - ) - } else { - if (path.isEmpty()) { - val name = content.name - if (!name.isNullOrEmpty()) { - val file = FileUtils.getFileStoragePath(name) - FileUtils.writeIntoFile(content.buffer, file) - path = file.absolutePath - content.filePath = path - Log.i( - "[Chat Room] Content file path was empty, created file from buffer at $path" - ) - } else if (content.isIcalendar) { - val filename = "conference.ics" - val file = FileUtils.getFileStoragePath(filename) - FileUtils.writeIntoFile(content.buffer, file) - path = file.absolutePath - content.filePath = path - Log.i( - "[Chat Room] Content file path was empty, created conference.ics from buffer at $path" - ) - } - } - - Log.i("[Chat Room] Opening file: $path") - sharedViewModel.contentToOpen.value = content - - if (corePreferences.useInAppFileViewerForNonEncryptedFiles || content.isFileEncrypted) { - val preventScreenshots = - viewModel.chatRoom.currentParams.isEncryptionEnabled - - val extension = FileUtils.getExtensionFromFileName(path) - val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) - when (FileUtils.getMimeType(mime)) { - FileUtils.MimeType.Image -> navigateToImageFileViewer( - preventScreenshots - ) - FileUtils.MimeType.Video -> navigateToVideoFileViewer( - preventScreenshots - ) - FileUtils.MimeType.Audio -> navigateToAudioFileViewer( - preventScreenshots - ) - FileUtils.MimeType.Pdf -> navigateToPdfFileViewer( - preventScreenshots - ) - FileUtils.MimeType.PlainText -> navigateToTextFileViewer( - preventScreenshots - ) - else -> { - if (content.isFileEncrypted) { - Log.w( - "[Chat Room] File is encrypted and can't be opened in one of our viewers..." - ) - showDialogForUserConsentBeforeExportingFileInThirdPartyApp( - content - ) - } else if (!FileUtils.openFileInThirdPartyApp( - requireActivity(), - path - ) - ) { - showDialogToSuggestOpeningFileAsText() - } - } - } - } else { - if (!FileUtils.openFileInThirdPartyApp(requireActivity(), path)) { - showDialogToSuggestOpeningFileAsText() - } - } - } - } - } - - adapter.urlClickEvent.observe( - viewLifecycleOwner - ) { - it.consume { url -> - val uri = Uri.parse(url) - val browserIntent = Intent( - Intent.ACTION_VIEW, - uri - ) - try { - startActivity(browserIntent) - } catch (se: SecurityException) { - Log.e("[Chat Room] Failed to start browser intent from uri [$uri]: $se") - } catch (anfe: ActivityNotFoundException) { - Log.e("[Chat Room] Failed to find app matching intent from uri [$uri]: $anfe") - } - } - } - - adapter.sipUriClickedEvent.observe( - viewLifecycleOwner - ) { - it.consume { sipUri -> - val args = Bundle() - args.putString("URI", sipUri) - args.putBoolean("Transfer", false) - // If auto start call setting is enabled, ignore it - args.putBoolean("SkipAutoCallStart", true) - navigateToDialer(args) - } - } - - adapter.callConferenceEvent.observe( - viewLifecycleOwner - ) { - it.consume { pair -> - navigateToConferenceWaitingRoom(pair.first, pair.second) - } - } - - adapter.scrollToChatMessageEvent.observe( - viewLifecycleOwner - ) { - it.consume { chatMessage -> - var index: Int - var loadSteps = 0 - var expectedChildCount: Int - do { - val events = listViewModel.events.value.orEmpty() - expectedChildCount = events.size - Log.e("[Chat Room] expectedChildCount : $expectedChildCount") - val eventLog = events.find { eventLog -> - if (eventLog.eventLog.type == EventLog.Type.ConferenceChatMessage) { - (eventLog.data as ChatMessageData).chatMessage.messageId == chatMessage.messageId - } else { - false - } - } - index = events.indexOf(eventLog) - if (index == -1) { - loadSteps += 1 - listViewModel.loadMoreData(events.size) - } - } while (index == -1 && loadSteps < 5) - - if (index != -1) { - if (loadSteps == 0) { - scrollTo(index, true) - } else { - lifecycleScope.launch { - withContext(Dispatchers.Default) { - var retryCount = 0 - do { - // We have to wait for newly loaded items to be added to list before being able to scroll - delay(500) - retryCount += 1 - } while (layoutManager.itemCount != expectedChildCount && retryCount < 5) - - withContext(Dispatchers.Main) { - scrollTo(index, true) - } - } - } - } - } else { - Log.w("[Chat Room] Failed to find matching event!") - } - } - } - - adapter.showReactionsListEvent.observe( - viewLifecycleOwner - ) { - it.consume { message -> - val modalBottomSheet = ChatMessageReactionsListDialogFragment() - modalBottomSheet.setMessage(message) - modalBottomSheet.show( - parentFragmentManager, - ChatMessageReactionsListDialogFragment.TAG - ) - } - } - - adapter.errorEvent.observe( - viewLifecycleOwner - ) { - it.consume { message -> - (requireActivity() as MainActivity).showSnackBar(message) - } - } - - binding.setMenuClickListener { - showPopupMenu(chatRoom) - } - - binding.setMenuLongClickListener { - // Only show debug infos if debug mode is enabled - if (corePreferences.debugLogs) { - val alertDialog = MaterialAlertDialogBuilder(requireContext()) - - val messageBuilder = StringBuilder() - messageBuilder.append("Chat room id:\n") - messageBuilder.append(viewModel.chatRoom.peerAddress.asString()) - messageBuilder.append("\n") - messageBuilder.append("Local account:\n") - messageBuilder.append(viewModel.chatRoom.localAddress.asString()) - val message = messageBuilder.toString() - alertDialog.setMessage(message) - - alertDialog.setNeutralButton(R.string.chat_message_context_menu_copy_text) { - _, _ -> - val clipboard: ClipboardManager = - coreContext.context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - val clip = ClipData.newPlainText("Chat room info", message) - clipboard.setPrimaryClip(clip) - } - - alertDialog.show() - true - } - false - } - - binding.setSecurityIconClickListener { - showParticipantsDevices() - } - - binding.setAttachFileClickListener { - if (PermissionHelper.get().hasReadExternalStoragePermission() || PermissionHelper.get().hasCameraPermission()) { - pickFile() - } else { - Log.i("[Chat Room] Asking for READ_EXTERNAL_STORAGE and CAMERA permissions") - Compatibility.requestReadExternalStorageAndCameraPermissions(this, 0) - } - } - - binding.setVoiceRecordingTouchListener { _, event -> - if (corePreferences.holdToRecordVoiceMessage) { - when (event.action) { - MotionEvent.ACTION_DOWN -> { - Log.i( - "[Chat Room] Start recording voice message as long as recording button is held" - ) - chatSendingViewModel.startVoiceRecording() - } - MotionEvent.ACTION_UP -> { - val voiceRecordingDuration = chatSendingViewModel.voiceRecordingDuration.value ?: 0 - if (voiceRecordingDuration < 1000) { - Log.w( - "[Chat Room] Voice recording button has been held for less than a second, considering miss click" - ) - chatSendingViewModel.cancelVoiceRecording() - (activity as MainActivity).showSnackBar( - R.string.chat_message_voice_recording_hold_to_record - ) - } else { - Log.i( - "[Chat Room] Voice recording button has been released, stop recording" - ) - chatSendingViewModel.stopVoiceRecording() - } - view.performClick() - } - } - } - false - } - - binding.footer.message.setControlEnterListener(object : RichEditTextSendListener { - override fun onControlEnterPressedAndReleased() { - Log.i("[Chat Room] Detected left control + enter key presses, sending message") - chatSendingViewModel.sendMessage() - } - }) - - binding.setCancelReplyToClickListener { - chatSendingViewModel.cancelReply() - } - - binding.setScrollToBottomClickListener { - scrollToFirstUnreadMessageOrBottom(true) - viewModel.isUserScrollingUp.value = false - } - - binding.setGroupCallListener { - showGroupCallDialog() - } - - if (textToShare?.isNotEmpty() == true) { - Log.i("[Chat Room] Found text to share") - chatSendingViewModel.textToSend.value = textToShare - } - if (filesToShare?.isNotEmpty() == true) { - lifecycleScope.launch { - withContext(Dispatchers.Main) { - chatSendingViewModel.attachingFileInProgress.value = true - for (filePath in filesToShare) { - val path = FileUtils.copyToLocalStorage(filePath) - Log.i( - "[Chat Room] Found [$filePath] file to share, matching path is [$path]" - ) - if (path != null) { - chatSendingViewModel.addAttachment(path) - } - } - chatSendingViewModel.attachingFileInProgress.value = false - } - } - } - - sharedViewModel.richContentUri.observe( - viewLifecycleOwner - ) { - it.consume { uri -> - Log.i("[Chat Room] Found rich content URI: $uri") - lifecycleScope.launch { - withContext(Dispatchers.Main) { - chatSendingViewModel.attachingFileInProgress.value = true - val path = FileUtils.getFilePath(requireContext(), uri) - Log.i("[Chat Room] Rich content URI [$uri] matching path is [$path]") - if (path != null) { - chatSendingViewModel.addAttachment(path) - } - chatSendingViewModel.attachingFileInProgress.value = false - } - } - } - } - - sharedViewModel.messageToForwardEvent.observe( - viewLifecycleOwner - ) { - it.consume { chatMessage -> - Log.i("[Chat Room] Found message to transfer") - showForwardConfirmationDialog(chatMessage) - sharedViewModel.isPendingMessageForward.value = false - } - } - - startPostponedEnterTransition() - } - - override fun deleteItems(indexesOfItemToDelete: ArrayList) { - val list = ArrayList() - for (index in indexesOfItemToDelete) { - val eventLog = adapter.currentList[index] - list.add(eventLog) - } - listViewModel.deleteEventLogs(list) - sharedViewModel.refreshChatRoomInListEvent.value = Event(true) - } - - @Deprecated("Deprecated in Java") - override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray - ) { - var atLeastOneGranted = false - for (result in grantResults) { - atLeastOneGranted = atLeastOneGranted || result == PackageManager.PERMISSION_GRANTED - } - - when (requestCode) { - 0 -> { - if (atLeastOneGranted) { - pickFile() - } - } - 2 -> { - if (atLeastOneGranted) { - chatSendingViewModel.startVoiceRecording() - } - } - } - } - - override fun onResume() { - super.onResume() - - if (this::viewModel.isInitialized) { - // Prevent notifications for this chat room to be displayed - val peerAddress = viewModel.chatRoom.peerAddress.asStringUriOnly() - coreContext.notificationsManager.currentlyDisplayedChatRoomAddress = peerAddress - - if (_adapter != null) { - try { - adapter.registerAdapterDataObserver(observer) - } catch (_: IllegalStateException) {} - } - - // Wait for items to be displayed - binding.chatMessagesList - .viewTreeObserver - .addOnGlobalLayoutListener(globalLayoutLayout) - } else { - Log.e( - "[Chat Room] Fragment resuming but viewModel lateinit property isn't initialized!" - ) - } - - (requireActivity() as MainActivity).addKeyboardVisibilityListener( - keyboardVisibilityListener - ) - } - - override fun onPause() { - if (::chatScrollListener.isInitialized) { - binding.chatMessagesList.removeOnScrollListener(chatScrollListener) - } - - binding.chatMessagesList - .viewTreeObserver.removeOnGlobalLayoutListener(globalLayoutLayout) - - if (_adapter != null) { - try { - adapter.unregisterAdapterDataObserver(observer) - } catch (_: IllegalStateException) {} - } - - // Conversation isn't visible anymore, any new message received in it will trigger a notification - coreContext.notificationsManager.currentlyDisplayedChatRoomAddress = null - - (requireActivity() as MainActivity).removeKeyboardVisibilityListener( - keyboardVisibilityListener - ) - - super.onPause() - } - - @Deprecated("Deprecated in Java") - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - chatSendingViewModel.attachFilePending.value = false - if (resultCode == Activity.RESULT_OK) { - lifecycleScope.launch { - withContext(Dispatchers.Main) { - chatSendingViewModel.attachingFileInProgress.value = true - for ( - fileToUploadPath in FileUtils.getFilesPathFromPickerIntent( - data, - chatSendingViewModel.temporaryFileUploadPath - ) - ) { - Log.i("[Chat Room] Found [$fileToUploadPath] file from intent") - chatSendingViewModel.addAttachment(fileToUploadPath) - } - chatSendingViewModel.attachingFileInProgress.value = false - } - } - } - } - - private fun enterEditionMode() { - listSelectionViewModel.isEditionEnabled.value = true - } - - private fun showParticipantsDevices() { - if (corePreferences.limeSecurityPopupEnabled) { - val dialogViewModel = DialogViewModel(getString(R.string.dialog_lime_security_message)) - dialogViewModel.showDoNotAskAgain = true - val dialog = DialogUtils.getDialog(requireContext(), dialogViewModel) - - dialogViewModel.showCancelButton { doNotAskAgain -> - if (doNotAskAgain) corePreferences.limeSecurityPopupEnabled = false - dialog.dismiss() - } - - val okLabel = if (viewModel.oneParticipantOneDevice) { - getString(R.string.dialog_call) - } else { - getString( - R.string.dialog_ok - ) - } - dialogViewModel.showOkButton( - { doNotAskAgain -> - if (doNotAskAgain) corePreferences.limeSecurityPopupEnabled = false - - val address = viewModel.onlyParticipantOnlyDeviceAddress - if (viewModel.oneParticipantOneDevice) { - if (address != null) { - coreContext.startCall(address, forceZRTP = true) - } - } else { - navigateToDevices() - } - - dialog.dismiss() - }, - okLabel - ) - - dialog.show() - } else { - val address = viewModel.onlyParticipantOnlyDeviceAddress - if (viewModel.oneParticipantOneDevice) { - if (address != null) { - coreContext.startCall(address, forceZRTP = true) - } - } else { - navigateToDevices() - } - } - } - - private fun showGroupInfo(chatRoom: ChatRoom) { - sharedViewModel.selectedGroupChatRoom.value = chatRoom - sharedViewModel.chatRoomParticipants.value = arrayListOf() - navigateToGroupInfo() - } - - private fun showEphemeralMessages() { - navigateToEphemeralInfo() - } - - private fun scheduleMeeting(chatRoom: ChatRoom) { - val participants = arrayListOf
() - for (participant in chatRoom.participants) { - participants.add(participant.address) - } - sharedViewModel.participantsListForNextScheduledMeeting.value = Event(participants) - navigateToConferenceScheduling() - } - - private fun showForwardConfirmationDialog(chatMessage: ChatMessage) { - val viewModel = DialogViewModel( - getString(R.string.chat_message_forward_confirmation_dialog) - ) - viewModel.iconResource = R.drawable.forward_message_default - viewModel.showIcon = true - val dialog: Dialog = DialogUtils.getDialog(requireContext(), viewModel) - - viewModel.showCancelButton { - Log.i("[Chat Room] Transfer cancelled") - dialog.dismiss() - } - - viewModel.showOkButton( - { - Log.i("[Chat Room] Transfer confirmed") - chatSendingViewModel.transferMessage(chatMessage) - dialog.dismiss() - }, - getString(R.string.chat_message_context_menu_forward) - ) - - dialog.show() - } - - private fun showPopupMenu(chatRoom: ChatRoom) { - val popupView: ChatRoomMenuBindingImpl = DataBindingUtil.inflate( - LayoutInflater.from(context), - R.layout.chat_room_menu, - null, - false - ) - val readOnly = chatRoom.isReadOnly - popupView.ephemeralEnabled = !readOnly - popupView.devicesEnabled = !readOnly - popupView.meetingEnabled = !readOnly - - val itemSize = AppUtils.getDimension(R.dimen.chat_room_popup_item_height).toInt() - var totalSize = itemSize * 8 - - val notificationsTurnedOff = viewModel.areNotificationsMuted() - if (notificationsTurnedOff) { - popupView.muteHidden = true - totalSize -= itemSize - } else { - popupView.unmuteHidden = true - totalSize -= itemSize - } - - if (viewModel.basicChatRoom || viewModel.oneToOneChatRoom) { - if (viewModel.contact.value != null) { - popupView.addToContactsHidden = true - } else { - popupView.goToContactHidden = true - - if (corePreferences.readOnlyNativeContacts) { - popupView.addToContactsHidden = true - totalSize -= itemSize - } - } - - popupView.meetingHidden = true - totalSize -= itemSize - } else { - popupView.addToContactsHidden = true - popupView.goToContactHidden = true - totalSize -= itemSize - } - - if (viewModel.basicChatRoom) { - popupView.groupInfoHidden = true - totalSize -= itemSize - popupView.devicesHidden = true - totalSize -= itemSize - popupView.ephemeralHidden = true - totalSize -= itemSize - } else { - if (!viewModel.encryptedChatRoom) { - popupView.devicesHidden = true - totalSize -= itemSize - popupView.ephemeralHidden = true - totalSize -= itemSize - } else { - if (viewModel.oneToOneChatRoom) { - popupView.groupInfoHidden = true - totalSize -= itemSize - } - - // If one participant one device, a click on security badge - // will directly start a call or show the dialog, so don't show this menu - if (viewModel.oneParticipantOneDevice) { - popupView.devicesHidden = true - totalSize -= itemSize - } - - if (viewModel.ephemeralChatRoom) { - if (chatRoom.currentParams.ephemeralMode == ChatRoom.EphemeralMode.AdminManaged) { - if (chatRoom.me?.isAdmin == false) { - Log.w( - "[Chat Room] Hiding ephemeral menu as mode is admin managed and we aren't admin" - ) - popupView.ephemeralHidden = true - totalSize -= itemSize - } - } - } - } - } - - // When using WRAP_CONTENT instead of real size, fails to place the - // popup window above if not enough space is available below - val popupWindow = PopupWindow( - popupView.root, - AppUtils.getDimension(R.dimen.chat_room_popup_width).toInt(), - totalSize, - true - ) - // Elevation is for showing a shadow around the popup - popupWindow.elevation = 20f - - popupView.setGroupInfoListener { - showGroupInfo(chatRoom) - popupWindow.dismiss() - } - popupView.setDevicesListener { - showParticipantsDevices() - popupWindow.dismiss() - } - popupView.setEphemeralListener { - showEphemeralMessages() - popupWindow.dismiss() - } - popupView.setMeetingListener { - scheduleMeeting(chatRoom) - popupWindow.dismiss() - } - popupView.setEditionModeListener { - enterEditionMode() - popupWindow.dismiss() - } - popupView.setMuteListener { - viewModel.muteNotifications(true) - sharedViewModel.refreshChatRoomInListEvent.value = Event(true) - popupWindow.dismiss() - } - popupView.setUnmuteListener { - viewModel.muteNotifications(false) - sharedViewModel.refreshChatRoomInListEvent.value = Event(true) - popupWindow.dismiss() - } - popupView.setAddToContactsListener { - popupWindow.dismiss() - val copy = viewModel.getRemoteAddress()?.clone() - if (copy != null) { - copy.clean() - val address = copy.asStringUriOnly() - Log.i("[Chat Room] Creating contact with SIP URI: $address") - navigateToContacts(address) - } - } - popupView.setGoToContactListener { - popupWindow.dismiss() - val contactId = viewModel.contact.value?.refKey - if (contactId != null) { - Log.i("[Chat Room] Displaying native contact [$contactId]") - navigateToNativeContact(contactId) - } else { - val copy = viewModel.getRemoteAddress()?.clone() - if (copy != null) { - copy.clean() - val address = copy.asStringUriOnly() - Log.i("[Chat Room] Displaying friend with address [$address]") - navigateToFriend(address) - } - } - } - - popupWindow.showAsDropDown(binding.menu, 0, 0, Gravity.BOTTOM) - } - - private fun addDeleteMessageTaskToQueue(eventLog: EventLogData, position: Int) { - val task = lifecycleScope.launch { - delay(2800) // Duration of Snackbar.LENGTH_LONG - withContext(Dispatchers.Main) { - if (isActive) { - Log.i("[Chat Room] Message/event deletion task is still active, proceed") - val chatMessage = eventLog.eventLog.chatMessage - if (chatMessage != null) { - Log.i("[Chat Room] Deleting message $chatMessage at position $position") - listViewModel.deleteMessage(chatMessage) - } else { - Log.i("[Chat Room] Deleting event $eventLog at position $position") - listViewModel.deleteEventLogs(arrayListOf(eventLog)) - } - sharedViewModel.refreshChatRoomInListEvent.value = Event(true) - } - } - } - - (requireActivity() as MainActivity).showSnackBar( - R.string.chat_message_removal_info, - R.string.chat_message_abort_removal - ) { - Log.i( - "[Chat Room] Canceled message/event deletion task: $task for message/event at position $position" - ) - adapter.notifyItemRangeChanged(position, adapter.itemCount - position) - task.cancel() - } - } - - private fun scrollToFirstUnreadMessageOrBottom(smooth: Boolean): Boolean { - if (_adapter != null && adapter.itemCount > 0) { - val recyclerView = binding.chatMessagesList - - // Scroll to first unread message if any, unless we are already on it - val firstUnreadMessagePosition = adapter.getFirstUnreadMessagePosition() - val currentPosition = (recyclerView.layoutManager as LinearLayoutManager).findFirstCompletelyVisibleItemPosition() - val indexToScrollTo = if (firstUnreadMessagePosition != -1 && firstUnreadMessagePosition != currentPosition) { - firstUnreadMessagePosition - } else { - adapter.itemCount - 1 - } - - Log.i( - "[Chat Room] Scrolling to position $indexToScrollTo, first unread message is at $firstUnreadMessagePosition" - ) - scrollTo(indexToScrollTo, smooth) - - if (firstUnreadMessagePosition == 0) { - // Return true only if all unread messages don't fit in the recyclerview height - return recyclerView.computeVerticalScrollRange() > recyclerView.height - } - } - return false - } - - private fun pickFile() { - chatSendingViewModel.attachFilePending.value = true - val intentsList = ArrayList() - - val pickerIntent = Intent(Intent.ACTION_GET_CONTENT) - pickerIntent.type = "*/*" - pickerIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true) - - if (PermissionHelper.get().hasCameraPermission()) { - // Allows to capture directly from the camera - val capturePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) - val tempFileName = System.currentTimeMillis().toString() + ".jpeg" - val file = FileUtils.getFileStoragePath(tempFileName) - chatSendingViewModel.temporaryFileUploadPath = file - try { - val publicUri = FileProvider.getUriForFile( - requireContext(), - requireContext().getString(R.string.file_provider), - file - ) - capturePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, publicUri) - capturePictureIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - capturePictureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) - intentsList.add(capturePictureIntent) - - val captureVideoIntent = Intent(MediaStore.ACTION_VIDEO_CAPTURE) - intentsList.add(captureVideoIntent) - } catch (e: Exception) { - Log.e("[Chat Room] Failed to pick file: $e") - } - } - - val chooserIntent = - Intent.createChooser(pickerIntent, getString(R.string.chat_message_pick_file_dialog)) - chooserIntent.putExtra( - Intent.EXTRA_INITIAL_INTENTS, - intentsList.toArray(arrayOf()) - ) - - startActivityForResult(chooserIntent, 0) - } - - private fun showDialogToSuggestOpeningFileAsText() { - val dialogViewModel = DialogViewModel( - getString(R.string.dialog_try_open_file_as_text_body), - getString(R.string.dialog_try_open_file_as_text_title) - ) - val dialog = DialogUtils.getDialog(requireContext(), dialogViewModel) - - dialogViewModel.showCancelButton { - dialog.dismiss() - } - - dialogViewModel.showOkButton({ - dialog.dismiss() - navigateToTextFileViewer(true) - }) - - dialog.show() - } - - private fun showDialogForUserConsentBeforeExportingFileInThirdPartyApp(content: Content) { - val dialogViewModel = DialogViewModel( - getString(R.string.chat_message_cant_open_file_in_app_dialog_message), - getString(R.string.chat_message_cant_open_file_in_app_dialog_title) - ) - val dialog = DialogUtils.getDialog(requireContext(), dialogViewModel) - - dialogViewModel.showDeleteButton( - { - dialog.dismiss() - lifecycleScope.launch { - Log.i( - "[Chat Room] [VFS] Content is encrypted, requesting plain file path for file [${content.filePath}]" - ) - val plainFilePath = content.exportPlainFile() - if (!FileUtils.openFileInThirdPartyApp(requireActivity(), plainFilePath)) { - showDialogToSuggestOpeningFileAsText() - } - } - }, - getString(R.string.chat_message_cant_open_file_in_app_dialog_export_button) - ) - - dialogViewModel.showOkButton( - { - dialog.dismiss() - navigateToTextFileViewer(true) - }, - getString(R.string.chat_message_cant_open_file_in_app_dialog_open_as_text_button) - ) - - dialogViewModel.showCancelButton { - dialog.dismiss() - } - - dialog.show() - } - - private fun showGroupCallDialog() { - val dialogViewModel = DialogViewModel( - getString(R.string.conference_start_group_call_dialog_message), - getString(R.string.conference_start_group_call_dialog_title) - ) - val dialog: Dialog = DialogUtils.getDialog(requireContext(), dialogViewModel) - - dialogViewModel.iconResource = R.drawable.icon_video_conf_incoming - dialogViewModel.showIcon = true - - dialogViewModel.showCancelButton { - dialog.dismiss() - } - - dialogViewModel.showOkButton( - { - dialog.dismiss() - viewModel.startGroupCall() - }, - getString(R.string.conference_start_group_call_dialog_ok_button) - ) - - dialog.show() - } - - private fun scrollTo(position: Int, smooth: Boolean = true) { - try { - if (smooth && corePreferences.enableAnimations) { - binding.chatMessagesList.smoothScrollToPosition(position) - } else { - binding.chatMessagesList.scrollToPosition(position) - } - } catch (iae: IllegalArgumentException) { - Log.e("[Chat Room] Can't scroll to position $position") - } - } - - private fun replyToChatMessage(chatMessage: ChatMessage) { - chatSendingViewModel.pendingChatMessageToReplyTo.value?.destroy() - chatSendingViewModel.pendingChatMessageToReplyTo.value = - ChatMessageData(chatMessage) - chatSendingViewModel.isPendingAnswer.value = true - - if (chatSendingViewModel.sendMessageEnabled.value == false) { - // Open keyboard - binding.footer.message.requestFocus() - (requireActivity() as MainActivity).showKeyboard() - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/DevicesFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/DevicesFragment.kt deleted file mode 100644 index a9f847813..000000000 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/DevicesFragment.kt +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.chat.fragments - -import android.os.Bundle -import android.view.View -import androidx.lifecycle.ViewModelProvider -import androidx.navigation.fragment.findNavController -import org.linphone.R -import org.linphone.activities.main.chat.viewmodels.DevicesListViewModel -import org.linphone.activities.main.chat.viewmodels.DevicesListViewModelFactory -import org.linphone.activities.main.fragments.SecureFragment -import org.linphone.core.tools.Log -import org.linphone.databinding.ChatRoomDevicesFragmentBinding - -class DevicesFragment : SecureFragment() { - private lateinit var listViewModel: DevicesListViewModel - - override fun getLayoutId(): Int = R.layout.chat_room_devices_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - val chatRoom = sharedViewModel.selectedChatRoom.value - if (chatRoom == null) { - Log.e("[Devices] Chat room is null, aborting!") - findNavController().navigateUp() - return - } - - isSecure = chatRoom.currentParams.isEncryptionEnabled - - listViewModel = ViewModelProvider( - this, - DevicesListViewModelFactory(chatRoom) - )[DevicesListViewModel::class.java] - binding.viewModel = listViewModel - } - - override fun onResume() { - super.onResume() - - listViewModel.updateParticipants() - } -} diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/EphemeralFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/EphemeralFragment.kt deleted file mode 100644 index ba4a3382e..000000000 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/EphemeralFragment.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.chat.fragments - -import android.os.Bundle -import android.view.View -import androidx.lifecycle.ViewModelProvider -import androidx.navigation.fragment.findNavController -import org.linphone.R -import org.linphone.activities.main.chat.viewmodels.EphemeralViewModel -import org.linphone.activities.main.chat.viewmodels.EphemeralViewModelFactory -import org.linphone.activities.main.fragments.SecureFragment -import org.linphone.core.tools.Log -import org.linphone.databinding.ChatRoomEphemeralFragmentBinding -import org.linphone.utils.Event - -class EphemeralFragment : SecureFragment() { - private lateinit var viewModel: EphemeralViewModel - - override fun getLayoutId(): Int { - return R.layout.chat_room_ephemeral_fragment - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - isSecure = true - binding.lifecycleOwner = viewLifecycleOwner - - val chatRoom = sharedViewModel.selectedChatRoom.value - if (chatRoom == null) { - Log.e("[Ephemeral] Chat room is null, aborting!") - findNavController().navigateUp() - return - } - - viewModel = ViewModelProvider( - this, - EphemeralViewModelFactory(chatRoom) - )[EphemeralViewModel::class.java] - binding.viewModel = viewModel - - binding.setValidClickListener { - viewModel.updateChatRoomEphemeralDuration() - sharedViewModel.refreshChatRoomInListEvent.value = Event(true) - goBack() - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/GroupInfoFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/GroupInfoFragment.kt deleted file mode 100644 index 3450a6ead..000000000 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/GroupInfoFragment.kt +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.chat.fragments - -import android.app.Dialog -import android.os.Bundle -import android.view.View -import androidx.lifecycle.ViewModelProvider -import androidx.recyclerview.widget.LinearLayoutManager -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.main.MainActivity -import org.linphone.activities.main.chat.GroupChatRoomMember -import org.linphone.activities.main.chat.adapters.GroupInfoParticipantsAdapter -import org.linphone.activities.main.chat.data.GroupInfoParticipantData -import org.linphone.activities.main.chat.viewmodels.GroupInfoViewModel -import org.linphone.activities.main.chat.viewmodels.GroupInfoViewModelFactory -import org.linphone.activities.main.fragments.SecureFragment -import org.linphone.activities.main.viewmodels.DialogViewModel -import org.linphone.activities.navigateToChatRoom -import org.linphone.activities.navigateToChatRoomCreation -import org.linphone.core.Address -import org.linphone.core.ChatRoom -import org.linphone.databinding.ChatRoomGroupInfoFragmentBinding -import org.linphone.utils.AppUtils -import org.linphone.utils.DialogUtils - -class GroupInfoFragment : SecureFragment() { - private lateinit var viewModel: GroupInfoViewModel - private lateinit var adapter: GroupInfoParticipantsAdapter - private var meAdminStatusChangedDialog: Dialog? = null - - override fun getLayoutId(): Int = R.layout.chat_room_group_info_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - val chatRoom: ChatRoom? = sharedViewModel.selectedGroupChatRoom.value - isSecure = chatRoom?.currentParams?.isEncryptionEnabled ?: false - - viewModel = ViewModelProvider( - this, - GroupInfoViewModelFactory(chatRoom) - )[GroupInfoViewModel::class.java] - binding.viewModel = viewModel - - viewModel.isEncrypted.value = sharedViewModel.createEncryptedChatRoom - - adapter = GroupInfoParticipantsAdapter( - viewLifecycleOwner, - chatRoom?.hasCapability(ChatRoom.Capabilities.Encrypted.toInt()) ?: (viewModel.isEncrypted.value == true) - ) - binding.participants.adapter = adapter - - val layoutManager = LinearLayoutManager(requireContext()) - binding.participants.layoutManager = layoutManager - - // Divider between items - binding.participants.addItemDecoration( - AppUtils.getDividerDecoration(requireContext(), layoutManager) - ) - - viewModel.participants.observe( - viewLifecycleOwner - ) { - adapter.submitList(it) - } - - viewModel.isMeAdmin.observe( - viewLifecycleOwner - ) { isMeAdmin -> - adapter.showAdminControls(isMeAdmin && chatRoom != null) - } - - viewModel.meAdminChangedEvent.observe( - viewLifecycleOwner - ) { - it.consume { isMeAdmin -> - showMeAdminStateChanged(isMeAdmin) - } - } - - adapter.participantRemovedEvent.observe( - viewLifecycleOwner - ) { - it.consume { participant -> - viewModel.removeParticipant(participant) - } - } - - addParticipantsFromSharedViewModel() - - viewModel.createdChatRoomEvent.observe( - viewLifecycleOwner - ) { - it.consume { chatRoom -> - goToChatRoom(chatRoom, true) - } - } - - viewModel.updatedChatRoomEvent.observe( - viewLifecycleOwner - ) { - it.consume { chatRoom -> - goToChatRoom(chatRoom, false) - } - } - - binding.setNextClickListener { - if (viewModel.chatRoom != null) { - viewModel.updateRoom() - } else { - viewModel.createChatRoom() - } - } - - binding.setParticipantsClickListener { - sharedViewModel.createEncryptedChatRoom = corePreferences.forceEndToEndEncryptedChat || viewModel.isEncrypted.value == true - - val list = arrayListOf
() - for (participant in viewModel.participants.value.orEmpty()) { - list.add(participant.participant.address) - } - sharedViewModel.chatRoomParticipants.value = list - sharedViewModel.chatRoomSubject = viewModel.subject.value.orEmpty() - - val args = Bundle() - args.putBoolean("createGroup", true) - navigateToChatRoomCreation(args) - } - - binding.setLeaveClickListener { - val dialogViewModel = DialogViewModel( - getString(R.string.chat_room_group_info_leave_dialog_message) - ) - val dialog: Dialog = DialogUtils.getDialog(requireContext(), dialogViewModel) - - dialogViewModel.showDeleteButton( - { - viewModel.leaveGroup() - dialog.dismiss() - }, - getString(R.string.chat_room_group_info_leave_dialog_button) - ) - - dialogViewModel.showCancelButton { - dialog.dismiss() - } - - dialog.show() - } - - viewModel.onMessageToNotifyEvent.observe( - viewLifecycleOwner - ) { - it.consume { messageResourceId -> - (activity as MainActivity).showSnackBar(messageResourceId) - } - } - } - - private fun addParticipantsFromSharedViewModel() { - val participants = sharedViewModel.chatRoomParticipants.value - if (participants != null && participants.size > 0) { - val list = arrayListOf() - - for (address in participants) { - val exists = viewModel.participants.value?.find { - it.participant.address.weakEqual(address) - } - - if (exists != null) { - list.add(exists) - } else { - list.add( - GroupInfoParticipantData( - GroupChatRoomMember( - address, - false, - hasLimeX3DHCapability = viewModel.isEncrypted.value == true - ) - ) - ) - } - } - - viewModel.participants.value = list - } - - if (sharedViewModel.chatRoomSubject.isNotEmpty()) { - viewModel.subject.value = sharedViewModel.chatRoomSubject - sharedViewModel.chatRoomSubject = "" - } - } - - private fun showMeAdminStateChanged(isMeAdmin: Boolean) { - meAdminStatusChangedDialog?.dismiss() - - val message = if (isMeAdmin) { - getString(R.string.chat_room_group_info_you_are_now_admin) - } else { - getString(R.string.chat_room_group_info_you_are_no_longer_admin) - } - val dialogViewModel = DialogViewModel(message) - val dialog = DialogUtils.getDialog(requireContext(), dialogViewModel) - - dialogViewModel.showOkButton({ - dialog.dismiss() - }) - - dialog.show() - meAdminStatusChangedDialog = dialog - } - - private fun goToChatRoom(chatRoom: ChatRoom, created: Boolean) { - sharedViewModel.selectedChatRoom.value = chatRoom - navigateToChatRoom(AppUtils.createBundleWithSharedTextAndFiles(sharedViewModel), created) - } -} diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/ImdnFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/ImdnFragment.kt deleted file mode 100644 index adadf75f5..000000000 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/ImdnFragment.kt +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.chat.fragments - -import android.os.Bundle -import android.view.View -import androidx.lifecycle.ViewModelProvider -import androidx.navigation.fragment.findNavController -import androidx.recyclerview.widget.LinearLayoutManager -import org.linphone.R -import org.linphone.activities.main.chat.adapters.ImdnAdapter -import org.linphone.activities.main.chat.viewmodels.ImdnViewModel -import org.linphone.activities.main.chat.viewmodels.ImdnViewModelFactory -import org.linphone.activities.main.fragments.SecureFragment -import org.linphone.core.tools.Log -import org.linphone.databinding.ChatRoomImdnFragmentBinding -import org.linphone.utils.AppUtils -import org.linphone.utils.RecyclerViewHeaderDecoration - -class ImdnFragment : SecureFragment() { - private lateinit var viewModel: ImdnViewModel - private lateinit var adapter: ImdnAdapter - - override fun getLayoutId(): Int { - return R.layout.chat_room_imdn_fragment - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - val chatRoom = sharedViewModel.selectedChatRoom.value - if (chatRoom == null) { - Log.e("[IMDN] Chat room is null, aborting!") - findNavController().navigateUp() - return - } - - isSecure = chatRoom.currentParams.isEncryptionEnabled - - if (arguments != null) { - val messageId = arguments?.getString("MessageId") - val message = if (messageId != null) chatRoom.findMessage(messageId) else null - if (message != null) { - Log.i("[IMDN] Found message $message with id $messageId") - viewModel = ViewModelProvider( - this, - ImdnViewModelFactory(message) - )[ImdnViewModel::class.java] - binding.viewModel = viewModel - } else { - Log.e("[IMDN] Couldn't find message with id $messageId in chat room $chatRoom") - findNavController().popBackStack() - return - } - } else { - Log.e("[IMDN] Couldn't find message id in intent arguments") - findNavController().popBackStack() - return - } - - adapter = ImdnAdapter(viewLifecycleOwner) - binding.participantsList.adapter = adapter - - val layoutManager = LinearLayoutManager(requireContext()) - binding.participantsList.layoutManager = layoutManager - - // Divider between items - binding.participantsList.addItemDecoration( - AppUtils.getDividerDecoration(requireContext(), layoutManager) - ) - - // Displays state header - val headerItemDecoration = RecyclerViewHeaderDecoration(requireContext(), adapter) - binding.participantsList.addItemDecoration(headerItemDecoration) - - viewModel.participants.observe( - viewLifecycleOwner - ) { - adapter.submitList(it) - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/chat/fragments/MasterChatRoomsFragment.kt b/app/src/main/java/org/linphone/activities/main/chat/fragments/MasterChatRoomsFragment.kt deleted file mode 100644 index e41ff59db..000000000 --- a/app/src/main/java/org/linphone/activities/main/chat/fragments/MasterChatRoomsFragment.kt +++ /dev/null @@ -1,412 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.chat.fragments - -import android.app.Dialog -import android.content.res.Configuration -import android.os.Bundle -import android.view.View -import androidx.core.content.ContextCompat -import androidx.lifecycle.ViewModelProvider -import androidx.navigation.fragment.NavHostFragment -import androidx.recyclerview.widget.ItemTouchHelper -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import androidx.slidingpanelayout.widget.SlidingPaneLayout -import com.google.android.material.transition.MaterialSharedAxis -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.GenericActivity -import org.linphone.activities.clearDisplayedChatRoom -import org.linphone.activities.main.MainActivity -import org.linphone.activities.main.chat.adapters.ChatRoomsListAdapter -import org.linphone.activities.main.chat.viewmodels.ChatRoomsListViewModel -import org.linphone.activities.main.fragments.MasterFragment -import org.linphone.activities.main.viewmodels.DialogViewModel -import org.linphone.activities.navigateToChatRoom -import org.linphone.activities.navigateToChatRoomCreation -import org.linphone.core.ChatRoom -import org.linphone.core.Factory -import org.linphone.core.tools.Log -import org.linphone.databinding.ChatRoomMasterFragmentBinding -import org.linphone.utils.* - -class MasterChatRoomsFragment : MasterFragment() { - override val dialogConfirmationMessageBeforeRemoval = R.plurals.chat_room_delete_dialog - private lateinit var listViewModel: ChatRoomsListViewModel - - private val observer = object : RecyclerView.AdapterDataObserver() { - override fun onChanged() { - scrollToTop() - } - override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { - if (positionStart == 0 && itemCount == 1) { - scrollToTop() - } - } - override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) { - scrollToTop() - } - } - - override fun getLayoutId(): Int = R.layout.chat_room_master_fragment - - override fun onDestroyView() { - binding.chatList.adapter = null - adapter.unregisterAdapterDataObserver(observer) - super.onDestroyView() - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - useMaterialSharedAxisXForwardAnimation = false - if (corePreferences.enableAnimations) { - val portraitOrientation = resources.configuration.orientation != Configuration.ORIENTATION_LANDSCAPE - val axis = if (portraitOrientation) MaterialSharedAxis.X else MaterialSharedAxis.Y - enterTransition = MaterialSharedAxis(axis, true) - reenterTransition = MaterialSharedAxis(axis, true) - returnTransition = MaterialSharedAxis(axis, false) - exitTransition = MaterialSharedAxis(axis, false) - } - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - isSecure = true - binding.lifecycleOwner = viewLifecycleOwner - - listViewModel = requireActivity().run { - ViewModelProvider(this)[ChatRoomsListViewModel::class.java] - } - binding.viewModel = listViewModel - - /* Shared view model & sliding pane related */ - - setUpSlidingPane(binding.slidingPane) - - binding.slidingPane.addPanelSlideListener(object : SlidingPaneLayout.PanelSlideListener { - override fun onPanelSlide(panel: View, slideOffset: Float) { } - - override fun onPanelOpened(panel: View) { } - - override fun onPanelClosed(panel: View) { - // Conversation isn't visible anymore, any new message received in it will trigger a notification - coreContext.notificationsManager.currentlyDisplayedChatRoomAddress = null - } - }) - - sharedViewModel.layoutChangedEvent.observe( - viewLifecycleOwner - ) { - it.consume { - sharedViewModel.isSlidingPaneSlideable.value = binding.slidingPane.isSlideable - if (binding.slidingPane.isSlideable) { - val navHostFragment = - childFragmentManager.findFragmentById(R.id.chat_nav_container) as NavHostFragment - if (navHostFragment.navController.currentDestination?.id == R.id.emptyChatFragment) { - Log.i( - "[Chat] Foldable device has been folded, closing side pane with empty fragment" - ) - binding.slidingPane.closePane() - } - } - } - } - - sharedViewModel.refreshChatRoomInListEvent.observe( - viewLifecycleOwner - ) { - it.consume { - val chatRoom = sharedViewModel.selectedChatRoom.value - if (chatRoom != null) { - listViewModel.notifyChatRoomUpdate(chatRoom) - } - } - } - - /* End of shared view model & sliding pane related */ - - _adapter = ChatRoomsListAdapter(listSelectionViewModel, viewLifecycleOwner) - // SubmitList is done on a background thread - // We need this adapter data observer to know when to scroll - adapter.registerAdapterDataObserver(observer) - - binding.chatList.setHasFixedSize(true) - binding.chatList.adapter = adapter - - val layoutManager = LinearLayoutManager(requireContext()) - binding.chatList.layoutManager = layoutManager - - // Swipe action - val swipeConfiguration = RecyclerViewSwipeConfiguration() - val white = ContextCompat.getColor(requireContext(), R.color.white_color) - - swipeConfiguration.rightToLeftAction = RecyclerViewSwipeConfiguration.Action( - requireContext().getString(R.string.dialog_delete), - white, - ContextCompat.getColor(requireContext(), R.color.red_color) - ) - swipeConfiguration.leftToRightAction = RecyclerViewSwipeConfiguration.Action( - requireContext().getString(R.string.received_chat_notification_mark_as_read_label), - white, - ContextCompat.getColor(requireContext(), R.color.imdn_read_color) - ) - val swipeListener = object : RecyclerViewSwipeListener { - override fun onLeftToRightSwipe(viewHolder: RecyclerView.ViewHolder) { - val index = viewHolder.bindingAdapterPosition - if (index < 0 || index >= adapter.currentList.size) { - Log.e("[Chat] Index is out of bound, can't mark chat room as read") - } else { - val data = adapter.currentList[viewHolder.bindingAdapterPosition] - data.markAsRead() - } - } - - override fun onRightToLeftSwipe(viewHolder: RecyclerView.ViewHolder) { - val viewModel = DialogViewModel(getString(R.string.chat_room_delete_one_dialog)) - viewModel.showIcon = true - viewModel.iconResource = R.drawable.dialog_delete_icon - val dialog: Dialog = DialogUtils.getDialog(requireContext(), viewModel) - - val index = viewHolder.bindingAdapterPosition - if (index < 0 || index >= adapter.currentList.size) { - Log.e("[Chat] Index is out of bound, can't delete chat room") - } else { - viewModel.showCancelButton { - adapter.notifyItemChanged(viewHolder.bindingAdapterPosition) - dialog.dismiss() - } - - viewModel.showDeleteButton( - { - val deletedChatRoom = - adapter.currentList[index].chatRoom - listViewModel.deleteChatRoom(deletedChatRoom) - if (!binding.slidingPane.isSlideable && - deletedChatRoom == sharedViewModel.selectedChatRoom.value - ) { - Log.i( - "[Chat] Currently displayed chat room has been deleted, removing detail fragment" - ) - clearDisplayedChatRoom() - } - dialog.dismiss() - }, - getString(R.string.dialog_delete) - ) - - dialog.show() - } - } - } - RecyclerViewSwipeUtils( - ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT, - swipeConfiguration, - swipeListener - ) - .attachToRecyclerView(binding.chatList) - - // Divider between items - binding.chatList.addItemDecoration( - AppUtils.getDividerDecoration(requireContext(), layoutManager) - ) - - listViewModel.chatRooms.observe( - viewLifecycleOwner - ) { chatRooms -> - adapter.submitList(chatRooms) - } - - listViewModel.chatRoomIndexUpdatedEvent.observe( - viewLifecycleOwner - ) { - it.consume { index -> - adapter.notifyItemChanged(index) - } - } - - adapter.selectedChatRoomEvent.observe( - viewLifecycleOwner - ) { - it.consume { chatRoom -> - if ((requireActivity() as GenericActivity).isDestructionPending) { - Log.w("[Chat] Activity is pending destruction, don't start navigating now!") - sharedViewModel.destructionPendingChatRoom = chatRoom - } else { - if (chatRoom.peerAddress.asStringUriOnly() == coreContext.notificationsManager.currentlyDisplayedChatRoomAddress) { - if (!binding.slidingPane.isOpen) { - Log.w("[Chat] Chat room is displayed but sliding pane is closed...") - if (!binding.slidingPane.openPane()) { - Log.e( - "[Chat] Tried to open pane to workaround already displayed chat room issue, failed!" - ) - } - } else { - Log.w("[Chat] This chat room is already displayed!") - } - } else { - sharedViewModel.selectedChatRoom.value = chatRoom - navigateToChatRoom( - AppUtils.createBundleWithSharedTextAndFiles( - sharedViewModel - ) - ) - binding.slidingPane.openPane() - } - } - } - } - - binding.setEditClickListener { - listSelectionViewModel.isEditionEnabled.value = true - } - - binding.setCancelForwardClickListener { - sharedViewModel.messageToForwardEvent.value?.consume { - Log.i("[Chat] Cancelling message forward") - } - sharedViewModel.isPendingMessageForward.value = false - } - - binding.setCancelSharingClickListener { - Log.i("[Chat] Cancelling text/files sharing") - sharedViewModel.textToShare.value = "" - sharedViewModel.filesToShare.value = arrayListOf() - listViewModel.fileSharingPending.value = false - listViewModel.textSharingPending.value = false - } - - binding.setNewOneToOneChatRoomClickListener { - sharedViewModel.chatRoomParticipants.value = arrayListOf() - navigateToChatRoomCreation(false, binding.slidingPane) - } - - binding.setNewGroupChatRoomClickListener { - sharedViewModel.selectedGroupChatRoom.value = null - sharedViewModel.chatRoomParticipants.value = arrayListOf() - navigateToChatRoomCreation(true, binding.slidingPane) - } - - val pendingDestructionChatRoom = sharedViewModel.destructionPendingChatRoom - if (pendingDestructionChatRoom != null) { - Log.w("[Chat] Found pending chat room from before activity was recreated") - sharedViewModel.destructionPendingChatRoom = null - sharedViewModel.selectedChatRoom.value = pendingDestructionChatRoom - navigateToChatRoom(AppUtils.createBundleWithSharedTextAndFiles(sharedViewModel)) - } - - val localSipUri = arguments?.getString("LocalSipUri") - val remoteSipUri = arguments?.getString("RemoteSipUri") - if (localSipUri != null && remoteSipUri != null) { - Log.i( - "[Chat] Found local [$localSipUri] & remote [$remoteSipUri] addresses in arguments" - ) - arguments?.clear() - val localAddress = Factory.instance().createAddress(localSipUri) - val remoteSipAddress = Factory.instance().createAddress(remoteSipUri) - val chatRoom = coreContext.core.searchChatRoom( - null, - localAddress, - remoteSipAddress, - arrayOfNulls(0) - ) - if (chatRoom != null) { - Log.i("[Chat] Found matching chat room $chatRoom") - adapter.selectedChatRoomEvent.value = Event(chatRoom) - } - } else { - sharedViewModel.textToShare.observe( - viewLifecycleOwner - ) { - if (it.isNotEmpty()) { - Log.i("[Chat] Found text to share") - listViewModel.textSharingPending.value = true - clearDisplayedChatRoom() - } else { - if (sharedViewModel.filesToShare.value.isNullOrEmpty()) { - listViewModel.textSharingPending.value = false - } - } - } - sharedViewModel.filesToShare.observe( - viewLifecycleOwner - ) { - if (it.isNotEmpty()) { - Log.i("[Chat] Found ${it.size} files to share") - listViewModel.fileSharingPending.value = true - clearDisplayedChatRoom() - } else { - if (sharedViewModel.textToShare.value.isNullOrEmpty()) { - listViewModel.fileSharingPending.value = false - } - } - } - sharedViewModel.isPendingMessageForward.observe( - viewLifecycleOwner - ) { - listViewModel.forwardPending.value = it - adapter.forwardPending(it) - if (it) { - Log.i("[Chat] Found chat message to transfer") - } - } - - listViewModel.onMessageToNotifyEvent.observe( - viewLifecycleOwner - ) { - it.consume { messageResourceId -> - (activity as MainActivity).showSnackBar(messageResourceId) - } - } - } - } - - override fun onResume() { - super.onResume() - - listViewModel.groupChatAvailable.value = LinphoneUtils.isGroupChatAvailable() - } - - override fun deleteItems(indexesOfItemToDelete: ArrayList) { - val list = ArrayList() - var closeSlidingPane = false - for (index in indexesOfItemToDelete) { - val chatRoom = adapter.currentList[index].chatRoom - list.add(chatRoom) - - if (chatRoom == sharedViewModel.selectedChatRoom.value) { - closeSlidingPane = true - } - } - listViewModel.deleteChatRooms(list) - - if (!binding.slidingPane.isSlideable && closeSlidingPane) { - Log.i("[Chat] Currently displayed chat room has been deleted, removing detail fragment") - clearDisplayedChatRoom() - } - } - - private fun scrollToTop() { - binding.chatList.scrollToPosition(0) - } -} diff --git a/app/src/main/java/org/linphone/activities/main/chat/receivers/RichContentReceiver.kt b/app/src/main/java/org/linphone/activities/main/chat/receivers/RichContentReceiver.kt deleted file mode 100644 index e8dfeaee0..000000000 --- a/app/src/main/java/org/linphone/activities/main/chat/receivers/RichContentReceiver.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.chat.receivers - -import android.content.ClipData -import android.net.Uri -import android.view.View -import androidx.core.util.component1 -import androidx.core.util.component2 -import androidx.core.view.ContentInfoCompat -import androidx.core.view.OnReceiveContentListener -import org.linphone.core.tools.Log - -class RichContentReceiver(private val contentReceived: (uri: Uri) -> Unit) : OnReceiveContentListener { - companion object { - val MIME_TYPES = arrayOf("image/png", "image/gif", "image/jpeg") - } - - override fun onReceiveContent(view: View, payload: ContentInfoCompat): ContentInfoCompat? { - val (uriContent, remaining) = payload.partition { item -> item.uri != null } - if (uriContent != null) { - val clip: ClipData = uriContent.clip - for (i in 0 until clip.itemCount) { - val uri: Uri = clip.getItemAt(i).uri - Log.i("[Content Receiver] Found URI: $uri") - contentReceived(uri) - } - } - // Return anything that your app didn't handle. This preserves the default platform - // behavior for text and anything else that you aren't implementing custom handling for. - return remaining - } -} diff --git a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatMessageSendingViewModel.kt b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatMessageSendingViewModel.kt deleted file mode 100644 index 9649aafc0..000000000 --- a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatMessageSendingViewModel.kt +++ /dev/null @@ -1,596 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.chat.viewmodels - -import android.view.inputmethod.EditorInfo -import android.widget.Toast -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import androidx.media.AudioFocusRequestCompat -import java.io.File -import java.text.SimpleDateFormat -import java.util.* -import kotlin.collections.ArrayList -import kotlinx.coroutines.* -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.main.chat.data.ChatMessageAttachmentData -import org.linphone.activities.main.chat.data.ChatMessageData -import org.linphone.compatibility.Compatibility -import org.linphone.core.* -import org.linphone.core.tools.Log -import org.linphone.utils.* -import org.linphone.utils.Event - -class ChatMessageSendingViewModelFactory(private val chatRoom: ChatRoom) : - ViewModelProvider.NewInstanceFactory() { - - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - return ChatMessageSendingViewModel(chatRoom) as T - } -} - -class ChatMessageSendingViewModel(private val chatRoom: ChatRoom) : ViewModel() { - var temporaryFileUploadPath: File? = null - - val attachments = MutableLiveData>() - - val attachFileEnabled = MutableLiveData() - - val attachFilePending = MutableLiveData() - - val sendMessageEnabled = MutableLiveData() - - val attachingFileInProgress = MutableLiveData() - - val isReadOnly = MutableLiveData() - - var textToSend = MutableLiveData() - - val isPendingAnswer = MutableLiveData() - - var pendingChatMessageToReplyTo = MutableLiveData() - - val requestRecordAudioPermissionEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val messageSentEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val voiceRecordingProgressBarMax = 10000 - - val isPendingVoiceRecord = MutableLiveData() - - val isVoiceRecording = MutableLiveData() - - val voiceRecordingDuration = MutableLiveData() - - val formattedDuration = MutableLiveData() - - val isPlayingVoiceRecording = MutableLiveData() - - val voiceRecordPlayingPosition = MutableLiveData() - - val imeFlags: Int = if (chatRoom.hasCapability(ChatRoom.Capabilities.Encrypted.toInt())) { - // IME_FLAG_NO_PERSONALIZED_LEARNING is only available on Android 8 and newer - Compatibility.getImeFlagsForSecureChatRoom() - } else { - EditorInfo.IME_NULL - } - - val isEmojiPickerOpen = MutableLiveData() - - val isEmojiPickerVisible = MutableLiveData() - - val isFileTransferAvailable = MutableLiveData() - - val requestKeyboardHidingEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - private lateinit var recorder: Recorder - - private var voiceRecordAudioFocusRequest: AudioFocusRequestCompat? = null - - private lateinit var voiceRecordingPlayer: Player - private val playerListener = PlayerListener { - Log.i("[Chat Message Sending] End of file reached") - stopVoiceRecordPlayer() - } - - private val chatRoomListener: ChatRoomListenerStub = object : ChatRoomListenerStub() { - override fun onStateChanged(chatRoom: ChatRoom, state: ChatRoom.State) { - updateChatRoomReadOnlyState() - } - - override fun onConferenceJoined(chatRoom: ChatRoom, eventLog: EventLog) { - updateChatRoomReadOnlyState() - } - - override fun onConferenceLeft(chatRoom: ChatRoom, eventLog: EventLog) { - updateChatRoomReadOnlyState() - } - } - - private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) - - init { - chatRoom.addListener(chatRoomListener) - - attachments.value = arrayListOf() - - attachFileEnabled.value = true - sendMessageEnabled.value = false - isEmojiPickerOpen.value = false - isEmojiPickerVisible.value = corePreferences.showEmojiPickerButton - isFileTransferAvailable.value = LinphoneUtils.isFileTransferAvailable() - updateChatRoomReadOnlyState() - } - - override fun onCleared() { - pendingChatMessageToReplyTo.value?.destroy() - - for (pendingAttachment in attachments.value.orEmpty()) { - removeAttachment(pendingAttachment) - } - - if (this::recorder.isInitialized) { - if (recorder.state != Recorder.State.Closed) { - recorder.close() - } - } - - if (this::voiceRecordingPlayer.isInitialized) { - stopVoiceRecordPlayer() - voiceRecordingPlayer.removeListener(playerListener) - } - - chatRoom.removeListener(chatRoomListener) - scope.cancel() - super.onCleared() - } - - fun onTextToSendChanged(value: String) { - sendMessageEnabled.value = value.trim().isNotEmpty() || attachments.value?.isNotEmpty() == true || isPendingVoiceRecord.value == true - - val showEmojiPicker = value.isEmpty() || AppUtils.isTextOnlyContainingEmoji(value) - isEmojiPickerVisible.value = corePreferences.showEmojiPickerButton && showEmojiPicker - - if (value.isNotEmpty()) { - if (attachFileEnabled.value == true && !corePreferences.allowMultipleFilesAndTextInSameMessage) { - attachFileEnabled.value = false - } - chatRoom.compose() - } else { - if (!corePreferences.allowMultipleFilesAndTextInSameMessage) { - attachFileEnabled.value = attachments.value?.isEmpty() ?: true - } - } - } - - fun addAttachment(path: String) { - val list = arrayListOf() - list.addAll(attachments.value.orEmpty()) - list.add( - ChatMessageAttachmentData(path) { - removeAttachment(it) - } - ) - attachments.value = list - - sendMessageEnabled.value = textToSend.value.orEmpty().trim().isNotEmpty() || list.isNotEmpty() || isPendingVoiceRecord.value == true - if (!corePreferences.allowMultipleFilesAndTextInSameMessage) { - attachFileEnabled.value = false - } - } - - private fun removeAttachment(attachment: ChatMessageAttachmentData) { - val list = arrayListOf() - list.addAll(attachments.value.orEmpty()) - list.remove(attachment) - attachments.value = list - - val pathToDelete = attachment.path - Log.i( - "[Chat Message Sending] Attachment is being removed, delete local copy [$pathToDelete]" - ) - FileUtils.deleteFile(pathToDelete) - - sendMessageEnabled.value = textToSend.value.orEmpty().trim().isNotEmpty() || list.isNotEmpty() || isPendingVoiceRecord.value == true - if (!corePreferences.allowMultipleFilesAndTextInSameMessage) { - attachFileEnabled.value = list.isEmpty() - } - } - - fun toggleEmojiPicker() { - isEmojiPickerOpen.value = isEmojiPickerOpen.value == false - if (isEmojiPickerOpen.value == true) { - requestKeyboardHidingEvent.value = Event(true) - } - } - - private fun createChatMessage(): ChatMessage { - val pendingMessageToReplyTo = pendingChatMessageToReplyTo.value - return if (isPendingAnswer.value == true && pendingMessageToReplyTo != null) { - chatRoom.createReplyMessage(pendingMessageToReplyTo.chatMessage) - } else { - chatRoom.createEmptyMessage() - } - } - - fun sendMessage() { - if (!isPlayerClosed()) { - stopVoiceRecordPlayer() - } - - if (isVoiceRecording.value == true) { - stopVoiceRecorder() - } - - val message = createChatMessage() - val isBasicChatRoom: Boolean = chatRoom.hasCapability(ChatRoom.Capabilities.Basic.toInt()) - - var voiceRecord = false - if (isPendingVoiceRecord.value == true && recorder.file != null) { - val content = recorder.createContent() - if (content != null) { - Log.i( - "[Chat Message Sending] Voice recording content created, file name is ${content.name} and duration is ${content.fileDuration}" - ) - message.addContent(content) - voiceRecord = true - } else { - Log.e("[Chat Message Sending] Voice recording content couldn't be created!") - } - - isPendingVoiceRecord.value = false - isVoiceRecording.value = false - } - - val toSend = textToSend.value.orEmpty().trim() - if (toSend.isNotEmpty()) { - if (voiceRecord && isBasicChatRoom) { - val textMessage = createChatMessage() - textMessage.addUtf8TextContent(toSend) - textMessage.send() - } else { - message.addUtf8TextContent(toSend) - } - } - - var fileContent = false - for (attachment in attachments.value.orEmpty()) { - val content = Factory.instance().createContent() - - content.type = when { - attachment.isImage -> "image" - attachment.isAudio -> "audio" - attachment.isVideo -> "video" - attachment.isPdf -> "application" - else -> "file" - } - content.subtype = FileUtils.getExtensionFromFileName(attachment.fileName) - content.name = attachment.fileName - content.filePath = attachment.path // Let the file body handler take care of the upload - - // Do not send file in the same message as the text in a BasicChatRoom - // and don't send multiple files in the same message if setting says so - if (isBasicChatRoom or (corePreferences.preventMoreThanOneFilePerMessage and (fileContent or voiceRecord))) { - val fileMessage = createChatMessage() - fileMessage.addFileContent(content) - fileMessage.send() - } else { - message.addFileContent(content) - fileContent = true - } - } - - if (message.contents.isNotEmpty()) { - message.send() - } - - cancelReply() - attachments.value = arrayListOf() - textToSend.value = "" - - messageSentEvent.value = Event(true) - } - - fun transferMessage(chatMessage: ChatMessage) { - val message = chatRoom.createForwardMessage(chatMessage) - message.send() - } - - fun cancelReply() { - pendingChatMessageToReplyTo.value?.destroy() - isPendingAnswer.value = false - } - - private fun tickerFlowRecording() = flow { - while (isVoiceRecording.value == true) { - emit(Unit) - delay(100) - } - } - - private fun tickerFlowPlaying() = flow { - while (isPlayingVoiceRecording.value == true) { - emit(Unit) - delay(100) - } - } - - fun toggleVoiceRecording() { - if (corePreferences.holdToRecordVoiceMessage) { - // Disables click listener just in case, touch listener will be used instead - return - } - - if (!this::recorder.isInitialized) { - initVoiceMessageRecorder() - } - - if (isVoiceRecording.value == true) { - stopVoiceRecording() - } else { - startVoiceRecording() - } - } - - fun startVoiceRecording() { - if (!PermissionHelper.get().hasRecordAudioPermission()) { - requestRecordAudioPermissionEvent.value = Event(true) - return - } - - if (voiceRecordAudioFocusRequest == null) { - voiceRecordAudioFocusRequest = AppUtils.acquireAudioFocusForVoiceRecordingOrPlayback( - coreContext.context - ) - } - - when (recorder.state) { - Recorder.State.Running -> Log.w("[Chat Message Sending] Recorder is already recording") - Recorder.State.Paused -> { - Log.w("[Chat Message Sending] Recorder isn't closed, resuming recording") - recorder.start() - } - Recorder.State.Closed -> { - val extension = when (recorder.params.fileFormat) { - Recorder.FileFormat.Mkv -> "mkv" - else -> "wav" - } - val tempFileName = "voice-recording-${System.currentTimeMillis()}.$extension" - val file = FileUtils.getFileStoragePath(tempFileName) - Log.w( - "[Chat Message Sending] Recorder is closed, starting recording in ${file.absoluteFile}" - ) - recorder.open(file.absolutePath) - recorder.start() - } - else -> {} - } - - val duration = recorder.duration - voiceRecordingDuration.value = duration - formattedDuration.value = SimpleDateFormat("mm:ss", Locale.getDefault()).format(duration) // duration is in ms - - isPendingVoiceRecord.value = true - isVoiceRecording.value = true - sendMessageEnabled.value = true - - val maxVoiceRecordDuration = corePreferences.voiceRecordingMaxDuration - tickerFlowRecording().onEach { - withContext(Dispatchers.Main) { - val duration = recorder.duration - voiceRecordingDuration.value = recorder.duration % voiceRecordingProgressBarMax - formattedDuration.value = SimpleDateFormat("mm:ss", Locale.getDefault()).format( - duration - ) // duration is in ms - - if (duration >= maxVoiceRecordDuration) { - Log.w( - "[Chat Message Sending] Max duration for voice recording exceeded (${maxVoiceRecordDuration}ms), stopping." - ) - stopVoiceRecording() - } - } - }.launchIn(scope) - } - - fun cancelVoiceRecording() { - if (recorder.state != Recorder.State.Closed) { - Log.i("[Chat Message Sending] Closing voice recorder") - recorder.close() - - val path = recorder.file - if (path != null) { - Log.i("[Chat Message Sending] Deleting voice recording file: $path") - FileUtils.deleteFile(path) - } - } - - val request = voiceRecordAudioFocusRequest - if (request != null) { - AppUtils.releaseAudioFocusForVoiceRecordingOrPlayback(coreContext.context, request) - voiceRecordAudioFocusRequest = null - } - - isPendingVoiceRecord.value = false - isVoiceRecording.value = false - sendMessageEnabled.value = textToSend.value.orEmpty().trim().isNotEmpty() == true || attachments.value?.isNotEmpty() == true - - if (!isPlayerClosed()) { - stopVoiceRecordPlayer() - } - } - - private fun stopVoiceRecorder() { - if (recorder.state == Recorder.State.Running) { - Log.i("[Chat Message Sending] Pausing / closing voice recorder") - recorder.pause() - recorder.close() - voiceRecordingDuration.value = recorder.duration - } - - val request = voiceRecordAudioFocusRequest - if (request != null) { - AppUtils.releaseAudioFocusForVoiceRecordingOrPlayback(coreContext.context, request) - voiceRecordAudioFocusRequest = null - } - - isVoiceRecording.value = false - } - - fun stopVoiceRecording() { - stopVoiceRecorder() - - if (corePreferences.sendVoiceRecordingRightAway) { - Log.i("[Chat Message Sending] Sending voice recording right away") - sendMessage() - } - } - - fun playRecordedMessage() { - if (isPlayerClosed()) { - Log.w("[Chat Message Sending] Player closed, let's open it first") - initVoiceRecordPlayer() - } - - if (AppUtils.isMediaVolumeLow(coreContext.context)) { - Toast.makeText( - coreContext.context, - R.string.chat_message_voice_recording_playback_low_volume, - Toast.LENGTH_LONG - ).show() - } - - if (voiceRecordAudioFocusRequest == null) { - voiceRecordAudioFocusRequest = AppUtils.acquireAudioFocusForVoiceRecordingOrPlayback( - coreContext.context - ) - } - - voiceRecordingPlayer.start() - isPlayingVoiceRecording.value = true - - tickerFlowPlaying().onEach { - withContext(Dispatchers.Main) { - voiceRecordPlayingPosition.value = voiceRecordingPlayer.currentPosition - } - }.launchIn(scope) - } - - fun pauseRecordedMessage() { - Log.i("[Chat Message Sending] Pausing voice record") - if (!isPlayerClosed()) { - voiceRecordingPlayer.pause() - } - - val request = voiceRecordAudioFocusRequest - if (request != null) { - AppUtils.releaseAudioFocusForVoiceRecordingOrPlayback(coreContext.context, request) - voiceRecordAudioFocusRequest = null - } - - isPlayingVoiceRecording.value = false - } - - private fun initVoiceMessageRecorder() { - Log.i("[Chat Message Sending] Creating recorder for voice message") - val recorderParams = coreContext.core.createRecorderParams() - if (corePreferences.voiceMessagesFormatMkv) { - recorderParams.fileFormat = Recorder.FileFormat.Mkv - } else { - recorderParams.fileFormat = Recorder.FileFormat.Wav - } - - val recordingAudioDevice = AudioRouteUtils.getAudioRecordingDeviceForVoiceMessage() - recorderParams.audioDevice = recordingAudioDevice - Log.i( - "[Chat Message Sending] Using device ${recorderParams.audioDevice?.id} to make the voice message recording" - ) - - recorder = coreContext.core.createRecorder(recorderParams) - } - - private fun initVoiceRecordPlayer() { - Log.i("[Chat Message Sending] Creating player for voice record") - - val playbackSoundCard = AudioRouteUtils.getAudioPlaybackDeviceIdForCallRecordingOrVoiceMessage() - Log.i( - "[Chat Message Sending] Using device $playbackSoundCard to make the voice message playback" - ) - - val localPlayer = coreContext.core.createLocalPlayer(playbackSoundCard, null, null) - if (localPlayer != null) { - voiceRecordingPlayer = localPlayer - } else { - Log.e("[Chat Message Sending] Couldn't create local player!") - return - } - voiceRecordingPlayer.addListener(playerListener) - - val path = recorder.file - if (path != null) { - voiceRecordingPlayer.open(path) - // Update recording duration using player value to ensure proper progress bar animation - voiceRecordingDuration.value = voiceRecordingPlayer.duration - } - } - - private fun stopVoiceRecordPlayer() { - if (!isPlayerClosed()) { - Log.i("[Chat Message Sending] Stopping voice record") - voiceRecordingPlayer.pause() - voiceRecordingPlayer.seek(0) - voiceRecordPlayingPosition.value = 0 - voiceRecordingPlayer.close() - } - - val request = voiceRecordAudioFocusRequest - if (request != null) { - AppUtils.releaseAudioFocusForVoiceRecordingOrPlayback(coreContext.context, request) - voiceRecordAudioFocusRequest = null - } - - isPlayingVoiceRecording.value = false - } - - private fun isPlayerClosed(): Boolean { - return !this::voiceRecordingPlayer.isInitialized || voiceRecordingPlayer.state == Player.State.Closed - } - - private fun updateChatRoomReadOnlyState() { - isReadOnly.value = chatRoom.isReadOnly || ( - chatRoom.hasCapability( - ChatRoom.Capabilities.Conference.toInt() - ) && chatRoom.participants.isEmpty() - ) - } -} diff --git a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatMessagesListViewModel.kt b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatMessagesListViewModel.kt deleted file mode 100644 index cc3cdad16..000000000 --- a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatMessagesListViewModel.kt +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.chat.viewmodels - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import java.util.* -import kotlin.math.max -import org.linphone.activities.main.chat.data.EventLogData -import org.linphone.core.* -import org.linphone.core.tools.Log -import org.linphone.utils.Event -import org.linphone.utils.LinphoneUtils -import org.linphone.utils.PermissionHelper - -class ChatMessagesListViewModelFactory(private val chatRoom: ChatRoom) : - ViewModelProvider.NewInstanceFactory() { - - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - return ChatMessagesListViewModel(chatRoom) as T - } -} - -class ChatMessagesListViewModel(private val chatRoom: ChatRoom) : ViewModel() { - companion object { - private const val MESSAGES_PER_PAGE = 20 - } - - val events = MutableLiveData>() - - val messageUpdatedEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val requestWriteExternalStoragePermissionEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - private val chatRoomListener: ChatRoomListenerStub = object : ChatRoomListenerStub() { - override fun onChatMessagesReceived(chatRoom: ChatRoom, eventLogs: Array) { - for (eventLog in eventLogs) { - addChatMessageEventLog(eventLog) - } - } - - override fun onChatMessageSending(chatRoom: ChatRoom, eventLog: EventLog) { - val position = events.value.orEmpty().size - - if (eventLog.type == EventLog.Type.ConferenceChatMessage) { - val chatMessage = eventLog.chatMessage - chatMessage ?: return - chatMessage.userData = position - } - - addEvent(eventLog) - } - - override fun onSecurityEvent(chatRoom: ChatRoom, eventLog: EventLog) { - addEvent(eventLog) - } - - override fun onParticipantAdded(chatRoom: ChatRoom, eventLog: EventLog) { - addEvent(eventLog) - } - - override fun onParticipantRemoved(chatRoom: ChatRoom, eventLog: EventLog) { - addEvent(eventLog) - } - - override fun onParticipantAdminStatusChanged(chatRoom: ChatRoom, eventLog: EventLog) { - addEvent(eventLog) - } - - override fun onSubjectChanged(chatRoom: ChatRoom, eventLog: EventLog) { - addEvent(eventLog) - } - - override fun onConferenceJoined(chatRoom: ChatRoom, eventLog: EventLog) { - if (!chatRoom.hasCapability(ChatRoom.Capabilities.OneToOne.toInt())) { - addEvent(eventLog) - } - } - - override fun onConferenceLeft(chatRoom: ChatRoom, eventLog: EventLog) { - if (!chatRoom.hasCapability(ChatRoom.Capabilities.OneToOne.toInt())) { - addEvent(eventLog) - } - } - - override fun onEphemeralMessageDeleted(chatRoom: ChatRoom, eventLog: EventLog) { - Log.i( - "[Chat Messages] An ephemeral chat message has expired, removing it from event list" - ) - deleteEvent(eventLog) - } - - override fun onEphemeralEvent(chatRoom: ChatRoom, eventLog: EventLog) { - addEvent(eventLog) - } - } - - init { - chatRoom.addListener(chatRoomListener) - - events.value = getEvents() - } - - override fun onCleared() { - events.value.orEmpty().forEach(EventLogData::destroy) - chatRoom.removeListener(chatRoomListener) - - super.onCleared() - } - - fun resendMessage(chatMessage: ChatMessage) { - val position: Int = chatMessage.userData as Int - chatMessage.send() - messageUpdatedEvent.value = Event(position) - } - - fun deleteMessage(chatMessage: ChatMessage) { - LinphoneUtils.deleteFilesAttachedToChatMessage(chatMessage) - chatRoom.deleteMessage(chatMessage) - - events.value.orEmpty().forEach(EventLogData::destroy) - events.value = getEvents() - } - - fun deleteEventLogs(listToDelete: ArrayList) { - for (eventLog in listToDelete) { - LinphoneUtils.deleteFilesAttachedToEventLog(eventLog.eventLog) - eventLog.eventLog.deleteFromDatabase() - } - - events.value.orEmpty().forEach(EventLogData::destroy) - events.value = getEvents() - } - - fun loadMoreData(totalItemsCount: Int) { - Log.i("[Chat Messages] Load more data, current total is $totalItemsCount") - val maxSize: Int = chatRoom.historyEventsSize - - if (totalItemsCount < maxSize) { - var upperBound: Int = totalItemsCount + MESSAGES_PER_PAGE - if (upperBound > maxSize) { - upperBound = maxSize - } - - val history: Array = chatRoom.getHistoryRangeEvents( - totalItemsCount, - upperBound - ) - val list = arrayListOf() - for (eventLog in history) { - list.add(EventLogData(eventLog)) - } - list.addAll(events.value.orEmpty()) - events.value = list - } - } - - private fun addEvent(eventLog: EventLog) { - val list = arrayListOf() - list.addAll(events.value.orEmpty()) - val found = list.find { data -> data.eventLog == eventLog } - if (found == null) { - list.add(EventLogData(eventLog)) - } - events.value = list - } - - private fun getEvents(): ArrayList { - val list = arrayListOf() - val unreadCount = chatRoom.unreadMessagesCount - var loadCount = max(MESSAGES_PER_PAGE, unreadCount) - Log.i( - "[Chat Messages] $unreadCount unread messages in this chat room, loading $loadCount from history" - ) - - val history = chatRoom.getHistoryEvents(loadCount) - var messageCount = 0 - for (eventLog in history) { - list.add(EventLogData(eventLog)) - if (eventLog.type == EventLog.Type.ConferenceChatMessage) { - messageCount += 1 - } - } - - // Load enough events to have at least all unread messages - while (unreadCount > 0 && messageCount < unreadCount) { - Log.w( - "[Chat Messages] There is only $messageCount messages in the last $loadCount events, loading $MESSAGES_PER_PAGE more" - ) - val moreHistory = chatRoom.getHistoryRangeEvents( - loadCount, - loadCount + MESSAGES_PER_PAGE - ) - loadCount += MESSAGES_PER_PAGE - for (eventLog in moreHistory) { - list.add(EventLogData(eventLog)) - if (eventLog.type == EventLog.Type.ConferenceChatMessage) { - messageCount += 1 - } - } - } - - return list - } - - private fun deleteEvent(eventLog: EventLog) { - val chatMessage = eventLog.chatMessage - if (chatMessage != null) { - LinphoneUtils.deleteFilesAttachedToChatMessage(chatMessage) - chatRoom.deleteMessage(chatMessage) - } - - events.value.orEmpty().forEach(EventLogData::destroy) - events.value = getEvents() - } - - private fun addChatMessageEventLog(eventLog: EventLog) { - if (eventLog.type == EventLog.Type.ConferenceChatMessage) { - val chatMessage = eventLog.chatMessage - chatMessage ?: return - chatMessage.userData = events.value.orEmpty().size - - val existingEvent = events.value.orEmpty().find { data -> - data.eventLog.type == EventLog.Type.ConferenceChatMessage && data.eventLog.chatMessage?.messageId == chatMessage.messageId - } - if (existingEvent != null) { - Log.w( - "[Chat Messages] Found already present chat message, don't add it it's probably the result of an auto download or an aggregated message received before but notified after the conversation was displayed" - ) - return - } - - if (!PermissionHelper.get().hasWriteExternalStoragePermission()) { - for (content in chatMessage.contents) { - if (content.isFileTransfer) { - Log.i( - "[Chat Messages] Android < 10 detected and WRITE_EXTERNAL_STORAGE permission isn't granted yet" - ) - requestWriteExternalStoragePermissionEvent.value = Event(true) - } - } - } - } - - addEvent(eventLog) - } -} diff --git a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomCreationViewModel.kt b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomCreationViewModel.kt deleted file mode 100644 index 56d6ab24e..000000000 --- a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomCreationViewModel.kt +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.chat.viewmodels - -import androidx.lifecycle.MutableLiveData -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.contact.ContactsSelectionViewModel -import org.linphone.core.* -import org.linphone.core.tools.Log -import org.linphone.utils.AppUtils -import org.linphone.utils.Event -import org.linphone.utils.LinphoneUtils - -class ChatRoomCreationViewModel : ContactsSelectionViewModel() { - val chatRoomCreatedEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val createGroupChat = MutableLiveData() - - val isEncrypted = MutableLiveData() - - val waitForChatRoomCreation = MutableLiveData() - - val secureChatAvailable = MutableLiveData() - - val secureChatMandatory: Boolean = corePreferences.forceEndToEndEncryptedChat - - private val listener = object : ChatRoomListenerStub() { - override fun onStateChanged(room: ChatRoom, state: ChatRoom.State) { - if (state == ChatRoom.State.Created) { - waitForChatRoomCreation.value = false - Log.i("[Chat Room Creation] Chat room created") - chatRoomCreatedEvent.value = Event(room) - } else if (state == ChatRoom.State.CreationFailed) { - Log.e("[Chat Room Creation] Group chat room creation has failed !") - waitForChatRoomCreation.value = false - onMessageToNotifyEvent.value = Event(R.string.chat_room_creation_failed_snack) - } - } - } - - init { - createGroupChat.value = false - isEncrypted.value = secureChatMandatory - waitForChatRoomCreation.value = false - secureChatAvailable.value = LinphoneUtils.isEndToEndEncryptedChatAvailable() - } - - fun updateEncryption(encrypted: Boolean) { - if (!encrypted && secureChatMandatory) { - Log.w( - "[Chat Room Creation] Something tries to force plain text chat room even if secureChatMandatory is enabled!" - ) - return - } - isEncrypted.value = encrypted - } - - fun createOneToOneChat(searchResult: SearchResult) { - waitForChatRoomCreation.value = true - val defaultAccount = coreContext.core.defaultAccount - var room: ChatRoom? - - val address = searchResult.address ?: coreContext.core.interpretUrl( - searchResult.phoneNumber ?: "", - LinphoneUtils.applyInternationalPrefix() - ) - if (address == null) { - Log.e("[Chat Room Creation] Can't get a valid address from search result $searchResult") - onMessageToNotifyEvent.value = Event(R.string.chat_room_creation_failed_snack) - waitForChatRoomCreation.value = false - return - } - - val encrypted = secureChatMandatory || isEncrypted.value == true - val params: ChatRoomParams = coreContext.core.createDefaultChatRoomParams() - params.backend = ChatRoom.Backend.Basic - params.isGroupEnabled = false - if (encrypted) { - params.isEncryptionEnabled = true - params.backend = ChatRoom.Backend.FlexisipChat - params.ephemeralMode = if (corePreferences.useEphemeralPerDeviceMode) { - ChatRoom.EphemeralMode.DeviceManaged - } else { - ChatRoom.EphemeralMode.AdminManaged - } - params.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default - Log.i( - "[Chat Room Creation] Ephemeral mode is ${params.ephemeralMode}, lifetime is ${params.ephemeralLifetime}" - ) - params.subject = AppUtils.getString(R.string.chat_room_dummy_subject) - } - - val participants = arrayOf(address) - val localAddress: Address? = defaultAccount?.params?.identityAddress - - room = coreContext.core.searchChatRoom(params, localAddress, null, participants) - if (room == null) { - Log.w( - "[Chat Room Creation] Couldn't find existing 1-1 chat room with remote ${address.asStringUriOnly()}, encryption=$encrypted and local identity ${localAddress?.asStringUriOnly()}" - ) - room = coreContext.core.createChatRoom(params, localAddress, participants) - - if (room != null) { - if (encrypted) { - val state = room.state - if (state == ChatRoom.State.Created) { - Log.i("[Chat Room Creation] Found already created chat room, using it") - chatRoomCreatedEvent.value = Event(room) - waitForChatRoomCreation.value = false - } else { - Log.i( - "[Chat Room Creation] Chat room creation is pending [$state], waiting for Created state" - ) - room.addListener(listener) - } - } else { - chatRoomCreatedEvent.value = Event(room) - waitForChatRoomCreation.value = false - } - } else { - Log.e( - "[Chat Room Creation] Couldn't create chat room with remote ${address.asStringUriOnly()} and local identity ${localAddress?.asStringUriOnly()}" - ) - waitForChatRoomCreation.value = false - } - } else { - Log.i( - "[Chat Room Creation] Found existing 1-1 chat room with remote ${address.asStringUriOnly()}, encryption=$encrypted and local identity ${localAddress?.asStringUriOnly()}" - ) - chatRoomCreatedEvent.value = Event(room) - waitForChatRoomCreation.value = false - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomViewModel.kt b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomViewModel.kt deleted file mode 100644 index 24bab68c1..000000000 --- a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomViewModel.kt +++ /dev/null @@ -1,447 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.chat.viewmodels - -import android.animation.ValueAnimator -import android.view.animation.LinearInterpolator -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.* -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.contact.ContactDataInterface -import org.linphone.contact.ContactsUpdatedListenerStub -import org.linphone.core.* -import org.linphone.core.tools.Log -import org.linphone.utils.AppUtils -import org.linphone.utils.LinphoneUtils -import org.linphone.utils.TimestampUtils - -class ChatRoomViewModelFactory(private val chatRoom: ChatRoom) : - ViewModelProvider.NewInstanceFactory() { - - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - return ChatRoomViewModel(chatRoom) as T - } -} - -class ChatRoomViewModel(val chatRoom: ChatRoom) : ViewModel(), ContactDataInterface { - override val contact: MutableLiveData = MutableLiveData() - override val displayName: MutableLiveData = MutableLiveData() - override val securityLevel: MutableLiveData = MutableLiveData() - override val showGroupChatAvatar: Boolean - get() = conferenceChatRoom && !oneToOneChatRoom - override val presenceStatus: MutableLiveData = MutableLiveData() - override val coroutineScope: CoroutineScope = viewModelScope - - val subject = MutableLiveData() - - val participants = MutableLiveData() - - val unreadMessagesCount = MutableLiveData() - - val remoteIsComposing = MutableLiveData() - - val composingList = MutableLiveData() - - val securityLevelIcon = MutableLiveData() - - val securityLevelContentDescription = MutableLiveData() - - val lastPresenceInfo = MutableLiveData() - - val ephemeralEnabled = MutableLiveData() - - val basicChatRoom: Boolean by lazy { - chatRoom.hasCapability(ChatRoom.Capabilities.Basic.toInt()) - } - - val oneToOneChatRoom: Boolean by lazy { - chatRoom.hasCapability(ChatRoom.Capabilities.OneToOne.toInt()) - } - - private val conferenceChatRoom: Boolean by lazy { - chatRoom.hasCapability(ChatRoom.Capabilities.Conference.toInt()) - } - - val encryptedChatRoom: Boolean by lazy { - chatRoom.hasCapability(ChatRoom.Capabilities.Encrypted.toInt()) - } - - val ephemeralChatRoom: Boolean by lazy { - chatRoom.hasCapability(ChatRoom.Capabilities.Ephemeral.toInt()) - } - - val meAdmin: MutableLiveData by lazy { - MutableLiveData() - } - - val isUserScrollingUp = MutableLiveData() - - var oneParticipantOneDevice: Boolean = false - - var onlyParticipantOnlyDeviceAddress: Address? = null - - val chatUnreadCountTranslateY = MutableLiveData() - - val groupCallAvailable: Boolean - get() = LinphoneUtils.isRemoteConferencingAvailable() - - private var addressToCall: Address? = null - - private val bounceAnimator: ValueAnimator by lazy { - ValueAnimator.ofFloat( - AppUtils.getDimension(R.dimen.tabs_fragment_unread_count_bounce_offset), - 0f - ).apply { - addUpdateListener { - val value = it.animatedValue as Float - chatUnreadCountTranslateY.value = value - } - interpolator = LinearInterpolator() - duration = 250 - repeatMode = ValueAnimator.REVERSE - repeatCount = ValueAnimator.INFINITE - } - } - - private val contactsUpdatedListener = object : ContactsUpdatedListenerStub() { - override fun onContactsUpdated() { - Log.d("[Chat Room] Contacts have changed") - contactLookup() - } - } - - private val coreListener: CoreListenerStub = object : CoreListenerStub() { - override fun onChatRoomRead(core: Core, room: ChatRoom) { - if (room == chatRoom) { - updateUnreadMessageCount() - } - } - } - - private val chatRoomListener: ChatRoomListenerStub = object : ChatRoomListenerStub() { - override fun onStateChanged(chatRoom: ChatRoom, state: ChatRoom.State) { - Log.i("[Chat Room] $chatRoom state changed: $state") - if (state == ChatRoom.State.Created) { - contactLookup() - updateSecurityIcon() - updateParticipants() - subject.value = chatRoom.subject - } - } - - override fun onSubjectChanged(chatRoom: ChatRoom, eventLog: EventLog) { - subject.value = chatRoom.subject - } - - override fun onChatMessagesReceived(chatRoom: ChatRoom, eventLogs: Array) { - updateUnreadMessageCount() - } - - override fun onParticipantAdded(chatRoom: ChatRoom, eventLog: EventLog) { - contactLookup() - updateSecurityIcon() - updateParticipants() - } - - override fun onParticipantRemoved(chatRoom: ChatRoom, eventLog: EventLog) { - contactLookup() - updateSecurityIcon() - updateParticipants() - } - - override fun onIsComposingReceived( - chatRoom: ChatRoom, - remoteAddr: Address, - isComposing: Boolean - ) { - updateRemotesComposing() - } - - override fun onConferenceJoined(chatRoom: ChatRoom, eventLog: EventLog) { - contactLookup() - updateSecurityIcon() - subject.value = chatRoom.subject - } - - override fun onSecurityEvent(chatRoom: ChatRoom, eventLog: EventLog) { - updateSecurityIcon() - } - - override fun onParticipantDeviceAdded(chatRoom: ChatRoom, eventLog: EventLog) { - updateSecurityIcon() - updateParticipants() - } - - override fun onParticipantDeviceRemoved(chatRoom: ChatRoom, eventLog: EventLog) { - updateSecurityIcon() - updateParticipants() - } - - override fun onEphemeralEvent(chatRoom: ChatRoom, eventLog: EventLog) { - ephemeralEnabled.value = chatRoom.isEphemeralEnabled - } - - override fun onParticipantAdminStatusChanged(chatRoom: ChatRoom, eventLog: EventLog) { - meAdmin.value = chatRoom.me?.isAdmin ?: false - } - } - - init { - chatRoom.core.addListener(coreListener) - chatRoom.addListener(chatRoomListener) - coreContext.contactsManager.addListener(contactsUpdatedListener) - - updateUnreadMessageCount() - - subject.value = chatRoom.subject - updateSecurityIcon() - meAdmin.value = chatRoom.me?.isAdmin ?: false - ephemeralEnabled.value = chatRoom.isEphemeralEnabled - - contactLookup() - updateParticipants() - - updateRemotesComposing() - } - - override fun onCleared() { - coreContext.contactsManager.removeListener(contactsUpdatedListener) - chatRoom.removeListener(chatRoomListener) - chatRoom.core.removeListener(coreListener) - if (corePreferences.enableAnimations) bounceAnimator.end() - super.onCleared() - } - - fun contactLookup() { - presenceStatus.value = ConsolidatedPresence.Offline - displayName.value = when { - basicChatRoom -> LinphoneUtils.getDisplayName( - chatRoom.peerAddress - ) - oneToOneChatRoom -> LinphoneUtils.getDisplayName( - chatRoom.participants.firstOrNull()?.address ?: chatRoom.peerAddress - ) - conferenceChatRoom -> chatRoom.subject.orEmpty() - else -> chatRoom.peerAddress.asStringUriOnly() - } - - if (oneToOneChatRoom) { - searchMatchingContact() - } else { - getParticipantsNames() - } - } - - fun startCall() { - val address = addressToCall ?: if (basicChatRoom) { - chatRoom.peerAddress - } else { - chatRoom.participants.firstOrNull()?.address - } - if (address != null) { - coreContext.startCall(address) - } else { - Log.e("[Chat Room] Failed to find a SIP address to call!") - } - } - - fun startGroupCall() { - val conferenceScheduler = coreContext.core.createConferenceScheduler() - val conferenceInfo = Factory.instance().createConferenceInfo() - - val localAddress = chatRoom.localAddress.clone() - localAddress.clean() // Remove GRUU - val addresses = Array(chatRoom.participants.size) { - index -> - chatRoom.participants[index].address - } - val localAccount = coreContext.core.accountList.find { - account -> - account.params.identityAddress?.weakEqual(localAddress) ?: false - } - - conferenceInfo.organizer = localAddress - conferenceInfo.subject = subject.value - conferenceInfo.setParticipants(addresses) - conferenceScheduler.account = localAccount - // Will trigger the conference creation/update automatically - conferenceScheduler.info = conferenceInfo - } - - fun areNotificationsMuted(): Boolean { - return chatRoom.muted - } - - fun muteNotifications(mute: Boolean) { - chatRoom.muted = mute - } - - fun getRemoteAddress(): Address? { - return if (basicChatRoom) { - chatRoom.peerAddress - } else { - if (chatRoom.participants.isNotEmpty()) { - chatRoom.participants[0].address - } else { - Log.e( - "[Chat Room] ${chatRoom.peerAddress} doesn't have any participant (state ${chatRoom.state})!" - ) - null - } - } - } - - private fun searchMatchingContact() { - val remoteAddress = getRemoteAddress() - if (remoteAddress != null) { - val friend = coreContext.contactsManager.findContactByAddress(remoteAddress) - if (friend != null) { - contact.value = friend!! - presenceStatus.value = friend.consolidatedPresence - computeLastSeenLabel(friend) - friend.addListener { - presenceStatus.value = it.consolidatedPresence - computeLastSeenLabel(friend) - } - } - } - } - - private fun computeLastSeenLabel(friend: Friend) { - if (friend.consolidatedPresence == ConsolidatedPresence.Online) { - lastPresenceInfo.value = AppUtils.getString(R.string.chat_room_presence_online) - return - } else if (friend.consolidatedPresence == ConsolidatedPresence.DoNotDisturb) { - lastPresenceInfo.value = AppUtils.getString(R.string.chat_room_presence_do_not_disturb) - return - } - - val timestamp = friend.presenceModel?.latestActivityTimestamp ?: -1L - lastPresenceInfo.value = if (timestamp != -1L) { - when { - TimestampUtils.isToday(timestamp) -> { - val time = TimestampUtils.timeToString(timestamp, timestampInSecs = true) - val text = - AppUtils.getString(R.string.chat_room_presence_last_seen_online_today) - "$text $time" - } - - TimestampUtils.isYesterday(timestamp) -> { - val time = TimestampUtils.timeToString(timestamp, timestampInSecs = true) - val text = AppUtils.getString( - R.string.chat_room_presence_last_seen_online_yesterday - ) - "$text $time" - } - - else -> { - val date = TimestampUtils.toString( - timestamp, - onlyDate = true, - shortDate = false, - hideYear = true - ) - val text = AppUtils.getString(R.string.chat_room_presence_last_seen_online) - "$text $date" - } - } - } else { - AppUtils.getString(R.string.chat_room_presence_away) - } - } - - private fun getParticipantsNames() { - if (oneToOneChatRoom) return - - var participantsList = "" - var index = 0 - for (participant in chatRoom.participants) { - val contact = coreContext.contactsManager.findContactByAddress(participant.address) - participantsList += contact?.name ?: LinphoneUtils.getDisplayName(participant.address) - index++ - if (index != chatRoom.nbParticipants) participantsList += ", " - } - participants.value = participantsList - } - - private fun updateSecurityIcon() { - val level = chatRoom.securityLevel - securityLevel.value = level - - securityLevelIcon.value = when (level) { - ChatRoom.SecurityLevel.Safe -> R.drawable.security_2_indicator - ChatRoom.SecurityLevel.Encrypted -> R.drawable.security_1_indicator - else -> R.drawable.security_alert_indicator - } - securityLevelContentDescription.value = when (level) { - ChatRoom.SecurityLevel.Safe -> R.string.content_description_security_level_safe - ChatRoom.SecurityLevel.Encrypted -> R.string.content_description_security_level_encrypted - else -> R.string.content_description_security_level_unsafe - } - } - - private fun updateRemotesComposing() { - val isComposing = chatRoom.isRemoteComposing - remoteIsComposing.value = isComposing - if (!isComposing) return - - var composing = "" - for (address in chatRoom.composingAddresses) { - val contact = coreContext.contactsManager.findContactByAddress(address) - composing += if (composing.isNotEmpty()) ", " else "" - composing += contact?.name ?: LinphoneUtils.getDisplayName(address) - } - composingList.value = AppUtils.getStringWithPlural( - R.plurals.chat_room_remote_composing, - chatRoom.composingAddresses.size, - composing - ) - } - - private fun updateParticipants() { - val participants = chatRoom.participants - - oneParticipantOneDevice = oneToOneChatRoom && - chatRoom.me?.devices?.size == 1 && - participants.firstOrNull()?.devices?.size == 1 - - addressToCall = if (basicChatRoom) { - chatRoom.peerAddress - } else { - participants.firstOrNull()?.address - } - - onlyParticipantOnlyDeviceAddress = participants.firstOrNull()?.devices?.firstOrNull()?.address - } - - private fun updateUnreadMessageCount() { - val count = chatRoom.unreadMessagesCount - unreadMessagesCount.value = count - if (count > 0 && corePreferences.enableAnimations) { - bounceAnimator.start() - } else if (count == 0 && bounceAnimator.isStarted) bounceAnimator.end() - } -} diff --git a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomsListViewModel.kt b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomsListViewModel.kt deleted file mode 100644 index 7f3915a7a..000000000 --- a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ChatRoomsListViewModel.kt +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.chat.viewmodels - -import androidx.lifecycle.MutableLiveData -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R -import org.linphone.activities.main.chat.data.ChatRoomData -import org.linphone.activities.main.viewmodels.MessageNotifierViewModel -import org.linphone.compatibility.Compatibility -import org.linphone.core.* -import org.linphone.core.tools.Log -import org.linphone.utils.Event -import org.linphone.utils.LinphoneUtils - -class ChatRoomsListViewModel : MessageNotifierViewModel() { - val chatRooms = MutableLiveData>() - - val fileSharingPending = MutableLiveData() - - val textSharingPending = MutableLiveData() - - val forwardPending = MutableLiveData() - - val groupChatAvailable = MutableLiveData() - - val chatRoomIndexUpdatedEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - private val chatRoomListener = object : ChatRoomListenerStub() { - override fun onStateChanged(chatRoom: ChatRoom, newState: ChatRoom.State) { - if (newState == ChatRoom.State.Deleted) { - Log.i( - "[Chat Rooms] Chat room [${LinphoneUtils.getChatRoomId(chatRoom)}] is in Deleted state, removing it from list" - ) - val list = arrayListOf() - val id = LinphoneUtils.getChatRoomId(chatRoom) - for (data in chatRooms.value.orEmpty()) { - if (data.id != id) { - list.add(data) - } - } - chatRooms.value = list - } - } - } - - private val listener: CoreListenerStub = object : CoreListenerStub() { - override fun onChatRoomStateChanged(core: Core, chatRoom: ChatRoom, state: ChatRoom.State) { - if (state == ChatRoom.State.Created) { - Log.i( - "[Chat Rooms] Chat room [${LinphoneUtils.getChatRoomId(chatRoom)}] is in Created state, adding it to list" - ) - val data = ChatRoomData(chatRoom) - val list = arrayListOf() - list.add(data) - list.addAll(chatRooms.value.orEmpty()) - chatRooms.value = list - } else if (state == ChatRoom.State.TerminationFailed) { - Log.e( - "[Chat Rooms] Group chat room removal for address ${chatRoom.peerAddress.asStringUriOnly()} has failed !" - ) - onMessageToNotifyEvent.value = Event(R.string.chat_room_removal_failed_snack) - } - } - - override fun onMessageSent(core: Core, chatRoom: ChatRoom, message: ChatMessage) { - onChatRoomMessageEvent(chatRoom) - } - - override fun onMessagesReceived( - core: Core, - chatRoom: ChatRoom, - messages: Array - ) { - onChatRoomMessageEvent(chatRoom) - } - - override fun onChatRoomRead(core: Core, chatRoom: ChatRoom) { - notifyChatRoomUpdate(chatRoom) - } - - override fun onChatRoomEphemeralMessageDeleted(core: Core, chatRoom: ChatRoom) { - notifyChatRoomUpdate(chatRoom) - } - - override fun onChatRoomSubjectChanged(core: Core, chatRoom: ChatRoom) { - notifyChatRoomUpdate(chatRoom) - } - } - - private var chatRoomsToDeleteCount = 0 - - init { - groupChatAvailable.value = LinphoneUtils.isGroupChatAvailable() - updateChatRooms() - coreContext.core.addListener(listener) - } - - override fun onCleared() { - coreContext.core.removeListener(listener) - - super.onCleared() - } - - fun deleteChatRoom(chatRoom: ChatRoom?) { - for (eventLog in chatRoom?.getHistoryMessageEvents(0).orEmpty()) { - LinphoneUtils.deleteFilesAttachedToEventLog(eventLog) - } - - chatRoomsToDeleteCount = 1 - if (chatRoom != null) { - coreContext.notificationsManager.dismissChatNotification(chatRoom) - Compatibility.removeChatRoomShortcut(coreContext.context, chatRoom) - chatRoom.addListener(chatRoomListener) - coreContext.core.deleteChatRoom(chatRoom) - } - } - - fun deleteChatRooms(chatRooms: ArrayList) { - chatRoomsToDeleteCount = chatRooms.size - for (chatRoom in chatRooms) { - for (eventLog in chatRoom.getHistoryMessageEvents(0)) { - LinphoneUtils.deleteFilesAttachedToEventLog(eventLog) - } - - coreContext.notificationsManager.dismissChatNotification(chatRoom) - Compatibility.removeChatRoomShortcut(coreContext.context, chatRoom) - chatRoom.addListener(chatRoomListener) - chatRoom.core.deleteChatRoom(chatRoom) - } - } - - fun updateChatRooms() { - chatRooms.value.orEmpty().forEach(ChatRoomData::destroy) - - val list = arrayListOf() - for (chatRoom in coreContext.core.chatRooms) { - list.add(ChatRoomData(chatRoom)) - } - chatRooms.value = list - } - - fun notifyChatRoomUpdate(chatRoom: ChatRoom) { - val index = findChatRoomIndex(chatRoom) - if (index == -1) { - updateChatRooms() - } else { - chatRoomIndexUpdatedEvent.value = Event(index) - } - } - - private fun reorderChatRooms() { - val list = arrayListOf() - list.addAll(chatRooms.value.orEmpty()) - list.sortByDescending { data -> data.chatRoom.lastUpdateTime } - chatRooms.value = list - } - - private fun findChatRoomIndex(chatRoom: ChatRoom): Int { - val id = LinphoneUtils.getChatRoomId(chatRoom) - for ((index, data) in chatRooms.value.orEmpty().withIndex()) { - if (id == data.id) { - return index - } - } - return -1 - } - - private fun onChatRoomMessageEvent(chatRoom: ChatRoom) { - when (findChatRoomIndex(chatRoom)) { - -1 -> updateChatRooms() - 0 -> chatRoomIndexUpdatedEvent.value = Event(0) - else -> reorderChatRooms() - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/DevicesListViewModel.kt b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/DevicesListViewModel.kt deleted file mode 100644 index 625ed9419..000000000 --- a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/DevicesListViewModel.kt +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.chat.viewmodels - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import org.linphone.activities.main.chat.data.DevicesListGroupData -import org.linphone.core.ChatRoom -import org.linphone.core.ChatRoomListenerStub -import org.linphone.core.EventLog - -class DevicesListViewModelFactory(private val chatRoom: ChatRoom) : - ViewModelProvider.NewInstanceFactory() { - - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - return DevicesListViewModel(chatRoom) as T - } -} - -class DevicesListViewModel(private val chatRoom: ChatRoom) : ViewModel() { - val participants = MutableLiveData>() - - private val listener = object : ChatRoomListenerStub() { - override fun onParticipantDeviceAdded(chatRoom: ChatRoom, eventLog: EventLog) { - updateParticipants() - } - - override fun onParticipantDeviceRemoved(chatRoom: ChatRoom, eventLog: EventLog) { - updateParticipants() - } - - override fun onParticipantAdded(chatRoom: ChatRoom, eventLog: EventLog) { - updateParticipants() - } - - override fun onParticipantRemoved(chatRoom: ChatRoom, eventLog: EventLog) { - updateParticipants() - } - } - - init { - chatRoom.addListener(listener) - } - - override fun onCleared() { - participants.value.orEmpty().forEach(DevicesListGroupData::destroy) - chatRoom.removeListener(listener) - - super.onCleared() - } - - fun updateParticipants() { - participants.value.orEmpty().forEach(DevicesListGroupData::destroy) - - val list = arrayListOf() - val me = chatRoom.me - if (me != null) list.add(DevicesListGroupData(me)) - for (participant in chatRoom.participants) { - list.add(DevicesListGroupData(participant)) - } - - participants.value = list - } -} diff --git a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/EphemeralViewModel.kt b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/EphemeralViewModel.kt deleted file mode 100644 index 22da304cf..000000000 --- a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/EphemeralViewModel.kt +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.chat.viewmodels - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import org.linphone.R -import org.linphone.activities.main.chat.data.DurationItemClicked -import org.linphone.activities.main.chat.data.EphemeralDurationData -import org.linphone.core.ChatRoom -import org.linphone.core.tools.Log - -class EphemeralViewModelFactory(private val chatRoom: ChatRoom) : - ViewModelProvider.NewInstanceFactory() { - - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - return EphemeralViewModel(chatRoom) as T - } -} - -class EphemeralViewModel(private val chatRoom: ChatRoom) : ViewModel() { - val durationsList = MutableLiveData>() - - var currentSelectedDuration: Long = 0 - - private val listener = object : DurationItemClicked { - override fun onDurationValueChanged(duration: Long) { - currentSelectedDuration = duration - computeEphemeralDurationValues() - } - } - - init { - Log.i( - "[Ephemeral Messages] Current lifetime is ${chatRoom.ephemeralLifetime}, ephemeral enabled? ${chatRoom.isEphemeralEnabled}" - ) - currentSelectedDuration = if (chatRoom.isEphemeralEnabled) chatRoom.ephemeralLifetime else 0 - computeEphemeralDurationValues() - } - - fun updateChatRoomEphemeralDuration() { - Log.i("[Ephemeral Messages] Selected value is $currentSelectedDuration") - if (currentSelectedDuration > 0) { - if (chatRoom.ephemeralLifetime != currentSelectedDuration) { - Log.i( - "[Ephemeral Messages] Setting new lifetime for ephemeral messages to $currentSelectedDuration" - ) - chatRoom.ephemeralLifetime = currentSelectedDuration - } else { - Log.i( - "[Ephemeral Messages] Configured lifetime for ephemeral messages was already $currentSelectedDuration" - ) - } - - if (!chatRoom.isEphemeralEnabled) { - Log.i("[Ephemeral Messages] Ephemeral messages were disabled, enable them") - chatRoom.isEphemeralEnabled = true - } - } else if (chatRoom.isEphemeralEnabled) { - Log.i("[Ephemeral Messages] Ephemeral messages were enabled, disable them") - chatRoom.isEphemeralEnabled = false - } - } - - private fun computeEphemeralDurationValues() { - val list = arrayListOf() - list.add( - EphemeralDurationData( - R.string.chat_room_ephemeral_message_disabled, - currentSelectedDuration, - 0, - listener - ) - ) - list.add( - EphemeralDurationData( - R.string.chat_room_ephemeral_message_one_minute, - currentSelectedDuration, - 60, - listener - ) - ) - list.add( - EphemeralDurationData( - R.string.chat_room_ephemeral_message_one_hour, - currentSelectedDuration, - 3600, - listener - ) - ) - list.add( - EphemeralDurationData( - R.string.chat_room_ephemeral_message_one_day, - currentSelectedDuration, - 86400, - listener - ) - ) - list.add( - EphemeralDurationData( - R.string.chat_room_ephemeral_message_three_days, - currentSelectedDuration, - 259200, - listener - ) - ) - list.add( - EphemeralDurationData( - R.string.chat_room_ephemeral_message_one_week, - currentSelectedDuration, - 604800, - listener - ) - ) - durationsList.value = list - } -} diff --git a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/GroupInfoViewModel.kt b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/GroupInfoViewModel.kt deleted file mode 100644 index ffeef03c8..000000000 --- a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/GroupInfoViewModel.kt +++ /dev/null @@ -1,257 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.chat.viewmodels - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.main.chat.GroupChatRoomMember -import org.linphone.activities.main.chat.data.GroupInfoParticipantData -import org.linphone.activities.main.viewmodels.MessageNotifierViewModel -import org.linphone.core.* -import org.linphone.core.tools.Log -import org.linphone.utils.Event - -class GroupInfoViewModelFactory(private val chatRoom: ChatRoom?) : - ViewModelProvider.NewInstanceFactory() { - - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - return GroupInfoViewModel(chatRoom) as T - } -} - -class GroupInfoViewModel(val chatRoom: ChatRoom?) : MessageNotifierViewModel() { - val createdChatRoomEvent = MutableLiveData>() - val updatedChatRoomEvent = MutableLiveData>() - - val subject = MutableLiveData() - - val participants = MutableLiveData>() - - val isEncrypted = MutableLiveData() - - val isMeAdmin = MutableLiveData() - - val canLeaveGroup = MutableLiveData() - - val waitForChatRoomCreation = MutableLiveData() - - val meAdminChangedEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - private val listener = object : ChatRoomListenerStub() { - override fun onStateChanged(chatRoom: ChatRoom, state: ChatRoom.State) { - if (state == ChatRoom.State.Created) { - waitForChatRoomCreation.value = false - createdChatRoomEvent.value = Event(chatRoom) // To trigger going to the chat room - } else if (state == ChatRoom.State.CreationFailed) { - Log.e("[Chat Room Group Info] Group chat room creation has failed !") - waitForChatRoomCreation.value = false - onMessageToNotifyEvent.value = Event(R.string.chat_room_creation_failed_snack) - } - } - - override fun onSubjectChanged(chatRoom: ChatRoom, eventLog: EventLog) { - subject.value = chatRoom.subject - } - - override fun onParticipantAdded(chatRoom: ChatRoom, eventLog: EventLog) { - updateParticipants() - } - - override fun onParticipantRemoved(chatRoom: ChatRoom, eventLog: EventLog) { - updateParticipants() - } - - override fun onParticipantAdminStatusChanged(chatRoom: ChatRoom, eventLog: EventLog) { - val admin = chatRoom.me?.isAdmin ?: false - if (admin != isMeAdmin.value) { - isMeAdmin.value = admin - meAdminChangedEvent.value = Event(admin) - } - updateParticipants() - } - } - - init { - subject.value = chatRoom?.subject - isMeAdmin.value = chatRoom == null || (chatRoom.me?.isAdmin == true && !chatRoom.isReadOnly) - canLeaveGroup.value = chatRoom != null && !chatRoom.isReadOnly - isEncrypted.value = corePreferences.forceEndToEndEncryptedChat || chatRoom?.hasCapability( - ChatRoom.Capabilities.Encrypted.toInt() - ) == true - - if (chatRoom != null) updateParticipants() - - chatRoom?.addListener(listener) - waitForChatRoomCreation.value = false - } - - override fun onCleared() { - participants.value.orEmpty().forEach(GroupInfoParticipantData::destroy) - chatRoom?.removeListener(listener) - - super.onCleared() - } - - fun createChatRoom() { - waitForChatRoomCreation.value = true - val params: ChatRoomParams = coreContext.core.createDefaultChatRoomParams() - params.isEncryptionEnabled = corePreferences.forceEndToEndEncryptedChat || isEncrypted.value == true - params.isGroupEnabled = true - if (params.isEncryptionEnabled) { - params.ephemeralMode = if (corePreferences.useEphemeralPerDeviceMode) { - ChatRoom.EphemeralMode.DeviceManaged - } else { - ChatRoom.EphemeralMode.AdminManaged - } - } - params.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default - Log.i( - "[Chat Room Group Info] Ephemeral mode is ${params.ephemeralMode}, lifetime is ${params.ephemeralLifetime}" - ) - params.subject = subject.value - - val addresses = arrayOfNulls
(participants.value.orEmpty().size) - var index = 0 - for (participant in participants.value.orEmpty()) { - addresses[index] = participant.participant.address - Log.i("[Chat Room Group Info] Participant ${participant.sipUri} will be added to group") - index += 1 - } - - val chatRoom: ChatRoom? = coreContext.core.createChatRoom( - params, - coreContext.core.defaultAccount?.params?.identityAddress, - addresses - ) - chatRoom?.addListener(listener) - if (chatRoom == null) { - Log.e("[Chat Room Group Info] Couldn't create chat room!") - waitForChatRoomCreation.value = false - onMessageToNotifyEvent.value = Event(R.string.chat_room_creation_failed_snack) - } - } - - fun updateRoom() { - if (chatRoom != null) { - // Subject - val newSubject = subject.value.orEmpty() - if (newSubject.isNotEmpty() && newSubject != chatRoom.subject) { - Log.i("[Chat Room Group Info] Subject changed to $newSubject") - chatRoom.subject = newSubject - } - - // Removed participants - val participantsToRemove = arrayListOf() - for (participant in chatRoom.participants) { - val member = participants.value.orEmpty().find { member -> - participant.address.weakEqual(member.participant.address) - } - if (member == null) { - Log.w( - "[Chat Room Group Info] Participant ${participant.address.asStringUriOnly()} will be removed from group" - ) - participantsToRemove.add(participant) - } - } - val toRemove = arrayOfNulls(participantsToRemove.size) - participantsToRemove.toArray(toRemove) - chatRoom.removeParticipants(toRemove) - - // Added participants & new admins - val participantsToAdd = arrayListOf
() - for (member in participants.value.orEmpty()) { - val participant = chatRoom.participants.find { participant -> - participant.address.weakEqual(member.participant.address) - } - if (participant != null) { - // Participant found, check if admin status needs to be updated - if (member.participant.isAdmin != participant.isAdmin) { - if (chatRoom.me?.isAdmin == true) { - Log.i( - "[Chat Room Group Info] Participant ${member.sipUri} will be admin? ${member.isAdmin}" - ) - chatRoom.setParticipantAdminStatus( - participant, - member.participant.isAdmin - ) - } - } - } else { - Log.i( - "[Chat Room Group Info] Participant ${member.sipUri} will be added to group" - ) - participantsToAdd.add(member.participant.address) - } - } - val toAdd = arrayOfNulls
(participantsToAdd.size) - participantsToAdd.toArray(toAdd) - chatRoom.addParticipants(toAdd) - - // Go back to chat room - updatedChatRoomEvent.value = Event(chatRoom) - } - } - - fun leaveGroup() { - if (chatRoom != null) { - Log.w("[Chat Room Group Info] Leaving group") - chatRoom.leave() - updatedChatRoomEvent.value = Event(chatRoom) - } - } - - fun removeParticipant(participant: GroupChatRoomMember) { - val list = arrayListOf() - for (data in participants.value.orEmpty()) { - if (!data.participant.address.weakEqual(participant.address)) { - list.add(data) - } - } - participants.value = list - } - - private fun updateParticipants() { - val list = arrayListOf() - - if (chatRoom != null) { - for (participant in chatRoom.participants) { - list.add( - GroupInfoParticipantData( - GroupChatRoomMember( - participant.address, - participant.isAdmin, - participant.securityLevel, - canBeSetAdmin = true - ) - ) - ) - } - } - - participants.value = list - } -} diff --git a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ImdnViewModel.kt b/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ImdnViewModel.kt deleted file mode 100644 index 2b9c72593..000000000 --- a/app/src/main/java/org/linphone/activities/main/chat/viewmodels/ImdnViewModel.kt +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.chat.viewmodels - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import org.linphone.activities.main.chat.data.ChatMessageData -import org.linphone.activities.main.chat.data.ImdnParticipantData -import org.linphone.core.ChatMessage -import org.linphone.core.ChatMessageListenerStub -import org.linphone.core.ParticipantImdnState - -class ImdnViewModelFactory(private val chatMessage: ChatMessage) : - ViewModelProvider.NewInstanceFactory() { - - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - return ImdnViewModel(chatMessage) as T - } -} - -class ImdnViewModel(private val chatMessage: ChatMessage) : ViewModel() { - val participants = MutableLiveData>() - - val chatMessageViewModel = ChatMessageData(chatMessage) - - private val listener = object : ChatMessageListenerStub() { - override fun onParticipantImdnStateChanged( - message: ChatMessage, - state: ParticipantImdnState - ) { - updateParticipantsLists() - } - } - - init { - chatMessage.addListener(listener) - updateParticipantsLists() - } - - override fun onCleared() { - participants.value.orEmpty().forEach(ImdnParticipantData::destroy) - chatMessage.removeListener(listener) - super.onCleared() - } - - private fun updateParticipantsLists() { - val list = arrayListOf() - - for (participant in chatMessage.getParticipantsByImdnState(ChatMessage.State.Displayed)) { - list.add(ImdnParticipantData(participant)) - } - for (participant in chatMessage.getParticipantsByImdnState( - ChatMessage.State.DeliveredToUser - )) { - list.add(ImdnParticipantData(participant)) - } - for (participant in chatMessage.getParticipantsByImdnState(ChatMessage.State.Delivered)) { - list.add(ImdnParticipantData(participant)) - } - for (participant in chatMessage.getParticipantsByImdnState(ChatMessage.State.NotDelivered)) { - list.add(ImdnParticipantData(participant)) - } - - participants.value = list - } -} diff --git a/app/src/main/java/org/linphone/activities/main/chat/views/MultiLineWrapContentWidthTextView.kt b/app/src/main/java/org/linphone/activities/main/chat/views/MultiLineWrapContentWidthTextView.kt deleted file mode 100644 index fe08e43a0..000000000 --- a/app/src/main/java/org/linphone/activities/main/chat/views/MultiLineWrapContentWidthTextView.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.chat.views - -import android.content.Context -import android.text.Layout -import android.text.method.LinkMovementMethod -import android.util.AttributeSet -import androidx.appcompat.widget.AppCompatTextView -import kotlin.math.ceil -import kotlin.math.max -import kotlin.math.round - -/** - * The purpose of this class is to have a TextView declared with wrap_content as width that won't - * fill it's parent if it is multi line. - */ -class MultiLineWrapContentWidthTextView : AppCompatTextView { - constructor(context: Context) : super(context) - - constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) - - constructor( - context: Context, - attrs: AttributeSet?, - defStyleAttr: Int - ) : super(context, attrs, defStyleAttr) - - override fun setText(text: CharSequence?, type: BufferType?) { - super.setText(text, type) - // Required for PatternClickableSpan - movementMethod = LinkMovementMethod.getInstance() - } - - override fun onMeasure(widthSpec: Int, heightSpec: Int) { - super.onMeasure(widthSpec, heightSpec) - - if (layout != null && layout.lineCount >= 2) { - val maxLineWidth = ceil(getMaxLineWidth(layout)).toInt() - if (maxLineWidth < measuredWidth) { - super.onMeasure( - MeasureSpec.makeMeasureSpec(maxLineWidth, MeasureSpec.getMode(widthSpec)), - heightSpec - ) - } - } - } - - private fun getMaxLineWidth(layout: Layout): Float { - var maxWidth = 0.0f - val lines = layout.lineCount - for (i in 0 until lines) { - maxWidth = max(maxWidth, layout.getLineWidth(i)) - } - return round(maxWidth) - } -} diff --git a/app/src/main/java/org/linphone/activities/main/chat/views/RichEditText.kt b/app/src/main/java/org/linphone/activities/main/chat/views/RichEditText.kt deleted file mode 100644 index 48662b95b..000000000 --- a/app/src/main/java/org/linphone/activities/main/chat/views/RichEditText.kt +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.chat.views - -import android.app.Activity -import android.content.Context -import android.util.AttributeSet -import android.view.KeyEvent -import androidx.appcompat.widget.AppCompatEditText -import androidx.core.view.ViewCompat -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.ViewModelStoreOwner -import org.linphone.activities.main.chat.receivers.RichContentReceiver -import org.linphone.activities.main.viewmodels.SharedMainViewModel -import org.linphone.core.tools.Log -import org.linphone.utils.Event - -/** - * Allows for image input inside an EditText, usefull for keyboards with gif support for example. - */ -class RichEditText : AppCompatEditText { - private var controlPressed = false - - private var sendListener: RichEditTextSendListener? = null - - constructor(context: Context) : super(context) { - initReceiveContentListener() - } - - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { - initReceiveContentListener() - } - - constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super( - context, - attrs, - defStyleAttr - ) { - initReceiveContentListener() - } - - fun setControlEnterListener(listener: RichEditTextSendListener) { - sendListener = listener - } - - private fun initReceiveContentListener() { - ViewCompat.setOnReceiveContentListener( - this, - RichContentReceiver.MIME_TYPES, - RichContentReceiver { uri -> - Log.i("[Rich Edit Text] Received URI: $uri") - val activity = context as Activity - val sharedViewModel = activity.run { - ViewModelProvider(activity as ViewModelStoreOwner)[SharedMainViewModel::class.java] - } - sharedViewModel.richContentUri.value = Event(uri) - } - ) - - setOnKeyListener { _, keyCode, event -> - if (keyCode == KeyEvent.KEYCODE_CTRL_LEFT) { - if (event.action == KeyEvent.ACTION_DOWN) { - controlPressed = true - } else if (event.action == KeyEvent.ACTION_UP) { - controlPressed = false - } - false - } else if (keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_UP && controlPressed) { - sendListener?.onControlEnterPressedAndReleased() - true - } else { - false - } - } - } -} - -interface RichEditTextSendListener { - fun onControlEnterPressedAndReleased() -} diff --git a/app/src/main/java/org/linphone/activities/main/conference/adapters/ScheduledConferencesAdapter.kt b/app/src/main/java/org/linphone/activities/main/conference/adapters/ScheduledConferencesAdapter.kt deleted file mode 100644 index 2d02ed599..000000000 --- a/app/src/main/java/org/linphone/activities/main/conference/adapters/ScheduledConferencesAdapter.kt +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.conference.adapters - -import android.content.Context -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.databinding.DataBindingUtil -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.MutableLiveData -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.RecyclerView -import org.linphone.R -import org.linphone.activities.main.adapters.SelectionListAdapter -import org.linphone.activities.main.conference.data.ScheduledConferenceData -import org.linphone.activities.main.viewmodels.ListTopBarViewModel -import org.linphone.databinding.ConferenceScheduleCellBinding -import org.linphone.databinding.ConferenceScheduleListHeaderBinding -import org.linphone.utils.Event -import org.linphone.utils.HeaderAdapter -import org.linphone.utils.TimestampUtils - -class ScheduledConferencesAdapter( - selectionVM: ListTopBarViewModel, - private val viewLifecycleOwner: LifecycleOwner -) : SelectionListAdapter( - selectionVM, - ConferenceInfoDiffCallback() -), - HeaderAdapter { - val copyAddressToClipboardEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val joinConferenceEvent: MutableLiveData>> by lazy { - MutableLiveData>>() - } - - val editConferenceEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val deleteConferenceInfoEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ScheduledConferencesAdapter.ViewHolder { - val binding: ConferenceScheduleCellBinding = DataBindingUtil.inflate( - LayoutInflater.from(parent.context), - R.layout.conference_schedule_cell, - parent, - false - ) - return ViewHolder(binding) - } - - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - (holder as ScheduledConferencesAdapter.ViewHolder).bind(getItem(position)) - } - - override fun displayHeaderForPosition(position: Int): Boolean { - if (position >= itemCount) return false - val conferenceInfo = getItem(position) - val previousPosition = position - 1 - return if (previousPosition >= 0) { - val previousItem = getItem(previousPosition) - !TimestampUtils.isSameDay( - previousItem.conferenceInfo.dateTime, - conferenceInfo.conferenceInfo.dateTime - ) - } else { - true - } - } - - override fun getHeaderViewForPosition(context: Context, position: Int): View { - val data = getItem(position) - val binding: ConferenceScheduleListHeaderBinding = DataBindingUtil.inflate( - LayoutInflater.from(context), - R.layout.conference_schedule_list_header, - null, - false - ) - binding.title = formatDate(context, data.conferenceInfo.dateTime) - binding.executePendingBindings() - return binding.root - } - - private fun formatDate(context: Context, date: Long): String { - if (TimestampUtils.isToday(date)) { - return context.getString(R.string.today) - } - return TimestampUtils.toString(date, onlyDate = true, shortDate = false, hideYear = false) - } - - inner class ViewHolder( - val binding: ConferenceScheduleCellBinding - ) : RecyclerView.ViewHolder(binding.root) { - fun bind(conferenceData: ScheduledConferenceData) { - with(binding) { - data = conferenceData - - lifecycleOwner = viewLifecycleOwner - - // This is for item selection through ListTopBarFragment - selectionListViewModel = selectionViewModel - selectionViewModel.isEditionEnabled.observe( - viewLifecycleOwner - ) { - position = bindingAdapterPosition - } - - setClickListener { - if (selectionViewModel.isEditionEnabled.value == true) { - selectionViewModel.onToggleSelect(bindingAdapterPosition) - } else { - conferenceData.toggleExpand() - } - } - - setLongClickListener { - if (selectionViewModel.isEditionEnabled.value == false) { - selectionViewModel.isEditionEnabled.value = true - // Selection will be handled by click listener - true - } - false - } - - setCopyAddressClickListener { - val address = conferenceData.getAddressAsString() - if (address.isNotEmpty()) { - copyAddressToClipboardEvent.value = Event(address) - } - } - - setJoinConferenceClickListener { - val address = conferenceData.conferenceInfo.uri - if (address != null) { - joinConferenceEvent.value = Event( - Pair(address.asStringUriOnly(), conferenceData.conferenceInfo.subject) - ) - } - } - - setEditConferenceClickListener { - val address = conferenceData.conferenceInfo.uri - if (address != null) { - editConferenceEvent.value = Event(address.asStringUriOnly()) - } - } - - setDeleteConferenceClickListener { - deleteConferenceInfoEvent.value = Event(conferenceData) - } - - executePendingBindings() - } - } - } -} - -private class ConferenceInfoDiffCallback : DiffUtil.ItemCallback() { - override fun areItemsTheSame( - oldItem: ScheduledConferenceData, - newItem: ScheduledConferenceData - ): Boolean { - return oldItem.conferenceInfo == newItem.conferenceInfo - } - - override fun areContentsTheSame( - oldItem: ScheduledConferenceData, - newItem: ScheduledConferenceData - ): Boolean { - return false - } -} diff --git a/app/src/main/java/org/linphone/activities/main/conference/data/ConferenceSchedulingParticipantData.kt b/app/src/main/java/org/linphone/activities/main/conference/data/ConferenceSchedulingParticipantData.kt deleted file mode 100644 index dd8828aab..000000000 --- a/app/src/main/java/org/linphone/activities/main/conference/data/ConferenceSchedulingParticipantData.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.conference.data - -import androidx.lifecycle.MutableLiveData -import org.linphone.contact.GenericContactData -import org.linphone.core.Address -import org.linphone.utils.LinphoneUtils - -class ConferenceSchedulingParticipantData( - val sipAddress: Address, - val showLimeBadge: Boolean = false, - val showDivider: Boolean = true, - val showBroadcastControls: Boolean = false, - val speaker: Boolean = false, - private val onAddedToSpeakers: ((data: ConferenceSchedulingParticipantData) -> Unit)? = null, - private val onRemovedFromSpeakers: ((data: ConferenceSchedulingParticipantData) -> Unit)? = null -) : - GenericContactData(sipAddress) { - val isSpeaker = MutableLiveData() - - val sipUri: String get() = LinphoneUtils.getDisplayableAddress(sipAddress) - - init { - isSpeaker.value = speaker - } - - fun changeIsSpeaker() { - isSpeaker.value = isSpeaker.value == false - if (isSpeaker.value == true) { - onAddedToSpeakers?.invoke(this) - } else { - onRemovedFromSpeakers?.invoke(this) - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/conference/data/Duration.kt b/app/src/main/java/org/linphone/activities/main/conference/data/Duration.kt deleted file mode 100644 index 61a6ee696..000000000 --- a/app/src/main/java/org/linphone/activities/main/conference/data/Duration.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.conference.data - -class Duration(val value: Int, val display: String) : Comparable { - override fun toString(): String { - return display - } - - override fun compareTo(other: Duration): Int { - return value.compareTo(other.value) - } -} diff --git a/app/src/main/java/org/linphone/activities/main/conference/data/ScheduledConferenceData.kt b/app/src/main/java/org/linphone/activities/main/conference/data/ScheduledConferenceData.kt deleted file mode 100644 index 13d7aaa51..000000000 --- a/app/src/main/java/org/linphone/activities/main/conference/data/ScheduledConferenceData.kt +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.conference.data - -import androidx.lifecycle.MutableLiveData -import java.util.concurrent.TimeUnit -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R -import org.linphone.core.ConferenceInfo -import org.linphone.core.ConferenceInfo.State -import org.linphone.core.Participant -import org.linphone.core.tools.Log -import org.linphone.utils.LinphoneUtils -import org.linphone.utils.TimestampUtils - -class ScheduledConferenceData(val conferenceInfo: ConferenceInfo, private val isFinished: Boolean) { - val expanded = MutableLiveData() - val backgroundResId = MutableLiveData() - - val address = MutableLiveData() - val subject = MutableLiveData() - val description = MutableLiveData() - val time = MutableLiveData() - val date = MutableLiveData() - val duration = MutableLiveData() - val organizer = MutableLiveData() - val canEdit = MutableLiveData() - val participantsShort = MutableLiveData() - val participantsExpanded = MutableLiveData() - val showDuration = MutableLiveData() - val isConferenceCancelled = MutableLiveData() - val isBroadcast = MutableLiveData() - val speakersExpanded = MutableLiveData() - - init { - expanded.value = false - isBroadcast.value = false - - address.value = conferenceInfo.uri?.asStringUriOnly() - subject.value = conferenceInfo.subject - description.value = conferenceInfo.description - - time.value = TimestampUtils.timeToString(conferenceInfo.dateTime) - date.value = TimestampUtils.toString( - conferenceInfo.dateTime, - onlyDate = true, - shortDate = false, - hideYear = false - ) - isConferenceCancelled.value = conferenceInfo.state == State.Cancelled - - val minutes = conferenceInfo.duration - val hours = TimeUnit.MINUTES.toHours(minutes.toLong()) - val remainMinutes = minutes - TimeUnit.HOURS.toMinutes(hours).toInt() - duration.value = TimestampUtils.durationToString(hours.toInt(), remainMinutes) - showDuration.value = minutes > 0 - - val organizerAddress = conferenceInfo.organizer - if (organizerAddress != null) { - val localAccount = coreContext.core.accountList.find { account -> - val address = account.params.identityAddress - address != null && organizerAddress.weakEqual(address) - } - canEdit.value = localAccount != null - - val contact = coreContext.contactsManager.findContactByAddress(organizerAddress) - organizer.value = if (contact != null) { - contact.name - } else { - LinphoneUtils.getDisplayName(conferenceInfo.organizer) - } - } else { - canEdit.value = false - Log.e( - "[Scheduled Conference] No organizer SIP URI found for: ${conferenceInfo.uri?.asStringUriOnly()}" - ) - } - - computeBackgroundResId() - computeParticipantsLists() - } - - fun destroy() {} - - fun delete() { - Log.w( - "[Scheduled Conference] Deleting conference info with URI: ${conferenceInfo.uri?.asStringUriOnly()}" - ) - coreContext.core.deleteConferenceInformation(conferenceInfo) - } - - fun toggleExpand() { - expanded.value = expanded.value == false - computeBackgroundResId() - } - - fun getAddressAsString(): String { - val address = conferenceInfo.uri?.clone() - if (address != null) { - address.displayName = conferenceInfo.subject - return address.asString() - } - return "" - } - - private fun computeBackgroundResId() { - backgroundResId.value = if (conferenceInfo.state == State.Cancelled) { - if (expanded.value == true) { - R.drawable.shape_round_red_background_with_orange_border - } else { - R.drawable.shape_round_red_background - } - } else if (isFinished) { - if (expanded.value == true) { - R.drawable.shape_round_dark_gray_background_with_orange_border - } else { - R.drawable.shape_round_dark_gray_background - } - } else { - if (expanded.value == true) { - R.drawable.shape_round_gray_background_with_orange_border - } else { - R.drawable.shape_round_gray_background - } - } - } - - private fun computeParticipantsLists() { - var participantsListShort = "" - var participantsListExpanded = "" - var speakersListExpanded = "" - - var allSpeaker = true - for (info in conferenceInfo.participantInfos) { - val participant = info.address - Log.i( - "[Scheduled Conference] Conference [${subject.value}] participant [${participant.asStringUriOnly()}] is a [${info.role}]" - ) - - val contact = coreContext.contactsManager.findContactByAddress(participant) - val name = if (contact != null) { - contact.name - } else { - LinphoneUtils.getDisplayName(participant) - } - val address = participant.asStringUriOnly() - participantsListShort += "$name, " - when (info.role) { - Participant.Role.Listener -> { - participantsListExpanded += "$name ($address)\n" - allSpeaker = false - } - else -> { // For meetings created before 5.3 SDK, a speaker might be Unknown - speakersListExpanded += "$name ($address)\n" - } - } - } - participantsListShort = participantsListShort.dropLast(2) - participantsListExpanded = participantsListExpanded.dropLast(1) - speakersListExpanded = speakersListExpanded.dropLast(1) - - participantsShort.value = participantsListShort - // If all participants have Speaker role then it is a meeting, else it is a broadcast - if (!allSpeaker) { - participantsExpanded.value = participantsListExpanded - speakersExpanded.value = speakersListExpanded - isBroadcast.value = true - } else { - participantsExpanded.value = speakersListExpanded - isBroadcast.value = false - } - Log.i( - "[Scheduled Conference] Conference [${subject.value}] is a ${if (allSpeaker) "meeting" else "broadcast"}" - ) - } -} diff --git a/app/src/main/java/org/linphone/activities/main/conference/data/TimeZoneData.kt b/app/src/main/java/org/linphone/activities/main/conference/data/TimeZoneData.kt deleted file mode 100644 index de9007e53..000000000 --- a/app/src/main/java/org/linphone/activities/main/conference/data/TimeZoneData.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.conference.data - -import java.util.* -import java.util.concurrent.TimeUnit -import kotlin.math.abs - -class TimeZoneData(timeZone: TimeZone) : Comparable { - val id: String = timeZone.id - private val hours: Long - private val minutes: Long - private val gmt: String - - init { - hours = TimeUnit.MILLISECONDS.toHours(timeZone.rawOffset.toLong()) - minutes = abs( - TimeUnit.MILLISECONDS.toMinutes(timeZone.rawOffset.toLong()) - - TimeUnit.HOURS.toMinutes(hours) - ) - - gmt = if (hours > 0) { - String.format("%s - GMT+%d:%02d", timeZone.id, hours, minutes) - } else { - String.format("%s - GMT%d:%02d", timeZone.id, hours, minutes) - } - } - - override fun toString(): String { - return gmt - } - - override fun compareTo(other: TimeZoneData): Int { - if (hours == other.hours) { - if (minutes == other.minutes) { - return id.compareTo(other.id) - } - return minutes.compareTo(other.minutes) - } - return hours.compareTo(other.hours) - } -} diff --git a/app/src/main/java/org/linphone/activities/main/conference/fragments/ConferenceSchedulingFragment.kt b/app/src/main/java/org/linphone/activities/main/conference/fragments/ConferenceSchedulingFragment.kt deleted file mode 100644 index c16a42c4f..000000000 --- a/app/src/main/java/org/linphone/activities/main/conference/fragments/ConferenceSchedulingFragment.kt +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.conference.fragments - -import android.os.Bundle -import android.text.format.DateFormat.is24HourFormat -import android.view.View -import androidx.navigation.navGraphViewModels -import com.google.android.material.datepicker.CalendarConstraints -import com.google.android.material.datepicker.DateValidatorPointForward -import com.google.android.material.datepicker.MaterialDatePicker -import com.google.android.material.timepicker.MaterialTimePicker -import com.google.android.material.timepicker.TimeFormat -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R -import org.linphone.activities.GenericFragment -import org.linphone.activities.main.conference.viewmodels.ConferenceSchedulingViewModel -import org.linphone.activities.navigateToParticipantsList -import org.linphone.core.Factory -import org.linphone.core.tools.Log -import org.linphone.databinding.ConferenceSchedulingFragmentBinding - -class ConferenceSchedulingFragment : GenericFragment() { - private val viewModel: ConferenceSchedulingViewModel by navGraphViewModels( - R.id.conference_scheduling_nav_graph - ) - - override fun getLayoutId(): Int = R.layout.conference_scheduling_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - binding.viewModel = viewModel - - sharedViewModel.participantsListForNextScheduledMeeting.observe( - viewLifecycleOwner - ) { - it.consume { participants -> - Log.i( - "[Conference Scheduling] Found participants (${participants.size}) to pre-populate for meeting schedule" - ) - viewModel.prePopulateParticipantsList(participants, true) - } - } - - sharedViewModel.addressOfConferenceInfoToEdit.observe( - viewLifecycleOwner - ) { - it.consume { address -> - val conferenceAddress = Factory.instance().createAddress(address) - if (conferenceAddress != null) { - Log.i( - "[Conference Scheduling] Trying to edit conference info using address: $address" - ) - val conferenceInfo = coreContext.core.findConferenceInformationFromUri( - conferenceAddress - ) - if (conferenceInfo != null) { - viewModel.populateFromConferenceInfo(conferenceInfo) - } else { - Log.e( - "[Conference Scheduling] Failed to find ConferenceInfo matching address: $address" - ) - } - } else { - Log.e("[Conference Scheduling] Failed to parse conference address: $address") - } - } - } - - binding.setNextClickListener { - navigateToParticipantsList() - } - - binding.setDatePickerClickListener { - val constraintsBuilder = - CalendarConstraints.Builder() - .setValidator(DateValidatorPointForward.now()) - val picker = - MaterialDatePicker.Builder.datePicker() - .setCalendarConstraints(constraintsBuilder.build()) - .setTitleText(R.string.conference_schedule_date) - .setSelection(viewModel.dateTimestamp) - .build() - picker.addOnPositiveButtonClickListener { - val selection = picker.selection - if (selection != null) { - viewModel.setDate(selection) - } - } - picker.show(requireFragmentManager(), "Date picker") - } - - binding.setTimePickerClickListener { - val isSystem24Hour = is24HourFormat(requireContext()) - val clockFormat = if (isSystem24Hour) TimeFormat.CLOCK_24H else TimeFormat.CLOCK_12H - val picker = - MaterialTimePicker.Builder() - .setTimeFormat(clockFormat) - .setTitleText(R.string.conference_schedule_time) - .setHour(viewModel.hour) - .setMinute(viewModel.minutes) - .build() - picker.addOnPositiveButtonClickListener { - viewModel.setTime(picker.hour, picker.minute) - } - picker.show(requireFragmentManager(), "Time picker") - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/conference/fragments/ConferenceSchedulingParticipantsListFragment.kt b/app/src/main/java/org/linphone/activities/main/conference/fragments/ConferenceSchedulingParticipantsListFragment.kt deleted file mode 100644 index b59247d44..000000000 --- a/app/src/main/java/org/linphone/activities/main/conference/fragments/ConferenceSchedulingParticipantsListFragment.kt +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.conference.fragments - -import android.content.pm.PackageManager -import android.os.Bundle -import android.view.View -import androidx.navigation.navGraphViewModels -import androidx.recyclerview.widget.LinearLayoutManager -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.GenericFragment -import org.linphone.activities.main.conference.viewmodels.ConferenceSchedulingViewModel -import org.linphone.activities.navigateToSummary -import org.linphone.contact.ContactsSelectionAdapter -import org.linphone.core.tools.Log -import org.linphone.databinding.ConferenceSchedulingParticipantsListFragmentBinding -import org.linphone.utils.AppUtils -import org.linphone.utils.PermissionHelper - -class ConferenceSchedulingParticipantsListFragment : GenericFragment() { - private val viewModel: ConferenceSchedulingViewModel by navGraphViewModels( - R.id.conference_scheduling_nav_graph - ) - private lateinit var adapter: ContactsSelectionAdapter - - override fun getLayoutId(): Int = R.layout.conference_scheduling_participants_list_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - binding.viewModel = viewModel - - adapter = ContactsSelectionAdapter(viewLifecycleOwner) - adapter.setLimeCapabilityRequired(viewModel.isEncrypted.value == true) - binding.contactsList.adapter = adapter - - val layoutManager = LinearLayoutManager(requireContext()) - binding.contactsList.layoutManager = layoutManager - - // Divider between items - binding.contactsList.addItemDecoration( - AppUtils.getDividerDecoration(requireContext(), layoutManager) - ) - - binding.setNextClickListener { - navigateToSummary() - } - - viewModel.contactsList.observe( - viewLifecycleOwner - ) { - adapter.submitList(it) - } - viewModel.sipContactsSelected.observe( - viewLifecycleOwner - ) { - viewModel.applyFilter() - } - - viewModel.selectedAddresses.observe( - viewLifecycleOwner - ) { - adapter.updateSelectedAddresses(it) - } - viewModel.filter.observe( - viewLifecycleOwner - ) { - viewModel.applyFilter() - } - - adapter.selectedContact.observe( - viewLifecycleOwner - ) { - it.consume { searchResult -> - viewModel.toggleSelectionForSearchResult(searchResult) - } - } - - if (corePreferences.enableNativeAddressBookIntegration) { - if (!PermissionHelper.get().hasReadContactsPermission()) { - Log.i("[Conference Creation] Asking for READ_CONTACTS permission") - requestPermissions(arrayOf(android.Manifest.permission.READ_CONTACTS), 0) - } - } - } - - @Deprecated("Deprecated in Java") - override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray - ) { - if (requestCode == 0) { - val granted = grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED - if (granted) { - Log.i("[Conference Creation] READ_CONTACTS permission granted") - coreContext.fetchContacts() - } else { - Log.w("[Conference Creation] READ_CONTACTS permission denied") - } - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/conference/fragments/ConferenceSchedulingSummaryFragment.kt b/app/src/main/java/org/linphone/activities/main/conference/fragments/ConferenceSchedulingSummaryFragment.kt deleted file mode 100644 index 2358616ed..000000000 --- a/app/src/main/java/org/linphone/activities/main/conference/fragments/ConferenceSchedulingSummaryFragment.kt +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.conference.fragments - -import android.os.Bundle -import android.view.View -import androidx.navigation.navGraphViewModels -import org.linphone.R -import org.linphone.activities.GenericFragment -import org.linphone.activities.main.MainActivity -import org.linphone.activities.main.conference.viewmodels.ConferenceSchedulingViewModel -import org.linphone.activities.navigateToDialer -import org.linphone.activities.navigateToScheduledConferences -import org.linphone.databinding.ConferenceSchedulingSummaryFragmentBinding - -class ConferenceSchedulingSummaryFragment : GenericFragment() { - private val viewModel: ConferenceSchedulingViewModel by navGraphViewModels( - R.id.conference_scheduling_nav_graph - ) - - override fun getLayoutId(): Int = R.layout.conference_scheduling_summary_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - binding.viewModel = viewModel - - viewModel.conferenceCreationCompletedEvent.observe( - viewLifecycleOwner - ) { - it.consume { - if (viewModel.scheduleForLater.value == true) { - (requireActivity() as MainActivity).showSnackBar( - R.string.conference_schedule_info_created - ) - navigateToScheduledConferences() - } else { - navigateToDialer() - } - } - } - - viewModel.onMessageToNotifyEvent.observe( - viewLifecycleOwner - ) { - it.consume { messageId -> - (activity as MainActivity).showSnackBar(messageId) - } - } - - viewModel.computeParticipantsData() - } -} diff --git a/app/src/main/java/org/linphone/activities/main/conference/fragments/ConferenceWaitingRoomFragment.kt b/app/src/main/java/org/linphone/activities/main/conference/fragments/ConferenceWaitingRoomFragment.kt deleted file mode 100644 index 1e7c25d7a..000000000 --- a/app/src/main/java/org/linphone/activities/main/conference/fragments/ConferenceWaitingRoomFragment.kt +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.conference.fragments - -import android.Manifest -import android.content.pm.PackageManager -import android.os.Bundle -import android.view.View -import androidx.lifecycle.ViewModelProvider -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R -import org.linphone.activities.GenericFragment -import org.linphone.activities.main.MainActivity -import org.linphone.activities.main.conference.viewmodels.ConferenceWaitingRoomViewModel -import org.linphone.core.tools.Log -import org.linphone.databinding.ConferenceWaitingRoomFragmentBinding -import org.linphone.utils.PermissionHelper - -class ConferenceWaitingRoomFragment : GenericFragment() { - private lateinit var viewModel: ConferenceWaitingRoomViewModel - - override fun getLayoutId(): Int = R.layout.conference_waiting_room_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - viewModel = ViewModelProvider( - this - )[ConferenceWaitingRoomViewModel::class.java] - binding.viewModel = viewModel - - val conferenceSubject = arguments?.getString("Subject") - viewModel.subject.value = conferenceSubject - - val address = arguments?.getString("Address") - viewModel.findConferenceInfoByAddress(address) - - viewModel.cancelConferenceJoiningEvent.observe( - viewLifecycleOwner - ) { - it.consume { - if (viewModel.joinInProgress.value == true) { - val conferenceUri = arguments?.getString("Address") - val callToCancel = coreContext.core.calls.find { call -> - call.remoteAddress.asStringUriOnly() == conferenceUri - } - if (callToCancel != null) { - Log.i( - "[Conference Waiting Room] Call to conference server with URI [$conferenceUri] was started, terminate it" - ) - callToCancel.terminate() - } else { - Log.w( - "[Conference Waiting Room] Call to conference server with URI [$conferenceUri] wasn't found!" - ) - } - } - goBack() - } - } - - viewModel.joinConferenceEvent.observe( - viewLifecycleOwner - ) { - it.consume { callParams -> - val conferenceUri = arguments?.getString("Address") - if (conferenceUri != null) { - val conferenceAddress = coreContext.core.interpretUrl(conferenceUri, false) - if (conferenceAddress != null) { - Log.i( - "[Conference Waiting Room] Calling conference SIP URI: ${conferenceAddress.asStringUriOnly()}" - ) - coreContext.startCall(conferenceAddress, callParams) - } else { - Log.e( - "[Conference Waiting Room] Failed to parse conference SIP URI: $conferenceUri" - ) - } - } else { - Log.e( - "[Conference Waiting Room] Failed to find conference SIP URI in arguments" - ) - } - } - } - - viewModel.askPermissionEvent.observe( - viewLifecycleOwner - ) { - it.consume { permission -> - Log.i("[Conference Waiting Room] Asking for $permission permission") - requestPermissions(arrayOf(permission), 0) - } - } - - viewModel.leaveWaitingRoomEvent.observe( - viewLifecycleOwner - ) { - it.consume { - goBack() - } - } - - viewModel.onMessageToNotifyEvent.observe( - viewLifecycleOwner - ) { - it.consume { message -> - (activity as MainActivity).showSnackBar(message) - } - } - - viewModel.networkNotReachableEvent.observe( - viewLifecycleOwner - ) { - it.consume { - (activity as MainActivity).showSnackBar(R.string.call_error_network_unreachable) - } - } - - checkPermissions() - } - - override fun onResume() { - super.onResume() - - coreContext.core.nativePreviewWindowId = binding.localPreviewVideoSurface - val enablePreview = viewModel.isVideoEnabled.value == true - if (enablePreview) { - Log.i("[Conference Waiting Room] Fragment is being resumed, enabling video preview") - } - coreContext.core.isVideoPreviewEnabled = enablePreview - } - - override fun onPause() { - Log.i("[Conference Waiting Room] Fragment is being paused, disabling video preview") - coreContext.core.isVideoPreviewEnabled = false - coreContext.core.nativePreviewWindowId = null - - super.onPause() - } - - private fun checkPermissions() { - val permissionsRequiredList = arrayListOf() - - if (!PermissionHelper.get().hasRecordAudioPermission()) { - Log.i("[Conference Waiting Room] Asking for RECORD_AUDIO permission") - permissionsRequiredList.add(Manifest.permission.RECORD_AUDIO) - } - - if (!PermissionHelper.get().hasCameraPermission()) { - Log.i("[Conference Waiting Room] Asking for CAMERA permission") - permissionsRequiredList.add(Manifest.permission.CAMERA) - } - - if (permissionsRequiredList.isNotEmpty()) { - val permissionsRequired = arrayOfNulls(permissionsRequiredList.size) - permissionsRequiredList.toArray(permissionsRequired) - requestPermissions(permissionsRequired, 0) - } - } - - @Deprecated("Deprecated in Java") - override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray - ) { - if (requestCode == 0) { - for (i in permissions.indices) { - when (permissions[i]) { - Manifest.permission.RECORD_AUDIO -> if (grantResults[i] == PackageManager.PERMISSION_GRANTED) { - Log.i("[Conference Waiting Room] RECORD_AUDIO permission has been granted") - viewModel.enableMic() - } - Manifest.permission.CAMERA -> if (grantResults[i] == PackageManager.PERMISSION_GRANTED) { - Log.i("[Conference Waiting Room] CAMERA permission has been granted") - coreContext.core.reloadVideoDevices() - viewModel.enableVideo() - } - } - } - } - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - } -} diff --git a/app/src/main/java/org/linphone/activities/main/conference/fragments/ScheduledConferencesFragment.kt b/app/src/main/java/org/linphone/activities/main/conference/fragments/ScheduledConferencesFragment.kt deleted file mode 100644 index a570af0c4..000000000 --- a/app/src/main/java/org/linphone/activities/main/conference/fragments/ScheduledConferencesFragment.kt +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.conference.fragments - -import android.app.Dialog -import android.content.ClipData -import android.content.ClipboardManager -import android.content.Context -import android.os.Bundle -import android.view.View -import androidx.core.content.ContextCompat -import androidx.lifecycle.ViewModelProvider -import androidx.recyclerview.widget.ItemTouchHelper -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import org.linphone.R -import org.linphone.activities.main.MainActivity -import org.linphone.activities.main.conference.adapters.ScheduledConferencesAdapter -import org.linphone.activities.main.conference.data.ScheduledConferenceData -import org.linphone.activities.main.conference.viewmodels.ScheduledConferencesViewModel -import org.linphone.activities.main.fragments.MasterFragment -import org.linphone.activities.main.viewmodels.DialogViewModel -import org.linphone.activities.navigateToConferenceScheduling -import org.linphone.activities.navigateToConferenceWaitingRoom -import org.linphone.core.tools.Log -import org.linphone.databinding.ConferencesScheduledFragmentBinding -import org.linphone.utils.* - -class ScheduledConferencesFragment : MasterFragment() { - override val dialogConfirmationMessageBeforeRemoval = R.plurals.conference_scheduled_delete_dialog - private lateinit var listViewModel: ScheduledConferencesViewModel - - override fun getLayoutId(): Int = R.layout.conferences_scheduled_fragment - - private var deleteConferenceInfoDialog: Dialog? = null - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - listViewModel = ViewModelProvider( - this - )[ScheduledConferencesViewModel::class.java] - binding.viewModel = listViewModel - - _adapter = ScheduledConferencesAdapter(listSelectionViewModel, viewLifecycleOwner) - binding.conferenceInfoList.adapter = adapter - - val layoutManager = LinearLayoutManager(requireContext()) - binding.conferenceInfoList.layoutManager = layoutManager - - // Swipe action - val swipeConfiguration = RecyclerViewSwipeConfiguration() - val white = ContextCompat.getColor(requireContext(), R.color.white_color) - - swipeConfiguration.rightToLeftAction = RecyclerViewSwipeConfiguration.Action( - requireContext().getString(R.string.dialog_delete), - white, - ContextCompat.getColor(requireContext(), R.color.red_color) - ) - val swipeListener = object : RecyclerViewSwipeListener { - override fun onLeftToRightSwipe(viewHolder: RecyclerView.ViewHolder) {} - - override fun onRightToLeftSwipe(viewHolder: RecyclerView.ViewHolder) { - val index = viewHolder.bindingAdapterPosition - if (index < 0 || index >= adapter.currentList.size) { - Log.e( - "[Scheduled Conferences] Index is out of bound, can't delete conference info" - ) - } else { - val deletedConfInfo = adapter.currentList[index] - showConfInfoDeleteConfirmationDialog(deletedConfInfo, index) - } - } - } - RecyclerViewSwipeUtils(ItemTouchHelper.LEFT, swipeConfiguration, swipeListener) - .attachToRecyclerView(binding.conferenceInfoList) - - // Displays date header - val headerItemDecoration = RecyclerViewHeaderDecoration(requireContext(), adapter) - binding.conferenceInfoList.addItemDecoration(headerItemDecoration) - - listViewModel.conferences.observe( - viewLifecycleOwner - ) { - adapter.submitList(it) - } - - adapter.copyAddressToClipboardEvent.observe( - viewLifecycleOwner - ) { - it.consume { address -> - val clipboard = - requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - val clip = ClipData.newPlainText("Conference address", address) - clipboard.setPrimaryClip(clip) - - (activity as MainActivity).showSnackBar( - R.string.conference_schedule_address_copied_to_clipboard - ) - } - } - - adapter.joinConferenceEvent.observe( - viewLifecycleOwner - ) { - it.consume { pair -> - navigateToConferenceWaitingRoom(pair.first, pair.second) - } - } - - adapter.editConferenceEvent.observe( - viewLifecycleOwner - ) { - it.consume { address -> - sharedViewModel.addressOfConferenceInfoToEdit.value = Event(address) - navigateToConferenceScheduling() - } - } - - adapter.deleteConferenceInfoEvent.observe( - viewLifecycleOwner - ) { - it.consume { data -> - showConfInfoDeleteConfirmationDialog(data, -1) - } - } - - binding.setNewConferenceClickListener { - navigateToConferenceScheduling() - } - } - - override fun deleteItems(indexesOfItemToDelete: ArrayList) { - val list = ArrayList() - for (index in indexesOfItemToDelete) { - val conferenceData = adapter.currentList[index] - list.add(conferenceData) - } - listViewModel.deleteConferencesInfo(list) - } - - private fun showConfInfoDeleteConfirmationDialog(data: ScheduledConferenceData, index: Int) { - val dialogViewModel = - DialogViewModel(AppUtils.getString(R.string.conference_scheduled_delete_one_dialog)) - deleteConferenceInfoDialog = - DialogUtils.getVoipDialog(requireContext(), dialogViewModel) - - dialogViewModel.showCancelButton( - { - if (index != -1) { - adapter.notifyItemChanged(index) - } - deleteConferenceInfoDialog?.dismiss() - }, - getString(R.string.dialog_cancel) - ) - - dialogViewModel.showDeleteButton( - { - listViewModel.deleteConferenceInfo(data) - deleteConferenceInfoDialog?.dismiss() - (requireActivity() as MainActivity).showSnackBar(R.string.conference_info_removed) - }, - getString(R.string.dialog_delete) - ) - - deleteConferenceInfoDialog?.show() - } -} diff --git a/app/src/main/java/org/linphone/activities/main/conference/viewmodels/ConferenceSchedulingViewModel.kt b/app/src/main/java/org/linphone/activities/main/conference/viewmodels/ConferenceSchedulingViewModel.kt deleted file mode 100644 index edbee3312..000000000 --- a/app/src/main/java/org/linphone/activities/main/conference/viewmodels/ConferenceSchedulingViewModel.kt +++ /dev/null @@ -1,416 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.conference.viewmodels - -import androidx.lifecycle.MediatorLiveData -import androidx.lifecycle.MutableLiveData -import java.util.* -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.main.conference.data.ConferenceSchedulingParticipantData -import org.linphone.activities.main.conference.data.Duration -import org.linphone.activities.main.conference.data.TimeZoneData -import org.linphone.contact.ContactsSelectionViewModel -import org.linphone.core.* -import org.linphone.core.tools.Log -import org.linphone.utils.AppUtils -import org.linphone.utils.Event -import org.linphone.utils.LinphoneUtils -import org.linphone.utils.TimestampUtils - -class ConferenceSchedulingViewModel : ContactsSelectionViewModel() { - val subject = MutableLiveData() - val description = MutableLiveData() - - val scheduleForLater = MutableLiveData() - val isUpdate = MutableLiveData() - - val isBroadcastAllowed = MutableLiveData() - val mode = MutableLiveData() - val modesList: List - - val formattedDate = MutableLiveData() - val formattedTime = MutableLiveData() - - val isEncrypted = MutableLiveData() - - val sendInviteViaChat = MutableLiveData() - val sendInviteViaEmail = MutableLiveData() - - val participantsData = MutableLiveData>() - val speakersData = MutableLiveData>() - - val address = MutableLiveData
() - - val conferenceCreationInProgress = MutableLiveData() - - val conferenceCreationCompletedEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val continueEnabled: MediatorLiveData = MediatorLiveData() - - var timeZone = MutableLiveData() - val timeZones: List = computeTimeZonesList() - - var duration = MutableLiveData() - val durationList: List = computeDurationList() - - var dateTimestamp: Long = System.currentTimeMillis() - var hour: Int = 0 - var minutes: Int = 0 - - private var confInfo: ConferenceInfo? = null - private val conferenceScheduler = coreContext.core.createConferenceScheduler() - - private val selectedSpeakersAddresses = MutableLiveData>() - - private val listener = object : ConferenceSchedulerListenerStub() { - override fun onStateChanged( - conferenceScheduler: ConferenceScheduler, - state: ConferenceScheduler.State - ) { - Log.i("[Conference Creation] Conference scheduler state is $state") - if (state == ConferenceScheduler.State.Ready) { - val conferenceAddress = conferenceScheduler.info?.uri - Log.i( - "[Conference Creation] Conference info created, address will be ${conferenceAddress?.asStringUriOnly()}" - ) - conferenceAddress ?: return - - address.value = conferenceAddress!! - - if (scheduleForLater.value == true) { - if (sendInviteViaChat.value == true) { - // Send conference info even when conf is not scheduled for later - // as the conference server doesn't invite participants automatically - Log.i( - "[Conference Creation] Scheduled conference is ready, sending invitations by chat" - ) - val chatRoomParams = LinphoneUtils.getConferenceInvitationsChatRoomParams() - conferenceScheduler.sendInvitations(chatRoomParams) - } else { - Log.i( - "[Conference Creation] Scheduled conference is ready, we were asked not to send invitations by chat so leaving fragment" - ) - conferenceCreationInProgress.value = false - conferenceCreationCompletedEvent.value = Event(true) - } - } else { - Log.i("[Conference Creation] Group call is ready, leaving fragment") - conferenceCreationInProgress.value = false - conferenceCreationCompletedEvent.value = Event(true) - } - } else if (state == ConferenceScheduler.State.Error) { - Log.e("[Conference Creation] Failed to create conference!") - conferenceCreationInProgress.value = false - onMessageToNotifyEvent.value = Event(R.string.conference_creation_failed) - } - } - - override fun onInvitationsSent( - conferenceScheduler: ConferenceScheduler, - failedInvitations: Array? - ) { - conferenceCreationInProgress.value = false - - if (failedInvitations?.isNotEmpty() == true) { - for (address in failedInvitations) { - Log.e( - "[Conference Creation] Conference information wasn't sent to participant ${address.asStringUriOnly()}" - ) - } - onMessageToNotifyEvent.value = Event( - R.string.conference_schedule_info_not_sent_to_participant - ) - } else { - Log.i( - "[Conference Creation] Conference information successfully sent to all participants" - ) - } - - val conferenceAddress = conferenceScheduler.info?.uri - if (conferenceAddress == null) { - Log.e("[Conference Creation] Conference address is null!") - } else { - conferenceCreationCompletedEvent.value = Event(true) - } - } - } - - init { - sipContactsSelected.value = true - - subject.value = "" - scheduleForLater.value = false - isUpdate.value = false - - isBroadcastAllowed.value = !corePreferences.disableBroadcastConference - modesList = arrayListOf( - AppUtils.getString(R.string.conference_schedule_mode_meeting), - AppUtils.getString(R.string.conference_schedule_mode_broadcast) - ) - mode.value = modesList.first() // Meeting by default - - isEncrypted.value = false - sendInviteViaChat.value = true - sendInviteViaEmail.value = false - - timeZone.value = timeZones.find { - it.id == TimeZone.getDefault().id - } - duration.value = durationList.find { - it.value == 60 - } - - continueEnabled.value = false - continueEnabled.addSource(subject) { - continueEnabled.value = allMandatoryFieldsFilled() - } - continueEnabled.addSource(scheduleForLater) { - continueEnabled.value = allMandatoryFieldsFilled() - } - continueEnabled.addSource(formattedDate) { - continueEnabled.value = allMandatoryFieldsFilled() - } - continueEnabled.addSource(formattedTime) { - continueEnabled.value = allMandatoryFieldsFilled() - } - - conferenceScheduler.addListener(listener) - } - - override fun onCleared() { - conferenceScheduler.removeListener(listener) - participantsData.value.orEmpty().forEach(ConferenceSchedulingParticipantData::destroy) - speakersData.value.orEmpty().forEach(ConferenceSchedulingParticipantData::destroy) - - super.onCleared() - } - - fun prePopulateParticipantsList(participants: ArrayList
, isSchedule: Boolean) { - selectedAddresses.value = participants - scheduleForLater.value = isSchedule - } - - fun populateFromConferenceInfo(conferenceInfo: ConferenceInfo) { - // Pre-set data from existing conference info, used when editing an already scheduled broadcast or meeting - confInfo = conferenceInfo - - address.value = conferenceInfo.uri - subject.value = conferenceInfo.subject - description.value = conferenceInfo.description - isUpdate.value = true - - val dateTime = conferenceInfo.dateTime - val calendar = Calendar.getInstance() - calendar.timeInMillis = dateTime * 1000 - setDate(calendar.timeInMillis) - setTime(calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE)) - - val conferenceDuration = conferenceInfo.duration - duration.value = durationList.find { it.value == conferenceDuration } - scheduleForLater.value = conferenceDuration > 0 - - val participantsList = arrayListOf
() - val speakersList = arrayListOf
() - for (info in conferenceInfo.participantInfos) { - val participant = info.address - participantsList.add(participant) - if (info.role == Participant.Role.Speaker) { - speakersList.add(participant) - } - } - if (participantsList.count() == speakersList.count()) { - // All participants are speaker, this is a meeting, clear speakers - Log.i("[Conference Creation] Conference info is a meeting") - speakersList.clear() - mode.value = modesList.first() - } else { - Log.i("[Conference Creation] Conference info is a broadcast") - mode.value = modesList.last() - } - selectedAddresses.value = participantsList - selectedSpeakersAddresses.value = speakersList - - computeParticipantsData() - } - - fun toggleSchedule() { - scheduleForLater.value = scheduleForLater.value == false - } - - fun setDate(d: Long) { - dateTimestamp = d - formattedDate.value = TimestampUtils.dateToString(dateTimestamp, false) - } - - fun setTime(h: Int, m: Int) { - hour = h - minutes = m - formattedTime.value = TimestampUtils.timeToString(hour, minutes) - } - - fun updateEncryption(enable: Boolean) { - isEncrypted.value = enable - } - - fun computeParticipantsData() { - participantsData.value.orEmpty().forEach(ConferenceSchedulingParticipantData::destroy) - speakersData.value.orEmpty().forEach(ConferenceSchedulingParticipantData::destroy) - - val participantsList = arrayListOf() - val speakersList = arrayListOf() - - for (address in selectedAddresses.value.orEmpty()) { - val isSpeaker = address in selectedSpeakersAddresses.value.orEmpty() - val data = ConferenceSchedulingParticipantData( - address, - showLimeBadge = isEncrypted.value == true, - showBroadcastControls = isModeBroadcastCurrentlySelected(), - speaker = isSpeaker, - onAddedToSpeakers = { data -> - Log.i( - "[Conference Creation] Participant [${address.asStringUriOnly()}] added to speakers" - ) - val participants = arrayListOf() - participants.addAll(participantsData.value.orEmpty()) - participants.remove(data) - participantsData.value = participants - - val speakers = arrayListOf() - speakers.addAll(speakersData.value.orEmpty()) - speakers.add(data) - speakersData.value = speakers - }, - onRemovedFromSpeakers = { data -> - Log.i( - "[Conference Creation] Participant [${address.asStringUriOnly()}] removed from speakers" - ) - val speakers = arrayListOf() - speakers.addAll(speakersData.value.orEmpty()) - speakers.remove(data) - speakersData.value = speakers - - val participants = arrayListOf() - participants.addAll(participantsData.value.orEmpty()) - participants.add(data) - participantsData.value = participants - } - ) - - if (isSpeaker) { - speakersList.add(data) - } else { - participantsList.add(data) - } - } - - participantsData.value = participantsList - speakersData.value = speakersList - } - - fun createConference() { - val participantsCount = selectedAddresses.value.orEmpty().size - if (participantsCount == 0) { - Log.e("[Conference Creation] Couldn't create conference without any participant!") - return - } - - conferenceCreationInProgress.value = true - val core = coreContext.core - val localAccount = core.defaultAccount - val localAddress = localAccount?.params?.identityAddress - - val conferenceInfo = if (isUpdate.value == true) { - confInfo?.clone() ?: Factory.instance().createConferenceInfo() - } else { - Factory.instance().createConferenceInfo() - } - conferenceInfo.organizer = localAddress - conferenceInfo.subject = subject.value - conferenceInfo.description = description.value - - val participants = arrayOfNulls(selectedAddresses.value.orEmpty().size) - var index = 0 - val isBroadcast = isModeBroadcastCurrentlySelected() - for (participant in participantsData.value.orEmpty()) { - val info = Factory.instance().createParticipantInfo(participant.sipAddress) - // For meetings, all participants must have Speaker role - info?.role = if (isBroadcast) Participant.Role.Listener else Participant.Role.Speaker - participants[index] = info - index += 1 - } - for (speaker in speakersData.value.orEmpty()) { - val info = Factory.instance().createParticipantInfo(speaker.sipAddress) - info?.role = Participant.Role.Speaker - participants[index] = info - index += 1 - } - conferenceInfo.setParticipantInfos(participants) - - if (scheduleForLater.value == true) { - val startTime = getConferenceStartTimestamp() - conferenceInfo.dateTime = startTime - val duration = duration.value?.value ?: 0 - conferenceInfo.duration = duration - } - - confInfo = conferenceInfo - conferenceScheduler.account = localAccount - // Will trigger the conference creation/update automatically - conferenceScheduler.info = conferenceInfo - } - - fun isModeBroadcastCurrentlySelected(): Boolean { - return mode.value == AppUtils.getString(R.string.conference_schedule_mode_broadcast) - } - - private fun computeTimeZonesList(): List { - return TimeZone.getAvailableIDs().map { id -> TimeZoneData(TimeZone.getTimeZone(id)) }.toList().sorted() - } - - private fun computeDurationList(): List { - // Duration value is in minutes as according to conferenceInfo.setDuration() doc - return arrayListOf(Duration(30, "30min"), Duration(60, "1h"), Duration(120, "2h")) - } - - private fun allMandatoryFieldsFilled(): Boolean { - return !subject.value.isNullOrEmpty() && - ( - scheduleForLater.value == false || - ( - !formattedDate.value.isNullOrEmpty() && - !formattedTime.value.isNullOrEmpty() - ) - ) - } - - private fun getConferenceStartTimestamp(): Long { - val calendar = Calendar.getInstance( - TimeZone.getTimeZone(timeZone.value?.id ?: TimeZone.getDefault().id) - ) - calendar.timeInMillis = dateTimestamp - calendar.set(Calendar.HOUR_OF_DAY, hour) - calendar.set(Calendar.MINUTE, minutes) - return calendar.timeInMillis / 1000 // Linphone expects a time_t (so in seconds) - } -} diff --git a/app/src/main/java/org/linphone/activities/main/conference/viewmodels/ConferenceWaitingRoomViewModel.kt b/app/src/main/java/org/linphone/activities/main/conference/viewmodels/ConferenceWaitingRoomViewModel.kt deleted file mode 100644 index ba194278d..000000000 --- a/app/src/main/java/org/linphone/activities/main/conference/viewmodels/ConferenceWaitingRoomViewModel.kt +++ /dev/null @@ -1,499 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.conference.viewmodels - -import android.Manifest -import android.animation.ValueAnimator -import androidx.lifecycle.MutableLiveData -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.main.viewmodels.MessageNotifierViewModel -import org.linphone.activities.voip.ConferenceDisplayMode -import org.linphone.core.* -import org.linphone.core.tools.Log -import org.linphone.utils.* -import org.linphone.utils.Event - -class ConferenceWaitingRoomViewModel : MessageNotifierViewModel() { - val subject = MutableLiveData() - - val isMicrophoneMuted = MutableLiveData() - - val audioRoutesEnabled = MutableLiveData() - - val audioRoutesSelected = MutableLiveData() - - val isSpeakerSelected = MutableLiveData() - - val isBluetoothHeadsetSelected = MutableLiveData() - - val layoutMenuSelected = MutableLiveData() - - val selectedLayout = MutableLiveData() - - val isVideoAvailable = MutableLiveData() - - val isVideoEnabled = MutableLiveData() - - val isSwitchCameraAvailable = MutableLiveData() - - val isLowBandwidth = MutableLiveData() - - val joinInProgress = MutableLiveData() - - val networkReachable = MutableLiveData() - - val isConferenceBroadcastWithListenerRole = MutableLiveData() - - val askPermissionEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val cancelConferenceJoiningEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val joinConferenceEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val leaveWaitingRoomEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val networkNotReachableEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val audioRoutesMenuTranslateY = MutableLiveData() - private val audioRoutesMenuAnimator: ValueAnimator by lazy { - ValueAnimator.ofFloat(AppUtils.getDimension(R.dimen.voip_audio_routes_menu_translate_y), 0f).apply { - addUpdateListener { - val value = it.animatedValue as Float - audioRoutesMenuTranslateY.value = value - } - duration = if (corePreferences.enableAnimations) 500 else 0 - } - } - - val conferenceLayoutMenuTranslateY = MutableLiveData() - private val conferenceLayoutMenuAnimator: ValueAnimator by lazy { - ValueAnimator.ofFloat(AppUtils.getDimension(R.dimen.voip_audio_routes_menu_translate_y), 0f).apply { - addUpdateListener { - val value = it.animatedValue as Float - conferenceLayoutMenuTranslateY.value = value - } - duration = if (corePreferences.enableAnimations) 500 else 0 - } - } - - val hideVideo = corePreferences.disableVideo - - private val callParams: CallParams = coreContext.core.createCallParams(null)!! - - private val listener: CoreListenerStub = object : CoreListenerStub() { - override fun onAudioDevicesListUpdated(core: Core) { - Log.i("[Conference Waiting Room] Audio devices list updated") - onAudioDevicesListUpdated() - } - - override fun onCallStateChanged( - core: Core, - call: Call, - state: Call.State?, - message: String - ) { - when (state) { - Call.State.End -> { - Log.i("[Conference Waiting Room] Call has ended, leaving waiting room fragment") - leaveWaitingRoomEvent.value = Event(true) - } - Call.State.Error -> { - Log.w( - "[Conference Waiting Room] Call has failed, leaving waiting room fragment" - ) - leaveWaitingRoomEvent.value = Event(true) - } - else -> {} - } - } - - override fun onConferenceStateChanged( - core: Core, - conference: Conference, - state: Conference.State? - ) { - if (state == Conference.State.Created) { - Log.i( - "[Conference Waiting Room] Conference has been created, leaving waiting room fragment" - ) - leaveWaitingRoomEvent.value = Event(true) - } - } - - override fun onNetworkReachable(core: Core, reachable: Boolean) { - Log.i("[Conference Waiting Room] Network reachability changed: [$reachable]") - networkReachable.value = reachable - if (!reachable) { - networkNotReachableEvent.value = Event(true) - } - } - } - - init { - val core = coreContext.core - core.addListener(listener) - - audioRoutesMenuTranslateY.value = AppUtils.getDimension( - R.dimen.voip_audio_routes_menu_translate_y - ) - conferenceLayoutMenuTranslateY.value = AppUtils.getDimension( - R.dimen.voip_audio_routes_menu_translate_y - ) - - val reachable = core.isNetworkReachable - networkReachable.value = reachable - if (!reachable) { - networkNotReachableEvent.value = Event(true) - } - - callParams.isMicEnabled = PermissionHelper.get().hasRecordAudioPermission() && coreContext.core.isMicEnabled - Log.i( - "[Conference Waiting Room] Microphone will be ${if (callParams.isMicEnabled) "enabled" else "muted"}" - ) - updateMicState() - - callParams.isVideoEnabled = isVideoAvailableInCore() - callParams.videoDirection = if (core.videoActivationPolicy.automaticallyInitiate) MediaDirection.SendRecv else MediaDirection.RecvOnly - updateVideoState() - - isLowBandwidth.value = false - if (LinphoneUtils.checkIfNetworkHasLowBandwidth(coreContext.context)) { - Log.w( - "[Conference Waiting Room] Enabling low bandwidth mode, forcing audio only layout!" - ) - callParams.isLowBandwidthEnabled = true - callParams.isVideoEnabled = false - callParams.videoDirection = MediaDirection.Inactive - isLowBandwidth.value = true - - updateVideoState() - onMessageToNotifyEvent.value = Event(R.string.conference_low_bandwidth) - } - - layoutMenuSelected.value = false - when (core.defaultConferenceLayout) { - Conference.Layout.Grid -> setMosaicLayout() - else -> setActiveSpeakerLayout() - } - - if (AudioRouteUtils.isBluetoothAudioRouteAvailable()) { - setBluetoothAudioRoute() - } else if (isVideoAvailableInCore() && isVideoEnabled.value == true) { - setSpeakerAudioRoute() - } else { - setEarpieceAudioRoute() - } - onAudioDevicesListUpdated() - } - - override fun onCleared() { - coreContext.core.removeListener(listener) - - super.onCleared() - } - - fun findConferenceInfoByAddress(stringAddress: String?) { - if (stringAddress != null) { - val address = Factory.instance().createAddress(stringAddress) - if (address != null) { - val conferenceInfo = coreContext.core.findConferenceInformationFromUri(address) - if (conferenceInfo != null) { - val myself = conferenceInfo.participantInfos.find { - it.address.asStringUriOnly() == coreContext.core.defaultAccount?.params?.identityAddress?.asStringUriOnly() - } - if (myself != null) { - Log.i( - "[Conference Waiting Room] Found our participant, it's role is [${myself.role}]" - ) - val areWeListener = myself.role == Participant.Role.Listener - isConferenceBroadcastWithListenerRole.value = areWeListener - } else { - Log.e( - "[Conference Waiting Room] Failed to find ourselves in participants info" - ) - } - } else { - Log.e( - "[Conference Waiting Room] Failed to find conference info using address [$stringAddress]" - ) - } - } - } else { - Log.e("[Conference Waiting Room] Can't find conference info using null address!") - } - } - - fun cancel() { - cancelConferenceJoiningEvent.value = Event(true) - } - - fun start() { - // Hide menus - audioRoutesSelected.value = false - layoutMenuSelected.value = false - - joinInProgress.value = true - joinConferenceEvent.value = Event(callParams) - } - - fun toggleMuteMicrophone() { - if (!PermissionHelper.get().hasRecordAudioPermission()) { - askPermissionEvent.value = Event(Manifest.permission.RECORD_AUDIO) - return - } - - callParams.isMicEnabled = !callParams.isMicEnabled - Log.i( - "[Conference Waiting Room] Microphone will be ${if (callParams.isMicEnabled) "enabled" else "muted"}" - ) - updateMicState() - } - - fun enableMic() { - Log.i("[Conference Waiting Room] Microphone will be enabled") - callParams.isMicEnabled = true - updateMicState() - } - - fun toggleSpeaker() { - if (isSpeakerSelected.value == true) { - setEarpieceAudioRoute() - } else { - setSpeakerAudioRoute() - } - } - - fun toggleAudioRoutesMenu() { - audioRoutesSelected.value = audioRoutesSelected.value != true - if (audioRoutesSelected.value == true) { - audioRoutesMenuAnimator.start() - } else { - audioRoutesMenuAnimator.reverse() - } - } - - fun setBluetoothAudioRoute() { - Log.i("[Conference Waiting Room] Set default output audio device to Bluetooth") - callParams.outputAudioDevice = coreContext.core.audioDevices.find { - it.type == AudioDevice.Type.Bluetooth && it.hasCapability( - AudioDevice.Capabilities.CapabilityPlay - ) - } - callParams.inputAudioDevice = coreContext.core.audioDevices.find { - it.type == AudioDevice.Type.Bluetooth && it.hasCapability( - AudioDevice.Capabilities.CapabilityRecord - ) - } - updateAudioRouteState() - - if (audioRoutesSelected.value == true) { - audioRoutesSelected.value = false - audioRoutesMenuAnimator.reverse() - } - } - - fun setSpeakerAudioRoute() { - Log.i("[Conference Waiting Room] Set default output audio device to Speaker") - callParams.outputAudioDevice = coreContext.core.audioDevices.find { - it.type == AudioDevice.Type.Speaker && it.hasCapability( - AudioDevice.Capabilities.CapabilityPlay - ) - } - callParams.inputAudioDevice = coreContext.core.audioDevices.find { - it.type == AudioDevice.Type.Microphone && it.hasCapability( - AudioDevice.Capabilities.CapabilityRecord - ) - } - updateAudioRouteState() - - if (audioRoutesSelected.value == true) { - audioRoutesSelected.value = false - audioRoutesMenuAnimator.reverse() - } - } - - fun setEarpieceAudioRoute() { - Log.i("[Conference Waiting Room] Set default output audio device to Earpiece") - callParams.outputAudioDevice = coreContext.core.audioDevices.find { - it.type == AudioDevice.Type.Earpiece && it.hasCapability( - AudioDevice.Capabilities.CapabilityPlay - ) - } - callParams.inputAudioDevice = coreContext.core.audioDevices.find { - it.type == AudioDevice.Type.Microphone && it.hasCapability( - AudioDevice.Capabilities.CapabilityRecord - ) - } - updateAudioRouteState() - - if (audioRoutesSelected.value == true) { - audioRoutesSelected.value = false - audioRoutesMenuAnimator.reverse() - } - } - - fun toggleLayoutMenu() { - layoutMenuSelected.value = layoutMenuSelected.value != true - if (layoutMenuSelected.value == true) { - conferenceLayoutMenuAnimator.start() - } else { - conferenceLayoutMenuAnimator.reverse() - } - } - - fun setMosaicLayout() { - Log.i("[Conference Waiting Room] Set default layout to Mosaic") - - callParams.conferenceVideoLayout = Conference.Layout.Grid - callParams.isVideoEnabled = isVideoAvailableInCore() - - updateLayout() - updateVideoState() - - layoutMenuSelected.value = false - conferenceLayoutMenuAnimator.reverse() - } - - fun setActiveSpeakerLayout() { - Log.i("[Conference Waiting Room] Set default layout to ActiveSpeaker") - - callParams.conferenceVideoLayout = Conference.Layout.ActiveSpeaker - callParams.isVideoEnabled = isVideoAvailableInCore() - - updateLayout() - updateVideoState() - - layoutMenuSelected.value = false - conferenceLayoutMenuAnimator.reverse() - } - - fun setAudioOnlyLayout() { - Log.i("[Conference Waiting Room] Set default layout to AudioOnly, disabling video in call") - callParams.isVideoEnabled = false - - updateLayout() - updateVideoState() - - layoutMenuSelected.value = false - conferenceLayoutMenuAnimator.reverse() - } - - fun toggleVideo() { - if (!PermissionHelper.get().hasCameraPermission()) { - askPermissionEvent.value = Event(Manifest.permission.CAMERA) - return - } - callParams.isVideoEnabled = isVideoAvailableInCore() - callParams.videoDirection = if (callParams.videoDirection == MediaDirection.SendRecv) MediaDirection.RecvOnly else MediaDirection.SendRecv - updateVideoState() - } - - fun enableVideo() { - callParams.isVideoEnabled = isVideoAvailableInCore() - callParams.videoDirection = MediaDirection.SendRecv - updateVideoState() - } - - fun switchCamera() { - Log.i("[Conference Waiting Room] Switching camera") - coreContext.switchCamera() - } - - private fun updateMicState() { - isMicrophoneMuted.value = !callParams.isMicEnabled - } - - private fun onAudioDevicesListUpdated() { - val bluetoothDeviceAvailable = AudioRouteUtils.isBluetoothAudioRouteAvailable() - if (!bluetoothDeviceAvailable && audioRoutesEnabled.value == true) { - Log.w( - "[Conference Waiting Room] Bluetooth device no longer available, switching back to default microphone & earpiece/speaker" - ) - } - audioRoutesEnabled.value = bluetoothDeviceAvailable - - if (!bluetoothDeviceAvailable) { - audioRoutesSelected.value = false - if (isBluetoothHeadsetSelected.value == true) { - for (audioDevice in coreContext.core.audioDevices) { - if (isVideoEnabled.value == true) { - if (audioDevice.type == AudioDevice.Type.Speaker) { - callParams.outputAudioDevice = audioDevice - } - } else { - if (audioDevice.type == AudioDevice.Type.Earpiece) { - callParams.outputAudioDevice = audioDevice - } - } - if (audioDevice.type == AudioDevice.Type.Microphone) { - callParams.inputAudioDevice = audioDevice - } - } - } - } - - updateAudioRouteState() - } - - private fun updateAudioRouteState() { - val outputDeviceType = callParams.outputAudioDevice?.type - isSpeakerSelected.value = outputDeviceType == AudioDevice.Type.Speaker - isBluetoothHeadsetSelected.value = outputDeviceType == AudioDevice.Type.Bluetooth - } - - private fun updateLayout() { - if (!callParams.isVideoEnabled) { - selectedLayout.value = ConferenceDisplayMode.AUDIO_ONLY - } else { - selectedLayout.value = when (callParams.conferenceVideoLayout) { - Conference.Layout.Grid -> ConferenceDisplayMode.GRID - else -> ConferenceDisplayMode.ACTIVE_SPEAKER - } - } - } - - private fun updateVideoState() { - isVideoAvailable.value = callParams.isVideoEnabled - isVideoEnabled.value = callParams.isVideoEnabled && callParams.videoDirection == MediaDirection.SendRecv - Log.i( - "[Conference Waiting Room] Video will be ${if (callParams.isVideoEnabled) "enabled" else "disabled"} with direction ${callParams.videoDirection}" - ) - - isSwitchCameraAvailable.value = callParams.isVideoEnabled && coreContext.showSwitchCameraButton() - coreContext.core.isVideoPreviewEnabled = isVideoEnabled.value == true - } - - private fun isVideoAvailableInCore(): Boolean { - val core = coreContext.core - return core.isVideoCaptureEnabled || core.isVideoPreviewEnabled - } -} diff --git a/app/src/main/java/org/linphone/activities/main/conference/viewmodels/ScheduledConferencesViewModel.kt b/app/src/main/java/org/linphone/activities/main/conference/viewmodels/ScheduledConferencesViewModel.kt deleted file mode 100644 index c8acab844..000000000 --- a/app/src/main/java/org/linphone/activities/main/conference/viewmodels/ScheduledConferencesViewModel.kt +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.conference.viewmodels - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.activities.main.conference.data.ScheduledConferenceData -import org.linphone.core.* -import org.linphone.core.tools.Log -import org.linphone.utils.LinphoneUtils - -class ScheduledConferencesViewModel : ViewModel() { - val conferences = MutableLiveData>() - - val showTerminated = MutableLiveData() - - private val conferenceScheduler: ConferenceScheduler by lazy { - val scheduler = coreContext.core.createConferenceScheduler() - scheduler.addListener(conferenceListener) - scheduler - } - - private val listener = object : CoreListenerStub() { - override fun onConferenceInfoReceived(core: Core, conferenceInfo: ConferenceInfo) { - Log.i("[Scheduled Conferences] New conference info received") - computeConferenceInfoList() - } - } - - private val conferenceListener = object : ConferenceSchedulerListenerStub() { - override fun onStateChanged( - conferenceScheduler: ConferenceScheduler, - state: ConferenceScheduler.State - ) { - Log.i("[Scheduled Conferences] Conference scheduler state is $state") - if (state == ConferenceScheduler.State.Ready) { - Log.i( - "[Scheduled Conferences] Conference ${conferenceScheduler.info?.subject} cancelled" - ) - val chatRoomParams = LinphoneUtils.getConferenceInvitationsChatRoomParams() - conferenceScheduler.sendInvitations(chatRoomParams) // Send cancel ICS - } - } - - override fun onInvitationsSent( - conferenceScheduler: ConferenceScheduler, - failedInvitations: Array? - ) { - if (failedInvitations?.isNotEmpty() == true) { - for (address in failedInvitations) { - Log.e( - "[Scheduled Conferences] Conference cancelled ICS wasn't sent to participant ${address.asStringUriOnly()}" - ) - } - } else { - Log.i( - "[Scheduled Conferences] Conference cancelled ICS successfully sent to all participants" - ) - } - } - } - - init { - coreContext.core.addListener(listener) - - showTerminated.value = false - - computeConferenceInfoList() - } - - override fun onCleared() { - coreContext.core.removeListener(listener) - conferences.value.orEmpty().forEach(ScheduledConferenceData::destroy) - - super.onCleared() - } - - fun applyFilter() { - computeConferenceInfoList() - } - - fun deleteConferenceInfo(data: ScheduledConferenceData) { - val conferenceInfoList = arrayListOf() - - conferenceInfoList.addAll(conferences.value.orEmpty()) - conferenceInfoList.remove(data) - - if (data.conferenceInfo.state != ConferenceInfo.State.Cancelled && data.canEdit.value == true) { - Log.i("[Scheduled Conferences] Cancelling conference ${data.conferenceInfo.subject}") - conferenceScheduler.cancelConference(data.conferenceInfo) - } - - data.delete() - data.destroy() - conferences.value = conferenceInfoList - } - - fun deleteConferencesInfo(toRemoveList: List) { - val conferenceInfoList = arrayListOf() - - for (confInfo in conferences.value.orEmpty()) { - if (confInfo in toRemoveList) { - confInfo.delete() - confInfo.destroy() - } else { - conferenceInfoList.add(confInfo) - } - } - - conferences.value = conferenceInfoList - } - - private fun computeConferenceInfoList() { - conferences.value.orEmpty().forEach(ScheduledConferenceData::destroy) - - val conferencesList = arrayListOf() - - val now = System.currentTimeMillis() / 1000 // Linphone uses time_t in seconds - - if (showTerminated.value == true) { - for (conferenceInfo in coreContext.core.conferenceInformationList) { - if (conferenceInfo.duration == 0) continue // This isn't a scheduled conference, don't display it - val limit = conferenceInfo.dateTime + conferenceInfo.duration - if (limit >= now) continue // This isn't a terminated conference, don't display it - val data = ScheduledConferenceData(conferenceInfo, true) - conferencesList.add(0, data) // Keep terminated meetings list in reverse order to always display most recent on top - } - } else { - val oneHourAgo = now - 7200 // Show all conferences from 2 hours ago and forward - for (conferenceInfo in coreContext.core.getConferenceInformationListAfterTime( - oneHourAgo - )) { - if (conferenceInfo.duration == 0) continue // This isn't a scheduled conference, don't display it - val data = ScheduledConferenceData(conferenceInfo, false) - conferencesList.add(data) - } - } - - conferences.value = conferencesList - Log.i("[Scheduled Conferences] Found ${conferencesList.size} future conferences") - } -} diff --git a/app/src/main/java/org/linphone/activities/main/contact/adapters/ContactsListAdapter.kt b/app/src/main/java/org/linphone/activities/main/contact/adapters/ContactsListAdapter.kt deleted file mode 100644 index 5d503f94f..000000000 --- a/app/src/main/java/org/linphone/activities/main/contact/adapters/ContactsListAdapter.kt +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.contact.adapters - -import android.content.Context -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.databinding.DataBindingUtil -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.MutableLiveData -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.RecyclerView -import org.linphone.R -import org.linphone.activities.main.adapters.SelectionListAdapter -import org.linphone.activities.main.contact.viewmodels.ContactViewModel -import org.linphone.activities.main.viewmodels.ListTopBarViewModel -import org.linphone.core.Friend -import org.linphone.databinding.ContactListCellBinding -import org.linphone.databinding.GenericListHeaderBinding -import org.linphone.utils.AppUtils -import org.linphone.utils.Event -import org.linphone.utils.HeaderAdapter - -class ContactsListAdapter( - selectionVM: ListTopBarViewModel, - private val viewLifecycleOwner: LifecycleOwner -) : SelectionListAdapter( - selectionVM, - ContactDiffCallback() -), - HeaderAdapter { - val selectedContactEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - val binding: ContactListCellBinding = DataBindingUtil.inflate( - LayoutInflater.from(parent.context), - R.layout.contact_list_cell, - parent, - false - ) - return ViewHolder(binding) - } - - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - (holder as ViewHolder).bind(getItem(position)) - } - - inner class ViewHolder( - val binding: ContactListCellBinding - ) : RecyclerView.ViewHolder(binding.root) { - fun bind(contactViewModel: ContactViewModel) { - with(binding) { - viewModel = contactViewModel - - lifecycleOwner = viewLifecycleOwner - - // This is for item selection through ListTopBarFragment - selectionListViewModel = selectionViewModel - selectionViewModel.isEditionEnabled.observe( - viewLifecycleOwner - ) { - position = bindingAdapterPosition - } - - setClickListener { - if (selectionViewModel.isEditionEnabled.value == true) { - selectionViewModel.onToggleSelect(bindingAdapterPosition) - } else { - val friend = contactViewModel.contact.value - // TODO FIXME !!! - if (friend != null) selectedContactEvent.value = Event(friend) - } - } - - setLongClickListener { - if (selectionViewModel.isEditionEnabled.value == false) { - selectionViewModel.isEditionEnabled.value = true - // Selection will be handled by click listener - true - } - false - } - - executePendingBindings() - } - } - } - - override fun displayHeaderForPosition(position: Int): Boolean { - if (position >= itemCount) return false - val contact = getItem(position) - val firstLetter = contact.fullName.firstOrNull().toString() - val previousPosition = position - 1 - return if (previousPosition >= 0) { - val previousItemFirstLetter = getItem(previousPosition).fullName.firstOrNull().toString() - !firstLetter.equals(previousItemFirstLetter, ignoreCase = true) - } else { - true - } - } - - override fun getHeaderViewForPosition(context: Context, position: Int): View { - val contact = getItem(position) - val firstLetter = AppUtils.getInitials(contact.fullName, 1) - val binding: GenericListHeaderBinding = DataBindingUtil.inflate( - LayoutInflater.from(context), - R.layout.generic_list_header, - null, - false - ) - binding.title = firstLetter - binding.executePendingBindings() - return binding.root - } -} - -private class ContactDiffCallback : DiffUtil.ItemCallback() { - override fun areItemsTheSame( - oldItem: ContactViewModel, - newItem: ContactViewModel - ): Boolean { - return oldItem.fullName.compareTo(newItem.fullName) == 0 - } - - override fun areContentsTheSame( - oldItem: ContactViewModel, - newItem: ContactViewModel - ): Boolean { - return true - } -} diff --git a/app/src/main/java/org/linphone/activities/main/contact/adapters/SyncAccountAdapter.kt b/app/src/main/java/org/linphone/activities/main/contact/adapters/SyncAccountAdapter.kt deleted file mode 100644 index f8303bcb7..000000000 --- a/app/src/main/java/org/linphone/activities/main/contact/adapters/SyncAccountAdapter.kt +++ /dev/null @@ -1,49 +0,0 @@ -package org.linphone.activities.main.contact.adapters - -import android.graphics.drawable.Drawable -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.BaseAdapter -import android.widget.ImageView -import android.widget.TextView -import kotlin.collections.ArrayList -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R - -class SyncAccountAdapter : BaseAdapter() { - private var accounts: ArrayList> = arrayListOf() - - init { - accounts.addAll(coreContext.contactsManager.getAvailableSyncAccounts()) - } - - override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { - val view: View = convertView ?: LayoutInflater.from(parent.context).inflate( - R.layout.contact_sync_account_picker_cell, - parent, - false - ) - val account = getItem(position) - - val icon = view.findViewById(R.id.account_icon) - icon.setImageDrawable(account.third) - icon.contentDescription = account.second - val name = view.findViewById(R.id.account_name) - name.text = account.first - - return view - } - - override fun getItem(position: Int): Triple { - return accounts[position] - } - - override fun getItemId(position: Int): Long { - return position.toLong() - } - - override fun getCount(): Int { - return accounts.size - } -} diff --git a/app/src/main/java/org/linphone/activities/main/contact/data/ContactEditorData.kt b/app/src/main/java/org/linphone/activities/main/contact/data/ContactEditorData.kt deleted file mode 100644 index 2b545fd57..000000000 --- a/app/src/main/java/org/linphone/activities/main/contact/data/ContactEditorData.kt +++ /dev/null @@ -1,360 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.contact.data - -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.media.ExifInterface -import android.provider.ContactsContract -import androidx.lifecycle.MutableLiveData -import java.io.ByteArrayOutputStream -import java.io.IOException -import kotlinx.coroutines.CoroutineScope -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.contact.* -import org.linphone.core.ChatRoom.SecurityLevel -import org.linphone.core.ConsolidatedPresence -import org.linphone.core.Friend -import org.linphone.core.tools.Log -import org.linphone.utils.AppUtils -import org.linphone.utils.ImageUtils -import org.linphone.utils.PermissionHelper - -class ContactEditorData(val friend: Friend?) : ContactDataInterface { - override val contact: MutableLiveData = MutableLiveData() - override val displayName: MutableLiveData = MutableLiveData() - override val securityLevel: MutableLiveData = MutableLiveData() - override val presenceStatus: MutableLiveData = MutableLiveData() - override val coroutineScope: CoroutineScope = coreContext.coroutineScope - - val firstName = MutableLiveData() - - val lastName = MutableLiveData() - - val organization = MutableLiveData() - - val displayOrganization = corePreferences.contactOrganizationVisible - - val tempPicturePath = MutableLiveData() - private var picture: ByteArray? = null - - val numbers = MutableLiveData>() - - val addresses = MutableLiveData>() - - var syncAccountName: String? = null - var syncAccountType: String? = null - - init { - if (friend != null) { - contact.value = friend!! - displayName.value = friend.name ?: "" - presenceStatus.value = friend.consolidatedPresence - friend.addListener { - presenceStatus.value = it.consolidatedPresence - } - } else { - displayName.value = "" - presenceStatus.value = ConsolidatedPresence.Offline - } - - organization.value = friend?.organization ?: "" - - firstName.value = "" - lastName.value = "" - val refKey = friend?.refKey - val vCard = friend?.vcard - if (vCard?.familyName.isNullOrEmpty() && vCard?.givenName.isNullOrEmpty()) { - if (refKey != null) { - Log.w("[Contact Editor] vCard first & last name not filled-in yet, doing it now") - fetchFirstAndLastNames(refKey) - } else { - Log.e( - "[Contact Editor] vCard first & last name not available as contact doesn't have a native ID" - ) - } - } else { - firstName.value = vCard?.givenName - lastName.value = vCard?.familyName - } - - updateNumbersAndAddresses(refKey) - } - - fun save(): Friend { - var contact = friend - var created = false - - if (contact == null) { - created = true - // From Crashlytics it seems both permissions are required... - val nativeId = if (PermissionHelper.get().hasReadContactsPermission() && - PermissionHelper.get().hasWriteContactsPermission() - ) { - Log.i("[Contact Editor] Creating native contact") - NativeContactEditor.createAndroidContact(syncAccountName, syncAccountType) - .toString() - } else { - Log.e("[Contact Editor] Can't create native contact, permission denied") - null - } - contact = coreContext.core.createFriend() - contact.refKey = nativeId - } - - if (contact.refKey != null) { - Log.i("[Contact Editor] Committing changes in native contact id ${contact.refKey}") - NativeContactEditor(contact) - .setFirstAndLastNames(firstName.value.orEmpty(), lastName.value.orEmpty()) - .setOrganization(organization.value.orEmpty()) - .setPhoneNumbers(numbers.value.orEmpty()) - .setSipAddresses(addresses.value.orEmpty()) - .setPicture(picture) - .commit() - } - - if (!created) contact.edit() - - contact.name = "${firstName.value.orEmpty()} ${lastName.value.orEmpty()}" - contact.organization = organization.value - - for (address in contact.addresses) { - contact.removeAddress(address) - } - for (address in addresses.value.orEmpty()) { - val sipAddress = address.newValue.value.orEmpty() - if (sipAddress.isEmpty() || address.toRemove.value == true) continue - - val parsed = coreContext.core.interpretUrl(sipAddress, false) - if (parsed != null) contact.addAddress(parsed) - } - - for (phone in contact.phoneNumbers) { - contact.removePhoneNumber(phone) - } - for (phone in numbers.value.orEmpty()) { - val phoneNumber = phone.newValue.value.orEmpty() - if (phoneNumber.isEmpty() || phone.toRemove.value == true) continue - - contact.addPhoneNumber(phoneNumber) - } - - val vCard = contact.vcard - if (vCard != null) { - vCard.familyName = lastName.value - vCard.givenName = firstName.value - } - - if (created) { - coreContext.core.defaultFriendList?.addLocalFriend(contact) - } else { - contact.done() - } - return contact - } - - fun setPictureFromPath(picturePath: String) { - var orientation = ExifInterface.ORIENTATION_NORMAL - var image = BitmapFactory.decodeFile(picturePath) - - try { - val ei = ExifInterface(picturePath) - orientation = ei.getAttributeInt( - ExifInterface.TAG_ORIENTATION, - ExifInterface.ORIENTATION_UNDEFINED - ) - Log.i("[Contact Editor] Exif rotation is $orientation") - } catch (e: IOException) { - Log.e("[Contact Editor] Failed to get Exif rotation, exception raised: $e") - } - - if (image == null) { - Log.e("[Contact Editor] Couldn't get bitmap from filePath: $picturePath") - return - } - - when (orientation) { - ExifInterface.ORIENTATION_ROTATE_90 -> - image = - ImageUtils.rotateImage(image, 90f) - ExifInterface.ORIENTATION_ROTATE_180 -> - image = - ImageUtils.rotateImage(image, 180f) - ExifInterface.ORIENTATION_ROTATE_270 -> - image = - ImageUtils.rotateImage(image, 270f) - } - - val stream = ByteArrayOutputStream() - image.compress(Bitmap.CompressFormat.JPEG, 100, stream) - picture = stream.toByteArray() - tempPicturePath.value = picturePath - image.recycle() - stream.close() - } - - fun addEmptySipAddress() { - val list = arrayListOf() - list.addAll(addresses.value.orEmpty()) - list.add(NumberOrAddressEditorData("", true)) - addresses.value = list - } - - fun addEmptyPhoneNumber() { - val list = arrayListOf() - list.addAll(numbers.value.orEmpty()) - list.add(NumberOrAddressEditorData("", false)) - numbers.value = list - } - - private fun updateNumbersAndAddresses(contactId: String?) { - val phoneNumbers = arrayListOf() - val sipAddresses = arrayListOf() - var fetched = false - - if (contactId != null) { - try { - // Try to get real values from contact to ensure edition/removal in native address book will go well - val cursor = coreContext.context.contentResolver.query( - ContactsContract.Data.CONTENT_URI, - arrayOf( - ContactsContract.Data.MIMETYPE, - ContactsContract.CommonDataKinds.Phone.NUMBER - ), - ContactsContract.CommonDataKinds.StructuredName.CONTACT_ID + " = ?", - arrayOf(contactId), - null - ) - - while (cursor != null && cursor.moveToNext()) { - val linphoneMime = AppUtils.getString(R.string.linphone_address_mime_type) - val mime: String? = - cursor.getString( - cursor.getColumnIndexOrThrow(ContactsContract.Data.MIMETYPE) - ) - if (mime == ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) { - val data1: String? = - cursor.getString( - cursor.getColumnIndexOrThrow( - ContactsContract.CommonDataKinds.Phone.NUMBER - ) - ) - if (data1 != null) { - phoneNumbers.add(NumberOrAddressEditorData(data1, false)) - } - } else if ( - mime == ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE || - mime == linphoneMime - ) { - val data1: String? = - cursor.getString( - cursor.getColumnIndexOrThrow( - ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS - ) - ) - if (data1 != null) { - sipAddresses.add(NumberOrAddressEditorData(data1, true)) - } - } - } - - cursor?.close() - fetched = true - } catch (e: Exception) { - Log.e("[Contact Editor] Failed to sip addresses & phone number: $e") - fetched = false - } - } - - if (!fetched) { - Log.w( - "[Contact Editor] Fall-backing to friend info (might be inaccurate and thus edition/removal might fail)" - ) - for (number in friend?.phoneNumbers.orEmpty()) { - phoneNumbers.add(NumberOrAddressEditorData(number, false)) - } - - for (address in friend?.addresses.orEmpty()) { - sipAddresses.add(NumberOrAddressEditorData(address.asStringUriOnly(), true)) - } - } - - if (phoneNumbers.isEmpty()) { - phoneNumbers.add(NumberOrAddressEditorData("", false)) - } - numbers.value = phoneNumbers - - if (sipAddresses.isEmpty()) { - sipAddresses.add(NumberOrAddressEditorData("", true)) - } - addresses.value = sipAddresses - } - - private fun fetchFirstAndLastNames(contactId: String) { - try { - val cursor = coreContext.context.contentResolver.query( - ContactsContract.Data.CONTENT_URI, - arrayOf( - ContactsContract.Data.MIMETYPE, - ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, - ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME - ), - ContactsContract.CommonDataKinds.StructuredName.CONTACT_ID + " = ?", - arrayOf(contactId), - null - ) - - while (cursor != null && cursor.moveToNext()) { - val mime: String? = cursor.getString( - cursor.getColumnIndexOrThrow(ContactsContract.Data.MIMETYPE) - ) - if (mime == ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) { - val givenName: String? = - cursor.getString( - cursor.getColumnIndexOrThrow( - ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME - ) - ) - if (!givenName.isNullOrEmpty()) { - friend?.vcard?.givenName = givenName - firstName.value = givenName!! - } - - val familyName: String? = - cursor.getString( - cursor.getColumnIndexOrThrow( - ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME - ) - ) - if (!familyName.isNullOrEmpty()) { - friend?.vcard?.familyName = familyName - lastName.value = familyName!! - } - } - } - - cursor?.close() - } catch (e: Exception) { - Log.e("[Contact Editor] Failed to fetch first & last name: $e") - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/contact/data/ContactNumberOrAddressData.kt b/app/src/main/java/org/linphone/activities/main/contact/data/ContactNumberOrAddressData.kt deleted file mode 100644 index 93f709d5e..000000000 --- a/app/src/main/java/org/linphone/activities/main/contact/data/ContactNumberOrAddressData.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.contact.data - -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.core.Address - -class ContactNumberOrAddressData( - val address: Address?, - val hasPresence: Boolean, - val displayedValue: String, - val isSip: Boolean = true, - val showSecureChat: Boolean = false, - val typeLabel: String = "", - private val listener: ContactNumberOrAddressClickListener -) { - val showInvite = !hasPresence && !isSip && corePreferences.showContactInviteBySms - - val chatAllowed = !corePreferences.disableChat - - val hidePlainChat = corePreferences.forceEndToEndEncryptedChat - - fun startCall() { - address ?: return - listener.onCall(address) - } - - fun startChat(secured: Boolean) { - address ?: return - listener.onChat(address, secured) - } - - fun smsInvite() { - listener.onSmsInvite(displayedValue) - } -} - -interface ContactNumberOrAddressClickListener { - fun onCall(address: Address) - - fun onChat(address: Address, isSecured: Boolean) - - fun onSmsInvite(number: String) -} diff --git a/app/src/main/java/org/linphone/activities/main/contact/data/NumberOrAddressEditorData.kt b/app/src/main/java/org/linphone/activities/main/contact/data/NumberOrAddressEditorData.kt deleted file mode 100644 index 44c22cb39..000000000 --- a/app/src/main/java/org/linphone/activities/main/contact/data/NumberOrAddressEditorData.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.contact.data - -import androidx.lifecycle.MutableLiveData - -class NumberOrAddressEditorData(val currentValue: String, val isSipAddress: Boolean) { - val newValue = MutableLiveData() - - val toRemove = MutableLiveData() - - init { - newValue.value = currentValue - toRemove.value = false - } - - fun remove() { - toRemove.value = true - } -} diff --git a/app/src/main/java/org/linphone/activities/main/contact/fragments/ContactEditorFragment.kt b/app/src/main/java/org/linphone/activities/main/contact/fragments/ContactEditorFragment.kt deleted file mode 100644 index 31c5497ac..000000000 --- a/app/src/main/java/org/linphone/activities/main/contact/fragments/ContactEditorFragment.kt +++ /dev/null @@ -1,251 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.contact.fragments - -import android.app.Activity -import android.app.Dialog -import android.content.Intent -import android.content.pm.PackageManager -import android.os.Bundle -import android.os.Parcelable -import android.provider.MediaStore -import android.view.View -import androidx.core.content.FileProvider -import androidx.lifecycle.lifecycleScope -import java.io.File -import kotlinx.coroutines.launch -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.GenericFragment -import org.linphone.activities.main.MainActivity -import org.linphone.activities.main.contact.data.ContactEditorData -import org.linphone.activities.main.contact.data.NumberOrAddressEditorData -import org.linphone.activities.main.contact.viewmodels.* -import org.linphone.activities.main.viewmodels.DialogViewModel -import org.linphone.activities.navigateToContact -import org.linphone.core.tools.Log -import org.linphone.databinding.ContactEditorFragmentBinding -import org.linphone.utils.DialogUtils -import org.linphone.utils.FileUtils -import org.linphone.utils.PermissionHelper - -class ContactEditorFragment : GenericFragment(), SyncAccountPickerFragment.SyncAccountPickedListener { - private lateinit var data: ContactEditorData - private var temporaryPicturePath: File? = null - - override fun getLayoutId(): Int = R.layout.contact_editor_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - val contact = sharedViewModel.selectedContact.value - val contactRefKey = contact?.refKey - val friend = if (contactRefKey != null) coreContext.core.getFriendByRefKey(contactRefKey) else null - data = ContactEditorData(friend ?: contact) - binding.viewModel = data - - useMaterialSharedAxisXForwardAnimation = sharedViewModel.isSlidingPaneSlideable.value == false - - binding.setAvatarClickListener { - pickFile() - } - - binding.setSaveChangesClickListener { - data.syncAccountName = null - data.syncAccountType = null - - if (data.friend == null) { - var atLeastASipAddressOrPhoneNumber = false - for (addr in data.addresses.value.orEmpty()) { - if (addr.newValue.value.orEmpty().isNotEmpty()) { - atLeastASipAddressOrPhoneNumber = true - break - } - } - if (!atLeastASipAddressOrPhoneNumber) { - for (number in data.numbers.value.orEmpty()) { - if (number.newValue.value.orEmpty().isNotEmpty()) { - atLeastASipAddressOrPhoneNumber = true - break - } - } - } - if (!atLeastASipAddressOrPhoneNumber) { - // Contact will be created without phone and SIP address - // Let's warn the user it won't be visible in Linphone app - Log.w( - "[Contact Editor] New contact without SIP address nor phone number, showing warning dialog" - ) - showInvisibleContactWarningDialog() - } else if (corePreferences.showNewContactAccountDialog) { - Log.i("[Contact Editor] New contact, ask user where to store it") - SyncAccountPickerFragment(this).show(childFragmentManager, "SyncAccountPicker") - } else { - Log.i("[Contact Editor] Saving new contact") - saveContact() - } - } else { - Log.i("[Contact Editor] Saving contact changes") - saveContact() - } - } - - val sipUri = arguments?.getString("SipUri") - if (sipUri != null) { - Log.i("[Contact Editor] Found SIP URI in arguments: $sipUri") - val newSipUri = NumberOrAddressEditorData("", true) - newSipUri.newValue.value = sipUri - - val list = arrayListOf() - list.addAll(data.addresses.value.orEmpty()) - list.add(newSipUri) - data.addresses.value = list - } - - if (!PermissionHelper.required(requireContext()).hasWriteContactsPermission()) { - Log.i("[Contact Editor] Asking for WRITE_CONTACTS permission") - requestPermissions(arrayOf(android.Manifest.permission.WRITE_CONTACTS), 0) - } - } - - override fun onSyncAccountClicked(name: String?, type: String?) { - Log.i("[Contact Editor] Saving new contact using account $name / $type") - data.syncAccountName = name - data.syncAccountType = type - saveContact() - } - - @Deprecated("Deprecated in Java") - override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray - ) { - if (requestCode == 0) { - val granted = grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED - if (granted) { - Log.i("[Contact Editor] WRITE_CONTACTS permission granted") - } else { - Log.w("[Contact Editor] WRITE_CONTACTS permission denied") - (activity as MainActivity).showSnackBar( - R.string.contact_editor_write_permission_denied - ) - goBack() - } - } - } - - @Deprecated("Deprecated in Java") - override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) { - if (resultCode == Activity.RESULT_OK) { - lifecycleScope.launch { - val contactImageFilePath = FileUtils.getFilePathFromPickerIntent( - intent, - temporaryPicturePath - ) - if (contactImageFilePath != null) { - data.setPictureFromPath(contactImageFilePath) - } - } - } - } - - private fun saveContact() { - val savedContact = data.save() - val id = savedContact.refKey - if (id != null) { - Log.i("[Contact Editor] Displaying contact $savedContact") - navigateToContact(id) - } else { - Log.w( - "[Contact Editor] Can't display $savedContact because it doesn't have a refKey, going back" - ) - goBack() - } - } - - private fun pickFile() { - val cameraIntents = ArrayList() - - // Handles image picking - val galleryIntent = Intent(Intent.ACTION_PICK) - galleryIntent.type = "image/*" - - if (PermissionHelper.get().hasCameraPermission()) { - // Allows to capture directly from the camera - val captureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) - val tempFileName = System.currentTimeMillis().toString() + ".jpeg" - val file = FileUtils.getFileStoragePath(tempFileName) - temporaryPicturePath = file - val publicUri = FileProvider.getUriForFile( - requireContext(), - requireContext().getString(R.string.file_provider), - file - ) - captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, publicUri) - captureIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - captureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) - cameraIntents.add(captureIntent) - } - - val chooserIntent = - Intent.createChooser(galleryIntent, getString(R.string.chat_message_pick_file_dialog)) - chooserIntent.putExtra( - Intent.EXTRA_INITIAL_INTENTS, - cameraIntents.toArray(arrayOf()) - ) - - startActivityForResult(chooserIntent, 0) - } - - private fun showInvisibleContactWarningDialog() { - val dialogViewModel = - DialogViewModel(getString(R.string.contacts_new_contact_wont_be_visible_warning_dialog)) - val dialog: Dialog = DialogUtils.getDialog(requireContext(), dialogViewModel) - - dialogViewModel.showCancelButton( - { - Log.i("[Contact Editor] Aborting new contact saving") - dialog.dismiss() - }, - getString(R.string.no) - ) - - dialogViewModel.showOkButton( - { - dialog.dismiss() - - if (corePreferences.showNewContactAccountDialog) { - Log.i("[Contact Editor] New contact, ask user where to store it") - SyncAccountPickerFragment(this).show(childFragmentManager, "SyncAccountPicker") - } else { - Log.i("[Contact Editor] Saving new contact") - saveContact() - } - }, - getString(R.string.yes) - ) - - dialog.show() - } -} diff --git a/app/src/main/java/org/linphone/activities/main/contact/fragments/DetailContactFragment.kt b/app/src/main/java/org/linphone/activities/main/contact/fragments/DetailContactFragment.kt deleted file mode 100644 index ab01a9b23..000000000 --- a/app/src/main/java/org/linphone/activities/main/contact/fragments/DetailContactFragment.kt +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.contact.fragments - -import android.app.Dialog -import android.content.Intent -import android.net.Uri -import android.os.Bundle -import android.view.View -import androidx.lifecycle.ViewModelProvider -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R -import org.linphone.activities.* -import org.linphone.activities.main.* -import org.linphone.activities.main.contact.viewmodels.ContactViewModel -import org.linphone.activities.main.contact.viewmodels.ContactViewModelFactory -import org.linphone.activities.main.viewmodels.DialogViewModel -import org.linphone.activities.navigateToChatRoom -import org.linphone.activities.navigateToContactEditor -import org.linphone.activities.navigateToDialer -import org.linphone.core.tools.Log -import org.linphone.databinding.ContactDetailFragmentBinding -import org.linphone.utils.DialogUtils -import org.linphone.utils.Event - -class DetailContactFragment : GenericFragment() { - private lateinit var viewModel: ContactViewModel - - override fun getLayoutId(): Int = R.layout.contact_detail_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - postponeEnterTransition() - - binding.lifecycleOwner = viewLifecycleOwner - - binding.sharedMainViewModel = sharedViewModel - - useMaterialSharedAxisXForwardAnimation = sharedViewModel.isSlidingPaneSlideable.value == false - - val id = arguments?.getString("id") - arguments?.clear() - if (id != null) { - Log.i("[Contact] Found contact id parameter in arguments: $id") - sharedViewModel.selectedContact.value = coreContext.contactsManager.findContactById(id) - } - - val contact = sharedViewModel.selectedContact.value - if (contact == null) { - Log.e("[Contact] Friend is null, aborting!") - goBack() - return - } - - viewModel = ViewModelProvider( - this, - ContactViewModelFactory(contact) - )[ContactViewModel::class.java] - binding.viewModel = viewModel - - viewModel.sendSmsToEvent.observe( - viewLifecycleOwner - ) { - it.consume { number -> - sendSms(number) - } - } - - viewModel.startCallToEvent.observe( - viewLifecycleOwner - ) { - it.consume { address -> - if (coreContext.core.callsNb > 0) { - Log.i( - "[Contact] Starting dialer with pre-filled URI ${address.asStringUriOnly()}, is transfer? ${sharedViewModel.pendingCallTransfer}" - ) - sharedViewModel.updateContactsAnimationsBasedOnDestination.value = - Event(R.id.dialerFragment) - sharedViewModel.updateDialerAnimationsBasedOnDestination.value = - Event(R.id.masterContactsFragment) - - val args = Bundle() - args.putString("URI", address.asStringUriOnly()) - args.putBoolean("Transfer", sharedViewModel.pendingCallTransfer) - args.putBoolean( - "SkipAutoCallStart", - true - ) // If auto start call setting is enabled, ignore it - navigateToDialer(args) - } else { - coreContext.startCall(address) - } - } - } - - viewModel.chatRoomCreatedEvent.observe( - viewLifecycleOwner - ) { - it.consume { chatRoom -> - sharedViewModel.updateContactsAnimationsBasedOnDestination.value = - Event(R.id.masterChatRoomsFragment) - val args = Bundle() - args.putString("LocalSipUri", chatRoom.localAddress.asStringUriOnly()) - args.putString("RemoteSipUri", chatRoom.peerAddress.asStringUriOnly()) - navigateToChatRoom(args) - } - } - - binding.setEditClickListener { - navigateToContactEditor() - } - - binding.setDeleteClickListener { - confirmContactRemoval() - } - - viewModel.onMessageToNotifyEvent.observe( - viewLifecycleOwner - ) { - it.consume { messageResourceId -> - (activity as MainActivity).showSnackBar(messageResourceId) - } - } - viewModel.updateNumbersAndAddresses() - - startPostponedEnterTransition() - } - - override fun onResume() { - super.onResume() - if (this::viewModel.isInitialized) { - viewModel.registerContactListener() - coreContext.contactsManager.contactIdToWatchFor = viewModel.contact.value?.refKey ?: "" - } - } - - override fun onPause() { - super.onPause() - coreContext.contactsManager.contactIdToWatchFor = "" - if (this::viewModel.isInitialized) { - viewModel.unregisterContactListener() - } - } - - private fun confirmContactRemoval() { - val dialogViewModel = DialogViewModel(getString(R.string.contact_delete_one_dialog)) - dialogViewModel.showIcon = true - dialogViewModel.iconResource = R.drawable.dialog_delete_icon - val dialog: Dialog = DialogUtils.getDialog(requireContext(), dialogViewModel) - - dialogViewModel.showCancelButton { - dialog.dismiss() - } - - dialogViewModel.showDeleteButton( - { - viewModel.deleteContact() - dialog.dismiss() - goBack() - }, - getString(R.string.dialog_delete) - ) - - dialog.show() - } - - private fun sendSms(number: String) { - val smsIntent = Intent(Intent.ACTION_SENDTO) - smsIntent.putExtra("address", number) - smsIntent.data = Uri.parse("smsto:$number") - val text = getString(R.string.contact_send_sms_invite_text).format( - getString(R.string.contact_send_sms_invite_download_link) - ) - smsIntent.putExtra("sms_body", text) - startActivity(smsIntent) - } -} diff --git a/app/src/main/java/org/linphone/activities/main/contact/fragments/MasterContactsFragment.kt b/app/src/main/java/org/linphone/activities/main/contact/fragments/MasterContactsFragment.kt deleted file mode 100644 index d388c9e78..000000000 --- a/app/src/main/java/org/linphone/activities/main/contact/fragments/MasterContactsFragment.kt +++ /dev/null @@ -1,392 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.contact.fragments - -import android.app.Dialog -import android.content.pm.PackageManager -import android.content.res.Configuration -import android.os.Bundle -import android.view.View -import androidx.core.content.ContextCompat -import androidx.lifecycle.ViewModelProvider -import androidx.navigation.fragment.NavHostFragment -import androidx.recyclerview.widget.ItemTouchHelper -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import com.google.android.material.transition.MaterialSharedAxis -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.SnackBarActivity -import org.linphone.activities.clearDisplayedContact -import org.linphone.activities.main.MainActivity -import org.linphone.activities.main.contact.adapters.ContactsListAdapter -import org.linphone.activities.main.contact.viewmodels.ContactsListViewModel -import org.linphone.activities.main.fragments.MasterFragment -import org.linphone.activities.main.viewmodels.DialogViewModel -import org.linphone.activities.navigateToContact -import org.linphone.activities.navigateToContactEditor -import org.linphone.core.Factory -import org.linphone.core.Friend -import org.linphone.core.tools.Log -import org.linphone.databinding.ContactMasterFragmentBinding -import org.linphone.utils.* - -class MasterContactsFragment : MasterFragment() { - override val dialogConfirmationMessageBeforeRemoval = R.plurals.contact_delete_dialog - private lateinit var listViewModel: ContactsListViewModel - - private var sipUriToAdd: String? = null - private var editOnClick: Boolean = false - private var contactIdToDisplay: String? = null - - override fun getLayoutId(): Int = R.layout.contact_master_fragment - - override fun onDestroyView() { - binding.contactsList.adapter = null - super.onDestroyView() - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - listViewModel = ViewModelProvider(this)[ContactsListViewModel::class.java] - binding.viewModel = listViewModel - - /* Shared view model & sliding pane related */ - - setUpSlidingPane(binding.slidingPane) - - useMaterialSharedAxisXForwardAnimation = false - sharedViewModel.updateContactsAnimationsBasedOnDestination.observe( - viewLifecycleOwner - ) { - it.consume { id -> - val forward = when (id) { - R.id.dialerFragment, R.id.masterChatRoomsFragment -> false - else -> true - } - if (corePreferences.enableAnimations) { - val portraitOrientation = - resources.configuration.orientation != Configuration.ORIENTATION_LANDSCAPE - val axis = - if (portraitOrientation) MaterialSharedAxis.X else MaterialSharedAxis.Y - enterTransition = MaterialSharedAxis(axis, forward) - reenterTransition = MaterialSharedAxis(axis, forward) - returnTransition = MaterialSharedAxis(axis, !forward) - exitTransition = MaterialSharedAxis(axis, !forward) - } - } - } - - sharedViewModel.layoutChangedEvent.observe( - viewLifecycleOwner - ) { - it.consume { - sharedViewModel.isSlidingPaneSlideable.value = binding.slidingPane.isSlideable - if (binding.slidingPane.isSlideable) { - val navHostFragment = - childFragmentManager.findFragmentById(R.id.contacts_nav_container) as NavHostFragment - if (navHostFragment.navController.currentDestination?.id == R.id.emptyContactFragment) { - Log.i( - "[Contacts] Foldable device has been folded, closing side pane with empty fragment" - ) - binding.slidingPane.closePane() - } - } - } - } - - /* End of shared view model & sliding pane related */ - - _adapter = ContactsListAdapter(listSelectionViewModel, viewLifecycleOwner) - binding.contactsList.setHasFixedSize(true) - binding.contactsList.adapter = adapter - - binding.setEditClickListener { - if (PermissionHelper.get().hasWriteContactsPermission()) { - listSelectionViewModel.isEditionEnabled.value = true - } else { - Log.i("[Contacts] Asking for WRITE_CONTACTS permission") - requestPermissions(arrayOf(android.Manifest.permission.WRITE_CONTACTS), 1) - } - } - - val layoutManager = LinearLayoutManager(requireContext()) - binding.contactsList.layoutManager = layoutManager - - // Swipe action - val swipeConfiguration = RecyclerViewSwipeConfiguration() - val white = ContextCompat.getColor(requireContext(), R.color.white_color) - - swipeConfiguration.rightToLeftAction = RecyclerViewSwipeConfiguration.Action( - requireContext().getString(R.string.dialog_delete), - white, - ContextCompat.getColor(requireContext(), R.color.red_color) - ) - val swipeListener = object : RecyclerViewSwipeListener { - override fun onLeftToRightSwipe(viewHolder: RecyclerView.ViewHolder) {} - - override fun onRightToLeftSwipe(viewHolder: RecyclerView.ViewHolder) { - val viewModel = DialogViewModel(getString(R.string.contact_delete_one_dialog)) - viewModel.showIcon = true - viewModel.iconResource = R.drawable.dialog_delete_icon - val dialog: Dialog = DialogUtils.getDialog(requireContext(), viewModel) - - val index = viewHolder.bindingAdapterPosition - if (index < 0 || index >= adapter.currentList.size) { - Log.e("[Contacts] Index is out of bound, can't delete contact") - } else { - val contactViewModel = adapter.currentList[index] - if (contactViewModel.isNativeContact.value == false) { - adapter.notifyItemChanged(index) - val activity = requireActivity() as MainActivity - activity.showSnackBar(R.string.contact_cant_be_deleted) - return - } - - viewModel.showCancelButton { - adapter.notifyItemChanged(index) - dialog.dismiss() - } - - viewModel.showDeleteButton( - { - val deletedContact = - adapter.currentList[index].contact.value - if (deletedContact != null) { - listViewModel.deleteContact(deletedContact) - if (!binding.slidingPane.isSlideable && - deletedContact == sharedViewModel.selectedContact.value - ) { - Log.i( - "[Contacts] Currently displayed contact has been deleted, removing detail fragment" - ) - clearDisplayedContact() - } - } - dialog.dismiss() - }, - getString(R.string.dialog_delete) - ) - } - - dialog.show() - } - } - - if (!corePreferences.readOnlyNativeContacts) { - RecyclerViewSwipeUtils(ItemTouchHelper.LEFT, swipeConfiguration, swipeListener) - .attachToRecyclerView(binding.contactsList) - } - - // Divider between items - binding.contactsList.addItemDecoration( - AppUtils.getDividerDecoration(requireContext(), layoutManager) - ) - - // Displays the first letter header - val headerItemDecoration = RecyclerViewHeaderDecoration(requireContext(), adapter) - binding.contactsList.addItemDecoration(headerItemDecoration) - - adapter.selectedContactEvent.observe( - viewLifecycleOwner - ) { - it.consume { contact -> - Log.d("[Contacts] Selected item in list changed: $contact") - sharedViewModel.selectedContact.value = contact - (requireActivity() as MainActivity).hideKeyboard() - - if (editOnClick) { - navigateToContactEditor(sipUriToAdd, binding.slidingPane) - editOnClick = false - sipUriToAdd = null - } else { - navigateToContact() - binding.slidingPane.openPane() - } - } - } - - coreContext.contactsManager.fetchInProgress.observe( - viewLifecycleOwner - ) { - listViewModel.fetchInProgress.value = it - } - - listViewModel.contactsList.observe( - viewLifecycleOwner - ) { - val id = contactIdToDisplay - if (id != null) { - val contact = coreContext.contactsManager.findContactById(id) - if (contact != null) { - contactIdToDisplay = null - Log.i("[Contacts] Found matching contact [$contact] after callback") - adapter.selectedContactEvent.value = Event(contact) - } else { - Log.w("[Contacts] No contact found matching id [$id] after callback") - } - } - adapter.submitList(it) - } - - listViewModel.moreResultsAvailableEvent.observe( - viewLifecycleOwner - ) { - it.consume { - (requireActivity() as SnackBarActivity).showSnackBar( - R.string.contacts_ldap_query_more_results_available - ) - } - } - - binding.setAllContactsToggleClickListener { - listViewModel.sipContactsSelected.value = false - } - binding.setSipContactsToggleClickListener { - listViewModel.sipContactsSelected.value = true - } - - listViewModel.sipContactsSelected.observe( - viewLifecycleOwner - ) { - listViewModel.updateContactsList(true) - } - - listViewModel.filter.observe( - viewLifecycleOwner - ) { - listViewModel.updateContactsList(false) - } - - binding.setNewContactClickListener { - // Remove any previously selected contact - sharedViewModel.selectedContact.value = null - editOnClick = false - navigateToContactEditor(sipUriToAdd, binding.slidingPane) - sipUriToAdd = null - } - - val id = arguments?.getString("id") - val sipUri = arguments?.getString("sipUri") - val addressString = arguments?.getString("address") - arguments?.clear() - - if (id != null) { - Log.i("[Contacts] Found contact id parameter in arguments [$id]") - val contact = coreContext.contactsManager.findContactById(id) - if (contact != null) { - Log.i("[Contacts] Found matching contact [${contact.name}]") - adapter.selectedContactEvent.value = Event(contact) - } else { - Log.w( - "[Contacts] Matching contact not found yet, waiting for contacts updated callback" - ) - contactIdToDisplay = id - } - } else if (sipUri != null) { - Log.i("[Contacts] Found sipUri parameter in arguments [$sipUri]") - sipUriToAdd = sipUri - (activity as MainActivity).showSnackBar( - R.string.contact_choose_existing_or_new_to_add_number - ) - editOnClick = true - } else if (addressString != null) { - val address = Factory.instance().createAddress(addressString) - if (address != null) { - Log.i( - "[Contacts] Found friend SIP address parameter in arguments [${address.asStringUriOnly()}]" - ) - val contact = coreContext.contactsManager.findContactByAddress(address) - if (contact != null) { - Log.i("[Contacts] Found matching contact $contact") - adapter.selectedContactEvent.value = Event(contact) - } else { - Log.w( - "[Contacts] No matching contact found for SIP address [${address.asStringUriOnly()}]" - ) - } - } - } - - if (corePreferences.enableNativeAddressBookIntegration) { - if (!PermissionHelper.get().hasReadContactsPermission()) { - Log.i("[Contacts] Asking for READ_CONTACTS permission") - requestPermissions(arrayOf(android.Manifest.permission.READ_CONTACTS), 0) - } - } - } - - override fun deleteItems(indexesOfItemToDelete: ArrayList) { - val list = ArrayList() - var closeSlidingPane = false - for (index in indexesOfItemToDelete) { - val contact = adapter.currentList[index].contact.value - if (contact != null) { - list.add(contact) - } - - if (contact == sharedViewModel.selectedContact.value) { - closeSlidingPane = true - } - } - listViewModel.deleteContacts(list) - - if (!binding.slidingPane.isSlideable && closeSlidingPane) { - Log.i( - "[Contacts] Currently displayed contact has been deleted, removing detail fragment" - ) - clearDisplayedContact() - } - } - - override fun onResume() { - super.onResume() - listViewModel.updateContactsList(true) - } - - @Deprecated("Deprecated in Java") - override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray - ) { - if (requestCode == 0) { - val granted = grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED - if (granted) { - Log.i("[Contacts] READ_CONTACTS permission granted") - coreContext.fetchContacts() - } else { - Log.w("[Contacts] READ_CONTACTS permission denied") - } - } else if (requestCode == 1) { - val granted = grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED - if (granted) { - Log.i("[Contacts] WRITE_CONTACTS permission granted") - listSelectionViewModel.isEditionEnabled.value = true - } else { - Log.w("[Contacts] WRITE_CONTACTS permission denied") - } - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/contact/fragments/SyncAccountPickerFragment.kt b/app/src/main/java/org/linphone/activities/main/contact/fragments/SyncAccountPickerFragment.kt deleted file mode 100644 index 64e31bb05..000000000 --- a/app/src/main/java/org/linphone/activities/main/contact/fragments/SyncAccountPickerFragment.kt +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.contact.fragments - -import android.os.Bundle -import android.view.* -import androidx.fragment.app.DialogFragment -import org.linphone.R -import org.linphone.activities.main.contact.adapters.SyncAccountAdapter -import org.linphone.core.tools.Log -import org.linphone.databinding.ContactSyncAccountPickerFragmentBinding - -class SyncAccountPickerFragment(private val listener: SyncAccountPickedListener) : DialogFragment() { - private var _binding: ContactSyncAccountPickerFragmentBinding? = null - private val binding get() = _binding!! - private lateinit var adapter: SyncAccountAdapter - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setStyle(STYLE_NO_TITLE, R.style.assistant_country_dialog_style) - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - _binding = ContactSyncAccountPickerFragmentBinding.inflate(inflater, container, false) - - adapter = SyncAccountAdapter() - binding.accountsList.adapter = adapter - - binding.accountsList.setOnItemClickListener { _, _, position, _ -> - if (position >= 0 && position < adapter.count) { - val account = adapter.getItem(position) - Log.i("[Sync Account Picker] Picked ${account.first} / ${account.second}") - listener.onSyncAccountClicked(account.first, account.second) - } - dismiss() - } - - binding.setLocalSyncAccountClickListener { - Log.i("[Sync Account Picker] Picked local account") - listener.onSyncAccountClicked(null, null) - dismiss() - } - - return binding.root - } - - interface SyncAccountPickedListener { - fun onSyncAccountClicked(name: String?, type: String?) - } -} diff --git a/app/src/main/java/org/linphone/activities/main/contact/viewmodels/ContactViewModel.kt b/app/src/main/java/org/linphone/activities/main/contact/viewmodels/ContactViewModel.kt deleted file mode 100644 index 68ac85ace..000000000 --- a/app/src/main/java/org/linphone/activities/main/contact/viewmodels/ContactViewModel.kt +++ /dev/null @@ -1,271 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.contact.viewmodels - -import android.content.ContentProviderOperation -import android.provider.ContactsContract -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.CoroutineScope -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.main.contact.data.ContactNumberOrAddressClickListener -import org.linphone.activities.main.contact.data.ContactNumberOrAddressData -import org.linphone.activities.main.viewmodels.MessageNotifierViewModel -import org.linphone.contact.ContactDataInterface -import org.linphone.contact.ContactsUpdatedListenerStub -import org.linphone.contact.hasLongTermPresence -import org.linphone.core.* -import org.linphone.core.tools.Log -import org.linphone.utils.Event -import org.linphone.utils.LinphoneUtils -import org.linphone.utils.PhoneNumberUtils - -class ContactViewModelFactory(private val friend: Friend) : - ViewModelProvider.NewInstanceFactory() { - - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - return ContactViewModel(friend) as T - } -} - -class ContactViewModel(friend: Friend) : MessageNotifierViewModel(), ContactDataInterface { - override val contact: MutableLiveData = MutableLiveData() - override val displayName: MutableLiveData = MutableLiveData() - override val securityLevel: MutableLiveData = MutableLiveData() - override val presenceStatus: MutableLiveData = MutableLiveData() - override val coroutineScope: CoroutineScope = viewModelScope - - var fullName = "" - - val displayOrganization = corePreferences.displayOrganization - - val numbersAndAddresses = MutableLiveData>() - - val sendSmsToEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val startCallToEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val chatRoomCreatedEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val waitForChatRoomCreation = MutableLiveData() - - val isNativeContact = MutableLiveData() - - val readOnlyNativeAddressBook = MutableLiveData() - - val hasLongTermPresence = MutableLiveData() - - private val chatRoomListener = object : ChatRoomListenerStub() { - override fun onStateChanged(chatRoom: ChatRoom, state: ChatRoom.State) { - if (state == ChatRoom.State.Created) { - chatRoom.removeListener(this) - waitForChatRoomCreation.value = false - chatRoomCreatedEvent.value = Event(chatRoom) - } else if (state == ChatRoom.State.CreationFailed) { - Log.e("[Contact Detail] Group chat room creation has failed !") - chatRoom.removeListener(this) - waitForChatRoomCreation.value = false - onMessageToNotifyEvent.value = Event(R.string.chat_room_creation_failed_snack) - } - } - } - - private val contactsListener = object : ContactsUpdatedListenerStub() { - override fun onContactUpdated(friend: Friend) { - if (friend.refKey == contact.value?.refKey) { - Log.i("[Contact Detail] Friend has been updated!") - contact.value = friend - displayName.value = friend.name - isNativeContact.value = friend.refKey != null - updateNumbersAndAddresses() - } - } - } - - private val listener = object : ContactNumberOrAddressClickListener { - override fun onCall(address: Address) { - startCallToEvent.value = Event(address) - } - - override fun onChat(address: Address, isSecured: Boolean) { - waitForChatRoomCreation.value = true - val chatRoom = LinphoneUtils.createOneToOneChatRoom(address, isSecured) - - if (chatRoom != null) { - val state = chatRoom.state - Log.i("[Contact Detail] Found existing chat room in state $state") - if (state == ChatRoom.State.Created || state == ChatRoom.State.Terminated) { - waitForChatRoomCreation.value = false - chatRoomCreatedEvent.value = Event(chatRoom) - } else { - chatRoom.addListener(chatRoomListener) - } - } else { - waitForChatRoomCreation.value = false - Log.e("[Contact Detail] Couldn't create chat room with address $address") - onMessageToNotifyEvent.value = Event(R.string.chat_room_creation_failed_snack) - } - } - - override fun onSmsInvite(number: String) { - sendSmsToEvent.value = Event(number) - } - } - - init { - fullName = friend.name ?: "" - - contact.value = friend - displayName.value = friend.name - isNativeContact.value = friend.refKey != null - presenceStatus.value = friend.consolidatedPresence - readOnlyNativeAddressBook.value = corePreferences.readOnlyNativeContacts - hasLongTermPresence.value = friend.hasLongTermPresence() - - friend.addListener { - presenceStatus.value = it.consolidatedPresence - hasLongTermPresence.value = it.hasLongTermPresence() - } - } - - override fun onCleared() { - destroy() - super.onCleared() - } - - fun destroy() { - } - - fun registerContactListener() { - coreContext.contactsManager.addListener(contactsListener) - } - - fun unregisterContactListener() { - coreContext.contactsManager.removeListener(contactsListener) - } - - fun deleteContact() { - val select = ContactsContract.Data.CONTACT_ID + " = ?" - val ops = java.util.ArrayList() - - val id = contact.value?.refKey - if (id != null) { - Log.i("[Contact] Setting Android contact id $id to batch removal") - val args = arrayOf(id) - ops.add( - ContentProviderOperation.newDelete(ContactsContract.RawContacts.CONTENT_URI) - .withSelection(select, args) - .build() - ) - } - - contact.value?.remove() - - if (ops.isNotEmpty()) { - try { - Log.i("[Contact] Removing ${ops.size} contacts") - coreContext.context.contentResolver.applyBatch(ContactsContract.AUTHORITY, ops) - } catch (e: Exception) { - Log.e("[Contact] $e") - } - } - } - - fun updateNumbersAndAddresses() { - val list = arrayListOf() - val friend = contact.value ?: return - - for (address in friend.addresses) { - val username = address.username - if (username in friend.phoneNumbers) continue - - val value = address.asStringUriOnly() - val presenceModel = friend.getPresenceModelForUriOrTel(value) - val hasPresence = presenceModel?.basicStatus == PresenceBasicStatus.Open - val isMe = coreContext.core.defaultAccount?.params?.identityAddress?.weakEqual(address) ?: false - val hasLimeCapability = corePreferences.allowEndToEndEncryptedChatWithoutPresence || ( - friend.getPresenceModelForUriOrTel( - value - )?.hasCapability(Friend.Capability.LimeX3Dh) ?: false - ) - val secureChatAllowed = LinphoneUtils.isEndToEndEncryptedChatAvailable() && !isMe && hasLimeCapability - val displayValue = if (coreContext.core.defaultAccount?.params?.domain == address.domain) (address.username ?: value) else value - val noa = ContactNumberOrAddressData( - address, - hasPresence, - displayValue, - showSecureChat = secureChatAllowed, - listener = listener - ) - list.add(noa) - } - - for (phoneNumber in friend.phoneNumbersWithLabel) { - val number = phoneNumber.phoneNumber - val presenceModel = friend.getPresenceModelForUriOrTel(number) - val hasPresence = presenceModel != null && presenceModel.basicStatus == PresenceBasicStatus.Open - val contactAddress = presenceModel?.contact ?: number - val address = coreContext.core.interpretUrl( - contactAddress, - LinphoneUtils.applyInternationalPrefix() - ) - address?.displayName = displayName.value.orEmpty() - val isMe = if (address != null) { - coreContext.core.defaultAccount?.params?.identityAddress?.weakEqual( - address - ) ?: false - } else { - false - } - val hasLimeCapability = corePreferences.allowEndToEndEncryptedChatWithoutPresence || ( - friend.getPresenceModelForUriOrTel( - number - )?.hasCapability(Friend.Capability.LimeX3Dh) ?: false - ) - val secureChatAllowed = LinphoneUtils.isEndToEndEncryptedChatAvailable() && !isMe && hasLimeCapability - val label = PhoneNumberUtils.vcardParamStringToAddressBookLabel( - coreContext.context.resources, - phoneNumber.label ?: "" - ) - val noa = ContactNumberOrAddressData( - address, - hasPresence, - number, - isSip = false, - showSecureChat = secureChatAllowed, - typeLabel = label, - listener = listener - ) - list.add(noa) - } - numbersAndAddresses.postValue(list) - } -} diff --git a/app/src/main/java/org/linphone/activities/main/contact/viewmodels/ContactsListViewModel.kt b/app/src/main/java/org/linphone/activities/main/contact/viewmodels/ContactsListViewModel.kt deleted file mode 100644 index 7ab26ee8e..000000000 --- a/app/src/main/java/org/linphone/activities/main/contact/viewmodels/ContactsListViewModel.kt +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.contact.viewmodels - -import android.content.ContentProviderOperation -import android.provider.ContactsContract -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import java.util.* -import kotlinx.coroutines.* -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.contact.ContactsUpdatedListenerStub -import org.linphone.core.* -import org.linphone.core.tools.Log -import org.linphone.utils.Event - -class ContactsListViewModel : ViewModel() { - val sipContactsSelected = MutableLiveData() - - val contactsList = MutableLiveData>() - - val nativeAddressBookEnabled = MutableLiveData() - - val readOnlyNativeAddressBook = MutableLiveData() - - val hideSipContactsList = MutableLiveData() - - val onlyShowSipContactsList = MutableLiveData() - - val fetchInProgress = MutableLiveData() - private var searchResultsPending: Boolean = false - private var fastFetchJob: Job? = null - - val filter = MutableLiveData() - private var previousFilter = "NotSet" - - val moreResultsAvailableEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - private val contactsUpdatedListener = object : ContactsUpdatedListenerStub() { - override fun onContactsUpdated() { - Log.i("[Contacts] Contacts have changed") - updateContactsList(true) - } - } - - private val magicSearchListener = object : MagicSearchListenerStub() { - override fun onSearchResultsReceived(magicSearch: MagicSearch) { - Log.i("[Contacts] Magic search contacts available") - searchResultsPending = false - processMagicSearchResults(magicSearch.lastSearch) - // Use coreContext.contactsManager.fetchInProgress instead of false in case contacts are still being loaded - fetchInProgress.value = coreContext.contactsManager.fetchInProgress.value - } - - override fun onLdapHaveMoreResults(magicSearch: MagicSearch, ldap: Ldap) { - moreResultsAvailableEvent.value = Event(true) - } - } - - init { - sipContactsSelected.value = coreContext.contactsManager.shouldDisplaySipContactsList() - nativeAddressBookEnabled.value = corePreferences.enableNativeAddressBookIntegration - readOnlyNativeAddressBook.value = corePreferences.readOnlyNativeContacts - - onlyShowSipContactsList.value = corePreferences.onlyShowSipContactsList - hideSipContactsList.value = corePreferences.hideSipContactsList - if (onlyShowSipContactsList.value == true) { - sipContactsSelected.value = true - } - if (hideSipContactsList.value == true) { - sipContactsSelected.value = false - } - - coreContext.contactsManager.addListener(contactsUpdatedListener) - coreContext.contactsManager.magicSearch.addListener(magicSearchListener) - } - - override fun onCleared() { - contactsList.value.orEmpty().forEach(ContactViewModel::destroy) - coreContext.contactsManager.magicSearch.removeListener(magicSearchListener) - coreContext.contactsManager.removeListener(contactsUpdatedListener) - - super.onCleared() - } - - fun updateContactsList(clearCache: Boolean) { - val filterValue = filter.value.orEmpty() - - if (clearCache || ( - previousFilter.isNotEmpty() && ( - previousFilter.length > filterValue.length || - (previousFilter.length == filterValue.length && previousFilter != filterValue) - ) - ) - ) { - coreContext.contactsManager.magicSearch.resetSearchCache() - } - previousFilter = filterValue - - val domain = if (sipContactsSelected.value == true) coreContext.core.defaultAccount?.params?.domain ?: "" else "" - val sources = MagicSearch.Source.Friends.toInt() or MagicSearch.Source.LdapServers.toInt() - val aggregation = MagicSearch.Aggregation.Friend - searchResultsPending = true - fastFetchJob?.cancel() - Log.i( - "[Contacts] Asking Magic search for contacts matching filter [$filterValue], domain [$domain] and in sources [$sources]" - ) - coreContext.contactsManager.magicSearch.getContactsListAsync( - filterValue, - domain, - sources, - aggregation - ) - - val spinnerDelay = corePreferences.delayBeforeShowingContactsSearchSpinner.toLong() - fastFetchJob = viewModelScope.launch { - withContext(Dispatchers.IO) { - delay(spinnerDelay) - } - withContext(Dispatchers.Main) { - if (searchResultsPending) { - fetchInProgress.value = true - } - } - } - } - - private fun processMagicSearchResults(results: Array) { - Log.i("[Contacts] Processing ${results.size} results") - contactsList.value.orEmpty().forEach(ContactViewModel::destroy) - - val list = arrayListOf() - - for (result in results) { - val friend = result.friend - - val viewModel = if (friend != null) { - ContactViewModel(friend) - } else { - Log.w("[Contacts] SearchResult [$result] has no Friend!") - val fakeFriend = coreContext.contactsManager.createFriendFromSearchResult( - result - ) - ContactViewModel(fakeFriend) - } - - list.add(viewModel) - } - - contactsList.value = list - Log.i("[Contacts] Processed ${results.size} results") - } - - fun deleteContact(friend: Friend) { - friend.remove() // TODO: FIXME: friend is const here! - - val id = friend.refKey - if (id == null) { - Log.w("[Contacts] Friend has no refkey, can't delete it from native address book") - return - } - - val select = ContactsContract.Data.CONTACT_ID + " = ?" - val ops = ArrayList() - - Log.i("[Contacts] Adding Android contact id $id to batch removal") - val args = arrayOf(id) - ops.add( - ContentProviderOperation.newDelete(ContactsContract.RawContacts.CONTENT_URI) - .withSelection(select, args) - .build() - ) - - if (ops.isNotEmpty()) { - try { - Log.i("[Contacts] Removing ${ops.size} contacts") - coreContext.context.contentResolver.applyBatch(ContactsContract.AUTHORITY, ops) - } catch (e: Exception) { - Log.e("[Contacts] $e") - } - } - } - - fun deleteContacts(list: ArrayList) { - val select = ContactsContract.Data.CONTACT_ID + " = ?" - val ops = ArrayList() - - for (friend in list) { - val id = friend.refKey - if (id != null) { - Log.i("[Contacts] Adding Android contact id $id to batch removal") - val args = arrayOf(id) - ops.add( - ContentProviderOperation.newDelete(ContactsContract.RawContacts.CONTENT_URI) - .withSelection(select, args) - .build() - ) - } - friend.remove() - } - - if (ops.isNotEmpty()) { - try { - Log.i("[Contacts] Removing ${ops.size} contacts") - coreContext.context.contentResolver.applyBatch(ContactsContract.AUTHORITY, ops) - } catch (e: Exception) { - Log.e("[Contacts] $e") - } - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/dialer/NumpadDigitListener.kt b/app/src/main/java/org/linphone/activities/main/dialer/NumpadDigitListener.kt deleted file mode 100644 index 49cfa6437..000000000 --- a/app/src/main/java/org/linphone/activities/main/dialer/NumpadDigitListener.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.dialer - -interface NumpadDigitListener { - fun handleClick(key: Char) - fun handleLongClick(key: Char): Boolean -} diff --git a/app/src/main/java/org/linphone/activities/main/dialer/fragments/ConfigViewerFragment.kt b/app/src/main/java/org/linphone/activities/main/dialer/fragments/ConfigViewerFragment.kt deleted file mode 100644 index 9ec0e6d47..000000000 --- a/app/src/main/java/org/linphone/activities/main/dialer/fragments/ConfigViewerFragment.kt +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.dialer.fragments - -import android.content.ActivityNotFoundException -import android.content.Intent -import android.os.Bundle -import android.view.View -import androidx.lifecycle.ViewModelProvider -import org.linphone.R -import org.linphone.activities.main.dialer.viewmodels.ConfigFileViewModel -import org.linphone.activities.main.fragments.SecureFragment -import org.linphone.core.tools.Log -import org.linphone.databinding.FileConfigViewerFragmentBinding - -class ConfigViewerFragment : SecureFragment() { - private lateinit var viewModel: ConfigFileViewModel - - override fun getLayoutId(): Int = R.layout.file_config_viewer_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - viewModel = ViewModelProvider(this)[ConfigFileViewModel::class.java] - binding.viewModel = viewModel - - isSecure = arguments?.getBoolean("Secure") ?: false - - binding.setExportClickListener { - shareConfig() - } - } - - private fun shareConfig() { - val intent = Intent(Intent.ACTION_SEND) - intent.putExtra(Intent.EXTRA_TEXT, viewModel.text.value.orEmpty()) - intent.type = "text/plain" - - try { - startActivity(Intent.createChooser(intent, getString(R.string.app_name))) - } catch (ex: ActivityNotFoundException) { - Log.e(ex) - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/dialer/fragments/DialerFragment.kt b/app/src/main/java/org/linphone/activities/main/dialer/fragments/DialerFragment.kt deleted file mode 100644 index 926442817..000000000 --- a/app/src/main/java/org/linphone/activities/main/dialer/fragments/DialerFragment.kt +++ /dev/null @@ -1,401 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.dialer.fragments - -import android.Manifest -import android.annotation.TargetApi -import android.app.Dialog -import android.content.ClipData -import android.content.ClipboardManager -import android.content.Context -import android.content.Intent -import android.content.pm.PackageManager -import android.content.res.Configuration -import android.net.Uri -import android.os.Bundle -import android.view.View -import androidx.lifecycle.ViewModelProvider -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import com.google.android.material.transition.MaterialSharedAxis -import org.linphone.BuildConfig -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.GenericActivity -import org.linphone.activities.main.MainActivity -import org.linphone.activities.main.dialer.viewmodels.DialerViewModel -import org.linphone.activities.main.fragments.SecureFragment -import org.linphone.activities.main.viewmodels.DialogViewModel -import org.linphone.activities.navigateToConferenceScheduling -import org.linphone.activities.navigateToConfigFileViewer -import org.linphone.activities.navigateToContacts -import org.linphone.compatibility.Compatibility -import org.linphone.core.tools.Log -import org.linphone.databinding.DialerFragmentBinding -import org.linphone.mediastream.Version -import org.linphone.telecom.TelecomHelper -import org.linphone.utils.AppUtils -import org.linphone.utils.DialogUtils -import org.linphone.utils.Event -import org.linphone.utils.PermissionHelper - -class DialerFragment : SecureFragment() { - private lateinit var viewModel: DialerViewModel - - private var uploadLogsInitiatedByUs = false - - override fun getLayoutId(): Int = R.layout.dialer_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - viewModel = ViewModelProvider(this)[DialerViewModel::class.java] - binding.viewModel = viewModel - - useMaterialSharedAxisXForwardAnimation = false - sharedViewModel.updateDialerAnimationsBasedOnDestination.observe( - viewLifecycleOwner - ) { - it.consume { id -> - val forward = when (id) { - R.id.masterChatRoomsFragment -> false - else -> true - } - if (corePreferences.enableAnimations) { - val portraitOrientation = - resources.configuration.orientation != Configuration.ORIENTATION_LANDSCAPE - val axis = - if (portraitOrientation) MaterialSharedAxis.X else MaterialSharedAxis.Y - enterTransition = MaterialSharedAxis(axis, forward) - reenterTransition = MaterialSharedAxis(axis, forward) - returnTransition = MaterialSharedAxis(axis, !forward) - exitTransition = MaterialSharedAxis(axis, !forward) - } - } - } - - binding.setNewContactClickListener { - sharedViewModel.updateDialerAnimationsBasedOnDestination.value = Event( - R.id.masterContactsFragment - ) - sharedViewModel.updateContactsAnimationsBasedOnDestination.value = Event( - R.id.dialerFragment - ) - navigateToContacts(viewModel.enteredUri.value) - } - - binding.setNewConferenceClickListener { - sharedViewModel.updateDialerAnimationsBasedOnDestination.value = Event( - R.id.conferenceSchedulingFragment - ) - navigateToConferenceScheduling() - } - - binding.setTransferCallClickListener { - if (viewModel.transferCall()) { - // Transfer has been consumed, otherwise it might have been a "bis" use - sharedViewModel.pendingCallTransfer = false - viewModel.transferVisibility.value = false - coreContext.onCallStarted() - } - } - - viewModel.enteredUri.observe( - viewLifecycleOwner - ) { - if (it == corePreferences.debugPopupCode) { - displayDebugPopup() - viewModel.enteredUri.value = "" - } - } - - viewModel.uploadFinishedEvent.observe( - viewLifecycleOwner - ) { - it.consume { url -> - // To prevent being trigger when using the Send Logs button in About page - if (uploadLogsInitiatedByUs) { - val clipboard = - requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - val clip = ClipData.newPlainText("Logs url", url) - clipboard.setPrimaryClip(clip) - - val activity = requireActivity() as MainActivity - activity.showSnackBar(R.string.logs_url_copied_to_clipboard) - - AppUtils.shareUploadedLogsUrl(activity, url) - } - } - } - - viewModel.updateAvailableEvent.observe( - viewLifecycleOwner - ) { - it.consume { url -> - displayNewVersionAvailableDialog(url) - } - } - - viewModel.onMessageToNotifyEvent.observe( - viewLifecycleOwner - ) { - it.consume { resourceId -> - (requireActivity() as MainActivity).showSnackBar(resourceId) - } - } - - if (corePreferences.firstStart) { - Log.w( - "[Dialer] First start detected, wait for assistant to be finished to check for update & request permissions" - ) - return - } - - if (arguments?.containsKey("URI") == true) { - val address = arguments?.getString("URI") ?: "" - Log.i("[Dialer] Found URI to call: $address") - val skipAutoCall = arguments?.getBoolean("SkipAutoCallStart") ?: false - - if (corePreferences.skipDialerForNewCallAndTransfer) { - if (sharedViewModel.pendingCallTransfer) { - Log.i( - "[Dialer] We were asked to skip dialer so starting new call to [$address] now" - ) - viewModel.transferCallTo(address) - } else { - Log.i( - "[Dialer] We were asked to skip dialer so starting transfer to [$address] now" - ) - viewModel.directCall(address) - } - } else if (corePreferences.callRightAway && !skipAutoCall) { - Log.i("[Dialer] Call right away setting is enabled, start the call to [$address]") - viewModel.directCall(address) - } else { - sharedViewModel.dialerUri = address - } - } - arguments?.clear() - - Log.i("[Dialer] Pending call transfer mode = ${sharedViewModel.pendingCallTransfer}") - viewModel.transferVisibility.value = sharedViewModel.pendingCallTransfer - - viewModel.autoInitiateVideoCalls.value = coreContext.core.videoActivationPolicy.automaticallyInitiate - - checkForUpdate() - - checkPermissions() - } - - override fun onPause() { - sharedViewModel.dialerUri = viewModel.enteredUri.value ?: "" - super.onPause() - } - - override fun onResume() { - super.onResume() - - if ((requireActivity() as GenericActivity).isTablet()) { - coreContext.core.nativePreviewWindowId = binding.videoPreviewWindow - } - - viewModel.updateShowVideoPreview() - viewModel.autoInitiateVideoCalls.value = coreContext.core.videoActivationPolicy.automaticallyInitiate - uploadLogsInitiatedByUs = false - - viewModel.enteredUri.value = sharedViewModel.dialerUri - } - - @Deprecated("Deprecated in Java") - override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray - ) { - if (requestCode == 0) { - if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - Log.i("[Dialer] READ_PHONE_STATE permission has been granted") - coreContext.initPhoneStateListener() - // If first permission has been granted, continue to ask for permissions, - // otherwise don't do it or it will loop indefinitely - checkPermissions() - } - } else if (requestCode == 1) { - var allGranted = true - for (result in grantResults) { - if (result != PackageManager.PERMISSION_GRANTED) { - allGranted = false - } - } - if (allGranted) { - Log.i("[Dialer] Telecom Manager permission have been granted") - enableTelecomManager() - } else { - Log.w("[Dialer] Telecom Manager permission have been denied (at least one of them)") - } - } else if (requestCode == 2) { - if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - Log.i("[Dialer] POST_NOTIFICATIONS permission has been granted") - } - checkTelecomManagerPermissions() - } - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - } - - private fun checkPermissions() { - if (!PermissionHelper.get().hasReadPhoneStatePermission()) { - Log.i("[Dialer] Asking for READ_PHONE_STATE permission") - requestPermissions(arrayOf(Manifest.permission.READ_PHONE_STATE), 0) - } else if (!PermissionHelper.get().hasPostNotificationsPermission()) { - // Don't check the following the previous permission is being asked - Log.i("[Dialer] Asking for POST_NOTIFICATIONS permission") - Compatibility.requestPostNotificationsPermission(this, 2) - } else if (Version.sdkAboveOrEqual(Version.API26_O_80)) { - // Don't check the following the previous permissions are being asked - checkTelecomManagerPermissions() - } - - // See https://developer.android.com/about/versions/14/behavior-changes-14#fgs-types - if (Version.sdkAboveOrEqual(Version.API34_ANDROID_14_UPSIDE_DOWN_CAKE)) { - val fullScreenIntentPermission = Compatibility.hasFullScreenIntentPermission( - requireContext() - ) - Log.i( - "[Dialer] Android 14 or above detected: full-screen intent permission is ${if (fullScreenIntentPermission) "granted" else "not granted"}" - ) - if (!fullScreenIntentPermission) { - (requireActivity() as MainActivity).showSnackBar( - R.string.android_14_full_screen_intent_permission_not_granted, - R.string.android_14_go_to_full_screen_intent_permission_setting - ) { - Compatibility.requestFullScreenIntentPermission(requireContext()) - } - } - } - } - - @TargetApi(Version.API26_O_80) - private fun checkTelecomManagerPermissions() { - if (!corePreferences.useTelecomManager) { - Log.i("[Dialer] Telecom Manager feature is disabled") - if (corePreferences.manuallyDisabledTelecomManager) { - Log.w("[Dialer] User has manually disabled Telecom Manager feature") - } else { - if (Compatibility.hasTelecomManagerPermissions(requireContext())) { - enableTelecomManager() - } else { - Log.i("[Dialer] Asking for Telecom Manager permissions") - Compatibility.requestTelecomManagerPermissions(requireActivity(), 1) - } - } - } else { - Log.i("[Dialer] Telecom Manager feature is already enabled") - } - } - - @TargetApi(Version.API26_O_80) - private fun enableTelecomManager() { - Log.i("[Dialer] Telecom Manager permissions granted") - if (!TelecomHelper.exists()) { - Log.i("[Dialer] Creating Telecom Helper") - if (Compatibility.hasTelecomManagerFeature(requireContext())) { - TelecomHelper.create(requireContext()) - } else { - Log.e( - "[Dialer] Telecom Helper can't be created, device doesn't support connection service!" - ) - return - } - } else { - Log.e("[Dialer] Telecom Manager was already created ?!") - } - corePreferences.useTelecomManager = true - } - - private fun displayDebugPopup() { - val alertDialog = MaterialAlertDialogBuilder(requireContext()) - alertDialog.setTitle(getString(R.string.debug_popup_title)) - - val items = if (corePreferences.debugLogs) { - resources.getStringArray(R.array.popup_send_log) - } else { - resources.getStringArray(R.array.popup_enable_log) - } - - alertDialog.setItems(items) { _, which -> - when (items[which]) { - getString(R.string.debug_popup_disable_logs) -> { - corePreferences.debugLogs = false - } - getString(R.string.debug_popup_enable_logs) -> { - corePreferences.debugLogs = true - } - getString(R.string.debug_popup_send_logs) -> { - uploadLogsInitiatedByUs = true - viewModel.uploadLogs() - } - getString(R.string.debug_popup_show_config_file) -> { - navigateToConfigFileViewer() - } - } - } - - alertDialog.show() - } - - private fun checkForUpdate() { - val lastTimestamp: Int = corePreferences.lastUpdateAvailableCheckTimestamp - val currentTimeStamp = System.currentTimeMillis().toInt() - val interval: Int = corePreferences.checkUpdateAvailableInterval - if (lastTimestamp == 0 || currentTimeStamp - lastTimestamp >= interval) { - val currentVersion = BuildConfig.VERSION_NAME - Log.i("[Dialer] Checking for update using current version [$currentVersion]") - coreContext.core.checkForUpdate(currentVersion) - corePreferences.lastUpdateAvailableCheckTimestamp = currentTimeStamp - } - } - - private fun displayNewVersionAvailableDialog(url: String) { - val viewModel = DialogViewModel(getString(R.string.dialog_update_available)) - val dialog: Dialog = DialogUtils.getDialog(requireContext(), viewModel) - - viewModel.showCancelButton { - dialog.dismiss() - } - - viewModel.showOkButton( - { - try { - val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) - startActivity(browserIntent) - } catch (ise: IllegalStateException) { - Log.e("[Dialer] Can't start ACTION_VIEW intent, IllegalStateException: $ise") - } finally { - dialog.dismiss() - } - }, - getString(R.string.dialog_ok) - ) - - dialog.show() - } -} diff --git a/app/src/main/java/org/linphone/activities/main/dialer/viewmodels/ConfigFileViewModel.kt b/app/src/main/java/org/linphone/activities/main/dialer/viewmodels/ConfigFileViewModel.kt deleted file mode 100644 index 103511b81..000000000 --- a/app/src/main/java/org/linphone/activities/main/dialer/viewmodels/ConfigFileViewModel.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.dialer.viewmodels - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import org.linphone.LinphoneApplication.Companion.coreContext - -class ConfigFileViewModel : ViewModel() { - val text = MutableLiveData() - - init { - text.value = coreContext.core.config.dump() - } -} diff --git a/app/src/main/java/org/linphone/activities/main/dialer/viewmodels/DialerViewModel.kt b/app/src/main/java/org/linphone/activities/main/dialer/viewmodels/DialerViewModel.kt deleted file mode 100644 index b931023d3..000000000 --- a/app/src/main/java/org/linphone/activities/main/dialer/viewmodels/DialerViewModel.kt +++ /dev/null @@ -1,249 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.dialer.viewmodels - -import android.content.Context -import android.os.Vibrator -import android.text.Editable -import android.widget.EditText -import androidx.lifecycle.MutableLiveData -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.activities.main.dialer.NumpadDigitListener -import org.linphone.activities.main.viewmodels.LogsUploadViewModel -import org.linphone.compatibility.Compatibility -import org.linphone.core.* -import org.linphone.core.tools.Log -import org.linphone.utils.Event -import org.linphone.utils.LinphoneUtils - -class DialerViewModel : LogsUploadViewModel() { - val enteredUri = MutableLiveData() - - val atLeastOneCall = MutableLiveData() - - val transferVisibility = MutableLiveData() - - val showPreview = MutableLiveData() - - val showSwitchCamera = MutableLiveData() - - val autoInitiateVideoCalls = MutableLiveData() - - val scheduleConferenceAvailable = MutableLiveData() - - val hideAddContactButton = MutableLiveData() - - val updateAvailableEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - private val vibrator = coreContext.context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator - - private var addressWaitingNetworkToBeCalled: String? = null - private var timeAtWitchWeTriedToCall: Long = 0 - - private var enteredUriCursorPosition: Int = 0 - - val onKeyClick: NumpadDigitListener = object : NumpadDigitListener { - override fun handleClick(key: Char) { - val sb: StringBuilder = StringBuilder(enteredUri.value) - try { - sb.insert(enteredUriCursorPosition, key.toString()) - } catch (ioobe: IndexOutOfBoundsException) { - sb.insert(sb.length, key.toString()) - } - enteredUri.value = sb.toString() - - if (vibrator.hasVibrator() && corePreferences.dtmfKeypadVibration) { - Compatibility.eventVibration(vibrator) - } - } - - override fun handleLongClick(key: Char): Boolean { - if (key == '1') { - val voiceMailUri = corePreferences.voiceMailUri - if (voiceMailUri != null) { - coreContext.startCall(voiceMailUri) - } - } else { - enteredUri.value += key.toString() - } - return true - } - } - - private val listener = object : CoreListenerStub() { - override fun onCallStateChanged( - core: Core, - call: Call, - state: Call.State, - message: String - ) { - atLeastOneCall.value = core.callsNb > 0 - } - - override fun onTransferStateChanged(core: Core, transfered: Call, callState: Call.State) { - if (callState == Call.State.OutgoingProgress) { - // Will work for both blind & attended transfer - onMessageToNotifyEvent.value = Event(org.linphone.R.string.dialer_transfer_succeded) - } - } - - override fun onNetworkReachable(core: Core, reachable: Boolean) { - val address = addressWaitingNetworkToBeCalled.orEmpty() - if (reachable && address.isNotEmpty()) { - val now = System.currentTimeMillis() - if (now - timeAtWitchWeTriedToCall > 1000) { - Log.e( - "[Dialer] More than 1 second has passed waiting for network, abort auto call to $address" - ) - enteredUri.value = address - } else { - Log.i("[Dialer] Network is available, continue auto call to $address") - coreContext.startCall(address) - } - - addressWaitingNetworkToBeCalled = null - timeAtWitchWeTriedToCall = 0 - } - } - - override fun onVersionUpdateCheckResultReceived( - core: Core, - result: VersionUpdateCheckResult, - version: String?, - url: String? - ) { - if (result == VersionUpdateCheckResult.NewVersionAvailable) { - Log.i("[Dialer] Update available, version [$version], url [$url]") - if (!url.isNullOrEmpty()) { - updateAvailableEvent.value = Event(url) - } - } - } - - override fun onAccountRegistrationStateChanged( - core: Core, - account: Account, - state: RegistrationState?, - message: String - ) { - scheduleConferenceAvailable.value = LinphoneUtils.isRemoteConferencingAvailable() - } - } - - init { - coreContext.core.addListener(listener) - - enteredUri.value = "" - atLeastOneCall.value = coreContext.core.callsNb > 0 - transferVisibility.value = false - hideAddContactButton.value = corePreferences.readOnlyNativeContacts - - showSwitchCamera.value = coreContext.showSwitchCameraButton() - scheduleConferenceAvailable.value = LinphoneUtils.isRemoteConferencingAvailable() - } - - override fun onCleared() { - coreContext.core.removeListener(listener) - - super.onCleared() - } - - // This is to workaround the cursor being set to the start when pressing a digit - fun onBeforeUriChanged(editText: EditText, count: Int, after: Int) { - enteredUriCursorPosition = editText.selectionEnd - enteredUriCursorPosition += after - count - } - - fun onAfterUriChanged(editText: EditText, editable: Editable?) { - val newLength = editable?.length ?: 0 - if (newLength <= enteredUriCursorPosition) enteredUriCursorPosition = newLength - if (enteredUriCursorPosition < 0) enteredUriCursorPosition = 0 - editText.setSelection(enteredUriCursorPosition) - } - - fun updateShowVideoPreview() { - val videoPreview = corePreferences.videoPreview - showPreview.value = videoPreview - coreContext.core.isVideoPreviewEnabled = videoPreview - } - - fun eraseLastChar() { - enteredUri.value = enteredUri.value?.dropLast(1) - } - - fun eraseAll(): Boolean { - enteredUri.value = "" - return true - } - - fun directCall(to: String) { - if (coreContext.core.isNetworkReachable) { - coreContext.startCall(to) - } else { - Log.w( - "[Dialer] Network isnt't reachable at the time, wait for network to start call (happens mainly when app is cold started)" - ) - timeAtWitchWeTriedToCall = System.currentTimeMillis() - addressWaitingNetworkToBeCalled = to - } - } - - fun startCall() { - val addressToCall = enteredUri.value.orEmpty() - if (addressToCall.isNotEmpty()) { - coreContext.startCall(addressToCall) - eraseAll() - } else { - setLastOutgoingCallAddress() - } - } - - fun transferCall(): Boolean { - val addressToCall = enteredUri.value.orEmpty() - return if (addressToCall.isNotEmpty()) { - transferCallTo(addressToCall) - eraseAll() - true - } else { - setLastOutgoingCallAddress() - false - } - } - - fun transferCallTo(addressToCall: String) { - if (!coreContext.transferCallTo(addressToCall)) { - onMessageToNotifyEvent.value = Event(org.linphone.R.string.dialer_transfer_failed) - } - } - - fun switchCamera() { - coreContext.switchCamera() - } - - private fun setLastOutgoingCallAddress() { - val callLog = coreContext.core.lastOutgoingCallLog - if (callLog != null) { - enteredUri.value = LinphoneUtils.getDisplayableAddress(callLog.remoteAddress) - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/files/adapters/PdfPagesListAdapter.kt b/app/src/main/java/org/linphone/activities/main/files/adapters/PdfPagesListAdapter.kt deleted file mode 100644 index cfb4b48a2..000000000 --- a/app/src/main/java/org/linphone/activities/main/files/adapters/PdfPagesListAdapter.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.files.adapters - -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView -import org.linphone.R -import org.linphone.activities.main.files.viewmodels.PdfFileViewModel - -class PdfPagesListAdapter(private val pdfViewModel: PdfFileViewModel) : RecyclerView.Adapter() { - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PdfPageViewHolder { - val view = LayoutInflater.from(parent.context).inflate( - R.layout.file_pdf_viewer_cell, - parent, - false - ) - return PdfPageViewHolder(view) - } - - override fun getItemCount(): Int { - return pdfViewModel.getPagesCount() - } - - override fun onBindViewHolder(holder: PdfPageViewHolder, position: Int) { - holder.bind(position) - } - - inner class PdfPageViewHolder(private val view: View) : RecyclerView.ViewHolder(view) { - fun bind(index: Int) { - pdfViewModel.loadPdfPageInto(index, view.findViewById(R.id.pdf_image)) - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/files/fragments/AudioViewerFragment.kt b/app/src/main/java/org/linphone/activities/main/files/fragments/AudioViewerFragment.kt deleted file mode 100644 index 8dd25f62d..000000000 --- a/app/src/main/java/org/linphone/activities/main/files/fragments/AudioViewerFragment.kt +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.files.fragments - -import android.annotation.SuppressLint -import android.os.Bundle -import android.view.KeyEvent -import android.view.View -import android.widget.MediaController -import androidx.lifecycle.ViewModelProvider -import androidx.navigation.fragment.findNavController -import org.linphone.R -import org.linphone.activities.main.files.viewmodels.AudioFileViewModel -import org.linphone.activities.main.files.viewmodels.AudioFileViewModelFactory -import org.linphone.core.tools.Log -import org.linphone.databinding.FileAudioViewerFragmentBinding - -class AudioViewerFragment : GenericViewerFragment() { - private lateinit var viewModel: AudioFileViewModel - - private lateinit var mediaController: MediaController - - override fun getLayoutId(): Int = R.layout.file_audio_viewer_fragment - - @SuppressLint("ClickableViewAccessibility") - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - val content = sharedViewModel.contentToOpen.value - if (content == null) { - Log.e("[Audio Viewer] Content is null, aborting!") - findNavController().navigateUp() - return - } - - viewModel = ViewModelProvider( - this, - AudioFileViewModelFactory(content) - )[AudioFileViewModel::class.java] - binding.viewModel = viewModel - - mediaController = object : MediaController(requireContext()) { - // This hack is even if media controller is showed with timeout=0 - // Once a control is touched, it will disappear 3 seconds later anyway - override fun show(timeout: Int) { - super.show(0) - } - - // This is to prevent the first back key press to only hide to media controls - override fun dispatchKeyEvent(event: KeyEvent?): Boolean { - if (event?.keyCode == KeyEvent.KEYCODE_BACK) { - goBack() - return true - } - return super.dispatchKeyEvent(event) - } - } - mediaController.setMediaPlayer(viewModel) - - viewModel.mediaPlayer.setOnPreparedListener { - mediaController.setAnchorView(binding.anchor) - // This will make the controls visible forever - mediaController.show(0) - } - } - - override fun onPause() { - mediaController.hide() - viewModel.mediaPlayer.pause() - - super.onPause() - } - - override fun onResume() { - super.onResume() - - mediaController.show(0) - } -} diff --git a/app/src/main/java/org/linphone/activities/main/files/fragments/GenericViewerFragment.kt b/app/src/main/java/org/linphone/activities/main/files/fragments/GenericViewerFragment.kt deleted file mode 100644 index 63ef1b4f5..000000000 --- a/app/src/main/java/org/linphone/activities/main/files/fragments/GenericViewerFragment.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.files.fragments - -import android.os.Bundle -import android.view.View -import androidx.databinding.ViewDataBinding -import androidx.navigation.fragment.findNavController -import org.linphone.R -import org.linphone.activities.main.fragments.SecureFragment -import org.linphone.core.tools.Log - -abstract class GenericViewerFragment : SecureFragment() { - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - isSecure = arguments?.getBoolean("Secure") ?: false - } - - override fun onStart() { - super.onStart() - - val content = sharedViewModel.contentToOpen.value - if (content == null) { - Log.e("[Generic Viewer] Content is null, aborting!") - findNavController().navigateUp() - return - } - - (childFragmentManager.findFragmentById(R.id.top_bar_fragment) as? TopBarFragment) - ?.setContent(content) - } -} diff --git a/app/src/main/java/org/linphone/activities/main/files/fragments/ImageViewerFragment.kt b/app/src/main/java/org/linphone/activities/main/files/fragments/ImageViewerFragment.kt deleted file mode 100644 index 4cf1dad5e..000000000 --- a/app/src/main/java/org/linphone/activities/main/files/fragments/ImageViewerFragment.kt +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.files.fragments - -import android.annotation.SuppressLint -import android.os.Bundle -import android.view.View -import androidx.lifecycle.ViewModelProvider -import androidx.navigation.fragment.findNavController -import org.linphone.R -import org.linphone.activities.main.MainActivity -import org.linphone.activities.main.files.viewmodels.ImageFileViewModel -import org.linphone.activities.main.files.viewmodels.ImageFileViewModelFactory -import org.linphone.compatibility.Compatibility -import org.linphone.core.tools.Log -import org.linphone.databinding.FileImageViewerFragmentBinding - -class ImageViewerFragment : GenericViewerFragment() { - private lateinit var viewModel: ImageFileViewModel - - override fun getLayoutId(): Int = R.layout.file_image_viewer_fragment - - @SuppressLint("ClickableViewAccessibility") - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - val content = sharedViewModel.contentToOpen.value - if (content == null) { - Log.e("[Image Viewer] Content is null, aborting!") - findNavController().navigateUp() - return - } - - viewModel = ViewModelProvider( - this, - ImageFileViewModelFactory(content) - )[ImageFileViewModel::class.java] - binding.viewModel = viewModel - - viewModel.fullScreenMode.observe( - viewLifecycleOwner - ) { hide -> - Compatibility.hideAndroidSystemUI(hide, requireActivity().window) - (requireActivity() as MainActivity).hideStatusFragment(hide) - } - } - - override fun onPause() { - super.onPause() - - viewModel.fullScreenMode.value = false - } -} diff --git a/app/src/main/java/org/linphone/activities/main/files/fragments/PdfViewerFragment.kt b/app/src/main/java/org/linphone/activities/main/files/fragments/PdfViewerFragment.kt deleted file mode 100644 index 2e123d104..000000000 --- a/app/src/main/java/org/linphone/activities/main/files/fragments/PdfViewerFragment.kt +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.files.fragments - -import android.os.Bundle -import android.view.View -import androidx.lifecycle.ViewModelProvider -import androidx.navigation.fragment.findNavController -import org.linphone.R -import org.linphone.activities.main.files.adapters.PdfPagesListAdapter -import org.linphone.activities.main.files.viewmodels.PdfFileViewModel -import org.linphone.activities.main.files.viewmodels.PdfFileViewModelFactory -import org.linphone.core.tools.Log -import org.linphone.databinding.FilePdfViewerFragmentBinding - -class PdfViewerFragment : GenericViewerFragment() { - private lateinit var viewModel: PdfFileViewModel - private lateinit var adapter: PdfPagesListAdapter - - override fun getLayoutId(): Int = R.layout.file_pdf_viewer_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - val content = sharedViewModel.contentToOpen.value - if (content == null) { - Log.e("[PDF Viewer] Content is null, aborting!") - findNavController().navigateUp() - return - } - - viewModel = ViewModelProvider( - this, - PdfFileViewModelFactory(content) - )[PdfFileViewModel::class.java] - binding.viewModel = viewModel - - viewModel.rendererReady.observe(viewLifecycleOwner) { - it.consume { - adapter = PdfPagesListAdapter(viewModel) - binding.pdfViewPager.adapter = adapter - } - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/files/fragments/TextViewerFragment.kt b/app/src/main/java/org/linphone/activities/main/files/fragments/TextViewerFragment.kt deleted file mode 100644 index 470ce50c1..000000000 --- a/app/src/main/java/org/linphone/activities/main/files/fragments/TextViewerFragment.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.files.fragments - -import android.os.Bundle -import android.view.View -import androidx.lifecycle.ViewModelProvider -import androidx.navigation.fragment.findNavController -import org.linphone.R -import org.linphone.activities.main.files.viewmodels.TextFileViewModel -import org.linphone.activities.main.files.viewmodels.TextFileViewModelFactory -import org.linphone.core.tools.Log -import org.linphone.databinding.FileTextViewerFragmentBinding - -class TextViewerFragment : GenericViewerFragment() { - private lateinit var viewModel: TextFileViewModel - - override fun getLayoutId(): Int = R.layout.file_text_viewer_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - val content = sharedViewModel.contentToOpen.value - if (content == null) { - Log.e("[Text Viewer] Content is null, aborting!") - findNavController().navigateUp() - return - } - - viewModel = ViewModelProvider( - this, - TextFileViewModelFactory(content) - )[TextFileViewModel::class.java] - binding.viewModel = viewModel - } -} diff --git a/app/src/main/java/org/linphone/activities/main/files/fragments/TopBarFragment.kt b/app/src/main/java/org/linphone/activities/main/files/fragments/TopBarFragment.kt deleted file mode 100644 index e956f4654..000000000 --- a/app/src/main/java/org/linphone/activities/main/files/fragments/TopBarFragment.kt +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.files.fragments - -import android.os.Bundle -import android.view.View -import android.webkit.MimeTypeMap -import androidx.lifecycle.lifecycleScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import org.linphone.R -import org.linphone.activities.GenericFragment -import org.linphone.activities.SnackBarActivity -import org.linphone.compatibility.Compatibility -import org.linphone.core.Content -import org.linphone.core.tools.Log -import org.linphone.databinding.FileViewerTopBarFragmentBinding -import org.linphone.utils.FileUtils -import org.linphone.utils.PermissionHelper - -class TopBarFragment : GenericFragment() { - private var content: Content? = null - private var plainFilePath: String = "" - - override fun getLayoutId(): Int = R.layout.file_viewer_top_bar_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - useMaterialSharedAxisXForwardAnimation = false - - binding.setExportClickListener { - val contentToExport = content - if (contentToExport != null) { - exportContent(contentToExport) - } else { - Log.e("[File Viewer] No Content set!") - } - } - } - - override fun onSaveInstanceState(outState: Bundle) { - outState.putString("FilePath", plainFilePath) - super.onSaveInstanceState(outState) - } - - override fun onViewStateRestored(savedInstanceState: Bundle?) { - super.onViewStateRestored(savedInstanceState) - plainFilePath = savedInstanceState?.getString("FilePath") ?: plainFilePath - } - - override fun onDestroyView() { - if (plainFilePath.isNotEmpty() && plainFilePath != content?.filePath.orEmpty()) { - Log.i("[File Viewer] Destroying plain file path: $plainFilePath") - FileUtils.deleteFile(plainFilePath) - } - super.onDestroyView() - } - - fun setContent(c: Content) { - Log.i("[File Viewer] Content file path is: ${c.filePath}") - content = c - binding.fileName.text = c.name - } - - private fun exportContent(content: Content) { - lifecycleScope.launch { - var mediaStoreFilePath = "" - if (PermissionHelper.get().hasWriteExternalStoragePermission()) { - val filePath = content.filePath.orEmpty() - Log.i("[File Viewer] Trying to export file [$filePath] through Media Store API") - - val extension = FileUtils.getExtensionFromFileName(filePath) - val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) - when (FileUtils.getMimeType(mime)) { - FileUtils.MimeType.Image -> { - val export = lifecycleScope.async { - Compatibility.addImageToMediaStore(requireContext(), content) - } - if (export.await()) { - Log.i( - "[File Viewer] Successfully exported image [${content.name}] to Media Store: ${content.userData}" - ) - mediaStoreFilePath = content.userData.toString() - } else { - Log.e( - "[File Viewer] Something went wrong while copying file to Media Store..." - ) - } - } - FileUtils.MimeType.Video -> { - val export = lifecycleScope.async { - Compatibility.addVideoToMediaStore(requireContext(), content) - } - if (export.await()) { - Log.i( - "[File Viewer] Successfully exported video [${content.name}] to Media Store: ${content.userData}" - ) - mediaStoreFilePath = content.userData.toString() - } else { - Log.e( - "[File Viewer] Something went wrong while copying file to Media Store..." - ) - } - } - FileUtils.MimeType.Audio -> { - val export = lifecycleScope.async { - Compatibility.addAudioToMediaStore(requireContext(), content) - } - if (export.await()) { - Log.i( - "[File Viewer] Successfully exported audio [${content.name}] to Media Store: ${content.userData}" - ) - mediaStoreFilePath = content.userData.toString() - } else { - Log.e( - "[File Viewer] Something went wrong while copying file to Media Store..." - ) - } - } - else -> { - Log.w( - "[File Viewer] File [${content.name}] isn't either an image, an audio file or a video, can't add it to the Media Store" - ) - } - } - } else { - Log.w( - "[File Viewer] Can't export image through Media Store API (requires Android 10 or WRITE_EXTERNAL permission, using fallback method..." - ) - } - - withContext(Dispatchers.Main) { - if (mediaStoreFilePath.isEmpty()) { - Log.w( - "[File Viewer] Media store file path is empty, media store export failed?" - ) - - val filePath = content.exportPlainFile().orEmpty() - plainFilePath = filePath.ifEmpty { content.filePath.orEmpty() } - Log.i("[File Viewer] Plain file path is: $plainFilePath") - if (plainFilePath.isNotEmpty()) { - if (!FileUtils.openFileInThirdPartyApp(requireActivity(), plainFilePath)) { - (requireActivity() as SnackBarActivity).showSnackBar( - R.string.chat_message_no_app_found_to_handle_file_mime_type - ) - if (plainFilePath != content.filePath.orEmpty()) { - Log.i( - "[File Viewer] No app to open plain file path [$plainFilePath], destroying it" - ) - FileUtils.deleteFile(plainFilePath) - } - plainFilePath = "" - } - } - } else { - plainFilePath = "" - Log.i("[File Viewer] Media store file path is: $mediaStoreFilePath") - FileUtils.openMediaStoreFile(requireActivity(), mediaStoreFilePath) - } - } - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/files/fragments/VideoViewerFragment.kt b/app/src/main/java/org/linphone/activities/main/files/fragments/VideoViewerFragment.kt deleted file mode 100644 index 1840b0f12..000000000 --- a/app/src/main/java/org/linphone/activities/main/files/fragments/VideoViewerFragment.kt +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.files.fragments - -import android.os.Bundle -import android.view.KeyEvent -import android.view.View -import android.widget.MediaController -import androidx.lifecycle.ViewModelProvider -import androidx.navigation.fragment.findNavController -import org.linphone.R -import org.linphone.activities.main.MainActivity -import org.linphone.activities.main.files.viewmodels.VideoFileViewModel -import org.linphone.activities.main.files.viewmodels.VideoFileViewModelFactory -import org.linphone.compatibility.Compatibility -import org.linphone.core.tools.Log -import org.linphone.databinding.FileVideoViewerFragmentBinding - -class VideoViewerFragment : GenericViewerFragment() { - private lateinit var viewModel: VideoFileViewModel - - private lateinit var mediaController: MediaController - - override fun getLayoutId(): Int = R.layout.file_video_viewer_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - val content = sharedViewModel.contentToOpen.value - if (content == null) { - Log.e("[Video Viewer] Content is null, aborting!") - findNavController().navigateUp() - return - } - - viewModel = ViewModelProvider( - this, - VideoFileViewModelFactory(content) - )[VideoFileViewModel::class.java] - binding.viewModel = viewModel - - mediaController = object : MediaController(requireContext()) { - // This is to prevent the first back key press to only hide to media controls - override fun dispatchKeyEvent(event: KeyEvent?): Boolean { - if (event?.keyCode == KeyEvent.KEYCODE_BACK) { - goBack() - return true - } - return super.dispatchKeyEvent(event) - } - } - initMediaController() - - viewModel.fullScreenMode.observe( - viewLifecycleOwner - ) { hide -> - Compatibility.hideAndroidSystemUI(hide, requireActivity().window) - (requireActivity() as MainActivity).hideStatusFragment(hide) - } - } - - override fun onResume() { - super.onResume() - - binding.videoView.start() - } - - override fun onPause() { - if (mediaController.isShowing) { - mediaController.hide() - } - - if (binding.videoView.isPlaying) { - binding.videoView.pause() - } - - viewModel.fullScreenMode.value = false - super.onPause() - } - - override fun onDestroyView() { - binding.videoView.stopPlayback() - - super.onDestroyView() - } - - private fun initMediaController() { - val videoView = binding.videoView - - videoView.setOnPreparedListener { mediaPlayer -> - mediaPlayer.setOnVideoSizeChangedListener { _, _, _ -> - videoView.setMediaController(mediaController) - // The following will make the video controls above the video - // mediaController.setAnchorView(videoView) - - // This will make the controls visible right away for 3 seconds - // If 0 as timeout, they will stay visible mediaController.hide() is called - mediaController.show() - } - } - - videoView.setOnErrorListener { _, what, extra -> - Log.e("[Video Viewer] Error: $what ($extra)") - false - } - - videoView.setVideoPath(viewModel.filePath) - } -} diff --git a/app/src/main/java/org/linphone/activities/main/files/viewmodels/AudioFileViewModel.kt b/app/src/main/java/org/linphone/activities/main/files/viewmodels/AudioFileViewModel.kt deleted file mode 100644 index 33e83b7d9..000000000 --- a/app/src/main/java/org/linphone/activities/main/files/viewmodels/AudioFileViewModel.kt +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.files.viewmodels - -import android.media.AudioAttributes -import android.media.MediaPlayer -import android.widget.MediaController -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import java.lang.IllegalStateException -import org.linphone.core.Content - -class AudioFileViewModelFactory(private val content: Content) : - ViewModelProvider.NewInstanceFactory() { - - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - return AudioFileViewModel(content) as T - } -} - -class AudioFileViewModel(content: Content) : FileViewerViewModel(content), MediaController.MediaPlayerControl { - val mediaPlayer = MediaPlayer() - - init { - mediaPlayer.apply { - setAudioAttributes( - AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).setUsage( - AudioAttributes.USAGE_MEDIA - ).build() - ) - setDataSource(filePath) - prepare() - start() - } - } - - override fun onCleared() { - mediaPlayer.release() - super.onCleared() - } - - override fun start() { - mediaPlayer.start() - } - - override fun pause() { - mediaPlayer.pause() - } - - override fun getDuration(): Int { - return mediaPlayer.duration - } - - override fun getCurrentPosition(): Int { - try { - return mediaPlayer.currentPosition - } catch (_: IllegalStateException) {} - return 0 - } - - override fun seekTo(pos: Int) { - mediaPlayer.seekTo(pos) - } - - override fun isPlaying(): Boolean { - return mediaPlayer.isPlaying - } - - override fun getBufferPercentage(): Int { - return 0 - } - - override fun canPause(): Boolean { - return true - } - - override fun canSeekBackward(): Boolean { - return true - } - - override fun canSeekForward(): Boolean { - return true - } - - override fun getAudioSessionId(): Int { - return mediaPlayer.audioSessionId - } -} diff --git a/app/src/main/java/org/linphone/activities/main/files/viewmodels/FileViewerViewModel.kt b/app/src/main/java/org/linphone/activities/main/files/viewmodels/FileViewerViewModel.kt deleted file mode 100644 index d9f46d86c..000000000 --- a/app/src/main/java/org/linphone/activities/main/files/viewmodels/FileViewerViewModel.kt +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.files.viewmodels - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import org.linphone.core.Content -import org.linphone.core.tools.Log -import org.linphone.utils.FileUtils - -open class FileViewerViewModel(val content: Content) : ViewModel() { - val filePath: String - private val deleteAfterUse: Boolean = content.isFileEncrypted - - val fullScreenMode = MutableLiveData() - - init { - filePath = if (deleteAfterUse) { - Log.i( - "[File Viewer] [VFS] Content is encrypted, requesting plain file path for file [${content.filePath}]" - ) - content.exportPlainFile() - } else { - content.filePath.orEmpty() - } - } - - override fun onCleared() { - if (deleteAfterUse) { - Log.i("[File Viewer] [VFS] Deleting temporary plain file [$filePath]") - FileUtils.deleteFile(filePath) - } - - super.onCleared() - } - - fun toggleFullScreen() { - fullScreenMode.value = fullScreenMode.value != true - } -} diff --git a/app/src/main/java/org/linphone/activities/main/files/viewmodels/ImageFileViewModel.kt b/app/src/main/java/org/linphone/activities/main/files/viewmodels/ImageFileViewModel.kt deleted file mode 100644 index bddf64d5e..000000000 --- a/app/src/main/java/org/linphone/activities/main/files/viewmodels/ImageFileViewModel.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.files.viewmodels - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import org.linphone.core.Content - -class ImageFileViewModelFactory(private val content: Content) : - ViewModelProvider.NewInstanceFactory() { - - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - return ImageFileViewModel(content) as T - } -} - -class ImageFileViewModel(content: Content) : FileViewerViewModel(content) diff --git a/app/src/main/java/org/linphone/activities/main/files/viewmodels/PdfFileViewModel.kt b/app/src/main/java/org/linphone/activities/main/files/viewmodels/PdfFileViewModel.kt deleted file mode 100644 index 259957fac..000000000 --- a/app/src/main/java/org/linphone/activities/main/files/viewmodels/PdfFileViewModel.kt +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.files.viewmodels - -import android.graphics.Bitmap -import android.graphics.pdf.PdfRenderer -import android.os.ParcelFileDescriptor -import android.widget.ImageView -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.viewModelScope -import java.io.File -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.core.Content -import org.linphone.core.tools.Log -import org.linphone.utils.Event - -class PdfFileViewModelFactory(private val content: Content) : - ViewModelProvider.NewInstanceFactory() { - - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - return PdfFileViewModel(content) as T - } -} - -class PdfFileViewModel(content: Content) : FileViewerViewModel(content) { - val operationInProgress = MutableLiveData() - - val rendererReady = MutableLiveData>() - - private lateinit var pdfRenderer: PdfRenderer - - init { - operationInProgress.value = false - - viewModelScope.launch { - withContext(Dispatchers.IO) { - val input = ParcelFileDescriptor.open( - File(filePath), - ParcelFileDescriptor.MODE_READ_ONLY - ) - pdfRenderer = PdfRenderer(input) - Log.i("[PDF Viewer] ${pdfRenderer.pageCount} pages in file $filePath") - rendererReady.postValue(Event(true)) - } - } - } - - override fun onCleared() { - if (this::pdfRenderer.isInitialized) { - pdfRenderer.close() - } - super.onCleared() - } - - fun getPagesCount(): Int { - if (this::pdfRenderer.isInitialized) { - return pdfRenderer.pageCount - } - return 0 - } - - fun loadPdfPageInto(index: Int, view: ImageView) { - viewModelScope.launch { - withContext(Dispatchers.IO) { - try { - operationInProgress.postValue(true) - - val page: PdfRenderer.Page = pdfRenderer.openPage(index) - val width = - if (coreContext.screenWidth <= coreContext.screenHeight) coreContext.screenWidth else coreContext.screenHeight - val bm = Bitmap.createBitmap( - width.toInt(), - (width / page.width * page.height).toInt(), - Bitmap.Config.ARGB_8888 - ) - page.render(bm, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY) - page.close() - - withContext(Dispatchers.Main) { - view.setImageBitmap(bm) - } - - operationInProgress.postValue(false) - } catch (e: Exception) { - Log.e("[PDF Viewer] Exception: $e") - operationInProgress.postValue(false) - } - } - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/files/viewmodels/TextFileViewModel.kt b/app/src/main/java/org/linphone/activities/main/files/viewmodels/TextFileViewModel.kt deleted file mode 100644 index 56aa44dbc..000000000 --- a/app/src/main/java/org/linphone/activities/main/files/viewmodels/TextFileViewModel.kt +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.files.viewmodels - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.viewModelScope -import java.io.BufferedReader -import java.io.FileReader -import java.lang.StringBuilder -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import org.linphone.core.Content -import org.linphone.core.tools.Log - -class TextFileViewModelFactory(private val content: Content) : - ViewModelProvider.NewInstanceFactory() { - - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - return TextFileViewModel(content) as T - } -} - -class TextFileViewModel(content: Content) : FileViewerViewModel(content) { - val operationInProgress = MutableLiveData() - - val text = MutableLiveData() - - init { - viewModelScope.launch { - withContext(Dispatchers.IO) { - try { - operationInProgress.postValue(true) - val br = BufferedReader(FileReader(filePath)) - var line: String? - val textBuilder = StringBuilder() - while (br.readLine().also { line = it } != null) { - textBuilder.append(line) - textBuilder.append('\n') - } - br.close() - - text.postValue(textBuilder.toString()) - operationInProgress.postValue(false) - } catch (e: Exception) { - Log.e("[Text Viewer] Exception: $e") - operationInProgress.postValue(false) - } - } - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/files/viewmodels/VideoFileViewModel.kt b/app/src/main/java/org/linphone/activities/main/files/viewmodels/VideoFileViewModel.kt deleted file mode 100644 index b9510c0cd..000000000 --- a/app/src/main/java/org/linphone/activities/main/files/viewmodels/VideoFileViewModel.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.files.viewmodels - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import org.linphone.core.Content - -class VideoFileViewModelFactory(private val content: Content) : - ViewModelProvider.NewInstanceFactory() { - - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - return VideoFileViewModel(content) as T - } -} - -class VideoFileViewModel(content: Content) : FileViewerViewModel(content) diff --git a/app/src/main/java/org/linphone/activities/main/fragments/EmptyFragment.kt b/app/src/main/java/org/linphone/activities/main/fragments/EmptyFragment.kt deleted file mode 100644 index b9fcfe1cb..000000000 --- a/app/src/main/java/org/linphone/activities/main/fragments/EmptyFragment.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.fragments - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import com.google.android.material.transition.MaterialSharedAxis -import org.linphone.R - -class EmptyFragment : Fragment() { - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) - returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) - return inflater.inflate(R.layout.empty_fragment, container, false) - } -} diff --git a/app/src/main/java/org/linphone/activities/main/fragments/ListTopBarFragment.kt b/app/src/main/java/org/linphone/activities/main/fragments/ListTopBarFragment.kt deleted file mode 100644 index d18ed62a7..000000000 --- a/app/src/main/java/org/linphone/activities/main/fragments/ListTopBarFragment.kt +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.fragments - -import android.os.Bundle -import android.view.View -import androidx.lifecycle.ViewModelProvider -import org.linphone.R -import org.linphone.activities.GenericFragment -import org.linphone.activities.main.viewmodels.ListTopBarViewModel -import org.linphone.databinding.ListEditTopBarFragmentBinding -import org.linphone.utils.Event - -class ListTopBarFragment : GenericFragment() { - private lateinit var viewModel: ListTopBarViewModel - - override fun getLayoutId(): Int = R.layout.list_edit_top_bar_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - useMaterialSharedAxisXForwardAnimation = false - - viewModel = ViewModelProvider(parentFragment ?: this)[ListTopBarViewModel::class.java] - binding.viewModel = viewModel - - binding.setCancelClickListener { - viewModel.isEditionEnabled.value = false - } - - binding.setSelectAllClickListener { - viewModel.selectAllEvent.value = Event(true) - } - - binding.setUnSelectAllClickListener { - viewModel.unSelectAllEvent.value = Event(true) - } - - binding.setDeleteClickListener { - viewModel.deleteSelectionEvent.value = Event(true) - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/fragments/MasterFragment.kt b/app/src/main/java/org/linphone/activities/main/fragments/MasterFragment.kt deleted file mode 100644 index b38de9c08..000000000 --- a/app/src/main/java/org/linphone/activities/main/fragments/MasterFragment.kt +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.fragments - -import android.app.Dialog -import android.os.Bundle -import android.view.View -import androidx.activity.OnBackPressedCallback -import androidx.core.view.doOnPreDraw -import androidx.databinding.ViewDataBinding -import androidx.lifecycle.ViewModelProvider -import androidx.slidingpanelayout.widget.SlidingPaneLayout -import org.linphone.R -import org.linphone.activities.main.adapters.SelectionListAdapter -import org.linphone.activities.main.viewmodels.DialogViewModel -import org.linphone.activities.main.viewmodels.ListTopBarViewModel -import org.linphone.core.tools.Log -import org.linphone.utils.AppUtils -import org.linphone.utils.DialogUtils -import org.linphone.utils.hideKeyboard - -/** - * This fragment can be inherited by all fragments that will display a list - * where items can be selected for removal through the ListTopBarFragment - */ -abstract class MasterFragment> : SecureFragment() { - protected var _adapter: U? = null - protected val adapter: U - get() { - if (_adapter == null) { - Log.e("[Master Fragment] Attempting to get a null adapter!") - } - return _adapter!! - } - - protected lateinit var listSelectionViewModel: ListTopBarViewModel - protected open val dialogConfirmationMessageBeforeRemoval: Int = R.plurals.dialog_default_delete - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - // List selection - listSelectionViewModel = ViewModelProvider(this)[ListTopBarViewModel::class.java] - - listSelectionViewModel.isEditionEnabled.observe( - viewLifecycleOwner - ) { - if (!it) listSelectionViewModel.onUnSelectAll() - } - - listSelectionViewModel.selectAllEvent.observe( - viewLifecycleOwner - ) { - it.consume { - listSelectionViewModel.onSelectAll(getItemCount() - 1) - } - } - - listSelectionViewModel.unSelectAllEvent.observe( - viewLifecycleOwner - ) { - it.consume { - listSelectionViewModel.onUnSelectAll() - } - } - - listSelectionViewModel.deleteSelectionEvent.observe( - viewLifecycleOwner - ) { - it.consume { - val confirmationDialog = AppUtils.getStringWithPlural( - dialogConfirmationMessageBeforeRemoval, - listSelectionViewModel.selectedItems.value.orEmpty().size - ) - val viewModel = DialogViewModel(confirmationDialog) - val dialog: Dialog = DialogUtils.getDialog(requireContext(), viewModel) - - viewModel.showCancelButton { - dialog.dismiss() - listSelectionViewModel.isEditionEnabled.value = false - } - - viewModel.showDeleteButton( - { - delete() - dialog.dismiss() - listSelectionViewModel.isEditionEnabled.value = false - }, - getString(R.string.dialog_delete) - ) - - dialog.show() - } - } - } - - fun setUpSlidingPane(slidingPane: SlidingPaneLayout) { - binding.root.doOnPreDraw { - sharedViewModel.isSlidingPaneSlideable.value = slidingPane.isSlideable - - requireActivity().onBackPressedDispatcher.addCallback( - viewLifecycleOwner, - SlidingPaneBackPressedCallback(slidingPane) - ) - } - - slidingPane.lockMode = SlidingPaneLayout.LOCK_MODE_LOCKED - } - - private fun delete() { - val list = listSelectionViewModel.selectedItems.value ?: arrayListOf() - deleteItems(list) - } - - private fun getItemCount(): Int { - return adapter.itemCount - } - - abstract fun deleteItems(indexesOfItemToDelete: ArrayList) - - class SlidingPaneBackPressedCallback(private val slidingPaneLayout: SlidingPaneLayout) : - OnBackPressedCallback( - slidingPaneLayout.isSlideable && slidingPaneLayout.isOpen - ), - SlidingPaneLayout.PanelSlideListener { - - init { - Log.d( - "[Master Fragment] SlidingPane isSlideable = ${slidingPaneLayout.isSlideable}, isOpen = ${slidingPaneLayout.isOpen}" - ) - slidingPaneLayout.addPanelSlideListener(this) - } - - override fun handleOnBackPressed() { - Log.d("[Master Fragment] handleOnBackPressed, closing sliding pane") - slidingPaneLayout.hideKeyboard() - slidingPaneLayout.closePane() - } - - override fun onPanelOpened(panel: View) { - Log.d("[Master Fragment] onPanelOpened") - isEnabled = true - } - - override fun onPanelClosed(panel: View) { - Log.d("[Master Fragment] onPanelClosed") - isEnabled = false - } - - override fun onPanelSlide(panel: View, slideOffset: Float) { } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/fragments/SecureFragment.kt b/app/src/main/java/org/linphone/activities/main/fragments/SecureFragment.kt deleted file mode 100644 index 7cc660755..000000000 --- a/app/src/main/java/org/linphone/activities/main/fragments/SecureFragment.kt +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.fragments - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.view.WindowManager -import androidx.core.view.ViewCompat -import androidx.databinding.ViewDataBinding -import androidx.lifecycle.lifecycleScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.activities.GenericFragment -import org.linphone.core.tools.Log - -abstract class SecureFragment : GenericFragment() { - protected var isSecure: Boolean = false - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - // Assume we might want to be secure to prevent quick visible blink while screen recording. - enableSecureMode(true) - return super.onCreateView(inflater, container, savedInstanceState) - } - - override fun onResume() { - if (isSecure) { - enableSecureMode(true) - } else { - // This is a workaround to prevent a small blink showing the previous secured screen - lifecycleScope.launch { - withContext(Dispatchers.Main) { - delay(200) - enableSecureMode(isSecure) - } - } - } - super.onResume() - } - - private fun enableSecureMode(enable: Boolean) { - if (corePreferences.disableSecureMode) { - Log.d("[Secure Fragment] Disabling secure flag on window due to setting") - return - } - - Log.d("[Secure Fragment] ${if (enable) "Enabling" else "Disabling"} secure flag on window") - val window = requireActivity().window - val windowManager = requireActivity().windowManager - - val flags: Int = window.attributes.flags - if ((enable && flags and WindowManager.LayoutParams.FLAG_SECURE != 0) || - (!enable && flags and WindowManager.LayoutParams.FLAG_SECURE == 0) - ) { - Log.d( - "[Secure Fragment] Secure flag is already ${if (enable) "enabled" else "disabled"}, skipping..." - ) - return - } - - if (enable) { - window.addFlags(WindowManager.LayoutParams.FLAG_SECURE) - } else { - window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE) - } - - if (ViewCompat.isAttachedToWindow(window.decorView)) { - Log.d("[Secure Fragment] Redrawing window decorView to apply flag") - try { - windowManager.updateViewLayout(window.decorView, window.attributes) - } catch (ise: IllegalStateException) { - Log.e("[Secure Fragment] Failed to update view layout: $ise") - } - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/fragments/StatusFragment.kt b/app/src/main/java/org/linphone/activities/main/fragments/StatusFragment.kt deleted file mode 100644 index 50b357198..000000000 --- a/app/src/main/java/org/linphone/activities/main/fragments/StatusFragment.kt +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.fragments - -import android.os.Bundle -import android.view.View -import androidx.lifecycle.ViewModelProvider -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R -import org.linphone.activities.GenericFragment -import org.linphone.activities.main.viewmodels.StatusViewModel -import org.linphone.core.tools.Log -import org.linphone.databinding.StatusFragmentBinding -import org.linphone.utils.Event - -class StatusFragment : GenericFragment() { - private lateinit var viewModel: StatusViewModel - - override fun getLayoutId(): Int = R.layout.status_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - useMaterialSharedAxisXForwardAnimation = false - - viewModel = ViewModelProvider(this)[StatusViewModel::class.java] - binding.viewModel = viewModel - - sharedViewModel.accountRemoved.observe( - viewLifecycleOwner - ) { - Log.i("[Status Fragment] An account was removed, update default account state") - val defaultAccount = coreContext.core.defaultAccount - if (defaultAccount != null) { - viewModel.updateDefaultAccountRegistrationStatus(defaultAccount.state) - } - } - - binding.setMenuClickListener { - sharedViewModel.toggleDrawerEvent.value = Event(true) - } - - binding.setRefreshClickListener { - viewModel.refreshRegister() - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/fragments/TabsFragment.kt b/app/src/main/java/org/linphone/activities/main/fragments/TabsFragment.kt deleted file mode 100644 index b62708eb3..000000000 --- a/app/src/main/java/org/linphone/activities/main/fragments/TabsFragment.kt +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.fragments - -import android.os.Bundle -import android.view.View -import androidx.lifecycle.ViewModelProvider -import androidx.navigation.NavController -import androidx.navigation.NavDestination -import androidx.navigation.fragment.findNavController -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.GenericFragment -import org.linphone.activities.main.viewmodels.TabsViewModel -import org.linphone.activities.navigateToCallHistory -import org.linphone.activities.navigateToChatRooms -import org.linphone.activities.navigateToContacts -import org.linphone.activities.navigateToDialer -import org.linphone.databinding.TabsFragmentBinding -import org.linphone.utils.Event - -class TabsFragment : GenericFragment(), NavController.OnDestinationChangedListener { - private lateinit var viewModel: TabsViewModel - - override fun getLayoutId(): Int = R.layout.tabs_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - useMaterialSharedAxisXForwardAnimation = false - - viewModel = requireActivity().run { - ViewModelProvider(this)[TabsViewModel::class.java] - } - binding.viewModel = viewModel - - binding.setHistoryClickListener { - when (findNavController().currentDestination?.id) { - R.id.masterContactsFragment -> sharedViewModel.updateContactsAnimationsBasedOnDestination.value = Event( - R.id.masterCallLogsFragment - ) - R.id.dialerFragment -> sharedViewModel.updateDialerAnimationsBasedOnDestination.value = Event( - R.id.masterCallLogsFragment - ) - } - navigateToCallHistory() - } - - binding.setContactsClickListener { - when (findNavController().currentDestination?.id) { - R.id.dialerFragment -> sharedViewModel.updateDialerAnimationsBasedOnDestination.value = Event( - R.id.masterContactsFragment - ) - } - sharedViewModel.updateContactsAnimationsBasedOnDestination.value = Event( - findNavController().currentDestination?.id ?: -1 - ) - navigateToContacts() - } - - binding.setDialerClickListener { - when (findNavController().currentDestination?.id) { - R.id.masterContactsFragment -> sharedViewModel.updateContactsAnimationsBasedOnDestination.value = Event( - R.id.dialerFragment - ) - } - sharedViewModel.updateDialerAnimationsBasedOnDestination.value = Event( - findNavController().currentDestination?.id ?: -1 - ) - navigateToDialer() - } - - binding.setChatClickListener { - when (findNavController().currentDestination?.id) { - R.id.masterContactsFragment -> sharedViewModel.updateContactsAnimationsBasedOnDestination.value = Event( - R.id.masterChatRoomsFragment - ) - R.id.dialerFragment -> sharedViewModel.updateDialerAnimationsBasedOnDestination.value = Event( - R.id.masterChatRoomsFragment - ) - } - navigateToChatRooms() - } - } - - override fun onStart() { - super.onStart() - findNavController().addOnDestinationChangedListener(this) - } - - override fun onStop() { - findNavController().removeOnDestinationChangedListener(this) - super.onStop() - } - - override fun onDestinationChanged( - controller: NavController, - destination: NavDestination, - arguments: Bundle? - ) { - if (corePreferences.enableAnimations) { - when (destination.id) { - R.id.masterCallLogsFragment -> binding.motionLayout.transitionToState( - R.id.call_history - ) - R.id.masterContactsFragment -> binding.motionLayout.transitionToState(R.id.contacts) - R.id.dialerFragment -> binding.motionLayout.transitionToState(R.id.dialer) - R.id.masterChatRoomsFragment -> binding.motionLayout.transitionToState( - R.id.chat_rooms - ) - } - } else { - when (destination.id) { - R.id.masterCallLogsFragment -> binding.motionLayout.setTransition( - R.id.call_history, - R.id.call_history - ) - R.id.masterContactsFragment -> binding.motionLayout.setTransition( - R.id.contacts, - R.id.contacts - ) - R.id.dialerFragment -> binding.motionLayout.setTransition(R.id.dialer, R.id.dialer) - R.id.masterChatRoomsFragment -> binding.motionLayout.setTransition( - R.id.chat_rooms, - R.id.chat_rooms - ) - } - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/history/adapters/CallLogsListAdapter.kt b/app/src/main/java/org/linphone/activities/main/history/adapters/CallLogsListAdapter.kt deleted file mode 100644 index 4c20b767e..000000000 --- a/app/src/main/java/org/linphone/activities/main/history/adapters/CallLogsListAdapter.kt +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.history.adapters - -import android.content.Context -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.databinding.DataBindingUtil -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.MutableLiveData -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.RecyclerView -import org.linphone.R -import org.linphone.activities.main.adapters.SelectionListAdapter -import org.linphone.activities.main.history.data.GroupedCallLogData -import org.linphone.activities.main.viewmodels.ListTopBarViewModel -import org.linphone.databinding.GenericListHeaderBinding -import org.linphone.databinding.HistoryListCellBinding -import org.linphone.utils.* - -class CallLogsListAdapter( - selectionVM: ListTopBarViewModel, - private val viewLifecycleOwner: LifecycleOwner -) : SelectionListAdapter( - selectionVM, - CallLogDiffCallback() -), - HeaderAdapter { - val selectedCallLogEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val startCallToEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { - val binding: HistoryListCellBinding = DataBindingUtil.inflate( - LayoutInflater.from(parent.context), - R.layout.history_list_cell, - parent, - false - ) - return ViewHolder(binding) - } - - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - (holder as ViewHolder).bind(getItem(position)) - } - - inner class ViewHolder( - val binding: HistoryListCellBinding - ) : RecyclerView.ViewHolder(binding.root) { - fun bind(callLogGroup: GroupedCallLogData) { - with(binding) { - val callLogViewModel = callLogGroup.lastCallLogViewModel - viewModel = callLogViewModel - - lifecycleOwner = viewLifecycleOwner - - // This is for item selection through ListTopBarFragment - selectionListViewModel = selectionViewModel - selectionViewModel.isEditionEnabled.observe( - viewLifecycleOwner - ) { - position = bindingAdapterPosition - } - - setClickListener { - if (selectionViewModel.isEditionEnabled.value == true) { - selectionViewModel.onToggleSelect(bindingAdapterPosition) - } else { - startCallToEvent.value = Event(callLogGroup) - } - } - - setLongClickListener { - if (selectionViewModel.isEditionEnabled.value == false) { - selectionViewModel.isEditionEnabled.value = true - // Selection will be handled by click listener - true - } - false - } - - // This listener is disabled when in edition mode - setDetailsClickListener { - selectedCallLogEvent.value = Event(callLogGroup) - } - - groupCount = callLogGroup.callLogs.size - - executePendingBindings() - } - } - } - - override fun displayHeaderForPosition(position: Int): Boolean { - if (position >= itemCount) return false - val callLogGroup = getItem(position) - val date = callLogGroup.lastCallLogStartTimestamp - val previousPosition = position - 1 - return if (previousPosition >= 0) { - val previousItemDate = getItem(previousPosition).lastCallLogStartTimestamp - !TimestampUtils.isSameDay(date, previousItemDate) - } else { - true - } - } - - override fun getHeaderViewForPosition(context: Context, position: Int): View { - val callLog = getItem(position) - val date = formatDate(context, callLog.lastCallLogStartTimestamp) - val binding: GenericListHeaderBinding = DataBindingUtil.inflate( - LayoutInflater.from(context), - R.layout.generic_list_header, - null, - false - ) - binding.title = date - binding.executePendingBindings() - return binding.root - } - - private fun formatDate(context: Context, date: Long): String { - if (TimestampUtils.isToday(date)) { - return context.getString(R.string.today) - } else if (TimestampUtils.isYesterday(date)) { - return context.getString(R.string.yesterday) - } - return TimestampUtils.toString(date, onlyDate = true, shortDate = false, hideYear = false) - } -} - -private class CallLogDiffCallback : DiffUtil.ItemCallback() { - override fun areItemsTheSame( - oldItem: GroupedCallLogData, - newItem: GroupedCallLogData - ): Boolean { - return oldItem.lastCallLogId == newItem.lastCallLogId - } - - override fun areContentsTheSame( - oldItem: GroupedCallLogData, - newItem: GroupedCallLogData - ): Boolean { - return oldItem.callLogs.size == newItem.callLogs.size - } -} diff --git a/app/src/main/java/org/linphone/activities/main/history/data/GroupedCallLogData.kt b/app/src/main/java/org/linphone/activities/main/history/data/GroupedCallLogData.kt deleted file mode 100644 index 3fa709f43..000000000 --- a/app/src/main/java/org/linphone/activities/main/history/data/GroupedCallLogData.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.history.data - -import org.linphone.activities.main.history.viewmodels.CallLogViewModel -import org.linphone.core.CallLog - -class GroupedCallLogData(callLog: CallLog) { - val callLogs = arrayListOf(callLog) - - var lastCallLog: CallLog = callLog - var lastCallLogId: String? = callLog.callId - var lastCallLogStartTimestamp: Long = callLog.startDate - val lastCallLogViewModel: CallLogViewModel - get() { - if (::_lastCallLogViewModel.isInitialized) { - return _lastCallLogViewModel - } - _lastCallLogViewModel = CallLogViewModel(lastCallLog) - return _lastCallLogViewModel - } - - private lateinit var _lastCallLogViewModel: CallLogViewModel - - fun destroy() { - if (::_lastCallLogViewModel.isInitialized) { - lastCallLogViewModel - } - } - - fun updateLastCallLog(callLog: CallLog) { - lastCallLog = callLog - lastCallLogId = callLog.callId - lastCallLogStartTimestamp = callLog.startDate - } -} diff --git a/app/src/main/java/org/linphone/activities/main/history/fragments/DetailCallLogFragment.kt b/app/src/main/java/org/linphone/activities/main/history/fragments/DetailCallLogFragment.kt deleted file mode 100644 index 8bb760ec9..000000000 --- a/app/src/main/java/org/linphone/activities/main/history/fragments/DetailCallLogFragment.kt +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.history.fragments - -import android.os.Bundle -import android.view.View -import androidx.navigation.fragment.findNavController -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R -import org.linphone.activities.* -import org.linphone.activities.main.* -import org.linphone.activities.main.history.viewmodels.CallLogViewModel -import org.linphone.activities.navigateToContacts -import org.linphone.core.tools.Log -import org.linphone.databinding.HistoryDetailFragmentBinding -import org.linphone.utils.Event - -class DetailCallLogFragment : GenericFragment() { - private lateinit var viewModel: CallLogViewModel - - override fun getLayoutId(): Int = R.layout.history_detail_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - binding.sharedMainViewModel = sharedViewModel - - val callLogGroup = sharedViewModel.selectedCallLogGroup.value - if (callLogGroup == null) { - Log.e("[History] Call log group is null, aborting!") - findNavController().navigateUp() - return - } - - viewModel = callLogGroup.lastCallLogViewModel - binding.viewModel = viewModel - if (viewModel.relatedCallLogs.value.orEmpty().isEmpty()) { - viewModel.addRelatedCallLogs(callLogGroup.callLogs) - } - - useMaterialSharedAxisXForwardAnimation = sharedViewModel.isSlidingPaneSlideable.value == false - - binding.setNewContactClickListener { - val copy = viewModel.callLog.remoteAddress.clone() - copy.clean() - val address = copy.asStringUriOnly() - Log.i("[History] Creating contact with SIP URI [$address]") - sharedViewModel.updateContactsAnimationsBasedOnDestination.value = Event( - R.id.masterCallLogsFragment - ) - navigateToContacts(address) - } - - binding.setContactClickListener { - sharedViewModel.updateContactsAnimationsBasedOnDestination.value = Event( - R.id.masterCallLogsFragment - ) - val contactId = viewModel.contact.value?.refKey - if (contactId != null) { - Log.i("[History] Displaying native contact [$contactId]") - navigateToNativeContact(contactId) - } else { - val copy = viewModel.callLog.remoteAddress.clone() - copy.clean() - val address = copy.asStringUriOnly() - Log.i("[History] Displaying friend with address [$address]") - navigateToFriend(address) - } - } - - viewModel.startCallEvent.observe( - viewLifecycleOwner - ) { - it.consume { callLog -> - // To remove the GRUU if any - val address = callLog.remoteAddress.clone() - address.clean() - - if (coreContext.core.callsNb > 0) { - Log.i( - "[History] Starting dialer with pre-filled URI [${address.asStringUriOnly()}], is transfer? ${sharedViewModel.pendingCallTransfer}" - ) - sharedViewModel.updateDialerAnimationsBasedOnDestination.value = - Event(R.id.masterCallLogsFragment) - - val args = Bundle() - args.putString("URI", address.asStringUriOnly()) - args.putBoolean("Transfer", sharedViewModel.pendingCallTransfer) - args.putBoolean( - "SkipAutoCallStart", - true - ) // If auto start call setting is enabled, ignore it - navigateToDialer(args) - } else { - val localAddress = callLog.localAddress - Log.i( - "[History] Starting call to ${address.asStringUriOnly()} with local address ${localAddress.asStringUriOnly()}" - ) - coreContext.startCall(address, localAddress = localAddress) - } - } - } - - viewModel.chatRoomCreatedEvent.observe( - viewLifecycleOwner - ) { - it.consume { chatRoom -> - val args = Bundle() - args.putString("LocalSipUri", chatRoom.localAddress.asStringUriOnly()) - args.putString("RemoteSipUri", chatRoom.peerAddress.asStringUriOnly()) - navigateToChatRoom(args) - } - } - - viewModel.onMessageToNotifyEvent.observe( - viewLifecycleOwner - ) { - it.consume { messageResourceId -> - (activity as MainActivity).showSnackBar(messageResourceId) - } - } - } - - override fun onResume() { - super.onResume() - viewModel.enableListener(true) - } - - override fun onPause() { - viewModel.enableListener(false) - super.onPause() - } -} diff --git a/app/src/main/java/org/linphone/activities/main/history/fragments/DetailConferenceCallLogFragment.kt b/app/src/main/java/org/linphone/activities/main/history/fragments/DetailConferenceCallLogFragment.kt deleted file mode 100644 index 984a8eac7..000000000 --- a/app/src/main/java/org/linphone/activities/main/history/fragments/DetailConferenceCallLogFragment.kt +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.history.fragments - -import android.os.Bundle -import android.view.View -import androidx.navigation.fragment.findNavController -import org.linphone.R -import org.linphone.activities.* -import org.linphone.activities.main.* -import org.linphone.activities.main.history.viewmodels.CallLogViewModel -import org.linphone.core.tools.Log -import org.linphone.databinding.HistoryConfDetailFragmentBinding - -class DetailConferenceCallLogFragment : GenericFragment() { - private lateinit var viewModel: CallLogViewModel - - override fun getLayoutId(): Int = R.layout.history_conf_detail_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - binding.sharedMainViewModel = sharedViewModel - - val callLogGroup = sharedViewModel.selectedCallLogGroup.value - if (callLogGroup == null) { - Log.e("[History] Call log group is null, aborting!") - findNavController().navigateUp() - return - } - - viewModel = callLogGroup.lastCallLogViewModel - binding.viewModel = viewModel - viewModel.addRelatedCallLogs(callLogGroup.callLogs) - - useMaterialSharedAxisXForwardAnimation = sharedViewModel.isSlidingPaneSlideable.value == false - - viewModel.onMessageToNotifyEvent.observe( - viewLifecycleOwner - ) { - it.consume { messageResourceId -> - (activity as MainActivity).showSnackBar(messageResourceId) - } - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/history/fragments/MasterCallLogsFragment.kt b/app/src/main/java/org/linphone/activities/main/history/fragments/MasterCallLogsFragment.kt deleted file mode 100644 index c10985411..000000000 --- a/app/src/main/java/org/linphone/activities/main/history/fragments/MasterCallLogsFragment.kt +++ /dev/null @@ -1,314 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.history.fragments - -import android.app.Dialog -import android.content.res.Configuration -import android.os.Bundle -import android.view.View -import androidx.core.content.ContextCompat -import androidx.lifecycle.ViewModelProvider -import androidx.navigation.fragment.NavHostFragment -import androidx.recyclerview.widget.ItemTouchHelper -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import com.google.android.material.transition.MaterialSharedAxis -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.* -import org.linphone.activities.clearDisplayedCallHistory -import org.linphone.activities.main.MainActivity -import org.linphone.activities.main.fragments.MasterFragment -import org.linphone.activities.main.history.adapters.CallLogsListAdapter -import org.linphone.activities.main.history.data.GroupedCallLogData -import org.linphone.activities.main.history.viewmodels.CallLogsListViewModel -import org.linphone.activities.main.viewmodels.DialogViewModel -import org.linphone.activities.main.viewmodels.TabsViewModel -import org.linphone.activities.navigateToCallHistory -import org.linphone.activities.navigateToConferenceCallHistory -import org.linphone.activities.navigateToDialer -import org.linphone.core.ConferenceInfo -import org.linphone.core.tools.Log -import org.linphone.databinding.HistoryMasterFragmentBinding -import org.linphone.utils.* - -class MasterCallLogsFragment : MasterFragment() { - override val dialogConfirmationMessageBeforeRemoval = R.plurals.history_delete_dialog - private lateinit var listViewModel: CallLogsListViewModel - - private val observer = object : RecyclerView.AdapterDataObserver() { - override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { - if (positionStart == 0 && itemCount == 1) { - scrollToTop() - } - } - } - - override fun getLayoutId(): Int = R.layout.history_master_fragment - - override fun onDestroyView() { - binding.callLogsList.adapter = null - adapter.unregisterAdapterDataObserver(observer) - super.onDestroyView() - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - useMaterialSharedAxisXForwardAnimation = false - - if (corePreferences.enableAnimations) { - val portraitOrientation = resources.configuration.orientation != Configuration.ORIENTATION_LANDSCAPE - val axis = if (portraitOrientation) MaterialSharedAxis.X else MaterialSharedAxis.Y - enterTransition = MaterialSharedAxis(axis, false) - reenterTransition = MaterialSharedAxis(axis, false) - returnTransition = MaterialSharedAxis(axis, true) - exitTransition = MaterialSharedAxis(axis, true) - } - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - listViewModel = ViewModelProvider(this)[CallLogsListViewModel::class.java] - binding.viewModel = listViewModel - - /* Shared view model & sliding pane related */ - - setUpSlidingPane(binding.slidingPane) - - sharedViewModel.layoutChangedEvent.observe( - viewLifecycleOwner - ) { - it.consume { - sharedViewModel.isSlidingPaneSlideable.value = binding.slidingPane.isSlideable - if (binding.slidingPane.isSlideable) { - val navHostFragment = - childFragmentManager.findFragmentById(R.id.history_nav_container) as NavHostFragment - if (navHostFragment.navController.currentDestination?.id == R.id.emptyCallHistoryFragment) { - Log.i( - "[History] Foldable device has been folded, closing side pane with empty fragment" - ) - binding.slidingPane.closePane() - } - } - } - } - - /* End of shared view model & sliding pane related */ - - _adapter = CallLogsListAdapter(listSelectionViewModel, viewLifecycleOwner) - // SubmitList is done on a background thread - // We need this adapter data observer to know when to scroll - adapter.registerAdapterDataObserver(observer) - binding.callLogsList.setHasFixedSize(true) - binding.callLogsList.adapter = adapter - - binding.setEditClickListener { - listSelectionViewModel.isEditionEnabled.value = true - } - - val layoutManager = LinearLayoutManager(requireContext()) - binding.callLogsList.layoutManager = layoutManager - - // Swipe action - val swipeConfiguration = RecyclerViewSwipeConfiguration() - val white = ContextCompat.getColor(requireContext(), R.color.white_color) - - swipeConfiguration.rightToLeftAction = RecyclerViewSwipeConfiguration.Action( - requireContext().getString(R.string.dialog_delete), - white, - ContextCompat.getColor(requireContext(), R.color.red_color) - ) - val swipeListener = object : RecyclerViewSwipeListener { - override fun onLeftToRightSwipe(viewHolder: RecyclerView.ViewHolder) {} - - override fun onRightToLeftSwipe(viewHolder: RecyclerView.ViewHolder) { - val viewModel = DialogViewModel(getString(R.string.history_delete_one_dialog)) - viewModel.showIcon = true - viewModel.iconResource = R.drawable.dialog_delete_icon - val dialog: Dialog = DialogUtils.getDialog(requireContext(), viewModel) - - val index = viewHolder.bindingAdapterPosition - if (index < 0 || index >= adapter.currentList.size) { - Log.e("[History] Index is out of bound, can't delete call log") - } else { - viewModel.showCancelButton { - adapter.notifyItemChanged(index) - dialog.dismiss() - } - - viewModel.showDeleteButton( - { - val deletedCallGroup = adapter.currentList[index] - listViewModel.deleteCallLogGroup(deletedCallGroup) - if (!binding.slidingPane.isSlideable && - deletedCallGroup.lastCallLogId == sharedViewModel.selectedCallLogGroup.value?.lastCallLogId - ) { - Log.i( - "[History] Currently displayed history has been deleted, removing detail fragment" - ) - clearDisplayedCallHistory() - } - dialog.dismiss() - }, - getString(R.string.dialog_delete) - ) - } - - dialog.show() - } - } - RecyclerViewSwipeUtils(ItemTouchHelper.LEFT, swipeConfiguration, swipeListener) - .attachToRecyclerView(binding.callLogsList) - - // Divider between items - binding.callLogsList.addItemDecoration( - AppUtils.getDividerDecoration(requireContext(), layoutManager) - ) - - // Displays formatted date header - val headerItemDecoration = RecyclerViewHeaderDecoration(requireContext(), adapter) - binding.callLogsList.addItemDecoration(headerItemDecoration) - - listViewModel.callLogs.observe( - viewLifecycleOwner - ) { callLogs -> - adapter.submitList(callLogs) - } - - listViewModel.contactsUpdatedEvent.observe( - viewLifecycleOwner - ) { - it.consume { - adapter.notifyItemRangeChanged(0, adapter.itemCount) - } - } - - adapter.selectedCallLogEvent.observe( - viewLifecycleOwner - ) { - it.consume { callLog -> - sharedViewModel.selectedCallLogGroup.value = callLog - if (callLog.lastCallLog.wasConference()) { - navigateToConferenceCallHistory(binding.slidingPane) - } else { - navigateToCallHistory(binding.slidingPane) - } - } - } - - adapter.startCallToEvent.observe( - viewLifecycleOwner - ) { - it.consume { callLogGroup -> - val callLog = callLogGroup.lastCallLog - val conferenceInfo = callLog.conferenceInfo - when { - conferenceInfo != null -> { - if (conferenceInfo.state == ConferenceInfo.State.Cancelled) { - var snackRes = R.string.conference_scheduled_cancelled_by_organizer - - val organizer = conferenceInfo.organizer - if (organizer != null) { - val localAccount = coreContext.core.accountList.find { account -> - val address = account.params.identityAddress - address != null && organizer.weakEqual(address) - } - if (localAccount != null) { - snackRes = R.string.conference_scheduled_cancelled_by_me - } - } - - val activity = requireActivity() as MainActivity - activity.showSnackBar(snackRes) - } else { - navigateToConferenceWaitingRoom( - conferenceInfo.uri?.asStringUriOnly().orEmpty(), - conferenceInfo.subject - ) - } - } - coreContext.core.callsNb > 0 -> { - val cleanAddress = LinphoneUtils.getCleanedAddress(callLog.remoteAddress) - Log.i( - "[History] Starting dialer with pre-filled URI ${cleanAddress.asStringUriOnly()}, is transfer? ${sharedViewModel.pendingCallTransfer}" - ) - sharedViewModel.updateDialerAnimationsBasedOnDestination.value = Event( - R.id.masterCallLogsFragment - ) - val args = Bundle() - args.putString("URI", cleanAddress.asStringUriOnly()) - args.putBoolean("Transfer", sharedViewModel.pendingCallTransfer) - args.putBoolean("SkipAutoCallStart", true) // If auto start call setting is enabled, ignore it - navigateToDialer(args) - } - else -> { - val cleanAddress = LinphoneUtils.getCleanedAddress(callLog.remoteAddress) - val localAddress = callLogGroup.lastCallLog.localAddress - Log.i( - "[History] Starting call to ${cleanAddress.asStringUriOnly()} with local address ${localAddress.asStringUriOnly()}" - ) - coreContext.startCall(cleanAddress, localAddress = localAddress) - } - } - } - } - - coreContext.core.resetMissedCallsCount() - coreContext.notificationsManager.dismissMissedCallNotification() - } - - override fun onResume() { - super.onResume() - - val tabsViewModel = requireActivity().run { - ViewModelProvider(this)[TabsViewModel::class.java] - } - tabsViewModel.updateMissedCallCount() - } - - override fun deleteItems(indexesOfItemToDelete: ArrayList) { - val list = ArrayList() - var closeSlidingPane = false - for (index in indexesOfItemToDelete) { - val callLogGroup = adapter.currentList[index] - list.add(callLogGroup) - - if (callLogGroup.lastCallLogId == sharedViewModel.selectedCallLogGroup.value?.lastCallLogId) { - closeSlidingPane = true - } - } - listViewModel.deleteCallLogGroups(list) - - if (!binding.slidingPane.isSlideable && closeSlidingPane) { - Log.i( - "[History] Currently displayed history has been deleted, removing detail fragment" - ) - clearDisplayedCallHistory() - } - } - - private fun scrollToTop() { - binding.callLogsList.scrollToPosition(0) - } -} diff --git a/app/src/main/java/org/linphone/activities/main/history/viewmodels/CallLogViewModel.kt b/app/src/main/java/org/linphone/activities/main/history/viewmodels/CallLogViewModel.kt deleted file mode 100644 index a5d1b74cb..000000000 --- a/app/src/main/java/org/linphone/activities/main/history/viewmodels/CallLogViewModel.kt +++ /dev/null @@ -1,260 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.history.viewmodels - -import androidx.lifecycle.MutableLiveData -import java.text.SimpleDateFormat -import java.util.* -import kotlin.collections.ArrayList -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.main.conference.data.ConferenceSchedulingParticipantData -import org.linphone.contact.GenericContactViewModel -import org.linphone.core.* -import org.linphone.core.tools.Log -import org.linphone.utils.AppUtils -import org.linphone.utils.Event -import org.linphone.utils.LinphoneUtils -import org.linphone.utils.TimestampUtils - -class CallLogViewModel(val callLog: CallLog, private val isRelated: Boolean = false) : GenericContactViewModel( - callLog.remoteAddress -) { - val peerSipUri: String by lazy { - LinphoneUtils.getDisplayableAddress(callLog.remoteAddress) - } - - val statusIconResource: Int by lazy { - if (callLog.dir == Call.Dir.Incoming) { - if (LinphoneUtils.isCallLogMissed(callLog)) { - R.drawable.call_status_missed - } else { - R.drawable.call_status_incoming - } - } else { - R.drawable.call_status_outgoing - } - } - - val iconContentDescription: Int by lazy { - if (callLog.dir == Call.Dir.Incoming) { - if (LinphoneUtils.isCallLogMissed(callLog)) { - R.string.content_description_missed_call - } else { - R.string.content_description_incoming_call - } - } else { - R.string.content_description_outgoing_call - } - } - - val directionIconResource: Int by lazy { - if (callLog.dir == Call.Dir.Incoming) { - if (LinphoneUtils.isCallLogMissed(callLog)) { - R.drawable.call_missed - } else { - R.drawable.call_incoming - } - } else { - R.drawable.call_outgoing - } - } - - val duration: String by lazy { - val dateFormat = SimpleDateFormat( - if (callLog.duration >= 3600) "HH:mm:ss" else "mm:ss", - Locale.getDefault() - ) - val cal = Calendar.getInstance() - cal[0, 0, 0, 0, 0] = callLog.duration - dateFormat.format(cal.time) - } - - val date: String by lazy { - TimestampUtils.toString(callLog.startDate, shortDate = false, hideYear = false) - } - - val startCallEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val chatRoomCreatedEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val waitForChatRoomCreation = MutableLiveData() - - val chatAllowed = !corePreferences.disableChat - - val hidePlainChat = corePreferences.forceEndToEndEncryptedChat - - val secureChatAllowed = LinphoneUtils.isEndToEndEncryptedChatAvailable() && ( - corePreferences.allowEndToEndEncryptedChatWithoutPresence || ( - contact.value?.getPresenceModelForUriOrTel( - peerSipUri - )?.hasCapability(Friend.Capability.LimeX3Dh) ?: false - ) - ) - - val relatedCallLogs = MutableLiveData>() - - private val listener = object : CoreListenerStub() { - override fun onCallLogUpdated(core: Core, log: CallLog) { - if (callLog.remoteAddress.weakEqual(log.remoteAddress) && callLog.localAddress.weakEqual( - log.localAddress - ) - ) { - Log.i( - "[History Detail] New call log for ${callLog.remoteAddress.asStringUriOnly()} with local address ${callLog.localAddress.asStringUriOnly()}" - ) - addRelatedCallLogs(arrayListOf(log)) - } - } - } - - val isConferenceCallLog = callLog.wasConference() - - val conferenceSubject = callLog.conferenceInfo?.subject - val conferenceParticipantsData = MutableLiveData>() - val organizerParticipantData = MutableLiveData() - val conferenceTime = MutableLiveData() - val conferenceDate = MutableLiveData() - - val readOnlyNativeAddressBook = MutableLiveData() - - override val showGroupChatAvatar: Boolean - get() = isConferenceCallLog - - private val chatRoomListener = object : ChatRoomListenerStub() { - override fun onStateChanged(chatRoom: ChatRoom, state: ChatRoom.State) { - if (state == ChatRoom.State.Created) { - waitForChatRoomCreation.value = false - chatRoomCreatedEvent.value = Event(chatRoom) - } else if (state == ChatRoom.State.CreationFailed) { - Log.e("[History Detail] Group chat room creation has failed !") - waitForChatRoomCreation.value = false - onMessageToNotifyEvent.value = Event(R.string.chat_room_creation_failed_snack) - } - } - } - - init { - waitForChatRoomCreation.value = false - readOnlyNativeAddressBook.value = corePreferences.readOnlyNativeContacts - - if (!isRelated) { - val conferenceInfo = callLog.conferenceInfo - if (conferenceInfo != null) { - conferenceTime.value = TimestampUtils.timeToString(conferenceInfo.dateTime) - conferenceDate.value = if (TimestampUtils.isToday(conferenceInfo.dateTime)) { - AppUtils.getString(R.string.today) - } else { - TimestampUtils.toString( - conferenceInfo.dateTime, - onlyDate = true, - shortDate = false, - hideYear = false - ) - } - val organizer = conferenceInfo.organizer - if (organizer != null) { - organizerParticipantData.value = - ConferenceSchedulingParticipantData( - organizer, - showLimeBadge = false, - showDivider = false - ) - } - val list = arrayListOf() - for (participant in conferenceInfo.participants) { - list.add( - ConferenceSchedulingParticipantData( - participant, - showLimeBadge = false, - showDivider = true - ) - ) - } - conferenceParticipantsData.value = list - } - } - } - - override fun onCleared() { - destroy() - super.onCleared() - } - - fun destroy() { - if (!isRelated) { - relatedCallLogs.value.orEmpty().forEach(CallLogViewModel::destroy) - organizerParticipantData.value?.destroy() - conferenceParticipantsData.value.orEmpty() - .forEach(ConferenceSchedulingParticipantData::destroy) - } - } - - fun startCall() { - startCallEvent.value = Event(callLog) - } - - fun startChat(isSecured: Boolean) { - waitForChatRoomCreation.value = true - val chatRoom = LinphoneUtils.createOneToOneChatRoom(callLog.remoteAddress, isSecured) - - if (chatRoom != null) { - val state = chatRoom.state - Log.i("[History Detail] Found existing chat room in state $state") - if (state == ChatRoom.State.Created || state == ChatRoom.State.Terminated) { - waitForChatRoomCreation.value = false - chatRoomCreatedEvent.value = Event(chatRoom) - } else { - chatRoom.addListener(chatRoomListener) - } - } else { - waitForChatRoomCreation.value = false - Log.e( - "[History Detail] Couldn't create chat room with address ${callLog.remoteAddress}" - ) - onMessageToNotifyEvent.value = Event(R.string.chat_room_creation_failed_snack) - } - } - - fun addRelatedCallLogs(callLogs: ArrayList) { - val list = arrayListOf() - - // We assume new logs are more recent than the ones we already have, so we add them first - for (callLog in callLogs) { - list.add(CallLogViewModel(callLog, true)) - } - list.addAll(relatedCallLogs.value.orEmpty()) - - relatedCallLogs.value = list - } - - fun enableListener(enable: Boolean) { - if (enable) { - coreContext.core.addListener(listener) - } else { - coreContext.core.removeListener(listener) - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/history/viewmodels/CallLogsListViewModel.kt b/app/src/main/java/org/linphone/activities/main/history/viewmodels/CallLogsListViewModel.kt deleted file mode 100644 index cc41770cc..000000000 --- a/app/src/main/java/org/linphone/activities/main/history/viewmodels/CallLogsListViewModel.kt +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.history.viewmodels - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.activities.main.history.data.GroupedCallLogData -import org.linphone.contact.ContactsUpdatedListenerStub -import org.linphone.core.* -import org.linphone.core.tools.Log -import org.linphone.utils.Event -import org.linphone.utils.LinphoneUtils -import org.linphone.utils.TimestampUtils - -class CallLogsListViewModel : ViewModel() { - val callLogs = MutableLiveData>() - - val filter = MutableLiveData() - - val showConferencesFilter = MutableLiveData() - - val contactsUpdatedEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - private val listener: CoreListenerStub = object : CoreListenerStub() { - override fun onCallLogUpdated(core: Core, log: CallLog) { - updateCallLogs() - } - } - - private val contactsUpdatedListener = object : ContactsUpdatedListenerStub() { - override fun onContactsUpdated() { - Log.i("[Call Logs] Contacts have changed") - contactsUpdatedEvent.value = Event(true) - } - } - - init { - filter.value = CallLogsFilter.ALL - updateCallLogs() - - showConferencesFilter.value = LinphoneUtils.isRemoteConferencingAvailable() - - coreContext.core.addListener(listener) - coreContext.contactsManager.addListener(contactsUpdatedListener) - } - - override fun onCleared() { - callLogs.value.orEmpty().forEach(GroupedCallLogData::destroy) - - coreContext.contactsManager.removeListener(contactsUpdatedListener) - coreContext.core.removeListener(listener) - - super.onCleared() - } - - fun showAllCallLogs() { - filter.value = CallLogsFilter.ALL - updateCallLogs() - } - - fun showOnlyMissedCallLogs() { - filter.value = CallLogsFilter.MISSED - updateCallLogs() - } - - fun showOnlyConferenceCallLogs() { - filter.value = CallLogsFilter.CONFERENCE - updateCallLogs() - } - - fun deleteCallLogGroup(callLog: GroupedCallLogData?) { - if (callLog != null) { - for (log in callLog.callLogs) { - coreContext.core.removeCallLog(log) - } - } - - updateCallLogs() - } - - fun deleteCallLogGroups(listToDelete: ArrayList) { - for (callLog in listToDelete) { - for (log in callLog.callLogs) { - coreContext.core.removeCallLog(log) - } - } - - updateCallLogs() - } - - private fun computeCallLogs(callLogs: Array, missed: Boolean, conference: Boolean): ArrayList { - var previousCallLogGroup: GroupedCallLogData? = null - val list = arrayListOf() - - for (callLog in callLogs) { - if ((!missed && !conference) || (missed && LinphoneUtils.isCallLogMissed(callLog)) || (conference && callLog.wasConference())) { - if (previousCallLogGroup == null) { - previousCallLogGroup = GroupedCallLogData(callLog) - } else if (!callLog.wasConference() && // Do not group conference call logs - callLog.wasConference() == previousCallLogGroup.lastCallLog.wasConference() && // Check that both are of the same type, if one has a conf-id and not the other the equal method will return true ! - previousCallLogGroup.lastCallLog.localAddress.weakEqual(callLog.localAddress) && - previousCallLogGroup.lastCallLog.remoteAddress.equal(callLog.remoteAddress) - ) { - if (TimestampUtils.isSameDay( - previousCallLogGroup.lastCallLogStartTimestamp, - callLog.startDate - ) - ) { - previousCallLogGroup.callLogs.add(callLog) - previousCallLogGroup.updateLastCallLog(callLog) - } else { - list.add(previousCallLogGroup) - previousCallLogGroup = GroupedCallLogData(callLog) - } - } else { - list.add(previousCallLogGroup) - previousCallLogGroup = GroupedCallLogData(callLog) - } - } - } - if (previousCallLogGroup != null && !list.contains(previousCallLogGroup)) { - list.add(previousCallLogGroup) - } - - return list - } - - private fun updateCallLogs() { - callLogs.value.orEmpty().forEach(GroupedCallLogData::destroy) - - val allCallLogs = coreContext.core.callLogs - Log.i("[Call Logs] ${allCallLogs.size} call logs found") - - callLogs.value = when (filter.value) { - CallLogsFilter.MISSED -> computeCallLogs(allCallLogs, missed = true, conference = false) - CallLogsFilter.CONFERENCE -> computeCallLogs( - allCallLogs, - missed = false, - conference = true - ) - else -> computeCallLogs(allCallLogs, missed = false, conference = false) - } - } -} - -enum class CallLogsFilter { - ALL, - MISSED, - CONFERENCE -} diff --git a/app/src/main/java/org/linphone/activities/main/recordings/adapters/RecordingsListAdapter.kt b/app/src/main/java/org/linphone/activities/main/recordings/adapters/RecordingsListAdapter.kt deleted file mode 100644 index 6151ba58a..000000000 --- a/app/src/main/java/org/linphone/activities/main/recordings/adapters/RecordingsListAdapter.kt +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.recordings.adapters - -import android.content.Context -import android.view.LayoutInflater -import android.view.TextureView -import android.view.View -import android.view.ViewGroup -import androidx.databinding.DataBindingUtil -import androidx.lifecycle.LifecycleOwner -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.RecyclerView -import org.linphone.R -import org.linphone.activities.main.adapters.SelectionListAdapter -import org.linphone.activities.main.recordings.data.RecordingData -import org.linphone.activities.main.viewmodels.ListTopBarViewModel -import org.linphone.databinding.GenericListHeaderBinding -import org.linphone.databinding.RecordingListCellBinding -import org.linphone.utils.* - -class RecordingsListAdapter( - selectionVM: ListTopBarViewModel, - private val viewLifecycleOwner: LifecycleOwner -) : SelectionListAdapter( - selectionVM, - RecordingDiffCallback() -), - HeaderAdapter { - - private lateinit var videoSurface: TextureView - - fun setVideoTextureView(textureView: TextureView) { - videoSurface = textureView - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { - val binding: RecordingListCellBinding = DataBindingUtil.inflate( - LayoutInflater.from(parent.context), - R.layout.recording_list_cell, - parent, - false - ) - return ViewHolder(binding) - } - - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - (holder as ViewHolder).bind(getItem(position)) - } - - inner class ViewHolder( - val binding: RecordingListCellBinding - ) : RecyclerView.ViewHolder(binding.root) { - fun bind(recording: RecordingData) { - with(binding) { - data = recording - - lifecycleOwner = viewLifecycleOwner - - // This is for item selection through ListTopBarFragment - position = bindingAdapterPosition - selectionListViewModel = selectionViewModel - - setClickListener { - if (selectionViewModel.isEditionEnabled.value == true) { - selectionViewModel.onToggleSelect(bindingAdapterPosition) - } - } - - setPlayListener { - if (recording.isPlaying.value == true) { - recording.pause() - } else { - recording.play() - if (recording.isVideoAvailable()) { - recording.setTextureView(videoSurface) - } - } - } - - executePendingBindings() - } - } - } - - override fun displayHeaderForPosition(position: Int): Boolean { - if (position >= itemCount) return false - - val recording = getItem(position) - val date = recording.date - val previousPosition = position - 1 - - return if (previousPosition >= 0) { - val previousItemDate = getItem(previousPosition).date - !TimestampUtils.isSameDay(date, previousItemDate) - } else { - true - } - } - - override fun getHeaderViewForPosition(context: Context, position: Int): View { - val recording = getItem(position) - val date = formatDate(context, recording.date.time) - val binding: GenericListHeaderBinding = DataBindingUtil.inflate( - LayoutInflater.from(context), - R.layout.generic_list_header, - null, - false - ) - binding.title = date - binding.executePendingBindings() - return binding.root - } - - private fun formatDate(context: Context, date: Long): String { - // Recordings is one of the few items in Linphone that is already in milliseconds - if (TimestampUtils.isToday(date, false)) { - return context.getString(R.string.today) - } else if (TimestampUtils.isYesterday(date, false)) { - return context.getString(R.string.yesterday) - } - return TimestampUtils.toString( - date, - onlyDate = true, - timestampInSecs = false, - shortDate = false, - hideYear = false - ) - } -} - -private class RecordingDiffCallback : DiffUtil.ItemCallback() { - override fun areItemsTheSame( - oldItem: RecordingData, - newItem: RecordingData - ): Boolean { - return oldItem.compareTo(newItem) == 0 - } - - override fun areContentsTheSame( - oldItem: RecordingData, - newItem: RecordingData - ): Boolean { - return false // for headers - } -} diff --git a/app/src/main/java/org/linphone/activities/main/recordings/data/RecordingData.kt b/app/src/main/java/org/linphone/activities/main/recordings/data/RecordingData.kt deleted file mode 100644 index 022ffd171..000000000 --- a/app/src/main/java/org/linphone/activities/main/recordings/data/RecordingData.kt +++ /dev/null @@ -1,214 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.recordings.data - -import android.graphics.SurfaceTexture -import android.view.TextureView -import androidx.lifecycle.MutableLiveData -import java.text.DateFormat -import java.text.SimpleDateFormat -import java.util.* -import java.util.regex.Pattern -import kotlinx.coroutines.* -import kotlinx.coroutines.channels.ticker -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.core.Player -import org.linphone.core.PlayerListener -import org.linphone.core.tools.Log -import org.linphone.utils.AudioRouteUtils -import org.linphone.utils.LinphoneUtils - -class RecordingData(val path: String, private val recordingListener: RecordingListener) : Comparable { - companion object { - val RECORD_PATTERN: Pattern = - Pattern.compile(".*/(.*)_(\\d{2}-\\d{2}-\\d{4}-\\d{2}-\\d{2}-\\d{2})\\..*") - } - - lateinit var name: String - lateinit var date: Date - - val duration = MutableLiveData() - val formattedDuration = MutableLiveData() - val formattedDate = MutableLiveData() - val position = MutableLiveData() - val formattedPosition = MutableLiveData() - val isPlaying = MutableLiveData() - - private val tickerChannel = ticker(1000, 1000) - - private lateinit var player: Player - private val listener = PlayerListener { - Log.i("[Recording] End of file reached") - stop() - recordingListener.onPlayingEnded() - } - - private val textureViewListener = object : TextureView.SurfaceTextureListener { - override fun onSurfaceTextureSizeChanged( - surface: SurfaceTexture, - width: Int, - height: Int - ) { } - - override fun onSurfaceTextureUpdated(surface: SurfaceTexture) { } - - override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean { - player.setWindowId(null) - return true - } - - override fun onSurfaceTextureAvailable( - surface: SurfaceTexture, - width: Int, - height: Int - ) { - Log.i("[Recording] Surface texture should be available now") - player.setWindowId(surface) - } - } - - private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob()) - - init { - val m = RECORD_PATTERN.matcher(path) - if (m.matches() && m.groupCount() >= 2) { - name = m.group(1) - date = LinphoneUtils.getRecordingDateFromFileName(m.group(2)) - } - isPlaying.value = false - - position.value = 0 - formattedPosition.value = SimpleDateFormat("mm:ss", Locale.getDefault()).format(0) - - initPlayer() - } - - override fun compareTo(other: RecordingData): Int { - return -date.compareTo(other.date) - } - - fun destroy() { - scope.cancel() - tickerChannel.cancel() - - player.setWindowId(null) - if (!isClosed()) player.close() - - player.removeListener(listener) - } - - fun play() { - if (isClosed()) { - player.open(path) - player.seek(0) - } - - player.start() - isPlaying.value = true - recordingListener.onPlayingStarted(isVideoAvailable()) - - scope.launch { - withContext(Dispatchers.IO) { - for (tick in tickerChannel) { - withContext(Dispatchers.Main) { - if (player.state == Player.State.Playing) { - updatePosition() - } - } - } - } - } - } - - fun isVideoAvailable(): Boolean { - return player.isVideoAvailable - } - - fun pause() { - player.pause() - isPlaying.value = false - } - - fun onProgressChanged(progress: Any) { - if (progress is Int) { - if (player.state == Player.State.Playing) { - pause() - } - player.seek(progress) - updatePosition() - } - } - - fun setTextureView(textureView: TextureView) { - Log.i("[Recording] Is TextureView available? ${textureView.isAvailable}") - if (textureView.isAvailable) { - player.setWindowId(textureView.surfaceTexture) - } else { - textureView.surfaceTextureListener = textureViewListener - } - } - - fun export() { - recordingListener.onExportClicked(path) - } - - private fun initPlayer() { - val playbackSoundCard = AudioRouteUtils.getAudioPlaybackDeviceIdForCallRecordingOrVoiceMessage() - Log.i("[Recording] Using device $playbackSoundCard to make the call recording playback") - - val localPlayer = coreContext.core.createLocalPlayer(playbackSoundCard, null, null) - if (localPlayer != null) { - player = localPlayer - } else { - Log.e("[Recording] Couldn't create local player!") - } - player.addListener(listener) - - player.open(path) - duration.value = player.duration - formattedDuration.value = SimpleDateFormat("mm:ss", Locale.getDefault()).format( - player.duration - ) // is already in milliseconds - formattedDate.value = DateFormat.getTimeInstance(DateFormat.SHORT).format(date) - } - - private fun updatePosition() { - val progress = if (isClosed()) 0 else player.currentPosition - position.postValue(progress) - formattedPosition.postValue(SimpleDateFormat("mm:ss", Locale.getDefault()).format(progress)) // is already in milliseconds - } - - private fun stop() { - pause() - player.seek(0) - updatePosition() - player.close() - } - - private fun isClosed(): Boolean { - return player.state == Player.State.Closed - } - - interface RecordingListener { - fun onPlayingStarted(videoAvailable: Boolean) - fun onPlayingEnded() - fun onExportClicked(path: String) - } -} diff --git a/app/src/main/java/org/linphone/activities/main/recordings/fragments/RecordingsFragment.kt b/app/src/main/java/org/linphone/activities/main/recordings/fragments/RecordingsFragment.kt deleted file mode 100644 index b8c48a8a0..000000000 --- a/app/src/main/java/org/linphone/activities/main/recordings/fragments/RecordingsFragment.kt +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.recordings.fragments - -import android.content.ActivityNotFoundException -import android.content.Intent -import android.os.Bundle -import android.view.MotionEvent -import android.view.View -import androidx.lifecycle.ViewModelProvider -import androidx.recyclerview.widget.LinearLayoutManager -import org.linphone.R -import org.linphone.activities.main.fragments.MasterFragment -import org.linphone.activities.main.recordings.adapters.RecordingsListAdapter -import org.linphone.activities.main.recordings.data.RecordingData -import org.linphone.activities.main.recordings.viewmodels.RecordingsViewModel -import org.linphone.core.tools.Log -import org.linphone.databinding.RecordingsFragmentBinding -import org.linphone.utils.AppUtils -import org.linphone.utils.FileUtils -import org.linphone.utils.RecyclerViewHeaderDecoration - -class RecordingsFragment : MasterFragment() { - private lateinit var viewModel: RecordingsViewModel - - private var videoX: Float = 0f - private var videoY: Float = 0f - - override fun getLayoutId(): Int = R.layout.recordings_fragment - - override fun onDestroyView() { - binding.recordingsList.adapter = null - super.onDestroyView() - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - viewModel = ViewModelProvider(this)[RecordingsViewModel::class.java] - binding.viewModel = viewModel - - _adapter = RecordingsListAdapter(listSelectionViewModel, viewLifecycleOwner) - binding.recordingsList.setHasFixedSize(true) - binding.recordingsList.adapter = adapter - - val layoutManager = LinearLayoutManager(requireContext()) - binding.recordingsList.layoutManager = layoutManager - - // Divider between items - binding.recordingsList.addItemDecoration( - AppUtils.getDividerDecoration(requireContext(), layoutManager) - ) - - // Displays the first letter header - val headerItemDecoration = RecyclerViewHeaderDecoration(requireContext(), adapter) - binding.recordingsList.addItemDecoration(headerItemDecoration) - - viewModel.recordingsList.observe( - viewLifecycleOwner - ) { recordings -> - adapter.submitList(recordings) - } - - viewModel.exportRecordingEvent.observe( - viewLifecycleOwner - ) { - it.consume { path -> - val publicFilePath = FileUtils.getPublicFilePath(requireContext(), "file://$path") - Log.i("[Recordings] Exporting file [$path] with public URI [$publicFilePath]") - val intent = Intent(Intent.ACTION_SEND) - intent.type = " video/x-matroska" - intent.putExtra(Intent.EXTRA_STREAM, publicFilePath) - intent.putExtra(Intent.EXTRA_TEXT, getString(R.string.recordings_export)) - - try { - requireActivity().startActivity( - Intent.createChooser(intent, getString(R.string.recordings_export)) - ) - } catch (anfe: ActivityNotFoundException) { - Log.e(anfe) - } - } - } - - binding.setEditClickListener { listSelectionViewModel.isEditionEnabled.value = true } - - binding.setVideoTouchListener { v, event -> - when (event.action) { - MotionEvent.ACTION_DOWN -> { - videoX = v.x - event.rawX - videoY = v.y - event.rawY - } - MotionEvent.ACTION_MOVE -> { - v.animate() - .x(event.rawX + videoX) - .y(event.rawY + videoY) - .setDuration(0) - .start() - } - else -> { - v.performClick() - false - } - } - true - } - - adapter.setVideoTextureView(binding.recordingVideoSurface) - } - - override fun deleteItems(indexesOfItemToDelete: ArrayList) { - val list = ArrayList() - for (index in indexesOfItemToDelete) { - val recording = adapter.currentList[index] - list.add(recording) - } - viewModel.deleteRecordings(list) - } - - override fun onResume() { - if (this::viewModel.isInitialized) { - viewModel.updateRecordingsList() - } else { - Log.e( - "[Recordings] Fragment resuming but viewModel lateinit property isn't initialized!" - ) - } - super.onResume() - } -} diff --git a/app/src/main/java/org/linphone/activities/main/recordings/viewmodels/RecordingsViewModel.kt b/app/src/main/java/org/linphone/activities/main/recordings/viewmodels/RecordingsViewModel.kt deleted file mode 100644 index cc12b4442..000000000 --- a/app/src/main/java/org/linphone/activities/main/recordings/viewmodels/RecordingsViewModel.kt +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.recordings.viewmodels - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.media.AudioFocusRequestCompat -import kotlin.collections.ArrayList -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.activities.main.recordings.data.RecordingData -import org.linphone.core.tools.Log -import org.linphone.utils.AppUtils -import org.linphone.utils.Event -import org.linphone.utils.FileUtils - -class RecordingsViewModel : ViewModel() { - val recordingsList = MutableLiveData>() - - val isVideoVisible = MutableLiveData() - - val exportRecordingEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - private var recordingPlayingAudioFocusRequest: AudioFocusRequestCompat? = null - - private val recordingListener = object : RecordingData.RecordingListener { - override fun onPlayingStarted(videoAvailable: Boolean) { - if (recordingPlayingAudioFocusRequest == null) { - recordingPlayingAudioFocusRequest = AppUtils.acquireAudioFocusForVoiceRecordingOrPlayback( - coreContext.context - ) - } - - isVideoVisible.value = videoAvailable - } - - override fun onPlayingEnded() { - val request = recordingPlayingAudioFocusRequest - if (request != null) { - AppUtils.releaseAudioFocusForVoiceRecordingOrPlayback(coreContext.context, request) - recordingPlayingAudioFocusRequest = null - } - - isVideoVisible.value = false - } - - override fun onExportClicked(path: String) { - exportRecordingEvent.value = Event(path) - } - } - - init { - isVideoVisible.value = false - } - - override fun onCleared() { - recordingsList.value.orEmpty().forEach(RecordingData::destroy) - - val request = recordingPlayingAudioFocusRequest - if (request != null) { - AppUtils.releaseAudioFocusForVoiceRecordingOrPlayback(coreContext.context, request) - recordingPlayingAudioFocusRequest = null - } - - super.onCleared() - } - - fun deleteRecordings(list: ArrayList) { - for (recording in list) { - // Hide video when removing a recording being played with video. - if (recording.isPlaying.value == true && recording.isVideoAvailable()) { - isVideoVisible.value = false - } - - Log.i("[Recordings] Deleting recording ${recording.path}") - FileUtils.deleteFile(recording.path) - } - - updateRecordingsList() - } - - fun updateRecordingsList() { - recordingsList.value.orEmpty().forEach(RecordingData::destroy) - val list = arrayListOf() - - for (f in FileUtils.getFileStorageDir().listFiles().orEmpty()) { - Log.d("[Recordings] Found file ${f.path}") - if (RecordingData.RECORD_PATTERN.matcher(f.path).matches()) { - list.add( - RecordingData( - f.path, - recordingListener - ) - ) - Log.i("[Recordings] Found record ${f.path}") - } - } - - list.sort() - recordingsList.value = list - } -} diff --git a/app/src/main/java/org/linphone/activities/main/settings/SettingListener.kt b/app/src/main/java/org/linphone/activities/main/settings/SettingListener.kt deleted file mode 100644 index 16708c9df..000000000 --- a/app/src/main/java/org/linphone/activities/main/settings/SettingListener.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.settings - -interface SettingListener { - fun onClicked() - - fun onAccountClicked(identity: String) - - fun onTextValueChanged(newValue: String) - - fun onBoolValueChanged(newValue: Boolean) - - fun onListValueChanged(position: Int) -} diff --git a/app/src/main/java/org/linphone/activities/main/settings/SettingListenerStub.kt b/app/src/main/java/org/linphone/activities/main/settings/SettingListenerStub.kt deleted file mode 100644 index 2bb5fa46c..000000000 --- a/app/src/main/java/org/linphone/activities/main/settings/SettingListenerStub.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.settings - -open class SettingListenerStub : SettingListener { - override fun onClicked() {} - - override fun onAccountClicked(identity: String) {} - - override fun onTextValueChanged(newValue: String) {} - - override fun onBoolValueChanged(newValue: Boolean) {} - - override fun onListValueChanged(position: Int) {} -} diff --git a/app/src/main/java/org/linphone/activities/main/settings/fragments/AccountSettingsFragment.kt b/app/src/main/java/org/linphone/activities/main/settings/fragments/AccountSettingsFragment.kt deleted file mode 100644 index b06648585..000000000 --- a/app/src/main/java/org/linphone/activities/main/settings/fragments/AccountSettingsFragment.kt +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.settings.fragments - -import android.app.Dialog -import android.os.Bundle -import android.view.View -import androidx.core.view.doOnPreDraw -import androidx.lifecycle.ViewModelProvider -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.main.settings.viewmodels.AccountSettingsViewModel -import org.linphone.activities.main.settings.viewmodels.AccountSettingsViewModelFactory -import org.linphone.activities.main.viewmodels.DialogViewModel -import org.linphone.activities.navigateToPhoneLinking -import org.linphone.core.tools.Log -import org.linphone.databinding.SettingsAccountFragmentBinding -import org.linphone.utils.DialogUtils -import org.linphone.utils.Event - -class AccountSettingsFragment : GenericSettingFragment() { - private lateinit var viewModel: AccountSettingsViewModel - - override fun getLayoutId(): Int = R.layout.settings_account_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - binding.sharedMainViewModel = sharedViewModel - - val identity = arguments?.getString("Identity") - if (identity == null) { - Log.e("[Account Settings] Identity is null, aborting!") - goBack() - return - } - - try { - viewModel = ViewModelProvider(this, AccountSettingsViewModelFactory(identity))[AccountSettingsViewModel::class.java] - } catch (nsee: NoSuchElementException) { - Log.e("[Account Settings] Failed to find Account object, aborting!") - goBack() - return - } - binding.viewModel = viewModel - - viewModel.linkPhoneNumberEvent.observe( - viewLifecycleOwner - ) { - it.consume { - val authInfo = viewModel.account.findAuthInfo() - if (authInfo == null) { - Log.e( - "[Account Settings] Failed to find auth info for account ${viewModel.account}" - ) - } else { - val args = Bundle() - args.putString("Username", authInfo.username) - args.putString("Password", authInfo.password) - args.putString("HA1", authInfo.ha1) - navigateToPhoneLinking(args) - } - } - } - - viewModel.accountRemovedEvent.observe( - viewLifecycleOwner - ) { - it.consume { - sharedViewModel.accountRemoved.value = true - goBack() - } - } - - viewModel.accountDefaultEvent.observe( - viewLifecycleOwner - ) { - it.consume { - sharedViewModel.defaultAccountChanged.value = true - } - } - - viewModel.deleteAccountRequiredEvent.observe( - viewLifecycleOwner - ) { - it.consume { - val defaultDomainAccount = viewModel.account.params.identityAddress?.domain == corePreferences.defaultDomain - Log.i( - "[Account Settings] User clicked on delete account, showing confirmation dialog for ${if (defaultDomainAccount) "default domain account" else "third party account"}" - ) - val dialogViewModel = if (defaultDomainAccount) { - DialogViewModel( - getString( - R.string.account_setting_delete_sip_linphone_org_confirmation_dialog - ), - getString(R.string.account_setting_delete_dialog_title) - ) - } else { - DialogViewModel( - getString(R.string.account_setting_delete_generic_confirmation_dialog), - getString(R.string.account_setting_delete_dialog_title) - ) - } - val dialog: Dialog = DialogUtils.getDialog(requireContext(), dialogViewModel) - - dialogViewModel.showIcon = true - dialogViewModel.iconResource = R.drawable.dialog_delete_icon - dialogViewModel.showSubscribeLinphoneOrgLink = defaultDomainAccount - - dialogViewModel.showCancelButton { - Log.i("[Account Settings] User cancelled account removal") - dialog.dismiss() - } - - dialogViewModel.showDeleteButton( - { - viewModel.startDeleteAccount() - dialog.dismiss() - }, - getString(R.string.dialog_delete) - ) - - dialog.show() - } - } - - view.doOnPreDraw { - // Notifies fragment is ready to be drawn - sharedViewModel.accountSettingsFragmentOpenedEvent.value = Event(true) - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/settings/fragments/AdvancedSettingsFragment.kt b/app/src/main/java/org/linphone/activities/main/settings/fragments/AdvancedSettingsFragment.kt deleted file mode 100644 index bfe786392..000000000 --- a/app/src/main/java/org/linphone/activities/main/settings/fragments/AdvancedSettingsFragment.kt +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.settings.fragments - -import android.content.* -import android.net.Uri -import android.os.Bundle -import android.provider.Settings -import android.view.View -import androidx.appcompat.app.AppCompatDelegate -import androidx.core.content.ContextCompat -import androidx.lifecycle.ViewModelProvider -import org.linphone.R -import org.linphone.activities.main.MainActivity -import org.linphone.activities.main.settings.viewmodels.AdvancedSettingsViewModel -import org.linphone.core.tools.Log -import org.linphone.core.tools.compatibility.DeviceUtils -import org.linphone.databinding.SettingsAdvancedFragmentBinding -import org.linphone.utils.AppUtils -import org.linphone.utils.PowerManagerUtils - -class AdvancedSettingsFragment : GenericSettingFragment() { - private lateinit var viewModel: AdvancedSettingsViewModel - - override fun getLayoutId(): Int = R.layout.settings_advanced_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - binding.sharedMainViewModel = sharedViewModel - - viewModel = ViewModelProvider(this)[AdvancedSettingsViewModel::class.java] - binding.viewModel = viewModel - - viewModel.uploadFinishedEvent.observe( - viewLifecycleOwner - ) { - it.consume { url -> - val clipboard = - requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - val clip = ClipData.newPlainText("Logs url", url) - clipboard.setPrimaryClip(clip) - - val activity = requireActivity() as MainActivity - activity.showSnackBar(R.string.logs_url_copied_to_clipboard) - - AppUtils.shareUploadedLogsUrl(activity, url) - } - } - - viewModel.uploadErrorEvent.observe( - viewLifecycleOwner - ) { - it.consume { - val activity = requireActivity() as MainActivity - activity.showSnackBar(R.string.logs_upload_failure) - } - } - - viewModel.resetCompleteEvent.observe( - viewLifecycleOwner - ) { - it.consume { - val activity = requireActivity() as MainActivity - activity.showSnackBar(R.string.logs_reset_complete) - } - } - - viewModel.setNightModeEvent.observe( - viewLifecycleOwner - ) { - it.consume { value -> - AppCompatDelegate.setDefaultNightMode( - when (value) { - 0 -> AppCompatDelegate.MODE_NIGHT_NO - 1 -> AppCompatDelegate.MODE_NIGHT_YES - else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM - } - ) - } - } - - viewModel.backgroundModeEnabled.value = !DeviceUtils.isAppUserRestricted(requireContext()) - - viewModel.goToBatterySettingsEvent.observe( - viewLifecycleOwner - ) { - it.consume { - try { - val intent = Intent("android.settings.IGNORE_BATTERY_OPTIMIZATION_SETTINGS") - startActivity(intent) - } catch (anfe: ActivityNotFoundException) { - Log.e("[Advanced Settings] ActivityNotFound exception: ", anfe) - } - } - } - - viewModel.powerManagerSettingsVisibility.value = PowerManagerUtils.getDevicePowerManagerIntent( - requireContext() - ) != null - viewModel.goToPowerManagerSettingsEvent.observe( - viewLifecycleOwner - ) { - it.consume { - val intent = PowerManagerUtils.getDevicePowerManagerIntent(requireActivity()) - if (intent != null) { - try { - startActivity(intent) - } catch (se: SecurityException) { - Log.e("[Advanced Settings] Security exception: ", se) - } - } - } - } - - viewModel.goToAndroidSettingsEvent.observe( - viewLifecycleOwner - ) { - it.consume { - val intent = Intent() - intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS - intent.addCategory(Intent.CATEGORY_DEFAULT) - intent.data = Uri.parse("package:${requireContext().packageName}") - intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY) - ContextCompat.startActivity(requireContext(), intent, null) - } - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/settings/fragments/AudioSettingsFragment.kt b/app/src/main/java/org/linphone/activities/main/settings/fragments/AudioSettingsFragment.kt deleted file mode 100644 index 78e2e2cf2..000000000 --- a/app/src/main/java/org/linphone/activities/main/settings/fragments/AudioSettingsFragment.kt +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.settings.fragments - -import android.content.pm.PackageManager -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import androidx.databinding.DataBindingUtil -import androidx.databinding.ViewDataBinding -import androidx.lifecycle.ViewModelProvider -import org.linphone.BR -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R -import org.linphone.activities.main.settings.SettingListenerStub -import org.linphone.activities.main.settings.viewmodels.AudioSettingsViewModel -import org.linphone.core.tools.Log -import org.linphone.databinding.SettingsAudioFragmentBinding -import org.linphone.utils.PermissionHelper - -class AudioSettingsFragment : GenericSettingFragment() { - private lateinit var viewModel: AudioSettingsViewModel - - override fun getLayoutId(): Int = R.layout.settings_audio_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - binding.sharedMainViewModel = sharedViewModel - - viewModel = ViewModelProvider(this)[AudioSettingsViewModel::class.java] - binding.viewModel = viewModel - - viewModel.askAudioRecordPermissionForEchoCancellerCalibrationEvent.observe( - viewLifecycleOwner - ) { - it.consume { - Log.i( - "[Audio Settings] Asking for RECORD_AUDIO permission for echo canceller calibration" - ) - requestPermissions(arrayOf(android.Manifest.permission.RECORD_AUDIO), 1) - } - } - - viewModel.askAudioRecordPermissionForEchoTesterEvent.observe( - viewLifecycleOwner - ) { - it.consume { - Log.i("[Audio Settings] Asking for RECORD_AUDIO permission for echo tester") - requestPermissions(arrayOf(android.Manifest.permission.RECORD_AUDIO), 2) - } - } - - initAudioCodecsList() - - if (!PermissionHelper.required(requireContext()).hasRecordAudioPermission()) { - Log.i("[Audio Settings] Asking for RECORD_AUDIO permission") - requestPermissions(arrayOf(android.Manifest.permission.RECORD_AUDIO), 0) - } - } - - @Deprecated("Deprecated in Java") - override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray - ) { - val granted = grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED - if (granted) { - Log.i("[Audio Settings] RECORD_AUDIO permission granted") - if (requestCode == 1) { - viewModel.startEchoCancellerCalibration() - } else if (requestCode == 2) { - viewModel.startEchoTester() - } - } else { - Log.w("[Audio Settings] RECORD_AUDIO permission denied") - } - } - - private fun initAudioCodecsList() { - val list = arrayListOf() - for (payload in coreContext.core.audioPayloadTypes) { - val binding = DataBindingUtil.inflate( - LayoutInflater.from(requireContext()), - R.layout.settings_widget_switch, - null, - false - ) - binding.setVariable(BR.title, payload.mimeType) - binding.setVariable(BR.subtitle, "${payload.clockRate} Hz") - binding.setVariable(BR.checked, payload.enabled()) - binding.setVariable( - BR.listener, - object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - payload.enable(newValue) - } - } - ) - binding.lifecycleOwner = viewLifecycleOwner - list.add(binding) - } - viewModel.audioCodecs.value = list - } -} diff --git a/app/src/main/java/org/linphone/activities/main/settings/fragments/CallSettingsFragment.kt b/app/src/main/java/org/linphone/activities/main/settings/fragments/CallSettingsFragment.kt deleted file mode 100644 index 735febf15..000000000 --- a/app/src/main/java/org/linphone/activities/main/settings/fragments/CallSettingsFragment.kt +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.settings.fragments - -import android.content.Intent -import android.content.pm.PackageManager -import android.net.Uri -import android.os.Build -import android.os.Bundle -import android.provider.Settings -import android.view.View -import androidx.lifecycle.ViewModelProvider -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.main.settings.viewmodels.CallSettingsViewModel -import org.linphone.compatibility.Compatibility -import org.linphone.core.tools.Log -import org.linphone.databinding.SettingsCallFragmentBinding -import org.linphone.mediastream.Version -import org.linphone.telecom.TelecomHelper - -class CallSettingsFragment : GenericSettingFragment() { - private lateinit var viewModel: CallSettingsViewModel - - override fun getLayoutId(): Int = R.layout.settings_call_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - binding.sharedMainViewModel = sharedViewModel - - viewModel = ViewModelProvider(this)[CallSettingsViewModel::class.java] - binding.viewModel = viewModel - - viewModel.systemWideOverlayEnabledEvent.observe( - viewLifecycleOwner - ) { - it.consume { - if (!Compatibility.canDrawOverlay(requireContext())) { - val intent = Intent( - "android.settings.action.MANAGE_OVERLAY_PERMISSION", - Uri.parse("package:${requireContext().packageName}") - ) - startActivityForResult(intent, 0) - } - } - } - - viewModel.goToAndroidNotificationSettingsEvent.observe( - viewLifecycleOwner - ) { - it.consume { - if (Build.VERSION.SDK_INT >= Version.API26_O_80) { - val i = Intent() - i.action = Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS - i.putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().packageName) - i.putExtra( - Settings.EXTRA_CHANNEL_ID, - getString(R.string.notification_channel_service_id) - ) - i.addCategory(Intent.CATEGORY_DEFAULT) - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - i.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY) - i.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) - startActivity(i) - } - } - } - - viewModel.enableTelecomManagerEvent.observe( - viewLifecycleOwner - ) { - it.consume { - if (Compatibility.hasTelecomManagerFeature(requireContext())) { - if (!Compatibility.hasTelecomManagerPermissions(requireContext())) { - Compatibility.requestTelecomManagerPermissions(requireActivity(), 1) - } else if (!TelecomHelper.exists()) { - corePreferences.useTelecomManager = true - Log.w("[Telecom Helper] Doesn't exists yet, creating it") - TelecomHelper.create(requireContext()) - updateTelecomManagerAccount() - } - } else { - Log.e( - "[Telecom Helper] Telecom Helper can't be created, device doesn't support connection service!" - ) - } - } - } - - viewModel.goToAndroidNotificationSettingsEvent.observe( - viewLifecycleOwner - ) { - it.consume { - if (Build.VERSION.SDK_INT >= Version.API26_O_80) { - val i = Intent() - i.action = Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS - i.putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().packageName) - i.putExtra( - Settings.EXTRA_CHANNEL_ID, - getString(R.string.notification_channel_service_id) - ) - i.addCategory(Intent.CATEGORY_DEFAULT) - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - i.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY) - i.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) - startActivity(i) - } - } - } - } - - @Deprecated("Deprecated in Java") - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - - if (requestCode == 0 && !Compatibility.canDrawOverlay(requireContext())) { - viewModel.overlayListener.onBoolValueChanged(false) - } else if (requestCode == 1) { - if (!TelecomHelper.exists()) { - Log.w("[Telecom Helper] Doesn't exists yet, creating it") - if (Compatibility.hasTelecomManagerFeature(requireContext())) { - TelecomHelper.create(requireContext()) - } else { - Log.e( - "[Telecom Helper] Telecom Helper can't be created, device doesn't support connection service" - ) - } - } - updateTelecomManagerAccount() - } - } - - private fun updateTelecomManagerAccount() { - if (!TelecomHelper.exists()) { - Log.e("[Telecom Helper] Doesn't exists, can't update account!") - return - } - // We have to refresh the account object otherwise isAccountEnabled will always return false... - val account = TelecomHelper.get().findExistingAccount(requireContext()) - TelecomHelper.get().updateAccount(account) - val enabled = TelecomHelper.get().isAccountEnabled() - Log.i("[Call Settings] Telecom Manager is ${if (enabled) "enabled" else "disabled"}") - viewModel.useTelecomManager.value = enabled - corePreferences.useTelecomManager = enabled - } - - @Deprecated("Deprecated in Java") - override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray - ) { - for (index in grantResults.indices) { - val result = grantResults[index] - if (result != PackageManager.PERMISSION_GRANTED) { - Log.w( - "[Call Settings] ${permissions[index]} permission denied but required for telecom manager" - ) - viewModel.useTelecomManager.value = false - corePreferences.useTelecomManager = false - return - } - } - - if (Compatibility.hasTelecomManagerFeature(requireContext())) { - TelecomHelper.create(requireContext()) - updateTelecomManagerAccount() - } else { - Log.e( - "[Telecom Helper] Telecom Helper can't be created, device doesn't support connection service" - ) - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/settings/fragments/ChatSettingsFragment.kt b/app/src/main/java/org/linphone/activities/main/settings/fragments/ChatSettingsFragment.kt deleted file mode 100644 index 23c6ef87e..000000000 --- a/app/src/main/java/org/linphone/activities/main/settings/fragments/ChatSettingsFragment.kt +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.settings.fragments - -import android.content.Intent -import android.os.Build -import android.os.Bundle -import android.provider.Settings -import android.view.View -import androidx.lifecycle.ViewModelProvider -import org.linphone.R -import org.linphone.activities.main.chat.viewmodels.ChatRoomsListViewModel -import org.linphone.activities.main.settings.viewmodels.ChatSettingsViewModel -import org.linphone.databinding.SettingsChatFragmentBinding -import org.linphone.mediastream.Version -import org.linphone.utils.ShortcutsHelper - -class ChatSettingsFragment : GenericSettingFragment() { - private lateinit var viewModel: ChatSettingsViewModel - - override fun getLayoutId(): Int = R.layout.settings_chat_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - binding.sharedMainViewModel = sharedViewModel - - viewModel = ViewModelProvider(this)[ChatSettingsViewModel::class.java] - binding.viewModel = viewModel - - viewModel.launcherShortcutsEvent.observe( - viewLifecycleOwner - ) { - it.consume { newValue -> - if (newValue) { - ShortcutsHelper.createShortcutsToChatRooms(requireContext()) - } else { - ShortcutsHelper.removeShortcuts(requireContext()) - } - } - } - - viewModel.goToAndroidNotificationSettingsEvent.observe( - viewLifecycleOwner - ) { - it.consume { - if (Build.VERSION.SDK_INT >= Version.API26_O_80) { - val i = Intent() - i.action = Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS - i.putExtra(Settings.EXTRA_APP_PACKAGE, requireContext().packageName) - i.putExtra( - Settings.EXTRA_CHANNEL_ID, - getString(R.string.notification_channel_chat_id) - ) - i.addCategory(Intent.CATEGORY_DEFAULT) - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - i.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY) - i.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) - startActivity(i) - } - } - } - - viewModel.reloadChatRoomsEvent.observe(viewLifecycleOwner) { - it.consume { - reloadChatRooms() - } - } - } - - private fun reloadChatRooms() { - val listViewModel = requireActivity().run { - ViewModelProvider(this)[ChatRoomsListViewModel::class.java] - } - listViewModel.updateChatRooms() - } -} diff --git a/app/src/main/java/org/linphone/activities/main/settings/fragments/ConferencesSettingsFragment.kt b/app/src/main/java/org/linphone/activities/main/settings/fragments/ConferencesSettingsFragment.kt deleted file mode 100644 index 02bac24ab..000000000 --- a/app/src/main/java/org/linphone/activities/main/settings/fragments/ConferencesSettingsFragment.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2010-2022 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.settings.fragments - -import android.os.Bundle -import android.view.View -import androidx.lifecycle.ViewModelProvider -import org.linphone.R -import org.linphone.activities.main.settings.viewmodels.ConferencesSettingsViewModel -import org.linphone.databinding.SettingsConferencesFragmentBinding - -class ConferencesSettingsFragment : GenericSettingFragment() { - private lateinit var viewModel: ConferencesSettingsViewModel - - override fun getLayoutId(): Int = R.layout.settings_conferences_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - binding.sharedMainViewModel = sharedViewModel - - viewModel = ViewModelProvider(this)[ConferencesSettingsViewModel::class.java] - binding.viewModel = viewModel - } -} diff --git a/app/src/main/java/org/linphone/activities/main/settings/fragments/ContactsSettingsFragment.kt b/app/src/main/java/org/linphone/activities/main/settings/fragments/ContactsSettingsFragment.kt deleted file mode 100644 index f949fe0d6..000000000 --- a/app/src/main/java/org/linphone/activities/main/settings/fragments/ContactsSettingsFragment.kt +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.settings.fragments - -import android.content.pm.PackageManager -import android.os.Bundle -import android.view.View -import androidx.lifecycle.ViewModelProvider -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.main.settings.SettingListenerStub -import org.linphone.activities.main.settings.viewmodels.ContactsSettingsViewModel -import org.linphone.activities.navigateToLdapSettings -import org.linphone.core.tools.Log -import org.linphone.databinding.SettingsContactsFragmentBinding -import org.linphone.utils.PermissionHelper -import org.linphone.utils.ShortcutsHelper - -class ContactsSettingsFragment : GenericSettingFragment() { - private lateinit var viewModel: ContactsSettingsViewModel - - override fun getLayoutId(): Int = R.layout.settings_contacts_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - binding.sharedMainViewModel = sharedViewModel - - viewModel = ViewModelProvider(this)[ContactsSettingsViewModel::class.java] - binding.viewModel = viewModel - - viewModel.launcherShortcutsEvent.observe( - viewLifecycleOwner - ) { - it.consume { newValue -> - if (newValue) { - ShortcutsHelper.createShortcutsToContacts(requireContext()) - } else { - ShortcutsHelper.removeShortcuts(requireContext()) - if (corePreferences.chatRoomShortcuts) { - ShortcutsHelper.createShortcutsToChatRooms(requireContext()) - } - } - } - } - - viewModel.askWriteContactsPermissionForPresenceStorageEvent.observe( - viewLifecycleOwner - ) { - it.consume { - Log.i( - "[Contacts Settings] Asking for WRITE_CONTACTS permission to be able to store presence" - ) - requestPermissions(arrayOf(android.Manifest.permission.WRITE_CONTACTS), 1) - } - } - - viewModel.publishPresenceToggledEvent.observe( - viewLifecycleOwner - ) { - it.consume { - sharedViewModel.publishPresenceToggled.value = true - } - } - - viewModel.ldapNewSettingsListener = object : SettingListenerStub() { - override fun onClicked() { - Log.i("[Contacts Settings] Clicked on new LDAP config") - navigateToLdapSettings(-1) - } - } - - viewModel.ldapSettingsClickedEvent.observe( - viewLifecycleOwner - ) { - it.consume { index -> - Log.i("[Contacts Settings] Clicked on LDAP config with index: $index") - navigateToLdapSettings(index) - } - } - - if (corePreferences.enableNativeAddressBookIntegration) { - if (!PermissionHelper.required(requireContext()).hasReadContactsPermission()) { - Log.i("[Contacts Settings] Asking for READ_CONTACTS permission") - requestPermissions(arrayOf(android.Manifest.permission.READ_CONTACTS), 0) - } - } - } - - @Deprecated("Deprecated in Java") - override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray - ) { - when (requestCode) { - 0 -> { - val granted = grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED - if (granted) { - Log.i("[Contacts Settings] READ_CONTACTS permission granted") - viewModel.readContactsPermissionGranted.value = true - coreContext.fetchContacts() - } else { - Log.w("[Contacts Settings] READ_CONTACTS permission denied") - } - } - 1 -> { - val granted = grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED - if (granted) { - Log.i("[Contacts Settings] WRITE_CONTACTS permission granted") - corePreferences.storePresenceInNativeContact = true - } else { - Log.w("[Contacts Settings] WRITE_CONTACTS permission denied") - } - } - } - } - - override fun onResume() { - super.onResume() - viewModel.updateLdapConfigurationsList() - } -} diff --git a/app/src/main/java/org/linphone/activities/main/settings/fragments/GenericSettingFragment.kt b/app/src/main/java/org/linphone/activities/main/settings/fragments/GenericSettingFragment.kt deleted file mode 100644 index 6d2844a71..000000000 --- a/app/src/main/java/org/linphone/activities/main/settings/fragments/GenericSettingFragment.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.settings.fragments - -import android.os.Bundle -import android.view.View -import androidx.databinding.ViewDataBinding -import org.linphone.activities.GenericFragment - -abstract class GenericSettingFragment : GenericFragment() { - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - useMaterialSharedAxisXForwardAnimation = sharedViewModel.isSlidingPaneSlideable.value == false - - super.onViewCreated(view, savedInstanceState) - } -} diff --git a/app/src/main/java/org/linphone/activities/main/settings/fragments/LdapSettingsFragment.kt b/app/src/main/java/org/linphone/activities/main/settings/fragments/LdapSettingsFragment.kt deleted file mode 100644 index aba760799..000000000 --- a/app/src/main/java/org/linphone/activities/main/settings/fragments/LdapSettingsFragment.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2010-2022 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.settings.fragments - -import android.os.Bundle -import android.view.View -import androidx.lifecycle.ViewModelProvider -import org.linphone.R -import org.linphone.activities.main.settings.viewmodels.LdapSettingsViewModel -import org.linphone.activities.main.settings.viewmodels.LdapSettingsViewModelFactory -import org.linphone.core.tools.Log -import org.linphone.databinding.SettingsLdapFragmentBinding - -class LdapSettingsFragment : GenericSettingFragment() { - private lateinit var viewModel: LdapSettingsViewModel - - override fun getLayoutId(): Int = R.layout.settings_ldap_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - binding.sharedMainViewModel = sharedViewModel - - val configIndex = arguments?.getInt("LdapConfigIndex") - if (configIndex == null) { - Log.e("[LDAP Settings] Config index not specified!") - goBack() - return - } - - try { - viewModel = ViewModelProvider(this, LdapSettingsViewModelFactory(configIndex))[LdapSettingsViewModel::class.java] - } catch (nsee: NoSuchElementException) { - Log.e("[LDAP Settings] Failed to find LDAP object, aborting!") - goBack() - return - } - binding.viewModel = viewModel - - viewModel.ldapConfigDeletedEvent.observe( - viewLifecycleOwner - ) { - it.consume { - goBack() - } - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/settings/fragments/NetworkSettingsFragment.kt b/app/src/main/java/org/linphone/activities/main/settings/fragments/NetworkSettingsFragment.kt deleted file mode 100644 index f990f2ada..000000000 --- a/app/src/main/java/org/linphone/activities/main/settings/fragments/NetworkSettingsFragment.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.settings.fragments - -import android.os.Bundle -import android.view.View -import androidx.lifecycle.ViewModelProvider -import org.linphone.R -import org.linphone.activities.main.settings.viewmodels.NetworkSettingsViewModel -import org.linphone.databinding.SettingsNetworkFragmentBinding - -class NetworkSettingsFragment : GenericSettingFragment() { - private lateinit var viewModel: NetworkSettingsViewModel - - override fun getLayoutId(): Int = R.layout.settings_network_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - binding.sharedMainViewModel = sharedViewModel - - viewModel = ViewModelProvider(this)[NetworkSettingsViewModel::class.java] - binding.viewModel = viewModel - } -} diff --git a/app/src/main/java/org/linphone/activities/main/settings/fragments/SettingsFragment.kt b/app/src/main/java/org/linphone/activities/main/settings/fragments/SettingsFragment.kt deleted file mode 100644 index 72cc41def..000000000 --- a/app/src/main/java/org/linphone/activities/main/settings/fragments/SettingsFragment.kt +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.settings.fragments - -import android.os.Bundle -import android.view.View -import androidx.core.view.doOnPreDraw -import androidx.lifecycle.ViewModelProvider -import androidx.navigation.fragment.NavHostFragment -import androidx.slidingpanelayout.widget.SlidingPaneLayout -import com.google.android.material.transition.MaterialSharedAxis -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.* -import org.linphone.activities.main.fragments.MasterFragment -import org.linphone.activities.main.fragments.SecureFragment -import org.linphone.activities.main.settings.SettingListenerStub -import org.linphone.activities.main.settings.viewmodels.SettingsViewModel -import org.linphone.activities.navigateToAccountSettings -import org.linphone.activities.navigateToAudioSettings -import org.linphone.activities.navigateToTunnelSettings -import org.linphone.activities.navigateToVideoSettings -import org.linphone.core.tools.Log -import org.linphone.databinding.SettingsFragmentBinding - -class SettingsFragment : SecureFragment() { - private lateinit var viewModel: SettingsViewModel - - override fun getLayoutId(): Int = R.layout.settings_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - useMaterialSharedAxisXForwardAnimation = false - if (corePreferences.enableAnimations) { - enterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) - reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true) - returnTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) - exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false) - } - - /* Shared view model & sliding pane related */ - - view.doOnPreDraw { sharedViewModel.isSlidingPaneSlideable.value = binding.slidingPane.isSlideable } - - // Account settings loading can take some time, so wait until it is ready before opening the pane - sharedViewModel.accountSettingsFragmentOpenedEvent.observe( - viewLifecycleOwner - ) { - it.consume { - binding.slidingPane.openPane() - } - } - - sharedViewModel.layoutChangedEvent.observe( - viewLifecycleOwner - ) { - it.consume { - sharedViewModel.isSlidingPaneSlideable.value = binding.slidingPane.isSlideable - if (binding.slidingPane.isSlideable) { - val navHostFragment = - childFragmentManager.findFragmentById(R.id.settings_nav_container) as NavHostFragment - if (navHostFragment.navController.currentDestination?.id == R.id.emptySettingsFragment) { - Log.i( - "[Settings] Foldable device has been folded, closing side pane with empty fragment" - ) - binding.slidingPane.closePane() - } - } - } - } - - requireActivity().onBackPressedDispatcher.addCallback( - viewLifecycleOwner, - MasterFragment.SlidingPaneBackPressedCallback(binding.slidingPane) - ) - - binding.slidingPane.lockMode = SlidingPaneLayout.LOCK_MODE_LOCKED - - /* End of shared view model & sliding pane related */ - - viewModel = ViewModelProvider(this)[SettingsViewModel::class.java] - binding.viewModel = viewModel - - sharedViewModel.accountRemoved.observe( - viewLifecycleOwner - ) { - Log.i("[Settings] Account removed, update accounts list") - viewModel.updateAccountsList() - } - - sharedViewModel.defaultAccountChanged.observe( - viewLifecycleOwner - ) { - Log.i("[Settings] Default account changed, update accounts list") - viewModel.updateAccountsList() - } - - val identity = arguments?.getString("Identity") - if (identity != null) { - Log.i("[Settings] Found identity parameter in arguments: $identity") - arguments?.clear() - navigateToAccountSettings(identity) - } - - viewModel.accountsSettingsListener = object : SettingListenerStub() { - override fun onAccountClicked(identity: String) { - Log.i("[Settings] Navigation to settings for account with identity: $identity") - navigateToAccountSettings(identity) - } - } - - viewModel.tunnelSettingsListener = object : SettingListenerStub() { - override fun onClicked() { - navigateToTunnelSettings(binding.slidingPane) - } - } - - viewModel.audioSettingsListener = object : SettingListenerStub() { - override fun onClicked() { - navigateToAudioSettings(binding.slidingPane) - } - } - - viewModel.videoSettingsListener = object : SettingListenerStub() { - override fun onClicked() { - navigateToVideoSettings(binding.slidingPane) - } - } - - viewModel.callSettingsListener = object : SettingListenerStub() { - override fun onClicked() { - navigateToCallSettings(binding.slidingPane) - } - } - - viewModel.chatSettingsListener = object : SettingListenerStub() { - override fun onClicked() { - navigateToChatSettings(binding.slidingPane) - } - } - - viewModel.networkSettingsListener = object : SettingListenerStub() { - override fun onClicked() { - navigateToNetworkSettings(binding.slidingPane) - } - } - - viewModel.contactsSettingsListener = object : SettingListenerStub() { - override fun onClicked() { - navigateToContactsSettings(binding.slidingPane) - } - } - - viewModel.advancedSettingsListener = object : SettingListenerStub() { - override fun onClicked() { - navigateToAdvancedSettings(binding.slidingPane) - } - } - - viewModel.conferencesSettingsListener = object : SettingListenerStub() { - override fun onClicked() { - navigateToConferencesSettings(binding.slidingPane) - } - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/settings/fragments/TunnelSettingsFragment.kt b/app/src/main/java/org/linphone/activities/main/settings/fragments/TunnelSettingsFragment.kt deleted file mode 100644 index 947f7bd6f..000000000 --- a/app/src/main/java/org/linphone/activities/main/settings/fragments/TunnelSettingsFragment.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.settings.fragments - -import android.os.Bundle -import android.view.View -import androidx.lifecycle.ViewModelProvider -import org.linphone.R -import org.linphone.activities.main.settings.viewmodels.TunnelSettingsViewModel -import org.linphone.databinding.SettingsTunnelFragmentBinding - -class TunnelSettingsFragment : GenericSettingFragment() { - private lateinit var viewModel: TunnelSettingsViewModel - - override fun getLayoutId(): Int = R.layout.settings_tunnel_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - binding.sharedMainViewModel = sharedViewModel - - viewModel = ViewModelProvider(this)[TunnelSettingsViewModel::class.java] - binding.viewModel = viewModel - } -} diff --git a/app/src/main/java/org/linphone/activities/main/settings/fragments/VideoSettingsFragment.kt b/app/src/main/java/org/linphone/activities/main/settings/fragments/VideoSettingsFragment.kt deleted file mode 100644 index 6ad8561bd..000000000 --- a/app/src/main/java/org/linphone/activities/main/settings/fragments/VideoSettingsFragment.kt +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.settings.fragments - -import android.content.pm.PackageManager -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import androidx.databinding.DataBindingUtil -import androidx.databinding.ViewDataBinding -import androidx.lifecycle.ViewModelProvider -import org.linphone.BR -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R -import org.linphone.activities.main.settings.SettingListenerStub -import org.linphone.activities.main.settings.viewmodels.VideoSettingsViewModel -import org.linphone.core.tools.Log -import org.linphone.databinding.SettingsVideoFragmentBinding -import org.linphone.utils.PermissionHelper - -class VideoSettingsFragment : GenericSettingFragment() { - private lateinit var viewModel: VideoSettingsViewModel - - override fun getLayoutId(): Int = R.layout.settings_video_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - binding.sharedMainViewModel = sharedViewModel - - viewModel = ViewModelProvider(this)[VideoSettingsViewModel::class.java] - binding.viewModel = viewModel - - initVideoCodecsList() - - if (!PermissionHelper.required(requireContext()).hasCameraPermission()) { - Log.i("[Video Settings] Asking for CAMERA permission") - requestPermissions(arrayOf(android.Manifest.permission.CAMERA), 0) - } - } - - @Deprecated("Deprecated in Java") - override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray - ) { - val granted = grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED - if (granted) { - Log.i("[Video Settings] CAMERA permission granted") - coreContext.core.reloadVideoDevices() - viewModel.initCameraDevicesList() - } else { - Log.w("[Video Settings] CAMERA permission denied") - } - } - - private fun initVideoCodecsList() { - val list = arrayListOf() - for (payload in coreContext.core.videoPayloadTypes) { - val binding = DataBindingUtil.inflate( - LayoutInflater.from(requireContext()), - R.layout.settings_widget_switch_and_text, - null, - false - ) - binding.setVariable(BR.switch_title, payload.mimeType) - binding.setVariable(BR.switch_subtitle, "") - binding.setVariable(BR.text_title, "recv-fmtp") - binding.setVariable(BR.text_subtitle, "") - binding.setVariable(BR.defaultValue, payload.recvFmtp) - binding.setVariable(BR.checked, payload.enabled()) - binding.setVariable( - BR.listener, - object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - payload.enable(newValue) - } - - override fun onTextValueChanged(newValue: String) { - payload.recvFmtp = newValue - } - } - ) - binding.lifecycleOwner = viewLifecycleOwner - list.add(binding) - } - viewModel.videoCodecs.value = list - } -} diff --git a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AccountSettingsViewModel.kt b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AccountSettingsViewModel.kt deleted file mode 100644 index 1b71748ec..000000000 --- a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AccountSettingsViewModel.kt +++ /dev/null @@ -1,561 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.settings.viewmodels - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import java.lang.NumberFormatException -import kotlin.collections.ArrayList -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.main.settings.SettingListenerStub -import org.linphone.core.* -import org.linphone.core.tools.Log -import org.linphone.utils.Event -import org.linphone.utils.LinphoneUtils - -class AccountSettingsViewModelFactory(private val identity: String) : - ViewModelProvider.NewInstanceFactory() { - - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - for (account in coreContext.core.accountList) { - if (account.params.identityAddress?.asStringUriOnly() == identity) { - return AccountSettingsViewModel(account) as T - } - } - val defaultAccount = coreContext.core.defaultAccount - if (defaultAccount != null) { - return AccountSettingsViewModel(defaultAccount) as T - } - - val firstAccount = coreContext.core.accountList.first() - return AccountSettingsViewModel(firstAccount) as T - } -} - -class AccountSettingsViewModel(val account: Account) : GenericSettingsViewModel() { - val isDefault = MutableLiveData() - - val displayName = MutableLiveData() - - val identity = MutableLiveData() - - val iconResource = MutableLiveData() - val iconContentDescription = MutableLiveData() - - lateinit var accountsSettingsListener: SettingListenerStub - - val waitForUnregister = MutableLiveData() - - val accountRemovedEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val accountDefaultEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val displayUsernameInsteadOfIdentity = corePreferences.replaceSipUriByUsername - - private var accountToDelete: Account? = null - - val listener: AccountListenerStub = object : AccountListenerStub() { - override fun onRegistrationStateChanged( - account: Account, - state: RegistrationState, - message: String - ) { - if (state == RegistrationState.Cleared && account == accountToDelete) { - Log.i( - "[Account Settings] Account to remove ([${account.params.identityAddress?.asStringUriOnly()}]) registration is now cleared, removing it" - ) - waitForUnregister.value = false - deleteAccount(account) - } else { - update() - if (state == RegistrationState.Ok) { - coreContext.contactsManager.updateLocalContacts() - } - } - } - } - - /* Settings part */ - - val userNameListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - val params = account.params.clone() - val identity = params.identityAddress - if (identity != null) { - val newIdentityAddress = identity.clone() - newIdentityAddress.username = newValue - params.identityAddress = newIdentityAddress - account.params = params - } else { - Log.e("[Account Settings] Account doesn't have an identity yet") - - val domain = params.domain - val newIdentityAddress = Factory.instance().createAddress("sip:$newValue@$domain") - if (newIdentityAddress != null) { - params.identityAddress = newIdentityAddress - account.params = params - } else { - Log.e( - "[Account Settings] Failed to create identity address sip:$newValue@$domain" - ) - } - } - } - } - val userName = MutableLiveData() - - val userIdListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - val authInfo = account.findAuthInfo() - if (authInfo != null) { - val newAuthInfo = authInfo.clone() - newAuthInfo.userid = newValue - core.removeAuthInfo(authInfo) - core.addAuthInfo(newAuthInfo) - } else { - Log.e("[Account Settings] Failed to find the matching auth info") - } - } - } - val userId = MutableLiveData() - - val passwordListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - val authInfo = account.findAuthInfo() - if (authInfo != null) { - val newAuthInfo = authInfo.clone() - newAuthInfo.password = newValue - core.removeAuthInfo(authInfo) - core.addAuthInfo(newAuthInfo) - } else { - Log.w("[Account Settings] Failed to find the matching auth info") - val params = account.params - val identity = params.identityAddress - if (identity != null && identity.username != null) { - val newAuthInfo = Factory.instance() - .createAuthInfo( - identity.username!!, - userId.value, - newValue, - null, - null, - identity.domain - ) - core.addAuthInfo(newAuthInfo) - } else { - Log.e( - "[Account Settings] Failed to find the user's identity, can't create a new auth info" - ) - } - } - } - } - val password = MutableLiveData() - - val domainListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - val params = account.params.clone() - val identity = params.identityAddress - if (identity != null) { - val authInfo = account.findAuthInfo() - if (authInfo != null) { - val newAuthInfo = authInfo.clone() - newAuthInfo.domain = newValue - core.removeAuthInfo(authInfo) - core.addAuthInfo(newAuthInfo) - } else { - Log.e("[Account Settings] Failed to find the matching auth info") - } - - identity.domain = newValue - params.identityAddress = identity - account.params = params - } else { - Log.e("[Account Settings] Account doesn't have an identity yet") - } - } - } - val domain = MutableLiveData() - - val displayNameListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - val params = account.params.clone() - val identity = params.identityAddress - if (identity != null) { - identity.displayName = newValue - params.identityAddress = identity - account.params = params - } else { - Log.e("[Account Settings] Account doesn't have an identity yet") - } - } - } - // displayName mutable is above - - val disableListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - val params = account.params.clone() - params.isRegisterEnabled = !newValue - account.params = params - } - } - val disable = MutableLiveData() - - val isDefaultListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - if (newValue) { - core.defaultAccount = account - accountDefaultEvent.value = Event(true) - } - } - } - // isDefault mutable is above - - private fun deleteAccount(account: Account) { - val authInfo = account.findAuthInfo() - if (authInfo != null) { - Log.i("[Account Settings] Found auth info $authInfo, removing it.") - core.removeAuthInfo(authInfo) - } else { - Log.w("[Account Settings] Couldn't find matching auth info...") - } - - core.removeAccount(account) - accountToDelete = null - accountRemovedEvent.value = Event(true) - } - - val deleteAccountRequiredEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - val deleteListener = object : SettingListenerStub() { - override fun onClicked() { - deleteAccountRequiredEvent.value = Event(true) - } - } - - val pushNotificationListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - val params = account.params.clone() - params.pushNotificationAllowed = newValue - account.params = params - } - } - val pushNotification = MutableLiveData() - val pushNotificationsAvailable = MutableLiveData() - - val transportListener = object : SettingListenerStub() { - override fun onListValueChanged(position: Int) { - val params = account.params.clone() - params.transport = TransportType.fromInt(position) - account.params = params - proxy.value = account.params.serverAddress?.asStringUriOnly() - } - } - val transportIndex = MutableLiveData() - val transportLabels = MutableLiveData>() - - val proxyListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - val params = account.params.clone() - val address = Factory.instance().createAddress(newValue) - if (address != null) { - params.serverAddress = address - account.params = params - transportIndex.value = account.params.transport.toInt() - } else { - Log.e("[Account Settings] Couldn't parse address: $newValue") - } - } - } - val proxy = MutableLiveData() - - val outboundProxyListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - val params = account.params.clone() - params.isOutboundProxyEnabled = newValue - account.params = params - } - } - val outboundProxy = MutableLiveData() - - val stunServerListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - val params = account.params.clone() - val natPolicy = params.natPolicy - val newNatPolicy = natPolicy?.clone() ?: core.createNatPolicy() - newNatPolicy.stunServer = newValue - newNatPolicy.isStunEnabled = newValue.isNotEmpty() - params.natPolicy = newNatPolicy - account.params = params - stunServer.value = newValue - } - } - val stunServer = MutableLiveData() - - val iceListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - val params = account.params.clone() - val natPolicy = params.natPolicy - val newNatPolicy = natPolicy?.clone() ?: core.createNatPolicy() - newNatPolicy.isIceEnabled = newValue - params.natPolicy = newNatPolicy - account.params = params - } - } - val ice = MutableLiveData() - - val avpfListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - val params = account.params.clone() - params.avpfMode = if (newValue) AVPFMode.Enabled else AVPFMode.Disabled - account.params = params - } - } - val avpf = MutableLiveData() - - val avpfRrIntervalListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - try { - val params = account.params.clone() - params.avpfRrInterval = newValue.toInt() - account.params = params - } catch (nfe: NumberFormatException) { - Log.e("[Account Settings] Failed to set AVPF RR interval ($newValue): $nfe") - } - } - } - val avpfRrInterval = MutableLiveData() - - val expiresListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - try { - val params = account.params.clone() - params.expires = newValue.toInt() - account.params = params - } catch (nfe: NumberFormatException) { - Log.e("[Account Settings] Failed to set expires ($newValue): $nfe") - } - } - } - val expires = MutableLiveData() - - val prefixListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - val params = account.params.clone() - params.internationalPrefix = newValue - account.params = params - } - } - val prefix = MutableLiveData() - - val dialPrefixListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - val params = account.params.clone() - params.useInternationalPrefixForCallsAndChats = newValue - account.params = params - } - } - val dialPrefix = MutableLiveData() - - val escapePlusListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - val params = account.params.clone() - params.isDialEscapePlusEnabled = newValue - account.params = params - } - } - val escapePlus = MutableLiveData() - - val linkPhoneNumberListener = object : SettingListenerStub() { - override fun onClicked() { - linkPhoneNumberEvent.value = Event(true) - } - } - val linkPhoneNumberEvent = MutableLiveData>() - val hideLinkPhoneNumber = MutableLiveData() - - val conferenceFactoryUriListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - val params = account.params.clone() - Log.i( - "[Account Settings] Forcing conference factory on account ${params.identityAddress?.asString()} to value: $newValue" - ) - params.conferenceFactoryUri = newValue - account.params = params - } - } - val conferenceFactoryUri = MutableLiveData() - - val audioVideoConferenceFactoryUriListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - val params = account.params.clone() - val uri = coreContext.core.interpretUrl(newValue, false) - Log.i( - "[Account Settings] Forcing audio/video conference factory on account ${params.identityAddress?.asString()} to value: $newValue" - ) - params.audioVideoConferenceFactoryAddress = uri - account.params = params - } - } - val audioVideoConferenceFactoryUri = MutableLiveData() - - val limeServerUrlListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - val params = account.params.clone() - params.limeServerUrl = newValue - account.params = params - } - } - val limeServerUrl = MutableLiveData() - - val disableBundleModeListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - val params = account.params.clone() - params.isRtpBundleEnabled = !newValue - account.params = params - } - } - val disableBundleMode = MutableLiveData() - - init { - update() - account.addListener(listener) - initTransportList() - } - - override fun onCleared() { - destroy() - super.onCleared() - } - - fun destroy() { - accountsSettingsListener = object : SettingListenerStub() {} - account.removeListener(listener) - } - - private fun update() { - isDefault.value = core.defaultAccount == account - val params = account.params - val identityAddress = params.identityAddress - if (identityAddress != null) { - displayName.value = identityAddress.displayName ?: "" - identity.value = identityAddress.asStringUriOnly() - } - - iconResource.value = when (account.state) { - RegistrationState.Ok -> R.drawable.led_registered - RegistrationState.Failed -> R.drawable.led_error - RegistrationState.Progress, RegistrationState.Refreshing -> R.drawable.led_registration_in_progress - else -> R.drawable.led_not_registered - } - iconContentDescription.value = when (account.state) { - RegistrationState.Ok -> R.string.status_connected - RegistrationState.Progress, RegistrationState.Refreshing -> R.string.status_in_progress - RegistrationState.Failed -> R.string.status_error - else -> R.string.status_not_connected - } - - userName.value = params.identityAddress?.username - userId.value = account.findAuthInfo()?.userid - domain.value = params.identityAddress?.domain - disable.value = !params.isRegisterEnabled - pushNotification.value = params.pushNotificationAllowed - pushNotificationsAvailable.value = LinphoneUtils.isPushNotificationAvailable() - proxy.value = params.serverAddress?.asStringUriOnly() - outboundProxy.value = params.isOutboundProxyEnabled - stunServer.value = params.natPolicy?.stunServer - ice.value = params.natPolicy?.isIceEnabled - avpf.value = params.avpfMode == AVPFMode.Enabled - avpfRrInterval.value = params.avpfRrInterval - expires.value = params.expires - prefix.value = params.internationalPrefix - dialPrefix.value = params.useInternationalPrefixForCallsAndChats - escapePlus.value = params.isDialEscapePlusEnabled - - conferenceFactoryUri.value = params.conferenceFactoryUri - audioVideoConferenceFactoryUri.value = params.audioVideoConferenceFactoryAddress?.asStringUriOnly() - limeServerUrl.value = params.limeServerUrl - - hideLinkPhoneNumber.value = corePreferences.hideLinkPhoneNumber || params.identityAddress?.domain != corePreferences.defaultDomain - disableBundleMode.value = !params.isRtpBundleEnabled - } - - private fun initTransportList() { - val labels = arrayListOf() - - // Keep following in the same order as TransportType enum - labels.add(prefs.getString(R.string.account_settings_transport_udp)) - labels.add(prefs.getString(R.string.account_settings_transport_tcp)) - labels.add(prefs.getString(R.string.account_settings_transport_tls)) - if (corePreferences.allowDtlsTransport) { - labels.add(prefs.getString(R.string.account_settings_transport_dtls)) - } - - transportLabels.value = labels - transportIndex.value = account.params.transport.toInt() - } - - fun startDeleteAccount() { - Log.i( - "[Account Settings] Starting to delete account [${account.params.identityAddress?.asStringUriOnly()}]" - ) - accountToDelete = account - - val registered = account.state == RegistrationState.Ok - waitForUnregister.value = registered - - if (core.defaultAccount == account) { - Log.i("[Account Settings] Account was default, let's look for a replacement") - for (accountIterator in core.accountList) { - if (account != accountIterator) { - core.defaultAccount = accountIterator - Log.i( - "[Account Settings] New default account is [${accountIterator.params.identityAddress?.asStringUriOnly()}]" - ) - break - } - } - } - - val params = account.params.clone() - params.isRegisterEnabled = false - account.params = params - - if (!registered) { - Log.w( - "[Account Settings] Account isn't registered, don't unregister before removing it" - ) - deleteAccount(account) - } else { - Log.i( - "[Account Settings] Waiting for account registration to be cleared before removing it" - ) - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AdvancedSettingsViewModel.kt b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AdvancedSettingsViewModel.kt deleted file mode 100644 index 118666070..000000000 --- a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AdvancedSettingsViewModel.kt +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.settings.viewmodels - -import androidx.lifecycle.MutableLiveData -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.main.settings.SettingListenerStub -import org.linphone.activities.main.viewmodels.LogsUploadViewModel -import org.linphone.core.CoreContext -import org.linphone.core.Factory -import org.linphone.core.LogLevel -import org.linphone.utils.Event - -class AdvancedSettingsViewModel : LogsUploadViewModel() { - private val prefs = corePreferences - private val core = coreContext.core - - val debugModeListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - prefs.debugLogs = newValue - val logLevel = if (newValue) LogLevel.Message else LogLevel.Error - Factory.instance().loggingService.setLogLevel(logLevel) - } - } - val debugMode = MutableLiveData() - - val logsServerUrlListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - core.logCollectionUploadServerUrl = newValue - } - } - val logsServerUrl = MutableLiveData() - - val sendDebugLogsListener = object : SettingListenerStub() { - override fun onClicked() { - uploadLogs() - } - } - - val resetDebugLogsListener = object : SettingListenerStub() { - override fun onClicked() { - resetLogs() - } - } - - val backgroundModeListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - prefs.keepServiceAlive = newValue - - if (newValue) { - coreContext.notificationsManager.startForeground() - } else { - coreContext.notificationsManager.stopForegroundNotificationIfPossible() - } - } - } - val backgroundMode = MutableLiveData() - val backgroundModeEnabled = MutableLiveData() - - val autoStartListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - prefs.autoStart = newValue - } - } - val autoStart = MutableLiveData() - - val darkModeListener = object : SettingListenerStub() { - override fun onListValueChanged(position: Int) { - darkModeIndex.value = position - val value = darkModeValues[position] - prefs.darkMode = value - setNightModeEvent.value = Event(value) - } - } - val darkModeIndex = MutableLiveData() - val darkModeLabels = MutableLiveData>() - private val darkModeValues = arrayListOf(-1, 0, 1) - val setNightModeEvent = MutableLiveData>() - - val animationsListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - prefs.enableAnimations = newValue - } - } - val animations = MutableLiveData() - - val deviceNameListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - prefs.deviceName = newValue - } - } - val deviceName = MutableLiveData() - - val remoteProvisioningUrlListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - if (newValue.isEmpty()) { - core.provisioningUri = null - } else { - core.provisioningUri = newValue - } - } - } - val remoteProvisioningUrl = MutableLiveData() - - val vfsListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - prefs.vfsEnabled = newValue - if (newValue) { - CoreContext.activateVFS() - // Don't do that when VFS is enabled - prefs.makePublicMediaFilesDownloaded = false - } - } - } - val vfs = MutableLiveData() - - val disableSecureFragmentListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - prefs.disableSecureMode = newValue - } - } - val disableSecureFragment = MutableLiveData() - - val goToBatterySettingsListener = object : SettingListenerStub() { - override fun onClicked() { - goToBatterySettingsEvent.value = Event(true) - } - } - val goToBatterySettingsEvent = MutableLiveData>() - val batterySettingsVisibility = MutableLiveData() - - val goToPowerManagerSettingsListener = object : SettingListenerStub() { - override fun onClicked() { - goToPowerManagerSettingsEvent.value = Event(true) - } - } - val goToPowerManagerSettingsEvent = MutableLiveData>() - val powerManagerSettingsVisibility = MutableLiveData() - - val goToAndroidSettingsListener = object : SettingListenerStub() { - override fun onClicked() { - goToAndroidSettingsEvent.value = Event(true) - } - } - val goToAndroidSettingsEvent = MutableLiveData>() - - init { - debugMode.value = prefs.debugLogs - logsServerUrl.value = core.logCollectionUploadServerUrl - backgroundMode.value = prefs.keepServiceAlive - autoStart.value = prefs.autoStart - - val labels = arrayListOf() - labels.add(prefs.getString(R.string.advanced_settings_dark_mode_label_auto)) - labels.add(prefs.getString(R.string.advanced_settings_dark_mode_label_no)) - labels.add(prefs.getString(R.string.advanced_settings_dark_mode_label_yes)) - darkModeLabels.value = labels - darkModeIndex.value = darkModeValues.indexOf(prefs.darkMode) - - animations.value = prefs.enableAnimations - deviceName.value = prefs.deviceName - remoteProvisioningUrl.value = core.provisioningUri - vfs.value = prefs.vfsEnabled - disableSecureFragment.value = prefs.disableSecureMode - - batterySettingsVisibility.value = true - } -} diff --git a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AudioSettingsViewModel.kt b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AudioSettingsViewModel.kt deleted file mode 100644 index 9e7bc11ce..000000000 --- a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/AudioSettingsViewModel.kt +++ /dev/null @@ -1,296 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.settings.viewmodels - -import androidx.databinding.ViewDataBinding -import androidx.lifecycle.MutableLiveData -import java.lang.NumberFormatException -import org.linphone.R -import org.linphone.activities.main.settings.SettingListenerStub -import org.linphone.core.AudioDevice -import org.linphone.core.Core -import org.linphone.core.CoreListenerStub -import org.linphone.core.EcCalibratorStatus -import org.linphone.utils.Event -import org.linphone.utils.PermissionHelper - -class AudioSettingsViewModel : GenericSettingsViewModel() { - val askAudioRecordPermissionForEchoCancellerCalibrationEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - val askAudioRecordPermissionForEchoTesterEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val softwareEchoCancellerListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - core.isEchoCancellationEnabled = newValue - if (!newValue) { - core.resetEchoCancellationCalibration() - softwareEchoCalibration.value = prefs.getString( - R.string.audio_settings_echo_canceller_calibration_summary - ) - } - } - } - val softwareEchoCanceller = MutableLiveData() - val listener = object : CoreListenerStub() { - override fun onEcCalibrationResult(core: Core, status: EcCalibratorStatus, delayMs: Int) { - if (status == EcCalibratorStatus.InProgress) return - echoCancellerCalibrationFinished(status, delayMs) - } - } - - val softwareEchoCancellerCalibrationListener = object : SettingListenerStub() { - override fun onClicked() { - if (PermissionHelper.get().hasRecordAudioPermission()) { - startEchoCancellerCalibration() - } else { - askAudioRecordPermissionForEchoCancellerCalibrationEvent.value = Event(true) - } - } - } - val softwareEchoCalibration = MutableLiveData() - - val echoTesterListener = object : SettingListenerStub() { - override fun onClicked() { - if (PermissionHelper.get().hasRecordAudioPermission()) { - if (echoTesterIsRunning) { - stopEchoTester() - } else { - startEchoTester() - } - } else { - askAudioRecordPermissionForEchoTesterEvent.value = Event(true) - } - } - } - private var echoTesterIsRunning = false - val echoTesterStatus = MutableLiveData() - val showEchoTester = MutableLiveData() - - val adaptiveRateControlListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - core.isAdaptiveRateControlEnabled = newValue - } - } - val adaptiveRateControl = MutableLiveData() - - val inputAudioDeviceListener = object : SettingListenerStub() { - override fun onListValueChanged(position: Int) { - val values = inputAudioDeviceValues.value.orEmpty() - if (values.size > position) { - core.defaultInputAudioDevice = values[position] - } - } - } - val inputAudioDeviceIndex = MutableLiveData() - val inputAudioDeviceLabels = MutableLiveData>() - private val inputAudioDeviceValues = MutableLiveData>() - - val outputAudioDeviceListener = object : SettingListenerStub() { - override fun onListValueChanged(position: Int) { - val values = outputAudioDeviceValues.value.orEmpty() - if (values.size > position) { - core.defaultOutputAudioDevice = values[position] - } - } - } - val outputAudioDeviceIndex = MutableLiveData() - val outputAudioDeviceLabels = MutableLiveData>() - private val outputAudioDeviceValues = MutableLiveData>() - - val preferBluetoothDevicesListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - prefs.routeAudioToBluetoothIfAvailable = newValue - } - } - val preferBluetoothDevices = MutableLiveData() - - val codecBitrateListener = object : SettingListenerStub() { - override fun onListValueChanged(position: Int) { - for (payloadType in core.audioPayloadTypes) { - if (payloadType.isVbr) { - payloadType.normalBitrate = codecBitrateValues[position] - } - } - } - } - val codecBitrateIndex = MutableLiveData() - val codecBitrateLabels = MutableLiveData>() - private val codecBitrateValues = arrayListOf(10, 15, 20, 36, 64, 128) - - val microphoneGainListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - try { - core.micGainDb = newValue.toFloat() - } catch (_: NumberFormatException) { - } - } - } - val microphoneGain = MutableLiveData() - - val playbackGainListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - try { - core.playbackGainDb = newValue.toFloat() - } catch (_: NumberFormatException) { - } - } - } - val playbackGain = MutableLiveData() - - val audioCodecs = MutableLiveData>() - - init { - softwareEchoCanceller.value = core.isEchoCancellationEnabled - adaptiveRateControl.value = core.isAdaptiveRateControlEnabled - softwareEchoCalibration.value = if (core.echoCancellationCalibration > 0) { - prefs.getString(R.string.audio_settings_echo_cancellation_calibration_value).format( - core.echoCancellationCalibration - ) - } else { - prefs.getString(R.string.audio_settings_echo_canceller_calibration_summary) - } - echoTesterStatus.value = prefs.getString(R.string.audio_settings_echo_tester_summary) - showEchoTester.value = prefs.debugLogs // Don't show echo tester unless debug mode is enabled, may confuse user over what it should be used for - - preferBluetoothDevices.value = prefs.routeAudioToBluetoothIfAvailable - initInputAudioDevicesList() - initOutputAudioDevicesList() - initCodecBitrateList() - microphoneGain.value = core.micGainDb - playbackGain.value = core.playbackGainDb - } - - fun startEchoCancellerCalibration() { - if (echoTesterIsRunning) { - stopEchoTester() - } - - core.addListener(listener) - core.startEchoCancellerCalibration() - softwareEchoCalibration.value = prefs.getString( - R.string.audio_settings_echo_cancellation_calibration_started - ) - } - - fun echoCancellerCalibrationFinished(status: EcCalibratorStatus, delay: Int) { - core.removeListener(listener) - - when (status) { - EcCalibratorStatus.DoneNoEcho -> { - softwareEchoCalibration.value = prefs.getString( - R.string.audio_settings_echo_cancellation_calibration_no_echo - ) - softwareEchoCanceller.value = false - } - EcCalibratorStatus.Done -> { - softwareEchoCalibration.value = prefs.getString( - R.string.audio_settings_echo_cancellation_calibration_value - ).format(delay) - softwareEchoCanceller.value = true - } - EcCalibratorStatus.Failed -> { - softwareEchoCalibration.value = prefs.getString( - R.string.audio_settings_echo_cancellation_calibration_failed - ) - } - EcCalibratorStatus.InProgress -> { // We should never get here but still - softwareEchoCalibration.value = prefs.getString( - R.string.audio_settings_echo_cancellation_calibration_started - ) - } - } - } - - fun startEchoTester() { - echoTesterIsRunning = true - echoTesterStatus.value = prefs.getString( - R.string.audio_settings_echo_tester_summary_is_running - ) - core.startEchoTester(0) - } - - fun stopEchoTester() { - echoTesterIsRunning = false - echoTesterStatus.value = prefs.getString( - R.string.audio_settings_echo_tester_summary_is_stopped - ) - core.stopEchoTester() - } - - private fun initInputAudioDevicesList() { - val labels = arrayListOf() - val values = arrayListOf() - var index = 0 - val default = core.defaultInputAudioDevice - - for (audioDevice in core.extendedAudioDevices) { - if (audioDevice.hasCapability(AudioDevice.Capabilities.CapabilityRecord)) { - labels.add(audioDevice.id) - values.add(audioDevice) - if (audioDevice.id == default?.id) { - inputAudioDeviceIndex.value = index - } - index += 1 - } - } - inputAudioDeviceLabels.value = labels - inputAudioDeviceValues.value = values - } - - private fun initOutputAudioDevicesList() { - val labels = arrayListOf() - val values = arrayListOf() - var index = 0 - val default = core.defaultOutputAudioDevice - - for (audioDevice in core.extendedAudioDevices) { - if (audioDevice.hasCapability(AudioDevice.Capabilities.CapabilityPlay)) { - labels.add(audioDevice.id) - values.add(audioDevice) - if (audioDevice.id == default?.id) { - outputAudioDeviceIndex.value = index - } - index += 1 - } - } - outputAudioDeviceLabels.value = labels - outputAudioDeviceValues.value = values - } - - private fun initCodecBitrateList() { - val labels = arrayListOf() - for (value in codecBitrateValues) { - labels.add("$value kbits/s") - } - codecBitrateLabels.value = labels - - var currentValue = 36 - for (payloadType in core.audioPayloadTypes) { - if (payloadType.isVbr && payloadType.normalBitrate in codecBitrateValues) { - currentValue = payloadType.normalBitrate - break - } - } - codecBitrateIndex.value = codecBitrateValues.indexOf(currentValue) - } -} diff --git a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/CallSettingsViewModel.kt b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/CallSettingsViewModel.kt deleted file mode 100644 index 45fb318f1..000000000 --- a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/CallSettingsViewModel.kt +++ /dev/null @@ -1,317 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.settings.viewmodels - -import android.os.Vibrator -import androidx.lifecycle.MutableLiveData -import java.io.File -import java.util.* -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R -import org.linphone.activities.main.settings.SettingListenerStub -import org.linphone.core.MediaEncryption -import org.linphone.core.tools.Log -import org.linphone.mediastream.Version -import org.linphone.telecom.TelecomHelper -import org.linphone.utils.AppUtils -import org.linphone.utils.Event - -class CallSettingsViewModel : GenericSettingsViewModel() { - val deviceRingtoneListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - core.ring = if (newValue) null else prefs.defaultRingtonePath - } - } - val deviceRingtone = MutableLiveData() - - val ringtoneListener = object : SettingListenerStub() { - override fun onListValueChanged(position: Int) { - if (position == 0) { - core.ring = null - } else { - core.ring = ringtoneValues[position] - } - } - } - val ringtoneIndex = MutableLiveData() - val ringtoneLabels = MutableLiveData>() - private val ringtoneValues = arrayListOf() - val showRingtonesList = MutableLiveData() - - val vibrateOnIncomingCallListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - core.isVibrationOnIncomingCallEnabled = newValue - } - } - val vibrateOnIncomingCall = MutableLiveData() - val canVibrate = MutableLiveData() - - val encryptionListener = object : SettingListenerStub() { - override fun onListValueChanged(position: Int) { - core.mediaEncryption = MediaEncryption.fromInt(encryptionValues[position]) - encryptionIndex.value = position - if (position == 0) { - encryptionMandatory.value = false - } - } - } - val encryptionIndex = MutableLiveData() - val encryptionLabels = MutableLiveData>() - private val encryptionValues = arrayListOf() - - val encryptionMandatoryListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - core.isMediaEncryptionMandatory = newValue - } - } - val encryptionMandatory = MutableLiveData() - - val useTelecomManagerListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - if (newValue) { - enableTelecomManagerEvent.value = Event(true) - } else { - if (TelecomHelper.exists()) { - Log.i("[Call Settings] Removing Telecom Manager account & destroying singleton") - TelecomHelper.get().removeAccount() - TelecomHelper.get().destroy() - TelecomHelper.destroy() - - Log.w("[Call Settings] Disabling Telecom Manager auto-enable") - prefs.manuallyDisabledTelecomManager = true - } - prefs.useTelecomManager = false - } - } - } - val useTelecomManager = MutableLiveData() - val enableTelecomManagerEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - val api29OrHigher = MutableLiveData() - - val overlayListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - prefs.showCallOverlay = newValue - } - } - val overlay = MutableLiveData() - - val systemWideOverlayListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - if (newValue) systemWideOverlayEnabledEvent.value = Event(true) - prefs.systemWideCallOverlay = newValue - } - } - val systemWideOverlay = MutableLiveData() - val systemWideOverlayEnabledEvent = MutableLiveData>() - - val sipInfoDtmfListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - core.useInfoForDtmf = newValue - } - } - val sipInfoDtmf = MutableLiveData() - - val rfc2833DtmfListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - core.useRfc2833ForDtmf = newValue - } - } - val rfc2833Dtmf = MutableLiveData() - - val autoStartCallRecordingListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - prefs.automaticallyStartCallRecording = newValue - } - } - val autoStartCallRecording = MutableLiveData() - - val remoteCallRecordingListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - core.isRecordAwareEnabled = newValue - } - } - val remoteCallRecording = MutableLiveData() - - val autoStartListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - prefs.callRightAway = newValue - } - } - val autoStart = MutableLiveData() - - val autoAnswerListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - prefs.autoAnswerEnabled = newValue - } - } - val autoAnswer = MutableLiveData() - - val autoAnswerDelayListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - try { - prefs.autoAnswerDelay = newValue.toInt() - } catch (_: NumberFormatException) { - } - } - } - val autoAnswerDelay = MutableLiveData() - - val incomingTimeoutListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - try { - core.incTimeout = newValue.toInt() - } catch (_: NumberFormatException) { - } - } - } - val incomingTimeout = MutableLiveData() - - val voiceMailUriListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - voiceMailUri.value = newValue - prefs.voiceMailUri = newValue - } - } - val voiceMailUri = MutableLiveData() - - val redirectToVoiceMailIncomingDeclinedCallsListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - prefs.redirectDeclinedCallToVoiceMail = newValue - } - } - val redirectToVoiceMailIncomingDeclinedCalls = MutableLiveData() - - val acceptEarlyMediaListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - prefs.acceptEarlyMedia = newValue - } - } - val acceptEarlyMedia = MutableLiveData() - - val ringDuringEarlyMediaListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - core.ringDuringIncomingEarlyMedia = newValue - } - } - val ringDuringEarlyMedia = MutableLiveData() - - val pauseCallsWhenAudioFocusIsLostListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - prefs.pauseCallsWhenAudioFocusIsLost = newValue - } - } - - val pauseCallsWhenAudioFocusIsLost = MutableLiveData() - - val goToAndroidNotificationSettingsListener = object : SettingListenerStub() { - override fun onClicked() { - goToAndroidNotificationSettingsEvent.value = Event(true) - } - } - val goToAndroidNotificationSettingsEvent = MutableLiveData>() - - init { - initRingtonesList() - deviceRingtone.value = core.ring == null - showRingtonesList.value = prefs.showAllRingtones - - vibrateOnIncomingCall.value = core.isVibrationOnIncomingCallEnabled - val vibrator = coreContext.context.getSystemService(Vibrator::class.java) - canVibrate.value = vibrator.hasVibrator() - if (canVibrate.value == false) { - Log.w("[Call Settings] Device doesn't seem to have a vibrator, hiding related setting") - } - - initEncryptionList() - encryptionMandatory.value = core.isMediaEncryptionMandatory - - useTelecomManager.value = prefs.useTelecomManager - api29OrHigher.value = Version.sdkAboveOrEqual(Version.API29_ANDROID_10) - - overlay.value = prefs.showCallOverlay - systemWideOverlay.value = prefs.systemWideCallOverlay - sipInfoDtmf.value = core.useInfoForDtmf - rfc2833Dtmf.value = core.useRfc2833ForDtmf - autoStartCallRecording.value = prefs.automaticallyStartCallRecording - remoteCallRecording.value = core.isRecordAwareEnabled - autoStart.value = prefs.callRightAway - autoAnswer.value = prefs.autoAnswerEnabled - autoAnswerDelay.value = prefs.autoAnswerDelay - incomingTimeout.value = core.incTimeout - voiceMailUri.value = prefs.voiceMailUri - redirectToVoiceMailIncomingDeclinedCalls.value = prefs.redirectDeclinedCallToVoiceMail - acceptEarlyMedia.value = prefs.acceptEarlyMedia - ringDuringEarlyMedia.value = core.ringDuringIncomingEarlyMedia - pauseCallsWhenAudioFocusIsLost.value = prefs.pauseCallsWhenAudioFocusIsLost - } - - private fun initRingtonesList() { - val labels = arrayListOf() - labels.add(AppUtils.getString(R.string.call_settings_device_ringtone_title)) - ringtoneValues.add("") - - val directory = File(prefs.ringtonesPath) - val files = directory.listFiles() - for (ringtone in files.orEmpty()) { - if (ringtone.absolutePath.endsWith(".mkv")) { - val name = ringtone.name - .substringBefore(".") - .replace("_", " ") - .capitalize(Locale.getDefault()) - labels.add(name) - ringtoneValues.add(ringtone.absolutePath) - } - } - - ringtoneLabels.value = labels - ringtoneIndex.value = if (core.ring == null) 0 else ringtoneValues.indexOf(core.ring) - } - - private fun initEncryptionList() { - val labels = arrayListOf() - - labels.add(prefs.getString(R.string.call_settings_media_encryption_none)) - encryptionValues.add(MediaEncryption.None.toInt()) - - if (core.mediaEncryptionSupported(MediaEncryption.SRTP)) { - labels.add(prefs.getString(R.string.call_settings_media_encryption_srtp)) - encryptionValues.add(MediaEncryption.SRTP.toInt()) - } - if (core.mediaEncryptionSupported(MediaEncryption.ZRTP)) { - if (core.postQuantumAvailable) { - labels.add( - prefs.getString(R.string.call_settings_media_encryption_zrtp_post_quantum) - ) - } else { - labels.add(prefs.getString(R.string.call_settings_media_encryption_zrtp)) - } - encryptionValues.add(MediaEncryption.ZRTP.toInt()) - } - if (core.mediaEncryptionSupported(MediaEncryption.DTLS)) { - labels.add(prefs.getString(R.string.call_settings_media_encryption_dtls)) - encryptionValues.add(MediaEncryption.DTLS.toInt()) - } - - encryptionLabels.value = labels - encryptionIndex.value = encryptionValues.indexOf(core.mediaEncryption.toInt()) - } -} diff --git a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/ChatSettingsViewModel.kt b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/ChatSettingsViewModel.kt deleted file mode 100644 index d926b7b45..000000000 --- a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/ChatSettingsViewModel.kt +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.settings.viewmodels - -import androidx.lifecycle.MutableLiveData -import java.lang.NumberFormatException -import org.linphone.R -import org.linphone.activities.main.settings.SettingListenerStub -import org.linphone.utils.Event - -class ChatSettingsViewModel : GenericSettingsViewModel() { - val markAsReadNotifDismissalListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - prefs.markAsReadUponChatMessageNotificationDismissal = newValue - } - } - val markAsReadNotifDismissal = MutableLiveData() - - val fileSharingUrlListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - core.fileTransferServer = newValue - } - } - val fileSharingUrl = MutableLiveData() - - val autoDownloadListener = object : SettingListenerStub() { - override fun onListValueChanged(position: Int) { - val maxSize = when (position) { - 0 -> -1 - 1 -> 0 - else -> 10000000 - } - core.maxSizeForAutoDownloadIncomingFiles = maxSize - autoDownloadMaxSize.value = maxSize - updateAutoDownloadIndexFromMaxSize(maxSize) - } - } - val autoDownloadIndex = MutableLiveData() - val autoDownloadLabels = MutableLiveData>() - - val autoDownloadMaxSizeListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - try { - val maxSize = newValue.toInt() - core.maxSizeForAutoDownloadIncomingFiles = maxSize - updateAutoDownloadIndexFromMaxSize(maxSize) - } catch (_: NumberFormatException) { - } - } - } - val autoDownloadMaxSize = MutableLiveData() - - val autoDownloadVoiceRecordingsListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - core.isAutoDownloadVoiceRecordingsEnabled = newValue - autoDownloadVoiceRecordings.value = newValue - } - } - val autoDownloadVoiceRecordings = MutableLiveData() - - val downloadedMediaPublicListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - prefs.makePublicMediaFilesDownloaded = newValue - downloadedMediaPublic.value = newValue - } - } - val downloadedMediaPublic = MutableLiveData() - - val hideNotificationContentListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - prefs.hideChatMessageContentInNotification = newValue - hideNotificationContent.value = newValue - } - } - val hideNotificationContent = MutableLiveData() - - val useInAppFileViewerListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - prefs.useInAppFileViewerForNonEncryptedFiles = newValue - useInAppFileViewer.value = newValue - } - } - val useInAppFileViewer = MutableLiveData() - - val launcherShortcutsListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - prefs.chatRoomShortcuts = newValue - launcherShortcutsEvent.value = Event(newValue) - } - } - val launcherShortcuts = MutableLiveData() - val launcherShortcutsEvent = MutableLiveData>() - - val hideEmptyRoomsListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - prefs.hideEmptyRooms = newValue - reloadChatRoomsEvent.value = Event(true) - } - } - val hideEmptyRooms = MutableLiveData() - - val hideRoomsRemovedProxiesListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - prefs.hideRoomsFromRemovedProxies = newValue - reloadChatRoomsEvent.value = Event(true) - } - } - val hideRoomsRemovedProxies = MutableLiveData() - - val goToAndroidNotificationSettingsListener = object : SettingListenerStub() { - override fun onClicked() { - goToAndroidNotificationSettingsEvent.value = Event(true) - } - } - val goToAndroidNotificationSettingsEvent = MutableLiveData>() - - val vfs = MutableLiveData() - - val reloadChatRoomsEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - init { - markAsReadNotifDismissal.value = prefs.markAsReadUponChatMessageNotificationDismissal - downloadedMediaPublic.value = prefs.makePublicMediaFilesDownloaded && !prefs.vfsEnabled - useInAppFileViewer.value = prefs.useInAppFileViewerForNonEncryptedFiles || prefs.vfsEnabled - hideNotificationContent.value = prefs.hideChatMessageContentInNotification - initAutoDownloadList() - autoDownloadVoiceRecordings.value = core.isAutoDownloadVoiceRecordingsEnabled - launcherShortcuts.value = prefs.chatRoomShortcuts - hideEmptyRooms.value = prefs.hideEmptyRooms - hideRoomsRemovedProxies.value = prefs.hideRoomsFromRemovedProxies - fileSharingUrl.value = core.fileTransferServer - vfs.value = prefs.vfsEnabled - } - - private fun initAutoDownloadList() { - val labels = arrayListOf() - labels.add(prefs.getString(R.string.chat_settings_auto_download_never)) - labels.add(prefs.getString(R.string.chat_settings_auto_download_always)) - labels.add(prefs.getString(R.string.chat_settings_auto_download_under_size)) - autoDownloadLabels.value = labels - - val currentValue = core.maxSizeForAutoDownloadIncomingFiles - autoDownloadMaxSize.value = currentValue - updateAutoDownloadIndexFromMaxSize(currentValue) - } - - private fun updateAutoDownloadIndexFromMaxSize(maxSize: Int) { - autoDownloadIndex.value = when (maxSize) { - -1 -> 0 - 0 -> 1 - else -> 2 - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/ConferencesSettingsViewModel.kt b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/ConferencesSettingsViewModel.kt deleted file mode 100644 index 0fadbee7f..000000000 --- a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/ConferencesSettingsViewModel.kt +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2010-2022 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.settings.viewmodels - -import androidx.lifecycle.MutableLiveData -import org.linphone.R -import org.linphone.activities.main.settings.SettingListenerStub -import org.linphone.core.Conference.Layout - -class ConferencesSettingsViewModel : GenericSettingsViewModel() { - val layoutListener = object : SettingListenerStub() { - override fun onListValueChanged(position: Int) { - core.defaultConferenceLayout = Layout.fromInt(layoutValues[position]) - layoutIndex.value = position - } - } - val layoutIndex = MutableLiveData() - val layoutLabels = MutableLiveData>() - private val layoutValues = arrayListOf() - - val enableBroadcastListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - prefs.disableBroadcastConference = !newValue - } - } - val enableBroadcast = MutableLiveData() - - init { - initLayoutsList() - enableBroadcast.value = !prefs.disableBroadcastConference - } - - private fun initLayoutsList() { - val labels = arrayListOf() - - labels.add(prefs.getString(R.string.conference_display_mode_active_speaker)) - layoutValues.add(Layout.ActiveSpeaker.toInt()) - - labels.add(prefs.getString(R.string.conference_display_mode_mosaic)) - layoutValues.add(Layout.Grid.toInt()) - - layoutLabels.value = labels - layoutIndex.value = layoutValues.indexOf(core.defaultConferenceLayout.toInt()) - } -} diff --git a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/ContactsSettingsViewModel.kt b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/ContactsSettingsViewModel.kt deleted file mode 100644 index 68847e3fb..000000000 --- a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/ContactsSettingsViewModel.kt +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.settings.viewmodels - -import androidx.lifecycle.MutableLiveData -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.activities.main.settings.SettingListenerStub -import org.linphone.core.ConsolidatedPresence -import org.linphone.core.tools.Log -import org.linphone.utils.Event -import org.linphone.utils.PermissionHelper - -class ContactsSettingsViewModel : GenericSettingsViewModel() { - val askWriteContactsPermissionForPresenceStorageEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val readContactsPermissionGranted = MutableLiveData() - - val friendListSubscribeListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - core.isFriendListSubscriptionEnabled = newValue - } - } - val friendListSubscribe = MutableLiveData() - val rlsAddressAvailable = MutableLiveData() - - val publishPresenceListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - prefs.publishPresence = newValue - - if (newValue) { - // Publish online presence when enabling setting - Log.i( - "[Contacts Settings] Presence has been enabled, PUBLISHING presence as Online" - ) - core.consolidatedPresence = ConsolidatedPresence.Online - } else { - // Unpublish presence when disabling setting - Log.i("[Contacts Settings] Presence has been disabled, un-PUBLISHING presence info") - core.consolidatedPresence = ConsolidatedPresence.Offline - } - - publishPresenceToggledEvent.value = Event(true) - } - } - val publishPresence = MutableLiveData() - val publishPresenceToggledEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val showNewContactAccountDialogListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - prefs.showNewContactAccountDialog = newValue - } - } - val showNewContactAccountDialog = MutableLiveData() - - val nativePresenceListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - if (newValue) { - if (PermissionHelper.get().hasWriteContactsPermission()) { - prefs.storePresenceInNativeContact = true - } else { - askWriteContactsPermissionForPresenceStorageEvent.value = Event(true) - } - } else { - prefs.storePresenceInNativeContact = false - } - } - } - val nativePresence = MutableLiveData() - - val showOrganizationListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - prefs.displayOrganization = newValue - } - } - val showOrganization = MutableLiveData() - - val launcherShortcutsListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - prefs.contactsShortcuts = newValue - launcherShortcutsEvent.value = Event(newValue) - } - } - val launcherShortcuts = MutableLiveData() - val launcherShortcutsEvent = MutableLiveData>() - - val ldapAvailable = MutableLiveData() - - val ldapConfigurations = MutableLiveData>() - - lateinit var ldapNewSettingsListener: SettingListenerStub - val ldapSettingsClickedEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - private var ldapSettingsListener = object : SettingListenerStub() { - override fun onAccountClicked(identity: String) { - ldapSettingsClickedEvent.value = Event(identity.toInt()) - } - } - - init { - readContactsPermissionGranted.value = PermissionHelper.get().hasReadContactsPermission() - - friendListSubscribe.value = core.isFriendListSubscriptionEnabled - rlsAddressAvailable.value = !core.config.getString("sip", "rls_uri", "").isNullOrEmpty() - publishPresence.value = prefs.publishPresence - - showNewContactAccountDialog.value = prefs.showNewContactAccountDialog - nativePresence.value = prefs.storePresenceInNativeContact - showOrganization.value = prefs.displayOrganization - launcherShortcuts.value = prefs.contactsShortcuts - - ldapAvailable.value = core.ldapAvailable() - ldapConfigurations.value = arrayListOf() - - updateLdapConfigurationsList() - } - - fun updateLdapConfigurationsList() { - val list = arrayListOf() - var index = 0 - for (ldap in coreContext.core.ldapList) { - val viewModel = LdapSettingsViewModel(ldap, index.toString()) - viewModel.ldapSettingsListener = ldapSettingsListener - list.add(viewModel) - index += 1 - } - - ldapConfigurations.value = list - } -} diff --git a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/GenericSettingsViewModel.kt b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/GenericSettingsViewModel.kt deleted file mode 100644 index 1313bbd40..000000000 --- a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/GenericSettingsViewModel.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.settings.viewmodels - -import androidx.lifecycle.ViewModel -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences - -abstract class GenericSettingsViewModel : ViewModel() { - protected val prefs = corePreferences - protected val core = coreContext.core -} diff --git a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/LdapSettingsViewModel.kt b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/LdapSettingsViewModel.kt deleted file mode 100644 index b2fa74fbf..000000000 --- a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/LdapSettingsViewModel.kt +++ /dev/null @@ -1,297 +0,0 @@ -/* - * Copyright (c) 2010-2022 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.settings.viewmodels - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import java.lang.NumberFormatException -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R -import org.linphone.activities.main.settings.SettingListenerStub -import org.linphone.core.Ldap -import org.linphone.core.tools.Log -import org.linphone.utils.Event - -class LdapSettingsViewModelFactory(private val index: Int) : - ViewModelProvider.NewInstanceFactory() { - - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - if (index >= 0 && index <= coreContext.core.ldapList.size) { - val ldap = coreContext.core.ldapList[index] - return LdapSettingsViewModel(ldap, index.toString()) as T - } - - val ldapParams = coreContext.core.createLdapParams() - val ldap = coreContext.core.createLdapWithParams(ldapParams) - return LdapSettingsViewModel(ldap, "-1") as T - } -} - -class LdapSettingsViewModel(private val ldap: Ldap, val index: String) : GenericSettingsViewModel() { - lateinit var ldapSettingsListener: SettingListenerStub - - val ldapConfigDeletedEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val ldapEnableListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - val params = ldap.params.clone() - params.enabled = newValue - ldap.params = params - } - } - val ldapEnable = MutableLiveData() - - val deleteListener = object : SettingListenerStub() { - override fun onClicked() { - coreContext.core.removeLdap(ldap) - ldapConfigDeletedEvent.value = Event(true) - } - } - - val ldapServerListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - val params = ldap.params.clone() - params.server = newValue - ldap.params = params - } - } - val ldapServer = MutableLiveData() - - val ldapBindDnListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - val params = ldap.params.clone() - params.bindDn = newValue - ldap.params = params - } - } - val ldapBindDn = MutableLiveData() - - val ldapPasswordListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - val params = ldap.params.clone() - params.password = newValue - ldap.params = params - } - } - val ldapPassword = MutableLiveData() - - val ldapAuthMethodListener = object : SettingListenerStub() { - override fun onListValueChanged(position: Int) { - val params = ldap.params.clone() - params.authMethod = Ldap.AuthMethod.fromInt(ldapAuthMethodValues[position]) - ldap.params = params - ldapAuthMethodIndex.value = position - } - } - val ldapAuthMethodIndex = MutableLiveData() - val ldapAuthMethodLabels = MutableLiveData>() - private val ldapAuthMethodValues = arrayListOf() - - val ldapTlsListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - val params = ldap.params.clone() - params.isTlsEnabled = newValue - ldap.params = params - } - } - val ldapTls = MutableLiveData() - - val ldapCertCheckListener = object : SettingListenerStub() { - override fun onListValueChanged(position: Int) { - val params = ldap.params.clone() - params.serverCertificatesVerificationMode = Ldap.CertVerificationMode.fromInt( - ldapCertCheckValues[position] - ) - ldap.params = params - ldapCertCheckIndex.value = position - } - } - val ldapCertCheckIndex = MutableLiveData() - val ldapCertCheckLabels = MutableLiveData>() - private val ldapCertCheckValues = arrayListOf() - - val ldapSearchBaseListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - val params = ldap.params.clone() - params.baseObject = newValue - ldap.params = params - } - } - val ldapSearchBase = MutableLiveData() - - val ldapSearchFilterListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - val params = ldap.params.clone() - params.filter = newValue - ldap.params = params - } - } - val ldapSearchFilter = MutableLiveData() - - val ldapSearchMaxResultsListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - try { - val intValue = newValue.toInt() - val params = ldap.params.clone() - params.maxResults = intValue - ldap.params = params - } catch (nfe: NumberFormatException) { - Log.e("[LDAP Settings] Failed to set max results ($newValue): $nfe") - } - } - } - val ldapSearchMaxResults = MutableLiveData() - - val ldapSearchTimeoutListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - try { - val intValue = newValue.toInt() - val params = ldap.params.clone() - params.timeout = intValue - ldap.params = params - } catch (nfe: NumberFormatException) { - Log.e("[LDAP Settings] Failed to set timeout ($newValue): $nfe") - } - } - } - val ldapSearchTimeout = MutableLiveData() - - val ldapRequestDelayListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - try { - val intValue = newValue.toInt() - val params = ldap.params.clone() - params.delay = intValue - ldap.params = params - } catch (nfe: NumberFormatException) { - Log.e("[LDAP Settings] Failed to set request delay ($newValue): $nfe") - } - } - } - val ldapRequestDelay = MutableLiveData() - - val ldapMinimumCharactersListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - try { - val intValue = newValue.toInt() - val params = ldap.params.clone() - params.minChars = intValue - ldap.params = params - } catch (nfe: NumberFormatException) { - Log.e("[LDAP Settings] Failed to set minimum characters ($newValue): $nfe") - } - } - } - val ldapMinimumCharacters = MutableLiveData() - - val ldapNameAttributeListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - val params = ldap.params.clone() - params.nameAttribute = newValue - ldap.params = params - } - } - val ldapNameAttribute = MutableLiveData() - - val ldapSipAttributeListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - val params = ldap.params.clone() - params.sipAttribute = newValue - ldap.params = params - } - } - val ldapSipAttribute = MutableLiveData() - - val ldapSipDomainListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - val params = ldap.params.clone() - params.sipDomain = newValue - ldap.params = params - } - } - val ldapSipDomain = MutableLiveData() - - val ldapDebugListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - val params = ldap.params.clone() - params.debugLevel = if (newValue) Ldap.DebugLevel.Verbose else Ldap.DebugLevel.Off - ldap.params = params - } - } - val ldapDebug = MutableLiveData() - - init { - val params = ldap.params - - ldapEnable.value = params.enabled - ldapServer.value = params.server - ldapBindDn.value = params.bindDn - ldapPassword.value = params.password - ldapTls.value = params.isTlsEnabled - ldapSearchBase.value = params.baseObject - ldapSearchFilter.value = params.filter - ldapSearchMaxResults.value = params.maxResults - ldapSearchTimeout.value = params.timeout - ldapRequestDelay.value = params.delay - ldapMinimumCharacters.value = params.minChars - ldapNameAttribute.value = params.nameAttribute - ldapSipAttribute.value = params.sipAttribute - ldapSipDomain.value = params.sipDomain - ldapDebug.value = params.debugLevel == Ldap.DebugLevel.Verbose - - initAuthMethodList() - initTlsCertCheckList() - } - - private fun initAuthMethodList() { - val labels = arrayListOf() - - labels.add(prefs.getString(R.string.contacts_settings_ldap_auth_method_anonymous)) - ldapAuthMethodValues.add(Ldap.AuthMethod.Anonymous.toInt()) - - labels.add(prefs.getString(R.string.contacts_settings_ldap_auth_method_simple)) - ldapAuthMethodValues.add(Ldap.AuthMethod.Simple.toInt()) - - ldapAuthMethodLabels.value = labels - ldapAuthMethodIndex.value = ldapAuthMethodValues.indexOf(ldap.params.authMethod.toInt()) - } - - private fun initTlsCertCheckList() { - val labels = arrayListOf() - - labels.add(prefs.getString(R.string.contacts_settings_ldap_cert_check_auto)) - ldapCertCheckValues.add(Ldap.CertVerificationMode.Default.toInt()) - - labels.add(prefs.getString(R.string.contacts_settings_ldap_cert_check_disabled)) - ldapCertCheckValues.add(Ldap.CertVerificationMode.Disabled.toInt()) - - labels.add(prefs.getString(R.string.contacts_settings_ldap_cert_check_enabled)) - ldapCertCheckValues.add(Ldap.CertVerificationMode.Enabled.toInt()) - - ldapCertCheckLabels.value = labels - ldapCertCheckIndex.value = ldapCertCheckValues.indexOf( - ldap.params.serverCertificatesVerificationMode.toInt() - ) - } -} diff --git a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/NetworkSettingsViewModel.kt b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/NetworkSettingsViewModel.kt deleted file mode 100644 index 057997971..000000000 --- a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/NetworkSettingsViewModel.kt +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.settings.viewmodels - -import androidx.lifecycle.MutableLiveData -import java.lang.NumberFormatException -import org.linphone.activities.main.settings.SettingListenerStub - -class NetworkSettingsViewModel : GenericSettingsViewModel() { - val wifiOnlyListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - core.isWifiOnlyEnabled = newValue - } - } - val wifiOnly = MutableLiveData() - - val allowIpv6Listener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - core.isIpv6Enabled = newValue - } - } - val allowIpv6 = MutableLiveData() - - val randomPortsListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - val port = if (newValue) -1 else 5060 - setTransportPort(port) - sipPort.value = port - } - } - val randomPorts = MutableLiveData() - - val sipPortListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - try { - val port = newValue.toInt() - setTransportPort(port) - } catch (_: NumberFormatException) { - } - } - } - val sipPort = MutableLiveData() - - init { - wifiOnly.value = core.isWifiOnlyEnabled - allowIpv6.value = core.isIpv6Enabled - randomPorts.value = getTransportPort() == -1 - sipPort.value = getTransportPort() - } - - private fun setTransportPort(port: Int) { - val transports = core.transports - transports.udpPort = port - transports.tcpPort = port - transports.tlsPort = -1 - core.transports = transports - } - - private fun getTransportPort(): Int { - val transports = core.transports - if (transports.udpPort > 0) return transports.udpPort - return transports.tcpPort - } -} diff --git a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/SettingsViewModel.kt b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/SettingsViewModel.kt deleted file mode 100644 index c2c39fda9..000000000 --- a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/SettingsViewModel.kt +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.settings.viewmodels - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.activities.main.settings.SettingListenerStub -import org.linphone.utils.LinphoneUtils - -class SettingsViewModel : ViewModel() { - private val tunnelAvailable: Boolean = coreContext.core.tunnelAvailable() - - val showAccountSettings: Boolean = corePreferences.showAccountSettings - val showTunnelSettings: Boolean = tunnelAvailable && corePreferences.showTunnelSettings - val showAudioSettings: Boolean = corePreferences.showAudioSettings - val showVideoSettings: Boolean = corePreferences.showVideoSettings - val showCallSettings: Boolean = corePreferences.showCallSettings - val showChatSettings: Boolean = corePreferences.showChatSettings - val showNetworkSettings: Boolean = corePreferences.showNetworkSettings - val showContactsSettings: Boolean = corePreferences.showContactsSettings - val showAdvancedSettings: Boolean = corePreferences.showAdvancedSettings - val showConferencesSettings: Boolean = corePreferences.showConferencesSettings - - val accounts = MutableLiveData>() - - private var accountClickListener = object : SettingListenerStub() { - override fun onAccountClicked(identity: String) { - accountsSettingsListener.onAccountClicked(identity) - } - } - - lateinit var accountsSettingsListener: SettingListenerStub - - lateinit var tunnelSettingsListener: SettingListenerStub - - lateinit var audioSettingsListener: SettingListenerStub - - lateinit var videoSettingsListener: SettingListenerStub - - lateinit var callSettingsListener: SettingListenerStub - - lateinit var chatSettingsListener: SettingListenerStub - - lateinit var networkSettingsListener: SettingListenerStub - - lateinit var contactsSettingsListener: SettingListenerStub - - lateinit var advancedSettingsListener: SettingListenerStub - - lateinit var conferencesSettingsListener: SettingListenerStub - - val primaryAccountDisplayNameListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - val address = coreContext.core.createPrimaryContactParsed() - address ?: return - address.displayName = newValue - address.username = primaryAccountUsername.value - coreContext.core.primaryContact = address.asString() - - primaryAccountDisplayName.value = newValue - } - } - val primaryAccountDisplayName = MutableLiveData() - - val primaryAccountUsernameListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - val address = coreContext.core.createPrimaryContactParsed() - address ?: return - address.username = newValue - address.displayName = primaryAccountDisplayName.value - coreContext.core.primaryContact = address.asString() - - primaryAccountUsername.value = newValue - } - } - val primaryAccountUsername = MutableLiveData() - - init { - updateAccountsList() - - val address = coreContext.core.createPrimaryContactParsed() - primaryAccountDisplayName.value = address?.displayName ?: "" - primaryAccountUsername.value = address?.username ?: "" - } - - override fun onCleared() { - accounts.value.orEmpty().forEach(AccountSettingsViewModel::destroy) - super.onCleared() - } - - fun updateAccountsList() { - accounts.value.orEmpty().forEach(AccountSettingsViewModel::destroy) - - val list = arrayListOf() - for (account in LinphoneUtils.getAccountsNotHidden()) { - val viewModel = AccountSettingsViewModel(account) - viewModel.accountsSettingsListener = accountClickListener - list.add(viewModel) - } - - accounts.value = list - } -} diff --git a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/TunnelSettingsViewModel.kt b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/TunnelSettingsViewModel.kt deleted file mode 100644 index 8bdcf2110..000000000 --- a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/TunnelSettingsViewModel.kt +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.settings.viewmodels - -import androidx.lifecycle.MutableLiveData -import java.lang.NumberFormatException -import org.linphone.R -import org.linphone.activities.main.settings.SettingListenerStub -import org.linphone.core.Factory -import org.linphone.core.Tunnel -import org.linphone.core.TunnelConfig - -class TunnelSettingsViewModel : GenericSettingsViewModel() { - val hostnameUrlListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - val config = getTunnelConfig() - config.host = newValue - updateTunnelConfig(config) - } - } - val hostnameUrl = MutableLiveData() - - val portListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - try { - val config = getTunnelConfig() - config.port = newValue.toInt() - updateTunnelConfig(config) - } catch (_: NumberFormatException) { - } - } - } - val port = MutableLiveData() - - val useDualModeListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - val tunnel = core.tunnel - tunnel?.isDualModeEnabled = newValue - } - } - val useDualMode = MutableLiveData() - - val hostnameUrl2Listener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - val config = getTunnelConfig() - config.host2 = newValue - updateTunnelConfig(config) - } - } - val hostnameUrl2 = MutableLiveData() - - val port2Listener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - try { - val config = getTunnelConfig() - config.port2 = newValue.toInt() - updateTunnelConfig(config) - } catch (_: NumberFormatException) { - } - } - } - val port2 = MutableLiveData() - - val modeListener = object : SettingListenerStub() { - override fun onListValueChanged(position: Int) { - core.tunnel?.mode = when (position) { - 0 -> Tunnel.Mode.Disable - 1 -> Tunnel.Mode.Enable - else -> Tunnel.Mode.Auto - } - } - } - val modeIndex = MutableLiveData() - val modeLabels = MutableLiveData>() - - init { - val tunnel = core.tunnel - val config = getTunnelConfig() - - hostnameUrl.value = config.host - port.value = config.port - useDualMode.value = tunnel?.isDualModeEnabled - hostnameUrl2.value = config.host2 - port2.value = config.port2 - - initModeList() - } - - private fun getTunnelConfig(): TunnelConfig { - val tunnel = core.tunnel - val configs = tunnel?.servers.orEmpty() - return if (configs.isNotEmpty()) { - configs.first() - } else { - Factory.instance().createTunnelConfig() - } - } - - private fun updateTunnelConfig(config: TunnelConfig) { - val tunnel = core.tunnel - tunnel?.cleanServers() - if (config.host?.isNotEmpty() == true) { - tunnel?.addServer(config) - } - } - - private fun initModeList() { - val labels = arrayListOf() - labels.add(prefs.getString(R.string.tunnel_settings_disabled_mode)) - labels.add(prefs.getString(R.string.tunnel_settings_always_mode)) - labels.add(prefs.getString(R.string.tunnel_settings_auto_mode)) - modeLabels.value = labels - - modeIndex.value = when (core.tunnel?.mode) { - Tunnel.Mode.Disable -> 0 - Tunnel.Mode.Enable -> 1 - else -> 2 - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/VideoSettingsViewModel.kt b/app/src/main/java/org/linphone/activities/main/settings/viewmodels/VideoSettingsViewModel.kt deleted file mode 100644 index db1db30ed..000000000 --- a/app/src/main/java/org/linphone/activities/main/settings/viewmodels/VideoSettingsViewModel.kt +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.settings.viewmodels - -import androidx.databinding.ViewDataBinding -import androidx.lifecycle.MutableLiveData -import java.lang.NumberFormatException -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R -import org.linphone.activities.main.settings.SettingListenerStub -import org.linphone.core.Factory -import org.linphone.core.tools.Log - -class VideoSettingsViewModel : GenericSettingsViewModel() { - val enableVideoListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - core.isVideoCaptureEnabled = newValue - core.isVideoDisplayEnabled = newValue - if (!newValue) { - tabletPreview.value = false - initiateCall.value = false - autoAccept.value = false - } - } - } - val enableVideo = MutableLiveData() - - val tabletPreviewListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - prefs.videoPreview = newValue - } - } - val tabletPreview = MutableLiveData() - val isTablet = MutableLiveData() - - val initiateCallListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - val policy = core.videoActivationPolicy - policy.automaticallyInitiate = newValue - core.videoActivationPolicy = policy - } - } - val initiateCall = MutableLiveData() - - val autoAcceptListener = object : SettingListenerStub() { - override fun onBoolValueChanged(newValue: Boolean) { - val policy = core.videoActivationPolicy - policy.automaticallyAccept = newValue - core.videoActivationPolicy = policy - } - } - val autoAccept = MutableLiveData() - - val cameraDeviceListener = object : SettingListenerStub() { - override fun onListValueChanged(position: Int) { - core.videoDevice = cameraDeviceLabels.value.orEmpty()[position] - } - } - val cameraDeviceIndex = MutableLiveData() - val cameraDeviceLabels = MutableLiveData>() - - val videoSizeListener = object : SettingListenerStub() { - override fun onListValueChanged(position: Int) { - core.setPreferredVideoDefinitionByName(videoSizeLabels.value.orEmpty()[position]) - } - } - val videoSizeIndex = MutableLiveData() - val videoSizeLabels = MutableLiveData>() - - val videoPresetListener = object : SettingListenerStub() { - override fun onListValueChanged(position: Int) { - videoPresetIndex.value = position // Needed to display/hide two below settings - val currentPreset = core.videoPreset - val newPreset = videoPresetLabels.value.orEmpty()[position] - if (newPreset != currentPreset) { - if (currentPreset == "custom") { - // Not "custom" anymore, reset FPS & bandwidth - core.preferredFramerate = 0f - core.downloadBandwidth = 0 - core.uploadBandwidth = 0 - } - core.videoPreset = newPreset - } - } - } - val videoPresetIndex = MutableLiveData() - val videoPresetLabels = MutableLiveData>() - - val preferredFpsListener = object : SettingListenerStub() { - override fun onListValueChanged(position: Int) { - core.preferredFramerate = preferredFpsLabels.value.orEmpty()[position].toFloat() - } - } - val preferredFpsIndex = MutableLiveData() - val preferredFpsLabels = MutableLiveData>() - - val bandwidthLimitListener = object : SettingListenerStub() { - override fun onTextValueChanged(newValue: String) { - try { - core.downloadBandwidth = newValue.toInt() - core.uploadBandwidth = newValue.toInt() - } catch (_: NumberFormatException) { - } - } - } - val bandwidthLimit = MutableLiveData() - - val videoCodecs = MutableLiveData>() - - init { - enableVideo.value = core.isVideoEnabled && core.videoSupported() - tabletPreview.value = prefs.videoPreview - isTablet.value = coreContext.context.resources.getBoolean(R.bool.isTablet) - initiateCall.value = core.videoActivationPolicy.automaticallyInitiate - autoAccept.value = core.videoActivationPolicy.automaticallyAccept - - initCameraDevicesList() - initVideoSizeList() - initVideoPresetList() - initFpsList() - - bandwidthLimit.value = core.downloadBandwidth - } - - fun initCameraDevicesList() { - val labels = arrayListOf() - for (camera in core.videoDevicesList) { - if (prefs.hideStaticImageCamera && camera.startsWith("StaticImage")) { - Log.w("[Video Settings] Do not display StaticImage camera") - } else { - labels.add(camera) - } - } - - cameraDeviceLabels.value = labels - val index = labels.indexOf(core.videoDevice) - if (index == -1) { - val firstDevice = cameraDeviceLabels.value.orEmpty().firstOrNull() - Log.w( - "[Video Settings] Device not found in labels list: ${core.videoDevice}, replace it by $firstDevice" - ) - if (firstDevice != null) { - cameraDeviceIndex.value = 0 - core.videoDevice = firstDevice - } - } else { - cameraDeviceIndex.value = index - } - } - - private fun initVideoSizeList() { - val labels = arrayListOf() - - for (size in Factory.instance().supportedVideoDefinitions) { - labels.add(size.name.orEmpty()) - } - - videoSizeLabels.value = labels - videoSizeIndex.value = labels.indexOf(core.preferredVideoDefinition.name) - } - - private fun initVideoPresetList() { - val labels = arrayListOf() - - labels.add("default") - labels.add("high-fps") - labels.add("custom") - - videoPresetLabels.value = labels - videoPresetIndex.value = labels.indexOf(core.videoPreset) - } - - private fun initFpsList() { - val labels = arrayListOf("5", "10", "15", "20", "25", "30") - preferredFpsLabels.value = labels - preferredFpsIndex.value = labels.indexOf(core.preferredFramerate.toInt().toString()) - } -} diff --git a/app/src/main/java/org/linphone/activities/main/sidemenu/fragments/SideMenuFragment.kt b/app/src/main/java/org/linphone/activities/main/sidemenu/fragments/SideMenuFragment.kt deleted file mode 100644 index 1e73cef17..000000000 --- a/app/src/main/java/org/linphone/activities/main/sidemenu/fragments/SideMenuFragment.kt +++ /dev/null @@ -1,266 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.sidemenu.fragments - -import android.app.Activity -import android.content.Intent -import android.os.Bundle -import android.os.Parcelable -import android.provider.MediaStore -import android.view.View -import androidx.core.content.FileProvider -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.lifecycleScope -import java.io.File -import kotlinx.coroutines.launch -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.* -import org.linphone.activities.assistant.AssistantActivity -import org.linphone.activities.main.MainActivity -import org.linphone.activities.main.settings.SettingListenerStub -import org.linphone.activities.main.sidemenu.viewmodels.SideMenuViewModel -import org.linphone.activities.main.viewmodels.DialogViewModel -import org.linphone.core.Factory -import org.linphone.core.tools.Log -import org.linphone.databinding.SideMenuFragmentBinding -import org.linphone.utils.* - -class SideMenuFragment : GenericFragment() { - private lateinit var viewModel: SideMenuViewModel - private var temporaryPicturePath: File? = null - - override fun getLayoutId(): Int = R.layout.side_menu_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - viewModel = ViewModelProvider(this)[SideMenuViewModel::class.java] - binding.viewModel = viewModel - - sharedViewModel.accountRemoved.observe( - viewLifecycleOwner - ) { - Log.i("[Side Menu] Account removed, update accounts list") - viewModel.updateAccountsList() - } - - sharedViewModel.defaultAccountChanged.observe( - viewLifecycleOwner - ) { - Log.i("[Side Menu] Default account changed, update accounts list") - viewModel.updateAccountsList() - } - - sharedViewModel.publishPresenceToggled.observe( - viewLifecycleOwner - ) { - viewModel.refreshConsolidatedPresence() - } - - viewModel.accountsSettingsListener = object : SettingListenerStub() { - override fun onAccountClicked(identity: String) { - Log.i("[Side Menu] Navigating to settings for account with identity: $identity") - - sharedViewModel.toggleDrawerEvent.value = Event(true) - - if (corePreferences.askForAccountPasswordToAccessSettings) { - showPasswordDialog(goToAccountSettings = true, accountIdentity = identity) - } else { - navigateToAccountSettings(identity) - } - } - } - - binding.setSelfPictureClickListener { - pickFile() - } - - binding.setAssistantClickListener { - sharedViewModel.toggleDrawerEvent.value = Event(true) - startActivity(Intent(context, AssistantActivity::class.java)) - } - - binding.setSettingsClickListener { - sharedViewModel.toggleDrawerEvent.value = Event(true) - - if (corePreferences.askForAccountPasswordToAccessSettings) { - showPasswordDialog(goToSettings = true) - } else { - navigateToSettings() - } - } - - binding.setRecordingsClickListener { - sharedViewModel.toggleDrawerEvent.value = Event(true) - navigateToRecordings() - } - - binding.setAboutClickListener { - sharedViewModel.toggleDrawerEvent.value = Event(true) - navigateToAbout() - } - - binding.setConferencesClickListener { - sharedViewModel.toggleDrawerEvent.value = Event(true) - navigateToScheduledConferences() - } - - binding.setQuitClickListener { - Log.i("[Side Menu] Quitting app") - requireActivity().finishAndRemoveTask() - - Log.i("[Side Menu] Stopping Core Context") - coreContext.notificationsManager.stopForegroundNotification() - coreContext.stop() - } - } - - @Deprecated("Deprecated in Java") - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (resultCode == Activity.RESULT_OK) { - lifecycleScope.launch { - val contactImageFilePath = FileUtils.getFilePathFromPickerIntent( - data, - temporaryPicturePath - ) - if (contactImageFilePath != null) { - viewModel.setPictureFromPath(contactImageFilePath) - } - } - } - } - - private fun pickFile() { - val cameraIntents = ArrayList() - - // Handles image picking - val galleryIntent = Intent(Intent.ACTION_PICK) - galleryIntent.type = "image/*" - - if (PermissionHelper.get().hasCameraPermission()) { - // Allows to capture directly from the camera - val captureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) - val tempFileName = System.currentTimeMillis().toString() + ".jpeg" - val file = FileUtils.getFileStoragePath(tempFileName) - temporaryPicturePath = file - val publicUri = FileProvider.getUriForFile( - requireContext(), - requireContext().getString(R.string.file_provider), - file - ) - captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, publicUri) - captureIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - captureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) - cameraIntents.add(captureIntent) - } - - val chooserIntent = - Intent.createChooser(galleryIntent, getString(R.string.chat_message_pick_file_dialog)) - chooserIntent.putExtra( - Intent.EXTRA_INITIAL_INTENTS, - cameraIntents.toArray(arrayOf()) - ) - - startActivityForResult(chooserIntent, 0) - } - - private fun showPasswordDialog( - goToSettings: Boolean = false, - goToAccountSettings: Boolean = false, - accountIdentity: String = "" - ) { - val dialogViewModel = DialogViewModel( - getString(R.string.settings_password_protection_dialog_title) - ) - dialogViewModel.showIcon = true - dialogViewModel.iconResource = R.drawable.security_toggle_icon_green - dialogViewModel.showPassword = true - dialogViewModel.passwordTitle = getString( - R.string.settings_password_protection_dialog_input_hint - ) - val dialog = DialogUtils.getDialog(requireContext(), dialogViewModel) - - dialogViewModel.showCancelButton { - dialog.dismiss() - } - - dialogViewModel.showOkButton( - { - val defaultAccount = coreContext.core.defaultAccount ?: coreContext.core.accountList.firstOrNull() - if (defaultAccount == null) { - Log.e("[Side Menu] No account found, can't check password input!") - (requireActivity() as MainActivity).showSnackBar(R.string.error_unexpected) - } else { - val authInfo = defaultAccount.findAuthInfo() - if (authInfo == null) { - Log.e( - "[Side Menu] No auth info found for account [${defaultAccount.params.identityAddress?.asString()}], can't check password input!" - ) - (requireActivity() as MainActivity).showSnackBar(R.string.error_unexpected) - } else { - val expectedHash = authInfo.ha1 - if (expectedHash == null) { - Log.e( - "[Side Menu] No ha1 found in auth info, can't check password input!" - ) - (requireActivity() as MainActivity).showSnackBar( - R.string.error_unexpected - ) - } else { - val hashAlgorithm = authInfo.algorithm ?: "MD5" - val userId = (authInfo.userid ?: authInfo.username).orEmpty() - val realm = authInfo.realm.orEmpty() - val password = dialogViewModel.password - val computedHash = Factory.instance().computeHa1ForAlgorithm( - userId, - password, - realm, - hashAlgorithm - ) - if (computedHash != expectedHash) { - Log.e( - "[Side Menu] Computed hash [$computedHash] using userId [$userId], realm [$realm] and algorithm [$hashAlgorithm] doesn't match expected hash!" - ) - (requireActivity() as MainActivity).showSnackBar( - R.string.settings_password_protection_dialog_invalid_input - ) - } else { - if (goToSettings) { - navigateToSettings() - } else if (goToAccountSettings) { - navigateToAccountSettings(accountIdentity) - } - } - } - } - } - - dialog.dismiss() - }, - getString(R.string.settings_password_protection_dialog_ok_label) - ) - - dialog.show() - } -} diff --git a/app/src/main/java/org/linphone/activities/main/sidemenu/viewmodels/SideMenuViewModel.kt b/app/src/main/java/org/linphone/activities/main/sidemenu/viewmodels/SideMenuViewModel.kt deleted file mode 100644 index 1f07fedb6..000000000 --- a/app/src/main/java/org/linphone/activities/main/sidemenu/viewmodels/SideMenuViewModel.kt +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.sidemenu.viewmodels - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.activities.main.settings.SettingListenerStub -import org.linphone.activities.main.settings.viewmodels.AccountSettingsViewModel -import org.linphone.core.* -import org.linphone.utils.LinphoneUtils - -class SideMenuViewModel : ViewModel() { - val showAccounts: Boolean = corePreferences.showAccountsInSideMenu - val showAssistant: Boolean = corePreferences.showAssistantInSideMenu - val showSettings: Boolean = corePreferences.showSettingsInSideMenu - val showRecordings: Boolean = corePreferences.showRecordingsInSideMenu - val showScheduledConferences = MutableLiveData() - val showAbout: Boolean = corePreferences.showAboutInSideMenu - val showQuit: Boolean = corePreferences.showQuitInSideMenu - - val defaultAccountViewModel = MutableLiveData() - val defaultAccountFound = MutableLiveData() - val defaultAccountAvatar = MutableLiveData() - - val accounts = MutableLiveData>() - - val presenceStatus = MutableLiveData() - - lateinit var accountsSettingsListener: SettingListenerStub - - private val listener: CoreListenerStub = object : CoreListenerStub() { - override fun onAccountRegistrationStateChanged( - core: Core, - account: Account, - state: RegistrationState, - message: String - ) { - // +1 is for the default account, otherwise this will trigger every time - if (accounts.value.isNullOrEmpty() || - coreContext.core.accountList.size != accounts.value.orEmpty().size + 1 - ) { - // Only refresh the list if an account has been added or removed - updateAccountsList() - } - } - } - - init { - defaultAccountFound.value = false - defaultAccountAvatar.value = corePreferences.defaultAccountAvatarPath - showScheduledConferences.value = corePreferences.showScheduledConferencesInSideMenu && - LinphoneUtils.isRemoteConferencingAvailable() - coreContext.core.addListener(listener) - updateAccountsList() - refreshConsolidatedPresence() - } - - override fun onCleared() { - defaultAccountViewModel.value?.destroy() - accounts.value.orEmpty().forEach(AccountSettingsViewModel::destroy) - coreContext.core.removeListener(listener) - super.onCleared() - } - - fun refreshConsolidatedPresence() { - presenceStatus.value = coreContext.core.consolidatedPresence - } - - fun updateAccountsList() { - defaultAccountFound.value = false // Do not assume a default account will still be found - defaultAccountViewModel.value?.destroy() - accounts.value.orEmpty().forEach(AccountSettingsViewModel::destroy) - - val list = arrayListOf() - val defaultAccount = coreContext.core.defaultAccount - if (defaultAccount != null) { - val defaultViewModel = AccountSettingsViewModel(defaultAccount) - defaultViewModel.accountsSettingsListener = object : SettingListenerStub() { - override fun onAccountClicked(identity: String) { - accountsSettingsListener.onAccountClicked(identity) - } - } - defaultAccountViewModel.value = defaultViewModel - defaultAccountFound.value = true - } - - for (account in LinphoneUtils.getAccountsNotHidden()) { - if (account != coreContext.core.defaultAccount) { - val viewModel = AccountSettingsViewModel(account) - viewModel.accountsSettingsListener = object : SettingListenerStub() { - override fun onAccountClicked(identity: String) { - accountsSettingsListener.onAccountClicked(identity) - } - } - list.add(viewModel) - } - } - accounts.value = list - - showScheduledConferences.value = corePreferences.showScheduledConferencesInSideMenu && - LinphoneUtils.isRemoteConferencingAvailable() - } - - fun setPictureFromPath(picturePath: String) { - corePreferences.defaultAccountAvatarPath = picturePath - defaultAccountAvatar.value = corePreferences.defaultAccountAvatarPath - coreContext.contactsManager.updateLocalContacts() - } -} diff --git a/app/src/main/java/org/linphone/activities/main/viewmodels/CallOverlayViewModel.kt b/app/src/main/java/org/linphone/activities/main/viewmodels/CallOverlayViewModel.kt deleted file mode 100644 index 79863593f..000000000 --- a/app/src/main/java/org/linphone/activities/main/viewmodels/CallOverlayViewModel.kt +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.viewmodels - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.core.Call -import org.linphone.core.Core -import org.linphone.core.CoreListenerStub -import org.linphone.core.tools.Log - -class CallOverlayViewModel : ViewModel() { - val displayCallOverlay = MutableLiveData() - - private val listener = object : CoreListenerStub() { - override fun onCallStateChanged( - core: Core, - call: Call, - state: Call.State?, - message: String - ) { - if (core.callsNb == 1 && call.state == Call.State.Connected) { - Log.i("[Call Overlay] First call connected, creating it") - createCallOverlay() - } - } - - override fun onLastCallEnded(core: Core) { - Log.i("[Call Overlay] Last call ended, removing it") - removeCallOverlay() - } - } - - init { - displayCallOverlay.value = corePreferences.showCallOverlay && - !corePreferences.systemWideCallOverlay && - coreContext.core.callsNb > 0 - - coreContext.core.addListener(listener) - } - - override fun onCleared() { - coreContext.core.removeListener(listener) - - super.onCleared() - } - - private fun createCallOverlay() { - // If overlay is disabled or if system-wide call overlay is enabled, abort - if (!corePreferences.showCallOverlay || corePreferences.systemWideCallOverlay) { - return - } - - displayCallOverlay.value = true - } - - private fun removeCallOverlay() { - displayCallOverlay.value = false - } -} diff --git a/app/src/main/java/org/linphone/activities/main/viewmodels/DialogViewModel.kt b/app/src/main/java/org/linphone/activities/main/viewmodels/DialogViewModel.kt deleted file mode 100644 index 3cc7440d5..000000000 --- a/app/src/main/java/org/linphone/activities/main/viewmodels/DialogViewModel.kt +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.viewmodels - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import org.linphone.R -import org.linphone.utils.AppUtils -import org.linphone.utils.Event - -class DialogViewModel(val message: String, val title: String = "") : ViewModel() { - var showDoNotAskAgain: Boolean = false - - var showZrtp: Boolean = false - - var zrtpReadSas: String = "" - - var zrtpListenSas: String = "" - - var showTitle: Boolean = false - - var showIcon: Boolean = false - - var iconResource: Int = 0 - - var showSubscribeLinphoneOrgLink: Boolean = false - - val doNotAskAgain = MutableLiveData() - - val dismissEvent = MutableLiveData>() - - var password: String = "" - - var passwordTitle: String = "" - - var passwordSubtitle: String = "" - - var showPassword: Boolean = false - - init { - doNotAskAgain.value = false - showTitle = title.isNotEmpty() - } - - var showCancel: Boolean = false - var cancelLabel: String = AppUtils.getString(R.string.dialog_cancel) - private var onCancel: (Boolean) -> Unit = {} - - fun showCancelButton(cancel: (Boolean) -> Unit) { - showCancel = true - onCancel = cancel - } - - fun showCancelButton(cancel: (Boolean) -> Unit, label: String = cancelLabel) { - showCancel = true - onCancel = cancel - cancelLabel = label - } - - fun onCancelClicked() { - onCancel(doNotAskAgain.value == true) - } - - var showDelete: Boolean = false - var deleteLabel: String = AppUtils.getString(R.string.dialog_delete) - private var onDelete: (Boolean) -> Unit = {} - - fun showDeleteButton(delete: (Boolean) -> Unit, label: String) { - showDelete = true - onDelete = delete - deleteLabel = label - } - - fun onDeleteClicked() { - onDelete(doNotAskAgain.value == true) - } - - var showOk: Boolean = false - var okLabel: String = AppUtils.getString(R.string.dialog_ok) - private var onOk: (Boolean) -> Unit = {} - - fun showOkButton(ok: (Boolean) -> Unit, label: String = okLabel) { - showOk = true - onOk = ok - okLabel = label - } - - fun onOkClicked() { - onOk(doNotAskAgain.value == true) - } - - fun dismiss() { - dismissEvent.value = Event(true) - } -} diff --git a/app/src/main/java/org/linphone/activities/main/viewmodels/ListTopBarViewModel.kt b/app/src/main/java/org/linphone/activities/main/viewmodels/ListTopBarViewModel.kt deleted file mode 100644 index 0c0ffc090..000000000 --- a/app/src/main/java/org/linphone/activities/main/viewmodels/ListTopBarViewModel.kt +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.viewmodels - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import org.linphone.utils.Event - -/** - * This view model is dedicated to the top bar while in edition mode for item(s) selection in list - */ -class ListTopBarViewModel : ViewModel() { - val isEditionEnabled = MutableLiveData() - - val isSelectionNotEmpty = MutableLiveData() - - val selectAllEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val unSelectAllEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val deleteSelectionEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val selectedItems = MutableLiveData>() - - init { - isEditionEnabled.value = false - isSelectionNotEmpty.value = false - selectedItems.value = arrayListOf() - } - - fun onSelectAll(lastIndex: Int) { - val list = arrayListOf() - list.addAll(0.rangeTo(lastIndex)) - - selectedItems.value = list - isSelectionNotEmpty.value = list.isNotEmpty() - } - - fun onUnSelectAll() { - val list = arrayListOf() - - selectedItems.value = list - isSelectionNotEmpty.value = false - } - - fun onToggleSelect(position: Int) { - val list = arrayListOf() - list.addAll(selectedItems.value.orEmpty()) - - if (list.contains(position)) { - list.remove(position) - } else { - list.add(position) - } - - isSelectionNotEmpty.value = list.isNotEmpty() - selectedItems.value = list - } -} diff --git a/app/src/main/java/org/linphone/activities/main/viewmodels/LogsUploadViewModel.kt b/app/src/main/java/org/linphone/activities/main/viewmodels/LogsUploadViewModel.kt deleted file mode 100644 index ca9a083df..000000000 --- a/app/src/main/java/org/linphone/activities/main/viewmodels/LogsUploadViewModel.kt +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ - -package org.linphone.activities.main.viewmodels - -import androidx.lifecycle.MutableLiveData -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.core.Core -import org.linphone.core.CoreListenerStub -import org.linphone.utils.Event - -open class LogsUploadViewModel : MessageNotifierViewModel() { - val uploadInProgress = MutableLiveData() - - val resetCompleteEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val uploadFinishedEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val uploadErrorEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - private val listener = object : CoreListenerStub() { - override fun onLogCollectionUploadStateChanged( - core: Core, - state: Core.LogCollectionUploadState, - info: String - ) { - if (state == Core.LogCollectionUploadState.Delivered) { - uploadInProgress.value = false - uploadFinishedEvent.value = Event(info) - } else if (state == Core.LogCollectionUploadState.NotDelivered) { - uploadInProgress.value = false - uploadErrorEvent.value = Event(true) - } - } - } - - init { - coreContext.core.addListener(listener) - uploadInProgress.value = false - } - - override fun onCleared() { - coreContext.core.removeListener(listener) - - super.onCleared() - } - - fun uploadLogs() { - uploadInProgress.value = true - coreContext.core.uploadLogCollection() - } - - fun resetLogs() { - coreContext.core.resetLogCollection() - resetCompleteEvent.value = Event(true) - } -} diff --git a/app/src/main/java/org/linphone/activities/main/viewmodels/MessageNotifierViewModel.kt b/app/src/main/java/org/linphone/activities/main/viewmodels/MessageNotifierViewModel.kt deleted file mode 100644 index 6e81ee8db..000000000 --- a/app/src/main/java/org/linphone/activities/main/viewmodels/MessageNotifierViewModel.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.viewmodels - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import org.linphone.utils.Event - -/* Helper for view models to notify user of a massage through a Snackbar */ -abstract class MessageNotifierViewModel : ViewModel() { - val onMessageToNotifyEvent = MutableLiveData>() -} diff --git a/app/src/main/java/org/linphone/activities/main/viewmodels/SharedMainViewModel.kt b/app/src/main/java/org/linphone/activities/main/viewmodels/SharedMainViewModel.kt deleted file mode 100644 index 5bc6ae662..000000000 --- a/app/src/main/java/org/linphone/activities/main/viewmodels/SharedMainViewModel.kt +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.viewmodels - -import android.net.Uri -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.activities.main.history.data.GroupedCallLogData -import org.linphone.core.* -import org.linphone.utils.Event - -class SharedMainViewModel : ViewModel() { - val toggleDrawerEvent = MutableLiveData>() - - val layoutChangedEvent = MutableLiveData>() - var isSlidingPaneSlideable = MutableLiveData() - - /* Call history */ - - val selectedCallLogGroup = MutableLiveData() - - /* Chat */ - - val selectedChatRoom = MutableLiveData() - var destructionPendingChatRoom: ChatRoom? = null - - val selectedGroupChatRoom = MutableLiveData() - - val filesToShare = MutableLiveData>() - - val textToShare = MutableLiveData() - - val messageToForwardEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val isPendingMessageForward = MutableLiveData() - - val contentToOpen = MutableLiveData() - - var createEncryptedChatRoom: Boolean = corePreferences.forceEndToEndEncryptedChat - - val chatRoomParticipants = MutableLiveData>() - - var chatRoomSubject: String = "" - - // When using keyboard to share gif or other, see RichContentReceiver & RichEditText classes - val richContentUri = MutableLiveData>() - - val refreshChatRoomInListEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - /* Contacts */ - - val selectedContact = MutableLiveData() - - // For correct animations directions - val updateContactsAnimationsBasedOnDestination: MutableLiveData> by lazy { - MutableLiveData>() - } - - /* Accounts */ - - val defaultAccountChanged: MutableLiveData by lazy { - MutableLiveData() - } - - val accountRemoved: MutableLiveData by lazy { - MutableLiveData() - } - - val publishPresenceToggled: MutableLiveData by lazy { - MutableLiveData() - } - - val accountSettingsFragmentOpenedEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - /* Call */ - - var pendingCallTransfer: Boolean = false - - /* Conference */ - - val addressOfConferenceInfoToEdit: MutableLiveData> by lazy { - MutableLiveData>() - } - - val participantsListForNextScheduledMeeting: MutableLiveData>> by lazy { - MutableLiveData>>() - } - - /* Dialer */ - - var dialerUri: String = "" - - // For correct animations directions - val updateDialerAnimationsBasedOnDestination: MutableLiveData> by lazy { - MutableLiveData>() - } -} diff --git a/app/src/main/java/org/linphone/activities/main/viewmodels/StatusViewModel.kt b/app/src/main/java/org/linphone/activities/main/viewmodels/StatusViewModel.kt deleted file mode 100644 index 7b10b5210..000000000 --- a/app/src/main/java/org/linphone/activities/main/viewmodels/StatusViewModel.kt +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.viewmodels - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import java.util.* -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R -import org.linphone.core.* -import org.linphone.core.tools.Log - -open class StatusViewModel : ViewModel() { - val registrationStatusText = MutableLiveData() - - val registrationStatusDrawable = MutableLiveData() - - val voiceMailCount = MutableLiveData() - - private val listener: CoreListenerStub = object : CoreListenerStub() { - override fun onAccountRegistrationStateChanged( - core: Core, - account: Account, - state: RegistrationState, - message: String - ) { - if (account == core.defaultAccount) { - updateDefaultAccountRegistrationStatus(state) - } else if (core.accountList.isEmpty()) { - // Update registration status when default account is removed - registrationStatusText.value = getStatusIconText(state) - registrationStatusDrawable.value = getStatusIconResource(state) - } - } - - override fun onNotifyReceived( - core: Core, - event: Event, - notifiedEvent: String, - body: Content? - ) { - if (body?.type == "application" && body.subtype == "simple-message-summary" && body.size > 0) { - val data = body.utf8Text?.lowercase(Locale.getDefault()) - val voiceMail = data?.split("voice-message: ") - if ((voiceMail?.size ?: 0) >= 2) { - val toParse = voiceMail!![1].split("/", limit = 0) - try { - val unreadCount: Int = toParse[0].toInt() - voiceMailCount.value = unreadCount - } catch (nfe: NumberFormatException) { - Log.e("[Status Fragment] $nfe") - } - } - } - } - } - - init { - val core = coreContext.core - core.addListener(listener) - - var state: RegistrationState = RegistrationState.None - val defaultAccount = core.defaultAccount - if (defaultAccount != null) { - state = defaultAccount.state - } - updateDefaultAccountRegistrationStatus(state) - } - - override fun onCleared() { - coreContext.core.removeListener(listener) - super.onCleared() - } - - fun refreshRegister() { - coreContext.core.refreshRegisters() - } - - fun updateDefaultAccountRegistrationStatus(state: RegistrationState) { - registrationStatusText.value = getStatusIconText(state) - registrationStatusDrawable.value = getStatusIconResource(state) - } - - private fun getStatusIconText(state: RegistrationState): Int { - return when (state) { - RegistrationState.Ok -> R.string.status_connected - RegistrationState.Progress, RegistrationState.Refreshing -> R.string.status_in_progress - RegistrationState.Failed -> R.string.status_error - else -> R.string.status_not_connected - } - } - - private fun getStatusIconResource(state: RegistrationState): Int { - return when (state) { - RegistrationState.Ok -> R.drawable.led_registered - RegistrationState.Progress, RegistrationState.Refreshing -> R.drawable.led_registration_in_progress - RegistrationState.Failed -> R.drawable.led_error - else -> R.drawable.led_not_registered - } - } -} diff --git a/app/src/main/java/org/linphone/activities/main/viewmodels/TabsViewModel.kt b/app/src/main/java/org/linphone/activities/main/viewmodels/TabsViewModel.kt deleted file mode 100644 index f637c71a3..000000000 --- a/app/src/main/java/org/linphone/activities/main/viewmodels/TabsViewModel.kt +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.main.viewmodels - -import android.animation.ValueAnimator -import android.view.animation.LinearInterpolator -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.core.* -import org.linphone.utils.AppUtils - -class TabsViewModel : ViewModel() { - val unreadMessagesCount = MutableLiveData() - val missedCallsCount = MutableLiveData() - - val leftAnchor = MutableLiveData() - val middleAnchor = MutableLiveData() - val rightAnchor = MutableLiveData() - - val historyMissedCountTranslateY = MutableLiveData() - val chatUnreadCountTranslateY = MutableLiveData() - - private val bounceAnimator: ValueAnimator by lazy { - ValueAnimator.ofFloat( - AppUtils.getDimension(R.dimen.tabs_fragment_unread_count_bounce_offset), - 0f - ).apply { - addUpdateListener { - val value = it.animatedValue as Float - historyMissedCountTranslateY.value = -value - chatUnreadCountTranslateY.value = -value - } - interpolator = LinearInterpolator() - duration = 250 - repeatMode = ValueAnimator.REVERSE - repeatCount = ValueAnimator.INFINITE - } - } - - private val listener: CoreListenerStub = object : CoreListenerStub() { - override fun onCallStateChanged( - core: Core, - call: Call, - state: Call.State, - message: String - ) { - if (state == Call.State.End || state == Call.State.Error) { - updateMissedCallCount() - } - } - - override fun onChatRoomRead(core: Core, chatRoom: ChatRoom) { - updateUnreadChatCount() - } - - override fun onMessagesReceived( - core: Core, - chatRoom: ChatRoom, - messages: Array - ) { - updateUnreadChatCount() - } - - override fun onChatRoomStateChanged(core: Core, chatRoom: ChatRoom, state: ChatRoom.State) { - if (state == ChatRoom.State.Deleted) { - updateUnreadChatCount() - } - } - } - - init { - coreContext.core.addListener(listener) - - if (corePreferences.disableChat) { - leftAnchor.value = 1 / 3F - middleAnchor.value = 2 / 3F - rightAnchor.value = 1F - } else { - leftAnchor.value = 0.25F - middleAnchor.value = 0.5F - rightAnchor.value = 0.75F - } - - updateUnreadChatCount() - updateMissedCallCount() - - if (corePreferences.enableAnimations) bounceAnimator.start() - } - - override fun onCleared() { - coreContext.core.removeListener(listener) - super.onCleared() - } - - fun updateMissedCallCount() { - missedCallsCount.value = coreContext.core.missedCallsCount - } - - fun updateUnreadChatCount() { - unreadMessagesCount.value = if (corePreferences.disableChat) 0 else coreContext.core.unreadChatMessageCountFromActiveLocals - } -} diff --git a/app/src/main/java/org/linphone/activities/voip/CallActivity.kt b/app/src/main/java/org/linphone/activities/voip/CallActivity.kt deleted file mode 100644 index 089bf912f..000000000 --- a/app/src/main/java/org/linphone/activities/voip/CallActivity.kt +++ /dev/null @@ -1,331 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.voip - -import android.Manifest -import android.content.Intent -import android.content.pm.PackageManager -import android.content.res.Configuration -import android.os.Build -import android.os.Bundle -import androidx.annotation.RequiresApi -import androidx.databinding.DataBindingUtil -import androidx.lifecycle.ViewModelProvider -import androidx.navigation.findNavController -import androidx.window.layout.FoldingFeature -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.* -import org.linphone.activities.main.MainActivity -import org.linphone.activities.voip.viewmodels.CallsViewModel -import org.linphone.activities.voip.viewmodels.ConferenceViewModel -import org.linphone.activities.voip.viewmodels.ControlsViewModel -import org.linphone.activities.voip.viewmodels.StatisticsListViewModel -import org.linphone.compatibility.Compatibility -import org.linphone.core.Call -import org.linphone.core.GlobalState -import org.linphone.core.tools.Log -import org.linphone.databinding.VoipActivityBinding -import org.linphone.mediastream.Version -import org.linphone.utils.PermissionHelper - -class CallActivity : ProximitySensorActivity() { - private lateinit var binding: VoipActivityBinding - private lateinit var controlsViewModel: ControlsViewModel - private lateinit var callsViewModel: CallsViewModel - private lateinit var conferenceViewModel: ConferenceViewModel - private lateinit var statsViewModel: StatisticsListViewModel - - override fun onCreate(savedInstanceState: Bundle?) { - // Flag in manifest should be enough starting Android 8.1 - if (Version.sdkStrictlyBelow(Version.API27_OREO_81)) { - Compatibility.setShowWhenLocked(this, true) - Compatibility.setTurnScreenOn(this, true) - Compatibility.requestDismissKeyguard(this) - } - - super.onCreate(savedInstanceState) - - binding = DataBindingUtil.setContentView(this, R.layout.voip_activity) - binding.lifecycleOwner = this - } - - override fun onPostCreate(savedInstanceState: Bundle?) { - super.onPostCreate(savedInstanceState) - - // This can't be done in onCreate(), has to be at least in onPostCreate() ! - val navController = binding.navHostFragment.findNavController() - val navControllerStoreOwner = navController.getViewModelStoreOwner(R.id.call_nav_graph) - - controlsViewModel = ViewModelProvider(navControllerStoreOwner)[ControlsViewModel::class.java] - binding.controlsViewModel = controlsViewModel - - callsViewModel = ViewModelProvider(navControllerStoreOwner)[CallsViewModel::class.java] - - conferenceViewModel = ViewModelProvider(navControllerStoreOwner)[ConferenceViewModel::class.java] - - statsViewModel = ViewModelProvider(navControllerStoreOwner)[StatisticsListViewModel::class.java] - - val isInPipMode = Compatibility.isInPictureInPictureMode(this) - Log.i("[Call Activity] onPostCreate: is in PiP mode? $isInPipMode") - controlsViewModel.pipMode.value = isInPipMode - - controlsViewModel.askPermissionEvent.observe( - this - ) { - it.consume { permission -> - Log.i("[Call Activity] Asking for $permission permission") - requestPermissions(arrayOf(permission), 0) - } - } - - controlsViewModel.fullScreenMode.observe( - this - ) { hide -> - Compatibility.hideAndroidSystemUI(hide, window) - } - - controlsViewModel.proximitySensorEnabled.observe( - this - ) { enabled -> - Log.i( - "[Call Activity] ${if (enabled) "Enabling" else "Disabling"} proximity sensor (if possible)" - ) - enableProximitySensor(enabled) - } - - controlsViewModel.isVideoEnabled.observe( - this - ) { enabled -> - Compatibility.enableAutoEnterPiP( - this, - enabled, - conferenceViewModel.conferenceExists.value == true - ) - } - - controlsViewModel.callStatsVisible.observe( - this - ) { visible -> - if (visible) statsViewModel.enable() else statsViewModel.disable() - } - - callsViewModel.noMoreCallEvent.observe( - this - ) { - it.consume { noMoreCall -> - if (noMoreCall) { - Log.i("[Call Activity] No more call event fired, finishing activity") - finish() - } - } - } - - callsViewModel.currentCallData.observe( - this - ) { callData -> - val call = callData.call - if (call.conference == null) { - Log.i( - "[Call Activity] Current call isn't linked to a conference, switching to SingleCall fragment" - ) - navigateToActiveCall() - } else { - Log.i( - "[Call Activity] Current call is linked to a conference, switching to ConferenceCall fragment" - ) - navigateToConferenceCall() - } - } - - callsViewModel.askPermissionEvent.observe( - this - ) { - it.consume { permission -> - Log.i("[Call Activity] Asking for $permission permission") - requestPermissions(arrayOf(permission), 0) - } - } - - conferenceViewModel.conferenceExists.observe( - this - ) { exists -> - if (exists) { - Log.i( - "[Call Activity] Found active conference, changing switching to ConferenceCall fragment" - ) - navigateToConferenceCall() - } else if (coreContext.core.callsNb > 0) { - Log.i( - "[Call Activity] Conference no longer exists, switching to SingleCall fragment" - ) - navigateToActiveCall() - } - } - - conferenceViewModel.isConferenceLocallyPaused.observe( - this - ) { paused -> - if (!paused) { - Log.i("[Call Activity] Entered conference, make sure conference fragment is active") - navigateToConferenceCall() - } - } - - checkPermissions() - } - - override fun onUserLeaveHint() { - super.onUserLeaveHint() - - if (coreContext.core.currentCall?.currentParams?.isVideoEnabled == true) { - Log.i("[Call Activity] Entering PiP mode") - Compatibility.enterPipMode(this, conferenceViewModel.conferenceExists.value == true) - } - } - - @RequiresApi(Build.VERSION_CODES.O) - override fun onPictureInPictureModeChanged( - isInPictureInPictureMode: Boolean, - newConfig: Configuration - ) { - super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig) - - Log.i( - "[Call Activity] onPictureInPictureModeChanged: is in PiP mode? $isInPictureInPictureMode" - ) - if (::controlsViewModel.isInitialized) { - // To hide UI except for TextureViews - controlsViewModel.pipMode.value = isInPictureInPictureMode - } - } - - override fun onResume() { - super.onResume() - - if (coreContext.core.callsNb == 0) { - Log.w("[Call Activity] Resuming but no call found...") - if (isTaskRoot) { - // When resuming app from recent tasks make sure MainActivity will be launched if there is no call - val intent = Intent() - intent.setClass(this, MainActivity::class.java) - intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK - startActivity(intent) - } else { - finish() - } - return - } - coreContext.removeCallOverlay() - - val currentCall = coreContext.core.currentCall - when (currentCall?.state) { - Call.State.OutgoingInit, Call.State.OutgoingEarlyMedia, Call.State.OutgoingProgress, Call.State.OutgoingRinging -> { - navigateToOutgoingCall() - } - Call.State.IncomingReceived, Call.State.IncomingEarlyMedia -> { - val earlyMediaVideoEnabled = corePreferences.acceptEarlyMedia && - currentCall.state == Call.State.IncomingEarlyMedia && - currentCall.currentParams.isVideoEnabled - navigateToIncomingCall(earlyMediaVideoEnabled) - } - else -> {} - } - } - - override fun onPause() { - val core = coreContext.core - if (core.callsNb > 0) { - coreContext.createCallOverlay() - } - - super.onPause() - } - - override fun onDestroy() { - if (coreContext.core.globalState != GlobalState.Off) { - coreContext.core.nativeVideoWindowId = null - coreContext.core.nativePreviewWindowId = null - } - - super.onDestroy() - } - - private fun checkPermissions() { - val permissionsRequiredList = arrayListOf() - - if (!PermissionHelper.get().hasRecordAudioPermission()) { - Log.i("[Call Activity] Asking for RECORD_AUDIO permission") - permissionsRequiredList.add(Manifest.permission.RECORD_AUDIO) - } - - if (callsViewModel.currentCallData.value?.call?.currentParams?.isVideoEnabled == true && - !PermissionHelper.get().hasCameraPermission() - ) { - Log.i("[Call Activity] Asking for CAMERA permission") - permissionsRequiredList.add(Manifest.permission.CAMERA) - } - - if (permissionsRequiredList.isNotEmpty()) { - val permissionsRequired = arrayOfNulls(permissionsRequiredList.size) - permissionsRequiredList.toArray(permissionsRequired) - requestPermissions(permissionsRequired, 0) - } - } - - override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray - ) { - if (requestCode == 0) { - for (i in permissions.indices) { - when (permissions[i]) { - Manifest.permission.RECORD_AUDIO -> if (grantResults[i] == PackageManager.PERMISSION_GRANTED) { - Log.i("[Call Activity] RECORD_AUDIO permission has been granted") - callsViewModel.updateMicState() - } - Manifest.permission.CAMERA -> if (grantResults[i] == PackageManager.PERMISSION_GRANTED) { - Log.i("[Call Activity] CAMERA permission has been granted") - coreContext.core.reloadVideoDevices() - controlsViewModel.toggleVideo() - } - Manifest.permission.WRITE_EXTERNAL_STORAGE -> if (grantResults[i] == PackageManager.PERMISSION_GRANTED) { - Log.i( - "[Call Activity] WRITE_EXTERNAL_STORAGE permission has been granted, taking snapshot" - ) - controlsViewModel.takeSnapshot() - } - } - } - } - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - } - - override fun onLayoutChanges(foldingFeature: FoldingFeature?) { - foldingFeature ?: return - Log.i( - "[Call Activity] Folding feature state changed: ${foldingFeature.state}, orientation is ${foldingFeature.orientation}" - ) - - controlsViewModel.foldingState.value = foldingFeature - } -} diff --git a/app/src/main/java/org/linphone/activities/voip/ConferenceDisplayMode.kt b/app/src/main/java/org/linphone/activities/voip/ConferenceDisplayMode.kt deleted file mode 100644 index 0e529b351..000000000 --- a/app/src/main/java/org/linphone/activities/voip/ConferenceDisplayMode.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2010-2022 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.voip - -enum class ConferenceDisplayMode { - GRID, - ACTIVE_SPEAKER, - AUDIO_ONLY -} diff --git a/app/src/main/java/org/linphone/activities/voip/data/CallData.kt b/app/src/main/java/org/linphone/activities/voip/data/CallData.kt deleted file mode 100644 index 4c2fe15db..000000000 --- a/app/src/main/java/org/linphone/activities/voip/data/CallData.kt +++ /dev/null @@ -1,315 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.voip.data - -import android.view.View -import android.widget.Toast -import androidx.lifecycle.MediatorLiveData -import androidx.lifecycle.MutableLiveData -import java.util.* -import kotlinx.coroutines.* -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R -import org.linphone.compatibility.Compatibility -import org.linphone.contact.GenericContactData -import org.linphone.core.* -import org.linphone.core.tools.Log -import org.linphone.utils.AppUtils -import org.linphone.utils.LinphoneUtils - -open class CallData(val call: Call) : GenericContactData(call.remoteAddress) { - interface CallContextMenuClickListener { - fun onShowContextMenu(anchor: View, callData: CallData) - } - - val displayableAddress = MutableLiveData() - - val isPaused = MutableLiveData() - val isRemotelyPaused = MutableLiveData() - val canBePaused = MutableLiveData() - - val isRecording = MutableLiveData() - val isRemotelyRecorded = MutableLiveData() - - val isInRemoteConference = MutableLiveData() - val remoteConferenceSubject = MutableLiveData() - val isConferenceCall = MediatorLiveData() - val conferenceParticipants = MutableLiveData>() - val conferenceParticipantsCountLabel = MutableLiveData() - - val isOutgoing = MutableLiveData() - val isIncoming = MutableLiveData() - - var chatRoom: ChatRoom? = null - - var contextMenuClickListener: CallContextMenuClickListener? = null - - private var timer: Timer? = null - - private val listener = object : CallListenerStub() { - override fun onStateChanged(call: Call, state: Call.State, message: String) { - if (call != this@CallData.call) return - Log.i("[Call] State changed: $state") - - update() - - if (call.state == Call.State.UpdatedByRemote) { - val remoteVideo = call.remoteParams?.isVideoEnabled ?: false - val localVideo = call.currentParams.isVideoEnabled - if (remoteVideo && !localVideo) { - // User has 30 secs to accept or decline call update - startVideoUpdateAcceptanceTimer() - } - } else if (state == Call.State.End || state == Call.State.Released || state == Call.State.Error) { - timer?.cancel() - } else if (state == Call.State.StreamsRunning) { - // Stop call update timer once user has accepted or declined call update - timer?.cancel() - } - } - - override fun onRemoteRecording(call: Call, recording: Boolean) { - Log.i("[Call] Remote recording changed: $recording") - isRemotelyRecorded.value = recording - } - - override fun onSnapshotTaken(call: Call, filePath: String) { - Log.i("[Call] Snapshot taken: $filePath") - val content = Factory.instance().createContent() - content.filePath = filePath - content.type = "image" - content.subtype = "jpeg" - content.name = filePath.substring(filePath.lastIndexOf("/") + 1) - - scope.launch { - if (Compatibility.addImageToMediaStore(coreContext.context, content)) { - Log.i("[Call] Added snapshot ${content.name} to Media Store") - val message = String.format( - AppUtils.getString(R.string.call_screenshot_taken), - content.name - ) - Toast.makeText(coreContext.context, message, Toast.LENGTH_SHORT).show() - } else { - Log.e("[Call] Something went wrong while copying file to Media Store...") - } - } - } - } - - private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob()) - - init { - call.addListener(listener) - isRemotelyRecorded.value = call.remoteParams?.isRecording - displayableAddress.value = LinphoneUtils.getDisplayableAddress(call.remoteAddress) - - isConferenceCall.addSource(remoteConferenceSubject) { - isConferenceCall.value = remoteConferenceSubject.value.orEmpty().isNotEmpty() || conferenceParticipants.value.orEmpty().isNotEmpty() - } - isConferenceCall.addSource(conferenceParticipants) { - isConferenceCall.value = remoteConferenceSubject.value.orEmpty().isNotEmpty() || conferenceParticipants.value.orEmpty().isNotEmpty() - } - - update() - } - - override fun destroy() { - call.removeListener(listener) - timer?.cancel() - scope.cancel() - - super.destroy() - } - - fun togglePause() { - if (isCallPaused()) { - resume() - } else { - pause() - } - } - - fun pause() { - call.pause() - } - - fun resume() { - call.resume() - } - - fun accept() { - call.accept() - } - - fun terminate() { - call.terminate() - } - - fun toggleRecording() { - if (call.params.isRecording) { - call.stopRecording() - } else { - call.startRecording() - } - isRecording.value = call.params.isRecording - } - - fun showContextMenu(anchor: View) { - contextMenuClickListener?.onShowContextMenu(anchor, this) - } - - fun isActiveAndNotInConference(): Boolean { - return isPaused.value == false && isRemotelyPaused.value == false && call.conference?.call != null && isInRemoteConference.value == false - } - - private fun isCallPaused(): Boolean { - return when (call.state) { - Call.State.Paused, Call.State.Pausing -> true - else -> false - } - } - - private fun isCallRemotelyPaused(): Boolean { - return when (call.state) { - Call.State.PausedByRemote -> { - val conference = call.conference - if (conference != null && conference.me.isFocus) { - Log.w( - "[Call] State is paused by remote but we are the focus of the conference, so considering call as active" - ) - false - } else { - true - } - } - else -> false - } - } - - private fun canCallBePaused(): Boolean { - return !call.mediaInProgress() && when (call.state) { - Call.State.StreamsRunning, Call.State.PausedByRemote -> true - else -> false - } - } - - private fun update() { - isRecording.value = call.params.isRecording - isPaused.value = isCallPaused() - isRemotelyPaused.value = isCallRemotelyPaused() - canBePaused.value = canCallBePaused() - - updateConferenceInfo() - - isOutgoing.value = when (call.state) { - Call.State.OutgoingInit, Call.State.OutgoingEarlyMedia, Call.State.OutgoingProgress, Call.State.OutgoingRinging -> true - else -> false - } - isIncoming.value = when (call.state) { - Call.State.IncomingReceived, Call.State.IncomingEarlyMedia -> true - else -> false - } - - // Check periodically until mediaInProgress is false - if (call.mediaInProgress()) { - scope.launch { - delay(1000) - update() - } - } - } - - private fun updateConferenceInfo() { - val conference = call.conference - isInRemoteConference.value = conference != null && conference.call != null - if (conference != null) { - Log.d("[Call] Found conference attached to call") - remoteConferenceSubject.value = LinphoneUtils.getConferenceSubject(conference) - Log.d( - "[Call] Found conference related to this call with subject [${remoteConferenceSubject.value}]" - ) - - val participantsList = arrayListOf() - for (participant in conference.participantList) { - val participantData = ConferenceInfoParticipantData(participant.address) - participantsList.add(participantData) - } - - conferenceParticipants.value = participantsList - conferenceParticipantsCountLabel.value = coreContext.context.getString( - R.string.conference_participants_title, - participantsList.size - ) - } else { - val conferenceAddress = LinphoneUtils.getConferenceAddress(call) - val conferenceInfo = if (conferenceAddress != null) { - coreContext.core.findConferenceInformationFromUri( - conferenceAddress - ) - } else { - null - } - if (conferenceInfo != null) { - Log.d( - "[Call] Found matching conference info with subject: ${conferenceInfo.subject}" - ) - remoteConferenceSubject.value = conferenceInfo.subject - - val participantsList = arrayListOf() - for (participant in conferenceInfo.participants) { - val participantData = ConferenceInfoParticipantData(participant) - participantsList.add(participantData) - } - - // Add organizer if not in participants list - val organizer = conferenceInfo.organizer - if (organizer != null) { - val found = participantsList.find { it.participant.weakEqual(organizer) } - if (found == null) { - val participantData = ConferenceInfoParticipantData(organizer) - participantsList.add(0, participantData) - } - } - - conferenceParticipants.value = participantsList - conferenceParticipantsCountLabel.value = coreContext.context.getString( - R.string.conference_participants_title, - participantsList.size - ) - } - } - } - - private fun startVideoUpdateAcceptanceTimer() { - timer?.cancel() - - timer = Timer("Call update timeout") - timer?.schedule( - object : TimerTask() { - override fun run() { - // Decline call update - coreContext.videoUpdateRequestTimedOut(call) - } - }, - 30000 - ) - Log.i("[Call] Starting 30 seconds timer to automatically decline video request") - } -} diff --git a/app/src/main/java/org/linphone/activities/voip/data/CallStatisticsData.kt b/app/src/main/java/org/linphone/activities/voip/data/CallStatisticsData.kt deleted file mode 100644 index 6d4051df7..000000000 --- a/app/src/main/java/org/linphone/activities/voip/data/CallStatisticsData.kt +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.voip.data - -import androidx.lifecycle.MutableLiveData -import org.linphone.contact.GenericContactData -import org.linphone.core.* - -class CallStatisticsData(val call: Call) : GenericContactData(call.remoteAddress) { - val audioStats = MutableLiveData>() - - val videoStats = MutableLiveData>() - - val mediaEncryptionStats = MutableLiveData>() - - val isVideoEnabled = MutableLiveData() - - private var enabled = false - - private val listener = object : CallListenerStub() { - override fun onStatsUpdated(call: Call, stats: CallStats) { - isVideoEnabled.value = call.currentParams.isVideoEnabled - updateCallStats(stats) - } - } - - init { - enabled = false - audioStats.value = arrayListOf() - videoStats.value = arrayListOf() - - initCallStats() - - val videoEnabled = call.currentParams.isVideoEnabled - isVideoEnabled.value = videoEnabled - - updateMediaEncryptionStats() - } - - fun enable() { - enabled = true - call.addListener(listener) - - // Needed for media encryption stats - updateMediaEncryptionStats() - } - - fun disable() { - enabled = false - call.removeListener(listener) - } - - override fun destroy() { - if (enabled) disable() - super.destroy() - } - - private fun updateMediaEncryptionStats() { - initCallStats() - } - - private fun initCallStats() { - val audioList = arrayListOf() - - audioList.add(StatItemData(StatType.CAPTURE)) - audioList.add(StatItemData(StatType.PLAYBACK)) - audioList.add(StatItemData(StatType.PAYLOAD)) - audioList.add(StatItemData(StatType.ENCODER)) - audioList.add(StatItemData(StatType.DECODER)) - audioList.add(StatItemData(StatType.DOWNLOAD_BW)) - audioList.add(StatItemData(StatType.UPLOAD_BW)) - audioList.add(StatItemData(StatType.ICE)) - audioList.add(StatItemData(StatType.IP_FAM)) - audioList.add(StatItemData(StatType.SENDER_LOSS)) - audioList.add(StatItemData(StatType.RECEIVER_LOSS)) - audioList.add(StatItemData(StatType.JITTER)) - - audioStats.value = audioList - - val mediaEncryptionList = arrayListOf() - - mediaEncryptionList.add(StatItemData(StatType.MEDIA_ENCRYPTION)) - - // ZRTP stats are only available when authentication token isn't null ! - if (call.currentParams.mediaEncryption == MediaEncryption.ZRTP && call.authenticationToken != null) { - mediaEncryptionList.add(StatItemData(StatType.ZRTP_CIPHER_ALGO)) - mediaEncryptionList.add(StatItemData(StatType.ZRTP_KEY_AGREEMENT_ALGO)) - mediaEncryptionList.add(StatItemData(StatType.ZRTP_HASH_ALGO)) - mediaEncryptionList.add(StatItemData(StatType.ZRTP_AUTH_TAG_ALGO)) - mediaEncryptionList.add(StatItemData(StatType.ZRTP_AUTH_SAS_ALGO)) - } - - mediaEncryptionStats.value = mediaEncryptionList - - val videoList = arrayListOf() - - videoList.add(StatItemData(StatType.CAPTURE)) - videoList.add(StatItemData(StatType.PLAYBACK)) - videoList.add(StatItemData(StatType.PAYLOAD)) - videoList.add(StatItemData(StatType.ENCODER)) - videoList.add(StatItemData(StatType.DECODER)) - videoList.add(StatItemData(StatType.DOWNLOAD_BW)) - videoList.add(StatItemData(StatType.UPLOAD_BW)) - videoList.add(StatItemData(StatType.ESTIMATED_AVAILABLE_DOWNLOAD_BW)) - videoList.add(StatItemData(StatType.ICE)) - videoList.add(StatItemData(StatType.IP_FAM)) - videoList.add(StatItemData(StatType.SENDER_LOSS)) - videoList.add(StatItemData(StatType.RECEIVER_LOSS)) - videoList.add(StatItemData(StatType.SENT_RESOLUTION)) - videoList.add(StatItemData(StatType.RECEIVED_RESOLUTION)) - videoList.add(StatItemData(StatType.SENT_FPS)) - videoList.add(StatItemData(StatType.RECEIVED_FPS)) - - videoStats.value = videoList - } - - private fun updateCallStats(stats: CallStats) { - if (stats.type == StreamType.Audio) { - for (stat in audioStats.value.orEmpty()) { - stat.update(call, stats) - } - for (stat in mediaEncryptionStats.value.orEmpty()) { - stat.update(call, stats) - } - } else if (stats.type == StreamType.Video) { - for (stat in videoStats.value.orEmpty()) { - stat.update(call, stats) - } - } - } -} diff --git a/app/src/main/java/org/linphone/activities/voip/data/ConferenceInfoParticipantData.kt b/app/src/main/java/org/linphone/activities/voip/data/ConferenceInfoParticipantData.kt deleted file mode 100644 index bd7fe2eb6..000000000 --- a/app/src/main/java/org/linphone/activities/voip/data/ConferenceInfoParticipantData.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2010-2022 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.voip.data - -import org.linphone.contact.GenericContactData -import org.linphone.core.* -import org.linphone.utils.LinphoneUtils - -class ConferenceInfoParticipantData( - val participant: Address -) : - GenericContactData(participant) { - val sipUri: String get() = LinphoneUtils.getDisplayableAddress(participant) -} diff --git a/app/src/main/java/org/linphone/activities/voip/data/ConferenceParticipantData.kt b/app/src/main/java/org/linphone/activities/voip/data/ConferenceParticipantData.kt deleted file mode 100644 index d0e679273..000000000 --- a/app/src/main/java/org/linphone/activities/voip/data/ConferenceParticipantData.kt +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.voip.data - -import androidx.lifecycle.MutableLiveData -import org.linphone.contact.GenericContactData -import org.linphone.core.* -import org.linphone.core.tools.Log -import org.linphone.utils.LinphoneUtils - -class ConferenceParticipantData( - val conference: Conference, - val participant: Participant -) : - GenericContactData(participant.address) { - val sipUri: String get() = LinphoneUtils.getDisplayableAddress(participant.address) - - val isAdmin = MutableLiveData() - val isMeAdmin = MutableLiveData() - val isSpeaker = MutableLiveData() - - init { - isAdmin.value = participant.isAdmin - isMeAdmin.value = conference.me.isAdmin - isSpeaker.value = participant.role == Participant.Role.Speaker - - Log.i( - "[Conference Participant] Participant ${participant.address.asStringUriOnly()} is ${if (participant.isAdmin) "admin" else "not admin"}" - ) - } - - fun setAdmin() { - if (conference.me.isAdmin) { - Log.i( - "[Conference Participant] Participant ${participant.address.asStringUriOnly()} will be set as admin" - ) - conference.setParticipantAdminStatus(participant, true) - } else { - Log.e( - "[Conference Participant] You aren't admin, you can't change participants admin rights" - ) - } - } - - fun unsetAdmin() { - if (conference.me.isAdmin) { - Log.i( - "[Conference Participant] Participant ${participant.address.asStringUriOnly()} will be unset as admin" - ) - conference.setParticipantAdminStatus(participant, false) - } else { - Log.e( - "[Conference Participant] You aren't admin, you can't change participants admin rights" - ) - } - } - - fun removeParticipantFromConference() { - if (conference.me.isAdmin) { - Log.i( - "[Conference Participant] Removing participant ${participant.address.asStringUriOnly()} from conference" - ) - conference.removeParticipant(participant) - } else { - Log.e( - "[Conference Participant] Can't remove participant ${participant.address.asStringUriOnly()} from conference, you aren't admin" - ) - } - } -} diff --git a/app/src/main/java/org/linphone/activities/voip/data/ConferenceParticipantDeviceData.kt b/app/src/main/java/org/linphone/activities/voip/data/ConferenceParticipantDeviceData.kt deleted file mode 100644 index 78cf5e860..000000000 --- a/app/src/main/java/org/linphone/activities/voip/data/ConferenceParticipantDeviceData.kt +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.voip.data - -import android.view.TextureView -import androidx.lifecycle.MediatorLiveData -import androidx.lifecycle.MutableLiveData -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.contact.GenericContactData -import org.linphone.core.* -import org.linphone.core.tools.Log - -class ConferenceParticipantDeviceData( - val participantDevice: ParticipantDevice, - val isMe: Boolean -) : - GenericContactData(participantDevice.address) { - val videoEnabled: MediatorLiveData = MediatorLiveData() - - val videoAvailable = MutableLiveData() - - val isSendingVideo = MutableLiveData() - - val isSpeaking = MutableLiveData() - - val isMuted = MutableLiveData() - - val isInConference = MutableLiveData() - - val isJoining = MutableLiveData() - - val isActiveSpeaker = MutableLiveData() - - private var textureView: TextureView? = null - - private val listener = object : ParticipantDeviceListenerStub() { - override fun onIsSpeakingChanged( - participantDevice: ParticipantDevice, - isSpeaking: Boolean - ) { - Log.i( - "[Conference Participant Device] Participant [${participantDevice.address.asStringUriOnly()}] is ${if (isSpeaking) "speaking" else "not speaking"}" - ) - this@ConferenceParticipantDeviceData.isSpeaking.value = isSpeaking - } - - override fun onIsMuted(participantDevice: ParticipantDevice, isMuted: Boolean) { - Log.i( - "[Conference Participant Device] Participant [${participantDevice.address.asStringUriOnly()}] is ${if (isMuted) "muted" else "not muted"}" - ) - this@ConferenceParticipantDeviceData.isMuted.value = isMuted - } - - override fun onStateChanged( - participantDevice: ParticipantDevice, - state: ParticipantDevice.State - ) { - Log.i( - "[Conference Participant Device] Participant [${participantDevice.address.asStringUriOnly()}] state has changed: $state" - ) - when (state) { - ParticipantDevice.State.Joining, ParticipantDevice.State.Alerting -> isJoining.value = true - ParticipantDevice.State.OnHold -> { - isInConference.value = false - } - ParticipantDevice.State.Present -> { - isJoining.value = false - isInConference.value = true - updateWindowId(textureView) - } - else -> {} - } - } - - override fun onStreamCapabilityChanged( - participantDevice: ParticipantDevice, - direction: MediaDirection, - streamType: StreamType - ) { - if (streamType == StreamType.Video) { - Log.i( - "[Conference Participant Device] Participant [${participantDevice.address.asStringUriOnly()}] video capability changed to $direction" - ) - isSendingVideo.value = direction == MediaDirection.SendRecv || direction == MediaDirection.SendOnly - } - } - - override fun onStreamAvailabilityChanged( - participantDevice: ParticipantDevice, - available: Boolean, - streamType: StreamType - ) { - if (streamType == StreamType.Video) { - Log.i( - "[Conference Participant Device] Participant [${participantDevice.address.asStringUriOnly()}] video availability changed to ${if (available) "available" else "unavailable"}" - ) - videoAvailable.value = available - if (available) { - updateWindowId(textureView) - } - } - } - } - - init { - Log.i( - "[Conference Participant Device] Created device width Address [${participantDevice.address.asStringUriOnly()}], is it myself? $isMe" - ) - participantDevice.addListener(listener) - - isSpeaking.value = false - isActiveSpeaker.value = false - isMuted.value = participantDevice.isMuted - - videoAvailable.value = participantDevice.getStreamAvailability(StreamType.Video) - val videoCapability = participantDevice.getStreamCapability(StreamType.Video) - isSendingVideo.value = videoCapability == MediaDirection.SendRecv || videoCapability == MediaDirection.SendOnly - isInConference.value = participantDevice.isInConference - - val state = participantDevice.state - isJoining.value = state == ParticipantDevice.State.Joining || state == ParticipantDevice.State.Alerting - Log.i( - "[Conference Participant Device] State for participant [${participantDevice.address.asStringUriOnly()}] is $state" - ) - - videoEnabled.value = isVideoAvailableAndSendReceive() - videoEnabled.addSource(videoAvailable) { - videoEnabled.value = isVideoAvailableAndSendReceive() - } - videoEnabled.addSource(isSendingVideo) { - videoEnabled.value = isVideoAvailableAndSendReceive() - } - - Log.i( - "[Conference Participant Device] Participant [${participantDevice.address.asStringUriOnly()}], is in conf? ${isInConference.value}, is video available? ${videoAvailable.value} ($videoCapability), is mic muted? ${isMuted.value}" - ) - } - - override fun destroy() { - participantDevice.removeListener(listener) - - super.destroy() - } - - fun switchCamera() { - coreContext.switchCamera() - } - - fun isSwitchCameraAvailable(): Boolean { - return isMe && coreContext.showSwitchCameraButton() - } - - fun setTextureView(tv: TextureView) { - textureView = tv - - Log.i( - "[Conference Participant Device] Setting textureView [$textureView] for participant [${participantDevice.address.asStringUriOnly()}]" - ) - updateWindowId(textureView) - } - - private fun updateWindowId(windowId: Any?) { - participantDevice.nativeVideoWindowId = windowId - } - - private fun isVideoAvailableAndSendReceive(): Boolean { - return videoAvailable.value == true && isSendingVideo.value == true - } -} diff --git a/app/src/main/java/org/linphone/activities/voip/data/StatItemData.kt b/app/src/main/java/org/linphone/activities/voip/data/StatItemData.kt deleted file mode 100644 index b69fd4edc..000000000 --- a/app/src/main/java/org/linphone/activities/voip/data/StatItemData.kt +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.voip.data - -import androidx.lifecycle.MutableLiveData -import java.text.DecimalFormat -import org.linphone.R -import org.linphone.core.* -import org.linphone.utils.AppUtils - -enum class StatType(val nameResource: Int) { - CAPTURE(R.string.call_stats_capture_filter), - PLAYBACK(R.string.call_stats_player_filter), - PAYLOAD(R.string.call_stats_codec), - ENCODER(R.string.call_stats_encoder_name), - DECODER(R.string.call_stats_decoder_name), - DOWNLOAD_BW(R.string.call_stats_download), - UPLOAD_BW(R.string.call_stats_upload), - ICE(R.string.call_stats_ice), - IP_FAM(R.string.call_stats_ip), - SENDER_LOSS(R.string.call_stats_sender_loss_rate), - RECEIVER_LOSS(R.string.call_stats_receiver_loss_rate), - JITTER(R.string.call_stats_jitter_buffer), - SENT_RESOLUTION(R.string.call_stats_video_resolution_sent), - RECEIVED_RESOLUTION(R.string.call_stats_video_resolution_received), - SENT_FPS(R.string.call_stats_video_fps_sent), - RECEIVED_FPS(R.string.call_stats_video_fps_received), - ESTIMATED_AVAILABLE_DOWNLOAD_BW(R.string.call_stats_estimated_download), - MEDIA_ENCRYPTION(R.string.call_stats_media_encryption), - ZRTP_CIPHER_ALGO(R.string.call_stats_zrtp_cipher_algo), - ZRTP_KEY_AGREEMENT_ALGO(R.string.call_stats_zrtp_key_agreement_algo), - ZRTP_HASH_ALGO(R.string.call_stats_zrtp_hash_algo), - ZRTP_AUTH_TAG_ALGO(R.string.call_stats_zrtp_auth_tag_algo), - ZRTP_AUTH_SAS_ALGO(R.string.call_stats_zrtp_sas_algo) -} - -class StatItemData(val type: StatType) { - companion object { - fun audioDeviceToString(device: AudioDevice?): String { - if (device == null) return "null" - return "${device.deviceName} [${device.type}] (${device.driverName})" - } - } - - val value = MutableLiveData() - - fun update(call: Call, stats: CallStats) { - val payloadType = if (stats.type == StreamType.Audio) call.currentParams.usedAudioPayloadType else call.currentParams.usedVideoPayloadType - payloadType ?: return - value.value = when (type) { - StatType.CAPTURE -> if (stats.type == StreamType.Audio) { - audioDeviceToString( - call.inputAudioDevice - ) - } else { - call.core.videoDevice - } - StatType.PLAYBACK -> if (stats.type == StreamType.Audio) { - audioDeviceToString( - call.outputAudioDevice - ) - } else { - call.core.videoDisplayFilter - } - StatType.PAYLOAD -> "${payloadType.mimeType}/${payloadType.clockRate / 1000} kHz" - StatType.ENCODER -> call.core.mediastreamerFactory.getDecoderText(payloadType.mimeType) - StatType.DECODER -> call.core.mediastreamerFactory.getEncoderText(payloadType.mimeType) - StatType.DOWNLOAD_BW -> "${stats.downloadBandwidth} kbits/s" - StatType.UPLOAD_BW -> "${stats.uploadBandwidth} kbits/s" - StatType.ICE -> stats.iceState.toString() - StatType.IP_FAM -> if (stats.ipFamilyOfRemote == Address.Family.Inet6) "IPv6" else "IPv4" - StatType.SENDER_LOSS -> DecimalFormat("##.##%").format(stats.senderLossRate) - StatType.RECEIVER_LOSS -> DecimalFormat("##.##%").format(stats.receiverLossRate) - StatType.JITTER -> DecimalFormat("##.## ms").format(stats.jitterBufferSizeMs) - StatType.SENT_RESOLUTION -> call.currentParams.sentVideoDefinition?.name - StatType.RECEIVED_RESOLUTION -> call.currentParams.receivedVideoDefinition?.name - StatType.SENT_FPS -> "${call.currentParams.sentFramerate}" - StatType.RECEIVED_FPS -> "${call.currentParams.receivedFramerate}" - StatType.ESTIMATED_AVAILABLE_DOWNLOAD_BW -> "${stats.estimatedDownloadBandwidth} kbit/s" - StatType.MEDIA_ENCRYPTION -> { - when (call.currentParams.mediaEncryption) { - MediaEncryption.ZRTP -> { - if (stats.isZrtpKeyAgreementAlgoPostQuantum) { - AppUtils.getString( - R.string.call_settings_media_encryption_zrtp_post_quantum - ) - } else { - AppUtils.getString(R.string.call_settings_media_encryption_zrtp) - } - } - MediaEncryption.DTLS -> AppUtils.getString( - R.string.call_settings_media_encryption_dtls - ) - MediaEncryption.SRTP -> AppUtils.getString( - R.string.call_settings_media_encryption_srtp - ) - MediaEncryption.None -> AppUtils.getString( - R.string.call_settings_media_encryption_none - ) - else -> "Unexpected!" - } - } - StatType.ZRTP_CIPHER_ALGO -> stats.zrtpCipherAlgo - StatType.ZRTP_KEY_AGREEMENT_ALGO -> stats.zrtpKeyAgreementAlgo - StatType.ZRTP_HASH_ALGO -> stats.zrtpHashAlgo - StatType.ZRTP_AUTH_TAG_ALGO -> stats.zrtpAuthTagAlgo - StatType.ZRTP_AUTH_SAS_ALGO -> stats.zrtpSasAlgo - } - } -} diff --git a/app/src/main/java/org/linphone/activities/voip/fragments/CallsListFragment.kt b/app/src/main/java/org/linphone/activities/voip/fragments/CallsListFragment.kt deleted file mode 100644 index d48bf8e71..000000000 --- a/app/src/main/java/org/linphone/activities/voip/fragments/CallsListFragment.kt +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.voip.fragments - -import android.content.Intent -import android.os.Bundle -import android.view.Gravity -import android.view.LayoutInflater -import android.view.View -import android.widget.PopupWindow -import androidx.databinding.DataBindingUtil -import androidx.navigation.navGraphViewModels -import org.linphone.R -import org.linphone.activities.main.MainActivity -import org.linphone.activities.voip.ConferenceDisplayMode -import org.linphone.activities.voip.data.CallData -import org.linphone.activities.voip.viewmodels.CallsViewModel -import org.linphone.activities.voip.viewmodels.ConferenceViewModel -import org.linphone.activities.voip.viewmodels.ControlsViewModel -import org.linphone.databinding.VoipCallContextMenuBindingImpl -import org.linphone.databinding.VoipCallsListFragmentBinding -import org.linphone.utils.AppUtils - -class CallsListFragment : GenericVideoPreviewFragment() { - private val callsViewModel: CallsViewModel by navGraphViewModels(R.id.call_nav_graph) - private val conferenceViewModel: ConferenceViewModel by navGraphViewModels(R.id.call_nav_graph) - private val controlsViewModel: ControlsViewModel by navGraphViewModels(R.id.call_nav_graph) - - override fun getLayoutId(): Int = R.layout.voip_calls_list_fragment - - private val callContextMenuClickListener = object : CallData.CallContextMenuClickListener { - override fun onShowContextMenu(anchor: View, callData: CallData) { - showCallMenu(anchor, callData) - } - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - binding.callsViewModel = callsViewModel - - binding.conferenceViewModel = conferenceViewModel - - binding.controlsViewModel = controlsViewModel - - binding.setCancelClickListener { - goBack() - } - - binding.setAddCallClickListener { - val intent = Intent() - intent.setClass(requireContext(), MainActivity::class.java) - intent.putExtra("Dialer", true) - intent.putExtra("Transfer", false) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - startActivity(intent) - } - - callsViewModel.callsData.observe( - viewLifecycleOwner - ) { - for (data in it) { - data.contextMenuClickListener = callContextMenuClickListener - } - } - - conferenceViewModel.conferenceDisplayMode.observe( - viewLifecycleOwner - ) { - binding.localPreviewVideoSurface.visibility = if (it == ConferenceDisplayMode.AUDIO_ONLY) { - View.GONE - } else { - View.VISIBLE - } - } - } - - override fun onResume() { - super.onResume() - - setupLocalVideoPreview(binding.localPreviewVideoSurface, binding.switchCamera) - } - - override fun onPause() { - super.onPause() - - cleanUpLocalVideoPreview(binding.localPreviewVideoSurface) - } - - private fun showCallMenu(anchor: View, callData: CallData) { - val popupView: VoipCallContextMenuBindingImpl = DataBindingUtil.inflate( - LayoutInflater.from(requireContext()), - R.layout.voip_call_context_menu, - null, - false - ) - - val itemSize = AppUtils.getDimension(R.dimen.voip_call_context_menu_item_height).toInt() - var totalSize = itemSize * 5 - - if (callData.isPaused.value == true || - callData.isIncoming.value == true || - callData.isOutgoing.value == true || - callData.isInRemoteConference.value == true - ) { - popupView.hidePause = true - totalSize -= itemSize - } - - if (callData.isIncoming.value == true || - callData.isOutgoing.value == true || - callData.isInRemoteConference.value == true - ) { - popupView.hideResume = true - popupView.hideTransfer = true - totalSize -= itemSize * 2 - } else if (callData.isPaused.value == false) { - popupView.hideResume = true - totalSize -= itemSize - } - - if (callData.isIncoming.value == false) { - popupView.hideAccept = true - totalSize -= itemSize - } - - // When using WRAP_CONTENT instead of real size, fails to place the - // popup window above if not enough space is available below - val popupWindow = PopupWindow( - popupView.root, - AppUtils.getDimension(R.dimen.voip_call_context_menu_width).toInt(), - totalSize, - true - ) - // Elevation is for showing a shadow around the popup - popupWindow.elevation = 20f - - popupView.setResumeClickListener { - callData.resume() - popupWindow.dismiss() - } - - popupView.setPauseClickListener { - callData.pause() - popupWindow.dismiss() - } - - popupView.setTransferClickListener { - val intent = Intent() - intent.setClass(requireContext(), MainActivity::class.java) - intent.putExtra("Dialer", true) - intent.putExtra("Transfer", true) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - startActivity(intent) - popupWindow.dismiss() - } - - popupView.setAnswerClickListener { - callData.accept() - popupWindow.dismiss() - } - - popupView.setHangupClickListener { - callData.terminate() - popupWindow.dismiss() - } - - popupWindow.showAsDropDown(anchor, 0, 0, Gravity.END or Gravity.TOP) - } -} diff --git a/app/src/main/java/org/linphone/activities/voip/fragments/ChatFragment.kt b/app/src/main/java/org/linphone/activities/voip/fragments/ChatFragment.kt deleted file mode 100644 index ab9bdf69b..000000000 --- a/app/src/main/java/org/linphone/activities/voip/fragments/ChatFragment.kt +++ /dev/null @@ -1,267 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.voip.fragments - -import android.app.Activity -import android.content.Intent -import android.content.pm.PackageManager -import android.os.Bundle -import android.os.Parcelable -import android.provider.MediaStore -import android.view.View -import androidx.core.content.FileProvider -import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.lifecycleScope -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import kotlinx.coroutines.launch -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R -import org.linphone.activities.GenericFragment -import org.linphone.activities.main.MainActivity -import org.linphone.activities.main.chat.adapters.ChatMessagesListAdapter -import org.linphone.activities.main.chat.data.ChatMessageData -import org.linphone.activities.main.chat.viewmodels.* -import org.linphone.activities.main.viewmodels.ListTopBarViewModel -import org.linphone.compatibility.Compatibility -import org.linphone.core.ChatRoom -import org.linphone.core.Factory -import org.linphone.core.tools.Log -import org.linphone.databinding.VoipChatFragmentBinding -import org.linphone.utils.FileUtils -import org.linphone.utils.PermissionHelper - -class ChatFragment : GenericFragment() { - private lateinit var adapter: ChatMessagesListAdapter - private lateinit var viewModel: ChatRoomViewModel - private lateinit var listViewModel: ChatMessagesListViewModel - private lateinit var chatSendingViewModel: ChatMessageSendingViewModel - - override fun getLayoutId(): Int = R.layout.voip_chat_fragment - - private val observer = object : RecyclerView.AdapterDataObserver() { - override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { - if (positionStart == adapter.itemCount - itemCount) { - adapter.notifyItemChanged(positionStart - 1) // For grouping purposes - scrollToBottom() - } - } - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - binding.setCancelClickListener { - goBack() - } - - binding.setChatRoomsListClickListener { - val intent = Intent() - intent.setClass(requireContext(), MainActivity::class.java) - intent.putExtra("Chat", true) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - startActivity(intent) - } - - binding.setAttachFileClickListener { - if (PermissionHelper.get().hasReadExternalStoragePermission() || PermissionHelper.get().hasCameraPermission()) { - pickFile() - } else { - Log.i("[Chat] Asking for READ_EXTERNAL_STORAGE and CAMERA permissions") - Compatibility.requestReadExternalStorageAndCameraPermissions(this, 0) - } - } - - val localSipUri = arguments?.getString("LocalSipUri") - val remoteSipUri = arguments?.getString("RemoteSipUri") - var chatRoom: ChatRoom? = null - if (localSipUri != null && remoteSipUri != null) { - Log.i( - "[Chat] Found local [$localSipUri] & remote [$remoteSipUri] addresses in arguments" - ) - - val localAddress = Factory.instance().createAddress(localSipUri) - val remoteSipAddress = Factory.instance().createAddress(remoteSipUri) - chatRoom = coreContext.core.searchChatRoom( - null, - localAddress, - remoteSipAddress, - arrayOfNulls(0) - ) - } - chatRoom ?: return - - viewModel = requireActivity().run { - ViewModelProvider( - this, - ChatRoomViewModelFactory(chatRoom) - )[ChatRoomViewModel::class.java] - } - binding.viewModel = viewModel - - listViewModel = ViewModelProvider( - this, - ChatMessagesListViewModelFactory(chatRoom) - )[ChatMessagesListViewModel::class.java] - - chatSendingViewModel = ViewModelProvider( - this, - ChatMessageSendingViewModelFactory(chatRoom) - )[ChatMessageSendingViewModel::class.java] - binding.chatSendingViewModel = chatSendingViewModel - - val listSelectionViewModel = ViewModelProvider(this)[ListTopBarViewModel::class.java] - adapter = ChatMessagesListAdapter(listSelectionViewModel, this) - // SubmitList is done on a background thread - // We need this adapter data observer to know when to scroll - binding.chatMessagesList.adapter = adapter - adapter.registerAdapterDataObserver(observer) - - // Disable context menu on each message - adapter.disableAdvancedContextMenuOptions() - - val layoutManager = LinearLayoutManager(requireContext()) - layoutManager.stackFromEnd = true - binding.chatMessagesList.layoutManager = layoutManager - - listViewModel.events.observe( - viewLifecycleOwner - ) { events -> - adapter.submitList(events) - } - - chatSendingViewModel.textToSend.observe( - viewLifecycleOwner - ) { - chatSendingViewModel.onTextToSendChanged(it) - } - - adapter.replyMessageEvent.observe( - viewLifecycleOwner - ) { - it.consume { chatMessage -> - chatSendingViewModel.pendingChatMessageToReplyTo.value?.destroy() - chatSendingViewModel.pendingChatMessageToReplyTo.value = - ChatMessageData(chatMessage) - chatSendingViewModel.isPendingAnswer.value = true - } - } - } - - override fun onResume() { - super.onResume() - - if (this::viewModel.isInitialized) { - // Prevent notifications for this chat room to be displayed - val peerAddress = viewModel.chatRoom.peerAddress.asStringUriOnly() - coreContext.notificationsManager.currentlyDisplayedChatRoomAddress = peerAddress - viewModel.chatRoom.markAsRead() - } else { - Log.e("[Chat] Fragment resuming but viewModel lateinit property isn't initialized!") - } - } - - override fun onPause() { - coreContext.notificationsManager.currentlyDisplayedChatRoomAddress = null - - super.onPause() - } - - @Deprecated("Deprecated in Java") - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (resultCode == Activity.RESULT_OK) { - lifecycleScope.launch { - for ( - fileToUploadPath in FileUtils.getFilesPathFromPickerIntent( - data, - chatSendingViewModel.temporaryFileUploadPath - ) - ) { - chatSendingViewModel.addAttachment(fileToUploadPath) - } - } - } - } - - @Deprecated("Deprecated in Java") - override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray - ) { - var atLeastOneGranted = false - for (result in grantResults) { - atLeastOneGranted = atLeastOneGranted || result == PackageManager.PERMISSION_GRANTED - } - - when (requestCode) { - 0 -> { - if (atLeastOneGranted) { - pickFile() - } - } - } - } - - private fun scrollToBottom() { - if (adapter.itemCount > 0) { - binding.chatMessagesList.scrollToPosition(adapter.itemCount - 1) - } - } - - private fun pickFile() { - val intentsList = ArrayList() - - val pickerIntent = Intent(Intent.ACTION_GET_CONTENT) - pickerIntent.type = "*/*" - pickerIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true) - - if (PermissionHelper.get().hasCameraPermission()) { - // Allows to capture directly from the camera - val capturePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) - val tempFileName = System.currentTimeMillis().toString() + ".jpeg" - val file = FileUtils.getFileStoragePath(tempFileName) - chatSendingViewModel.temporaryFileUploadPath = file - val publicUri = FileProvider.getUriForFile( - requireContext(), - requireContext().getString(R.string.file_provider), - file - ) - capturePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, publicUri) - capturePictureIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - capturePictureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) - intentsList.add(capturePictureIntent) - - val captureVideoIntent = Intent(MediaStore.ACTION_VIDEO_CAPTURE) - intentsList.add(captureVideoIntent) - } - - val chooserIntent = - Intent.createChooser(pickerIntent, getString(R.string.chat_message_pick_file_dialog)) - chooserIntent.putExtra( - Intent.EXTRA_INITIAL_INTENTS, - intentsList.toArray(arrayOf()) - ) - - startActivityForResult(chooserIntent, 0) - } -} diff --git a/app/src/main/java/org/linphone/activities/voip/fragments/ConferenceAddParticipantsFragment.kt b/app/src/main/java/org/linphone/activities/voip/fragments/ConferenceAddParticipantsFragment.kt deleted file mode 100644 index 4b588a2eb..000000000 --- a/app/src/main/java/org/linphone/activities/voip/fragments/ConferenceAddParticipantsFragment.kt +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.voip.fragments - -import android.content.pm.PackageManager -import android.os.Bundle -import android.view.View -import androidx.lifecycle.ViewModelProvider -import androidx.navigation.navGraphViewModels -import androidx.recyclerview.widget.LinearLayoutManager -import org.linphone.LinphoneApplication -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.GenericFragment -import org.linphone.activities.voip.viewmodels.ConferenceParticipantsViewModel -import org.linphone.activities.voip.viewmodels.ConferenceParticipantsViewModelFactory -import org.linphone.activities.voip.viewmodels.ConferenceViewModel -import org.linphone.contact.ContactsSelectionAdapter -import org.linphone.core.tools.Log -import org.linphone.databinding.VoipConferenceParticipantsAddFragmentBinding -import org.linphone.utils.AppUtils -import org.linphone.utils.PermissionHelper - -class ConferenceAddParticipantsFragment : GenericFragment() { - private val conferenceViewModel: ConferenceViewModel by navGraphViewModels(R.id.call_nav_graph) - private lateinit var viewModel: ConferenceParticipantsViewModel - private lateinit var adapter: ContactsSelectionAdapter - - override fun getLayoutId(): Int = R.layout.voip_conference_participants_add_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - val conference = conferenceViewModel.conference.value - conference ?: return - - viewModel = ViewModelProvider( - this, - ConferenceParticipantsViewModelFactory(conference) - )[ConferenceParticipantsViewModel::class.java] - - binding.viewModel = viewModel - - adapter = ContactsSelectionAdapter(viewLifecycleOwner) - adapter.setLimeCapabilityRequired(false) // TODO: Use right value from conference - binding.contactsList.adapter = adapter - - val layoutManager = LinearLayoutManager(requireContext()) - binding.contactsList.layoutManager = layoutManager - - // Divider between items - binding.contactsList.addItemDecoration( - AppUtils.getDividerDecoration(requireContext(), layoutManager) - ) - - binding.setApplyClickListener { - viewModel.applyChanges() - goBack() - } - - viewModel.contactsList.observe( - viewLifecycleOwner - ) { - adapter.submitList(it) - } - viewModel.sipContactsSelected.observe( - viewLifecycleOwner - ) { - viewModel.applyFilter() - } - - viewModel.selectedAddresses.observe( - viewLifecycleOwner - ) { - adapter.updateSelectedAddresses(it) - } - viewModel.filter.observe( - viewLifecycleOwner - ) { - viewModel.applyFilter() - } - - adapter.selectedContact.observe( - viewLifecycleOwner - ) { - it.consume { searchResult -> - viewModel.toggleSelectionForSearchResult(searchResult) - } - } - - if (corePreferences.enableNativeAddressBookIntegration) { - if (!PermissionHelper.get().hasReadContactsPermission()) { - Log.i("[Conference Add Participants] Asking for READ_CONTACTS permission") - requestPermissions(arrayOf(android.Manifest.permission.READ_CONTACTS), 0) - } - } - } - - @Deprecated("Deprecated in Java") - override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray - ) { - if (requestCode == 0) { - val granted = grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED - if (granted) { - Log.i("[Conference Add Participants] READ_CONTACTS permission granted") - LinphoneApplication.coreContext.fetchContacts() - } else { - Log.w("[Conference Add Participants] READ_CONTACTS permission denied") - } - } - } -} diff --git a/app/src/main/java/org/linphone/activities/voip/fragments/ConferenceCallFragment.kt b/app/src/main/java/org/linphone/activities/voip/fragments/ConferenceCallFragment.kt deleted file mode 100644 index e8b922067..000000000 --- a/app/src/main/java/org/linphone/activities/voip/fragments/ConferenceCallFragment.kt +++ /dev/null @@ -1,592 +0,0 @@ -/* - * Copyright (c) 2010-2022 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.voip.fragments - -import android.annotation.SuppressLint -import android.content.Intent -import android.content.res.Configuration -import android.os.Bundle -import android.os.SystemClock -import android.view.View -import android.view.animation.AccelerateDecelerateInterpolator -import android.widget.Chronometer -import android.widget.Toast -import androidx.constraintlayout.widget.ConstraintLayout -import androidx.constraintlayout.widget.ConstraintSet -import androidx.navigation.navGraphViewModels -import androidx.transition.AutoTransition -import androidx.transition.TransitionManager -import androidx.window.layout.FoldingFeature -import com.google.android.material.snackbar.Snackbar -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.* -import org.linphone.activities.main.MainActivity -import org.linphone.activities.voip.ConferenceDisplayMode -import org.linphone.activities.voip.viewmodels.CallsViewModel -import org.linphone.activities.voip.viewmodels.ConferenceViewModel -import org.linphone.activities.voip.viewmodels.ControlsViewModel -import org.linphone.activities.voip.viewmodels.StatisticsListViewModel -import org.linphone.activities.voip.views.RoundCornersTextureView -import org.linphone.core.Conference -import org.linphone.core.StreamType -import org.linphone.core.tools.Log -import org.linphone.databinding.VoipConferenceCallFragmentBinding - -class ConferenceCallFragment : GenericFragment() { - private val controlsViewModel: ControlsViewModel by navGraphViewModels(R.id.call_nav_graph) - private val callsViewModel: CallsViewModel by navGraphViewModels(R.id.call_nav_graph) - private val conferenceViewModel: ConferenceViewModel by navGraphViewModels(R.id.call_nav_graph) - private val statsViewModel: StatisticsListViewModel by navGraphViewModels(R.id.call_nav_graph) - - override fun getLayoutId(): Int = R.layout.voip_conference_call_fragment - - override fun onStart() { - useMaterialSharedAxisXForwardAnimation = false - - super.onStart() - } - - @SuppressLint("CutPasteId") - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - controlsViewModel.hideCallStats() // In case it was toggled on during incoming/outgoing fragment was visible - - binding.lifecycleOwner = viewLifecycleOwner - - binding.controlsViewModel = controlsViewModel - - binding.callsViewModel = callsViewModel - - binding.conferenceViewModel = conferenceViewModel - - binding.statsViewModel = statsViewModel - - conferenceViewModel.reloadConferenceFragmentEvent.observe( - viewLifecycleOwner - ) { - it.consume { - Log.i( - "[Conference Call] Reloading fragment after toggling video ON while in AUDIO_ONLY layout" - ) - refreshConferenceFragment() - } - } - - conferenceViewModel.conferenceDisplayMode.observe( - viewLifecycleOwner - ) { displayMode -> - startTimer(R.id.active_conference_timer) - - if (displayMode == ConferenceDisplayMode.ACTIVE_SPEAKER) { - if (conferenceViewModel.conferenceExists.value == true) { - Log.i( - "[Conference Call] Local participant is in conference and current layout is active speaker, updating Core's native window id" - ) - val window = binding.root.findViewById( - R.id.conference_active_speaker_remote_video - ) - coreContext.core.nativeVideoWindowId = window - - val preview = binding.root.findViewById( - R.id.local_preview_video_surface - ) - if (preview != null) { - conferenceViewModel.meParticipant.value?.setTextureView(preview) - } - } else { - Log.i( - "[Conference Call] Either not in conference or current layout isn't active speaker, updating Core's native window id" - ) - coreContext.core.nativeVideoWindowId = null - } - } - - when (displayMode) { - ConferenceDisplayMode.AUDIO_ONLY -> { - controlsViewModel.fullScreenMode.value = false - } - else -> { - val conference = conferenceViewModel.conference.value - if (conference != null) switchToFullScreenIfPossible(conference) - } - } - } - - conferenceViewModel.conferenceParticipantDevices.observe( - viewLifecycleOwner - ) { - if ( - conferenceViewModel.conferenceDisplayMode.value == ConferenceDisplayMode.GRID && - it.size > conferenceViewModel.maxParticipantsForMosaicLayout - ) { - Log.w( - "[Conference Call] More than ${conferenceViewModel.maxParticipantsForMosaicLayout} participants (${it.size}), forcing active speaker layout" - ) - conferenceViewModel.changeLayout(ConferenceDisplayMode.ACTIVE_SPEAKER, false) - refreshConferenceFragment() - // Can't use SnackBar whilst changing fragment - Toast.makeText( - requireContext(), - R.string.conference_too_many_participants_for_mosaic_layout, - Toast.LENGTH_LONG - ).show() - } - } - - conferenceViewModel.secondParticipantJoinedEvent.observe( - viewLifecycleOwner - ) { - it.consume { - switchToActiveSpeakerLayoutForTwoParticipants() - } - } - - conferenceViewModel.moreThanTwoParticipantsJoinedEvent.observe( - viewLifecycleOwner - ) { - it.consume { - switchToActiveSpeakerLayoutForMoreThanTwoParticipants() - } - } - - conferenceViewModel.conference.observe( - viewLifecycleOwner - ) { conference -> - if (conference != null) switchToFullScreenIfPossible(conference) - } - - conferenceViewModel.conferenceCreationPending.observe( - viewLifecycleOwner - ) { creationPending -> - if (!creationPending) { - val conference = conferenceViewModel.conference.value - if (conference != null) switchToFullScreenIfPossible(conference) - } - } - - conferenceViewModel.firstToJoinEvent.observe( - viewLifecycleOwner - ) { - it.consume { - Snackbar - .make( - binding.coordinator, - R.string.conference_first_to_join, - Snackbar.LENGTH_LONG - ) - .setAnchorView(binding.primaryButtons.hangup) - .show() - } - } - - conferenceViewModel.allParticipantsLeftEvent.observe( - viewLifecycleOwner - ) { - it.consume { - Snackbar - .make(binding.coordinator, R.string.conference_last_user, Snackbar.LENGTH_LONG) - .setAnchorView(binding.primaryButtons.hangup) - .show() - - switchToActiveSpeakerLayoutWhenAlone() - } - } - - controlsViewModel.goToConferenceParticipantsListEvent.observe( - viewLifecycleOwner - ) { - it.consume { - navigateToConferenceParticipants() - } - } - - controlsViewModel.goToChatEvent.observe( - viewLifecycleOwner - ) { - it.consume { - goToChat() - } - } - - controlsViewModel.goToCallsListEvent.observe( - viewLifecycleOwner - ) { - it.consume { - navigateToCallsList() - } - } - - controlsViewModel.goToConferenceLayoutSettingsEvent.observe( - viewLifecycleOwner - ) { - it.consume { - navigateToConferenceLayout() - } - } - - controlsViewModel.foldingState.observe( - viewLifecycleOwner - ) { feature -> - updateHingeRelatedConstraints(feature) - } - - callsViewModel.callUpdateEvent.observe( - viewLifecycleOwner - ) { - it.consume { call -> - val conference = call.conference - if (conference != null && conferenceViewModel.conference.value == null) { - Log.i( - "[Conference Call] Found conference attached to call and no conference in dedicated view model, init & configure it" - ) - conferenceViewModel.initConference(conference) - conferenceViewModel.configureConference(conference) - } - } - } - - controlsViewModel.goToDialerEvent.observe( - viewLifecycleOwner - ) { - it.consume { isCallTransfer -> - val intent = Intent() - intent.setClass(requireContext(), MainActivity::class.java) - if (corePreferences.skipDialerForNewCallAndTransfer) { - intent.putExtra("Contacts", true) - } else { - intent.putExtra("Dialer", true) - } - intent.putExtra("Transfer", isCallTransfer) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - startActivity(intent) - } - } - } - - override fun onPause() { - super.onPause() - - controlsViewModel.hideExtraButtons(true) - } - - override fun onResume() { - super.onResume() - - if (conferenceViewModel.conferenceDisplayMode.value == ConferenceDisplayMode.ACTIVE_SPEAKER) { - Log.i( - "[Conference Call] Conference fragment is resuming, current display mode is active speaker, adjusting layout" - ) - adjustActiveSpeakerLayout() - } - } - - private fun switchToFullScreenIfPossible(conference: Conference) { - if (corePreferences.enableFullScreenWhenJoiningVideoConference) { - if (conference.currentParams.isVideoEnabled) { - when { - conference.me.devices.isEmpty() -> { - Log.i( - "[Conference Call] Conference has video enabled but our device hasn't joined yet" - ) - } - conference.me.devices.find { - it.isInConference && it.getStreamAvailability( - StreamType.Video - ) - } != null -> { - Log.i( - "[Conference Call] Conference has video enabled & our device has video enabled, enabling full screen mode" - ) - controlsViewModel.fullScreenMode.value = true - } - else -> { - Log.i( - "[Conference Call] Conference has video enabled but our device video is disabled" - ) - } - } - } - } - } - - private fun goToChat() { - val intent = Intent() - intent.setClass(requireContext(), MainActivity::class.java) - intent.putExtra("Chat", true) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - startActivity(intent) - } - - private fun startTimer(timerId: Int) { - val timer: Chronometer? = binding.root.findViewById(timerId) - if (timer == null) { - Log.w("[Conference Call] Timer not found, maybe view wasn't inflated yet?") - return - } - - val conference = conferenceViewModel.conference.value - if (conference != null) { - val duration = 1000 * conference.duration // Linphone timestamps are in seconds - timer.base = SystemClock.elapsedRealtime() - duration - } else { - Log.e("[Conference Call] Conference not found, timer will have no base") - } - - timer.start() - } - - private fun updateHingeRelatedConstraints(feature: FoldingFeature) { - Log.i("[Conference Call] Updating constraint layout hinges: $feature") - val constraintLayout = binding.root.findViewById( - R.id.conference_constraint_layout - ) - ?: return - val set = ConstraintSet() - set.clone(constraintLayout) - - // Only modify UI in table top mode - if (feature.orientation == FoldingFeature.Orientation.HORIZONTAL && - feature.state == FoldingFeature.State.HALF_OPENED - ) { - set.setGuidelinePercent(R.id.hinge_top, 0.5f) - set.setGuidelinePercent(R.id.hinge_bottom, 0.5f) - controlsViewModel.folded.value = true - } else { - set.setGuidelinePercent(R.id.hinge_top, 0f) - set.setGuidelinePercent(R.id.hinge_bottom, 1f) - controlsViewModel.folded.value = false - } - - set.applyTo(constraintLayout) - } - - private fun animateConstraintLayout( - constraintLayout: ConstraintLayout, - set: ConstraintSet - ) { - val trans = AutoTransition() - trans.duration = 500 - trans.interpolator = AccelerateDecelerateInterpolator() - TransitionManager.beginDelayedTransition(constraintLayout, trans) - set.applyTo(constraintLayout) - } - - private fun adjustActiveSpeakerLayout() { - if (conferenceViewModel.conference.value?.state == Conference.State.Created) { - val participantsCount = conferenceViewModel.conferenceParticipantDevices.value.orEmpty().size - Log.i( - "[Conference Call] Updating active speaker layout for [$participantsCount] participants" - ) - when (participantsCount) { - 1 -> switchToActiveSpeakerLayoutWhenAlone() - 2 -> switchToActiveSpeakerLayoutForTwoParticipants() - else -> switchToActiveSpeakerLayoutForMoreThanTwoParticipants() - } - } else { - Log.w( - "[Conference] Active speaker layout not adjusted, conference state is: ${conferenceViewModel.conference.value?.state}" - ) - } - } - - private fun getConstraintSet(constraintLayout: ConstraintLayout): ConstraintSet { - val set = ConstraintSet() - set.clone(constraintLayout) - - set.clear(R.id.local_participant_background, ConstraintSet.TOP) - set.clear(R.id.local_participant_background, ConstraintSet.START) - set.clear(R.id.local_participant_background, ConstraintSet.LEFT) - set.clear(R.id.local_participant_background, ConstraintSet.BOTTOM) - set.clear(R.id.local_participant_background, ConstraintSet.END) - set.clear(R.id.local_participant_background, ConstraintSet.RIGHT) - - return set - } - - private fun switchToActiveSpeakerLayoutForMoreThanTwoParticipants() { - if (conferenceViewModel.conferenceDisplayMode.value != ConferenceDisplayMode.ACTIVE_SPEAKER) return - - val constraintLayout = - binding.root.findViewById(R.id.conference_constraint_layout) - ?: return - val set = getConstraintSet(constraintLayout) - - val margin = resources.getDimension(R.dimen.voip_active_speaker_miniature_margin).toInt() - val portraitOrientation = resources.configuration.orientation != Configuration.ORIENTATION_LANDSCAPE - if (portraitOrientation) { - set.connect( - R.id.local_participant_background, - ConstraintSet.START, - R.id.conference_constraint_layout, - ConstraintSet.START, - margin - ) - set.connect( - R.id.local_participant_background, - ConstraintSet.BOTTOM, - R.id.miniatures, - ConstraintSet.BOTTOM, - 0 - ) - set.connect( - R.id.local_participant_background, - ConstraintSet.TOP, - R.id.miniatures, - ConstraintSet.TOP, - 0 - ) - } else { - set.connect( - R.id.local_participant_background, - ConstraintSet.BOTTOM, - R.id.hinge_bottom, - ConstraintSet.BOTTOM, - 0 - ) - set.connect( - R.id.local_participant_background, - ConstraintSet.START, - R.id.active_speaker_background, - ConstraintSet.END, - 0 - ) - set.connect( - R.id.local_participant_background, - ConstraintSet.END, - R.id.scroll_indicator, - ConstraintSet.START, - 0 - ) - } - - val size = resources.getDimension(R.dimen.voip_active_speaker_miniature_size).toInt() - set.constrainWidth(R.id.local_participant_background, size) - set.constrainHeight(R.id.local_participant_background, size) - - val avatarSize = resources.getDimension( - R.dimen.voip_conference_active_speaker_miniature_avatar_size - ).toInt() - set.constrainWidth(R.id.local_participant_avatar, avatarSize) - set.constrainHeight(R.id.local_participant_avatar, avatarSize) - - Log.i("[Conference Call] Updating active speaker layout for 3 or more participants") - if (corePreferences.enableAnimations) { - animateConstraintLayout(constraintLayout, set) - } else { - set.applyTo(constraintLayout) - } - } - - private fun switchToActiveSpeakerLayoutForTwoParticipants() { - if (conferenceViewModel.conferenceDisplayMode.value != ConferenceDisplayMode.ACTIVE_SPEAKER) return - - val constraintLayout = - binding.root.findViewById(R.id.conference_constraint_layout) - ?: return - val set = getConstraintSet(constraintLayout) - - val margin = resources.getDimension(R.dimen.voip_active_speaker_miniature_margin).toInt() - set.connect( - R.id.local_participant_background, - ConstraintSet.BOTTOM, - R.id.hinge_bottom, - ConstraintSet.BOTTOM, - margin - ) - // Don't know why but if we use END instead of RIGHT, margin isn't applied... - set.connect( - R.id.local_participant_background, - ConstraintSet.RIGHT, - R.id.conference_constraint_layout, - ConstraintSet.RIGHT, - margin - ) - - val size = resources.getDimension(R.dimen.voip_active_speaker_miniature_size).toInt() - set.constrainWidth(R.id.local_participant_background, size) - set.constrainHeight(R.id.local_participant_background, size) - - val avatarSize = resources.getDimension( - R.dimen.voip_conference_active_speaker_miniature_avatar_size - ).toInt() - set.constrainWidth(R.id.local_participant_avatar, avatarSize) - set.constrainHeight(R.id.local_participant_avatar, avatarSize) - - Log.i("[Conference Call] Updating active speaker layout for 2 participants") - if (corePreferences.enableAnimations) { - animateConstraintLayout(constraintLayout, set) - } else { - set.applyTo(constraintLayout) - } - } - - private fun switchToActiveSpeakerLayoutWhenAlone() { - if (conferenceViewModel.conferenceDisplayMode.value != ConferenceDisplayMode.ACTIVE_SPEAKER) return - - val constraintLayout = - binding.root.findViewById(R.id.conference_constraint_layout) - ?: return - val set = getConstraintSet(constraintLayout) - - set.connect( - R.id.local_participant_background, - ConstraintSet.BOTTOM, - R.id.hinge_bottom, - ConstraintSet.BOTTOM, - 0 - ) - set.connect( - R.id.local_participant_background, - ConstraintSet.END, - R.id.conference_constraint_layout, - ConstraintSet.END, - 0 - ) - set.connect( - R.id.local_participant_background, - ConstraintSet.TOP, - R.id.top_barrier, - ConstraintSet.BOTTOM, - 0 - ) - set.connect( - R.id.local_participant_background, - ConstraintSet.START, - R.id.conference_constraint_layout, - ConstraintSet.START, - 0 - ) - - set.constrainWidth(R.id.local_participant_background, 0) - set.constrainHeight(R.id.local_participant_background, 0) - set.constrainWidth(R.id.local_participant_avatar, 0) - set.constrainHeight(R.id.local_participant_avatar, 0) - - Log.i("[Conference Call] Updating active speaker layout for 1 participant (myself)") - if (corePreferences.enableAnimations) { - animateConstraintLayout(constraintLayout, set) - } else { - set.applyTo(constraintLayout) - } - } -} diff --git a/app/src/main/java/org/linphone/activities/voip/fragments/ConferenceLayoutFragment.kt b/app/src/main/java/org/linphone/activities/voip/fragments/ConferenceLayoutFragment.kt deleted file mode 100644 index f840c99ac..000000000 --- a/app/src/main/java/org/linphone/activities/voip/fragments/ConferenceLayoutFragment.kt +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.voip.fragments - -import android.os.Bundle -import android.view.View -import androidx.constraintlayout.widget.ConstraintLayout -import androidx.navigation.navGraphViewModels -import org.linphone.R -import org.linphone.activities.voip.ConferenceDisplayMode -import org.linphone.activities.voip.viewmodels.ConferenceViewModel -import org.linphone.activities.voip.viewmodels.ControlsViewModel -import org.linphone.databinding.VoipConferenceLayoutFragmentBinding - -class ConferenceLayoutFragment : GenericVideoPreviewFragment() { - private val conferenceViewModel: ConferenceViewModel by navGraphViewModels(R.id.call_nav_graph) - private val controlsViewModel: ControlsViewModel by navGraphViewModels(R.id.call_nav_graph) - - override fun getLayoutId(): Int = R.layout.voip_conference_layout_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - binding.conferenceViewModel = conferenceViewModel - - binding.controlsViewModel = controlsViewModel - - binding.setCancelClickListener { - goBack() - } - - conferenceViewModel.conferenceParticipantDevices.observe( - viewLifecycleOwner - ) { - if (it.size > conferenceViewModel.maxParticipantsForMosaicLayout && conferenceViewModel.conferenceDisplayMode.value == ConferenceDisplayMode.GRID) { - showTooManyParticipantsForMosaicLayoutDialog() - } - } - - conferenceViewModel.conferenceDisplayMode.observe( - viewLifecycleOwner - ) { - binding.localPreviewVideoSurface.visibility = if (it == ConferenceDisplayMode.AUDIO_ONLY) { - View.GONE - } else { - View.VISIBLE - } - } - - binding.setDismissDialogClickListener { - val dialog = binding.root.findViewById( - R.id.too_many_participants_dialog - ) - dialog?.visibility = View.GONE - } - } - - override fun onResume() { - super.onResume() - - if (conferenceViewModel.conferenceParticipantDevices.value.orEmpty().size > conferenceViewModel.maxParticipantsForMosaicLayout) { - showTooManyParticipantsForMosaicLayoutDialog() - } - - setupLocalVideoPreview(binding.localPreviewVideoSurface, binding.switchCamera) - } - - override fun onPause() { - super.onPause() - - cleanUpLocalVideoPreview(binding.localPreviewVideoSurface) - } - - private fun showTooManyParticipantsForMosaicLayoutDialog() { - val dialog = binding.root.findViewById(R.id.too_many_participants_dialog) - dialog?.visibility = View.VISIBLE - } -} diff --git a/app/src/main/java/org/linphone/activities/voip/fragments/ConferenceParticipantsFragment.kt b/app/src/main/java/org/linphone/activities/voip/fragments/ConferenceParticipantsFragment.kt deleted file mode 100644 index c0acf62fa..000000000 --- a/app/src/main/java/org/linphone/activities/voip/fragments/ConferenceParticipantsFragment.kt +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.voip.fragments - -import android.os.Bundle -import android.view.View -import android.widget.Toast -import androidx.navigation.navGraphViewModels -import org.linphone.R -import org.linphone.activities.navigateToAddParticipants -import org.linphone.activities.voip.viewmodels.ConferenceViewModel -import org.linphone.activities.voip.viewmodels.ControlsViewModel -import org.linphone.core.tools.Log -import org.linphone.databinding.VoipConferenceParticipantsFragmentBinding - -class ConferenceParticipantsFragment : GenericVideoPreviewFragment() { - private val conferenceViewModel: ConferenceViewModel by navGraphViewModels(R.id.call_nav_graph) - private val controlsViewModel: ControlsViewModel by navGraphViewModels(R.id.call_nav_graph) - - // Only display events happening during while this fragment is visible - private var skipEvents = true - - override fun getLayoutId(): Int = R.layout.voip_conference_participants_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - binding.conferenceViewModel = conferenceViewModel - - binding.controlsViewModel = controlsViewModel - - conferenceViewModel.conferenceExists.observe( - viewLifecycleOwner - ) { exists -> - if (!exists) { - Log.w("[Conference Participants] Conference no longer exists, going back") - goBack() - } - } - - conferenceViewModel.participantAdminStatusChangedEvent.observe( - viewLifecycleOwner - ) { - it.consume { participantData -> - val participantName = - participantData.contact.value?.name ?: participantData.displayName.value - val message = if (participantData.participant.isAdmin) { - getString(R.string.conference_admin_set).format(participantName) - } else { - getString(R.string.conference_admin_unset).format(participantName) - } - if (!skipEvents) { - Toast.makeText(context, message, Toast.LENGTH_SHORT).show() - } - } - } - - binding.setCancelClickListener { - goBack() - } - - binding.setEditClickListener { - navigateToAddParticipants() - } - } - - override fun onResume() { - super.onResume() - - skipEvents = false - setupLocalVideoPreview(binding.localPreviewVideoSurface, binding.switchCamera) - } - - override fun onPause() { - super.onPause() - - skipEvents = true - cleanUpLocalVideoPreview(binding.localPreviewVideoSurface) - } -} diff --git a/app/src/main/java/org/linphone/activities/voip/fragments/GenericVideoPreviewFragment.kt b/app/src/main/java/org/linphone/activities/voip/fragments/GenericVideoPreviewFragment.kt deleted file mode 100644 index 98372c58e..000000000 --- a/app/src/main/java/org/linphone/activities/voip/fragments/GenericVideoPreviewFragment.kt +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) 2010-2022 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.voip.fragments - -import android.view.MotionEvent -import android.view.TextureView -import android.view.View -import android.widget.ImageView -import androidx.databinding.ViewDataBinding -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.activities.GenericFragment - -abstract class GenericVideoPreviewFragment : GenericFragment() { - private var previewX: Float = 0f - private var previewY: Float = 0f - private var switchX: Float = 0f - private var switchY: Float = 0f - - private var switchCameraImageView: ImageView? = null - - private val previewTouchListener = View.OnTouchListener { view, event -> - when (event.action) { - MotionEvent.ACTION_DOWN -> { - previewX = view.x - event.rawX - previewY = view.y - event.rawY - switchX = (switchCameraImageView?.x ?: 0f) - event.rawX - switchY = (switchCameraImageView?.y ?: 0f) - event.rawY - true - } - MotionEvent.ACTION_MOVE -> { - view.animate() - .x(event.rawX + previewX) - .y(event.rawY + previewY) - .setDuration(0) - .start() - switchCameraImageView?.apply { - animate() - .x(event.rawX + switchX) - .y(event.rawY + switchY) - .setDuration(0) - .start() - } - true - } - else -> { - view.performClick() - false - } - } - } - - protected fun setupLocalVideoPreview(localVideoPreview: TextureView, switchCamera: ImageView?) { - switchCameraImageView = switchCamera - localVideoPreview.setOnTouchListener(previewTouchListener) - coreContext.core.nativePreviewWindowId = localVideoPreview - } - - protected fun cleanUpLocalVideoPreview(localVideoPreview: TextureView) { - localVideoPreview.setOnTouchListener(null) - } -} diff --git a/app/src/main/java/org/linphone/activities/voip/fragments/IncomingCallFragment.kt b/app/src/main/java/org/linphone/activities/voip/fragments/IncomingCallFragment.kt deleted file mode 100644 index b10a04710..000000000 --- a/app/src/main/java/org/linphone/activities/voip/fragments/IncomingCallFragment.kt +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.voip.fragments - -import android.os.Bundle -import android.os.SystemClock -import android.view.View -import android.widget.Chronometer -import androidx.navigation.navGraphViewModels -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R -import org.linphone.activities.GenericFragment -import org.linphone.activities.navigateToActiveCall -import org.linphone.activities.voip.viewmodels.CallsViewModel -import org.linphone.activities.voip.viewmodels.ControlsViewModel -import org.linphone.core.tools.Log -import org.linphone.databinding.VoipCallIncomingFragmentBinding - -class IncomingCallFragment : GenericFragment() { - private val controlsViewModel: ControlsViewModel by navGraphViewModels(R.id.call_nav_graph) - private val callsViewModel: CallsViewModel by navGraphViewModels(R.id.call_nav_graph) - - override fun getLayoutId(): Int = R.layout.voip_call_incoming_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - binding.controlsViewModel = controlsViewModel - - binding.callsViewModel = callsViewModel - - callsViewModel.callConnectedEvent.observe( - viewLifecycleOwner - ) { - it.consume { - navigateToActiveCall() - } - } - - callsViewModel.callEndedEvent.observe( - viewLifecycleOwner - ) { - it.consume { - navigateToActiveCall() - } - } - - callsViewModel.currentCallData.observe( - viewLifecycleOwner - ) { - if (it != null) { - val timer = binding.root.findViewById(R.id.incoming_call_timer) - timer.base = - SystemClock.elapsedRealtime() - (1000 * it.call.duration) // Linphone timestamps are in seconds - timer.start() - } - } - - val earlyMediaVideo = arguments?.getBoolean("earlyMediaVideo") ?: false - if (earlyMediaVideo) { - Log.i("[Incoming Call] Video early media detected, setting native window id") - coreContext.core.nativeVideoWindowId = binding.remoteVideoSurface - } - } - - // We don't want the proximity sensor to turn screen OFF in this fragment - override fun onResume() { - super.onResume() - controlsViewModel.forceDisableProximitySensor.value = true - } - - override fun onPause() { - controlsViewModel.forceDisableProximitySensor.value = false - super.onPause() - } -} diff --git a/app/src/main/java/org/linphone/activities/voip/fragments/OutgoingCallFragment.kt b/app/src/main/java/org/linphone/activities/voip/fragments/OutgoingCallFragment.kt deleted file mode 100644 index 8ba1baa55..000000000 --- a/app/src/main/java/org/linphone/activities/voip/fragments/OutgoingCallFragment.kt +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.voip.fragments - -import android.os.Bundle -import android.os.SystemClock -import android.view.View -import android.widget.Chronometer -import androidx.navigation.navGraphViewModels -import org.linphone.R -import org.linphone.activities.navigateToActiveCall -import org.linphone.activities.voip.viewmodels.CallsViewModel -import org.linphone.activities.voip.viewmodels.ControlsViewModel -import org.linphone.databinding.VoipCallOutgoingFragmentBinding - -class OutgoingCallFragment : GenericVideoPreviewFragment() { - private val controlsViewModel: ControlsViewModel by navGraphViewModels(R.id.call_nav_graph) - private val callsViewModel: CallsViewModel by navGraphViewModels(R.id.call_nav_graph) - - override fun getLayoutId(): Int = R.layout.voip_call_outgoing_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - - binding.controlsViewModel = controlsViewModel - - binding.callsViewModel = callsViewModel - - callsViewModel.callConnectedEvent.observe( - viewLifecycleOwner - ) { - it.consume { - navigateToActiveCall() - } - } - - callsViewModel.callEndedEvent.observe( - viewLifecycleOwner - ) { - it.consume { - navigateToActiveCall() - } - } - - callsViewModel.currentCallData.observe( - viewLifecycleOwner - ) { - if (it != null) { - val timer = binding.root.findViewById(R.id.outgoing_call_timer) - timer.base = - SystemClock.elapsedRealtime() - (1000 * it.call.duration) // Linphone timestamps are in seconds - timer.start() - } - } - - controlsViewModel.isOutgoingEarlyMedia.observe( - viewLifecycleOwner - ) { - if (it) { - setupLocalVideoPreview(binding.localPreviewVideoSurface, binding.switchCamera) - } - } - } - - // We don't want the proximity sensor to turn screen OFF in this fragment - override fun onResume() { - super.onResume() - - controlsViewModel.forceDisableProximitySensor.value = true - if (controlsViewModel.isOutgoingEarlyMedia.value == true) { - setupLocalVideoPreview(binding.localPreviewVideoSurface, binding.switchCamera) - } - } - - override fun onPause() { - super.onPause() - - controlsViewModel.forceDisableProximitySensor.value = false - cleanUpLocalVideoPreview(binding.localPreviewVideoSurface) - } -} diff --git a/app/src/main/java/org/linphone/activities/voip/fragments/SingleCallFragment.kt b/app/src/main/java/org/linphone/activities/voip/fragments/SingleCallFragment.kt deleted file mode 100644 index 2f90854af..000000000 --- a/app/src/main/java/org/linphone/activities/voip/fragments/SingleCallFragment.kt +++ /dev/null @@ -1,265 +0,0 @@ -/* - * Copyright (c) 2010-2022 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.voip.fragments - -import android.app.Dialog -import android.content.Intent -import android.os.Bundle -import android.os.SystemClock -import android.view.View -import android.widget.Chronometer -import androidx.constraintlayout.widget.ConstraintSet -import androidx.navigation.navGraphViewModels -import androidx.window.layout.FoldingFeature -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.* -import org.linphone.activities.main.MainActivity -import org.linphone.activities.main.viewmodels.DialogViewModel -import org.linphone.activities.voip.viewmodels.CallsViewModel -import org.linphone.activities.voip.viewmodels.ConferenceViewModel -import org.linphone.activities.voip.viewmodels.ControlsViewModel -import org.linphone.activities.voip.viewmodels.StatisticsListViewModel -import org.linphone.core.* -import org.linphone.core.tools.Log -import org.linphone.databinding.VoipSingleCallFragmentBinding -import org.linphone.utils.AppUtils -import org.linphone.utils.DialogUtils - -class SingleCallFragment : GenericVideoPreviewFragment() { - private val controlsViewModel: ControlsViewModel by navGraphViewModels(R.id.call_nav_graph) - private val callsViewModel: CallsViewModel by navGraphViewModels(R.id.call_nav_graph) - private val conferenceViewModel: ConferenceViewModel by navGraphViewModels(R.id.call_nav_graph) - private val statsViewModel: StatisticsListViewModel by navGraphViewModels(R.id.call_nav_graph) - - private var dialog: Dialog? = null - - override fun getLayoutId(): Int = R.layout.voip_single_call_fragment - - override fun onStart() { - useMaterialSharedAxisXForwardAnimation = false - - super.onStart() - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - controlsViewModel.hideCallStats() // In case it was toggled on during incoming/outgoing fragment was visible - - binding.lifecycleOwner = viewLifecycleOwner - - binding.controlsViewModel = controlsViewModel - - binding.callsViewModel = callsViewModel - - binding.conferenceViewModel = conferenceViewModel - - binding.statsViewModel = statsViewModel - - callsViewModel.currentCallData.observe( - viewLifecycleOwner - ) { callData -> - if (callData != null) { - val call = callData.call - when (val callState = call.state) { - Call.State.IncomingReceived, Call.State.IncomingEarlyMedia -> { - Log.i( - "[Single Call] New current call is in [$callState] state, switching to IncomingCall fragment" - ) - navigateToIncomingCall() - } - Call.State.OutgoingRinging, Call.State.OutgoingEarlyMedia -> { - Log.i( - "[Single Call] New current call is in [$callState] state, switching to OutgoingCall fragment" - ) - navigateToOutgoingCall() - } - else -> { - Log.i( - "[Single Call] New current call is in [$callState] state, updating call UI" - ) - val timer = binding.root.findViewById(R.id.active_call_timer) - timer.base = - SystemClock.elapsedRealtime() - (1000 * call.duration) // Linphone timestamps are in seconds - timer.start() - - if (corePreferences.enableFullScreenWhenJoiningVideoCall) { - if (call.currentParams.isVideoEnabled) { - Log.i( - "[Single Call] Call params have video enabled, enabling full screen mode" - ) - controlsViewModel.fullScreenMode.value = true - } - } - } - } - } - } - - controlsViewModel.goToConferenceParticipantsListEvent.observe( - viewLifecycleOwner - ) { - it.consume { - navigateToConferenceParticipants() - } - } - - controlsViewModel.goToChatEvent.observe( - viewLifecycleOwner - ) { - it.consume { - goToChat() - } - } - - controlsViewModel.goToCallsListEvent.observe( - viewLifecycleOwner - ) { - it.consume { - navigateToCallsList() - } - } - - controlsViewModel.goToConferenceLayoutSettingsEvent.observe( - viewLifecycleOwner - ) { - it.consume { - navigateToConferenceLayout() - } - } - - controlsViewModel.foldingState.observe( - viewLifecycleOwner - ) { feature -> - updateHingeRelatedConstraints(feature) - } - - callsViewModel.callUpdateEvent.observe( - viewLifecycleOwner - ) { - it.consume { call -> - if (call.state == Call.State.StreamsRunning) { - dialog?.dismiss() - } else if (call.state == Call.State.UpdatedByRemote) { - if (coreContext.core.isVideoEnabled) { - val remoteVideo = call.remoteParams?.isVideoEnabled ?: false - val localVideo = call.currentParams.isVideoEnabled - if (remoteVideo && !localVideo) { - showCallVideoUpdateDialog(call) - } - } else { - Log.w( - "[Single Call] Video display & capture are disabled, don't show video dialog" - ) - } - } - } - } - - controlsViewModel.goToDialerEvent.observe( - viewLifecycleOwner - ) { - it.consume { isCallTransfer -> - val intent = Intent() - intent.setClass(requireContext(), MainActivity::class.java) - if (corePreferences.skipDialerForNewCallAndTransfer) { - intent.putExtra("Contacts", true) - } else { - intent.putExtra("Dialer", true) - } - intent.putExtra("Transfer", isCallTransfer) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - startActivity(intent) - } - } - } - - override fun onResume() { - super.onResume() - - coreContext.core.nativeVideoWindowId = binding.remoteVideoSurface - setupLocalVideoPreview(binding.localPreviewVideoSurface, binding.switchCamera) - } - - override fun onPause() { - super.onPause() - - controlsViewModel.hideExtraButtons(true) - cleanUpLocalVideoPreview(binding.localPreviewVideoSurface) - } - - private fun showCallVideoUpdateDialog(call: Call) { - val viewModel = DialogViewModel( - AppUtils.getString(R.string.call_video_update_requested_dialog) - ) - dialog = DialogUtils.getVoipDialog(requireContext(), viewModel) - - viewModel.showCancelButton( - { - coreContext.answerCallVideoUpdateRequest(call, false) - dialog?.dismiss() - }, - getString(R.string.dialog_decline) - ) - - viewModel.showOkButton( - { - coreContext.answerCallVideoUpdateRequest(call, true) - dialog?.dismiss() - }, - getString(R.string.dialog_accept) - ) - - dialog?.show() - } - - private fun goToChat() { - val intent = Intent() - intent.setClass(requireContext(), MainActivity::class.java) - intent.putExtra("Chat", true) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - startActivity(intent) - } - - private fun updateHingeRelatedConstraints(feature: FoldingFeature) { - Log.i("[Single Call] Updating constraint layout hinges: $feature") - - val constraintLayout = binding.constraintLayout - val set = ConstraintSet() - set.clone(constraintLayout) - - // Only modify UI in table top mode - if (feature.orientation == FoldingFeature.Orientation.HORIZONTAL && - feature.state == FoldingFeature.State.HALF_OPENED - ) { - set.setGuidelinePercent(R.id.hinge_top, 0.5f) - set.setGuidelinePercent(R.id.hinge_bottom, 0.5f) - controlsViewModel.folded.value = true - } else { - set.setGuidelinePercent(R.id.hinge_top, 0f) - set.setGuidelinePercent(R.id.hinge_bottom, 1f) - controlsViewModel.folded.value = false - } - - set.applyTo(constraintLayout) - } -} diff --git a/app/src/main/java/org/linphone/activities/voip/fragments/StatusFragment.kt b/app/src/main/java/org/linphone/activities/voip/fragments/StatusFragment.kt deleted file mode 100644 index 0536d1da6..000000000 --- a/app/src/main/java/org/linphone/activities/voip/fragments/StatusFragment.kt +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.voip.fragments - -import android.app.Dialog -import android.os.Bundle -import android.view.View -import androidx.lifecycle.ViewModelProvider -import androidx.navigation.navGraphViewModels -import java.util.* -import org.linphone.R -import org.linphone.activities.GenericFragment -import org.linphone.activities.main.viewmodels.DialogViewModel -import org.linphone.activities.voip.viewmodels.ControlsViewModel -import org.linphone.activities.voip.viewmodels.StatusViewModel -import org.linphone.core.Call -import org.linphone.core.tools.Log -import org.linphone.databinding.VoipStatusFragmentBinding -import org.linphone.utils.DialogUtils - -class StatusFragment : GenericFragment() { - private lateinit var viewModel: StatusViewModel - private val controlsViewModel: ControlsViewModel by navGraphViewModels(R.id.call_nav_graph) - - private var zrtpDialog: Dialog? = null - - override fun getLayoutId(): Int = R.layout.voip_status_fragment - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - binding.lifecycleOwner = viewLifecycleOwner - useMaterialSharedAxisXForwardAnimation = false - - viewModel = ViewModelProvider(this)[StatusViewModel::class.java] - binding.viewModel = viewModel - - binding.setRefreshClickListener { - viewModel.refreshRegister() - } - - viewModel.showZrtpDialogEvent.observe( - viewLifecycleOwner - ) { - it.consume { call -> - if (call.state == Call.State.Connected || call.state == Call.State.StreamsRunning) { - showZrtpDialog(call) - } - } - } - - viewModel.showCallStatsEvent.observe( - viewLifecycleOwner - ) { - it.consume { - controlsViewModel.showCallStats(skipAnimation = true) - } - } - } - - override fun onDestroy() { - if (zrtpDialog != null) { - zrtpDialog?.dismiss() - } - super.onDestroy() - } - - private fun showZrtpDialog(call: Call) { - if (zrtpDialog != null && zrtpDialog?.isShowing == true) { - Log.w( - "[Status Fragment] ZRTP dialog already visible, closing it and creating a new one" - ) - zrtpDialog?.dismiss() - zrtpDialog = null - } - - val token = call.authenticationToken - if (token == null || token.length < 4) { - Log.e("[Status Fragment] ZRTP token is invalid: $token") - return - } - - val toRead: String - val toListen: String - when (call.dir) { - Call.Dir.Incoming -> { - toRead = token.substring(0, 2) - toListen = token.substring(2) - } - else -> { - toRead = token.substring(2) - toListen = token.substring(0, 2) - } - } - - val viewModel = DialogViewModel( - getString(R.string.zrtp_dialog_explanation), - getString(R.string.zrtp_dialog_title) - ) - viewModel.showZrtp = true - viewModel.zrtpReadSas = toRead.uppercase(Locale.getDefault()) - viewModel.zrtpListenSas = toListen.uppercase(Locale.getDefault()) - viewModel.showIcon = true - viewModel.iconResource = if (call.audioStats?.isZrtpKeyAgreementAlgoPostQuantum == true) { - R.drawable.security_post_quantum - } else { - R.drawable.security_2_indicator - } - - val dialog: Dialog = DialogUtils.getVoipDialog(requireContext(), viewModel) - - viewModel.showCancelButton( - { - if (call.state != Call.State.End && call.state != Call.State.Released) { - if (call.authenticationTokenVerified) { - Log.w( - "[Status Fragment] Removing trust from previously verified ZRTP SAS auth token" - ) - this@StatusFragment.viewModel.previouslyDeclineToken = true - call.authenticationTokenVerified = false - } - } else { - Log.e( - "[Status Fragment] Can't decline the ZRTP SAS token, call is in state [${call.state}]" - ) - } - dialog.dismiss() - zrtpDialog = null - }, - getString(R.string.zrtp_dialog_later_button_label) - ) - - viewModel.showOkButton( - { - if (call.state != Call.State.End && call.state != Call.State.Released) { - call.authenticationTokenVerified = true - } else { - Log.e( - "[Status Fragment] Can't verify the ZRTP SAS token, call is in state [${call.state}]" - ) - } - dialog.dismiss() - zrtpDialog = null - }, - getString(R.string.zrtp_dialog_correct_button_label) - ) - - viewModel.dismissEvent.observe(viewLifecycleOwner) { - it.consume { - dialog.dismiss() - } - } - - zrtpDialog = dialog - dialog.show() - } -} diff --git a/app/src/main/java/org/linphone/activities/voip/viewmodels/CallsViewModel.kt b/app/src/main/java/org/linphone/activities/voip/viewmodels/CallsViewModel.kt deleted file mode 100644 index d12d0ce90..000000000 --- a/app/src/main/java/org/linphone/activities/voip/viewmodels/CallsViewModel.kt +++ /dev/null @@ -1,330 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.voip.viewmodels - -import android.Manifest -import androidx.lifecycle.MediatorLiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R -import org.linphone.activities.voip.data.CallData -import org.linphone.core.* -import org.linphone.core.tools.Log -import org.linphone.utils.AppUtils -import org.linphone.utils.Event -import org.linphone.utils.PermissionHelper - -class CallsViewModel : ViewModel() { - val currentCallData = MutableLiveData() - - val callsData = MutableLiveData>() - - val inactiveCallsCount = MutableLiveData() - - val currentCallUnreadChatMessageCount = MutableLiveData() - - val chatAndCallsCount = MediatorLiveData() - - val isMicrophoneMuted = MutableLiveData() - - val isMuteMicrophoneEnabled = MutableLiveData() - - val callConnectedEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val callEndedEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val callUpdateEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val noMoreCallEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val askPermissionEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - private val listener = object : CoreListenerStub() { - override fun onChatRoomRead(core: Core, chatRoom: ChatRoom) { - updateUnreadChatCount() - } - - override fun onMessagesReceived( - core: Core, - chatRoom: ChatRoom, - messages: Array - ) { - updateUnreadChatCount() - } - - override fun onLastCallEnded(core: Core) { - Log.i("[Calls] Last call ended") - currentCallData.value?.destroy() - noMoreCallEvent.value = Event(true) - } - - override fun onCallStateChanged(core: Core, call: Call, state: Call.State, message: String) { - Log.i("[Calls] Call with ID [${call.callLog.callId}] state changed: $state") - - if (state == Call.State.IncomingEarlyMedia || state == Call.State.IncomingReceived || state == Call.State.OutgoingInit) { - if (!callDataAlreadyExists(call)) { - addCallToList(call) - } - } - - val currentCall = core.currentCall - Log.i("[Calls] Current call is ${currentCall?.remoteAddress?.asStringUriOnly()}") - if (currentCall != null && currentCallData.value?.call != currentCall) { - updateCurrentCallData(currentCall) - } else if (currentCall == null && core.callsNb > 0) { - updateCurrentCallData(null) - } else if (currentCallData.value == null) { - updateCurrentCallData(currentCall) - } - - if (state == Call.State.End || state == Call.State.Released || state == Call.State.Error) { - removeCallFromList(call) - if (core.callsNb > 0) { - Log.i( - "[Calls] Call has ended but there are still at least one other existing call" - ) - callEndedEvent.value = Event(call) - } - } else if (call.state == Call.State.UpdatedByRemote) { - // If the correspondent asks to turn on video while audio call, - // defer update until user has chosen whether to accept it or not - val remoteVideo = call.remoteParams?.isVideoEnabled ?: false - val localVideo = call.currentParams.isVideoEnabled - val autoAccept = call.core.videoActivationPolicy.automaticallyAccept - if (remoteVideo && !localVideo && !autoAccept) { - if (coreContext.core.isVideoCaptureEnabled || coreContext.core.isVideoDisplayEnabled) { - call.deferUpdate() - callUpdateEvent.value = Event(call) - } else { - coreContext.answerCallVideoUpdateRequest(call, false) - } - } - } else if (state == Call.State.Connected) { - callConnectedEvent.value = Event(call) - } else if (state == Call.State.StreamsRunning) { - callUpdateEvent.value = Event(call) - } - - updateInactiveCallsCount() - } - } - - init { - coreContext.core.addListener(listener) - - val currentCall = coreContext.core.currentCall ?: coreContext.core.calls.firstOrNull() - if (currentCall != null) { - Log.i( - "[Calls] Initializing ViewModel using call [${currentCall.remoteAddress.asStringUriOnly()}] as current" - ) - currentCallData.value?.destroy() - - val viewModel = CallData(currentCall) - currentCallData.value = viewModel - } - - chatAndCallsCount.value = 0 - chatAndCallsCount.addSource(inactiveCallsCount) { - chatAndCallsCount.value = updateCallsAndChatCount() - } - chatAndCallsCount.addSource(currentCallUnreadChatMessageCount) { - chatAndCallsCount.value = updateCallsAndChatCount() - } - - initCallList() - updateInactiveCallsCount() - updateUnreadChatCount() - updateMicState() - } - - override fun onCleared() { - coreContext.core.removeListener(listener) - - currentCallData.value?.destroy() - callsData.value.orEmpty().forEach(CallData::destroy) - - super.onCleared() - } - - fun toggleMuteMicrophone() { - if (!PermissionHelper.get().hasRecordAudioPermission()) { - askPermissionEvent.value = Event(Manifest.permission.RECORD_AUDIO) - return - } - - val call = currentCallData.value?.call - if (call != null && call.conference != null) { - val micMuted = call.conference?.microphoneMuted ?: false - call.conference?.microphoneMuted = !micMuted - } else { - val micMuted = call?.microphoneMuted ?: false - call?.microphoneMuted = !micMuted - } - updateMicState() - } - - fun mergeCallsIntoConference() { - Log.i("[Calls] Merging all calls into new conference") - val core = coreContext.core - val params = core.createConferenceParams(null) - params.subject = AppUtils.getString(R.string.conference_local_title) - // Prevent group call to start in audio only layout - params.isVideoEnabled = true - val conference = core.createConferenceWithParams(params) - conference?.addParticipants(core.calls) - } - - private fun initCallList() { - val calls = arrayListOf() - - for (call in coreContext.core.calls) { - val data: CallData = if (currentCallData.value?.call == call) { - currentCallData.value!! - } else { - CallData(call) - } - Log.i("[Calls] Adding call with ID ${call.callLog.callId} to calls list") - calls.add(data) - } - - callsData.value = calls - } - - private fun addCallToList(call: Call) { - Log.i("[Calls] Adding call with ID ${call.callLog.callId} to calls list") - - val calls = arrayListOf() - calls.addAll(callsData.value.orEmpty()) - - val data = CallData(call) - calls.add(data) - - callsData.value = calls - } - - private fun removeCallFromList(call: Call) { - Log.i("[Calls] Removing call with ID ${call.callLog.callId} from calls list") - - val calls = arrayListOf() - for (data in callsData.value.orEmpty()) { - if (data.call == call) { - data.destroy() - } else { - calls.add(data) - } - } - - callsData.value = calls - } - - private fun updateCurrentCallData(currentCall: Call?) { - var callToUse = currentCall - if (currentCall == null) { - if (coreContext.core.callsNb == 1) { - // Make sure the current call data is matching the only call - val firstData = callsData.value?.firstOrNull() - if (firstData != null && currentCallData.value != firstData) { - Log.i( - "[Calls] Only one call in Core and the current call data doesn't match it, updating it" - ) - currentCallData.value = firstData!! - } - return - } - - val firstCall = coreContext.core.calls.find { call -> - call.state != Call.State.Error && call.state != Call.State.End && call.state != Call.State.Released - } - if (firstCall != null && currentCallData.value?.call != firstCall) { - Log.i( - "[Calls] Using [${firstCall.remoteAddress.asStringUriOnly()}] call as \"current\" call" - ) - callToUse = firstCall - } - } - - if (callToUse == null) { - Log.w("[Calls] No call found to be used as \"current\"") - return - } - - var found = false - for (callData in callsData.value.orEmpty()) { - if (callData.call == callToUse) { - Log.i( - "[Calls] Updating current call to: ${callData.call.remoteAddress.asStringUriOnly()}" - ) - currentCallData.value = callData - found = true - break - } - } - if (!found) { - Log.w( - "[Calls] Call with ID [${callToUse.callLog.callId}] not found in calls data list, shouldn't happen!" - ) - val viewModel = CallData(callToUse) - currentCallData.value = viewModel - } - - updateMicState() - // updateUnreadChatCount() - } - - private fun callDataAlreadyExists(call: Call): Boolean { - for (callData in callsData.value.orEmpty()) { - if (callData.call == call) { - return true - } - } - return false - } - - fun updateMicState() { - isMicrophoneMuted.value = !PermissionHelper.get().hasRecordAudioPermission() || currentCallData.value?.call?.microphoneMuted == true - isMuteMicrophoneEnabled.value = currentCallData.value?.call != null - } - - private fun updateCallsAndChatCount(): Int { - return (inactiveCallsCount.value ?: 0) + (currentCallUnreadChatMessageCount.value ?: 0) - } - - private fun updateUnreadChatCount() { - // For now we don't display in-call chat, so use global unread chat messages count - currentCallUnreadChatMessageCount.value = coreContext.core.unreadChatMessageCountFromActiveLocals - } - - private fun updateInactiveCallsCount() { - // TODO: handle local conference - val callsNb = coreContext.core.callsNb - inactiveCallsCount.value = if (callsNb > 0) callsNb - 1 else 0 - } -} diff --git a/app/src/main/java/org/linphone/activities/voip/viewmodels/ConferenceParticipantsViewModel.kt b/app/src/main/java/org/linphone/activities/voip/viewmodels/ConferenceParticipantsViewModel.kt deleted file mode 100644 index a3dc7aa9c..000000000 --- a/app/src/main/java/org/linphone/activities/voip/viewmodels/ConferenceParticipantsViewModel.kt +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.voip.viewmodels - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import org.linphone.contact.ContactsSelectionViewModel -import org.linphone.core.Address -import org.linphone.core.Conference -import org.linphone.core.tools.Log - -class ConferenceParticipantsViewModelFactory(private val conference: Conference) : - ViewModelProvider.NewInstanceFactory() { - - @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class): T { - return ConferenceParticipantsViewModel(conference) as T - } -} - -class ConferenceParticipantsViewModel(val conference: Conference) : ContactsSelectionViewModel() { - init { - selectCurrentParticipants() - } - - fun applyChanges() { - // Adding new participants first, because if we remove all of them (or all of them except one) - // It will terminate the conference first and we won't be able to add new participants after - for (address in selectedAddresses.value.orEmpty()) { - val participant = conference.participantList.find { participant -> - participant.address.weakEqual(address) - } - if (participant == null) { - Log.i( - "[Conference Participants] Participant ${address.asStringUriOnly()} will be added to group" - ) - conference.addParticipant(address) - } - } - - // Removing participants - for (participant in conference.participantList) { - val member = selectedAddresses.value.orEmpty().find { address -> - participant.address.weakEqual(address) - } - if (member == null) { - Log.w( - "[Conference Participants] Participant ${participant.address.asStringUriOnly()} will be removed from conference" - ) - conference.removeParticipant(participant) - } - } - } - - private fun selectCurrentParticipants() { - val list = arrayListOf
() - - for (participant in conference.participantList) { - list.add(participant.address) - } - - selectedAddresses.value = list - } -} diff --git a/app/src/main/java/org/linphone/activities/voip/viewmodels/ConferenceViewModel.kt b/app/src/main/java/org/linphone/activities/voip/viewmodels/ConferenceViewModel.kt deleted file mode 100644 index 14f41f2ce..000000000 --- a/app/src/main/java/org/linphone/activities/voip/viewmodels/ConferenceViewModel.kt +++ /dev/null @@ -1,772 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.voip.viewmodels - -import androidx.lifecycle.MediatorLiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.voip.ConferenceDisplayMode -import org.linphone.activities.voip.data.ConferenceParticipantData -import org.linphone.activities.voip.data.ConferenceParticipantDeviceData -import org.linphone.core.* -import org.linphone.core.tools.Log -import org.linphone.utils.AppUtils -import org.linphone.utils.Event -import org.linphone.utils.LinphoneUtils - -class ConferenceViewModel : ViewModel() { - val conferenceExists = MutableLiveData() - val subject = MutableLiveData() - val isConferenceLocallyPaused = MutableLiveData() - val isVideoConference = MutableLiveData() - val isMeAdmin = MutableLiveData() - - val conference = MutableLiveData() - val conferenceCreationPending = MutableLiveData() - val conferenceParticipants = MutableLiveData>() - val conferenceParticipantDevices = MutableLiveData>() - val conferenceDisplayMode = MutableLiveData() - val activeSpeakerConferenceParticipantDevices = MediatorLiveData>() - - val isRecording = MutableLiveData() - val isRemotelyRecorded = MutableLiveData() - - val maxParticipantsForMosaicLayout = corePreferences.maxConferenceParticipantsForMosaicLayout - - val twoOrMoreParticipants = MutableLiveData() - val moreThanTwoParticipants = MutableLiveData() - - val speakingParticipantFound = MutableLiveData() - val speakingParticipant = MutableLiveData() - val speakingParticipantVideoEnabled = MutableLiveData() - val meParticipant = MutableLiveData() - - val isBroadcast = MutableLiveData() - val isMeListenerOnly = MutableLiveData() - - val participantAdminStatusChangedEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val firstToJoinEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val allParticipantsLeftEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val secondParticipantJoinedEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val moreThanTwoParticipantsJoinedEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - private var waitForNextStreamsRunningToUpdateLayout = false - - val reloadConferenceFragmentEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - private val conferenceListener = object : ConferenceListenerStub() { - override fun onParticipantAdded(conference: Conference, participant: Participant) { - Log.i("[Conference] Participant added: ${participant.address.asStringUriOnly()}") - updateParticipantsList(conference) - } - - override fun onParticipantRemoved(conference: Conference, participant: Participant) { - Log.i("[Conference] Participant removed: ${participant.address.asStringUriOnly()}") - updateParticipantsList(conference) - } - - override fun onParticipantDeviceAdded( - conference: Conference, - participantDevice: ParticipantDevice - ) { - Log.i( - "[Conference] Participant device added: ${participantDevice.address.asStringUriOnly()}" - ) - addParticipantDevice(conference, participantDevice) - - if (conferenceParticipantDevices.value.orEmpty().size == 2) { - secondParticipantJoinedEvent.value = Event(true) - } else if (conferenceParticipantDevices.value.orEmpty().size > 2) { - moreThanTwoParticipantsJoinedEvent.value = Event(true) - } - } - - override fun onParticipantDeviceRemoved( - conference: Conference, - participantDevice: ParticipantDevice - ) { - Log.i( - "[Conference] Participant device removed: ${participantDevice.address.asStringUriOnly()}" - ) - removeParticipantDevice(participantDevice) - - when (conferenceParticipantDevices.value.orEmpty().size) { - 1 -> { - speakingParticipant.value?.videoEnabled?.value = false - speakingParticipantVideoEnabled.value = false - allParticipantsLeftEvent.value = Event(true) - } - 2 -> { - secondParticipantJoinedEvent.value = Event(true) - } - else -> {} - } - } - - override fun onParticipantAdminStatusChanged( - conference: Conference, - participant: Participant - ) { - Log.i( - "[Conference] Participant admin status changed [${participant.address.asStringUriOnly()}] is ${if (participant.isAdmin) "now admin" else "no longer admin"}" - ) - isMeAdmin.value = conference.me.isAdmin - updateParticipantsList(conference) - - if (conference.me.address.weakEqual(participant.address)) { - Log.i( - "[Conference] Found me participant [${participant.address.asStringUriOnly()}]" - ) - val participantData = ConferenceParticipantData(conference, participant) - participantAdminStatusChangedEvent.value = Event(participantData) - return - } - - val participantData = conferenceParticipants.value.orEmpty().find { data -> - data.participant.address.weakEqual( - participant.address - ) - } - if (participantData != null) { - participantAdminStatusChangedEvent.value = Event(participantData) - } else { - Log.w( - "[Conference] Failed to find participant [${participant.address.asStringUriOnly()}] in conferenceParticipants list" - ) - } - } - - override fun onSubjectChanged(conference: Conference, subject: String) { - Log.i("[Conference] Subject changed: $subject") - this@ConferenceViewModel.subject.value = subject - } - - override fun onParticipantDeviceStateChanged( - conference: Conference, - device: ParticipantDevice, - state: ParticipantDevice.State - ) { - if (conference.isMe(device.address)) { - when (state) { - ParticipantDevice.State.Present -> { - Log.i("[Conference] Entered conference") - isConferenceLocallyPaused.value = false - } - ParticipantDevice.State.OnHold -> { - Log.i("[Conference] Left conference") - isConferenceLocallyPaused.value = true - } - else -> {} - } - } else { - speakingParticipantVideoEnabled.value = speakingParticipant.value?.isInConference?.value == true && speakingParticipant.value?.isSendingVideo?.value == true - } - } - - override fun onActiveSpeakerParticipantDevice( - conference: Conference, - participantDevice: ParticipantDevice - ) { - Log.i( - "[Conference] Participant [${participantDevice.address.asStringUriOnly()}] is currently being displayed as active speaker" - ) - val device = conferenceParticipantDevices.value.orEmpty().find { - it.participantDevice.address.weakEqual(participantDevice.address) - } - - if (device != null && device != speakingParticipant.value) { - Log.i("[Conference] Found actively speaking participant device") - speakingParticipant.value?.isActiveSpeaker?.value = false - device.isActiveSpeaker.value = true - speakingParticipant.value = device!! - speakingParticipantFound.value = true - speakingParticipantVideoEnabled.value = speakingParticipant.value?.isInConference?.value == true && speakingParticipant.value?.isSendingVideo?.value == true - } else if (device == null) { - Log.w( - "[Conference] Participant device [${participantDevice.address.asStringUriOnly()}] is the active speaker but couldn't find it in devices list" - ) - } - } - - override fun onParticipantDeviceMediaAvailabilityChanged( - conference: Conference, - device: ParticipantDevice - ) { - speakingParticipantVideoEnabled.value = speakingParticipant.value?.isInConference?.value == true && speakingParticipant.value?.isSendingVideo?.value == true - } - - override fun onParticipantDeviceMediaCapabilityChanged( - conference: Conference, - device: ParticipantDevice - ) { - speakingParticipantVideoEnabled.value = speakingParticipant.value?.isInConference?.value == true && speakingParticipant.value?.isSendingVideo?.value == true - } - - override fun onStateChanged(conference: Conference, state: Conference.State) { - Log.i("[Conference] State changed: $state") - isVideoConference.value = conference.currentParams.isVideoEnabled && !corePreferences.disableVideo - - when (state) { - Conference.State.Created -> { - configureConference(conference) - } - Conference.State.TerminationPending -> { - terminateConference(conference) - } - else -> {} - } - } - } - - private val listener = object : CoreListenerStub() { - override fun onConferenceStateChanged( - core: Core, - conference: Conference, - state: Conference.State - ) { - Log.i("[Conference] Conference state changed: $state") - if (state == Conference.State.Instantiated) { - conferenceCreationPending.value = true - initConference(conference) - } else if (state == Conference.State.Created) { - if (conferenceCreationPending.value == true) { - conferenceCreationPending.value = false - } - } - } - - override fun onCallStateChanged( - core: Core, - call: Call, - state: Call.State?, - message: String - ) { - if (state == Call.State.StreamsRunning && waitForNextStreamsRunningToUpdateLayout) { - waitForNextStreamsRunningToUpdateLayout = false - reloadConferenceFragmentEvent.value = Event(true) - } - if (state == Call.State.StreamsRunning && call.conference?.isIn == true) { - isConferenceLocallyPaused.value = false - conferenceParticipantDevices.value?.forEach { - if (it.isMe) { - it.isInConference.value = true - } - } - } - } - } - - init { - coreContext.core.addListener(listener) - conferenceExists.value = false - - conferenceParticipants.value = arrayListOf() - conferenceParticipantDevices.value = arrayListOf() - activeSpeakerConferenceParticipantDevices.addSource(conferenceParticipantDevices) { - activeSpeakerConferenceParticipantDevices.value = conferenceParticipantDevices.value.orEmpty().drop( - 1 - ) - } - - subject.value = AppUtils.getString(R.string.conference_default_title) - - var conference = coreContext.core.conference ?: coreContext.core.currentCall?.conference - if (conference == null) { - for (call in coreContext.core.calls) { - if (call.conference != null) { - conference = call.conference - break - } - } - } - if (conference != null) { - val state = conference.state - Log.i("[Conference] Found an existing conference: $conference in state $state") - if (state != Conference.State.TerminationPending && state != Conference.State.Terminated) { - initConference(conference) - if (state == Conference.State.Instantiated) { - conferenceCreationPending.value = true - } else if (state == Conference.State.Created) { - if (conferenceCreationPending.value == true) { - conferenceCreationPending.value = false - } - configureConference(conference) - } - } - } - } - - override fun onCleared() { - coreContext.core.removeListener(listener) - conference.value?.removeListener(conferenceListener) - - conferenceParticipants.value.orEmpty().forEach(ConferenceParticipantData::destroy) - conferenceParticipantDevices.value.orEmpty().forEach( - ConferenceParticipantDeviceData::destroy - ) - activeSpeakerConferenceParticipantDevices.value.orEmpty().forEach( - ConferenceParticipantDeviceData::destroy - ) - - super.onCleared() - } - - fun pauseConference() { - Log.i("[Conference] Leaving conference temporarily") - conference.value?.leave() - } - - fun resumeConference() { - Log.i("[Conference] Entering conference again") - conference.value?.enter() - } - - fun toggleRecording() { - if (conference.value?.isRecording == true) { - Log.i("[Conference] Stopping conference recording") - conference.value?.stopRecording() - } else { - val path = LinphoneUtils.getRecordingFilePathForConference( - conference.value?.currentParams?.subject - ) - Log.i("[Conference] Starting recording in file $path") - conference.value?.startRecording(path) - } - isRecording.value = conference.value?.isRecording - } - - fun initConference(conference: Conference) { - conferenceExists.value = true - - this@ConferenceViewModel.conference.value = conference - conference.addListener(conferenceListener) - - isRecording.value = conference.isRecording - subject.value = LinphoneUtils.getConferenceSubject(conference) - - updateConferenceLayout(conference) - } - - fun configureConference(conference: Conference) { - updateParticipantsList(conference) - if (conferenceParticipants.value.orEmpty().isEmpty()) { - firstToJoinEvent.value = Event(true) - } - - updateParticipantsDevicesList(conference) - if (conferenceParticipantDevices.value.orEmpty().size == 2) { - secondParticipantJoinedEvent.value = Event(true) - } else if (conferenceParticipantDevices.value.orEmpty().size > 2) { - moreThanTwoParticipantsJoinedEvent.value = Event(true) - } - - isConferenceLocallyPaused.value = if (conference.call == null) false else !conference.isIn - isMeAdmin.value = conference.me.isAdmin - isVideoConference.value = conference.currentParams.isVideoEnabled && !corePreferences.disableVideo - subject.value = LinphoneUtils.getConferenceSubject(conference) - - updateConferenceLayout(conference) - } - - fun addCallsToConference() { - Log.i("[Conference] Trying to merge all calls into existing conference") - val conf = conference.value - conf ?: return - - for (call in coreContext.core.calls) { - if (call.conference == null) { - Log.i("[Conference] Adding call [$call] as participant for conference [$conf]") - conf.addParticipant(call) - } - } - if (!conf.isIn) { - Log.i("[Conference] Conference was paused, resuming it") - conf.enter() - } - } - - fun switchLayoutFromAudioOnlyToActiveSpeaker() { - if (conferenceDisplayMode.value == ConferenceDisplayMode.AUDIO_ONLY) { - Log.i( - "[Conference] Trying to switch from AUDIO_ONLY to ACTIVE_SPEAKER and toggle video ON" - ) - changeLayout(ConferenceDisplayMode.ACTIVE_SPEAKER, true) - waitForNextStreamsRunningToUpdateLayout = true - } else { - Log.w( - "[Conference] Can't switch from AUDIO_ONLY to ACTIVE_SPEAKER as current display mode isn't AUDIO_ONLY but ${conferenceDisplayMode.value}" - ) - } - } - - fun changeLayout(layout: ConferenceDisplayMode, forceSendingVideo: Boolean) { - Log.i("[Conference] Trying to change conference layout to $layout") - val conference = conference.value - if (conference != null) { - val call = conference.call - if (call != null) { - val params = call.core.createCallParams(call) - if (params == null) { - Log.e("[Conference] Failed to create call params from conference call!") - return - } - - params.isVideoEnabled = layout != ConferenceDisplayMode.AUDIO_ONLY - if (forceSendingVideo) { - Log.w("[Conference] Forcing video direction to SendRecv") - params.videoDirection = MediaDirection.SendRecv - } else { - if (conferenceDisplayMode.value == ConferenceDisplayMode.AUDIO_ONLY) { - // Previous layout was audio only, make sure video isn't sent without user consent when switching layout - params.videoDirection = MediaDirection.RecvOnly - } - Log.i("[Conference] Video direction is ${params.videoDirection}") - } - - params.conferenceVideoLayout = when (layout) { - ConferenceDisplayMode.GRID -> Conference.Layout.Grid - else -> Conference.Layout.ActiveSpeaker - } - call.update(params) - - conferenceDisplayMode.value = layout - val list = sortDevicesDataList(conferenceParticipantDevices.value.orEmpty()) - conferenceParticipantDevices.value = list - } else { - Log.e("[Conference] Failed to get call from conference!") - } - } else { - Log.e("[Conference] Conference is null in ConferenceViewModel") - } - } - - private fun updateConferenceLayout(conference: Conference) { - val call = conference.call - var videoDirection = MediaDirection.Inactive - - if (call == null) { - conferenceDisplayMode.value = ConferenceDisplayMode.AUDIO_ONLY - Log.w("[Conference] Call is null, assuming audio only layout for local conference") - } else { - val params = call.params - videoDirection = params.videoDirection - conferenceDisplayMode.value = if (!params.isVideoEnabled) { - ConferenceDisplayMode.AUDIO_ONLY - } else { - when (params.conferenceVideoLayout) { - Conference.Layout.Grid -> ConferenceDisplayMode.GRID - else -> ConferenceDisplayMode.ACTIVE_SPEAKER - } - } - } - - val list = sortDevicesDataList(conferenceParticipantDevices.value.orEmpty()) - conferenceParticipantDevices.value = list - - Log.i( - "[Conference] Current layout is [${conferenceDisplayMode.value}], video direction is [$videoDirection]" - ) - } - - private fun terminateConference(conference: Conference) { - conferenceExists.value = false - isVideoConference.value = false - - conference.removeListener(conferenceListener) - - conferenceParticipants.value.orEmpty().forEach(ConferenceParticipantData::destroy) - conferenceParticipantDevices.value.orEmpty().forEach( - ConferenceParticipantDeviceData::destroy - ) - activeSpeakerConferenceParticipantDevices.value.orEmpty().forEach( - ConferenceParticipantDeviceData::destroy - ) - - conferenceParticipants.value = arrayListOf() - conferenceParticipantDevices.value = arrayListOf() - } - - private fun updateParticipantsList(conference: Conference) { - conferenceParticipants.value.orEmpty().forEach(ConferenceParticipantData::destroy) - val participants = arrayListOf() - - val participantsList = conference.participantList - Log.i("[Conference] Conference has ${participantsList.size} participants") - for (participant in participantsList) { - val participantDevices = participant.devices - Log.i( - "[Conference] Participant found: ${participant.address.asStringUriOnly()} with ${participantDevices.size} device(s)" - ) - - val participantData = ConferenceParticipantData(conference, participant) - participants.add(participantData) - } - - conferenceParticipants.value = participants - } - - private fun updateParticipantsDevicesList(conference: Conference) { - conferenceParticipantDevices.value.orEmpty().forEach( - ConferenceParticipantDeviceData::destroy - ) - activeSpeakerConferenceParticipantDevices.value.orEmpty().forEach( - ConferenceParticipantDeviceData::destroy - ) - val devices = arrayListOf() - - val participantsList = conference.participantList - Log.i("[Conference] Conference has ${participantsList.size} participants") - - val activelySpeakingParticipantDevice = conference.activeSpeakerParticipantDevice - var foundActivelySpeakingParticipantDevice = false - speakingParticipantFound.value = false - speakingParticipantVideoEnabled.value = false - - val conferenceAddress = conference.conferenceAddress ?: return - val conferenceInfo = conference.core.findConferenceInformationFromUri( - conferenceAddress - ) - var allSpeaker = true - for (info in conferenceInfo?.participantInfos.orEmpty()) { - if (info.role == Participant.Role.Listener) { - allSpeaker = false - } - } - isBroadcast.value = !allSpeaker - if (!allSpeaker) { - Log.i( - "[Conference] Not all participants are speaker, considering it is a broadcast" - ) - } - - for (participant in participantsList) { - val participantDevices = participant.devices - Log.i( - "[Conference] Participant found: ${participant.address.asStringUriOnly()} with ${participantDevices.size} device(s)" - ) - - for (device in participantDevices) { - Log.i( - "[Conference] Participant device found: ${device.name} (${device.address.asStringUriOnly()})" - ) - - val info = conferenceInfo?.participantInfos?.find { - it.address.weakEqual(participant.address) - } - if (info != null) { - Log.i("[Conference] Participant role is [${info.role.name}]") - val listener = info.role == Participant.Role.Listener || info.role == Participant.Role.Unknown - if (listener) { - continue - } - } - - val deviceData = ConferenceParticipantDeviceData(device, false) - devices.add(deviceData) - - if (activelySpeakingParticipantDevice == device) { - Log.i( - "[Conference] Actively speaking participant device found: ${device.name} (${device.address.asStringUriOnly()})" - ) - speakingParticipant.value = deviceData - deviceData.isActiveSpeaker.value = true - foundActivelySpeakingParticipantDevice = true - speakingParticipantFound.value = true - speakingParticipantVideoEnabled.value = speakingParticipant.value?.isInConference?.value == true && speakingParticipant.value?.isSendingVideo?.value == true - } - } - } - - if (!foundActivelySpeakingParticipantDevice && devices.isNotEmpty()) { - Log.w( - "[Conference] Actively speaking participant device not found, using first participant device available" - ) - val deviceData = devices.first() - speakingParticipant.value = deviceData - deviceData.isActiveSpeaker.value = true - speakingParticipantFound.value = true - speakingParticipantVideoEnabled.value = speakingParticipant.value?.isInConference?.value == true && speakingParticipant.value?.isSendingVideo?.value == true - } - - for (device in conference.me.devices) { - Log.i( - "[Conference] Participant device for myself found: ${device.name} (${device.address.asStringUriOnly()})" - ) - - val info = conferenceInfo?.participantInfos?.find { - it.address.weakEqual(device.address) - } - if (info != null) { - Log.i("[Conference] Me role is [${info.role.name}]") - val listener = info.role == Participant.Role.Listener || info.role == Participant.Role.Unknown - isMeListenerOnly.value = listener - if (listener) { - continue - } - } - - val deviceData = ConferenceParticipantDeviceData(device, true) - devices.add(deviceData) - meParticipant.value = deviceData - } - - conferenceParticipantDevices.value = devices - twoOrMoreParticipants.value = devices.size >= 2 - moreThanTwoParticipants.value = devices.size > 2 - } - - private fun addParticipantDevice(conference: Conference, device: ParticipantDevice) { - val devices = arrayListOf() - devices.addAll(conferenceParticipantDevices.value.orEmpty()) - - val existingDevice = devices.find { - it.participantDevice.address.weakEqual(device.address) - } - if (existingDevice != null) { - Log.e( - "[Conference] Participant is already in devices list: ${device.name} (${device.address.asStringUriOnly()})" - ) - return - } - - Log.i( - "[Conference] New participant device found: ${device.name} (${device.address.asStringUriOnly()})" - ) - - val conferenceAddress = conference.conferenceAddress ?: return - val conferenceInfo = conference.core.findConferenceInformationFromUri( - conferenceAddress - ) - val info = conferenceInfo?.participantInfos?.find { - it.address.weakEqual(device.address) - } - if (info != null) { - Log.i("[Conference] New participant role is [${info.role.name}]") - val listener = - info.role == Participant.Role.Listener || info.role == Participant.Role.Unknown - if (listener) { - return - } - } - - val deviceData = ConferenceParticipantDeviceData(device, false) - devices.add(deviceData) - - val sortedDevices = sortDevicesDataList(devices) - - if (speakingParticipant.value == null || speakingParticipantFound.value == false) { - speakingParticipant.value = deviceData - deviceData.isActiveSpeaker.value = true - speakingParticipantFound.value = true - speakingParticipantVideoEnabled.value = speakingParticipant.value?.isInConference?.value == true && speakingParticipant.value?.isSendingVideo?.value == true - } - - conferenceParticipantDevices.value = sortedDevices - twoOrMoreParticipants.value = sortedDevices.size >= 2 - moreThanTwoParticipants.value = sortedDevices.size > 2 - } - - private fun removeParticipantDevice(device: ParticipantDevice) { - val devices = arrayListOf() - var removedDeviceWasActiveSpeaker = false - - for (participantDevice in conferenceParticipantDevices.value.orEmpty()) { - if (participantDevice.participantDevice.address.asStringUriOnly() != device.address.asStringUriOnly()) { - devices.add(participantDevice) - } else { - if (speakingParticipant.value == participantDevice) { - Log.w( - "[Conference] Removed participant device was the actively speaking participant device" - ) - removedDeviceWasActiveSpeaker = true - } - participantDevice.destroy() - } - } - - val devicesCount = devices.size - if (devicesCount == conferenceParticipantDevices.value.orEmpty().size) { - Log.e( - "[Conference] Failed to remove participant device: ${device.name} (${device.address.asStringUriOnly()})" - ) - } - - if (removedDeviceWasActiveSpeaker && devicesCount > 1) { - Log.w( - "[Conference] Updating actively speaking participant device using first one available" - ) - // Using second device as first is ourselves - val deviceData = devices[1] - speakingParticipant.value = deviceData - deviceData.isActiveSpeaker.value = true - speakingParticipantFound.value = true - speakingParticipantVideoEnabled.value = speakingParticipant.value?.isInConference?.value == true && speakingParticipant.value?.isSendingVideo?.value == true - } - - conferenceParticipantDevices.value = devices - twoOrMoreParticipants.value = devicesCount >= 2 - moreThanTwoParticipants.value = devicesCount > 2 - } - - private fun sortDevicesDataList(devices: List): ArrayList { - val sortedList = arrayListOf() - sortedList.addAll(devices) - - val meDeviceData = sortedList.find { - it.isMe - } - if (meDeviceData != null) { - val index = sortedList.indexOf(meDeviceData) - val expectedIndex = if (conferenceDisplayMode.value == ConferenceDisplayMode.ACTIVE_SPEAKER) { - 0 - } else { - sortedList.size - 1 - } - if (index != expectedIndex) { - Log.i( - "[Conference] Me device data is at index $index, moving it to index $expectedIndex" - ) - sortedList.removeAt(index) - sortedList.add(expectedIndex, meDeviceData) - } - } - - return sortedList - } -} diff --git a/app/src/main/java/org/linphone/activities/voip/viewmodels/ControlsViewModel.kt b/app/src/main/java/org/linphone/activities/voip/viewmodels/ControlsViewModel.kt deleted file mode 100644 index fc8b58bb4..000000000 --- a/app/src/main/java/org/linphone/activities/voip/viewmodels/ControlsViewModel.kt +++ /dev/null @@ -1,595 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.voip.viewmodels - -import android.Manifest -import android.animation.ValueAnimator -import android.view.animation.LinearInterpolator -import androidx.lifecycle.MediatorLiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.window.layout.FoldingFeature -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.core.* -import org.linphone.core.tools.Log -import org.linphone.utils.* -import org.linphone.utils.Event - -class ControlsViewModel : ViewModel() { - val isSpeakerSelected = MutableLiveData() - - val isBluetoothHeadsetSelected = MutableLiveData() - - val audioRoutesSelected = MutableLiveData() - - val audioRoutesEnabled = MutableLiveData() - - val isVideoAvailable = MutableLiveData() - - val isVideoEnabled = MutableLiveData() - - val isSendingVideo = MutableLiveData() - - val isVideoUpdateInProgress = MutableLiveData() - - val isSwitchCameraAvailable = MutableLiveData() - - val isOutgoingEarlyMedia = MutableLiveData() - - val isIncomingEarlyMediaVideo = MutableLiveData() - - val isIncomingCallVideo = MutableLiveData() - - val showExtras = MutableLiveData() - - val fullScreenMode = MutableLiveData() - - val folded = MutableLiveData() - - val pipMode = MutableLiveData() - - val chatRoomCreationInProgress = MutableLiveData() - - val numpadVisible = MutableLiveData() - - val dtmfHistory = MutableLiveData() - - val callStatsVisible = MutableLiveData() - - val proximitySensorEnabled = MediatorLiveData() - - val forceDisableProximitySensor = MutableLiveData() - - val showTakeSnapshotButton = MutableLiveData() - - val attendedTransfer = MutableLiveData() - - val chatDisabled = MutableLiveData() - - val goToConferenceParticipantsListEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val goToChatEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val goToCallsListEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val goToConferenceLayoutSettingsEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val askPermissionEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val goToDialerEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val foldingState = MutableLiveData() - - val hideVideo = corePreferences.disableVideo - - private val nonEarpieceOutputAudioDevice = MutableLiveData() - - private val listener: CoreListenerStub = object : CoreListenerStub() { - override fun onCallStateChanged( - core: Core, - call: Call, - state: Call.State, - message: String - ) { - Log.i("[Call Controls] State changed: $state") - isOutgoingEarlyMedia.value = state == Call.State.OutgoingEarlyMedia - isIncomingEarlyMediaVideo.value = state == Call.State.IncomingEarlyMedia && call.remoteParams?.isVideoEnabled == true - isIncomingCallVideo.value = call.remoteParams?.isVideoEnabled == true && coreContext.core.videoActivationPolicy.automaticallyAccept - attendedTransfer.value = core.callsNb > 1 - - if (state == Call.State.StreamsRunning) { - if (!call.currentParams.isVideoEnabled && fullScreenMode.value == true) { - fullScreenMode.value = false - } - isVideoUpdateInProgress.value = false - proximitySensorEnabled.value = shouldProximitySensorBeEnabled() - } else if (state == Call.State.PausedByRemote) { - fullScreenMode.value = false - } - - if (core.currentCall?.currentParams?.isVideoEnabled == true && !PermissionHelper.get().hasCameraPermission()) { - askPermissionEvent.value = Event(Manifest.permission.CAMERA) - } - - updateUI() - } - - override fun onAudioDeviceChanged(core: Core, audioDevice: AudioDevice) { - Log.i("[Call Controls] Audio device changed: ${audioDevice.deviceName}") - - nonEarpieceOutputAudioDevice.value = audioDevice.type != AudioDevice.Type.Earpiece - updateSpeakerState() - updateBluetoothHeadsetState() - } - - override fun onAudioDevicesListUpdated(core: Core) { - Log.i("[Call Controls] Audio devices list updated") - val wasBluetoothPreviouslyAvailable = audioRoutesEnabled.value == true - updateAudioRoutesState() - - if (AudioRouteUtils.isHeadsetAudioRouteAvailable()) { - AudioRouteUtils.routeAudioToHeadset() - } else if (!wasBluetoothPreviouslyAvailable && corePreferences.routeAudioToBluetoothIfAvailable) { - // Only attempt to route audio to bluetooth automatically when bluetooth device is connected - if (AudioRouteUtils.isBluetoothAudioRouteAvailable()) { - AudioRouteUtils.routeAudioToBluetooth() - } - } - } - } - - val extraButtonsMenuTranslateY = MutableLiveData() - private val extraButtonsMenuAnimator: ValueAnimator by lazy { - ValueAnimator.ofFloat( - AppUtils.getDimension(R.dimen.voip_call_extra_buttons_translate_y), - 0f - ).apply { - addUpdateListener { - val value = it.animatedValue as Float - extraButtonsMenuTranslateY.value = value - } - duration = if (corePreferences.enableAnimations) 500 else 0 - } - } - - val audioRoutesMenuTranslateY = MutableLiveData() - private val audioRoutesMenuAnimator: ValueAnimator by lazy { - ValueAnimator.ofFloat(AppUtils.getDimension(R.dimen.voip_audio_routes_menu_translate_y), 0f).apply { - addUpdateListener { - val value = it.animatedValue as Float - audioRoutesMenuTranslateY.value = value - } - duration = if (corePreferences.enableAnimations) 500 else 0 - } - } - - val bouncyCounterTranslateY = MutableLiveData() - - private val bounceAnimator: ValueAnimator by lazy { - ValueAnimator.ofFloat(AppUtils.getDimension(R.dimen.voip_counter_bounce_offset), 0f).apply { - addUpdateListener { - val value = it.animatedValue as Float - bouncyCounterTranslateY.value = value - } - interpolator = LinearInterpolator() - duration = 250 - repeatMode = ValueAnimator.REVERSE - repeatCount = ValueAnimator.INFINITE - } - } - - init { - coreContext.core.addListener(listener) - - chatDisabled.value = corePreferences.disableChat - fullScreenMode.value = false - extraButtonsMenuTranslateY.value = AppUtils.getDimension( - R.dimen.voip_call_extra_buttons_translate_y - ) - audioRoutesMenuTranslateY.value = AppUtils.getDimension( - R.dimen.voip_audio_routes_menu_translate_y - ) - audioRoutesSelected.value = false - forceDisableProximitySensor.value = false - - nonEarpieceOutputAudioDevice.value = coreContext.core.outputAudioDevice?.type != AudioDevice.Type.Earpiece - proximitySensorEnabled.value = shouldProximitySensorBeEnabled() - proximitySensorEnabled.addSource(isVideoEnabled) { - proximitySensorEnabled.value = shouldProximitySensorBeEnabled() - } - proximitySensorEnabled.addSource(nonEarpieceOutputAudioDevice) { - proximitySensorEnabled.value = shouldProximitySensorBeEnabled() - } - proximitySensorEnabled.addSource(forceDisableProximitySensor) { - proximitySensorEnabled.value = shouldProximitySensorBeEnabled() - } - - val currentCall = coreContext.core.currentCall ?: coreContext.core.calls.firstOrNull() - val state = currentCall?.state ?: Call.State.Idle - Log.i("[Call Controls] Current state is: $state") - isOutgoingEarlyMedia.value = state == Call.State.OutgoingEarlyMedia - isIncomingEarlyMediaVideo.value = state == Call.State.IncomingEarlyMedia && currentCall?.remoteParams?.isVideoEnabled == true - isIncomingCallVideo.value = currentCall?.remoteParams?.isVideoEnabled == true && coreContext.core.videoActivationPolicy.automaticallyAccept - - updateUI() - - if (corePreferences.enableAnimations) bounceAnimator.start() - } - - override fun onCleared() { - coreContext.core.removeListener(listener) - - super.onCleared() - } - - fun hangUp() { - val core = coreContext.core - when { - core.currentCall != null -> core.currentCall?.terminate() - core.conference?.isIn == true -> core.terminateConference() - else -> core.terminateAllCalls() - } - } - - fun answer() { - val currentCall = coreContext.core.currentCall ?: coreContext.core.calls.find { - call -> - call.state == Call.State.IncomingReceived || call.state == Call.State.IncomingEarlyMedia - } - if (currentCall != null) { - coreContext.answerCall(currentCall) - } else { - Log.e("[Controls] Can't find any current call to answer") - } - } - - fun toggleSpeaker() { - if (AudioRouteUtils.isSpeakerAudioRouteCurrentlyUsed()) { - forceEarpieceAudioRoute() - } else { - forceSpeakerAudioRoute() - } - } - - fun toggleRoutesMenu() { - audioRoutesSelected.value = audioRoutesSelected.value != true - if (audioRoutesSelected.value == true) { - audioRoutesMenuAnimator.start() - } else { - audioRoutesMenuAnimator.reverse() - } - } - - fun forceEarpieceAudioRoute() { - if (AudioRouteUtils.isHeadsetAudioRouteAvailable()) { - Log.i("[Call Controls] Headset found, route audio to it instead of earpiece") - AudioRouteUtils.routeAudioToHeadset() - } else { - AudioRouteUtils.routeAudioToEarpiece() - } - updateSpeakerState() - updateBluetoothHeadsetState() - } - - fun forceSpeakerAudioRoute() { - AudioRouteUtils.routeAudioToSpeaker() - updateSpeakerState() - updateBluetoothHeadsetState() - } - - fun forceBluetoothAudioRoute() { - AudioRouteUtils.routeAudioToBluetooth() - updateSpeakerState() - updateBluetoothHeadsetState() - } - - fun toggleVideo() { - if (!PermissionHelper.get().hasCameraPermission()) { - Log.w( - "[Call Controls] Camera permission isn't granted, asking it before toggling video" - ) - askPermissionEvent.value = Event(Manifest.permission.CAMERA) - return - } - - val core = coreContext.core - val currentCall = core.currentCall - if (currentCall != null) { - val state = currentCall.state - if (state == Call.State.End || state == Call.State.Released || state == Call.State.Error) { - Log.e("[Call Controls] Current call state is $state, aborting video toggle") - return - } - - isVideoUpdateInProgress.value = true - val params = core.createCallParams(currentCall) - if (currentCall.conference != null) { - if (params?.isVideoEnabled == false) { - params.isVideoEnabled = true - params.videoDirection = MediaDirection.SendRecv - } else { - if (params?.videoDirection == MediaDirection.SendRecv || params?.videoDirection == MediaDirection.SendOnly) { - params.videoDirection = MediaDirection.RecvOnly - } else { - params?.videoDirection = MediaDirection.SendRecv - } - } - } else { - params?.isVideoEnabled = params?.isVideoEnabled == false - Log.i( - "[Call Controls] Updating call with video enabled set to ${params?.isVideoEnabled}" - ) - } - currentCall.update(params) - } else { - Log.e("[Call Controls] Can't toggle video, no current call found!") - } - } - - fun switchCamera() { - coreContext.switchCamera() - } - - fun takeSnapshot() { - if (!PermissionHelper.get().hasWriteExternalStoragePermission()) { - askPermissionEvent.value = Event(Manifest.permission.WRITE_EXTERNAL_STORAGE) - } else { - val currentCall = coreContext.core.currentCall - if (currentCall != null && currentCall.currentParams.isVideoEnabled) { - val fileName = System.currentTimeMillis().toString() + ".jpeg" - val fullPath = FileUtils.getFileStoragePath(fileName).absolutePath - Log.i("[Call Controls] Snapshot will be save under $fullPath") - currentCall.takeVideoSnapshot(fullPath) - } else { - Log.e("[Call Controls] Current call doesn't have video, can't take snapshot") - } - } - } - - fun showExtraButtons() { - extraButtonsMenuAnimator.start() - showExtras.value = true - } - - fun hideExtraButtons(skipAnimation: Boolean) { - // Animation must be skipped when called from Fragment's onPause() ! - if (skipAnimation) { - extraButtonsMenuTranslateY.value = AppUtils.getDimension( - R.dimen.voip_call_extra_buttons_translate_y - ) - } else { - extraButtonsMenuAnimator.reverse() - } - showExtras.value = false - chatRoomCreationInProgress.value = false - } - - fun toggleFullScreen() { - if (fullScreenMode.value == false && isVideoEnabled.value == false) return - fullScreenMode.value = fullScreenMode.value != true - } - - fun goToConferenceParticipantsList() { - goToConferenceParticipantsListEvent.value = Event(true) - } - - fun goToChat() { - chatRoomCreationInProgress.value = true - goToChatEvent.value = Event(true) - } - - fun showNumpad() { - hideExtraButtons(false) - numpadVisible.value = true - } - - fun hideNumpad() { - numpadVisible.value = false - } - - fun handleDtmfClick(key: Char) { - dtmfHistory.value = "${dtmfHistory.value.orEmpty()}$key" - coreContext.core.playDtmf(key, 1) - coreContext.core.currentCall?.sendDtmf(key) - } - - fun goToCallsList() { - goToCallsListEvent.value = Event(true) - } - - fun showCallStats(skipAnimation: Boolean = false) { - hideExtraButtons(skipAnimation) - callStatsVisible.value = true - } - - fun hideCallStats() { - callStatsVisible.value = false - } - - fun goToConferenceLayout() { - goToConferenceLayoutSettingsEvent.value = Event(true) - } - - fun transferCall() { - // In case there is more than 1 call, transfer will be attended instead of blind - if (coreContext.core.callsNb > 1) { - attendedTransfer() - } else { - goToDialerForCallTransfer() - } - } - - private fun attendedTransfer() { - val core = coreContext.core - val currentCall = core.currentCall - - if (currentCall == null) { - Log.e("[Call Controls] Can't do an attended transfer without a current call") - return - } - if (core.callsNb <= 1) { - Log.e("[Call Controls] Need at least two calls to do an attended transfer") - return - } - - val callToTransferTo = core.calls.findLast { - it.state == Call.State.Paused - } - if (callToTransferTo == null) { - Log.e( - "[Call Controls] Couldn't find a call in Paused state to transfer current call to" - ) - return - } - - Log.i( - "[Call Controls] Doing an attended transfer between active call [${currentCall.remoteAddress.asStringUriOnly()}] and paused call [${callToTransferTo.remoteAddress.asStringUriOnly()}]" - ) - val result = callToTransferTo.transferToAnother(currentCall) - if (result != 0) { - Log.e("[Call Controls] Attended transfer failed!") - } - } - - private fun goToDialerForCallTransfer() { - goToDialerEvent.value = Event(true) - } - - fun goToDialerForNewCall() { - goToDialerEvent.value = Event(false) - } - - private fun updateUI() { - updateVideoAvailable() - updateVideoEnabled() - updateSpeakerState() - updateBluetoothHeadsetState() - updateAudioRoutesState() - } - - private fun updateSpeakerState() { - isSpeakerSelected.value = AudioRouteUtils.isSpeakerAudioRouteCurrentlyUsed() - } - - private fun updateBluetoothHeadsetState() { - isBluetoothHeadsetSelected.value = AudioRouteUtils.isBluetoothAudioRouteCurrentlyUsed() - } - - private fun updateAudioRoutesState() { - val bluetoothDeviceAvailable = AudioRouteUtils.isBluetoothAudioRouteAvailable() - audioRoutesEnabled.value = bluetoothDeviceAvailable - - if (!bluetoothDeviceAvailable) { - audioRoutesSelected.value = false - } - } - - private fun updateVideoAvailable() { - val core = coreContext.core - val currentCall = core.currentCall - isVideoAvailable.value = (core.isVideoCaptureEnabled || core.isVideoPreviewEnabled) && - ((currentCall != null && !currentCall.mediaInProgress()) || core.conference?.isIn == true) - } - - private fun updateVideoEnabled() { - val currentCall = coreContext.core.currentCall ?: coreContext.core.calls.firstOrNull() - val enabled = currentCall?.currentParams?.isVideoEnabled ?: false - // Prevent speaker to turn on each time a participant joins a video conference - val isConference = currentCall?.conference != null - if (enabled && !isConference && isVideoEnabled.value == false) { - Log.i("[Call Controls] Video is being turned on") - if (corePreferences.routeAudioToSpeakerWhenVideoIsEnabled) { - // Do not turn speaker on when video is enabled if headset or bluetooth is used - if (!AudioRouteUtils.isHeadsetAudioRouteAvailable() && - !AudioRouteUtils.isBluetoothAudioRouteCurrentlyUsed() - ) { - Log.i( - "[Call Controls] Video enabled and no wired headset not bluetooth in use, routing audio to speaker" - ) - AudioRouteUtils.routeAudioToSpeaker() - } - } - } - - isVideoEnabled.value = enabled - showTakeSnapshotButton.value = enabled && corePreferences.showScreenshotButton - val videoDirection = if (coreContext.core.currentCall?.conference != null) { - coreContext.core.currentCall?.currentParams?.videoDirection - } else { - coreContext.core.currentCall?.params?.videoDirection - } - val isVideoBeingSent = videoDirection == MediaDirection.SendRecv || videoDirection == MediaDirection.SendOnly - isSendingVideo.value = isVideoBeingSent - isSwitchCameraAvailable.value = enabled && coreContext.showSwitchCameraButton() && isVideoBeingSent - } - - private fun shouldProximitySensorBeEnabled(): Boolean { - val currentCall = coreContext.core.currentCall ?: coreContext.core.calls.firstOrNull() - if (currentCall != null) { - when (val state = currentCall.state) { - Call.State.OutgoingEarlyMedia, Call.State.OutgoingProgress, Call.State.OutgoingRinging, Call.State.OutgoingInit -> { - Log.i( - "[Call Controls] Call is in outgoing state [$state], enabling proximity sensor" - ) - return true - } - Call.State.IncomingEarlyMedia, Call.State.IncomingReceived -> { - Log.i( - "[Call Controls] Call is in incoming state [$state], enabling proximity sensor" - ) - return true - } - else -> { } - } - } - - if (forceDisableProximitySensor.value == true) { - Log.i( - "[Call Controls] Forcing proximity sensor to be disabled (usually in incoming/outgoing call fragments)" - ) - } else if (isVideoEnabled.value == true) { - Log.i( - "[Call Controls] Active call current params says video is enabled, proximity sensor will be disabled" - ) - } else if (nonEarpieceOutputAudioDevice.value == true) { - Log.i( - "[Call Controls] Current audio route is not earpiece, proximity sensor will be disabled" - ) - } - - return forceDisableProximitySensor.value == false && - !(isVideoEnabled.value ?: false) && - !(nonEarpieceOutputAudioDevice.value ?: false) - } -} diff --git a/app/src/main/java/org/linphone/activities/voip/viewmodels/StatisticsListViewModel.kt b/app/src/main/java/org/linphone/activities/voip/viewmodels/StatisticsListViewModel.kt deleted file mode 100644 index 2f7fbadbe..000000000 --- a/app/src/main/java/org/linphone/activities/voip/viewmodels/StatisticsListViewModel.kt +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.voip.viewmodels - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.activities.voip.data.CallStatisticsData -import org.linphone.core.Call -import org.linphone.core.Core -import org.linphone.core.CoreListenerStub - -class StatisticsListViewModel : ViewModel() { - val callStatsList = MutableLiveData>() - - private var enabled = false - - private val listener = object : CoreListenerStub() { - override fun onCallStateChanged( - core: Core, - call: Call, - state: Call.State, - message: String - ) { - if (state == Call.State.End || state == Call.State.Error || state == Call.State.Connected) { - computeCallsList() - } - } - } - - init { - coreContext.core.addListener(listener) - - computeCallsList() - } - - fun enable() { - enabled = true - for (stat in callStatsList.value.orEmpty()) { - stat.enable() - } - } - - fun disable() { - enabled = false - for (stat in callStatsList.value.orEmpty()) { - stat.disable() - } - } - - override fun onCleared() { - callStatsList.value.orEmpty().forEach(CallStatisticsData::destroy) - coreContext.core.removeListener(listener) - - super.onCleared() - } - - private fun computeCallsList() { - callStatsList.value.orEmpty().forEach(CallStatisticsData::destroy) - - val list = arrayListOf() - for (call in coreContext.core.calls) { - if (call.state != Call.State.End && call.state != Call.State.Released && call.state != Call.State.Error) { - val data = CallStatisticsData(call) - list.add(data) - if (enabled) { - data.enable() - } - } - } - - callStatsList.value = list - } -} diff --git a/app/src/main/java/org/linphone/activities/voip/viewmodels/StatusViewModel.kt b/app/src/main/java/org/linphone/activities/voip/viewmodels/StatusViewModel.kt deleted file mode 100644 index ec2d25fbd..000000000 --- a/app/src/main/java/org/linphone/activities/voip/viewmodels/StatusViewModel.kt +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.voip.viewmodels - -import androidx.lifecycle.MutableLiveData -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R -import org.linphone.activities.main.viewmodels.StatusViewModel -import org.linphone.core.* -import org.linphone.utils.Event - -class StatusViewModel : StatusViewModel() { - val callQualityIcon = MutableLiveData() - val callQualityContentDescription = MutableLiveData() - - val encryptionIcon = MutableLiveData() - val encryptionContentDescription = MutableLiveData() - val encryptionIconVisible = MutableLiveData() - - val showZrtpDialogEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - val showCallStatsEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - var previouslyDeclineToken = false - - private val listener = object : CoreListenerStub() { - override fun onCallStatsUpdated(core: Core, call: Call, stats: CallStats) { - updateCallQualityIcon() - } - - override fun onCallEncryptionChanged( - core: Core, - call: Call, - on: Boolean, - authenticationToken: String? - ) { - updateEncryptionInfo(call) - // Check if we just declined a previously validated token - // In that case, don't show the ZRTP dialog again - if (!previouslyDeclineToken) { - previouslyDeclineToken = false - showZrtpDialog(call) - } - } - - override fun onCallStateChanged( - core: Core, - call: Call, - state: Call.State, - message: String - ) { - if (call == core.currentCall) { - updateEncryptionInfo(call) - } - } - } - - init { - coreContext.core.addListener(listener) - - updateCallQualityIcon() - - val currentCall = coreContext.core.currentCall - if (currentCall != null) { - updateEncryptionInfo(currentCall) - showZrtpDialog(currentCall) - } - } - - override fun onCleared() { - coreContext.core.removeListener(listener) - - super.onCleared() - } - - fun showZrtpDialog() { - val currentCall = coreContext.core.currentCall - if (currentCall != null) { - showZrtpDialog(currentCall, force = true) - } - } - - fun showCallStats() { - showCallStatsEvent.value = Event(true) - } - - fun updateEncryptionInfo(call: Call) { - if (call.dir == Call.Dir.Incoming && call.state == Call.State.IncomingReceived && call.core.isMediaEncryptionMandatory) { - // If the incoming call view is displayed while encryption is mandatory, - // we can safely show the security_ok icon - encryptionIcon.value = R.drawable.security_ok - encryptionIconVisible.value = true - encryptionContentDescription.value = R.string.content_description_call_secured - return - } - if (call.state == Call.State.End || call.state == Call.State.Released) { - return - } - - when (call.currentParams.mediaEncryption ?: MediaEncryption.None) { - MediaEncryption.SRTP, MediaEncryption.DTLS -> { - encryptionIcon.value = R.drawable.security_ok - encryptionIconVisible.value = true - encryptionContentDescription.value = R.string.content_description_call_secured - } - MediaEncryption.ZRTP -> { - encryptionIcon.value = when (call.authenticationTokenVerified || call.authenticationToken == null) { - true -> R.drawable.security_ok - else -> R.drawable.security_pending - } - encryptionContentDescription.value = when (call.authenticationTokenVerified || call.authenticationToken == null) { - true -> R.string.content_description_call_secured - else -> R.string.content_description_call_security_pending - } - encryptionIconVisible.value = true - } - MediaEncryption.None -> { - encryptionIcon.value = R.drawable.security_ko - // Do not show unsecure icon if user doesn't want to do call encryption - encryptionIconVisible.value = call.core.mediaEncryption != MediaEncryption.None - encryptionContentDescription.value = R.string.content_description_call_not_secured - } - } - } - - private fun showZrtpDialog(call: Call, force: Boolean = false) { - if ( - call.currentParams.mediaEncryption == MediaEncryption.ZRTP && - call.authenticationToken != null && - (!call.authenticationTokenVerified || force) - ) { - showZrtpDialogEvent.value = Event(call) - } - } - - private fun updateCallQualityIcon() { - val call = coreContext.core.currentCall ?: coreContext.core.calls.firstOrNull() - val quality = call?.currentQuality ?: 0f - callQualityIcon.value = when { - quality >= 4 -> R.drawable.call_quality_indicator_4 - quality >= 3 -> R.drawable.call_quality_indicator_3 - quality >= 2 -> R.drawable.call_quality_indicator_2 - quality >= 1 -> R.drawable.call_quality_indicator_1 - else -> R.drawable.call_quality_indicator_0 - } - callQualityContentDescription.value = when { - quality >= 4 -> R.string.content_description_call_quality_4 - quality >= 3 -> R.string.content_description_call_quality_3 - quality >= 2 -> R.string.content_description_call_quality_2 - quality >= 1 -> R.string.content_description_call_quality_1 - else -> R.string.content_description_call_quality_0 - } - } -} diff --git a/app/src/main/java/org/linphone/activities/voip/views/GridBoxLayout.kt b/app/src/main/java/org/linphone/activities/voip/views/GridBoxLayout.kt deleted file mode 100644 index 37a57c1dc..000000000 --- a/app/src/main/java/org/linphone/activities/voip/views/GridBoxLayout.kt +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (c) 2010-2022 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.voip.views - -import android.annotation.SuppressLint -import android.content.Context -import android.util.AttributeSet -import android.widget.GridLayout -import androidx.core.view.children -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.core.tools.Log - -class GridBoxLayout : GridLayout { - companion object { - private val placementMatrix = arrayOf( - intArrayOf(1, 2, 3, 4, 5, 6), - intArrayOf(1, 1, 2, 2, 3, 3), - intArrayOf(1, 1, 1, 2, 2, 2), - intArrayOf(1, 1, 1, 1, 2, 2), - intArrayOf(1, 1, 1, 1, 1, 2), - intArrayOf(1, 1, 1, 1, 1, 1) - ) - } - - constructor(context: Context) : this(context, null) - constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) - constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( - context, - attrs, - defStyleAttr - ) - - var centerContent: Boolean = false - private var previousChildCount = 0 - private var previousCellSize = 0 - - @SuppressLint("DrawAllocation") - override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { - if (childCount == 0 || (!changed && previousChildCount == childCount)) { - super.onLayout(changed, left, top, right, bottom) - // To prevent display issue the first time conference is locally paused - children.forEach { child -> - child.post { - child.layoutParams.width = previousCellSize - child.layoutParams.height = previousCellSize - child.requestLayout() - } - } - return - } - - // To prevent java.lang.IllegalArgumentException: columnCount must be greater than or equal - // to the maximum of all grid indices (and spans) defined in the LayoutParams of each child. - children.forEach { child -> - child.layoutParams = LayoutParams() - } - - val maxChild = placementMatrix[0].size - if (childCount > maxChild) { - val maxMosaicParticipants = corePreferences.maxConferenceParticipantsForMosaicLayout - Log.e( - "[GridBoxLayout] $childCount children but placementMatrix only knows how to display $maxChild (max allowed participants for grid layout in settings is $maxMosaicParticipants)" - ) - return - } - - val availableSize = Pair(right - left, bottom - top) - var cellSize = 0 - for (index in 1..childCount) { - val neededColumns = placementMatrix[index - 1][childCount - 1] - val candidateWidth = 1 * availableSize.first / neededColumns - val candidateHeight = 1 * availableSize.second / index - val candidateSize = if (candidateWidth < candidateHeight) candidateWidth else candidateHeight - if (candidateSize > cellSize) { - columnCount = neededColumns - rowCount = index - cellSize = candidateSize - } - } - previousCellSize = cellSize - previousChildCount = childCount - - super.onLayout(changed, left, top, right, bottom) - children.forEach { child -> - child.layoutParams.width = cellSize - child.layoutParams.height = cellSize - child.post { - child.requestLayout() - } - } - - if (centerContent) { - setPadding( - (availableSize.first - (columnCount * cellSize)) / 2, - (availableSize.second - (rowCount * cellSize)) / 2, - (availableSize.first - (columnCount * cellSize)) / 2, - (availableSize.second - (rowCount * cellSize)) / 2 - ) - } - Log.d( - "[GridBoxLayout] cellsize=$cellSize columns=$columnCount rows=$rowCount availablesize=$availableSize" - ) - } -} diff --git a/app/src/main/java/org/linphone/activities/voip/views/RoundCornersTextureView.kt b/app/src/main/java/org/linphone/activities/voip/views/RoundCornersTextureView.kt deleted file mode 100644 index 80a4279a6..000000000 --- a/app/src/main/java/org/linphone/activities/voip/views/RoundCornersTextureView.kt +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.voip.views - -import android.content.Context -import android.graphics.Outline -import android.graphics.Rect -import android.util.AttributeSet -import android.view.View -import android.view.ViewOutlineProvider -import org.linphone.R -import org.linphone.mediastream.video.capture.CaptureTextureView - -class RoundCornersTextureView : CaptureTextureView { - private var mRadius: Float = 0f - - constructor(context: Context) : super(context) { - mAlignTopRight = true - mDisplayMode = DisplayMode.BLACK_BARS - setRoundCorners() - } - - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { - readAttributes(attrs) - setRoundCorners() - } - - constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super( - context, - attrs, - defStyleAttr - ) { - readAttributes(attrs) - setRoundCorners() - } - - private fun readAttributes(attrs: AttributeSet) { - context.theme.obtainStyledAttributes( - attrs, - R.styleable.RoundCornersTextureView, - 0, - 0 - ).apply { - try { - mAlignTopRight = getBoolean(R.styleable.RoundCornersTextureView_alignTopRight, true) - val mode = getInteger( - R.styleable.RoundCornersTextureView_displayMode, - DisplayMode.BLACK_BARS.ordinal - ) - mDisplayMode = when (mode) { - 1 -> DisplayMode.OCCUPY_ALL_SPACE - 2 -> DisplayMode.HYBRID - else -> DisplayMode.BLACK_BARS - } - mRadius = getFloat( - R.styleable.RoundCornersTextureView_radius, - context.resources.getDimension(R.dimen.voip_round_corners_texture_view_radius) - ) - } finally { - recycle() - } - } - } - - private fun setRoundCorners() { - outlineProvider = object : ViewOutlineProvider() { - override fun getOutline(view: View, outline: Outline) { - val rect = if (previewRectF != null && - actualDisplayMode == DisplayMode.BLACK_BARS && - mAlignTopRight - ) { - Rect( - previewRectF.left.toInt(), - previewRectF.top.toInt(), - previewRectF.right.toInt(), - previewRectF.bottom.toInt() - ) - } else { - Rect( - 0, - 0, - width, - height - ) - } - outline.setRoundRect(rect, mRadius) - } - } - clipToOutline = true - } - - override fun setAspectRatio(width: Int, height: Int) { - super.setAspectRatio(width, height) - - val previewSize = previewVideoSize - if (previewSize.width > 0 && previewSize.height > 0) { - setRoundCorners() - } - } -} diff --git a/app/src/main/java/org/linphone/activities/voip/views/ScrollDotsView.kt b/app/src/main/java/org/linphone/activities/voip/views/ScrollDotsView.kt deleted file mode 100644 index e0498b948..000000000 --- a/app/src/main/java/org/linphone/activities/voip/views/ScrollDotsView.kt +++ /dev/null @@ -1,261 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.activities.voip.views - -import android.content.Context -import android.graphics.Canvas -import android.graphics.Color -import android.graphics.Paint -import android.graphics.Rect -import android.util.AttributeSet -import android.view.View -import android.view.View.OnScrollChangeListener -import android.widget.FrameLayout -import android.widget.HorizontalScrollView -import android.widget.ScrollView -import kotlin.math.ceil -import kotlin.math.roundToInt -import org.linphone.R -import org.linphone.core.tools.Log -import org.linphone.utils.AppUtils - -class ScrollDotsView : View { - private var count = 2 - private var itemCount = 0 - private var selected = 0 - - private var radius: Float = 5f - private var margin: Float = 2f - - private var screenWidth: Float = 0f - private var itemWidth: Float = 0f - private var screenHeight: Float = 0f - private var itemHeight: Float = 0f - - private lateinit var dotPaint: Paint - private lateinit var selectedDotPaint: Paint - - private var scrollViewRef = 0 - private lateinit var scrollView: FrameLayout - private var isHorizontal = false - - private val scrollListener = OnScrollChangeListener { v, scrollX, scrollY, _, _ -> - if (isHorizontal) { - if (v !is HorizontalScrollView) { - Log.e("[Scoll Dots] ScrollView reference isn't a HorizontalScrollView!") - return@OnScrollChangeListener - } - - val childWidth: Int = v.getChildAt(0).measuredWidth - val scrollViewWidth = v.measuredWidth - val scrollableX = childWidth - scrollViewWidth - - if (scrollableX > 0) { - val percent = (scrollX.toFloat() * 100 / scrollableX).toDouble() - if (count > 1) { - val selectedDot = percent / (100 / (count - 1)) - val dot = selectedDot.roundToInt() - if (dot != selected) { - setSelectedDot(dot) - } - } - } - } else { - if (v !is ScrollView) { - Log.e("[Scoll Dots] ScrollView reference isn't a ScrollView!") - return@OnScrollChangeListener - } - - val childHeight: Int = v.getChildAt(0).measuredHeight - val scrollViewHeight = v.measuredHeight - val scrollableY = childHeight - scrollViewHeight - - if (scrollableY > 0) { - val percent = (scrollY.toFloat() * 100 / scrollableY).toDouble() - if (count > 1) { - val selectedDot = percent / (100 / (count - 1)) - val dot = selectedDot.roundToInt() - if (dot != selected) { - setSelectedDot(dot) - } - } - } - } - } - - constructor(context: Context) : super(context) { init(context) } - - constructor(context: Context, attrs: AttributeSet) : this(context, attrs, 0) - - constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super( - context, - attrs, - defStyleAttr - ) { - init(context) - - context.theme.obtainStyledAttributes( - attrs, - R.styleable.ScrollDot, - defStyleAttr, - 0 - ).apply { - try { - radius = getDimension(R.styleable.ScrollDot_dotRadius, 5f) - - count = getInt(R.styleable.ScrollDot_dotCount, 1) - - val color = getColor( - R.styleable.ScrollDot_dotColor, - context.resources.getColor(R.color.voip_gray_dots) - ) - dotPaint.color = color - val selectedColor = getColor( - R.styleable.ScrollDot_selectedDotColor, - context.resources.getColor(R.color.voip_dark_gray) - ) - selectedDotPaint.color = selectedColor - - selected = getInt(R.styleable.ScrollDot_selectedDot, 1) - - scrollViewRef = getResourceId(R.styleable.ScrollDot_scrollView, 0) - - invalidate() - } catch (e: Exception) { - Log.e("[Scroll Dots] $e") - } finally { - recycle() - } - } - } - - fun init(context: Context) { - radius = AppUtils.dpToPixels(context, 5f) - margin = AppUtils.dpToPixels(context, 5f) - - dotPaint = Paint() - dotPaint.color = Color.parseColor("#D8D8D8") - selectedDotPaint = Paint() - selectedDotPaint.color = Color.parseColor("#4B5964") - - val screenRect = Rect() - getWindowVisibleDisplayFrame(screenRect) - screenWidth = screenRect.width().toFloat() - screenHeight = screenRect.height().toFloat() - - val marginBetweenItems = context.resources.getDimension( - R.dimen.voip_active_speaker_miniature_margin - ) - itemWidth = context.resources.getDimension(R.dimen.voip_active_speaker_miniature_size) + marginBetweenItems - itemHeight = context.resources.getDimension(R.dimen.voip_active_speaker_miniature_size) + marginBetweenItems - - Log.d( - "[Scroll Dots] Screen size is $screenWidth/$screenHeight and item size is $itemWidth/$itemHeight" - ) - } - - override fun onDraw(canvas: Canvas) { - super.onDraw(canvas) - - for (i in 0 until count) { - if (i == selected) { - val position = (i + 1) * margin + (i * 2 + 1) * radius - canvas.drawCircle( - if (isHorizontal) position else radius, - if (isHorizontal) radius else position, - radius, - selectedDotPaint - ) - } else { - val position = (i + 1) * margin + (i * 2 + 1) * radius - canvas.drawCircle( - if (isHorizontal) position else radius, - if (isHorizontal) radius else position, - radius, - dotPaint - ) - } - } - } - - override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec) - - checkOrientation() - - if (isHorizontal) { - val width = ((radius * 2 + margin) * count + margin).toInt() - val height: Int = (radius * 2).toInt() - setMeasuredDimension(width, height) - } else { - val height = ((radius * 2 + margin) * count + margin).toInt() - val width: Int = (radius * 2).toInt() - setMeasuredDimension(width, height) - } - } - - private fun checkOrientation() { - if (scrollViewRef > 0) { - try { - scrollView = (parent as View).findViewById(scrollViewRef) - scrollView.setOnScrollChangeListener(scrollListener) - Log.d("[Scroll Dots] ScrollView scroll listener set") - isHorizontal = scrollView is HorizontalScrollView - Log.d("[Scroll Dots] ScrollView is horizontal ? $isHorizontal") - requestLayout() - setItemCount(itemCount) - } catch (e: Exception) { - Log.e("[Scroll Dots] Failed to find ScrollView from id $scrollViewRef: $e") - } - } else { - Log.e("[Scroll Dots] No ScrollView reference given") - } - } - - private fun setDotCount(count: Int) { - this.count = count - requestLayout() - invalidate() - } - - fun setItemCount(items: Int) { - itemCount = items - if (isHorizontal) { - val itemsPerScreen = (screenWidth / itemWidth) - val dots = ceil(items.toDouble() / itemsPerScreen).toInt() - Log.d( - "[Scroll Dots] Calculated $count for $items items ($itemsPerScreen items fit in screen width), given that screen width is $screenWidth and item width is $itemWidth" - ) - setDotCount(dots) - } else { - val itemsPerScreen = (screenHeight / itemHeight) - val dots = ceil(items.toDouble() / itemsPerScreen).toInt() - Log.d( - "[Scroll Dots] Calculated $count for $items items ($itemsPerScreen items fit in screen height), given that screen height is $screenHeight and item height is $itemHeight" - ) - setDotCount(dots) - } - } - - fun setSelectedDot(index: Int) { - selected = index - invalidate() - } -} diff --git a/app/src/main/java/org/linphone/compatibility/Api23Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api23Compatibility.kt deleted file mode 100644 index 25e399413..000000000 --- a/app/src/main/java/org/linphone/compatibility/Api23Compatibility.kt +++ /dev/null @@ -1,286 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.compatibility - -import android.Manifest -import android.annotation.SuppressLint -import android.app.Activity -import android.app.Notification -import android.app.PendingIntent -import android.app.Service -import android.bluetooth.BluetoothAdapter -import android.content.ContentValues -import android.content.Context -import android.content.Intent -import android.content.pm.PackageManager -import android.graphics.Bitmap -import android.net.Uri -import android.os.Build -import android.os.Environment -import android.os.Vibrator -import android.provider.MediaStore -import android.provider.Settings -import android.view.View -import android.view.Window -import android.view.WindowManager -import android.view.inputmethod.EditorInfo -import androidx.fragment.app.Fragment -import org.linphone.R -import org.linphone.core.Content -import org.linphone.core.tools.Log -import org.linphone.utils.AppUtils -import org.linphone.utils.FileUtils -import org.linphone.utils.PermissionHelper - -class Api23Compatibility { - companion object { - fun hasPermission(context: Context, permission: String): Boolean { - return context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED - } - - fun requestReadPhoneStatePermission(fragment: Fragment, code: Int) { - fragment.requestPermissions(arrayOf(Manifest.permission.READ_PHONE_STATE), code) - } - - fun canDrawOverlay(context: Context): Boolean { - return Settings.canDrawOverlays(context) - } - - fun getBitmapFromUri(context: Context, uri: Uri): Bitmap { - return MediaStore.Images.Media.getBitmap(context.contentResolver, uri) - } - - @SuppressLint("MissingPermission") - fun eventVibration(vibrator: Vibrator) { - val pattern = longArrayOf(0, 100, 100) - vibrator.vibrate(pattern, -1) - } - - @SuppressLint("MissingPermission") - fun getDeviceName(context: Context): String { - val adapter = BluetoothAdapter.getDefaultAdapter() - var name = adapter?.name - if (name == null) { - name = Settings.Secure.getString( - context.contentResolver, - "bluetooth_name" - ) - } - if (name == null) { - name = Build.MANUFACTURER + " " + Build.MODEL - } - return name - } - - fun setShowWhenLocked(activity: Activity, enable: Boolean) { - if (enable) { - activity.window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED) - } else { - activity.window.clearFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED) - } - } - - fun setTurnScreenOn(activity: Activity, enable: Boolean) { - if (enable) { - activity.window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON) - } else { - activity.window.clearFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON) - } - } - - fun requestDismissKeyguard(activity: Activity) { - activity.window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD) - } - - fun startForegroundService(context: Context, intent: Intent) { - context.startService(intent) - } - - fun startForegroundService(service: Service, notifId: Int, notif: Notification?) { - service.startForeground(notifId, notif) - } - - fun hideAndroidSystemUI(hide: Boolean, window: Window) { - val decorView = window.decorView - val uiOptions = if (hide) { - View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION - } else { - View.SYSTEM_UI_FLAG_VISIBLE - } - decorView.systemUiVisibility = uiOptions - - if (hide) { - window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION) - } else { - window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION) - } - } - - fun getUpdateCurrentPendingIntentFlag(): Int { - return PendingIntent.FLAG_UPDATE_CURRENT - } - - fun getImeFlagsForSecureChatRoom(): Int { - return EditorInfo.IME_NULL - } - - suspend fun addImageToMediaStore(context: Context, content: Content): Boolean { - return addContentToMediaStore(context, content, isImage = true) - } - - suspend fun addVideoToMediaStore(context: Context, content: Content): Boolean { - return addContentToMediaStore(context, content, isVideo = true) - } - - suspend fun addAudioToMediaStore(context: Context, content: Content): Boolean { - return addContentToMediaStore(context, content, isAudio = true) - } - - private suspend fun addContentToMediaStore( - context: Context, - content: Content, - isImage: Boolean = false, - isVideo: Boolean = false, - isAudio: Boolean = false - ): Boolean { - if (!PermissionHelper.get().hasWriteExternalStoragePermission()) { - Log.e("[Media Store] Write external storage permission denied") - return false - } - - val isContentEncrypted = content.isFileEncrypted - val filePath = if (content.isFileEncrypted) { - val plainFilePath = content.exportPlainFile().orEmpty() - Log.i( - "[Media Store] [VFS] Content is encrypted, plain file path is: $plainFilePath" - ) - plainFilePath - } else { - content.filePath - } - - if (filePath.isNullOrEmpty()) { - Log.e("[Media Store] Content doesn't have a file path!") - return false - } - - val appName = AppUtils.getString(R.string.app_name) - val directory = when { - isImage -> Environment.DIRECTORY_PICTURES - isVideo -> Environment.DIRECTORY_MOVIES - isAudio -> Environment.DIRECTORY_MUSIC - else -> Environment.DIRECTORY_DOWNLOADS - } - val relativePath = "$directory/$appName" - val fileName = content.name - val mime = "${content.type}/${content.subtype}" - val type = when { - isImage -> "image" - isVideo -> "video" - isAudio -> "audio" - else -> "unexpected" - } - Log.i( - "[Media Store] Adding $type [$filePath] to Media Store with name [$fileName] and MIME [$mime], asking to be stored in: $relativePath" - ) - - val mediaStoreFilePath = when { - isImage -> { - val values = ContentValues().apply { - put(MediaStore.Images.Media.DISPLAY_NAME, fileName) - put(MediaStore.Images.Media.MIME_TYPE, mime) - } - val collection = MediaStore.Images.Media.getContentUri("external") - addContentValuesToCollection(context, filePath, collection, values) - } - isVideo -> { - val values = ContentValues().apply { - put(MediaStore.Video.Media.TITLE, fileName) - put(MediaStore.Video.Media.DISPLAY_NAME, fileName) - put(MediaStore.Video.Media.MIME_TYPE, mime) - } - val collection = MediaStore.Video.Media.getContentUri("external") - addContentValuesToCollection(context, filePath, collection, values) - } - isAudio -> { - val values = ContentValues().apply { - put(MediaStore.Audio.Media.TITLE, fileName) - put(MediaStore.Audio.Media.DISPLAY_NAME, fileName) - put(MediaStore.Audio.Media.MIME_TYPE, mime) - } - val collection = MediaStore.Audio.Media.getContentUri("external") - addContentValuesToCollection(context, filePath, collection, values) - } - else -> "" - } - - if (isContentEncrypted) { - Log.w("[Media Store] Content was encrypted, delete plain version: $filePath") - FileUtils.deleteFile(filePath) - } - - if (mediaStoreFilePath.isNotEmpty()) { - Log.i("[Media Store] Exported file path is: $mediaStoreFilePath") - content.userData = mediaStoreFilePath - return true - } - - return false - } - - private suspend fun addContentValuesToCollection( - context: Context, - filePath: String, - collection: Uri, - values: ContentValues - ): String { - try { - val fileUri = context.contentResolver.insert(collection, values) - if (fileUri == null) { - Log.e("[Media Store] Failed to get a URI to where store the file, aborting") - return "" - } - - context.contentResolver.openOutputStream(fileUri).use { out -> - if (FileUtils.copyFileTo(filePath, out)) { - return fileUri.toString() - } - } - } catch (e: Exception) { - Log.e("[Media Store] Exception: $e") - } - return "" - } - - fun requestReadExternalStorageAndCameraPermissions(fragment: Fragment, code: Int) { - fragment.requestPermissions( - arrayOf( - Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.CAMERA - ), - code - ) - } - - fun hasReadExternalStoragePermission(context: Context): Boolean { - return Compatibility.hasPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE) - } - } -} diff --git a/app/src/main/java/org/linphone/compatibility/Api25Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api25Compatibility.kt deleted file mode 100644 index 39111541f..000000000 --- a/app/src/main/java/org/linphone/compatibility/Api25Compatibility.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.compatibility - -import android.annotation.SuppressLint -import android.annotation.TargetApi -import android.app.Activity -import android.bluetooth.BluetoothAdapter -import android.content.Context -import android.os.Build -import android.provider.Settings - -@TargetApi(25) -class Api25Compatibility { - companion object { - @SuppressLint("MissingPermission") - fun getDeviceName(context: Context): String { - var name = Settings.Global.getString( - context.contentResolver, - Settings.Global.DEVICE_NAME - ) - if (name == null) { - val adapter = BluetoothAdapter.getDefaultAdapter() - name = adapter?.name - } - if (name == null) { - name = Settings.Secure.getString( - context.contentResolver, - "bluetooth_name" - ) - } - if (name == null) { - name = Build.MANUFACTURER + " " + Build.MODEL - } - return name - } - - fun isInPictureInPictureMode(activity: Activity): Boolean { - return activity.isInPictureInPictureMode - } - } -} diff --git a/app/src/main/java/org/linphone/compatibility/Api26Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api26Compatibility.kt deleted file mode 100644 index 95dcb07af..000000000 --- a/app/src/main/java/org/linphone/compatibility/Api26Compatibility.kt +++ /dev/null @@ -1,415 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.compatibility - -import android.Manifest -import android.annotation.SuppressLint -import android.annotation.TargetApi -import android.app.* -import android.content.Context -import android.content.Intent -import android.content.pm.PackageManager -import android.graphics.Bitmap -import android.media.AudioAttributes -import android.os.VibrationEffect -import android.os.Vibrator -import android.telecom.CallAudioState -import android.view.WindowManager -import android.view.inputmethod.EditorInfo -import android.widget.RemoteViews -import androidx.annotation.StringRes -import androidx.core.app.NotificationCompat -import androidx.core.app.NotificationManagerCompat -import androidx.core.app.Person -import androidx.core.content.ContextCompat -import androidx.core.graphics.drawable.IconCompat -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.contact.getThumbnailUri -import org.linphone.core.Call -import org.linphone.core.Friend -import org.linphone.core.tools.Log -import org.linphone.notifications.Notifiable -import org.linphone.notifications.NotificationsManager -import org.linphone.telecom.NativeCallWrapper -import org.linphone.utils.ImageUtils -import org.linphone.utils.LinphoneUtils - -@TargetApi(26) -class Api26Compatibility { - companion object { - fun enterPipMode(activity: Activity, conference: Boolean) { - val supportsPip = activity.packageManager - .hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) - Log.i("[Call] Is PiP supported: $supportsPip") - if (supportsPip) { - // Force portrait layout if in conference, otherwise for landscape - // Our layouts behave better in these orientation - val params = PictureInPictureParams.Builder() - .setAspectRatio(Compatibility.getPipRatio(activity, conference, !conference)) - .build() - try { - if (!activity.enterPictureInPictureMode(params)) { - Log.e("[Call] Failed to enter PiP mode") - } else { - Log.i( - "[Call] Entering PiP mode with ${if (conference) "portrait" else "landscape"} aspect ratio" - ) - } - } catch (e: Exception) { - Log.e("[Call] Can't build PiP params: $e") - } - } - } - - fun createServiceChannel(context: Context, notificationManager: NotificationManagerCompat) { - // Create service notification channel - val id = context.getString(R.string.notification_channel_service_id) - val name = context.getString(R.string.notification_channel_service_name) - val description = context.getString(R.string.notification_channel_service_name) - val channel = NotificationChannel(id, name, NotificationManager.IMPORTANCE_LOW) - channel.description = description - channel.enableVibration(false) - channel.enableLights(false) - channel.setShowBadge(false) - notificationManager.createNotificationChannel(channel) - } - - fun createMissedCallChannel( - context: Context, - notificationManager: NotificationManagerCompat - ) { - val id = context.getString(R.string.notification_channel_missed_call_id) - val name = context.getString(R.string.notification_channel_missed_call_name) - val description = context.getString(R.string.notification_channel_missed_call_name) - val channel = NotificationChannel(id, name, NotificationManager.IMPORTANCE_LOW) - channel.description = description - channel.lightColor = context.getColor(R.color.notification_led_color) - channel.enableVibration(true) - channel.enableLights(true) - channel.setShowBadge(true) - notificationManager.createNotificationChannel(channel) - } - - fun createIncomingCallChannel( - context: Context, - notificationManager: NotificationManagerCompat - ) { - // Create incoming calls notification channel - val id = context.getString(R.string.notification_channel_incoming_call_id) - val name = context.getString(R.string.notification_channel_incoming_call_name) - val description = context.getString(R.string.notification_channel_incoming_call_name) - val channel = NotificationChannel(id, name, NotificationManager.IMPORTANCE_HIGH) - channel.description = description - channel.lightColor = context.getColor(R.color.notification_led_color) - channel.enableVibration(true) - channel.enableLights(true) - channel.setShowBadge(true) - notificationManager.createNotificationChannel(channel) - } - - fun createMessageChannel( - context: Context, - notificationManager: NotificationManagerCompat - ) { - // Create messages notification channel - val id = context.getString(R.string.notification_channel_chat_id) - val name = context.getString(R.string.notification_channel_chat_name) - val description = context.getString(R.string.notification_channel_chat_name) - val channel = NotificationChannel(id, name, NotificationManager.IMPORTANCE_HIGH) - channel.description = description - channel.lightColor = context.getColor(R.color.notification_led_color) - channel.enableLights(true) - channel.enableVibration(true) - channel.setShowBadge(true) - notificationManager.createNotificationChannel(channel) - } - - fun getChannelImportance( - notificationManager: NotificationManagerCompat, - channelId: String - ): Int { - val channel = notificationManager.getNotificationChannel(channelId) - return channel?.importance ?: NotificationManagerCompat.IMPORTANCE_NONE - } - - fun getOverlayType(): Int { - return WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY - } - - fun createIncomingCallNotification( - context: Context, - call: Call, - notifiable: Notifiable, - pendingIntent: PendingIntent, - notificationsManager: NotificationsManager - ): Notification { - val contact: Friend? - val roundPicture: Bitmap? - val displayName: String - val address: String - val info: String - - val remoteContact = call.remoteContact - val conferenceAddress = if (remoteContact != null) { - coreContext.core.interpretUrl( - remoteContact, - false - ) - } else { - null - } - val conferenceInfo = if (conferenceAddress != null) { - coreContext.core.findConferenceInformationFromUri( - conferenceAddress - ) - } else { - null - } - if (conferenceInfo == null) { - Log.i( - "[Notifications Manager] No conference info found for remote contact address $remoteContact" - ) - contact = coreContext.contactsManager.findContactByAddress(call.remoteAddress) - roundPicture = - ImageUtils.getRoundBitmapFromUri(context, contact?.getThumbnailUri()) - displayName = contact?.name ?: LinphoneUtils.getDisplayName(call.remoteAddress) - address = LinphoneUtils.getDisplayableAddress(call.remoteAddress) - info = context.getString(R.string.incoming_call_notification_title) - } else { - contact = null - displayName = conferenceInfo.subject ?: context.getString(R.string.conference) - address = LinphoneUtils.getDisplayableAddress(conferenceInfo.organizer) - roundPicture = coreContext.contactsManager.groupBitmap - info = context.getString(R.string.incoming_group_call_notification_title) - Log.i( - "[Notifications Manager] Displaying incoming group call notification with subject $displayName for remote contact address $remoteContact" - ) - } - - val notificationLayoutHeadsUp = RemoteViews( - context.packageName, - R.layout.call_incoming_notification_heads_up - ) - notificationLayoutHeadsUp.setTextViewText(R.id.caller, displayName) - notificationLayoutHeadsUp.setTextViewText(R.id.sip_uri, address) - notificationLayoutHeadsUp.setTextViewText(R.id.incoming_call_info, info) - - if (roundPicture != null) { - notificationLayoutHeadsUp.setImageViewBitmap(R.id.caller_picture, roundPicture) - } - - val builder = NotificationCompat.Builder( - context, - context.getString(R.string.notification_channel_incoming_call_id) - ) - .setStyle(NotificationCompat.DecoratedCustomViewStyle()) - .addPerson(notificationsManager.getPerson(contact, displayName, roundPicture)) - .setSmallIcon(R.drawable.topbar_call_notification) - .setContentTitle(displayName) - .setContentText(context.getString(R.string.incoming_call_notification_title)) - .setCategory(NotificationCompat.CATEGORY_CALL) - .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) - .setPriority(NotificationCompat.PRIORITY_HIGH) - .setWhen(System.currentTimeMillis()) - .setAutoCancel(false) - .setShowWhen(true) - .setOngoing(true) - .setColor(ContextCompat.getColor(context, R.color.primary_color)) - .setFullScreenIntent(pendingIntent, true) - .addAction(notificationsManager.getCallDeclineAction(notifiable)) - .addAction(notificationsManager.getCallAnswerAction(notifiable)) - .setCustomHeadsUpContentView(notificationLayoutHeadsUp) - - if (!corePreferences.preventInterfaceFromShowingUp) { - builder.setContentIntent(pendingIntent) - } - - return builder.build() - } - - fun createCallNotification( - context: Context, - call: Call, - notifiable: Notifiable, - pendingIntent: PendingIntent, - channel: String, - notificationsManager: NotificationsManager - ): Notification { - @StringRes val stringResourceId: Int - val iconResourceId: Int - val roundPicture: Bitmap? - val title: String - val person: Person - - val conferenceAddress = LinphoneUtils.getConferenceAddress(call) - val conferenceInfo = if (conferenceAddress != null) { - coreContext.core.findConferenceInformationFromUri( - conferenceAddress - ) - } else { - null - } - if (conferenceInfo != null) { - Log.i( - "[Notifications Manager] Displaying group call notification with subject ${conferenceInfo.subject}" - ) - } else { - Log.i( - "[Notifications Manager] No conference info found for remote contact address ${call.remoteAddress} (${call.remoteContact})" - ) - } - - if (conferenceInfo == null) { - val contact: Friend? = - coreContext.contactsManager.findContactByAddress(call.remoteAddress) - roundPicture = ImageUtils.getRoundBitmapFromUri(context, contact?.getThumbnailUri()) - val displayName = contact?.name ?: LinphoneUtils.getDisplayName(call.remoteAddress) - title = contact?.name ?: displayName - person = notificationsManager.getPerson(contact, displayName, roundPicture) - } else { - title = conferenceInfo.subject ?: context.getString(R.string.conference) - roundPicture = coreContext.contactsManager.groupBitmap - person = Person.Builder() - .setName(title) - .setIcon(IconCompat.createWithBitmap(roundPicture)) - .build() - } - - when (call.state) { - Call.State.Paused, Call.State.Pausing, Call.State.PausedByRemote -> { - stringResourceId = R.string.call_notification_paused - iconResourceId = R.drawable.topbar_call_paused_notification - } - Call.State.OutgoingRinging, Call.State.OutgoingProgress, Call.State.OutgoingInit, Call.State.OutgoingEarlyMedia -> { - stringResourceId = R.string.call_notification_outgoing - iconResourceId = if (call.params.isVideoEnabled) { - R.drawable.topbar_videocall_notification - } else { - R.drawable.topbar_call_notification - } - } - else -> { - stringResourceId = R.string.call_notification_active - iconResourceId = if (call.currentParams.isVideoEnabled) { - R.drawable.topbar_videocall_notification - } else { - R.drawable.topbar_call_notification - } - } - } - - val builder = NotificationCompat.Builder( - context, - channel - ) - .setContentTitle(title) - .setContentText(context.getString(stringResourceId)) - .setSmallIcon(iconResourceId) - .setLargeIcon(roundPicture) - .addPerson(person) - .setAutoCancel(false) - .setCategory(NotificationCompat.CATEGORY_CALL) - .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) - .setPriority(NotificationCompat.PRIORITY_LOW) - .setWhen(System.currentTimeMillis()) - .setShowWhen(true) - .setOngoing(true) - .setColor(ContextCompat.getColor(context, R.color.notification_led_color)) - .addAction(notificationsManager.getCallDeclineAction(notifiable)) - - if (!corePreferences.preventInterfaceFromShowingUp) { - builder.setContentIntent(pendingIntent) - } - - return builder.build() - } - - @SuppressLint("MissingPermission") - fun eventVibration(vibrator: Vibrator) { - val effect = VibrationEffect.createWaveform( - longArrayOf(0L, 100L, 100L), - intArrayOf(0, VibrationEffect.DEFAULT_AMPLITUDE, 0), - -1 - ) - val audioAttrs = AudioAttributes.Builder() - .setUsage(AudioAttributes.USAGE_NOTIFICATION_EVENT) - .build() - vibrator.vibrate(effect, audioAttrs) - } - - fun changeAudioRouteForTelecomManager(connection: NativeCallWrapper, route: Int): Boolean { - Log.i( - "[Telecom Helper] Changing audio route [${routeToString(route)}] on connection [${connection.callId}] with state [${connection.stateAsString()}]" - ) - - val audioState = connection.callAudioState - if (audioState != null) { - Log.i("[Telecom Helper] Current audio route is ${routeToString(audioState.route)}") - if (audioState.route == route) { - Log.w("[Telecom Helper] Connection is already using this route") - return false - } - } else { - Log.w("[Telecom Helper] Failed to retrieve connection's call audio state!") - return false - } - - connection.setAudioRoute(route) - return true - } - - fun requestTelecomManagerPermission(activity: Activity, code: Int) { - activity.requestPermissions( - arrayOf( - Manifest.permission.READ_PHONE_STATE, - Manifest.permission.MANAGE_OWN_CALLS - ), - code - ) - } - - fun getImeFlagsForSecureChatRoom(): Int { - return EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING - } - - fun startForegroundService(context: Context, intent: Intent) { - context.startForegroundService(intent) - } - - fun hasTelecomManagerFeature(context: Context): Boolean { - return context.packageManager.hasSystemFeature( - PackageManager.FEATURE_CONNECTION_SERVICE - ) - } - - private fun routeToString(route: Int): String { - return when (route) { - CallAudioState.ROUTE_BLUETOOTH -> "BLUETOOTH" - CallAudioState.ROUTE_EARPIECE -> "EARPIECE" - CallAudioState.ROUTE_SPEAKER -> "SPEAKER" - CallAudioState.ROUTE_WIRED_HEADSET -> "WIRED_HEADSET" - CallAudioState.ROUTE_WIRED_OR_EARPIECE -> "WIRED_OR_EARPIECE" - else -> "Unknown: $route" - } - } - } -} diff --git a/app/src/main/java/org/linphone/compatibility/Api27Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api27Compatibility.kt deleted file mode 100644 index 55724fa6c..000000000 --- a/app/src/main/java/org/linphone/compatibility/Api27Compatibility.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.compatibility - -import android.annotation.TargetApi -import android.app.* -import android.content.Context - -@TargetApi(27) -class Api27Compatibility { - companion object { - fun setShowWhenLocked(activity: Activity, enable: Boolean) { - activity.setShowWhenLocked(enable) - } - - fun setTurnScreenOn(activity: Activity, enable: Boolean) { - activity.setTurnScreenOn(enable) - } - - fun requestDismissKeyguard(activity: Activity) { - val keyguardManager = activity.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager - keyguardManager.requestDismissKeyguard(activity, null) - } - } -} diff --git a/app/src/main/java/org/linphone/compatibility/Api28Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api28Compatibility.kt deleted file mode 100644 index cccd52d54..000000000 --- a/app/src/main/java/org/linphone/compatibility/Api28Compatibility.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2010-2023 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.compatibility - -import android.annotation.TargetApi -import android.content.ClipboardManager - -@TargetApi(28) -class Api28Compatibility { - companion object { - fun clearClipboard(clipboard: ClipboardManager) { - clipboard.clearPrimaryClip() - } - } -} diff --git a/app/src/main/java/org/linphone/compatibility/Api29Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api29Compatibility.kt deleted file mode 100644 index ee6ad686c..000000000 --- a/app/src/main/java/org/linphone/compatibility/Api29Compatibility.kt +++ /dev/null @@ -1,268 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.compatibility - -import android.Manifest -import android.annotation.TargetApi -import android.app.NotificationChannel -import android.app.NotificationManager -import android.content.ContentValues -import android.content.Context -import android.content.Intent -import android.graphics.Bitmap -import android.graphics.ImageDecoder -import android.net.Uri -import android.os.Environment -import android.provider.MediaStore -import android.view.View -import android.view.contentcapture.ContentCaptureContext -import android.view.contentcapture.ContentCaptureSession -import androidx.core.app.NotificationManagerCompat -import org.linphone.R -import org.linphone.core.ChatRoom -import org.linphone.core.Content -import org.linphone.core.tools.Log -import org.linphone.utils.AppUtils -import org.linphone.utils.FileUtils -import org.linphone.utils.LinphoneUtils - -@TargetApi(29) -class Api29Compatibility { - companion object { - fun hasReadPhoneStatePermission(context: Context): Boolean { - val granted = Compatibility.hasPermission(context, Manifest.permission.READ_PHONE_STATE) - if (granted) { - Log.d("[Permission Helper] Permission READ_PHONE_STATE is granted") - } else { - Log.w("[Permission Helper] Permission READ_PHONE_STATE is denied") - } - return granted - } - - fun hasTelecomManagerPermission(context: Context): Boolean { - return Compatibility.hasPermission(context, Manifest.permission.READ_PHONE_STATE) && - Compatibility.hasPermission(context, Manifest.permission.MANAGE_OWN_CALLS) - } - - fun createMessageChannel( - context: Context, - notificationManager: NotificationManagerCompat - ) { - // Create messages notification channel - val id = context.getString(R.string.notification_channel_chat_id) - val name = context.getString(R.string.notification_channel_chat_name) - val description = context.getString(R.string.notification_channel_chat_name) - val channel = NotificationChannel(id, name, NotificationManager.IMPORTANCE_HIGH) - channel.description = description - channel.lightColor = context.getColor(R.color.notification_led_color) - channel.enableLights(true) - channel.enableVibration(true) - channel.setShowBadge(true) - channel.setAllowBubbles(true) - notificationManager.createNotificationChannel(channel) - } - - fun extractLocusIdFromIntent(intent: Intent): String? { - return intent.getStringExtra(Intent.EXTRA_LOCUS_ID) - } - - fun setLocusIdInContentCaptureSession(root: View, chatRoom: ChatRoom) { - val session: ContentCaptureSession? = root.contentCaptureSession - if (session != null) { - val id = LinphoneUtils.getChatRoomId(chatRoom.localAddress, chatRoom.peerAddress) - session.contentCaptureContext = ContentCaptureContext.forLocusId(id) - } - } - - fun canChatMessageChannelBubble(context: Context): Boolean { - val notificationManager: NotificationManager = - context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - val bubblesAllowed = notificationManager.areBubblesAllowed() - Log.i( - "[Notifications Manager] Bubbles notifications are ${if (bubblesAllowed) "allowed" else "forbidden"}" - ) - return bubblesAllowed - } - - fun getBitmapFromUri(context: Context, uri: Uri): Bitmap { - return ImageDecoder.decodeBitmap( - ImageDecoder.createSource(context.contentResolver, uri) - ) - } - - suspend fun addImageToMediaStore(context: Context, content: Content): Boolean { - return addContentToMediaStore(context, content, isImage = true) - } - - suspend fun addVideoToMediaStore(context: Context, content: Content): Boolean { - return addContentToMediaStore(context, content, isVideo = true) - } - - suspend fun addAudioToMediaStore(context: Context, content: Content): Boolean { - return addContentToMediaStore(context, content, isAudio = true) - } - - private suspend fun addContentToMediaStore( - context: Context, - content: Content, - isImage: Boolean = false, - isVideo: Boolean = false, - isAudio: Boolean = false - ): Boolean { - val isContentEncrypted = content.isFileEncrypted - val filePath = if (content.isFileEncrypted) { - val plainFilePath = content.exportPlainFile().orEmpty() - Log.i( - "[Media Store] [VFS] Content is encrypted, plain file path is: $plainFilePath" - ) - plainFilePath - } else { - content.filePath - } - - if (filePath.isNullOrEmpty()) { - Log.e("[Media Store] Content doesn't have a file path!") - return false - } - - val appName = AppUtils.getString(R.string.app_name) - val directory = when { - isImage -> Environment.DIRECTORY_PICTURES - isVideo -> Environment.DIRECTORY_MOVIES - isAudio -> Environment.DIRECTORY_MUSIC - else -> Environment.DIRECTORY_DOWNLOADS - } - val relativePath = "$directory/$appName" - val fileName = content.name - val mime = "${content.type}/${content.subtype}" - val type = when { - isImage -> "image" - isVideo -> "video" - isAudio -> "audio" - else -> "unexpected" - } - Log.i( - "[Media Store] Adding $type [$filePath] to Media Store with name [$fileName] and MIME [$mime], asking to be stored in: $relativePath" - ) - - val mediaStoreFilePath = when { - isImage -> { - val values = ContentValues().apply { - put(MediaStore.Images.Media.DISPLAY_NAME, fileName) - put(MediaStore.Images.Media.MIME_TYPE, mime) - put(MediaStore.Images.Media.RELATIVE_PATH, relativePath) - put(MediaStore.Images.Media.IS_PENDING, 1) - } - val collection = MediaStore.Images.Media.getContentUri( - MediaStore.VOLUME_EXTERNAL_PRIMARY - ) - addContentValuesToCollection( - context, - filePath, - collection, - values, - MediaStore.Images.Media.IS_PENDING - ) - } - isVideo -> { - val values = ContentValues().apply { - put(MediaStore.Video.Media.TITLE, fileName) - put(MediaStore.Video.Media.DISPLAY_NAME, fileName) - put(MediaStore.Video.Media.MIME_TYPE, mime) - put(MediaStore.Video.Media.RELATIVE_PATH, relativePath) - put(MediaStore.Video.Media.IS_PENDING, 1) - } - val collection = MediaStore.Video.Media.getContentUri( - MediaStore.VOLUME_EXTERNAL_PRIMARY - ) - addContentValuesToCollection( - context, - filePath, - collection, - values, - MediaStore.Video.Media.IS_PENDING - ) - } - isAudio -> { - val values = ContentValues().apply { - put(MediaStore.Audio.Media.TITLE, fileName) - put(MediaStore.Audio.Media.DISPLAY_NAME, fileName) - put(MediaStore.Audio.Media.MIME_TYPE, mime) - put(MediaStore.Audio.Media.RELATIVE_PATH, relativePath) - put(MediaStore.Audio.Media.IS_PENDING, 1) - } - val collection = MediaStore.Audio.Media.getContentUri( - MediaStore.VOLUME_EXTERNAL_PRIMARY - ) - addContentValuesToCollection( - context, - filePath, - collection, - values, - MediaStore.Audio.Media.IS_PENDING - ) - } - else -> "" - } - - if (isContentEncrypted) { - Log.w("[Media Store] Content was encrypted, delete plain version: $filePath") - FileUtils.deleteFile(filePath) - } - - if (mediaStoreFilePath.isNotEmpty()) { - Log.i("[Media Store] Exported file path is: $mediaStoreFilePath") - content.userData = mediaStoreFilePath - return true - } - - return false - } - - private suspend fun addContentValuesToCollection( - context: Context, - filePath: String, - collection: Uri, - values: ContentValues, - pendingKey: String - ): String { - try { - val fileUri = context.contentResolver.insert(collection, values) - if (fileUri == null) { - Log.e("[Media Store] Failed to get a URI to where store the file, aborting") - return "" - } - - context.contentResolver.openOutputStream(fileUri).use { out -> - if (FileUtils.copyFileTo(filePath, out)) { - values.clear() - values.put(pendingKey, 0) - context.contentResolver.update(fileUri, values, null, null) - - return fileUri.toString() - } - } - } catch (e: Exception) { - Log.e("[Media Store] Exception: $e") - } - return "" - } - } -} diff --git a/app/src/main/java/org/linphone/compatibility/Api30Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api30Compatibility.kt deleted file mode 100644 index 7d41cb093..000000000 --- a/app/src/main/java/org/linphone/compatibility/Api30Compatibility.kt +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.compatibility - -import android.Manifest -import android.annotation.TargetApi -import android.app.Activity -import android.content.Context -import android.content.pm.ShortcutManager -import android.view.Window -import androidx.core.view.WindowCompat -import androidx.core.view.WindowInsetsCompat -import androidx.core.view.WindowInsetsControllerCompat -import androidx.fragment.app.Fragment -import org.linphone.core.ChatRoom -import org.linphone.core.tools.Log -import org.linphone.utils.LinphoneUtils - -@TargetApi(30) -class Api30Compatibility { - companion object { - fun hasReadPhoneNumbersPermission(context: Context): Boolean { - val granted = Compatibility.hasPermission( - context, - Manifest.permission.READ_PHONE_NUMBERS - ) - if (granted) { - Log.d("[Permission Helper] Permission READ_PHONE_NUMBERS is granted") - } else { - Log.w("[Permission Helper] Permission READ_PHONE_NUMBERS is denied") - } - return granted - } - - fun requestReadPhoneNumbersPermission(fragment: Fragment, code: Int) { - fragment.requestPermissions(arrayOf(Manifest.permission.READ_PHONE_NUMBERS), code) - } - - fun requestTelecomManagerPermission(activity: Activity, code: Int) { - activity.requestPermissions( - arrayOf( - Manifest.permission.READ_PHONE_NUMBERS, - Manifest.permission.READ_PHONE_STATE, - Manifest.permission.MANAGE_OWN_CALLS - ), - code - ) - } - - fun hasTelecomManagerPermission(context: Context): Boolean { - return Compatibility.hasPermission(context, Manifest.permission.READ_PHONE_NUMBERS) && - Compatibility.hasPermission(context, Manifest.permission.READ_PHONE_STATE) && - Compatibility.hasPermission(context, Manifest.permission.MANAGE_OWN_CALLS) - } - - fun removeChatRoomShortcut(context: Context, chatRoom: ChatRoom) { - val shortcutManager = context.getSystemService(ShortcutManager::class.java) - val id = LinphoneUtils.getChatRoomId(chatRoom.localAddress, chatRoom.peerAddress) - val shortcutsToRemoveList = arrayListOf(id) - shortcutManager.removeLongLivedShortcuts(shortcutsToRemoveList) - } - - fun hideAndroidSystemUI(hide: Boolean, window: Window) { - val windowInsetsCompat = WindowInsetsControllerCompat(window, window.decorView) - if (hide) { - WindowCompat.setDecorFitsSystemWindows(window, false) - windowInsetsCompat.let { - it.hide(WindowInsetsCompat.Type.systemBars()) - it.systemBarsBehavior = - WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE - } - } else { - windowInsetsCompat.show(WindowInsetsCompat.Type.systemBars()) - WindowCompat.setDecorFitsSystemWindows(window, true) - } - } - } -} diff --git a/app/src/main/java/org/linphone/compatibility/Api31Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api31Compatibility.kt deleted file mode 100644 index e91b57f4c..000000000 --- a/app/src/main/java/org/linphone/compatibility/Api31Compatibility.kt +++ /dev/null @@ -1,305 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.compatibility - -import android.annotation.TargetApi -import android.app.* -import android.content.Context -import android.content.Intent -import android.content.pm.PackageManager -import androidx.core.content.ContextCompat -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.contact.getThumbnailUri -import org.linphone.core.Call -import org.linphone.core.tools.Log -import org.linphone.notifications.Notifiable -import org.linphone.notifications.NotificationsManager -import org.linphone.utils.ImageUtils -import org.linphone.utils.LinphoneUtils - -@TargetApi(31) -class Api31Compatibility { - companion object { - fun getUpdateCurrentPendingIntentFlag(): Int { - return PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE - } - - fun createIncomingCallNotification( - context: Context, - call: Call, - notifiable: Notifiable, - pendingIntent: PendingIntent, - notificationsManager: NotificationsManager - ): Notification { - val remoteContact = call.remoteContact - val conferenceAddress = if (remoteContact != null) { - coreContext.core.interpretUrl( - remoteContact, - false - ) - } else { - null - } - val conferenceInfo = if (conferenceAddress != null) { - coreContext.core.findConferenceInformationFromUri( - conferenceAddress - ) - } else { - null - } - if (conferenceInfo != null) { - Log.i( - "[Notifications Manager] Displaying incoming group call notification with subject ${conferenceInfo.subject} and remote contact address $remoteContact" - ) - } else { - Log.i( - "[Notifications Manager] No conference info found for remote contact address $remoteContact" - ) - } - - val caller = if (conferenceInfo == null) { - val contact = - coreContext.contactsManager.findContactByAddress(call.remoteAddress) - val roundPicture = - ImageUtils.getRoundBitmapFromUri(context, contact?.getThumbnailUri()) - val displayName = contact?.name ?: LinphoneUtils.getDisplayName(call.remoteAddress) - - val person = notificationsManager.getPerson(contact, displayName, roundPicture) - Person.Builder() - .setName(person.name) - .setIcon(person.icon?.toIcon(context)) - .setUri(person.uri) - .setKey(person.key) - .setImportant(person.isImportant) - .build() - } else { - Person.Builder() - .setName( - if (conferenceInfo.subject.isNullOrEmpty()) { - context.getString( - R.string.conference_incoming_title - ) - } else { - conferenceInfo.subject - } - ) - .setIcon(coreContext.contactsManager.groupAvatar.toIcon(context)) - .setImportant(false) - .build() - } - - val declineIntent = notificationsManager.getCallDeclinePendingIntent(notifiable) - val answerIntent = notificationsManager.getCallAnswerPendingIntent(notifiable) - - val isVideoEnabledInRemoteParams = call.remoteParams?.isVideoEnabled ?: false - val isVideoAutomaticallyAccepted = call.core.videoActivationPolicy.automaticallyAccept - val isVideo = isVideoEnabledInRemoteParams && isVideoAutomaticallyAccepted - - val builder = Notification.Builder( - context, - context.getString(R.string.notification_channel_incoming_call_id) - ).apply { - try { - style = Notification.CallStyle.forIncomingCall( - caller, - declineIntent, - answerIntent - ).setIsVideo(isVideo) - } catch (iae: IllegalArgumentException) { - Log.e( - "[Api31 Compatibility] Can't use notification call style: $iae, using API 26 notification instead" - ) - return Api26Compatibility.createIncomingCallNotification( - context, - call, - notifiable, - pendingIntent, - notificationsManager - ) - } - setSmallIcon(R.drawable.topbar_call_notification) - setCategory(Notification.CATEGORY_CALL) - setVisibility(Notification.VISIBILITY_PUBLIC) - setWhen(System.currentTimeMillis()) - setAutoCancel(false) - setShowWhen(true) - setOngoing(true) - setColor(ContextCompat.getColor(context, R.color.primary_color)) - setFullScreenIntent(pendingIntent, true) - } - - if (!corePreferences.preventInterfaceFromShowingUp) { - builder.setContentIntent(pendingIntent) - } - - return builder.build() - } - - fun createCallNotification( - context: Context, - call: Call, - notifiable: Notifiable, - pendingIntent: PendingIntent, - channel: String, - notificationsManager: NotificationsManager - ): Notification { - val conferenceAddress = LinphoneUtils.getConferenceAddress(call) - val conferenceInfo = if (conferenceAddress != null) { - coreContext.core.findConferenceInformationFromUri( - conferenceAddress - ) - } else { - null - } - if (conferenceInfo != null) { - Log.i( - "[Notifications Manager] Displaying group call notification with subject ${conferenceInfo.subject}" - ) - } else { - Log.i( - "[Notifications Manager] No conference info found for remote contact address ${call.remoteAddress} (${call.remoteContact})" - ) - } - - val caller = if (conferenceInfo == null) { - val contact = - coreContext.contactsManager.findContactByAddress(call.remoteAddress) - val roundPicture = - ImageUtils.getRoundBitmapFromUri(context, contact?.getThumbnailUri()) - val displayName = contact?.name ?: LinphoneUtils.getDisplayName(call.remoteAddress) - - val person = notificationsManager.getPerson(contact, displayName, roundPicture) - Person.Builder() - .setName(person.name) - .setIcon(person.icon?.toIcon(context)) - .setUri(person.uri) - .setKey(person.key) - .setImportant(person.isImportant) - .build() - } else { - Person.Builder() - .setName(conferenceInfo.subject) - .setIcon(coreContext.contactsManager.groupAvatar.toIcon(context)) - .setImportant(false) - .build() - } - val isVideo = call.currentParams.isVideoEnabled - val iconResourceId: Int = when (call.state) { - Call.State.Paused, Call.State.Pausing, Call.State.PausedByRemote -> { - R.drawable.topbar_call_paused_notification - } - else -> { - if (isVideo) { - R.drawable.topbar_videocall_notification - } else { - R.drawable.topbar_call_notification - } - } - } - val declineIntent = notificationsManager.getCallDeclinePendingIntent(notifiable) - - val builder = Notification.Builder( - context, - channel - ).apply { - try { - style = Notification.CallStyle.forOngoingCall(caller, declineIntent) - .setIsVideo(isVideo) - } catch (iae: IllegalArgumentException) { - Log.e( - "[Api31 Compatibility] Can't use notification call style: $iae, using API 26 notification instead" - ) - return Api26Compatibility.createCallNotification( - context, - call, - notifiable, - pendingIntent, - channel, - notificationsManager - ) - } - setSmallIcon(iconResourceId) - setAutoCancel(false) - setCategory(Notification.CATEGORY_CALL) - setVisibility(Notification.VISIBILITY_PUBLIC) - setWhen(System.currentTimeMillis()) - setShowWhen(true) - setOngoing(true) - setColor(ContextCompat.getColor(context, R.color.notification_led_color)) - // This is required for CallStyle notification - setFullScreenIntent(pendingIntent, true) - } - - if (!corePreferences.preventInterfaceFromShowingUp) { - builder.setContentIntent(pendingIntent) - } - - return builder.build() - } - - fun startForegroundService(context: Context, intent: Intent) { - try { - context.startForegroundService(intent) - } catch (fssnae: ForegroundServiceStartNotAllowedException) { - Log.e("[Api31 Compatibility] Can't start service as foreground! $fssnae") - } catch (se: SecurityException) { - Log.e("[Api31 Compatibility] Can't start service as foreground! $se") - } catch (e: Exception) { - Log.e("[Api31 Compatibility] Can't start service as foreground! $e") - } - } - - fun startForegroundService(service: Service, notifId: Int, notif: Notification?) { - try { - service.startForeground(notifId, notif) - } catch (fssnae: ForegroundServiceStartNotAllowedException) { - Log.e("[Api31 Compatibility] Can't start service as foreground! $fssnae") - } catch (se: SecurityException) { - Log.e("[Api31 Compatibility] Can't start service as foreground! $se") - } catch (e: Exception) { - Log.e("[Api31 Compatibility] Can't start service as foreground! $e") - } - } - - fun enableAutoEnterPiP(activity: Activity, enable: Boolean, conference: Boolean) { - val supportsPip = activity.packageManager - .hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) - Log.i("[Call] Is PiP supported: $supportsPip") - if (supportsPip) { - // Force portrait layout if in conference, otherwise for landscape - // Our layouts behave better in these orientation - val params = PictureInPictureParams.Builder() - .setAutoEnterEnabled(enable) - .setAspectRatio(Compatibility.getPipRatio(activity, conference, !conference)) - .build() - try { - activity.setPictureInPictureParams(params) - Log.i( - "[Call] PiP auto enter enabled params set to $enable with ${if (conference) "portrait" else "landscape"} aspect ratio" - ) - } catch (e: Exception) { - Log.e("[Call] Can't build PiP params: $e") - } - } - } - } -} diff --git a/app/src/main/java/org/linphone/compatibility/Api33Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api33Compatibility.kt deleted file mode 100644 index 2a524fa34..000000000 --- a/app/src/main/java/org/linphone/compatibility/Api33Compatibility.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2010-2022 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.compatibility - -import android.Manifest -import android.annotation.TargetApi -import android.content.Context -import android.content.pm.PackageManager -import androidx.fragment.app.Fragment - -@TargetApi(33) -class Api33Compatibility { - companion object { - fun requestPostNotificationsPermission(fragment: Fragment, code: Int) { - fragment.requestPermissions( - arrayOf( - Manifest.permission.POST_NOTIFICATIONS - ), - code - ) - } - - fun hasPostNotificationsPermission(context: Context): Boolean { - return Compatibility.hasPermission(context, Manifest.permission.POST_NOTIFICATIONS) - } - - fun requestReadMediaAndCameraPermissions(fragment: Fragment, code: Int) { - fragment.requestPermissions( - arrayOf( - Manifest.permission.READ_MEDIA_IMAGES, - Manifest.permission.READ_MEDIA_VIDEO, - Manifest.permission.READ_MEDIA_AUDIO, - Manifest.permission.CAMERA - ), - code - ) - } - - fun hasReadExternalStoragePermission(context: Context): Boolean { - return Compatibility.hasPermission(context, Manifest.permission.READ_MEDIA_IMAGES) || - Compatibility.hasPermission(context, Manifest.permission.READ_MEDIA_VIDEO) || - Compatibility.hasPermission(context, Manifest.permission.READ_MEDIA_AUDIO) - } - - fun hasTelecomManagerFeature(context: Context): Boolean { - return context.packageManager.hasSystemFeature(PackageManager.FEATURE_TELECOM) - } - } -} diff --git a/app/src/main/java/org/linphone/compatibility/Api34Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Api34Compatibility.kt deleted file mode 100644 index 79c164dea..000000000 --- a/app/src/main/java/org/linphone/compatibility/Api34Compatibility.kt +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (c) 2010-2023 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.compatibility - -import android.annotation.TargetApi -import android.app.ForegroundServiceStartNotAllowedException -import android.app.Notification -import android.app.NotificationManager -import android.app.Service -import android.content.Context -import android.content.Intent -import android.content.pm.ServiceInfo -import android.net.Uri -import android.provider.Settings -import androidx.core.content.ContextCompat -import org.linphone.core.tools.Log -import org.linphone.utils.PermissionHelper - -@TargetApi(34) -class Api34Compatibility { - companion object { - fun hasFullScreenIntentPermission(context: Context): Boolean { - val notificationManager = context.getSystemService(NotificationManager::class.java) as NotificationManager - // See https://developer.android.com/reference/android/app/NotificationManager#canUseFullScreenIntent%28%29 - return notificationManager.canUseFullScreenIntent() - } - - fun requestFullScreenIntentPermission(context: Context) { - val intent = Intent() - // See https://developer.android.com/reference/android/provider/Settings#ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT - intent.action = Settings.ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT - intent.data = Uri.parse("package:${context.packageName}") - intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY) - ContextCompat.startActivity(context, intent, null) - } - - fun startCallForegroundService( - service: Service, - notifId: Int, - notif: Notification, - isCallActive: Boolean - ) { - val mask = if (isCallActive) { - Log.i( - "[Api34 Compatibility] Trying to start service as foreground using at least FOREGROUND_SERVICE_TYPE_PHONE_CALL" - ) - var computeMask = ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL - if (PermissionHelper.get().hasCameraPermission()) { - Log.i( - "[Api34 Compatibility] CAMERA permission has been granted, adding FOREGROUND_SERVICE_TYPE_CAMERA" - ) - computeMask = computeMask or ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA - } - if (PermissionHelper.get().hasRecordAudioPermission()) { - Log.i( - "[Api34 Compatibility] RECORD_AUDIO permission has been granted, adding FOREGROUND_SERVICE_TYPE_MICROPHONE" - ) - computeMask = computeMask or ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE - } - computeMask - } else { - Log.i( - "[Api34 Compatibility] Trying to start service as foreground using only FOREGROUND_SERVICE_TYPE_PHONE_CALL because call isn't active yet" - ) - ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL - } - - try { - service.startForeground( - notifId, - notif, - mask - ) - } catch (fssnae: ForegroundServiceStartNotAllowedException) { - Log.e("[Api34 Compatibility] Can't start service as foreground! $fssnae") - } catch (se: SecurityException) { - Log.e("[Api34 Compatibility] Can't start service as foreground! $se") - } catch (e: Exception) { - Log.e("[Api34 Compatibility] Can't start service as foreground! $e") - } - } - - fun startDataSyncForegroundService( - service: Service, - notifId: Int, - notif: Notification, - isCallActive: Boolean - ) { - val mask = if (isCallActive) { - Log.i( - "[Api34 Compatibility] Trying to start service as foreground using at least FOREGROUND_SERVICE_TYPE_PHONE_CALL or FOREGROUND_SERVICE_TYPE_DATA_SYNC" - ) - var computeMask = ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL or ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC - if (PermissionHelper.get().hasCameraPermission()) { - Log.i( - "[Api34 Compatibility] CAMERA permission has been granted, adding FOREGROUND_SERVICE_TYPE_CAMERA" - ) - computeMask = computeMask or ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA - } - if (PermissionHelper.get().hasRecordAudioPermission()) { - Log.i( - "[Api34 Compatibility] RECORD_AUDIO permission has been granted, adding FOREGROUND_SERVICE_TYPE_MICROPHONE" - ) - computeMask = computeMask or ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE - } - computeMask - } else { - Log.i( - "[Api34 Compatibility] Trying to start service as foreground using only FOREGROUND_SERVICE_TYPE_DATA_SYNC because no call at the time" - ) - ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC - } - - try { - service.startForeground( - notifId, - notif, - mask - ) - } catch (fssnae: ForegroundServiceStartNotAllowedException) { - Log.e("[Api34 Compatibility] Can't start service as foreground! $fssnae") - } catch (se: SecurityException) { - Log.e("[Api34 Compatibility] Can't start service as foreground! $se") - } catch (e: Exception) { - Log.e("[Api34 Compatibility] Can't start service as foreground! $e") - } - } - } -} diff --git a/app/src/main/java/org/linphone/compatibility/Compatibility.kt b/app/src/main/java/org/linphone/compatibility/Compatibility.kt deleted file mode 100644 index c0557bbcd..000000000 --- a/app/src/main/java/org/linphone/compatibility/Compatibility.kt +++ /dev/null @@ -1,503 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.compatibility - -import android.app.Activity -import android.app.Notification -import android.app.PendingIntent -import android.app.Service -import android.content.ClipboardManager -import android.content.Context -import android.content.Intent -import android.graphics.Bitmap -import android.net.Uri -import android.os.Build -import android.os.Vibrator -import android.telephony.TelephonyManager -import android.util.DisplayMetrics -import android.util.Rational -import android.view.View -import android.view.Window -import android.view.WindowManager -import androidx.core.app.NotificationManagerCompat -import androidx.fragment.app.Fragment -import java.util.* -import org.linphone.core.Call -import org.linphone.core.ChatRoom -import org.linphone.core.Content -import org.linphone.mediastream.Version -import org.linphone.notifications.Notifiable -import org.linphone.notifications.NotificationsManager -import org.linphone.telecom.NativeCallWrapper - -@Suppress("DEPRECATION") -class Compatibility { - companion object { - fun hasPermission(context: Context, permission: String): Boolean { - return Api23Compatibility.hasPermission(context, permission) - } - - // See https://developer.android.com/about/versions/11/privacy/permissions#phone-numbers - fun hasReadPhoneStateOrNumbersPermission(context: Context): Boolean { - return if (Version.sdkAboveOrEqual(Version.API30_ANDROID_11)) { - Api30Compatibility.hasReadPhoneNumbersPermission(context) - } else { - Api29Compatibility.hasReadPhoneStatePermission(context) - } - } - - // See https://developer.android.com/about/versions/11/privacy/permissions#phone-numbers - fun requestReadPhoneStateOrNumbersPermission(fragment: Fragment, code: Int) { - if (Version.sdkAboveOrEqual(Version.API30_ANDROID_11)) { - Api30Compatibility.requestReadPhoneNumbersPermission(fragment, code) - } else { - Api23Compatibility.requestReadPhoneStatePermission(fragment, code) - } - } - - // See https://developer.android.com/about/versions/11/privacy/permissions#phone-numbers - fun hasTelecomManagerPermissions(context: Context): Boolean { - return if (Version.sdkAboveOrEqual(Version.API30_ANDROID_11)) { - Api30Compatibility.hasTelecomManagerPermission(context) - } else if (Version.sdkAboveOrEqual(Version.API29_ANDROID_10)) { - Api29Compatibility.hasTelecomManagerPermission(context) - } else { - false - } - } - - fun requestTelecomManagerPermissions(activity: Activity, code: Int) { - if (Version.sdkAboveOrEqual(Version.API30_ANDROID_11)) { - Api30Compatibility.requestTelecomManagerPermission(activity, code) - } else { - Api26Compatibility.requestTelecomManagerPermission(activity, code) - } - } - - fun requestPostNotificationsPermission(fragment: Fragment, code: Int) { - if (Version.sdkAboveOrEqual(Version.API33_ANDROID_13_TIRAMISU)) { - Api33Compatibility.requestPostNotificationsPermission(fragment, code) - } - } - - fun hasPostNotificationsPermission(context: Context): Boolean { - return if (Version.sdkAboveOrEqual(Version.API33_ANDROID_13_TIRAMISU)) { - Api33Compatibility.hasPostNotificationsPermission(context) - } else { - true - } - } - - fun requestReadExternalStorageAndCameraPermissions(fragment: Fragment, code: Int) { - if (Version.sdkAboveOrEqual(Version.API33_ANDROID_13_TIRAMISU)) { - Api33Compatibility.requestReadMediaAndCameraPermissions(fragment, code) - } else { - Api23Compatibility.requestReadExternalStorageAndCameraPermissions(fragment, code) - } - } - - fun hasReadExternalStoragePermission(context: Context): Boolean { - return if (Version.sdkAboveOrEqual(Version.API33_ANDROID_13_TIRAMISU)) { - Api33Compatibility.hasReadExternalStoragePermission(context) - } else { - Api23Compatibility.hasReadExternalStoragePermission(context) - } - } - - fun getDeviceName(context: Context): String { - return when (Version.sdkAboveOrEqual(Version.API25_NOUGAT_71)) { - true -> Api25Compatibility.getDeviceName(context) - else -> Api23Compatibility.getDeviceName(context) - } - } - - fun createPhoneListener(telephonyManager: TelephonyManager): PhoneStateInterface { - return if (Version.sdkStrictlyBelow(Version.API31_ANDROID_12)) { - PhoneStateListener(telephonyManager) - } else { - TelephonyListener(telephonyManager) - } - } - - /* UI */ - - fun setShowWhenLocked(activity: Activity, enable: Boolean) { - if (Version.sdkStrictlyBelow(Version.API27_OREO_81)) { - Api23Compatibility.setShowWhenLocked(activity, enable) - } else { - Api27Compatibility.setShowWhenLocked(activity, enable) - } - } - - fun setTurnScreenOn(activity: Activity, enable: Boolean) { - if (Version.sdkStrictlyBelow(Version.API27_OREO_81)) { - Api23Compatibility.setTurnScreenOn(activity, enable) - } else { - Api27Compatibility.setTurnScreenOn(activity, enable) - } - } - - fun requestDismissKeyguard(activity: Activity) { - if (Version.sdkStrictlyBelow(Version.API27_OREO_81)) { - Api23Compatibility.requestDismissKeyguard(activity) - } else { - Api27Compatibility.requestDismissKeyguard(activity) - } - } - - fun getBitmapFromUri(context: Context, uri: Uri): Bitmap { - return if (Version.sdkStrictlyBelow(Version.API29_ANDROID_10)) { - Api23Compatibility.getBitmapFromUri(context, uri) - } else { - Api29Compatibility.getBitmapFromUri(context, uri) - } - } - - /* Notifications */ - - fun createNotificationChannels( - context: Context, - notificationManager: NotificationManagerCompat - ) { - if (Version.sdkAboveOrEqual(Version.API26_O_80)) { - Api26Compatibility.createServiceChannel(context, notificationManager) - Api26Compatibility.createMissedCallChannel(context, notificationManager) - Api26Compatibility.createIncomingCallChannel(context, notificationManager) - if (Version.sdkAboveOrEqual(Version.API29_ANDROID_10)) { - Api29Compatibility.createMessageChannel(context, notificationManager) - } else { - Api26Compatibility.createMessageChannel(context, notificationManager) - } - } - } - - fun getChannelImportance( - notificationManager: NotificationManagerCompat, - channelId: String - ): Int { - if (Version.sdkAboveOrEqual(Version.API26_O_80)) { - return Api26Compatibility.getChannelImportance(notificationManager, channelId) - } - return NotificationManagerCompat.IMPORTANCE_DEFAULT - } - - fun getOverlayType(): Int { - if (Version.sdkAboveOrEqual(Version.API26_O_80)) { - return Api26Compatibility.getOverlayType() - } - return WindowManager.LayoutParams.TYPE_PHONE - } - - fun createIncomingCallNotification( - context: Context, - call: Call, - notifiable: Notifiable, - pendingIntent: PendingIntent, - notificationsManager: NotificationsManager - ): Notification { - val manufacturer = Build.MANUFACTURER.lowercase(Locale.getDefault()) - // Samsung One UI 4.0 (API 31) doesn't (currently) display CallStyle notifications well - // Tested on Samsung S10 and Z Fold 2 - if (Version.sdkAboveOrEqual(Version.API31_ANDROID_12) && manufacturer != "samsung") { - return Api31Compatibility.createIncomingCallNotification( - context, - call, - notifiable, - pendingIntent, - notificationsManager - ) - } else if (manufacturer == "xiaomi") { // Xiaomi devices don't handle CustomHeadsUpContentView correctly - return XiaomiCompatibility.createIncomingCallNotification( - context, - call, - notifiable, - pendingIntent, - notificationsManager - ) - } - return Api26Compatibility.createIncomingCallNotification( - context, - call, - notifiable, - pendingIntent, - notificationsManager - ) - } - - fun createCallNotification( - context: Context, - call: Call, - notifiable: Notifiable, - pendingIntent: PendingIntent, - channel: String, - notificationsManager: NotificationsManager - ): Notification { - val manufacturer = Build.MANUFACTURER.lowercase(Locale.getDefault()) - // Samsung One UI 4.0 (API 31) doesn't (currently) display CallStyle notifications well - // Tested on Samsung S10 and Z Fold 2 - if (Version.sdkAboveOrEqual(Version.API31_ANDROID_12) && manufacturer != "samsung") { - return Api31Compatibility.createCallNotification( - context, - call, - notifiable, - pendingIntent, - channel, - notificationsManager - ) - } - return Api26Compatibility.createCallNotification( - context, - call, - notifiable, - pendingIntent, - channel, - notificationsManager - ) - } - - fun startForegroundService(context: Context, intent: Intent) { - if (Version.sdkAboveOrEqual(Version.API31_ANDROID_12)) { - Api31Compatibility.startForegroundService(context, intent) - } else if (Version.sdkAboveOrEqual(Version.API26_O_80)) { - Api26Compatibility.startForegroundService(context, intent) - } else { - Api23Compatibility.startForegroundService(context, intent) - } - } - - private fun startForegroundService(service: Service, notifId: Int, notif: Notification?) { - if (Version.sdkAboveOrEqual(Version.API31_ANDROID_12)) { - Api31Compatibility.startForegroundService(service, notifId, notif) - } else { - Api23Compatibility.startForegroundService(service, notifId, notif) - } - } - - fun startCallForegroundService( - service: Service, - notifId: Int, - notif: Notification, - isCallActive: Boolean - ) { - if (Version.sdkAboveOrEqual(Version.API34_ANDROID_14_UPSIDE_DOWN_CAKE)) { - Api34Compatibility.startCallForegroundService(service, notifId, notif, isCallActive) - } else { - startForegroundService(service, notifId, notif) - } - } - - fun startDataSyncForegroundService( - service: Service, - notifId: Int, - notif: Notification, - isCallActive: Boolean - ) { - if (Version.sdkAboveOrEqual(Version.API34_ANDROID_14_UPSIDE_DOWN_CAKE)) { - Api34Compatibility.startDataSyncForegroundService( - service, - notifId, - notif, - isCallActive - ) - } else { - startForegroundService(service, notifId, notif) - } - } - - /* Call */ - - fun canDrawOverlay(context: Context): Boolean { - return Api23Compatibility.canDrawOverlay(context) - } - - fun isInPictureInPictureMode(activity: Activity): Boolean { - if (Version.sdkAboveOrEqual(Version.API25_NOUGAT_71)) { - return Api25Compatibility.isInPictureInPictureMode(activity) - } - return false - } - - fun enterPipMode(activity: Activity, conference: Boolean) { - if (Version.sdkStrictlyBelow(Version.API31_ANDROID_12) && Version.sdkAboveOrEqual( - Version.API26_O_80 - ) - ) { - Api26Compatibility.enterPipMode(activity, conference) - } - } - - fun enableAutoEnterPiP(activity: Activity, enable: Boolean, conference: Boolean) { - if (Version.sdkAboveOrEqual(Version.API31_ANDROID_12)) { - Api31Compatibility.enableAutoEnterPiP(activity, enable, conference) - } - } - - fun getPipRatio( - activity: Activity, - forcePortrait: Boolean = false, - forceLandscape: Boolean = false - ): Rational { - val displayMetrics = DisplayMetrics() - activity.windowManager.defaultDisplay.getMetrics(displayMetrics) - var height = displayMetrics.heightPixels - var width = displayMetrics.widthPixels - - val aspectRatio = width / height - if (aspectRatio < 1 / 2.39) { - height = 2.39.toInt() - width = 1 - } else if (aspectRatio > 2.39) { - width = 2.39.toInt() - height = 1 - } - - val ratio = if (width > height) { - if (forcePortrait) { - Rational(height, width) - } else { - Rational(width, height) - } - } else { - if (forceLandscape) { - Rational(height, width) - } else { - Rational(width, height) - } - } - return ratio - } - - fun eventVibration(vibrator: Vibrator) { - if (Version.sdkAboveOrEqual(Version.API26_O_80)) { - Api26Compatibility.eventVibration(vibrator) - } else { - Api23Compatibility.eventVibration(vibrator) - } - } - - fun changeAudioRouteForTelecomManager(connection: NativeCallWrapper, route: Int): Boolean { - if (Version.sdkAboveOrEqual(Version.API26_O_80)) { - return Api26Compatibility.changeAudioRouteForTelecomManager(connection, route) - } - return false - } - - fun hideAndroidSystemUI(hide: Boolean, window: Window) { - if (Version.sdkAboveOrEqual(Version.API30_ANDROID_11)) { - Api30Compatibility.hideAndroidSystemUI(hide, window) - } else { - Api23Compatibility.hideAndroidSystemUI(hide, window) - } - } - - /* Chat */ - - fun removeChatRoomShortcut(context: Context, chatRoom: ChatRoom) { - if (Version.sdkAboveOrEqual(Version.API30_ANDROID_11)) { - Api30Compatibility.removeChatRoomShortcut(context, chatRoom) - } - } - - fun extractLocusIdFromIntent(intent: Intent): String? { - if (Version.sdkAboveOrEqual(Version.API29_ANDROID_10)) { - return Api29Compatibility.extractLocusIdFromIntent(intent) - } - return null - } - - fun setLocusIdInContentCaptureSession(root: View, chatRoom: ChatRoom) { - if (Version.sdkAboveOrEqual(Version.API29_ANDROID_10)) { - return Api29Compatibility.setLocusIdInContentCaptureSession(root, chatRoom) - } - } - - fun canChatMessageChannelBubble(context: Context): Boolean { - if (Version.sdkAboveOrEqual(Version.API29_ANDROID_10)) { - return Api29Compatibility.canChatMessageChannelBubble(context) - } - return false - } - - suspend fun addImageToMediaStore(context: Context, content: Content): Boolean { - if (Version.sdkAboveOrEqual(Version.API29_ANDROID_10)) { - return Api29Compatibility.addImageToMediaStore(context, content) - } - return Api23Compatibility.addImageToMediaStore(context, content) - } - - suspend fun addVideoToMediaStore(context: Context, content: Content): Boolean { - if (Version.sdkAboveOrEqual(Version.API29_ANDROID_10)) { - return Api29Compatibility.addVideoToMediaStore(context, content) - } - return Api23Compatibility.addVideoToMediaStore(context, content) - } - - suspend fun addAudioToMediaStore(context: Context, content: Content): Boolean { - if (Version.sdkAboveOrEqual(Version.API29_ANDROID_10)) { - return Api29Compatibility.addAudioToMediaStore(context, content) - } - return Api23Compatibility.addAudioToMediaStore(context, content) - } - - fun getUpdateCurrentPendingIntentFlag(): Int { - if (Version.sdkAboveOrEqual(Version.API31_ANDROID_12)) { - return Api31Compatibility.getUpdateCurrentPendingIntentFlag() - } - return Api23Compatibility.getUpdateCurrentPendingIntentFlag() - } - - fun getImeFlagsForSecureChatRoom(): Int { - if (Version.sdkAboveOrEqual(Version.API26_O_80)) { - return Api26Compatibility.getImeFlagsForSecureChatRoom() - } - return Api23Compatibility.getImeFlagsForSecureChatRoom() - } - - fun hasTelecomManagerFeature(context: Context): Boolean { - if (Version.sdkAboveOrEqual(Version.API33_ANDROID_13_TIRAMISU)) { - return Api33Compatibility.hasTelecomManagerFeature(context) - } else if (Version.sdkAboveOrEqual(Version.API26_O_80)) { - return Api26Compatibility.hasTelecomManagerFeature(context) - } - return false - } - - fun clearClipboard(clipboard: ClipboardManager) { - if (Version.sdkAboveOrEqual(Version.API28_PIE_90)) { - Api28Compatibility.clearClipboard(clipboard) - } - } - - fun hasFullScreenIntentPermission(context: Context): Boolean { - if (Version.sdkAboveOrEqual(Version.API34_ANDROID_14_UPSIDE_DOWN_CAKE)) { - return Api34Compatibility.hasFullScreenIntentPermission(context) - } - return true - } - - fun requestFullScreenIntentPermission(context: Context): Boolean { - if (Version.sdkAboveOrEqual(Version.API34_ANDROID_14_UPSIDE_DOWN_CAKE)) { - Api34Compatibility.requestFullScreenIntentPermission(context) - return true - } - return false - } - } -} diff --git a/app/src/main/java/org/linphone/compatibility/PhoneStateInterface.kt b/app/src/main/java/org/linphone/compatibility/PhoneStateInterface.kt deleted file mode 100644 index 4d7723081..000000000 --- a/app/src/main/java/org/linphone/compatibility/PhoneStateInterface.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.compatibility - -interface PhoneStateInterface { - fun destroy() - - fun isInCall(): Boolean -} diff --git a/app/src/main/java/org/linphone/compatibility/PhoneStateListener.kt b/app/src/main/java/org/linphone/compatibility/PhoneStateListener.kt deleted file mode 100644 index d5ca88920..000000000 --- a/app/src/main/java/org/linphone/compatibility/PhoneStateListener.kt +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.compatibility - -import android.telephony.PhoneStateListener -import android.telephony.TelephonyManager -import org.linphone.core.tools.Log - -class PhoneStateListener(private val telephonyManager: TelephonyManager) : PhoneStateInterface { - private var gsmCallActive = false - private val phoneStateListener = object : PhoneStateListener() { - @Deprecated("Deprecated in Java") - override fun onCallStateChanged(state: Int, phoneNumber: String?) { - gsmCallActive = when (state) { - TelephonyManager.CALL_STATE_OFFHOOK -> { - Log.i("[Context] Phone state is off hook") - true - } - TelephonyManager.CALL_STATE_RINGING -> { - Log.i("[Context] Phone state is ringing") - true - } - TelephonyManager.CALL_STATE_IDLE -> { - Log.i("[Context] Phone state is idle") - false - } - else -> { - Log.w("[Context] Phone state is unexpected: $state") - false - } - } - } - } - - init { - Log.i("[Phone State Listener] Registering phone state listener") - telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE) - } - - override fun destroy() { - Log.i("[Phone State Listener] Unregistering phone state listener") - telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE) - } - - override fun isInCall(): Boolean { - return gsmCallActive - } -} diff --git a/app/src/main/java/org/linphone/compatibility/TelephonyListener.kt b/app/src/main/java/org/linphone/compatibility/TelephonyListener.kt deleted file mode 100644 index df96c50f6..000000000 --- a/app/src/main/java/org/linphone/compatibility/TelephonyListener.kt +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.compatibility - -import android.annotation.TargetApi -import android.os.Handler -import android.os.Looper -import android.telephony.TelephonyCallback -import android.telephony.TelephonyManager -import java.util.concurrent.Executor -import org.linphone.core.tools.Log - -@TargetApi(31) -class TelephonyListener(private val telephonyManager: TelephonyManager) : PhoneStateInterface { - private var gsmCallActive = false - - private fun runOnUiThreadExecutor(): Executor { - val handler = Handler(Looper.getMainLooper()) - return Executor { - handler.post(it) - } - } - - inner class TelephonyListener : TelephonyCallback(), TelephonyCallback.CallStateListener { - override fun onCallStateChanged(state: Int) { - gsmCallActive = when (state) { - TelephonyManager.CALL_STATE_OFFHOOK -> { - Log.i("[Context] Phone state is off hook") - true - } - TelephonyManager.CALL_STATE_RINGING -> { - Log.i("[Context] Phone state is ringing") - true - } - TelephonyManager.CALL_STATE_IDLE -> { - Log.i("[Context] Phone state is idle") - false - } - else -> { - Log.w("[Context] Phone state is unexpected: $state") - false - } - } - } - } - private val telephonyListener = TelephonyListener() - - init { - Log.i("[Telephony Listener] Registering telephony callback") - telephonyManager.registerTelephonyCallback(runOnUiThreadExecutor(), telephonyListener) - } - - override fun destroy() { - Log.i("[Telephony Listener] Unregistering telephony callback") - telephonyManager.unregisterTelephonyCallback(telephonyListener) - } - - override fun isInCall(): Boolean { - return gsmCallActive - } -} diff --git a/app/src/main/java/org/linphone/compatibility/XiaomiCompatibility.kt b/app/src/main/java/org/linphone/compatibility/XiaomiCompatibility.kt deleted file mode 100644 index a914c537d..000000000 --- a/app/src/main/java/org/linphone/compatibility/XiaomiCompatibility.kt +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.compatibility - -import android.annotation.TargetApi -import android.app.* -import android.content.Context -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import androidx.core.app.NotificationCompat -import androidx.core.content.ContextCompat -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.contact.getThumbnailUri -import org.linphone.core.Call -import org.linphone.core.Friend -import org.linphone.core.tools.Log -import org.linphone.notifications.Notifiable -import org.linphone.notifications.NotificationsManager -import org.linphone.utils.ImageUtils -import org.linphone.utils.LinphoneUtils - -@TargetApi(26) -class XiaomiCompatibility { - companion object { - fun createIncomingCallNotification( - context: Context, - call: Call, - notifiable: Notifiable, - pendingIntent: PendingIntent, - notificationsManager: NotificationsManager - ): Notification { - val contact: Friend? - val roundPicture: Bitmap? - val displayName: String - val address: String - val info: String - - val remoteContact = call.remoteContact - val conferenceAddress = if (remoteContact != null) { - coreContext.core.interpretUrl( - remoteContact, - false - ) - } else { - null - } - val conferenceInfo = if (conferenceAddress != null) { - coreContext.core.findConferenceInformationFromUri( - conferenceAddress - ) - } else { - null - } - if (conferenceInfo == null) { - Log.i( - "[Notifications Manager] No conference info found for remote contact address $remoteContact" - ) - contact = coreContext.contactsManager.findContactByAddress(call.remoteAddress) - roundPicture = - ImageUtils.getRoundBitmapFromUri(context, contact?.getThumbnailUri()) - displayName = contact?.name ?: LinphoneUtils.getDisplayName(call.remoteAddress) - address = LinphoneUtils.getDisplayableAddress(call.remoteAddress) - info = context.getString(R.string.incoming_call_notification_title) - } else { - contact = null - displayName = conferenceInfo.subject ?: context.getString(R.string.conference) - address = LinphoneUtils.getDisplayableAddress(conferenceInfo.organizer) - roundPicture = coreContext.contactsManager.groupBitmap - info = context.getString(R.string.incoming_group_call_notification_title) - Log.i( - "[Notifications Manager] Displaying incoming group call notification with subject $displayName and remote contact address $remoteContact" - ) - } - - val builder = NotificationCompat.Builder( - context, - context.getString(R.string.notification_channel_incoming_call_id) - ) - .addPerson(notificationsManager.getPerson(contact, displayName, roundPicture)) - .setSmallIcon(R.drawable.topbar_call_notification) - .setLargeIcon( - roundPicture ?: BitmapFactory.decodeResource( - context.resources, - R.drawable.voip_single_contact_avatar_alt - ) - ) - .setContentTitle(displayName) - .setContentText(address) - .setSubText(info) - .setCategory(NotificationCompat.CATEGORY_CALL) - .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) - .setPriority(NotificationCompat.PRIORITY_HIGH) - .setWhen(System.currentTimeMillis()) - .setAutoCancel(false) - .setShowWhen(true) - .setOngoing(true) - .setColor(ContextCompat.getColor(context, R.color.primary_color)) - .setFullScreenIntent(pendingIntent, true) - .addAction(notificationsManager.getCallDeclineAction(notifiable)) - .addAction(notificationsManager.getCallAnswerAction(notifiable)) - - if (!corePreferences.preventInterfaceFromShowingUp) { - builder.setContentIntent(pendingIntent) - } - - return builder.build() - } - } -} diff --git a/app/src/main/java/org/linphone/contact/ContactAvatarGenerator.kt b/app/src/main/java/org/linphone/contact/ContactAvatarGenerator.kt deleted file mode 100644 index 2cb3b17b9..000000000 --- a/app/src/main/java/org/linphone/contact/ContactAvatarGenerator.kt +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (c) 2010-2022 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.contact - -import android.content.Context -import android.graphics.* -import android.graphics.drawable.BitmapDrawable -import android.text.TextPaint -import android.util.TypedValue -import androidx.core.content.ContextCompat -import org.linphone.R -import org.linphone.utils.AppUtils - -class ContactAvatarGenerator(private val context: Context) { - private var textSize: Float - private var textColor: Int - private var avatarSize: Int - private var name = " " - private var backgroundColor: Int - - init { - val theme = context.theme - - val backgroundColorTypedValue = TypedValue() - theme.resolveAttribute(R.attr.primaryTextColor, backgroundColorTypedValue, true) - backgroundColor = ContextCompat.getColor(context, backgroundColorTypedValue.resourceId) - - val textColorTypedValue = TypedValue() - theme.resolveAttribute(R.attr.secondaryTextColor, textColorTypedValue, true) - textColor = ContextCompat.getColor(context, textColorTypedValue.resourceId) - - textSize = AppUtils.getDimension(R.dimen.contact_avatar_text_size) - - avatarSize = AppUtils.getDimension(R.dimen.contact_avatar_size).toInt() - } - - fun setTextSize(size: Float) = apply { - textSize = size - } - - fun setTextColorResource(resource: Int) = apply { - textColor = ContextCompat.getColor(context, resource) - } - - fun setAvatarSize(size: Int) = apply { - avatarSize = size - } - - fun setLabel(label: String) = apply { - name = label - } - - fun setBackgroundColorAttribute(attribute: Int) = apply { - val theme = context.theme - val backgroundColorTypedValue = TypedValue() - theme.resolveAttribute(attribute, backgroundColorTypedValue, true) - backgroundColor = ContextCompat.getColor(context, backgroundColorTypedValue.resourceId) - } - - fun build(): BitmapDrawable { - val label = AppUtils.getInitials(name) - val textPainter = getTextPainter() - val painter = getPainter() - - val bitmap = Bitmap.createBitmap(avatarSize, avatarSize, Bitmap.Config.ARGB_8888) - val canvas = Canvas(bitmap) - val areaRect = Rect(0, 0, avatarSize, avatarSize) - val bounds = RectF(areaRect) - bounds.right = textPainter.measureText(label, 0, label.length) - bounds.bottom = textPainter.descent() - textPainter.ascent() - bounds.left += (areaRect.width() - bounds.right) / 2.0f - bounds.top += (areaRect.height() - bounds.bottom) / 2.0f - - val halfSize = (avatarSize / 2).toFloat() - canvas.drawCircle(halfSize, halfSize, halfSize, painter) - canvas.drawText(label, bounds.left, bounds.top - textPainter.ascent(), textPainter) - - return BitmapDrawable(context.resources, bitmap) - } - - private fun getTextPainter(): TextPaint { - val textPainter = TextPaint() - textPainter.isAntiAlias = true - textPainter.textSize = textSize - textPainter.color = textColor - textPainter.typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD) - return textPainter - } - - private fun getPainter(): Paint { - val painter = Paint() - painter.isAntiAlias = true - painter.color = backgroundColor - return painter - } -} diff --git a/app/src/main/java/org/linphone/contact/ContactDataInterface.kt b/app/src/main/java/org/linphone/contact/ContactDataInterface.kt deleted file mode 100644 index 2402d92bd..000000000 --- a/app/src/main/java/org/linphone/contact/ContactDataInterface.kt +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.contact - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.CoroutineScope -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.activities.main.viewmodels.MessageNotifierViewModel -import org.linphone.core.Address -import org.linphone.core.ChatRoom.SecurityLevel -import org.linphone.core.ConsolidatedPresence -import org.linphone.core.Friend -import org.linphone.utils.LinphoneUtils - -interface ContactDataInterface { - val contact: MutableLiveData - - val displayName: MutableLiveData - - val securityLevel: MutableLiveData - - val showGroupChatAvatar: Boolean - get() = false - - val presenceStatus: MutableLiveData - - val coroutineScope: CoroutineScope -} - -open class GenericContactData(private val sipAddress: Address) : ContactDataInterface { - final override val contact: MutableLiveData = MutableLiveData() - final override val displayName: MutableLiveData = MutableLiveData() - final override val securityLevel: MutableLiveData = MutableLiveData() - final override val presenceStatus: MutableLiveData = MutableLiveData() - final override val coroutineScope: CoroutineScope = coreContext.coroutineScope - - init { - securityLevel.value = SecurityLevel.ClearText - presenceStatus.value = ConsolidatedPresence.Offline - contactLookup() - } - - open fun destroy() { - } - - protected fun contactLookup() { - displayName.value = LinphoneUtils.getDisplayName(sipAddress) - - val friend = coreContext.contactsManager.findContactByAddress(sipAddress) - if (friend != null) { - contact.value = friend!! - presenceStatus.value = friend.consolidatedPresence - friend.addListener { - presenceStatus.value = it.consolidatedPresence - } - } - } -} - -abstract class GenericContactViewModel(private val sipAddress: Address) : MessageNotifierViewModel(), ContactDataInterface { - final override val contact: MutableLiveData = MutableLiveData() - final override val displayName: MutableLiveData = MutableLiveData() - final override val securityLevel: MutableLiveData = MutableLiveData() - final override val presenceStatus: MutableLiveData = MutableLiveData() - final override val coroutineScope: CoroutineScope = viewModelScope - - init { - securityLevel.value = SecurityLevel.ClearText - presenceStatus.value = ConsolidatedPresence.Offline - contactLookup() - } - - private fun contactLookup() { - displayName.value = LinphoneUtils.getDisplayName(sipAddress) - val friend = coreContext.contactsManager.findContactByAddress(sipAddress) - if (friend != null) { - contact.value = friend!! - presenceStatus.value = friend.consolidatedPresence - friend.addListener { - presenceStatus.value = it.consolidatedPresence - } - } - } -} diff --git a/app/src/main/java/org/linphone/contact/ContactLoader.kt b/app/src/main/java/org/linphone/contact/ContactLoader.kt deleted file mode 100644 index 7e752cb02..000000000 --- a/app/src/main/java/org/linphone/contact/ContactLoader.kt +++ /dev/null @@ -1,344 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.contact - -import android.content.ContentUris -import android.database.Cursor -import android.database.StaleDataException -import android.net.Uri -import android.os.Bundle -import android.provider.ContactsContract -import android.util.Patterns -import androidx.lifecycle.lifecycleScope -import androidx.loader.app.LoaderManager -import androidx.loader.content.CursorLoader -import androidx.loader.content.Loader -import java.lang.Exception -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.cancel -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.core.* -import org.linphone.core.tools.Log -import org.linphone.utils.AppUtils -import org.linphone.utils.PhoneNumberUtils - -class ContactLoader : LoaderManager.LoaderCallbacks { - companion object { - val projection = arrayOf( - ContactsContract.Data.CONTACT_ID, - ContactsContract.Contacts.DISPLAY_NAME_PRIMARY, - ContactsContract.Data.MIMETYPE, - ContactsContract.Contacts.STARRED, - ContactsContract.Contacts.LOOKUP_KEY, - "data1", // ContactsContract.CommonDataKinds.Phone.NUMBER, ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS, ContactsContract.CommonDataKinds.Organization.COMPANY - ContactsContract.CommonDataKinds.Phone.TYPE, - ContactsContract.CommonDataKinds.Phone.LABEL, - ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER - ) - } - - override fun onCreateLoader(id: Int, args: Bundle?): Loader { - val lastFetch = coreContext.contactsManager.latestContactFetch - Log.i( - "[Contacts Loader] Loader created, ${if (lastFetch.isEmpty()) "first fetch" else "last fetch happened at [$lastFetch]"}" - ) - coreContext.contactsManager.fetchInProgress.value = true - - val mimeType = ContactsContract.Data.MIMETYPE - val mimeSelection = "$mimeType = ? OR $mimeType = ? OR $mimeType = ? OR $mimeType = ?" - - val selection = if (corePreferences.fetchContactsFromDefaultDirectory) { - ContactsContract.Data.IN_DEFAULT_DIRECTORY + " == 1 AND ($mimeSelection)" - } else { - mimeSelection - } - - val linphoneMime = AppUtils.getString(R.string.linphone_address_mime_type) - val selectionArgs = arrayOf( - ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE, - ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE, - linphoneMime, - ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE - ) - - return CursorLoader( - coreContext.context, - ContactsContract.Data.CONTENT_URI, - projection, - selection, - selectionArgs, - ContactsContract.Data.CONTACT_ID + " ASC" - ) - } - - override fun onLoadFinished(loader: Loader, cursor: Cursor?) { - if (cursor == null) { - Log.e("[Contacts Loader] Cursor is null!") - return - } - Log.i("[Contacts Loader] Load finished, found ${cursor.count} entries in cursor") - - val core = coreContext.core - val linphoneMime = loader.context.getString(R.string.linphone_address_mime_type) - val preferNormalizedPhoneNumber = corePreferences.preferNormalizedPhoneNumbersFromAddressBook - - if (core.globalState == GlobalState.Shutdown || core.globalState == GlobalState.Off) { - Log.w("[Contacts Loader] Core is being stopped or already destroyed, abort") - return - } - - coreContext.lifecycleScope.launch { - val friends = HashMap() - - withContext(Dispatchers.IO) { - try { - // Cursor can be null now that we are on a different dispatcher according to Crashlytics - val friendsPhoneNumbers = arrayListOf() - val friendsAddresses = arrayListOf
() - var previousId = "" - while (cursor != null && !cursor.isClosed && cursor.moveToNext()) { - try { - val id: String = - cursor.getString( - cursor.getColumnIndexOrThrow(ContactsContract.Data.CONTACT_ID) - ) - val mime: String? = - cursor.getString( - cursor.getColumnIndexOrThrow(ContactsContract.Data.MIMETYPE) - ) - - if (previousId.isEmpty() || previousId != id) { - friendsPhoneNumbers.clear() - friendsAddresses.clear() - previousId = id - } - - val friend = friends[id] ?: core.createFriend() - friend.refKey = id - if (friend.name.isNullOrEmpty()) { - val displayName: String? = - cursor.getString( - cursor.getColumnIndexOrThrow( - ContactsContract.Data.DISPLAY_NAME_PRIMARY - ) - ) - friend.name = displayName - - friend.photo = Uri.withAppendedPath( - ContentUris.withAppendedId( - ContactsContract.Contacts.CONTENT_URI, - id.toLong() - ), - ContactsContract.Contacts.Photo.CONTENT_DIRECTORY - ).toString() - - val starred = - cursor.getInt( - cursor.getColumnIndexOrThrow( - ContactsContract.Contacts.STARRED - ) - ) == 1 - friend.starred = starred - val lookupKey = - cursor.getString( - cursor.getColumnIndexOrThrow( - ContactsContract.Contacts.LOOKUP_KEY - ) - ) - friend.nativeUri = - "${ContactsContract.Contacts.CONTENT_LOOKUP_URI}/$lookupKey" - - // Disable short term presence - friend.isSubscribesEnabled = false - friend.incSubscribePolicy = SubscribePolicy.SPDeny - } - - when (mime) { - ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE -> { - val data1: String? = - cursor.getString( - cursor.getColumnIndexOrThrow( - ContactsContract.CommonDataKinds.Phone.NUMBER - ) - ) - val data2: String? = - cursor.getString( - cursor.getColumnIndexOrThrow( - ContactsContract.CommonDataKinds.Phone.TYPE - ) - ) - val data3: String? = - cursor.getString( - cursor.getColumnIndexOrThrow( - ContactsContract.CommonDataKinds.Phone.LABEL - ) - ) - val data4: String? = - cursor.getString( - cursor.getColumnIndexOrThrow( - ContactsContract.CommonDataKinds.Phone.NORMALIZED_NUMBER - ) - ) - - val label = - PhoneNumberUtils.addressBookLabelTypeToVcardParamString( - data2?.toInt() - ?: ContactsContract.CommonDataKinds.BaseTypes.TYPE_CUSTOM, - data3 - ) - - val number = - if (preferNormalizedPhoneNumber || - data1.isNullOrEmpty() || - !Patterns.PHONE.matcher(data1).matches() - ) { - data4 ?: data1 - } else { - data1 - } - - if (number != null) { - if ( - friendsPhoneNumbers.find { - PhoneNumberUtils.arePhoneNumberWeakEqual( - it, - number - ) - } == null - ) { - val phoneNumber = Factory.instance() - .createFriendPhoneNumber(number, label) - friend.addPhoneNumberWithLabel(phoneNumber) - friendsPhoneNumbers.add(number) - } - } - } - linphoneMime, ContactsContract.CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE -> { - val sipAddress: String? = - cursor.getString( - cursor.getColumnIndexOrThrow( - ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS - ) - ) - if (sipAddress != null) { - val address = core.interpretUrl(sipAddress, false) - if (address != null && - friendsAddresses.find { - it.weakEqual(address) - } == null - ) { - friend.addAddress(address) - friendsAddresses.add(address) - } - } - } - ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE -> { - val organization: String? = - cursor.getString( - cursor.getColumnIndexOrThrow( - ContactsContract.CommonDataKinds.Organization.COMPANY - ) - ) - if (organization != null) { - friend.organization = organization - } - } - // Our API not being thread safe this causes crashes sometimes given the Play Store reports - // So these values will be fetched at the only moment they are required: contact edition - /*ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE -> { - if (data2 != null && data3 != null) { - val vCard = friend.vcard - vCard?.givenName = data2 - vCard?.familyName = data3 - } - }*/ - } - - friends[id] = friend - } catch (e: Exception) { - Log.e("[Contacts Loader] Exception: $e") - } - } - - withContext(Dispatchers.Main) { - if (core.globalState == GlobalState.Shutdown || core.globalState == GlobalState.Off) { - Log.w( - "[Contacts Loader] Core is being stopped or already destroyed, abort" - ) - } else { - Log.i("[Contacts Loader] ${friends.size} friends created") - val contactId = coreContext.contactsManager.contactIdToWatchFor - if (contactId.isNotEmpty()) { - val friend = friends[contactId] - Log.i( - "[Contacts Loader] Manager was asked to monitor contact id $contactId" - ) - if (friend != null) { - Log.i( - "[Contacts Loader] Found new contact matching id $contactId, notifying listeners" - ) - coreContext.contactsManager.notifyListeners(friend) - } - } - - val fl = core.defaultFriendList ?: core.createFriendList() - for (friend in fl.friends) { - fl.removeFriend(friend) - } - - if (fl != core.defaultFriendList) core.addFriendList(fl) - - val friendsList = friends.values - for (friend in friendsList) { - fl.addLocalFriend(friend) - } - friends.clear() - Log.i("[Contacts Loader] Friends added") - - // Only update subscriptions when default account is registered or anytime if it isn't the first contacts fetch - if (core.defaultAccount?.state == RegistrationState.Ok || coreContext.contactsManager.latestContactFetch.isNotEmpty()) { - Log.i("[Contacts Loader] Updating friend list [$fl] subscriptions") - fl.updateSubscriptions() - } - - coreContext.contactsManager.fetchFinished() - } - } - } catch (sde: StaleDataException) { - Log.e("[Contacts Loader] State Data Exception: $sde") - } catch (ise: IllegalStateException) { - Log.e("[Contacts Loader] Illegal State Exception: $ise") - } catch (e: Exception) { - Log.e("[Contacts Loader] Exception: $e") - } finally { - cancel() - } - } - } - } - - override fun onLoaderReset(loader: Loader) { - Log.i("[Contacts Loader] Loader reset") - } -} diff --git a/app/src/main/java/org/linphone/contact/ContactSelectionData.kt b/app/src/main/java/org/linphone/contact/ContactSelectionData.kt deleted file mode 100644 index 94be145f5..000000000 --- a/app/src/main/java/org/linphone/contact/ContactSelectionData.kt +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.contact - -import androidx.lifecycle.MutableLiveData -import kotlinx.coroutines.CoroutineScope -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.core.* -import org.linphone.utils.LinphoneUtils - -class ContactSelectionData(private val searchResult: SearchResult) : ContactDataInterface { - override val contact: MutableLiveData = MutableLiveData() - override val displayName: MutableLiveData = MutableLiveData() - override val securityLevel: MutableLiveData = MutableLiveData() - override val presenceStatus: MutableLiveData = MutableLiveData() - override val coroutineScope: CoroutineScope = coreContext.coroutineScope - - val isDisabled: MutableLiveData by lazy { - MutableLiveData() - } - - val isSelected: MutableLiveData by lazy { - MutableLiveData() - } - - val isLinphoneUser: Boolean by lazy { - searchResult.friend?.getPresenceModelForUriOrTel( - searchResult.phoneNumber ?: searchResult.address?.asStringUriOnly() ?: "" - )?.basicStatus == PresenceBasicStatus.Open - } - - val sipUri: String by lazy { - searchResult.phoneNumber ?: LinphoneUtils.getDisplayableAddress(searchResult.address) - } - - val address: Address? by lazy { - searchResult.address - } - - val hasLimeX3DHCapability: Boolean - get() = LinphoneUtils.isEndToEndEncryptedChatAvailable() && searchResult.hasCapability( - Friend.Capability.LimeX3Dh - ) - - init { - isDisabled.value = false - isSelected.value = false - presenceStatus.value = ConsolidatedPresence.Offline - searchMatchingContact() - } - - private fun searchMatchingContact() { - val friend = searchResult.friend - if (friend != null) { - contact.value = friend!! - displayName.value = friend.name - presenceStatus.value = friend.consolidatedPresence - friend.addListener { - presenceStatus.value = it.consolidatedPresence - } - } else { - val address = searchResult.address - if (address != null) { - val found = coreContext.contactsManager.findContactByAddress(address) - if (found != null) { - contact.value = found!! - presenceStatus.value = found.consolidatedPresence - found.addListener { - presenceStatus.value = it.consolidatedPresence - } - } - displayName.value = LinphoneUtils.getDisplayName(address) - } else if (searchResult.phoneNumber != null) { - val found = coreContext.contactsManager.findContactByPhoneNumber( - searchResult.phoneNumber.orEmpty() - ) - if (found != null) { - contact.value = found!! - presenceStatus.value = found.consolidatedPresence - found.addListener { - presenceStatus.value = it.consolidatedPresence - } - } - displayName.value = searchResult.phoneNumber.orEmpty() - } - } - } -} diff --git a/app/src/main/java/org/linphone/contact/ContactsManager.kt b/app/src/main/java/org/linphone/contact/ContactsManager.kt deleted file mode 100644 index 07aafe7e8..000000000 --- a/app/src/main/java/org/linphone/contact/ContactsManager.kt +++ /dev/null @@ -1,465 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.contact - -import android.accounts.Account -import android.accounts.AccountManager -import android.accounts.AuthenticatorDescription -import android.content.ContentResolver -import android.content.ContentUris -import android.content.Context -import android.graphics.Bitmap -import android.graphics.BitmapFactory -import android.graphics.drawable.Drawable -import android.net.Uri -import android.provider.ContactsContract -import android.util.Patterns -import androidx.core.app.Person -import androidx.core.graphics.drawable.IconCompat -import androidx.lifecycle.MutableLiveData -import java.io.IOException -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.core.* -import org.linphone.core.tools.Log -import org.linphone.utils.ImageUtils -import org.linphone.utils.LinphoneUtils -import org.linphone.utils.PermissionHelper -import org.linphone.utils.TimestampUtils - -interface ContactsUpdatedListener { - fun onContactsUpdated() - - fun onContactUpdated(friend: Friend) -} - -open class ContactsUpdatedListenerStub : ContactsUpdatedListener { - override fun onContactsUpdated() {} - - override fun onContactUpdated(friend: Friend) {} -} - -class ContactsManager(private val context: Context) { - val magicSearch: MagicSearch by lazy { - val magicSearch = coreContext.core.createMagicSearch() - magicSearch.limitedSearch = false - magicSearch - } - - var latestContactFetch: String = "" - - val fetchInProgress = MutableLiveData() - - var contactIdToWatchFor: String = "" - - val contactAvatar: IconCompat - val groupAvatar: IconCompat - val groupBitmap: Bitmap - - private val localFriends = arrayListOf() - - private val contactsUpdatedListeners = ArrayList() - - private val friendListListener: FriendListListenerStub = object : FriendListListenerStub() { - @Synchronized - override fun onPresenceReceived(list: FriendList, friends: Array) { - Log.i("[Contacts Manager] Presence received") - for (friend in friends) { - refreshContactOnPresenceReceived(friend) - } - Log.i("[Contacts Manager] Contacts refreshed due to presence received") - notifyListeners() - Log.i("[Contacts Manager] Presence notified to all listeners") - } - } - - private val coreListener: CoreListenerStub = object : CoreListenerStub() { - override fun onFriendListCreated(core: Core, friendList: FriendList) { - friendList.addListener(friendListListener) - } - - override fun onFriendListRemoved(core: Core, friendList: FriendList) { - friendList.removeListener(friendListListener) - } - } - - init { - initSyncAccount() - - contactAvatar = IconCompat.createWithResource( - context, - R.drawable.voip_single_contact_avatar_alt - ) - groupAvatar = IconCompat.createWithResource( - context, - R.drawable.voip_multiple_contacts_avatar_alt - ) - groupBitmap = BitmapFactory.decodeResource( - context.resources, - R.drawable.voip_multiple_contacts_avatar_alt - ) - - val core = coreContext.core - core.addListener(coreListener) - for (list in core.friendsLists) { - list.addListener(friendListListener) - } - Log.i("[Contacts Manager] Created") - } - - fun shouldDisplaySipContactsList(): Boolean { - return coreContext.core.defaultAccount?.params?.identityAddress?.domain == corePreferences.defaultDomain - } - - fun fetchFinished() { - Log.i("[Contacts Manager] Contacts loader have finished") - latestContactFetch = TimestampUtils.timeToString(System.currentTimeMillis(), false) - updateLocalContacts() - fetchInProgress.value = false - notifyListeners() - } - - @Synchronized - fun updateLocalContacts() { - Log.i("[Contacts Manager] Updating local contact(s)") - localFriends.clear() - - for (account in coreContext.core.accountList) { - val friend = coreContext.core.createFriend() - friend.name = LinphoneUtils.getDisplayName(account.params.identityAddress) - - val address = account.params.identityAddress ?: continue - friend.address = address - - val pictureUri = corePreferences.defaultAccountAvatarPath - if (pictureUri != null) { - val parsedUri = if (pictureUri.startsWith("/")) "file:$pictureUri" else pictureUri - Log.i("[Contacts Manager] Found local picture URI: $parsedUri") - friend.photo = parsedUri - } - - Log.i( - "[Contacts Manager] Local contact created for account [${address.asString()}] and picture [${friend.photo}]" - ) - localFriends.add(friend) - } - } - - @Synchronized - fun getMePerson(localAddress: Address): Person { - val friend = localFriends.find { localFriend -> - localFriend.addresses.find { address -> - address.weakEqual(localAddress) - } != null - } - return friend?.getPerson() - ?: Person.Builder().setName(LinphoneUtils.getDisplayName(localAddress)).build() - } - - @Synchronized - fun getAndroidContactIdFromUri(uri: Uri): String? { - val projection = arrayOf(ContactsContract.Data.CONTACT_ID) - val cursor = context.contentResolver.query(uri, projection, null, null, null) - if (cursor?.moveToFirst() == true) { - val nameColumnIndex = cursor.getColumnIndex(ContactsContract.Data.CONTACT_ID) - val id = cursor.getString(nameColumnIndex) - cursor.close() - return id - } - return null - } - - @Synchronized - fun findContactById(id: String): Friend? { - return coreContext.core.defaultFriendList?.findFriendByRefKey(id) - } - - @Synchronized - fun findContactByPhoneNumber(number: String): Friend? { - return coreContext.core.findFriendByPhoneNumber(number) - } - - @Synchronized - fun findContactByAddress(address: Address): Friend? { - for (friend in localFriends) { - if (friend.address?.weakEqual(address) == true) { - return friend - } - } - - val friend = coreContext.core.findFriend(address) - if (friend != null) return friend - - val username = address.username - if (username != null && Patterns.PHONE.matcher(username).matches()) { - return findContactByPhoneNumber(username) - } - - return null - } - - @Synchronized - fun isAddressMyself(address: Address): Boolean { - for (friend in localFriends) { - if (friend.address?.weakEqual(address) == true) { - return true - } - } - return false - } - - @Synchronized - fun addListener(listener: ContactsUpdatedListener) { - contactsUpdatedListeners.add(listener) - } - - @Synchronized - fun removeListener(listener: ContactsUpdatedListener) { - contactsUpdatedListeners.remove(listener) - } - - @Synchronized - fun notifyListeners() { - val list = contactsUpdatedListeners.toMutableList() - for (listener in list) { - listener.onContactsUpdated() - } - } - - @Synchronized - fun notifyListeners(friend: Friend) { - val list = contactsUpdatedListeners.toMutableList() - for (listener in list) { - listener.onContactUpdated(friend) - } - } - - @Synchronized - fun destroy() { - val core = coreContext.core - for (list in core.friendsLists) list.removeListener(friendListListener) - core.removeListener(coreListener) - } - - private fun initSyncAccount() { - val accountManager = context.getSystemService(Context.ACCOUNT_SERVICE) as AccountManager - val accounts = accountManager.getAccountsByType( - context.getString(R.string.sync_account_type) - ) - if (accounts.isEmpty()) { - val newAccount = Account( - context.getString(R.string.sync_account_name), - context.getString( - R.string.sync_account_type - ) - ) - try { - accountManager.addAccountExplicitly(newAccount, null, null) - Log.i("[Contacts Manager] Contact account added") - } catch (e: Exception) { - Log.e("[Contacts Manager] Couldn't initialize sync account: $e") - } - } else { - for (account in accounts) { - Log.i( - "[Contacts Manager] Found account with name [${account.name}] and type [${account.type}]" - ) - } - } - } - - fun getAvailableSyncAccounts(): List> { - val accountManager = context.getSystemService(Context.ACCOUNT_SERVICE) as AccountManager - val packageManager = context.packageManager - val syncAdapters = ContentResolver.getSyncAdapterTypes() - val authenticators: Array = accountManager.authenticatorTypes - val available = arrayListOf>() - - for (syncAdapter in syncAdapters) { - if (syncAdapter.authority == "com.android.contacts" && syncAdapter.isUserVisible) { - if (syncAdapter.supportsUploading() || syncAdapter.accountType == context.getString( - R.string.sync_account_type - ) - ) { - Log.i( - "[Contacts Manager] Found sync adapter for com.android.contacts authority: ${syncAdapter.accountType}" - ) - val accounts = accountManager.getAccountsByType(syncAdapter.accountType) - for (account in accounts) { - Log.i( - "[Contacts Manager] Found account for account type ${syncAdapter.accountType}: ${account.name}" - ) - for (authenticator in authenticators) { - if (authenticator.type == account.type) { - val drawable = packageManager.getDrawable( - authenticator.packageName, - authenticator.smallIconId, - null - ) - val triple = Triple(account.name, account.type, drawable) - available.add(triple) - } - } - } - } - } - } - - return available - } - - @Synchronized - private fun refreshContactOnPresenceReceived(friend: Friend) { - Log.d( - "[Contacts Manager] Received presence information for contact [${friend.name}]: [${friend.consolidatedPresence}]" - ) - if (corePreferences.storePresenceInNativeContact && PermissionHelper.get().hasWriteContactsPermission()) { - if (friend.refKey != null) { - Log.i("[Contacts Manager] Storing presence in native contact ${friend.refKey}") - storePresenceInNativeContact(friend) - } - } - notifyListeners(friend) - } - - private fun storePresenceInNativeContact(friend: Friend) { - val contactEditor = NativeContactEditor(friend) - for (phoneNumber in friend.phoneNumbers) { - val sipAddress = friend.getContactForPhoneNumberOrAddress(phoneNumber) - if (sipAddress != null) { - Log.d( - "[Contacts Manager] Found presence information to store in native contact $friend under Linphone sync account" - ) - contactEditor.setPresenceInformation( - phoneNumber, - sipAddress - ) - } - } - contactEditor.commit() - } - - fun createFriendFromSearchResult(searchResult: SearchResult): Friend { - val searchResultFriend = searchResult.friend - if (searchResultFriend != null) return searchResultFriend - - val friend = coreContext.core.createFriend() - - val address = searchResult.address - if (address != null) { - friend.address = address - } - - val number = searchResult.phoneNumber - if (number != null) { - friend.addPhoneNumber(number) - - if (address != null && address.username == number) { - friend.removeAddress(address) - } - } - - return friend - } -} - -fun Friend.getContactForPhoneNumberOrAddress(value: String): String? { - val presenceModel = getPresenceModelForUriOrTel(value) - if (presenceModel != null && presenceModel.basicStatus == PresenceBasicStatus.Open) return presenceModel.contact - return null -} - -fun Friend.hasLongTermPresence(): Boolean { - for (address in addresses) { - val presenceModel = getPresenceModelForUriOrTel(address.asStringUriOnly()) - if (presenceModel != null && presenceModel.basicStatus == PresenceBasicStatus.Open) return true - } - for (number in phoneNumbers) { - val presenceModel = getPresenceModelForUriOrTel(number) - if (presenceModel != null && presenceModel.basicStatus == PresenceBasicStatus.Open) return true - } - return false -} - -fun Friend.getThumbnailUri(): Uri? { - return getPictureUri(true) -} - -fun Friend.getPictureUri(thumbnailPreferred: Boolean = false): Uri? { - val refKey = refKey - if (refKey != null) { - try { - val lookupUri = ContentUris.withAppendedId( - ContactsContract.Contacts.CONTENT_URI, - refKey.toLong() - ) - - if (!thumbnailPreferred) { - val pictureUri = Uri.withAppendedPath( - lookupUri, - ContactsContract.Contacts.Photo.DISPLAY_PHOTO - ) - // Check that the URI points to a real file - val contentResolver = coreContext.context.contentResolver - try { - val fd = contentResolver.openAssetFileDescriptor(pictureUri, "r") - if (fd != null) { - fd.close() - return pictureUri - } - } catch (_: IOException) { } - } - - // Fallback to thumbnail if high res picture isn't available - return Uri.withAppendedPath( - lookupUri, - ContactsContract.Contacts.Photo.CONTENT_DIRECTORY - ) - } catch (_: Exception) { } - } else if (photo != null) { - try { - return Uri.parse(photo) - } catch (_: Exception) { } - } - return null -} - -fun Friend.getPerson(): Person { - val personBuilder = Person.Builder().setName(name) - - val bm: Bitmap? = - ImageUtils.getRoundBitmapFromUri( - coreContext.context, - getThumbnailUri() - ) - personBuilder.setIcon( - if (bm == null) { - coreContext.contactsManager.contactAvatar - } else { - IconCompat.createWithAdaptiveBitmap(bm) - } - ) - - personBuilder.setKey(refKey) - personBuilder.setUri(nativeUri) - personBuilder.setImportant(starred) - return personBuilder.build() -} diff --git a/app/src/main/java/org/linphone/contact/ContactsSelectionAdapter.kt b/app/src/main/java/org/linphone/contact/ContactsSelectionAdapter.kt deleted file mode 100644 index 76cffca9d..000000000 --- a/app/src/main/java/org/linphone/contact/ContactsSelectionAdapter.kt +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.contact - -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.databinding.DataBindingUtil -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.MutableLiveData -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.ListAdapter -import androidx.recyclerview.widget.RecyclerView -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R -import org.linphone.core.Address -import org.linphone.core.Friend.Capability -import org.linphone.core.SearchResult -import org.linphone.databinding.ContactSelectionCellBinding -import org.linphone.utils.Event - -class ContactsSelectionAdapter( - private val viewLifecycleOwner: LifecycleOwner -) : ListAdapter(SearchResultDiffCallback()) { - val selectedContact = MutableLiveData>() - - private val selection = MutableLiveData>() - - private val requireGroupChatCapability = MutableLiveData() - private var requireLimeCapability = MutableLiveData() - - init { - requireGroupChatCapability.value = false - requireLimeCapability.value = false - } - - fun updateSelectedAddresses(selectedAddresses: List
) { - selection.value = selectedAddresses - } - - fun setLimeCapabilityRequired(enabled: Boolean) { - requireLimeCapability.value = enabled - } - - fun setGroupChatCapabilityRequired(enabled: Boolean) { - requireGroupChatCapability.value = enabled - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { - val binding: ContactSelectionCellBinding = DataBindingUtil.inflate( - LayoutInflater.from(parent.context), - R.layout.contact_selection_cell, - parent, - false - ) - return ViewHolder(binding) - } - - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - (holder as ViewHolder).bind(getItem(position)) - } - - inner class ViewHolder( - private val binding: ContactSelectionCellBinding - ) : RecyclerView.ViewHolder(binding.root) { - fun bind(searchResult: SearchResult) { - with(binding) { - val searchResultViewModel = ContactSelectionData(searchResult) - data = searchResultViewModel - - lifecycleOwner = viewLifecycleOwner - - requireLimeCapability.observe(viewLifecycleOwner) { - updateSecurity(searchResult, searchResultViewModel) - } - requireGroupChatCapability.observe(viewLifecycleOwner) { - updateSecurity(searchResult, searchResultViewModel) - } - - selection.observe(viewLifecycleOwner) { selectedAddresses -> - val selected = selectedAddresses.find { address -> - val searchAddress = searchResult.address - if (searchAddress != null) address.weakEqual(searchAddress) else false - } - searchResultViewModel.isSelected.value = selected != null - } - - setClickListener { - selectedContact.value = Event(searchResult) - } - - executePendingBindings() - } - } - - private fun updateSecurity( - searchResult: SearchResult, - viewModel: ContactSelectionData - ) { - val securityEnabled = requireLimeCapability.value ?: false - val groupCapabilityRequired = requireGroupChatCapability.value ?: false - val searchAddress = searchResult.address - val isMyself = securityEnabled && searchAddress != null && coreContext.core.defaultAccount?.params?.identityAddress?.weakEqual( - searchAddress - ) ?: false - val limeCheck = !securityEnabled || ( - securityEnabled && searchResult.hasCapability( - Capability.LimeX3Dh - ) - ) - val groupCheck = !groupCapabilityRequired || ( - groupCapabilityRequired && searchResult.hasCapability( - Capability.GroupChat - ) - ) - val disabled = if (searchResult.friend != null) !limeCheck || !groupCheck || isMyself else false // Generated entry from search filter - - viewModel.isDisabled.value = disabled - - if (disabled && viewModel.isSelected.value == true) { - // Remove item from selection if both selected and disabled - selectedContact.postValue(Event(searchResult)) - } - } - } -} - -private class SearchResultDiffCallback : DiffUtil.ItemCallback() { - override fun areItemsTheSame( - oldItem: SearchResult, - newItem: SearchResult - ): Boolean { - val oldAddress = oldItem.address - val newAddress = newItem.address - return if (oldAddress != null && newAddress != null) oldAddress.weakEqual(newAddress) else false - } - - override fun areContentsTheSame( - oldItem: SearchResult, - newItem: SearchResult - ): Boolean { - return newItem.friend != null - } -} diff --git a/app/src/main/java/org/linphone/contact/ContactsSelectionViewModel.kt b/app/src/main/java/org/linphone/contact/ContactsSelectionViewModel.kt deleted file mode 100644 index 46f47dbc6..000000000 --- a/app/src/main/java/org/linphone/contact/ContactsSelectionViewModel.kt +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.contact - -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.* -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.activities.main.viewmodels.MessageNotifierViewModel -import org.linphone.core.* -import org.linphone.core.tools.Log -import org.linphone.utils.Event - -open class ContactsSelectionViewModel : MessageNotifierViewModel() { - val contactsList = MutableLiveData>() - - val sipContactsSelected = MutableLiveData() - - val selectedAddresses = MutableLiveData>() - - val filter = MutableLiveData() - private var previousFilter = "NotSet" - - val fetchInProgress = MutableLiveData() - private var searchResultsPending: Boolean = false - private var fastFetchJob: Job? = null - - val moreResultsAvailableEvent: MutableLiveData> by lazy { - MutableLiveData>() - } - - private val contactsUpdatedListener = object : ContactsUpdatedListenerStub() { - override fun onContactsUpdated() { - Log.i("[Contacts Selection] Contacts have changed") - applyFilter() - } - } - - private val magicSearchListener = object : MagicSearchListenerStub() { - override fun onSearchResultsReceived(magicSearch: MagicSearch) { - searchResultsPending = false - processMagicSearchResults(magicSearch.lastSearch) - fetchInProgress.value = false - } - - override fun onLdapHaveMoreResults(magicSearch: MagicSearch, ldap: Ldap) { - moreResultsAvailableEvent.value = Event(true) - } - } - - init { - sipContactsSelected.value = coreContext.contactsManager.shouldDisplaySipContactsList() - - selectedAddresses.value = arrayListOf() - - coreContext.contactsManager.addListener(contactsUpdatedListener) - coreContext.contactsManager.magicSearch.addListener(magicSearchListener) - } - - override fun onCleared() { - coreContext.contactsManager.magicSearch.removeListener(magicSearchListener) - coreContext.contactsManager.removeListener(contactsUpdatedListener) - - super.onCleared() - } - - fun applyFilter() { - val filterValue = filter.value.orEmpty().trim() - - if (previousFilter.isNotEmpty() && ( - previousFilter.length > filterValue.length || - (previousFilter.length == filterValue.length && previousFilter != filterValue) - ) - ) { - coreContext.contactsManager.magicSearch.resetSearchCache() - } - previousFilter = filterValue - - val domain = if (sipContactsSelected.value == true) coreContext.core.defaultAccount?.params?.domain ?: "" else "" - searchResultsPending = true - fastFetchJob?.cancel() - coreContext.contactsManager.magicSearch.getContactsListAsync( - filterValue, - domain, - MagicSearch.Source.All.toInt(), - MagicSearch.Aggregation.None - ) - - val spinnerDelay = corePreferences.delayBeforeShowingContactsSearchSpinner.toLong() - fastFetchJob = viewModelScope.launch { - withContext(Dispatchers.IO) { - delay(spinnerDelay) - withContext(Dispatchers.Main) { - if (searchResultsPending) { - fetchInProgress.value = true - } - } - } - } - } - - fun clearFilter() { - filter.value = "" - } - - fun toggleSelectionForSearchResult(searchResult: SearchResult) { - val address = searchResult.address - if (address != null) { - toggleSelectionForAddress(address) - } - } - - fun toggleSelectionForAddress(address: Address) { - val list = arrayListOf
() - list.addAll(selectedAddresses.value.orEmpty()) - - val found = list.find { - it.weakEqual(address) - } - - if (found != null) { - list.remove(found) - } else { - val contact = coreContext.contactsManager.findContactByAddress(address) - if (contact != null) { - val clone = address.clone() - clone.displayName = contact.name - list.add(clone) - } else { - list.add(address) - } - } - - selectedAddresses.value = list - } - - private fun processMagicSearchResults(results: Array) { - Log.i("[Contacts Selection] Processing ${results.size} results") - val list = arrayListOf() - for (result in results) { - if (result.sourceFlags == MagicSearch.Source.Request.toInt()) { - val address = result.address - if (address != null) { - val found = list.find { - it.address?.weakEqual(address) ?: false - } - if (found != null) { - Log.i( - "[Contacts Selection] User-input is already present in search results, skipping request" - ) - continue - } - } - } - list.add(result) - } - contactsList.postValue(list) - } -} diff --git a/app/src/main/java/org/linphone/contact/DummyAuthenticationService.kt b/app/src/main/java/org/linphone/contact/DummyAuthenticationService.kt deleted file mode 100644 index 3cc3cf4b0..000000000 --- a/app/src/main/java/org/linphone/contact/DummyAuthenticationService.kt +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ - -package org.linphone.contact - -import android.accounts.AbstractAccountAuthenticator -import android.accounts.Account -import android.accounts.AccountAuthenticatorResponse -import android.app.Service -import android.content.Context -import android.content.Intent -import android.os.Bundle -import android.os.IBinder - -// Below classes are required to be able to create our DummySyncService... -internal class DummyAuthenticator(context: Context) : AbstractAccountAuthenticator(context) { - override fun getAuthTokenLabel(authTokenType: String?): String { - throw UnsupportedOperationException() - } - - override fun confirmCredentials( - response: AccountAuthenticatorResponse?, - account: Account?, - options: Bundle? - ): Bundle? = null - - override fun updateCredentials( - response: AccountAuthenticatorResponse?, - account: Account?, - authTokenType: String?, - options: Bundle? - ): Bundle { - throw UnsupportedOperationException() - } - - override fun getAuthToken( - response: AccountAuthenticatorResponse?, - account: Account?, - authTokenType: String?, - options: Bundle? - ): Bundle { - throw UnsupportedOperationException() - } - - override fun hasFeatures( - response: AccountAuthenticatorResponse?, - account: Account?, - features: Array? - ): Bundle { - throw UnsupportedOperationException() - } - - override fun editProperties( - response: AccountAuthenticatorResponse?, - accountType: String? - ): Bundle { - throw UnsupportedOperationException() - } - - override fun addAccount( - response: AccountAuthenticatorResponse?, - accountType: String?, - authTokenType: String?, - requiredFeatures: Array?, - options: Bundle? - ): Bundle? = null -} - -class DummyAuthenticationService : Service() { - private lateinit var authenticator: DummyAuthenticator - - override fun onCreate() { - authenticator = DummyAuthenticator(this) - } - - override fun onBind(intent: Intent): IBinder { - return authenticator.iBinder - } -} diff --git a/app/src/main/java/org/linphone/contact/DummySyncService.kt b/app/src/main/java/org/linphone/contact/DummySyncService.kt deleted file mode 100644 index 3aca7e36a..000000000 --- a/app/src/main/java/org/linphone/contact/DummySyncService.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.contact - -import android.accounts.Account -import android.app.Service -import android.content.* -import android.os.Bundle -import android.os.IBinder - -// Below classes are required to be able to use our own contact MIME type entry... -class DummySyncAdapter(context: Context, autoInit: Boolean) : AbstractThreadedSyncAdapter( - context, - autoInit -) { - override fun onPerformSync( - account: Account?, - extras: Bundle?, - authority: String?, - provider: ContentProviderClient?, - syncResult: SyncResult? - ) { } -} - -class DummySyncService : Service() { - companion object { - private val syncAdapterLock = Any() - private var syncAdapter: DummySyncAdapter? = null - } - - override fun onCreate() { - synchronized(syncAdapterLock) { - if (syncAdapter == null) { - syncAdapter = DummySyncAdapter(applicationContext, true) - } - } - } - - override fun onBind(intent: Intent?): IBinder? { - return syncAdapter?.syncAdapterBinder - } -} diff --git a/app/src/main/java/org/linphone/contact/NativeContactEditor.kt b/app/src/main/java/org/linphone/contact/NativeContactEditor.kt deleted file mode 100644 index 1e96818fb..000000000 --- a/app/src/main/java/org/linphone/contact/NativeContactEditor.kt +++ /dev/null @@ -1,594 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.contact - -import android.content.ContentProviderOperation -import android.content.ContentUris -import android.net.Uri -import android.provider.ContactsContract -import android.provider.ContactsContract.CommonDataKinds -import android.provider.ContactsContract.RawContacts -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R -import org.linphone.activities.main.contact.data.NumberOrAddressEditorData -import org.linphone.core.Friend -import org.linphone.core.tools.Log -import org.linphone.utils.AppUtils -import org.linphone.utils.PermissionHelper - -class NativeContactEditor(val friend: Friend) { - companion object { - fun createAndroidContact(accountName: String?, accountType: String?): Long { - Log.i("[Native Contact Editor] Using sync account $accountName with type $accountType") - - val changes = arrayListOf() - changes.add( - ContentProviderOperation.newInsert(RawContacts.CONTENT_URI) - .withValue(RawContacts.ACCOUNT_NAME, accountName) - .withValue(RawContacts.ACCOUNT_TYPE, accountType) - .build() - ) - val contentResolver = coreContext.context.contentResolver - val results = contentResolver.applyBatch(ContactsContract.AUTHORITY, changes) - for (result in results) { - val uri = result.uri - Log.i("[Native Contact Editor] Contact creation result is $uri") - if (uri != null) { - try { - val cursor = contentResolver.query( - uri, - arrayOf(RawContacts.CONTACT_ID), - null, - null, - null - ) - if (cursor != null) { - cursor.moveToNext() - val contactId: Long = cursor.getLong(0) - Log.i("[Native Contact Editor] New contact id is $contactId") - cursor.close() - return contactId - } - } catch (e: Exception) { - Log.e("[Native Contact Editor] Failed to get cursor: $e") - } - } - } - - return 0 - } - } - - private val changes = arrayListOf() - private val selection = - "${ContactsContract.Data.CONTACT_ID} =? AND ${ContactsContract.Data.MIMETYPE} =?" - private val phoneNumberSelection = - "$selection AND (${CommonDataKinds.Phone.NUMBER}=? OR ${CommonDataKinds.Phone.NORMALIZED_NUMBER}=?)" - private val presenceUpdateSelection = - "${ContactsContract.Data.CONTACT_ID} =? AND ${ContactsContract.Data.MIMETYPE} =? AND data3=?" - private val contactUri = ContactsContract.Data.CONTENT_URI - - private var rawId: String? = null - private var syncAccountRawId: String? = null - private var pictureByteArray: ByteArray? = null - - init { - val contentResolver = coreContext.context.contentResolver - val cursor = contentResolver.query( - RawContacts.CONTENT_URI, - arrayOf(RawContacts._ID), - "${RawContacts.CONTACT_ID} =?", - arrayOf(friend.refKey), - null - ) - if (cursor?.moveToFirst() == true) { - do { - if (rawId == null) { - try { - rawId = cursor.getString(cursor.getColumnIndexOrThrow(RawContacts._ID)) - Log.d( - "[Native Contact Editor] Found raw id $rawId for native contact with id ${friend.refKey}" - ) - } catch (iae: IllegalArgumentException) { - Log.e("[Native Contact Editor] Exception: $iae") - } - } - } while (cursor.moveToNext() && rawId == null) - } - cursor?.close() - } - - fun setFirstAndLastNames(firstName: String, lastName: String): NativeContactEditor { - if (firstName == friend.vcard?.givenName && lastName == friend.vcard?.familyName) { - Log.w("[Native Contact Editor] First & last names haven't changed") - return this - } - - val builder = if (friend.vcard?.givenName == null && friend.vcard?.familyName == null) { - // Probably a contact creation - ContentProviderOperation.newInsert(contactUri) - .withValue(ContactsContract.Data.RAW_CONTACT_ID, rawId) - } else { - ContentProviderOperation.newUpdate(contactUri) - .withSelection( - selection, - arrayOf(friend.refKey, CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) - ) - } - - builder.withValue( - ContactsContract.Data.MIMETYPE, - CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE - ) - .withValue( - CommonDataKinds.StructuredName.GIVEN_NAME, - firstName - ) - .withValue( - CommonDataKinds.StructuredName.FAMILY_NAME, - lastName - ) - addChanges(builder.build()) - return this - } - - fun setOrganization(value: String): NativeContactEditor { - val previousValue = friend.organization.orEmpty() - if (value == previousValue) { - Log.d("[Native Contact Editor] Organization hasn't changed") - return this - } - - val builder = if (previousValue.isNotEmpty()) { - ContentProviderOperation.newUpdate(contactUri) - .withSelection( - "$selection AND ${CommonDataKinds.Organization.COMPANY} =?", - arrayOf( - friend.refKey, - CommonDataKinds.Organization.CONTENT_ITEM_TYPE, - previousValue - ) - ) - } else { - ContentProviderOperation.newInsert(contactUri) - .withValue(ContactsContract.Data.RAW_CONTACT_ID, rawId) - } - - builder.withValue( - ContactsContract.Data.MIMETYPE, - CommonDataKinds.Organization.CONTENT_ITEM_TYPE - ) - .withValue( - CommonDataKinds.Organization.COMPANY, - value - ) - - addChanges(builder.build()) - return this - } - - fun setPhoneNumbers(value: List): NativeContactEditor { - var addCount = 0 - var removeCount = 0 - var editCount = 0 - - for (phoneNumber in value) { - when { - phoneNumber.currentValue.isEmpty() -> { - // New phone number to add - val number = phoneNumber.newValue.value.orEmpty() - if (number.isNotEmpty()) { - addCount++ - addPhoneNumber(number) - } - } - phoneNumber.toRemove.value == true -> { - // Existing number to remove - removeCount++ - removePhoneNumber(phoneNumber.currentValue) - } - phoneNumber.currentValue != phoneNumber.newValue.value -> { - // Existing number to update - val number = phoneNumber.newValue.value.orEmpty() - if (number.isNotEmpty()) { - editCount++ - updatePhoneNumber(phoneNumber.currentValue, number) - } - } - } - } - - Log.i( - "[Native Contact Editor] $addCount numbers added, $removeCount numbers removed and $editCount numbers updated" - ) - return this - } - - fun setSipAddresses(value: List): NativeContactEditor { - var addCount = 0 - var removeCount = 0 - var editCount = 0 - - for (sipAddress in value) { - when { - sipAddress.currentValue.isEmpty() -> { - // New address to add - val address = sipAddress.newValue.value.orEmpty() - if (address.isNotEmpty()) { - addCount++ - addSipAddress(address) - } - } - sipAddress.toRemove.value == true -> { - // Existing address to remove - removeCount++ - removeLinphoneOrSipAddress(sipAddress.currentValue) - } - sipAddress.currentValue != sipAddress.newValue.value -> { - // Existing address to update - val address = sipAddress.newValue.value.orEmpty() - if (address.isNotEmpty()) { - editCount++ - updateLinphoneOrSipAddress(sipAddress.currentValue, address) - } - } - } - } - - Log.i( - "[Native Contact Editor] $addCount addresses added, $removeCount addresses removed and $editCount addresses updated" - ) - return this - } - - fun setPicture(value: ByteArray?): NativeContactEditor { - pictureByteArray = value - if (value != null) Log.i("[Native Contact Editor] Adding operation: picture set/update") - return this - } - - fun setPresenceInformation(phoneNumber: String, sipAddress: String): NativeContactEditor { - if (syncAccountRawId == null) { - val contentResolver = coreContext.context.contentResolver - val cursor = contentResolver.query( - RawContacts.CONTENT_URI, - arrayOf(RawContacts._ID, RawContacts.ACCOUNT_TYPE), - "${RawContacts.CONTACT_ID} =?", - arrayOf(friend.refKey), - null - ) - if (cursor?.moveToFirst() == true) { - do { - try { - val accountType = - cursor.getString(cursor.getColumnIndexOrThrow(RawContacts.ACCOUNT_TYPE)) - if (accountType == AppUtils.getString(R.string.sync_account_type) && syncAccountRawId == null) { - syncAccountRawId = - cursor.getString(cursor.getColumnIndexOrThrow(RawContacts._ID)) - Log.d( - "[Native Contact Editor] Found linphone raw id $syncAccountRawId for native contact with id ${friend.refKey}" - ) - } - } catch (iae: IllegalArgumentException) { - Log.e("[Native Contact Editor] Exception: $iae") - } - } while (cursor.moveToNext() && syncAccountRawId == null) - } - cursor?.close() - } - - if (syncAccountRawId == null) { - Log.w("[Native Contact Editor] Linphone raw id not found") - val insert = ContentProviderOperation.newInsert(RawContacts.CONTENT_URI) - .withValue(RawContacts.ACCOUNT_NAME, AppUtils.getString(R.string.sync_account_name)) - .withValue(RawContacts.ACCOUNT_TYPE, AppUtils.getString(R.string.sync_account_type)) - .withValue(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DEFAULT) - .build() - addChanges(insert) - val update = - ContentProviderOperation.newUpdate( - ContactsContract.AggregationExceptions.CONTENT_URI - ) - .withValue( - ContactsContract.AggregationExceptions.TYPE, - ContactsContract.AggregationExceptions.TYPE_KEEP_TOGETHER - ) - .withValue(ContactsContract.AggregationExceptions.RAW_CONTACT_ID1, rawId) - .withValueBackReference( - ContactsContract.AggregationExceptions.RAW_CONTACT_ID2, - 0 - ) - .build() - addChanges(update) - commit(true) - } - - if (syncAccountRawId == null) { - Log.e( - "[Native Contact Editor] Can't add presence to contact in Linphone sync account, no raw id" - ) - return this - } - - Log.d("[Native Contact Editor] Trying to add presence information to contact") - setPresenceLinphoneSipAddressForPhoneNumber(sipAddress, phoneNumber) - return this - } - - fun commit(updateSyncAccountRawId: Boolean = false) { - if (PermissionHelper.get().hasWriteContactsPermission()) { - try { - if (changes.isNotEmpty()) { - val contentResolver = coreContext.context.contentResolver - val results = contentResolver.applyBatch(ContactsContract.AUTHORITY, changes) - for (result in results) { - val uri = result.uri - Log.i("[Native Contact Editor] Result is $uri") - if (uri != null && updateSyncAccountRawId && syncAccountRawId == null) { - syncAccountRawId = ContentUris.parseId(uri).toString() - Log.i( - "[Native Contact Editor] Sync account raw id is $syncAccountRawId" - ) - } - } - } - if (pictureByteArray != null) { - updatePicture() - } - } catch (e: Exception) { - Log.e("[Native Contact Editor] Exception raised while applying changes: $e") - } - } else { - Log.e("[Native Contact Editor] WRITE_CONTACTS permission isn't granted!") - } - changes.clear() - } - - private fun addChanges(operation: ContentProviderOperation) { - Log.i("[Native Contact Editor] Adding operation: $operation") - changes.add(operation) - } - - private fun addPhoneNumber(phoneNumber: String) { - val insert = ContentProviderOperation.newInsert(contactUri) - .withValue(ContactsContract.Data.RAW_CONTACT_ID, rawId) - .withValue( - ContactsContract.Data.MIMETYPE, - CommonDataKinds.Phone.CONTENT_ITEM_TYPE - ) - .withValue(CommonDataKinds.Phone.NUMBER, phoneNumber) - .withValue( - CommonDataKinds.Phone.TYPE, - CommonDataKinds.Phone.TYPE_MOBILE - ) - .build() - addChanges(insert) - } - - private fun updatePhoneNumber(currentValue: String, phoneNumber: String) { - val update = ContentProviderOperation.newUpdate(contactUri) - .withSelection( - phoneNumberSelection, - arrayOf( - friend.refKey, - CommonDataKinds.Phone.CONTENT_ITEM_TYPE, - currentValue, - currentValue - ) - ) - .withValue(ContactsContract.Data.MIMETYPE, CommonDataKinds.Phone.CONTENT_ITEM_TYPE) - .withValue(CommonDataKinds.Phone.NUMBER, phoneNumber) - .withValue( - CommonDataKinds.Phone.TYPE, - CommonDataKinds.Phone.TYPE_MOBILE - ) - .build() - addChanges(update) - } - - private fun removePhoneNumber(phoneNumber: String) { - val delete = ContentProviderOperation.newDelete(contactUri) - .withSelection( - phoneNumberSelection, - arrayOf( - friend.refKey, - CommonDataKinds.Phone.CONTENT_ITEM_TYPE, - phoneNumber, - phoneNumber - ) - ) - .build() - addChanges(delete) - } - - private fun addSipAddress(address: String) { - val insert = ContentProviderOperation.newInsert(contactUri) - .withValue(ContactsContract.Data.RAW_CONTACT_ID, rawId) - .withValue( - ContactsContract.Data.MIMETYPE, - CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE - ) - .withValue("data1", address) // value - .build() - addChanges(insert) - } - - private fun updateLinphoneOrSipAddress(currentValue: String, sipAddress: String) { - val updateLegacy = ContentProviderOperation.newUpdate(contactUri) - .withSelection( - "${ContactsContract.Data.CONTACT_ID} =? AND ${ContactsContract.Data.MIMETYPE} =? AND data1=?", - arrayOf( - friend.refKey, - AppUtils.getString(R.string.linphone_address_mime_type), - currentValue - ) - ) - .withValue("data1", sipAddress) // value - .withValue("data2", AppUtils.getString(R.string.app_name)) // summary - .withValue("data3", sipAddress) // detail - .build() - - val update = ContentProviderOperation.newUpdate(contactUri) - .withSelection( - "${ContactsContract.Data.CONTACT_ID} =? AND ${ContactsContract.Data.MIMETYPE} =? AND data1=?", - arrayOf( - friend.refKey, - CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE, - currentValue - ) - ) - .withValue("data1", sipAddress) // value - .build() - - addChanges(updateLegacy) - addChanges(update) - } - - private fun removeLinphoneOrSipAddress(sipAddress: String) { - val delete = ContentProviderOperation.newDelete(contactUri) - .withSelection( - "${ContactsContract.Data.CONTACT_ID} =? AND (${ContactsContract.Data.MIMETYPE} =? OR ${ContactsContract.Data.MIMETYPE} =?) AND data1=?", - arrayOf( - friend.refKey, - CommonDataKinds.SipAddress.CONTENT_ITEM_TYPE, - AppUtils.getString(R.string.linphone_address_mime_type), - sipAddress - ) - ) - .build() - addChanges(delete) - } - - private fun setPresenceLinphoneSipAddressForPhoneNumber(sipAddress: String, phoneNumber: String) { - val contentResolver = coreContext.context.contentResolver - val cursor = contentResolver.query( - ContactsContract.Data.CONTENT_URI, - arrayOf("data1"), - "${ContactsContract.Data.RAW_CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} = ? AND data3 = ?", - arrayOf( - syncAccountRawId, - AppUtils.getString(R.string.linphone_address_mime_type), - phoneNumber - ), - null - ) - val count = cursor?.count ?: 0 - val data1 = if (count > 0) { - if (cursor?.moveToFirst() == true) { - try { - cursor.getString(cursor.getColumnIndexOrThrow("data1")) - } catch (iae: IllegalArgumentException) { - Log.e("[Native Contact Editor] Exception: $iae") - } - } else { - null - } - } else { - null - } - cursor?.close() - - val address = if (sipAddress.endsWith(";user=phone")) { - sipAddress.substring(0, sipAddress.length - ";user=phone".length) - } else { - sipAddress - } - - if (count == 0) { - Log.i( - "[Native Contact Editor] No existing presence information found for this phone number ($phoneNumber) & SIP address ($address), let's add it" - ) - addPresenceLinphoneSipAddressForPhoneNumber(address, phoneNumber) - } else { - if (data1 != null && data1 == address) { - Log.d( - "[Native Contact Editor] There is already an entry for this phone number and SIP address, skipping" - ) - } else { - Log.w( - "[Native Contact Editor] There is already an entry for this phone number ($phoneNumber) but not for the same SIP address ($data1 != $address)" - ) - updatePresenceLinphoneSipAddressForPhoneNumber(address, phoneNumber) - } - } - } - - private fun addPresenceLinphoneSipAddressForPhoneNumber(address: String, detail: String) { - val insert = ContentProviderOperation.newInsert(contactUri) - .withValue(ContactsContract.Data.RAW_CONTACT_ID, syncAccountRawId) - .withValue( - ContactsContract.Data.MIMETYPE, - AppUtils.getString(R.string.linphone_address_mime_type) - ) - .withValue("data1", address) // value - .withValue("data2", AppUtils.getString(R.string.app_name)) // summary - .withValue("data3", detail) // detail - .build() - addChanges(insert) - } - - private fun updatePresenceLinphoneSipAddressForPhoneNumber( - sipAddress: String, - phoneNumber: String - ) { - val update = ContentProviderOperation.newUpdate(contactUri) - .withSelection( - presenceUpdateSelection, - arrayOf( - friend.refKey, - AppUtils.getString(R.string.linphone_address_mime_type), - phoneNumber - ) - ) - .withValue( - ContactsContract.Data.MIMETYPE, - AppUtils.getString(R.string.linphone_address_mime_type) - ) - .withValue("data1", sipAddress) // value - .withValue("data2", AppUtils.getString(R.string.app_name)) // summary - .withValue("data3", phoneNumber) // detail - .build() - addChanges(update) - } - - private fun updatePicture() { - val value = pictureByteArray - val id = rawId - if (value == null || id == null) return - - try { - val uri = Uri.withAppendedPath( - ContentUris.withAppendedId(RawContacts.CONTENT_URI, id.toLong()), - RawContacts.DisplayPhoto.CONTENT_DIRECTORY - ) - val contentResolver = coreContext.context.contentResolver - val assetFileDescriptor = contentResolver.openAssetFileDescriptor(uri, "rw") - val outputStream = assetFileDescriptor?.createOutputStream() - outputStream?.write(value) - outputStream?.close() - assetFileDescriptor?.close() - Log.i("[Native Contact Editor] Picture updated") - } catch (e: Exception) { - Log.e("[Native Contact Editor] Failed to update picture, raised exception: $e") - } - - pictureByteArray = null - } -} diff --git a/app/src/main/java/org/linphone/core/BootReceiver.kt b/app/src/main/java/org/linphone/core/BootReceiver.kt deleted file mode 100644 index 951eacde0..000000000 --- a/app/src/main/java/org/linphone/core/BootReceiver.kt +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.core - -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import androidx.core.app.NotificationManagerCompat -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.compatibility.Compatibility -import org.linphone.core.tools.Log - -class BootReceiver : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - if (intent.action.equals(Intent.ACTION_BOOT_COMPLETED, ignoreCase = true)) { - val autoStart = corePreferences.autoStart - Log.i("[Boot Receiver] Device is starting, autoStart is $autoStart") - if (autoStart) { - startService(context) - } - } else if (intent.action.equals(Intent.ACTION_MY_PACKAGE_REPLACED, ignoreCase = true)) { - val autoStart = corePreferences.autoStart - Log.i("[Boot Receiver] App has been updated, autoStart is $autoStart") - if (autoStart) { - startService(context) - } - } - } - - private fun startService(context: Context) { - val serviceChannel = context.getString(R.string.notification_channel_service_id) - val notificationManager = NotificationManagerCompat.from(context) - if (Compatibility.getChannelImportance(notificationManager, serviceChannel) == NotificationManagerCompat.IMPORTANCE_NONE) { - Log.w("[Boot Receiver] Service channel is disabled!") - return - } - - val serviceIntent = Intent(Intent.ACTION_MAIN).setClass(context, CoreService::class.java) - serviceIntent.putExtra("StartForeground", true) - Compatibility.startForegroundService(context, serviceIntent) - } -} diff --git a/app/src/main/java/org/linphone/core/CoreContext.kt b/app/src/main/java/org/linphone/core/CoreContext.kt deleted file mode 100644 index 812642c77..000000000 --- a/app/src/main/java/org/linphone/core/CoreContext.kt +++ /dev/null @@ -1,1319 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.core - -import android.app.Application -import android.content.Context -import android.content.Intent -import android.content.SharedPreferences -import android.graphics.PixelFormat -import android.media.AudioDeviceCallback -import android.media.AudioDeviceInfo -import android.media.AudioManager -import android.os.Handler -import android.os.Looper -import android.security.keystore.KeyGenParameterSpec -import android.security.keystore.KeyProperties -import android.telephony.TelephonyManager -import android.util.Base64 -import android.util.Pair -import android.view.* -import android.webkit.MimeTypeMap -import androidx.lifecycle.* -import androidx.loader.app.LoaderManager -import com.google.firebase.crashlytics.FirebaseCrashlytics -import java.io.File -import java.math.BigInteger -import java.nio.charset.StandardCharsets -import java.security.KeyStore -import java.security.MessageDigest -import java.text.Collator -import java.util.* -import javax.crypto.Cipher -import javax.crypto.KeyGenerator -import javax.crypto.SecretKey -import javax.crypto.spec.GCMParameterSpec -import kotlin.math.abs -import kotlinx.coroutines.* -import org.linphone.BuildConfig -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.compatibility.Compatibility -import org.linphone.compatibility.PhoneStateInterface -import org.linphone.contact.ContactLoader -import org.linphone.contact.ContactsManager -import org.linphone.contact.getContactForPhoneNumberOrAddress -import org.linphone.core.tools.Log -import org.linphone.mediastream.Version -import org.linphone.notifications.NotificationsManager -import org.linphone.telecom.TelecomHelper -import org.linphone.utils.* -import org.linphone.utils.Event - -class CoreContext( - val context: Context, - coreConfig: Config, - service: CoreService? = null, - useAutoStartDescription: Boolean = false -) : - LifecycleOwner, ViewModelStoreOwner { - - private val _lifecycleRegistry = LifecycleRegistry(this) - override val lifecycle: Lifecycle - get() = _lifecycleRegistry - - private val _viewModelStore = ViewModelStore() - override val viewModelStore: ViewModelStore - get() = _viewModelStore - - private val contactLoader = ContactLoader() - - private val collator: Collator = Collator.getInstance() - - var stopped = false - val core: Core - val handler: Handler = Handler(Looper.getMainLooper()) - - var screenWidth: Float = 0f - var screenHeight: Float = 0f - - val appVersion: String by lazy { - val appVersion = BuildConfig.VERSION_NAME - val appBranch = context.getString(R.string.linphone_app_branch) - val appBuildType = BuildConfig.BUILD_TYPE - "$appVersion ($appBranch, $appBuildType)" - } - - val sdkVersion: String by lazy { - val sdkVersion = context.getString(org.linphone.core.R.string.linphone_sdk_version) - val sdkBranch = context.getString(org.linphone.core.R.string.linphone_sdk_branch) - val sdkBuildType = org.linphone.core.BuildConfig.BUILD_TYPE - "$sdkVersion ($sdkBranch, $sdkBuildType)" - } - - val contactsManager: ContactsManager by lazy { - ContactsManager(context) - } - - val notificationsManager: NotificationsManager by lazy { - NotificationsManager(context) - } - - val callErrorMessageResourceId: MutableLiveData> by lazy { - MutableLiveData>() - } - - val coroutineScope = CoroutineScope(Dispatchers.Main + SupervisorJob()) - - private val loggingService = Factory.instance().loggingService - - private var overlayX = 0f - private var overlayY = 0f - private var callOverlay: View? = null - private var previousCallState = Call.State.Idle - private lateinit var phoneStateListener: PhoneStateInterface - - private val activityMonitor = ActivityMonitor() - - private val listener: CoreListenerStub = object : CoreListenerStub() { - override fun onGlobalStateChanged(core: Core, state: GlobalState, message: String) { - Log.i("[Context] Global state changed [$state]") - if (state == GlobalState.On) { - if (corePreferences.disableVideo) { - // if video has been disabled, don't forget to tell the Core to disable it as well - Log.w( - "[Context] Video has been disabled in app, disabling it as well in the Core" - ) - core.isVideoCaptureEnabled = false - core.isVideoDisplayEnabled = false - - val videoPolicy = core.videoActivationPolicy - videoPolicy.automaticallyInitiate = false - videoPolicy.automaticallyAccept = false - core.videoActivationPolicy = videoPolicy - } - - fetchContacts() - } - } - - override fun onAccountRegistrationStateChanged( - core: Core, - account: Account, - state: RegistrationState?, - message: String - ) { - Log.i( - "[Context] Account [${account.params.identityAddress?.asStringUriOnly()}] registration state changed [$state]" - ) - if (state == RegistrationState.Ok && account == core.defaultAccount) { - notificationsManager.stopForegroundNotificationIfPossible() - } - } - - override fun onPushNotificationReceived(core: Core, payload: String?) { - Log.i("[Context] Push notification received: $payload") - } - - override fun onCallStateChanged( - core: Core, - call: Call, - state: Call.State, - message: String - ) { - Log.i("[Context] Call state changed [$state]") - if (state == Call.State.IncomingReceived || state == Call.State.IncomingEarlyMedia) { - if (declineCallDueToGsmActiveCall()) { - call.decline(Reason.Busy) - return - } - - // Starting SDK 24 (Android 7.0) we rely on the fullscreen intent of the call incoming notification - if (Version.sdkStrictlyBelow(Version.API24_NOUGAT_70)) { - onIncomingReceived() - } - - if (corePreferences.autoAnswerEnabled) { - val autoAnswerDelay = corePreferences.autoAnswerDelay - if (autoAnswerDelay == 0) { - Log.w("[Context] Auto answering call immediately") - answerCall(call) - } else { - Log.i( - "[Context] Scheduling auto answering in $autoAnswerDelay milliseconds" - ) - handler.postDelayed( - { - Log.w("[Context] Auto answering call") - answerCall(call) - }, - autoAnswerDelay.toLong() - ) - } - } - } else if (state == Call.State.OutgoingProgress) { - val conferenceInfo = core.findConferenceInformationFromUri(call.remoteAddress) - // Do not show outgoing call view for conference calls, wait for connected state - if (conferenceInfo == null) { - onOutgoingStarted() - } - - if (core.callsNb == 1 && corePreferences.routeAudioToBluetoothIfAvailable) { - AudioRouteUtils.routeAudioToBluetooth(call) - } - } else if (state == Call.State.Connected) { - onCallStarted() - } else if (state == Call.State.StreamsRunning) { - if (previousCallState == Call.State.Connected) { - // Do not automatically route audio to bluetooth after first call - if (core.callsNb == 1) { - // Only try to route bluetooth / headphone / headset when the call is in StreamsRunning for the first time - Log.i( - "[Context] First call going into StreamsRunning state for the first time, trying to route audio to headset or bluetooth if available" - ) - if (AudioRouteUtils.isHeadsetAudioRouteAvailable()) { - AudioRouteUtils.routeAudioToHeadset(call) - } else if (corePreferences.routeAudioToBluetoothIfAvailable && AudioRouteUtils.isBluetoothAudioRouteAvailable()) { - AudioRouteUtils.routeAudioToBluetooth(call) - } - } - - // Only start call recording when the call is in StreamsRunning for the first time - if (corePreferences.automaticallyStartCallRecording && !call.params.isRecording) { - if (call.conference == null) { // TODO: FIXME: We disabled conference recording for now - Log.i( - "[Context] We were asked to start the call recording automatically" - ) - call.startRecording() - } - } - } - } else if (state == Call.State.End || state == Call.State.Error || state == Call.State.Released) { - if (state == Call.State.Error) { - Log.w( - "[Context] Call error reason is ${call.errorInfo.protocolCode} / ${call.errorInfo.reason} / ${call.errorInfo.phrase}" - ) - val toastMessage = when (call.errorInfo.reason) { - Reason.Busy -> context.getString(R.string.call_error_user_busy) - Reason.IOError -> context.getString(R.string.call_error_io_error) - Reason.NotAcceptable -> context.getString( - R.string.call_error_incompatible_media_params - ) - Reason.NotFound -> context.getString(R.string.call_error_user_not_found) - Reason.ServerTimeout -> context.getString( - R.string.call_error_server_timeout - ) - Reason.TemporarilyUnavailable -> context.getString( - R.string.call_error_temporarily_unavailable - ) - else -> context.getString(R.string.call_error_generic).format( - "${call.errorInfo.protocolCode} / ${call.errorInfo.phrase}" - ) - } - callErrorMessageResourceId.value = Event(toastMessage) - } else if (state == Call.State.End && - call.dir == Call.Dir.Outgoing && - call.errorInfo.reason == Reason.Declined && - core.callsNb == 0 - ) { - Log.i("[Context] Call has been declined") - val toastMessage = context.getString(R.string.call_error_declined) - callErrorMessageResourceId.value = Event(toastMessage) - } - } - - previousCallState = state - } - - override fun onLastCallEnded(core: Core) { - Log.i("[Context] Last call has ended") - removeCallOverlay() - if (!core.isMicEnabled) { - Log.w("[Context] Mic was muted in Core, enabling it back for next call") - core.isMicEnabled = true - } - } - - override fun onMessagesReceived( - core: Core, - chatRoom: ChatRoom, - messages: Array - ) { - for (message in messages) { - exportFileInMessage(message) - } - } - } - - private val loggingServiceListener = object : LoggingServiceListenerStub() { - override fun onLogMessageWritten( - logService: LoggingService, - domain: String, - level: LogLevel, - message: String - ) { - if (corePreferences.logcatLogsOutput) { - when (level) { - LogLevel.Error -> android.util.Log.e(domain, message) - LogLevel.Warning -> android.util.Log.w(domain, message) - LogLevel.Message -> android.util.Log.i(domain, message) - LogLevel.Fatal -> android.util.Log.wtf(domain, message) - else -> android.util.Log.d(domain, message) - } - } - FirebaseCrashlytics.getInstance().log("[$domain] [${level.name}] $message") - } - } - - init { - if (context.resources.getBoolean(R.bool.crashlytics_enabled)) { - loggingService.addListener(loggingServiceListener) - Log.i("[Context] Crashlytics enabled, register logging service listener") - } - - _lifecycleRegistry.currentState = Lifecycle.State.INITIALIZED - - Log.i("=========================================") - Log.i("==== Linphone-android information dump ====") - Log.i("VERSION=${BuildConfig.VERSION_NAME} / ${BuildConfig.VERSION_CODE}") - Log.i("PACKAGE=${BuildConfig.APPLICATION_ID}") - Log.i("BUILD TYPE=${BuildConfig.BUILD_TYPE}") - Log.i("=========================================") - - if (service != null) { - Log.i("[Context] Starting foreground service") - notificationsManager.startForegroundToKeepAppAlive(service, useAutoStartDescription) - } - - core = Factory.instance().createCoreWithConfig(coreConfig, context) - - stopped = false - _lifecycleRegistry.currentState = Lifecycle.State.CREATED - - (context as Application).registerActivityLifecycleCallbacks(activityMonitor) - Log.i("[Context] Ready") - } - - private val audioDeviceCallback = object : AudioDeviceCallback() { - override fun onAudioDevicesAdded(addedDevices: Array?) { - if (!addedDevices.isNullOrEmpty()) { - Log.i("[Context] [${addedDevices.size}] new device(s) have been added") - core.reloadSoundDevices() - } - } - - override fun onAudioDevicesRemoved(removedDevices: Array?) { - if (!removedDevices.isNullOrEmpty()) { - Log.i("[Context] [${removedDevices.size}] existing device(s) have been removed") - core.reloadSoundDevices() - } - } - } - - fun start() { - Log.i("[Context] Starting") - - core.addListener(listener) - - // CoreContext listener must be added first! - if (Version.sdkAboveOrEqual(Version.API26_O_80) && corePreferences.useTelecomManager) { - if (Compatibility.hasTelecomManagerPermissions(context)) { - Log.i( - "[Context] Creating Telecom Helper, disabling audio focus requests in AudioHelper" - ) - core.config.setBool("audio", "android_disable_audio_focus_requests", true) - val telecomHelper = TelecomHelper.required(context) - Log.i( - "[Context] Telecom Helper created, account is ${if (telecomHelper.isAccountEnabled()) "enabled" else "disabled"}" - ) - } else { - Log.w("[Context] Can't create Telecom Helper, permissions have been revoked") - corePreferences.useTelecomManager = false - } - } - - configureCore() - - core.start() - _lifecycleRegistry.currentState = Lifecycle.State.STARTED - - initPhoneStateListener() - - notificationsManager.onCoreReady() - - collator.strength = Collator.NO_DECOMPOSITION - - if (corePreferences.vfsEnabled) { - val notClearedCount = FileUtils.countFilesInDirectory(corePreferences.vfsCachePath) - if (notClearedCount > 0) { - Log.w( - "[Context] [VFS] There are [$notClearedCount] plain files not cleared from previous app lifetime, removing them now" - ) - } - FileUtils.clearExistingPlainFiles() - } - - if (corePreferences.keepServiceAlive) { - Log.i("[Context] Background mode setting is enabled, starting Service") - notificationsManager.startForeground() - } - - _lifecycleRegistry.currentState = Lifecycle.State.RESUMED - - val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager - audioManager.registerAudioDeviceCallback(audioDeviceCallback, handler) - Log.i("[Context] Started") - } - - fun stop() { - Log.i("[Context] Stopping") - coroutineScope.cancel() - - if (::phoneStateListener.isInitialized) { - phoneStateListener.destroy() - } - notificationsManager.destroy() - contactsManager.destroy() - if (TelecomHelper.exists()) { - Log.i("[Context] Destroying telecom helper") - TelecomHelper.get().destroy() - TelecomHelper.destroy() - } - - val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager - audioManager.unregisterAudioDeviceCallback(audioDeviceCallback) - - core.stop() - core.removeListener(listener) - stopped = true - _lifecycleRegistry.currentState = Lifecycle.State.DESTROYED - loggingService.removeListener(loggingServiceListener) - - (context as Application).unregisterActivityLifecycleCallbacks(activityMonitor) - } - - fun onForeground() { - // We can't rely on defaultAccount?.params?.isPublishEnabled - // as it will be modified by the SDK when changing the presence status - if (corePreferences.publishPresence) { - Log.i("[Context] App is in foreground, PUBLISHING presence as Online") - core.consolidatedPresence = ConsolidatedPresence.Online - } - } - - fun onBackground() { - // We can't rely on defaultAccount?.params?.isPublishEnabled - // as it will be modified by the SDK when changing the presence status - if (corePreferences.publishPresence) { - Log.i("[Context] App is in background, un-PUBLISHING presence info") - // We don't use ConsolidatedPresence.Busy but Offline to do an unsubscribe, - // Flexisip will handle the Busy status depending on other devices - core.consolidatedPresence = ConsolidatedPresence.Offline - } - } - - private fun configureCore() { - Log.i("[Context] Configuring Core") - - core.staticPicture = corePreferences.staticPicturePath - - // Migration code - if (core.config.getBool("app", "incoming_call_vibration", true)) { - core.isVibrationOnIncomingCallEnabled = true - core.config.setBool("app", "incoming_call_vibration", false) - } - - if (core.config.getInt("misc", "conference_layout", 1) > 1) { - core.config.setInt("misc", "conference_layout", 1) - } - - // Now LIME server URL is set on accounts - val limeServerUrl = core.limeX3DhServerUrl.orEmpty() - if (limeServerUrl.isNotEmpty()) { - Log.w("[Context] Removing LIME X3DH server URL from Core config") - core.limeX3DhServerUrl = null - } - - // Disable Telecom Manager on Android < 10 to prevent crash due to OS bug in Android 9 - if (Version.sdkStrictlyBelow(Version.API29_ANDROID_10)) { - if (corePreferences.useTelecomManager) { - Log.w( - "[Context] Android < 10 detected, disabling telecom manager to prevent crash due to OS bug" - ) - } - corePreferences.useTelecomManager = false - corePreferences.manuallyDisabledTelecomManager = true - } - - initUserCertificates() - - computeUserAgent() - - val fiveTwoMigrationRequired = core.config.getBool("app", "migration_5.2_required", true) - if (fiveTwoMigrationRequired) { - Log.i( - "[Context] Starting migration of muted chat room from shared preferences to our SDK" - ) - val sharedPreferences: SharedPreferences = context.getSharedPreferences( - "notifications", - Context.MODE_PRIVATE - ) - val editor = sharedPreferences.edit() - - for (chatRoom in core.chatRooms) { - val id = LinphoneUtils.getChatRoomId(chatRoom) - if (sharedPreferences.getBoolean(id, false)) { - Log.i("[Context] Migrating muted flag for chat room [$id]") - chatRoom.muted = true - editor.remove(id) - } - } - - editor.apply() - core.config.setBool( - "app", - "migration_5.2_required", - false - ) - Log.i("[Context] Migration of muted chat room finished") - - core.setVideoCodecPriorityPolicy(CodecPriorityPolicy.Auto) - Log.i("[Context] Video codec priority policy updated to Auto") - } - - val fiveOneMigrationRequired = core.config.getBool("app", "migration_5.1_required", true) - if (fiveOneMigrationRequired) { - core.config.setBool( - "sip", - "update_presence_model_timestamp_before_publish_expires_refresh", - true - ) - } - - for (account in core.accountList) { - if (account.params.identityAddress?.domain == corePreferences.defaultDomain) { - var paramsChanged = false - val params = account.params.clone() - - if (fiveOneMigrationRequired) { - val newExpire = 31536000 // 1 year - if (account.params.expires != newExpire) { - Log.i( - "[Context] Updating expire on account ${params.identityAddress?.asString()} from ${account.params.expires} to newExpire" - ) - params.expires = newExpire - paramsChanged = true - } - - // Enable presence publish/subscribe for new feature - if (!account.params.isPublishEnabled) { - Log.i( - "[Context] Enabling presence publish on account ${params.identityAddress?.asString()}" - ) - params.isPublishEnabled = true - params.publishExpires = 120 - paramsChanged = true - } - } - - // Ensure conference factory URI is set on sip.linphone.org accounts - if (account.params.conferenceFactoryUri == null) { - val uri = corePreferences.conferenceServerUri - Log.i( - "[Context] Setting conference factory on account ${params.identityAddress?.asString()} to default value: $uri" - ) - params.conferenceFactoryUri = uri - paramsChanged = true - } - - // Ensure audio/video conference factory URI is set on sip.linphone.org accounts - if (account.params.audioVideoConferenceFactoryAddress == null) { - val uri = corePreferences.audioVideoConferenceServerUri - val address = core.interpretUrl(uri, false) - if (address != null) { - Log.i( - "[Context] Setting audio/video conference factory on account ${params.identityAddress?.asString()} to default value: $uri" - ) - params.audioVideoConferenceFactoryAddress = address - paramsChanged = true - } else { - Log.e("[Context] Failed to parse audio/video conference factory URI: $uri") - } - } - - // Enable Bundle mode by default - if (!account.params.isRtpBundleEnabled) { - Log.i( - "[Context] Enabling RTP bundle mode on account ${params.identityAddress?.asString()}" - ) - params.isRtpBundleEnabled = true - paramsChanged = true - } - - // Ensure we allow CPIM messages in basic chat rooms - if (!account.params.isCpimInBasicChatRoomEnabled) { - params.isCpimInBasicChatRoomEnabled = true - paramsChanged = true - Log.i( - "[Context] CPIM allowed in basic chat rooms for account ${params.identityAddress?.asString()}" - ) - } - - if (account.params.limeServerUrl.isNullOrEmpty()) { - if (limeServerUrl.isNotEmpty()) { - params.limeServerUrl = limeServerUrl - paramsChanged = true - Log.i( - "[Context] Moving Core's LIME X3DH server URL [$limeServerUrl] on account ${params.identityAddress?.asString()}" - ) - } else { - params.limeServerUrl = corePreferences.limeServerUrl - paramsChanged = true - Log.w( - "[Context] Linphone account [${params.identityAddress?.asString()}] didn't have a LIME X3DH server URL, setting one: ${corePreferences.limeServerUrl}" - ) - } - } - - if (paramsChanged) { - Log.i("[Context] Account params have been updated, apply changes") - account.params = params - } - } - } - core.config.setBool("app", "migration_5.1_required", false) - - Log.i("[Context] Core configured") - } - - private fun computeUserAgent() { - val deviceName: String = corePreferences.deviceName - val appName: String = context.resources.getString(R.string.user_agent_app_name) - val androidVersion = BuildConfig.VERSION_NAME - val userAgent = "$appName/$androidVersion ($deviceName) LinphoneSDK" - val sdkVersion = context.getString(org.linphone.core.R.string.linphone_sdk_version) - val sdkBranch = context.getString(org.linphone.core.R.string.linphone_sdk_branch) - val sdkUserAgent = "$sdkVersion ($sdkBranch)" - core.setUserAgent(userAgent, sdkUserAgent) - } - - private fun initUserCertificates() { - val userCertsPath = corePreferences.userCertificatesPath - val f = File(userCertsPath) - if (!f.exists()) { - if (!f.mkdir()) { - Log.e("[Context] $userCertsPath can't be created.") - } - } - core.userCertificatesPath = userCertsPath - } - - fun fetchContacts() { - if (corePreferences.enableNativeAddressBookIntegration) { - if (PermissionHelper.required(context).hasReadContactsPermission()) { - Log.i("[Context] Init contacts loader") - val manager = LoaderManager.getInstance(this@CoreContext) - manager.restartLoader(0, null, contactLoader) - } - } - } - - fun newAccountConfigured(isLinphoneAccount: Boolean) { - Log.i( - "[Context] A new ${if (isLinphoneAccount) AppUtils.getString(R.string.app_name) else "third-party"} account has been configured" - ) - - if (isLinphoneAccount) { - core.config.setString("sip", "rls_uri", corePreferences.defaultRlsUri) - val rlsAddress = core.interpretUrl(corePreferences.defaultRlsUri, false) - if (rlsAddress != null) { - for (friendList in core.friendsLists) { - friendList.rlsAddress = rlsAddress - } - } - if (core.mediaEncryption == MediaEncryption.None) { - Log.i("[Context] Enabling SRTP media encryption instead of None") - core.mediaEncryption = MediaEncryption.SRTP - } - } else { - Log.i("[Context] Background mode with foreground service automatically enabled") - corePreferences.keepServiceAlive = true - notificationsManager.startForeground() - } - - contactsManager.updateLocalContacts() - } - - /* Call related functions */ - - fun initPhoneStateListener() { - if (PermissionHelper.required(context).hasReadPhoneStatePermission()) { - try { - phoneStateListener = - Compatibility.createPhoneListener( - context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager - ) - } catch (exception: SecurityException) { - val hasReadPhoneStatePermission = - PermissionHelper.get().hasReadPhoneStateOrPhoneNumbersPermission() - Log.e( - "[Context] Failed to create phone state listener: $exception, READ_PHONE_STATE permission status is $hasReadPhoneStatePermission" - ) - } - } else { - Log.w( - "[Context] Can't create phone state listener, READ_PHONE_STATE permission isn't granted" - ) - } - } - - fun declineCallDueToGsmActiveCall(): Boolean { - if (!corePreferences.useTelecomManager) { // Can't use the following call with Telecom Manager API as it will "fake" GSM calls - var gsmCallActive = false - if (::phoneStateListener.isInitialized) { - gsmCallActive = phoneStateListener.isInCall() - } - - if (gsmCallActive) { - Log.w("[Context] Refusing the call with reason busy because a GSM call is active") - return true - } - } else { - if (TelecomHelper.exists()) { - if (!TelecomHelper.get().isIncomingCallPermitted() || - TelecomHelper.get().isInManagedCall() - ) { - Log.w( - "[Context] Refusing the call with reason busy because Telecom Manager will reject the call" - ) - return true - } - } else { - Log.e("[Context] Telecom Manager singleton wasn't created!") - } - } - return false - } - - fun videoUpdateRequestTimedOut(call: Call) { - coroutineScope.launch { - Log.w("[Context] 30 seconds have passed, declining video request") - answerCallVideoUpdateRequest(call, false) - } - } - - fun answerCallVideoUpdateRequest(call: Call, accept: Boolean) { - val params = core.createCallParams(call) - - if (accept) { - params?.isVideoEnabled = true - core.isVideoCaptureEnabled = true - core.isVideoDisplayEnabled = true - } else { - params?.isVideoEnabled = false - } - - call.acceptUpdate(params) - } - - fun answerCall(call: Call) { - Log.i("[Context] Answering call $call") - val params = core.createCallParams(call) - if (params == null) { - Log.w("[Context] Answering call without params!") - call.accept() - return - } - - params.recordFile = LinphoneUtils.getRecordingFilePathForAddress(call.remoteAddress) - - if (LinphoneUtils.checkIfNetworkHasLowBandwidth(context)) { - Log.w("[Context] Enabling low bandwidth mode!") - params.isLowBandwidthEnabled = true - } - - if (call.callLog.wasConference()) { - // Prevent incoming group call to start in audio only layout - // Do the same as the conference waiting room - params.isVideoEnabled = true - params.videoDirection = if (core.videoActivationPolicy.automaticallyInitiate) MediaDirection.SendRecv else MediaDirection.RecvOnly - Log.i( - "[Context] Enabling video on call params to prevent audio-only layout when answering" - ) - } - - call.acceptWithParams(params) - } - - fun declineCall(call: Call) { - val voiceMailUri = corePreferences.voiceMailUri - if (voiceMailUri != null && corePreferences.redirectDeclinedCallToVoiceMail) { - val voiceMailAddress = core.interpretUrl(voiceMailUri, false) - if (voiceMailAddress != null) { - Log.i("[Context] Redirecting call $call to voice mail URI: $voiceMailUri") - call.redirectTo(voiceMailAddress) - } - } else { - val reason = if (core.callsNb > 1) { - Reason.Busy - } else { - Reason.Declined - } - Log.i("[Context] Declining call [$call] with reason [$reason]") - call.decline(reason) - } - } - - fun terminateCall(call: Call) { - Log.i("[Context] Terminating call $call") - call.terminate() - } - - fun transferCallTo(addressToCall: String): Boolean { - val currentCall = core.currentCall ?: core.calls.firstOrNull() - if (currentCall == null) { - Log.e("[Context] Couldn't find a call to transfer") - } else { - val address = core.interpretUrl(addressToCall, LinphoneUtils.applyInternationalPrefix()) - if (address != null) { - Log.i("[Context] Transferring current call to $addressToCall") - currentCall.transferTo(address) - return true - } - } - return false - } - - fun startCall(to: String) { - var stringAddress = to.trim() - if (android.util.Patterns.PHONE.matcher(to).matches()) { - val contact = contactsManager.findContactByPhoneNumber(to) - val alias = contact?.getContactForPhoneNumberOrAddress(to) - if (alias != null) { - Log.i("[Context] Found matching alias $alias for phone number $to, using it") - stringAddress = alias - } - } - - val address: Address? = core.interpretUrl( - stringAddress, - LinphoneUtils.applyInternationalPrefix() - ) - if (address == null) { - Log.e("[Context] Failed to parse $stringAddress, abort outgoing call") - callErrorMessageResourceId.value = Event( - context.getString(R.string.call_error_network_unreachable) - ) - return - } - - startCall(address) - } - - fun startCall( - address: Address, - callParams: CallParams? = null, - forceZRTP: Boolean = false, - localAddress: Address? = null - ) { - if (!core.isNetworkReachable) { - Log.e("[Context] Network unreachable, abort outgoing call") - callErrorMessageResourceId.value = Event( - context.getString(R.string.call_error_network_unreachable) - ) - return - } - - val params = callParams ?: core.createCallParams(null) - if (params == null) { - val call = core.inviteAddress(address) - Log.w("[Context] Starting call $call without params") - return - } - - if (forceZRTP) { - params.mediaEncryption = MediaEncryption.ZRTP - } - if (LinphoneUtils.checkIfNetworkHasLowBandwidth(context)) { - Log.w("[Context] Enabling low bandwidth mode!") - params.isLowBandwidthEnabled = true - } - params.recordFile = LinphoneUtils.getRecordingFilePathForAddress(address) - - if (localAddress != null) { - val account = core.accountList.find { account -> - account.params.identityAddress?.weakEqual(localAddress) ?: false - } - if (account != null) { - params.account = account - Log.i( - "[Context] Using account matching address ${localAddress.asStringUriOnly()} as From" - ) - } else { - Log.e( - "[Context] Failed to find account matching address ${localAddress.asStringUriOnly()}" - ) - } - } - - if (corePreferences.sendEarlyMedia) { - params.isEarlyMediaSendingEnabled = true - } - - val call = core.inviteAddressWithParams(address, params) - Log.i("[Context] Starting call $call") - } - - fun switchCamera() { - val currentDevice = core.videoDevice - Log.i("[Context] Current camera device is $currentDevice") - - for (camera in core.videoDevicesList) { - if (camera != currentDevice && camera != "StaticImage: Static picture") { - Log.i("[Context] New camera device will be $camera") - core.videoDevice = camera - break - } - } - - val conference = core.conference - if (conference == null || !conference.isIn) { - val call = core.currentCall - if (call == null) { - Log.w("[Context] Switching camera while not in call") - return - } - call.update(null) - } - } - - fun showSwitchCameraButton(): Boolean { - return !corePreferences.disableVideo && core.videoDevicesList.size > 2 // Count StaticImage camera - } - - fun createCallOverlay() { - if (!corePreferences.showCallOverlay || !corePreferences.systemWideCallOverlay || callOverlay != null) { - return - } - - if (overlayY == 0f) overlayY = AppUtils.pixelsToDp(40f) - val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager - // WRAP_CONTENT doesn't work well on some launchers... - val params: WindowManager.LayoutParams = WindowManager.LayoutParams( - AppUtils.getDimension(R.dimen.call_overlay_size).toInt(), - AppUtils.getDimension(R.dimen.call_overlay_size).toInt(), - Compatibility.getOverlayType(), - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, - PixelFormat.TRANSLUCENT - ) - params.x = overlayX.toInt() - params.y = overlayY.toInt() - params.gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL - val overlay = LayoutInflater.from(context).inflate(R.layout.call_overlay, null) - - var initX = overlayX - var initY = overlayY - overlay.setOnTouchListener { view, event -> - when (event.action) { - MotionEvent.ACTION_DOWN -> { - initX = params.x - event.rawX - initY = params.y - event.rawY - } - MotionEvent.ACTION_MOVE -> { - val x = (event.rawX + initX).toInt() - val y = (event.rawY + initY).toInt() - - params.x = x - params.y = y - windowManager.updateViewLayout(overlay, params) - } - MotionEvent.ACTION_UP -> { - if (abs(overlayX - params.x) < CorePreferences.OVERLAY_CLICK_SENSITIVITY && - abs(overlayY - params.y) < CorePreferences.OVERLAY_CLICK_SENSITIVITY - ) { - view.performClick() - } - overlayX = params.x.toFloat() - overlayY = params.y.toFloat() - } - else -> return@setOnTouchListener false - } - true - } - overlay.setOnClickListener { - onCallOverlayClick() - } - - try { - windowManager.addView(overlay, params) - callOverlay = overlay - } catch (e: Exception) { - Log.e("[Context] Failed to add overlay in windowManager: $e") - } - } - - fun onCallOverlayClick() { - val call = core.currentCall ?: core.calls.firstOrNull() - if (call != null) { - Log.i("[Context] Overlay clicked, go back to call view") - when (call.state) { - Call.State.IncomingReceived, Call.State.IncomingEarlyMedia -> onIncomingReceived() - Call.State.OutgoingInit, Call.State.OutgoingProgress, Call.State.OutgoingRinging, Call.State.OutgoingEarlyMedia -> onOutgoingStarted() - else -> onCallStarted() - } - } else { - Log.e("[Context] Couldn't find call, why is the overlay clicked?!") - } - } - - fun removeCallOverlay() { - if (callOverlay != null) { - val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager - windowManager.removeView(callOverlay) - callOverlay = null - } - } - - /* Coroutine related */ - - private fun exportFileInMessage(message: ChatMessage) { - // Only do it if auto download feature isn't disabled, otherwise it's done in the user-initiated download process - if (core.maxSizeForAutoDownloadIncomingFiles != -1) { - var hasFile = false - for (content in message.contents) { - if (content.isFile) { - hasFile = true - break - } - } - if (hasFile) { - exportFilesInMessageToMediaStore(message) - } - } - } - - private fun exportFilesInMessageToMediaStore(message: ChatMessage) { - if (message.isEphemeral) { - Log.w("[Context] Do not make ephemeral file(s) public") - return - } - if (corePreferences.vfsEnabled) { - Log.w("[Context] [VFS] Do not make received file(s) public when VFS is enabled") - return - } - if (!corePreferences.makePublicMediaFilesDownloaded) { - Log.w("[Context] Making received files public setting disabled") - return - } - - if (PermissionHelper.get().hasWriteExternalStoragePermission()) { - for (content in message.contents) { - if (content.isFile && content.filePath != null && content.userData == null) { - Log.i("[Context] Trying to export file [${content.name}] to MediaStore") - addContentToMediaStore(content) - } - } - } else { - Log.e( - "[Context] Can't make file public, app doesn't have WRITE_EXTERNAL_STORAGE permission" - ) - } - } - - fun addContentToMediaStore(content: Content) { - if (corePreferences.vfsEnabled) { - Log.w("[Context] [VFS] Do not make received file(s) public when VFS is enabled") - return - } - if (!corePreferences.makePublicMediaFilesDownloaded) { - Log.w("[Context] Making received files public setting disabled") - return - } - - if (PermissionHelper.get().hasWriteExternalStoragePermission()) { - coroutineScope.launch { - val filePath = content.filePath.orEmpty() - Log.i("[Context] Trying to export file [$filePath] through Media Store API") - - val extension = FileUtils.getExtensionFromFileName(filePath) - val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) - when (FileUtils.getMimeType(mime)) { - FileUtils.MimeType.Image -> { - if (Compatibility.addImageToMediaStore(context, content)) { - Log.i( - "[Context] Successfully exported image [${content.name}] to Media Store" - ) - } else { - Log.e( - "[Context] Something went wrong while copying file to Media Store..." - ) - } - } - FileUtils.MimeType.Video -> { - if (Compatibility.addVideoToMediaStore(context, content)) { - Log.i( - "[Context] Successfully exported video [${content.name}] to Media Store" - ) - } else { - Log.e( - "[Context] Something went wrong while copying file to Media Store..." - ) - } - } - FileUtils.MimeType.Audio -> { - if (Compatibility.addAudioToMediaStore(context, content)) { - Log.i( - "[Context] Successfully exported audio [${content.name}] to Media Store" - ) - } else { - Log.e( - "[Context] Something went wrong while copying file to Media Store..." - ) - } - } - else -> { - Log.w( - "[Context] File [$filePath] isn't either an image, an audio file or a video [${content.type}/${content.subtype}], can't add it to the Media Store" - ) - } - } - } - } - } - - fun checkIfForegroundServiceNotificationCanBeRemovedAfterDelay(delayInMs: Long) { - coroutineScope.launch { - withContext(Dispatchers.Default) { - delay(delayInMs) - withContext(Dispatchers.Main) { - if (core.defaultAccount != null && core.defaultAccount?.state == RegistrationState.Ok) { - Log.i( - "[Context] Default account is registered, cancel foreground service notification if possible" - ) - notificationsManager.stopForegroundNotificationIfPossible() - } - } - } - } - } - - /* Start call related activities */ - - private fun onIncomingReceived() { - if (corePreferences.preventInterfaceFromShowingUp) { - Log.w("[Context] We were asked to not show the incoming call screen") - return - } - - Log.i("[Context] Starting IncomingCallActivity") - val intent = Intent(context, org.linphone.activities.voip.CallActivity::class.java) - // This flag is required to start an Activity from a Service context - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) - context.startActivity(intent) - } - - private fun onOutgoingStarted() { - if (corePreferences.preventInterfaceFromShowingUp) { - Log.w("[Context] We were asked to not show the outgoing call screen") - return - } - - Log.i("[Context] Starting OutgoingCallActivity") - val intent = Intent(context, org.linphone.activities.voip.CallActivity::class.java) - // This flag is required to start an Activity from a Service context - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) - context.startActivity(intent) - } - - fun onCallStarted() { - if (corePreferences.preventInterfaceFromShowingUp) { - Log.w("[Context] We were asked to not show the call screen") - return - } - - Log.i("[Context] Starting CallActivity") - val intent = Intent(context, org.linphone.activities.voip.CallActivity::class.java) - // This flag is required to start an Activity from a Service context - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) - context.startActivity(intent) - } - - /* VFS */ - - companion object { - private const val TRANSFORMATION = "AES/GCM/NoPadding" - private const val ANDROID_KEY_STORE = "AndroidKeyStore" - private const val ALIAS = "vfs" - private const val LINPHONE_VFS_ENCRYPTION_AES256GCM128_SHA256 = 2 - private const val VFS_IV = "vfsiv" - private const val VFS_KEY = "vfskey" - - @Throws(java.lang.Exception::class) - private fun generateSecretKey() { - val keyGenerator = - KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE) - keyGenerator.init( - KeyGenParameterSpec.Builder( - ALIAS, - KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT - ) - .setBlockModes(KeyProperties.BLOCK_MODE_GCM) - .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) - .build() - ) - keyGenerator.generateKey() - } - - @Throws(java.lang.Exception::class) - private fun getSecretKey(): SecretKey? { - val ks = KeyStore.getInstance(ANDROID_KEY_STORE) - ks.load(null) - val entry = ks.getEntry(ALIAS, null) as KeyStore.SecretKeyEntry - return entry.secretKey - } - - @Throws(java.lang.Exception::class) - fun generateToken(): String { - return sha512(UUID.randomUUID().toString()) - } - - @Throws(java.lang.Exception::class) - private fun encryptData(textToEncrypt: String): Pair { - val cipher = Cipher.getInstance(TRANSFORMATION) - cipher.init(Cipher.ENCRYPT_MODE, getSecretKey()) - val iv = cipher.iv - return Pair( - iv, - cipher.doFinal(textToEncrypt.toByteArray(StandardCharsets.UTF_8)) - ) - } - - @Throws(java.lang.Exception::class) - private fun decryptData(encrypted: String?, encryptionIv: ByteArray): String { - val cipher = Cipher.getInstance(TRANSFORMATION) - val spec = GCMParameterSpec(128, encryptionIv) - cipher.init(Cipher.DECRYPT_MODE, getSecretKey(), spec) - val encryptedData = Base64.decode(encrypted, Base64.DEFAULT) - return String(cipher.doFinal(encryptedData), StandardCharsets.UTF_8) - } - - @Throws(java.lang.Exception::class) - fun encryptToken(string_to_encrypt: String): Pair { - val encryptedData = encryptData(string_to_encrypt) - return Pair( - Base64.encodeToString(encryptedData.first, Base64.DEFAULT), - Base64.encodeToString(encryptedData.second, Base64.DEFAULT) - ) - } - - @Throws(java.lang.Exception::class) - fun sha512(input: String): String { - val md = MessageDigest.getInstance("SHA-512") - val messageDigest = md.digest(input.toByteArray()) - val no = BigInteger(1, messageDigest) - var hashtext = no.toString(16) - while (hashtext.length < 32) { - hashtext = "0$hashtext" - } - return hashtext - } - - @Throws(java.lang.Exception::class) - fun getVfsKey(sharedPreferences: SharedPreferences): String { - val keyStore = KeyStore.getInstance(ANDROID_KEY_STORE) - keyStore.load(null) - return decryptData( - sharedPreferences.getString(VFS_KEY, null), - Base64.decode(sharedPreferences.getString(VFS_IV, null), Base64.DEFAULT) - ) - } - - fun activateVFS() { - try { - Log.i("[Context] [VFS] Activating VFS") - val preferences = corePreferences.encryptedSharedPreferences - if (preferences == null) { - Log.e("[Context] [VFS] Can't get encrypted SharedPreferences, can't init VFS") - return - } - - if (preferences.getString(VFS_IV, null) == null) { - generateSecretKey() - encryptToken(generateToken()).let { data -> - preferences - .edit() - .putString(VFS_IV, data.first) - .putString(VFS_KEY, data.second) - .commit() - } - } - Factory.instance().setVfsEncryption( - LINPHONE_VFS_ENCRYPTION_AES256GCM128_SHA256, - getVfsKey(preferences).toByteArray().copyOfRange(0, 32), - 32 - ) - - Log.i("[Context] [VFS] VFS activated") - } catch (e: Exception) { - Log.f("[Context] [VFS] Unable to activate VFS encryption: $e") - } - } - } -} diff --git a/app/src/main/java/org/linphone/core/CorePreferences.kt b/app/src/main/java/org/linphone/core/CorePreferences.kt deleted file mode 100644 index e65c90932..000000000 --- a/app/src/main/java/org/linphone/core/CorePreferences.kt +++ /dev/null @@ -1,769 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.core - -import android.content.Context -import android.content.SharedPreferences -import androidx.security.crypto.EncryptedSharedPreferences -import androidx.security.crypto.MasterKey -import java.io.File -import java.io.FileInputStream -import java.io.FileOutputStream -import java.security.KeyStoreException -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.compatibility.Compatibility -import org.linphone.core.tools.Log -import org.linphone.utils.LinphoneUtils - -class CorePreferences constructor(private val context: Context) { - private var _config: Config? = null - var config: Config - get() = _config ?: coreContext.core.config - set(value) { - _config = value - } - - /* VFS encryption */ - - companion object { - const val OVERLAY_CLICK_SENSITIVITY = 10 - - private const val encryptedSharedPreferencesFile = "encrypted.pref" - } - - val encryptedSharedPreferences: SharedPreferences? by lazy { - val masterKey: MasterKey = MasterKey.Builder( - context, - MasterKey.DEFAULT_MASTER_KEY_ALIAS - ).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build() - - try { - EncryptedSharedPreferences.create( - context, - encryptedSharedPreferencesFile, - masterKey, - EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, - EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM - ) - } catch (kse: KeyStoreException) { - Log.e("[VFS] Keystore exception: $kse") - null - } catch (e: Exception) { - Log.e("[VFS] Exception: $e") - null - } - } - - var vfsEnabled: Boolean - get() = encryptedSharedPreferences?.getBoolean("vfs_enabled", false) ?: false - set(value) { - val preferences = encryptedSharedPreferences - if (preferences == null) { - Log.e("[VFS] Failed to get encrypted SharedPreferences") - return - } - - if (!value && preferences.getBoolean("vfs_enabled", false)) { - Log.w("[VFS] It is not possible to disable VFS once it has been enabled") - return - } - - preferences.edit().putBoolean("vfs_enabled", value)?.apply() - // When VFS is enabled we disable logcat output for linphone logs - // TODO: decide if we do it - // logcatLogsOutput = false - } - - /* App settings */ - - var debugLogs: Boolean - get() = config.getBool("app", "debug", org.linphone.BuildConfig.DEBUG) - set(value) { - config.setBool("app", "debug", value) - } - - var logcatLogsOutput: Boolean - get() = config.getBool("app", "print_logs_into_logcat", true) - set(value) { - config.setBool("app", "print_logs_into_logcat", value) - } - - var autoStart: Boolean - get() = config.getBool("app", "auto_start", true) - set(value) { - config.setBool("app", "auto_start", value) - } - - var keepServiceAlive: Boolean - get() = config.getBool("app", "keep_service_alive", false) - set(value) { - config.setBool("app", "keep_service_alive", value) - } - - var readAndAgreeTermsAndPrivacy: Boolean - get() = config.getBool("app", "read_and_agree_terms_and_privacy", false) - set(value) { - config.setBool("app", "read_and_agree_terms_and_privacy", value) - } - - /* UI */ - - var forcePortrait: Boolean - get() = config.getBool("app", "force_portrait_orientation", false) - set(value) { - config.setBool("app", "force_portrait_orientation", value) - } - - var replaceSipUriByUsername: Boolean - get() = config.getBool("app", "replace_sip_uri_by_username", false) - set(value) { - config.setBool("app", "replace_sip_uri_by_username", value) - } - - var enableAnimations: Boolean - get() = config.getBool("app", "enable_animations", false) - set(value) { - config.setBool("app", "enable_animations", value) - } - - /** -1 means auto, 0 no, 1 yes */ - var darkMode: Int - get() { - if (!darkModeAllowed) return 0 - return config.getInt("app", "dark_mode", -1) - } - set(value) { - config.setInt("app", "dark_mode", value) - } - - /** Allow to make screenshots of encrypted chat rooms, disables fragment's secure mode */ - var disableSecureMode: Boolean - get() = config.getBool("app", "disable_fragment_secure_mode", false) - set(value) { - config.setBool("app", "disable_fragment_secure_mode", value) - } - - /* Audio */ - - /* Video */ - - var videoPreview: Boolean - get() = config.getBool("app", "video_preview", false) - set(value) = config.setBool("app", "video_preview", value) - - /* Chat */ - - var preventMoreThanOneFilePerMessage: Boolean - get() = config.getBool("app", "prevent_more_than_one_file_per_message", false) - set(value) { - config.setBool("app", "prevent_more_than_one_file_per_message", value) - } - - var markAsReadUponChatMessageNotificationDismissal: Boolean - get() = config.getBool("app", "mark_as_read_notif_dismissal", false) - set(value) { - config.setBool("app", "mark_as_read_notif_dismissal", value) - } - - var makePublicMediaFilesDownloaded: Boolean - // Keep old name for backward compatibility - get() = config.getBool("app", "make_downloaded_images_public_in_gallery", true) - set(value) { - config.setBool("app", "make_downloaded_images_public_in_gallery", value) - } - - var useInAppFileViewerForNonEncryptedFiles: Boolean - get() = config.getBool("app", "use_in_app_file_viewer_for_non_encrypted_files", false) - set(value) { - config.setBool("app", "use_in_app_file_viewer_for_non_encrypted_files", value) - } - - var hideChatMessageContentInNotification: Boolean - get() = config.getBool("app", "hide_chat_message_content_in_notification", false) - set(value) { - config.setBool("app", "hide_chat_message_content_in_notification", value) - } - - var hideEmptyRooms: Boolean - get() = config.getBool("misc", "hide_empty_chat_rooms", true) - set(value) { - config.setBool("misc", "hide_empty_chat_rooms", value) - } - - var hideRoomsFromRemovedProxies: Boolean - get() = config.getBool("misc", "hide_chat_rooms_from_removed_proxies", true) - set(value) { - config.setBool("misc", "hide_chat_rooms_from_removed_proxies", value) - } - - var deviceName: String - get() = config.getString("app", "device_name", Compatibility.getDeviceName(context))!! - set(value) = config.setString("app", "device_name", value) - - var chatRoomShortcuts: Boolean - get() = config.getBool("app", "chat_room_shortcuts", true) - set(value) { - config.setBool("app", "chat_room_shortcuts", value) - } - - /* Voice Recordings */ - - var voiceRecordingMaxDuration: Int - get() = config.getInt("app", "voice_recording_max_duration", 600000) // in ms - set(value) = config.setInt("app", "voice_recording_max_duration", value) - - var holdToRecordVoiceMessage: Boolean - get() = config.getBool("app", "voice_recording_hold_and_release_mode", false) - set(value) = config.setBool("app", "voice_recording_hold_and_release_mode", value) - - var sendVoiceRecordingRightAway: Boolean - get() = config.getBool("app", "voice_recording_send_right_away", false) - set(value) = config.setBool("app", "voice_recording_send_right_away", value) - - /* Contacts */ - - var storePresenceInNativeContact: Boolean - get() = config.getBool("app", "store_presence_in_native_contact", false) - set(value) { - config.setBool("app", "store_presence_in_native_contact", value) - } - - var showNewContactAccountDialog: Boolean - get() = config.getBool("app", "show_new_contact_account_dialog", true) - set(value) { - config.setBool("app", "show_new_contact_account_dialog", value) - } - - var displayOrganization: Boolean - get() = config.getBool("app", "display_contact_organization", contactOrganizationVisible) - set(value) { - config.setBool("app", "display_contact_organization", value) - } - - var contactsShortcuts: Boolean - get() = config.getBool("app", "contact_shortcuts", false) - set(value) { - config.setBool("app", "contact_shortcuts", value) - } - - var publishPresence: Boolean - get() = config.getBool("app", "publish_presence", true) - set(value) { - config.setBool("app", "publish_presence", value) - } - - /* Call */ - - var sendEarlyMedia: Boolean - get() = config.getBool("sip", "outgoing_calls_early_media", false) - set(value) { - config.setBool("sip", "outgoing_calls_early_media", value) - } - - var acceptEarlyMedia: Boolean - get() = config.getBool("sip", "incoming_calls_early_media", false) - set(value) { - config.setBool("sip", "incoming_calls_early_media", value) - } - - var autoAnswerEnabled: Boolean - get() = config.getBool("app", "auto_answer", false) - set(value) { - config.setBool("app", "auto_answer", value) - } - - var autoAnswerDelay: Int - get() = config.getInt("app", "auto_answer_delay", 0) - set(value) { - config.setInt("app", "auto_answer_delay", value) - } - - // Show overlay inside of application - var showCallOverlay: Boolean - get() = config.getBool("app", "call_overlay", true) - set(value) { - config.setBool("app", "call_overlay", value) - } - - // Show overlay even when app is in background, requires permission - var systemWideCallOverlay: Boolean - get() = config.getBool("app", "system_wide_call_overlay", false) - set(value) { - config.setBool("app", "system_wide_call_overlay", value) - } - - var callRightAway: Boolean - get() = config.getBool("app", "call_right_away", false) - set(value) { - config.setBool("app", "call_right_away", value) - } - - // Will send user to contacts list directly - var skipDialerForNewCallAndTransfer: Boolean - get() = config.getBool("app", "skip_dialer_for_new_call_and_transfer", false) - set(value) { - config.setBool("app", "skip_dialer_for_new_call_and_transfer", value) - } - - var automaticallyStartCallRecording: Boolean - get() = config.getBool("app", "auto_start_call_record", false) - set(value) { - config.setBool("app", "auto_start_call_record", value) - } - - var useTelecomManager: Boolean - // Some permissions are required, so keep it to false so user has to manually enable it and give permissions - get() = config.getBool("app", "use_self_managed_telecom_manager", false) - set(value) { - config.setBool("app", "use_self_managed_telecom_manager", value) - // We need to disable audio focus requests when enabling telecom manager, otherwise it creates conflicts - config.setBool("audio", "android_disable_audio_focus_requests", value) - } - - // We will try to auto enable Telecom Manager feature, but in case user disables it don't try again - var manuallyDisabledTelecomManager: Boolean - get() = config.getBool("app", "user_disabled_self_managed_telecom_manager", false) - set(value) { - config.setBool("app", "user_disabled_self_managed_telecom_manager", value) - } - - // Also uses Hearing Aids if available - var routeAudioToBluetoothIfAvailable: Boolean - get() = config.getBool("app", "route_audio_to_bluetooth_if_available", true) - set(value) { - config.setBool("app", "route_audio_to_bluetooth_if_available", value) - } - - // This won't be done if bluetooth or wired headset is used - var routeAudioToSpeakerWhenVideoIsEnabled: Boolean - get() = config.getBool("app", "route_audio_to_speaker_when_video_enabled", true) - set(value) { - config.setBool("app", "route_audio_to_speaker_when_video_enabled", value) - } - - // Automatically handled by SDK - var pauseCallsWhenAudioFocusIsLost: Boolean - get() = config.getBool("audio", "android_pause_calls_when_audio_focus_lost", true) - set(value) { - config.setBool("audio", "android_pause_calls_when_audio_focus_lost", value) - } - - var enableFullScreenWhenJoiningVideoCall: Boolean - get() = config.getBool("app", "enter_video_call_enable_full_screen_mode", false) - set(value) { - config.setBool("app", "enter_video_call_enable_full_screen_mode", value) - } - - var enableFullScreenWhenJoiningVideoConference: Boolean - get() = config.getBool("app", "enter_video_conference_enable_full_screen_mode", true) - set(value) { - config.setBool("app", "enter_video_conference_enable_full_screen_mode", value) - } - - var disableBroadcastConference: Boolean - get() = config.getBool("app", "disable_broadcast_conference_feature", true) - set(value) { - config.setBool("app", "disable_broadcast_conference_feature", value) - } - - /* Assistant */ - - var firstStart: Boolean - get() = config.getBool("app", "first_start", true) - set(value) { - config.setBool("app", "first_start", value) - } - - var xmlRpcServerUrl: String? - get() = config.getString("assistant", "xmlrpc_url", null) - set(value) { - config.setString("assistant", "xmlrpc_url", value) - } - - var hideLinkPhoneNumber: Boolean - get() = config.getBool("app", "hide_link_phone_number", false) - set(value) { - config.setBool("app", "hide_link_phone_number", value) - } - - /* Dialog related */ - - var limeSecurityPopupEnabled: Boolean - get() = config.getBool("app", "lime_security_popup_enabled", true) - set(value) { - config.setBool("app", "lime_security_popup_enabled", value) - } - - /* Other */ - - var voiceMailUri: String? - get() = config.getString("app", "voice_mail", null) - set(value) { - config.setString("app", "voice_mail", value) - } - - var redirectDeclinedCallToVoiceMail: Boolean - get() = config.getBool("app", "redirect_declined_call_to_voice_mail", true) - set(value) { - config.setBool("app", "redirect_declined_call_to_voice_mail", value) - } - - var lastUpdateAvailableCheckTimestamp: Int - get() = config.getInt("app", "version_check_url_last_timestamp", 0) - set(value) { - config.setInt("app", "version_check_url_last_timestamp", value) - } - - var defaultAccountAvatarPath: String? - get() = config.getString("app", "default_avatar_path", null) - set(value) { - config.setString("app", "default_avatar_path", value) - } - - /* *** Read only application settings, some were previously in non_localizable_custom *** */ - - /* UI related */ - - val contactOrganizationVisible: Boolean - get() = config.getBool("app", "display_contact_organization", true) - - private val darkModeAllowed: Boolean - get() = config.getBool("app", "dark_mode_allowed", true) - - /* Feature related */ - - val showScreenshotButton: Boolean - get() = config.getBool("app", "show_take_screenshot_button_in_call", false) - - val dtmfKeypadVibration: Boolean - get() = config.getBool("app", "dtmf_keypad_vibraton", false) - - val allowMultipleFilesAndTextInSameMessage: Boolean - get() = config.getBool("app", "allow_multiple_files_and_text_in_same_message", true) - - val enableNativeAddressBookIntegration: Boolean - get() = config.getBool("app", "enable_native_address_book", true) - - val fetchContactsFromDefaultDirectory: Boolean - get() = config.getBool("app", "fetch_contacts_from_default_directory", true) - - val delayBeforeShowingContactsSearchSpinner: Int - get() = config.getInt("app", "delay_before_showing_contacts_search_spinner", 200) - - // From Android Contact APIs we can also retrieve the internationalized phone number - // By default we display the same value as the native address book app - val preferNormalizedPhoneNumbersFromAddressBook: Boolean - get() = config.getBool("app", "prefer_normalized_phone_numbers_from_address_book", false) - - val hideStaticImageCamera: Boolean - get() = config.getBool("app", "hide_static_image_camera", true) - - // Will prevent user adding contact and editing / removing existing contacts - val readOnlyNativeContacts: Boolean - get() = config.getBool("app", "read_only_native_address_book", false) - - // Will hide the contacts selector to allow listing all contacts, even those without a SIP address - val onlyShowSipContactsList: Boolean - get() = config.getBool("app", "only_show_sip_contacts_list", false) - - // Will hide the SIP contacts selector, leaving only the all contacts list - val hideSipContactsList: Boolean - get() = config.getBool("app", "hide_sip_contacts_list", false) - - // Will disable chat feature completely - val disableChat: Boolean - get() = config.getBool("app", "disable_chat_feature", false) - - // Will disable video feature completely - val disableVideo: Boolean - get() = config.getBool("app", "disable_video_feature", false) - - val forceEndToEndEncryptedChat: Boolean - get() = config.getBool("app", "force_lime_chat_rooms", false) - - // Turning this ON will show the secure chat button even if there is no LIME capability in presence (or no presence) - val allowEndToEndEncryptedChatWithoutPresence: Boolean - get() = config.getBool("app", "allow_lime_friend_without_capability", false) - - val showEmojiPickerButton: Boolean - get() = config.getBool("app", "show_emoji_picker", true) - - // This will prevent UI from showing up, except for the launcher & the foreground service notification - val preventInterfaceFromShowingUp: Boolean - get() = config.getBool("app", "keep_app_invisible", false) - - // By default we will record voice messages using MKV format and Opus audio encoding - // If disabled, WAV format will be used instead. Warning: files will be heavier! - val voiceMessagesFormatMkv: Boolean - get() = config.getBool("app", "record_voice_messages_in_mkv_format", true) - - val useEphemeralPerDeviceMode: Boolean - get() = config.getBool("app", "ephemeral_chat_messages_settings_per_device", true) - - // If enabled user will see all ringtones bundled in our SDK - // and will be able to choose which one to use if not using it's device's default - val showAllRingtones: Boolean - get() = config.getBool("app", "show_all_available_ringtones", false) - - val showContactInviteBySms: Boolean - get() = config.getBool("app", "show_invite_contact_by_sms", true) - - val autoRemoteProvisioningOnConfigUriHandler: Boolean - get() = config.getBool("app", "auto_apply_provisioning_config_uri_handler", false) - - val askForAccountPasswordToAccessSettings: Boolean - get() = config.getBool("app", "require_password_to_access_settings", false) - - /* Default values related */ - - val defaultDomain: String - get() = config.getString("app", "default_domain", "sip.linphone.org")!! - - val defaultRlsUri: String - get() = config.getString("sip", "rls_uri", "sips:rls@sip.linphone.org")!! - - val debugPopupCode: String - get() = config.getString("app", "debug_popup_magic", "#1234#")!! - - // If there is more participants than this value in a conference, force ActiveSpeaker layout - val maxConferenceParticipantsForMosaicLayout: Int - get() = config.getInt("app", "conference_mosaic_layout_max_participants", 6) - - val conferenceServerUri: String - get() = config.getString( - "app", - "default_conference_factory_uri", - "sip:conference-factory@sip.linphone.org" - )!! - - val audioVideoConferenceServerUri: String - get() = config.getString( - "app", - "default_audio_video_conference_factory_uri", - "sip:videoconference-factory@sip.linphone.org" - )!! - - val limeServerUrl: String - get() = config.getString( - "app", - "default_lime_server_url", - "https://lime.linphone.org/lime-server/lime-server.php" - )!! - - val checkUpdateAvailableInterval: Int - get() = config.getInt("app", "version_check_interval", 86400000) - - /* Assistant */ - - val showCreateAccount: Boolean - get() = config.getBool("app", "assistant_create_account", true) - - val showLinphoneLogin: Boolean - get() = config.getBool("app", "assistant_linphone_login", true) - - val showGenericLogin: Boolean - get() = config.getBool("app", "assistant_generic_login", true) - - val showRemoteProvisioning: Boolean - get() = config.getBool("app", "assistant_remote_provisioning", true) - - /* Side Menu */ - - val showAccountsInSideMenu: Boolean - get() = config.getBool("app", "side_menu_accounts", true) - - val showAssistantInSideMenu: Boolean - get() = config.getBool("app", "side_menu_assistant", true) - - val showSettingsInSideMenu: Boolean - get() = config.getBool("app", "side_menu_settings", true) - - val showRecordingsInSideMenu: Boolean - get() = config.getBool("app", "side_menu_recordings", true) - - val showScheduledConferencesInSideMenu: Boolean - get() = config.getBool( - "app", - "side_menu_conferences", - LinphoneUtils.isRemoteConferencingAvailable() - ) - - val showAboutInSideMenu: Boolean - get() = config.getBool("app", "side_menu_about", true) - - val showQuitInSideMenu: Boolean - get() = config.getBool("app", "side_menu_quit", true) - - /* Settings */ - - val allowDtlsTransport: Boolean - get() = config.getBool("app", "allow_dtls_transport", false) - - val showAccountSettings: Boolean - get() = config.getBool("app", "settings_accounts", true) - - val showTunnelSettings: Boolean - get() = config.getBool("app", "settings_tunnel", true) - - val showAudioSettings: Boolean - get() = config.getBool("app", "settings_audio", true) - - val showVideoSettings: Boolean - get() = config.getBool("app", "settings_video", !disableVideo) - - val showCallSettings: Boolean - get() = config.getBool("app", "settings_call", true) - - val showChatSettings: Boolean - get() = config.getBool("app", "settings_chat", !disableChat) - - val showNetworkSettings: Boolean - get() = config.getBool("app", "settings_network", true) - - val showContactsSettings: Boolean - get() = config.getBool("app", "settings_contacts", true) - - val showAdvancedSettings: Boolean - get() = config.getBool("app", "settings_advanced", true) - - val showConferencesSettings: Boolean - get() = config.getBool( - "app", - "settings_conferences", - LinphoneUtils.isRemoteConferencingAvailable() - ) - - /* Assets stuff */ - - val configPath: String - get() = context.filesDir.absolutePath + "/.linphonerc" - - val factoryConfigPath: String - get() = context.filesDir.absolutePath + "/linphonerc" - - val linphoneDefaultValuesPath: String - get() = context.filesDir.absolutePath + "/assistant_linphone_default_values" - - val defaultValuesPath: String - get() = context.filesDir.absolutePath + "/assistant_default_values" - - val ringtonesPath: String - get() = context.filesDir.absolutePath + "/share/sounds/linphone/rings/" - - val defaultRingtonePath: String - get() = ringtonesPath + "notes_of_the_optimistic.mkv" - - val userCertificatesPath: String - get() = context.filesDir.absolutePath + "/user-certs" - - val staticPicturePath: String - get() = context.filesDir.absolutePath + "/share/images/nowebcamcif.jpg" - - val vfsCachePath: String - get() = context.cacheDir.absolutePath + "/evfs/" - - fun copyAssetsFromPackage() { - copy("linphonerc_default", configPath) - copy("linphonerc_factory", factoryConfigPath, true) - copy("assistant_linphone_default_values", linphoneDefaultValuesPath, true) - copy("assistant_default_values", defaultValuesPath, true) - - move( - context.filesDir.absolutePath + "/linphone-log-history.db", - context.filesDir.absolutePath + "/call-history.db" - ) - move( - context.filesDir.absolutePath + "/zrtp_secrets", - context.filesDir.absolutePath + "/zrtp-secrets.db" - ) - } - - fun getString(resource: Int): String { - return context.getString(resource) - } - - private fun copy(from: String, to: String, overrideIfExists: Boolean = false) { - val outFile = File(to) - if (outFile.exists()) { - if (!overrideIfExists) { - android.util.Log.i( - context.getString(org.linphone.R.string.app_name), - "[Preferences] File $to already exists" - ) - return - } - } - android.util.Log.i( - context.getString(org.linphone.R.string.app_name), - "[Preferences] Overriding $to by $from asset" - ) - - val outStream = FileOutputStream(outFile) - val inFile = context.assets.open(from) - val buffer = ByteArray(1024) - var length: Int = inFile.read(buffer) - - while (length > 0) { - outStream.write(buffer, 0, length) - length = inFile.read(buffer) - } - - inFile.close() - outStream.flush() - outStream.close() - } - - private fun move(from: String, to: String, overrideIfExists: Boolean = false) { - val inFile = File(from) - val outFile = File(to) - if (inFile.exists()) { - if (outFile.exists() && !overrideIfExists) { - android.util.Log.w( - context.getString(org.linphone.R.string.app_name), - "[Preferences] Can't move [$from] to [$to], destination file already exists" - ) - } else { - val inStream = FileInputStream(inFile) - val outStream = FileOutputStream(outFile) - - val buffer = ByteArray(1024) - var read: Int - while (inStream.read(buffer).also { read = it } != -1) { - outStream.write(buffer, 0, read) - } - - inStream.close() - outStream.flush() - outStream.close() - - inFile.delete() - android.util.Log.i( - context.getString(org.linphone.R.string.app_name), - "[Preferences] Successfully moved [$from] to [$to]" - ) - } - } else { - android.util.Log.w( - context.getString(org.linphone.R.string.app_name), - "[Preferences] Can't move [$from] to [$to], source file doesn't exists" - ) - } - } -} diff --git a/app/src/main/java/org/linphone/core/CorePushReceiver.kt b/app/src/main/java/org/linphone/core/CorePushReceiver.kt deleted file mode 100644 index d771183df..000000000 --- a/app/src/main/java/org/linphone/core/CorePushReceiver.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.core - -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import org.linphone.LinphoneApplication.Companion.ensureCoreExists -import org.linphone.core.tools.Log - -class CorePushReceiver : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - ensureCoreExists(context.applicationContext, true) - Log.i("[Push Notification] Push notification has been received in broadcast receiver") - } -} diff --git a/app/src/main/java/org/linphone/core/CoreService.kt b/app/src/main/java/org/linphone/core/CoreService.kt deleted file mode 100644 index f2f715efb..000000000 --- a/app/src/main/java/org/linphone/core/CoreService.kt +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.core - -import android.content.Intent -import org.linphone.LinphoneApplication -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.LinphoneApplication.Companion.ensureCoreExists -import org.linphone.core.tools.Log -import org.linphone.core.tools.service.CoreService - -class CoreService : CoreService() { - override fun onCreate() { - super.onCreate() - Log.i("[Service] Created") - } - - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - Log.i("[Service] Starting, ensuring Core exists") - - if (corePreferences.keepServiceAlive) { - Log.i("[Service] Starting as foreground to keep app alive in background") - val contextCreated = ensureCoreExists( - applicationContext, - pushReceived = false, - service = this, - useAutoStartDescription = false - ) - if (!contextCreated) { - // Only start foreground notification if context already exists, otherwise context will do it itself - coreContext.notificationsManager.startForegroundToKeepAppAlive(this, false) - } - } else if (intent?.extras?.get("StartForeground") == true) { - Log.i("[Service] Starting as foreground due to device boot or app update") - val contextCreated = ensureCoreExists( - applicationContext, - pushReceived = false, - service = this, - useAutoStartDescription = true, - skipCoreStart = true - ) - if (contextCreated) { - coreContext.start() - } else { - // Only start foreground notification if context already exists, otherwise context will do it itself - coreContext.notificationsManager.startForegroundToKeepAppAlive(this, true) - } - coreContext.checkIfForegroundServiceNotificationCanBeRemovedAfterDelay(5000) - } else { - ensureCoreExists( - applicationContext, - pushReceived = false, - service = this, - useAutoStartDescription = false - ) - } - - coreContext.notificationsManager.serviceCreated(this) - - return super.onStartCommand(intent, flags, startId) - } - - override fun createServiceNotificationChannel() { - // Done elsewhere - } - - override fun showForegroundServiceNotification(isVideoCall: Boolean) { - Log.i("[Service] Starting service as foreground") - coreContext.notificationsManager.startCallForeground(this) - } - - override fun hideForegroundServiceNotification() { - Log.i("[Service] Stopping service as foreground") - coreContext.notificationsManager.stopCallForeground() - } - - override fun onTaskRemoved(rootIntent: Intent?) { - if (LinphoneApplication.contextExists()) { - if (coreContext.core.callsNb > 0) { - Log.w( - "[Service] Task removed but there is at least one active call, do not stop the Core!" - ) - } else if (!corePreferences.keepServiceAlive) { - if (coreContext.core.isInBackground) { - Log.i("[Service] Task removed, stopping Core") - coreContext.stop() - } else { - Log.w("[Service] Task removed but Core is not in background, skipping") - } - } else { - Log.i( - "[Service] Task removed but we were asked to keep the service alive, so doing nothing" - ) - } - } - - super.onTaskRemoved(rootIntent) - } - - override fun onDestroy() { - if (LinphoneApplication.contextExists()) { - Log.i("[Service] Stopping") - coreContext.notificationsManager.serviceDestroyed() - } - - super.onDestroy() - } -} diff --git a/app/src/main/java/org/linphone/notifications/NotificationBroadcastReceiver.kt b/app/src/main/java/org/linphone/notifications/NotificationBroadcastReceiver.kt deleted file mode 100644 index 6665f3f1c..000000000 --- a/app/src/main/java/org/linphone/notifications/NotificationBroadcastReceiver.kt +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.notifications - -import android.app.NotificationManager -import android.app.RemoteInput -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.ensureCoreExists -import org.linphone.core.Call -import org.linphone.core.Core -import org.linphone.core.tools.Log - -class NotificationBroadcastReceiver : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - Log.i("[Notification Broadcast Receiver] Ensuring Core exists") - ensureCoreExists(context.applicationContext, false) - - val notificationId = intent.getIntExtra(NotificationsManager.INTENT_NOTIF_ID, 0) - Log.i( - "[Notification Broadcast Receiver] Got notification broadcast for ID [$notificationId]" - ) - - if (intent.action == NotificationsManager.INTENT_REPLY_NOTIF_ACTION || intent.action == NotificationsManager.INTENT_MARK_AS_READ_ACTION) { - handleChatIntent(context, intent, notificationId) - } else if (intent.action == NotificationsManager.INTENT_ANSWER_CALL_NOTIF_ACTION || intent.action == NotificationsManager.INTENT_HANGUP_CALL_NOTIF_ACTION) { - handleCallIntent(intent) - } - } - - private fun handleChatIntent(context: Context, intent: Intent, notificationId: Int) { - val remoteSipAddress = intent.getStringExtra(NotificationsManager.INTENT_REMOTE_ADDRESS) - if (remoteSipAddress == null) { - Log.e( - "[Notification Broadcast Receiver] Remote SIP address is null for notification id $notificationId" - ) - return - } - val core: Core = coreContext.core - - val remoteAddress = core.interpretUrl(remoteSipAddress, false) - if (remoteAddress == null) { - Log.e( - "[Notification Broadcast Receiver] Couldn't interpret remote address $remoteSipAddress" - ) - return - } - - val localIdentity = intent.getStringExtra(NotificationsManager.INTENT_LOCAL_IDENTITY) - if (localIdentity == null) { - Log.e( - "[Notification Broadcast Receiver] Local identity is null for notification id $notificationId" - ) - return - } - val localAddress = core.interpretUrl(localIdentity, false) - if (localAddress == null) { - Log.e( - "[Notification Broadcast Receiver] Couldn't interpret local address $localIdentity" - ) - return - } - - val room = core.searchChatRoom(null, localAddress, remoteAddress, arrayOfNulls(0)) - if (room == null) { - Log.e( - "[Notification Broadcast Receiver] Couldn't find chat room for remote address $remoteSipAddress and local address $localIdentity" - ) - return - } - - if (intent.action == NotificationsManager.INTENT_REPLY_NOTIF_ACTION) { - val reply = getMessageText(intent)?.toString() - if (reply == null) { - Log.e("[Notification Broadcast Receiver] Couldn't get reply text") - return - } - - val msg = room.createMessageFromUtf8(reply) - msg.userData = notificationId - msg.addListener(coreContext.notificationsManager.chatListener) - msg.send() - Log.i("[Notification Broadcast Receiver] Reply sent for notif id $notificationId") - } else { - room.markAsRead() - if (!coreContext.notificationsManager.dismissChatNotification(room)) { - Log.w( - "[Notification Broadcast Receiver] Notifications Manager failed to cancel notification" - ) - val notificationManager = context.getSystemService(NotificationManager::class.java) - notificationManager.cancel(NotificationsManager.CHAT_TAG, notificationId) - } - } - } - - private fun handleCallIntent(intent: Intent) { - val remoteSipAddress = intent.getStringExtra(NotificationsManager.INTENT_REMOTE_ADDRESS) - if (remoteSipAddress == null) { - Log.e("[Notification Broadcast Receiver] Remote SIP address is null for notification") - return - } - - val core: Core = coreContext.core - - val remoteAddress = core.interpretUrl(remoteSipAddress, false) - val call = if (remoteAddress != null) core.getCallByRemoteAddress2(remoteAddress) else null - if (call == null) { - Log.e( - "[Notification Broadcast Receiver] Couldn't find call from remote address $remoteSipAddress" - ) - return - } - - if (intent.action == NotificationsManager.INTENT_ANSWER_CALL_NOTIF_ACTION) { - coreContext.answerCall(call) - } else { - if (call.state == Call.State.IncomingReceived || - call.state == Call.State.IncomingEarlyMedia - ) { - coreContext.declineCall(call) - } else { - coreContext.terminateCall(call) - } - } - } - - private fun getMessageText(intent: Intent): CharSequence? { - val remoteInput = RemoteInput.getResultsFromIntent(intent) - return remoteInput?.getCharSequence(NotificationsManager.KEY_TEXT_REPLY) - } -} diff --git a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt b/app/src/main/java/org/linphone/notifications/NotificationsManager.kt deleted file mode 100644 index d28715188..000000000 --- a/app/src/main/java/org/linphone/notifications/NotificationsManager.kt +++ /dev/null @@ -1,1385 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.notifications - -import android.annotation.SuppressLint -import android.app.Notification -import android.app.NotificationManager -import android.app.PendingIntent -import android.content.Context -import android.content.Intent -import android.graphics.Bitmap -import android.net.Uri -import android.os.Bundle -import android.webkit.MimeTypeMap -import androidx.core.app.NotificationCompat -import androidx.core.app.NotificationManagerCompat -import androidx.core.app.Person -import androidx.core.app.RemoteInput -import androidx.core.content.ContextCompat -import androidx.core.content.LocusIdCompat -import androidx.core.graphics.drawable.IconCompat -import androidx.navigation.NavDeepLinkBuilder -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.chat_bubble.ChatBubbleActivity -import org.linphone.activities.main.MainActivity -import org.linphone.activities.voip.CallActivity -import org.linphone.compatibility.Compatibility -import org.linphone.contact.getPerson -import org.linphone.contact.getThumbnailUri -import org.linphone.core.* -import org.linphone.core.tools.Log -import org.linphone.utils.* - -class Notifiable(val notificationId: Int) { - val messages: ArrayList = arrayListOf() - - var isGroup: Boolean = false - var groupTitle: String? = null - var localIdentity: String? = null - var myself: String? = null - var remoteAddress: String? = null -} - -class NotifiableMessage( - var message: String, - val friend: Friend?, - val sender: String, - val time: Long, - val senderAvatar: Bitmap? = null, - var filePath: Uri? = null, - var fileMime: String? = null, - val isOutgoing: Boolean = false, - val isReaction: Boolean = false, - val reactionToMessageId: String? = null, - val reactionFrom: String? = null -) - -class NotificationsManager(private val context: Context) { - companion object { - const val CHAT_NOTIFICATIONS_GROUP = "CHAT_NOTIF_GROUP" - const val KEY_TEXT_REPLY = "key_text_reply" - const val INTENT_NOTIF_ID = "NOTIFICATION_ID" - const val INTENT_REPLY_NOTIF_ACTION = "org.linphone.REPLY_ACTION" - const val INTENT_HANGUP_CALL_NOTIF_ACTION = "org.linphone.HANGUP_CALL_ACTION" - const val INTENT_ANSWER_CALL_NOTIF_ACTION = "org.linphone.ANSWER_CALL_ACTION" - const val INTENT_MARK_AS_READ_ACTION = "org.linphone.MARK_AS_READ_ACTION" - const val INTENT_LOCAL_IDENTITY = "LOCAL_IDENTITY" - const val INTENT_REMOTE_ADDRESS = "REMOTE_ADDRESS" - - private const val SERVICE_NOTIF_ID = 1 - private const val MISSED_CALLS_NOTIF_ID = 10 - - const val CHAT_TAG = "Chat" - private const val MISSED_CALL_TAG = "Missed call" - } - - private val notificationManager: NotificationManagerCompat by lazy { - NotificationManagerCompat.from(context) - } - private val chatNotificationsMap: HashMap = HashMap() - private val callNotificationsMap: HashMap = HashMap() - private val previousChatNotifications: ArrayList = arrayListOf() - private val chatBubbleNotifications: ArrayList = arrayListOf() - - private var currentForegroundServiceNotificationId: Int = 0 - private var serviceNotification: Notification? = null - - private var service: CoreService? = null - - var currentlyDisplayedChatRoomAddress: String? = null - - private val listener: CoreListenerStub = object : CoreListenerStub() { - override fun onCallStateChanged( - core: Core, - call: Call, - state: Call.State, - message: String - ) { - Log.i("[Notifications Manager] Call state changed [$state]") - - if (corePreferences.preventInterfaceFromShowingUp) { - Log.w("[Notifications Manager] We were asked to not show the call notifications") - return - } - - when (call.state) { - Call.State.IncomingEarlyMedia, Call.State.IncomingReceived -> { - if (service != null) { - Log.i( - "[Notifications Manager] Service isn't null, show incoming call notification" - ) - displayIncomingCallNotification(call, false) - } else { - Log.w("[Notifications Manager] No service found, waiting for it to start") - } - } - Call.State.End, Call.State.Error -> dismissCallNotification(call) - Call.State.Released -> { - if (LinphoneUtils.isCallLogMissed(call.callLog)) { - displayMissedCallNotification(call.remoteAddress) - } - } - Call.State.OutgoingInit, Call.State.OutgoingProgress, Call.State.OutgoingRinging -> { - displayCallNotification(call, false) - } - else -> displayCallNotification(call, true) - } - } - - override fun onMessagesReceived( - core: Core, - room: ChatRoom, - messages: Array - ) { - Log.i("[Notifications Manager] Received ${messages.size} aggregated messages") - if (corePreferences.disableChat) return - - if (corePreferences.preventInterfaceFromShowingUp) { - Log.w("[Notifications Manager] We were asked to not show the chat notifications") - return - } - - if (currentlyDisplayedChatRoomAddress == room.peerAddress.asStringUriOnly()) { - Log.i( - "[Notifications Manager] Chat room is currently displayed, do not notify received message" - ) - // Mark as read is now done in the DetailChatRoomFragment - return - } - - if (room.muted) { - val id = LinphoneUtils.getChatRoomId(room.localAddress, room.peerAddress) - Log.i("[Notifications Manager] Chat room $id has been muted") - return - } - - if (corePreferences.chatRoomShortcuts) { - if (ShortcutsHelper.isShortcutToChatRoomAlreadyCreated(context, room)) { - Log.i("[Notifications Manager] Chat room shortcut already exists") - } else { - Log.i( - "[Notifications Manager] Ensure chat room shortcut exists for bubble notification" - ) - ShortcutsHelper.createShortcutsToChatRooms(context) - } - } - - val notifiable = getNotifiableForRoom(room) - val updated = updateChatNotifiableWithMessages(notifiable, room, messages) - if (!updated) { - Log.w( - "[Notifications Manager] No changes made to notifiable, do not display it again" - ) - return - } - - if (notifiable.messages.isNotEmpty()) { - displayChatNotifiable(room, notifiable) - } else { - Log.w( - "[Notifications Manager] No message to display in received aggregated messages" - ) - } - } - - override fun onReactionRemoved( - core: Core, - chatRoom: ChatRoom, - message: ChatMessage, - address: Address - ) { - Log.i( - "[Notifications Manager] [${address.asStringUriOnly()}] removed it's previously sent reaction for chat message [$message]" - ) - if (corePreferences.disableChat) return - - if (chatRoom.muted) { - val id = LinphoneUtils.getChatRoomId(chatRoom.localAddress, chatRoom.peerAddress) - Log.i("[Notifications Manager] Chat room $id has been muted") - return - } - - val chatRoomPeerAddress = chatRoom.peerAddress.asStringUriOnly() - var notifiable: Notifiable? = chatNotificationsMap[chatRoomPeerAddress] - if (notifiable == null) { - Log.i( - "[Notifications Manager] No notification for chat room [$chatRoomPeerAddress], nothing to do" - ) - return - } - - val from = address.asStringUriOnly() - val found = notifiable.messages.find { - it.isReaction && it.reactionToMessageId == message.messageId && it.reactionFrom == from - } - if (found != null) { - if (notifiable.messages.remove(found)) { - if (notifiable.messages.isNotEmpty()) { - Log.i( - "[Notifications Manager] After removing original reaction notification there is still messages, updating notification" - ) - displayChatNotifiable(chatRoom, notifiable) - } else { - Log.i( - "[Notifications Manager] After removing original reaction notification there is nothing left to display, remove notification" - ) - notificationManager.cancel(CHAT_TAG, notifiable.notificationId) - } - } - } else { - Log.w( - "[Notifications Manager] Original reaction not found in currently displayed notification" - ) - } - } - - override fun onNewMessageReaction( - core: Core, - chatRoom: ChatRoom, - message: ChatMessage, - reaction: ChatMessageReaction - ) { - val address = reaction.fromAddress - val defaultAccountAddress = core.defaultAccount?.params?.identityAddress - // Do not notify our own reactions, it won't be done anyway since the chat room is very likely to be currently displayed - if (defaultAccountAddress != null && defaultAccountAddress.weakEqual(address)) return - - Log.i( - "[Notifications Manager] Reaction received [${reaction.body}] from [${address.asStringUriOnly()}] for chat message [$message]" - ) - if (corePreferences.disableChat) return - - if (corePreferences.preventInterfaceFromShowingUp) { - Log.w("[Notifications Manager] We were asked to not show the chat notifications") - return - } - - if (currentlyDisplayedChatRoomAddress == chatRoom.peerAddress.asStringUriOnly()) { - Log.i( - "[Notifications Manager] Chat room is currently displayed, do not notify received reaction" - ) - // Mark as read is now done in the DetailChatRoomFragment - return - } - - if (chatRoom.muted) { - val id = LinphoneUtils.getChatRoomId(chatRoom.localAddress, chatRoom.peerAddress) - Log.i("[Notifications Manager] Chat room $id has been muted") - return - } - - if (coreContext.contactsManager.isAddressMyself(address)) { - Log.i( - "[Notifications Manager] Reaction has been sent by ourselves, do not notify it" - ) - return - } - - if (corePreferences.chatRoomShortcuts) { - if (ShortcutsHelper.isShortcutToChatRoomAlreadyCreated(context, chatRoom)) { - Log.i("[Notifications Manager] Chat room shortcut already exists") - } else { - Log.i( - "[Notifications Manager] Ensure chat room shortcut exists for bubble notification" - ) - ShortcutsHelper.createShortcutsToChatRooms(context) - } - } - - val newNotifiable = createChatReactionNotifiable( - chatRoom, - reaction.body, - address, - message - ) - if (newNotifiable.messages.isNotEmpty()) { - displayChatNotifiable(chatRoom, newNotifiable) - } else { - Log.e( - "[Notifications Manager] Notifiable is empty but we should have displayed the reaction!" - ) - } - } - - override fun onChatRoomRead(core: Core, chatRoom: ChatRoom) { - val address = chatRoom.peerAddress.asStringUriOnly() - val notifiable = chatNotificationsMap[address] - if (notifiable != null) { - if (chatBubbleNotifications.contains(notifiable.notificationId)) { - Log.i( - "[Notifications Manager] Chat room [$chatRoom] has been marked as read, not removing notification because of a chat bubble" - ) - } else { - Log.i( - "[Notifications Manager] Chat room [$chatRoom] has been marked as read, removing notification if any" - ) - dismissChatNotification(chatRoom) - } - } else { - val notificationId = getNotificationIdForChat(chatRoom) - if (chatBubbleNotifications.contains(notificationId)) { - Log.i( - "[Notifications Manager] Chat room [$chatRoom] has been marked as read but no notifiable found, not removing notification because of a chat bubble" - ) - } else { - Log.i( - "[Notifications Manager] Chat room [$chatRoom] has been marked as read but no notifiable found, removing notification if any" - ) - dismissChatNotification(chatRoom) - } - } - } - - override fun onLastCallEnded(core: Core) { - Log.i( - "[Notifications Manager] Last call ended, make sure foreground service is stopped and notification removed" - ) - stopCallForeground() - } - } - - val chatListener: ChatMessageListener = object : ChatMessageListenerStub() { - override fun onMsgStateChanged(message: ChatMessage, state: ChatMessage.State) { - message.userData ?: return - val id = message.userData as Int - Log.i("[Notifications Manager] Reply message state changed [$state] for id $id") - - if (state != ChatMessage.State.InProgress) { - // No need to be called here twice - message.removeListener(this) - } - - if (state == ChatMessage.State.Delivered || state == ChatMessage.State.Displayed) { - val address = message.chatRoom.peerAddress.asStringUriOnly() - val notifiable = chatNotificationsMap[address] - if (notifiable != null) { - if (notifiable.notificationId != id) { - Log.w( - "[Notifications Manager] ID doesn't match: ${notifiable.notificationId} != $id" - ) - } - displayReplyMessageNotification(message, notifiable) - } else { - Log.e( - "[Notifications Manager] Couldn't find notification for chat room $address" - ) - cancel(id, CHAT_TAG) - } - } else if (state == ChatMessage.State.NotDelivered) { - Log.e("[Notifications Manager] Reply wasn't delivered") - cancel(id, CHAT_TAG) - } - } - } - - init { - Compatibility.createNotificationChannels(context, notificationManager) - - val manager = context.getSystemService(NotificationManager::class.java) as NotificationManager - for (notification in manager.activeNotifications) { - if (notification.tag.isNullOrEmpty()) { // We use null tag for call notifications otherwise it will create duplicates when used with Service.startForeground()... - Log.w( - "[Notifications Manager] Found existing call? notification [${notification.id}], cancelling it" - ) - manager.cancel(notification.id) - } else if (notification.tag == CHAT_TAG) { - Log.i( - "[Notifications Manager] Found existing chat notification [${notification.id}]" - ) - previousChatNotifications.add(notification.id) - } - } - } - - fun onCoreReady() { - coreContext.core.addListener(listener) - } - - fun destroy() { - // Don't use cancelAll to keep message notifications ! - // When a message is received by a push, it will create a CoreService - // but it might be getting killed quite quickly after that - // causing the notification to be missed by the user... - Log.i( - "[Notifications Manager] Getting destroyed, clearing foreground Service & call notifications" - ) - - if (currentForegroundServiceNotificationId > 0 && !corePreferences.keepServiceAlive) { - Log.i("[Notifications Manager] Clearing foreground Service") - stopForegroundNotification() - } - - if (callNotificationsMap.size > 0) { - Log.i("[Notifications Manager] Clearing call notifications") - for (notifiable in callNotificationsMap.values) { - notificationManager.cancel(notifiable.notificationId) - } - } - - coreContext.core.removeListener(listener) - } - - @SuppressLint("MissingPermission") - private fun notify(id: Int, notification: Notification, tag: String? = null) { - if (!PermissionHelper.get().hasPostNotificationsPermission()) { - Log.w( - "[Notifications Manager] Can't notify [$id] with tag [$tag], POST_NOTIFICATIONS permission isn't granted!" - ) - return - } - - Log.i("[Notifications Manager] Notifying [$id] with tag [$tag]") - try { - notificationManager.notify(tag, id, notification) - } catch (iae: IllegalArgumentException) { - if (service == null && tag == null) { - // We can't notify using CallStyle if there isn't a foreground service running - Log.w( - "[Notifications Manager] Foreground service hasn't started yet, can't display a CallStyle notification until then: $iae" - ) - } else { - Log.e("[Notifications Manager] Exception occurred: $iae") - } - } - } - - fun cancel(id: Int, tag: String? = null) { - Log.i("[Notifications Manager] Canceling [$id] with tag [$tag]") - notificationManager.cancel(tag, id) - } - - fun resetChatNotificationCounterForSipUri(sipUri: String) { - val notifiable: Notifiable? = chatNotificationsMap[sipUri] - notifiable?.messages?.clear() - } - - /* Service related */ - - fun startForeground() { - val serviceChannel = context.getString(R.string.notification_channel_service_id) - if (Compatibility.getChannelImportance(notificationManager, serviceChannel) == NotificationManagerCompat.IMPORTANCE_NONE) { - Log.w("[Notifications Manager] Service channel is disabled!") - return - } - - val coreService = service - if (coreService != null) { - startForeground(coreService) - } else { - Log.w( - "[Notifications Manager] Can't start service as foreground without a service, starting it now" - ) - val intent = Intent() - intent.setClass(coreContext.context, CoreService::class.java) - try { - Compatibility.startForegroundService(coreContext.context, intent) - } catch (ise: IllegalStateException) { - Log.e("[Notifications Manager] Failed to start Service: $ise") - } catch (se: SecurityException) { - Log.e("[Notifications Manager] Failed to start Service: $se") - } - } - } - - fun startCallForeground(coreService: CoreService) { - service = coreService - when { - currentForegroundServiceNotificationId != 0 -> { - if (currentForegroundServiceNotificationId != SERVICE_NOTIF_ID) { - Log.e( - "[Notifications Manager] There is already a foreground service notification [$currentForegroundServiceNotificationId]" - ) - } else { - Log.i( - "[Notifications Manager] There is already a foreground service notification, no need to use the call notification to keep Service alive" - ) - } - } - coreContext.core.callsNb > 0 -> { - // When this method will be called, we won't have any notification yet - val call = coreContext.core.currentCall ?: coreContext.core.calls[0] - when (call.state) { - Call.State.IncomingReceived, Call.State.IncomingEarlyMedia -> { - Log.i( - "[Notifications Manager] Creating incoming call notification to be used as foreground service" - ) - displayIncomingCallNotification(call, true) - } - else -> { - Log.i( - "[Notifications Manager] Creating call notification to be used as foreground service" - ) - displayCallNotification(call, true) - } - } - } - } - } - - private fun startForeground(coreService: CoreService) { - service = coreService - - val notification = serviceNotification ?: createServiceNotification(false) - if (notification == null) { - Log.e( - "[Notifications Manager] Failed to create service notification, aborting foreground service!" - ) - return - } - - currentForegroundServiceNotificationId = SERVICE_NOTIF_ID - Log.i( - "[Notifications Manager] Starting service as foreground [$currentForegroundServiceNotificationId]" - ) - - val core = coreContext.core - val isActiveCall = if (core.callsNb > 0) { - val currentCall = core.currentCall ?: core.calls.first() - when (currentCall.state) { - Call.State.IncomingReceived, Call.State.IncomingEarlyMedia, Call.State.OutgoingInit, Call.State.OutgoingProgress, Call.State.OutgoingRinging -> false - else -> true - } - } else { - false - } - - Compatibility.startDataSyncForegroundService( - coreService, - currentForegroundServiceNotificationId, - notification, - isActiveCall - ) - } - - fun startForegroundToKeepAppAlive( - coreService: CoreService, - useAutoStartDescription: Boolean = true - ) { - service = coreService - - val notification = serviceNotification ?: createServiceNotification(useAutoStartDescription) - if (notification == null) { - Log.e( - "[Notifications Manager] Failed to create service notification, aborting foreground service!" - ) - return - } - - currentForegroundServiceNotificationId = SERVICE_NOTIF_ID - Log.i( - "[Notifications Manager] Starting service as foreground [$currentForegroundServiceNotificationId]" - ) - - Compatibility.startDataSyncForegroundService( - coreService, - currentForegroundServiceNotificationId, - notification, - false - ) - } - - private fun startForeground( - notificationId: Int, - callNotification: Notification, - isCallActive: Boolean - ) { - val coreService = service - if (coreService != null && (currentForegroundServiceNotificationId == 0 || currentForegroundServiceNotificationId == notificationId)) { - Log.i( - "[Notifications Manager] Starting service as foreground using call notification [$notificationId]" - ) - try { - currentForegroundServiceNotificationId = notificationId - - Compatibility.startCallForegroundService( - coreService, - currentForegroundServiceNotificationId, - callNotification, - isCallActive - ) - } catch (e: Exception) { - Log.e("[Notifications Manager] Foreground service wasn't allowed! $e") - currentForegroundServiceNotificationId = 0 - } - } else { - Log.w( - "[Notifications Manager] Can't start foreground service using notification id [$notificationId] (current foreground service notification id is [$currentForegroundServiceNotificationId]) and service [$coreService]" - ) - } - } - - fun stopForegroundNotification() { - if (service != null) { - if (currentForegroundServiceNotificationId != 0) { - Log.i( - "[Notifications Manager] Stopping service as foreground [$currentForegroundServiceNotificationId]" - ) - currentForegroundServiceNotificationId = 0 - } - service?.stopForeground(true) - } - } - - fun stopForegroundNotificationIfPossible() { - if (service != null && currentForegroundServiceNotificationId == SERVICE_NOTIF_ID && !corePreferences.keepServiceAlive) { - Log.i( - "[Notifications Manager] Stopping auto-started service notification [$currentForegroundServiceNotificationId]" - ) - stopForegroundNotification() - } - } - - fun stopCallForeground() { - if (service != null && currentForegroundServiceNotificationId != SERVICE_NOTIF_ID) { - Log.i( - "[Notifications Manager] Stopping call notification [$currentForegroundServiceNotificationId] used as foreground service" - ) - stopForegroundNotification() - } - } - - fun serviceCreated(createdService: CoreService) { - Log.i("[Notifications Manager] Service has been created, keeping it around") - service = createdService - } - - fun serviceDestroyed() { - Log.i("[Notifications Manager] Service has been destroyed") - stopForegroundNotification() - service = null - } - - private fun createServiceNotification(useAutoStartDescription: Boolean = false): Notification? { - val serviceChannel = context.getString(R.string.notification_channel_service_id) - if (Compatibility.getChannelImportance(notificationManager, serviceChannel) == NotificationManagerCompat.IMPORTANCE_NONE) { - Log.w("[Notifications Manager] Service channel is disabled!") - return null - } - - val pendingIntent = NavDeepLinkBuilder(context) - .setComponentName(MainActivity::class.java) - .setGraph(R.navigation.main_nav_graph) - .setDestination(R.id.dialerFragment) - .createPendingIntent() - - val builder = NotificationCompat.Builder(context, serviceChannel) - .setContentTitle(context.getString(R.string.service_name)) - .setContentText( - if (useAutoStartDescription) { - context.getString( - R.string.service_auto_start_description - ) - } else { - context.getString(R.string.service_description) - } - ) - .setSmallIcon(R.drawable.topbar_service_notification) - .setCategory(NotificationCompat.CATEGORY_SERVICE) - .setVisibility(NotificationCompat.VISIBILITY_SECRET) - .setWhen(System.currentTimeMillis()) - .setShowWhen(true) - .setOngoing(true) - .setColor(ContextCompat.getColor(context, R.color.primary_color)) - - if (!corePreferences.preventInterfaceFromShowingUp) { - builder.setContentIntent(pendingIntent) - } - - val notif = builder.build() - serviceNotification = notif - return notif - } - - /* Call related */ - - private fun getNotificationIdForCall(call: Call): Int { - return call.callLog.startDate.toInt() - } - - private fun getNotifiableForCall(call: Call): Notifiable { - val address = call.remoteAddress.asStringUriOnly() - var notifiable: Notifiable? = callNotificationsMap[address] - if (notifiable == null) { - notifiable = Notifiable(getNotificationIdForCall(call)) - notifiable.remoteAddress = call.remoteAddress.asStringUriOnly() - - callNotificationsMap[address] = notifiable - } - return notifiable - } - - fun getPerson(friend: Friend?, displayName: String, picture: Bitmap?): Person { - return friend?.getPerson() - ?: Person.Builder() - .setName(displayName) - .setIcon( - if (picture != null) { - IconCompat.createWithAdaptiveBitmap(picture) - } else { - coreContext.contactsManager.contactAvatar - } - ) - .setKey(displayName) - .build() - } - - fun displayIncomingCallNotification(call: Call, useAsForeground: Boolean) { - if (coreContext.declineCallDueToGsmActiveCall()) { - Log.w( - "[Notifications Manager] Call will be declined, do not show incoming call notification" - ) - return - } - - val notifiable = getNotifiableForCall(call) - if (notifiable.notificationId == currentForegroundServiceNotificationId) { - Log.i( - "[Notifications Manager] There is already a Service foreground notification for this incoming call, skipping" - ) - return - } - - try { - val showLockScreenNotification = android.provider.Settings.Secure.getInt( - context.contentResolver, - "lock_screen_show_notifications", - 0 - ) - Log.i( - "[Notifications Manager] Are notifications allowed on lock screen? ${showLockScreenNotification != 0} ($showLockScreenNotification)" - ) - } catch (e: Exception) { - Log.e( - "[Notifications Manager] Failed to get android.provider.Settings.Secure.getInt(lock_screen_show_notifications): $e" - ) - } - - val incomingCallNotificationIntent = Intent(context, CallActivity::class.java) - incomingCallNotificationIntent.addFlags( - Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_NO_USER_ACTION or Intent.FLAG_FROM_BACKGROUND - ) - val pendingIntent = PendingIntent.getActivity( - context, - 0, - incomingCallNotificationIntent, - PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE - ) - - val notification = Compatibility.createIncomingCallNotification( - context, - call, - notifiable, - pendingIntent, - this - ) - Log.i( - "[Notifications Manager] Notifying incoming call notification [${notifiable.notificationId}]" - ) - notify(notifiable.notificationId, notification) - - if (useAsForeground) { - Log.i( - "[Notifications Manager] Notifying incoming call notification for foreground service [${notifiable.notificationId}]" - ) - startForeground(notifiable.notificationId, notification, false) - } - } - - private fun displayMissedCallNotification(remoteAddress: Address) { - val missedCallCount: Int = coreContext.core.missedCallsCount - val body: String - if (missedCallCount > 1) { - body = context.getString(R.string.missed_calls_notification_body) - .format(missedCallCount) - Log.i( - "[Notifications Manager] Updating missed calls notification count to $missedCallCount" - ) - } else { - val friend: Friend? = coreContext.contactsManager.findContactByAddress(remoteAddress) - body = context.getString(R.string.missed_call_notification_body) - .format(friend?.name ?: LinphoneUtils.getDisplayName(remoteAddress)) - Log.i("[Notifications Manager] Creating missed call notification") - } - - val pendingIntent = NavDeepLinkBuilder(context) - .setComponentName(MainActivity::class.java) - .setGraph(R.navigation.main_nav_graph) - .setDestination(R.id.masterCallLogsFragment) - .createPendingIntent() - - val builder = NotificationCompat.Builder( - context, - context.getString(R.string.notification_channel_missed_call_id) - ) - .setContentTitle(context.getString(R.string.missed_call_notification_title)) - .setContentText(body) - .setSmallIcon(R.drawable.topbar_missed_call_notification) - .setAutoCancel(true) - // .setCategory(NotificationCompat.CATEGORY_EVENT) No one really matches "missed call" - .setVisibility(NotificationCompat.VISIBILITY_PRIVATE) - .setWhen(System.currentTimeMillis()) - .setShowWhen(true) - .setNumber(missedCallCount) - .setColor(ContextCompat.getColor(context, R.color.notification_led_color)) - - if (!corePreferences.preventInterfaceFromShowingUp) { - builder.setContentIntent(pendingIntent) - } - - val notification = builder.build() - notify(MISSED_CALLS_NOTIF_ID, notification, MISSED_CALL_TAG) - } - - fun dismissMissedCallNotification() { - cancel(MISSED_CALLS_NOTIF_ID, MISSED_CALL_TAG) - } - - private fun displayCallNotification(call: Call, isCallActive: Boolean) { - val notifiable = getNotifiableForCall(call) - - val serviceChannel = context.getString(R.string.notification_channel_service_id) - val channelToUse = when ( - val serviceChannelImportance = Compatibility.getChannelImportance( - notificationManager, - serviceChannel - ) - ) { - NotificationManagerCompat.IMPORTANCE_NONE -> { - Log.w( - "[Notifications Manager] Service channel is disabled, using incoming call channel instead!" - ) - context.getString(R.string.notification_channel_incoming_call_id) - } - NotificationManagerCompat.IMPORTANCE_LOW -> { - // Expected, nothing to do - serviceChannel - } - else -> { - // If user disables & enabled back service notifications channel, importance won't be low anymore but default! - Log.w( - "[Notifications Manager] Service channel importance is $serviceChannelImportance and not LOW (${NotificationManagerCompat.IMPORTANCE_LOW}) as expected!" - ) - serviceChannel - } - } - - val callNotificationIntent = Intent(context, CallActivity::class.java) - callNotificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - val pendingIntent = PendingIntent.getActivity( - context, - 0, - callNotificationIntent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE - ) - - val notification = Compatibility.createCallNotification( - context, - call, - notifiable, - pendingIntent, - channelToUse, - this - ) - Log.i("[Notifications Manager] Notifying call notification [${notifiable.notificationId}]") - notify(notifiable.notificationId, notification) - - val coreService = service - if (coreService != null && (currentForegroundServiceNotificationId == 0 || currentForegroundServiceNotificationId == notifiable.notificationId)) { - Log.i( - "[Notifications Manager] Notifying call notification for foreground service [${notifiable.notificationId}]" - ) - startForeground(notifiable.notificationId, notification, isCallActive) - } else if (coreService != null && currentForegroundServiceNotificationId == SERVICE_NOTIF_ID) { - // To add microphone & camera foreground service use to foreground service if needed - startForeground(coreService) - } - } - - private fun dismissCallNotification(call: Call) { - val address = call.remoteAddress.asStringUriOnly() - val notifiable: Notifiable? = callNotificationsMap[address] - if (notifiable != null) { - cancel(notifiable.notificationId) - callNotificationsMap.remove(address) - } else { - Log.w("[Notifications Manager] No notification found for call ${call.callLog.callId}") - } - - // To remove microphone & camera foreground service use to foreground service if needed - val coreService = service - if (coreService != null && currentForegroundServiceNotificationId == SERVICE_NOTIF_ID) { - startForeground(coreService) - } - } - - /* Chat related */ - - private fun getNotificationIdForChat(chatRoom: ChatRoom): Int { - return LinphoneUtils.getChatRoomId(chatRoom).hashCode() - } - - private fun displayChatNotifiable(room: ChatRoom, notifiable: Notifiable) { - val localAddress = room.localAddress.asStringUriOnly() - val peerAddress = room.peerAddress.asStringUriOnly() - val args = Bundle() - args.putString("RemoteSipUri", peerAddress) - args.putString("LocalSipUri", localAddress) - - val pendingIntent = NavDeepLinkBuilder(context) - .setComponentName(MainActivity::class.java) - .setGraph(R.navigation.main_nav_graph) - .setDestination(R.id.masterChatRoomsFragment) - .setArguments(args) - .createPendingIntent() - - // PendingIntents attached to bubbles must be mutable - val target = Intent(context, ChatBubbleActivity::class.java) - target.putExtra("RemoteSipUri", peerAddress) - target.putExtra("LocalSipUri", localAddress) - val bubbleIntent = PendingIntent.getActivity( - context, - notifiable.notificationId, - target, - Compatibility.getUpdateCurrentPendingIntentFlag() - ) - - val id = LinphoneUtils.getChatRoomId(room.localAddress, room.peerAddress) - val me = coreContext.contactsManager.getMePerson(room.localAddress) - val notification = createMessageNotification( - notifiable, - pendingIntent, - bubbleIntent, - id, - me - ) - notify(notifiable.notificationId, notification, CHAT_TAG) - } - - private fun updateChatNotifiableWithMessages( - notifiable: Notifiable, - room: ChatRoom, - messages: Array - ): Boolean { - var updated = false - for (message in messages) { - if (message.isRead || message.isOutgoing) continue - val friend = coreContext.contactsManager.findContactByAddress(message.fromAddress) - val notifiableMessage = getNotifiableMessage(message, friend) - notifiable.messages.add(notifiableMessage) - updated = true - } - - if (room.hasCapability(ChatRoom.Capabilities.OneToOne.toInt())) { - notifiable.isGroup = false - } else { - notifiable.isGroup = true - notifiable.groupTitle = room.subject - } - return updated - } - - private fun createChatReactionNotifiable( - room: ChatRoom, - reaction: String, - from: Address, - message: ChatMessage - ): Notifiable { - val notifiable = getNotifiableForRoom(room) - - // Check for previous reaction notifiable from the same person for the same message - val fromAddr = from.asStringUriOnly() - val found = notifiable.messages.find { - it.isReaction && it.reactionToMessageId == message.messageId && it.reactionFrom == fromAddr - } - if (found != null) { - Log.i( - "[Notifications Manager] Found a previous notifiable for a reaction from the same person to the same message, removing it" - ) - notifiable.messages.remove(found) - } - - val friend = coreContext.contactsManager.findContactByAddress(from) - val roundPicture = ImageUtils.getRoundBitmapFromUri(context, friend?.getThumbnailUri()) - val displayName = friend?.name ?: LinphoneUtils.getDisplayName(from) - - val originalMessage = LinphoneUtils.getTextDescribingMessage(message) - val text = AppUtils.getString(R.string.chat_message_reaction_received).format( - displayName, - reaction, - originalMessage - ) - - val notifiableMessage = NotifiableMessage( - text, - friend, - displayName, - message.time, - senderAvatar = roundPicture, - isOutgoing = false, - isReaction = true, - reactionToMessageId = message.messageId, - reactionFrom = from.asStringUriOnly() - ) - notifiable.messages.add(notifiableMessage) - - return notifiable - } - - private fun getNotifiableForRoom(room: ChatRoom): Notifiable { - val address = room.peerAddress.asStringUriOnly() - var notifiable: Notifiable? = chatNotificationsMap[address] - if (notifiable == null) { - notifiable = Notifiable(getNotificationIdForChat(room)) - notifiable.myself = LinphoneUtils.getDisplayName(room.localAddress) - notifiable.localIdentity = room.localAddress.asStringUriOnly() - notifiable.remoteAddress = room.peerAddress.asStringUriOnly() - - chatNotificationsMap[address] = notifiable - - if (room.hasCapability(ChatRoom.Capabilities.OneToOne.toInt())) { - notifiable.isGroup = false - } else { - notifiable.isGroup = true - notifiable.groupTitle = room.subject - } - } - return notifiable - } - - private fun getNotifiableMessage(message: ChatMessage, friend: Friend?): NotifiableMessage { - val roundPicture = ImageUtils.getRoundBitmapFromUri(context, friend?.getThumbnailUri()) - val displayName = friend?.name ?: LinphoneUtils.getDisplayName(message.fromAddress) - val text = LinphoneUtils.getTextDescribingMessage(message) - val notifiableMessage = NotifiableMessage( - text, - friend, - displayName, - message.time * 1000, /* Linphone timestamps are in seconds */ - senderAvatar = roundPicture, - isOutgoing = message.isOutgoing - ) - - for (content in message.contents) { - if (content.isFile) { - val path = content.filePath - if (path != null) { - val contentUri: Uri = FileUtils.getPublicFilePath(context, path) - val filePath: String = contentUri.toString() - val extension = FileUtils.getExtensionFromFileName(filePath) - if (extension.isNotEmpty()) { - val mime = - MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) - notifiableMessage.filePath = contentUri - notifiableMessage.fileMime = mime - Log.i( - "[Notifications Manager] Added file $contentUri with MIME $mime to notification" - ) - } else { - Log.e( - "[Notifications Manager] Couldn't find extension for incoming message with file $path" - ) - } - } - } - } - - return notifiableMessage - } - - private fun displayReplyMessageNotification(message: ChatMessage, notifiable: Notifiable) { - Log.i( - "[Notifications Manager] Updating message notification with reply for notification ${notifiable.notificationId}" - ) - - val text = message.contents.find { content -> content.isText }?.utf8Text ?: "" - val senderAddress = message.fromAddress - val reply = NotifiableMessage( - text, - null, - notifiable.myself ?: LinphoneUtils.getDisplayName(senderAddress), - System.currentTimeMillis(), - isOutgoing = true - ) - notifiable.messages.add(reply) - - displayChatNotifiable(message.chatRoom, notifiable) - } - - fun dismissChatNotification(room: ChatRoom): Boolean { - val address = room.peerAddress.asStringUriOnly() - val notifiable: Notifiable? = chatNotificationsMap[address] - if (notifiable != null) { - Log.i( - "[Notifications Manager] Dismissing notification for chat room $room with id ${notifiable.notificationId}" - ) - notifiable.messages.clear() - cancel(notifiable.notificationId, CHAT_TAG) - return true - } else { - val previousNotificationId = previousChatNotifications.find { id -> - id == getNotificationIdForChat( - room - ) - } - if (previousNotificationId != null) { - if (chatBubbleNotifications.contains(previousNotificationId)) { - Log.i( - "[Notifications Manager] Found previous notification with same ID [$previousNotificationId] but not cancelling it as it's ID is in chat bubbles list" - ) - } else { - Log.i( - "[Notifications Manager] Found previous notification with same ID [$previousNotificationId], canceling it" - ) - cancel(previousNotificationId, CHAT_TAG) - } - return true - } - } - return false - } - - fun changeDismissNotificationUponReadForChatRoom(chatRoom: ChatRoom, dismiss: Boolean) { - val notificationId = getNotificationIdForChat(chatRoom) - if (dismiss) { - Log.i( - "[Notifications Manager] Allow notification with id [$notificationId] to be dismissed when chat room will be marked as read, used for chat bubble" - ) - chatBubbleNotifications.add(notificationId) - } else { - Log.i( - "[Notifications Manager] Prevent notification with id [$notificationId] from being dismissed when chat room will be marked as read, used for chat bubble" - ) - chatBubbleNotifications.remove(notificationId) - } - } - - /* Notifications */ - - private fun createMessageNotification( - notifiable: Notifiable, - pendingIntent: PendingIntent, - bubbleIntent: PendingIntent, - id: String, - me: Person - ): Notification { - val style = NotificationCompat.MessagingStyle(me) - val allPersons = arrayListOf() - - var lastPersonAvatar: Bitmap? = null - var lastPerson: Person? = null - for (message in notifiable.messages) { - val friend = message.friend - val person = getPerson(friend, message.sender, message.senderAvatar) - - if (!message.isOutgoing) { - // We don't want to see our own avatar - lastPerson = person - lastPersonAvatar = message.senderAvatar - - if (allPersons.find { it.key == person.key } == null) { - allPersons.add(person) - } - } - - val senderPerson = if (message.isOutgoing) null else person // Use null for ourselves - val msg = if (corePreferences.hideChatMessageContentInNotification) { - NotificationCompat.MessagingStyle.Message( - AppUtils.getString(R.string.chat_message_notification_hidden_content), - message.time, - senderPerson - ) - } else { - val tmp = NotificationCompat.MessagingStyle.Message( - message.message, - message.time, - senderPerson - ) - if (message.filePath != null) tmp.setData(message.fileMime, message.filePath) - tmp - } - - style.addMessage(msg) - if (message.isOutgoing) { - style.addHistoricMessage(msg) - } - } - - style.conversationTitle = if (notifiable.isGroup) notifiable.groupTitle else lastPerson?.name - style.isGroupConversation = notifiable.isGroup - - val icon = lastPerson?.icon ?: coreContext.contactsManager.contactAvatar - val bubble = NotificationCompat.BubbleMetadata.Builder(bubbleIntent, icon) - .setDesiredHeightResId(R.dimen.chat_message_bubble_desired_height) - .build() - - val largeIcon = if (notifiable.isGroup) coreContext.contactsManager.groupBitmap else lastPersonAvatar - val notificationBuilder = NotificationCompat.Builder( - context, - context.getString(R.string.notification_channel_chat_id) - ) - .setSmallIcon(R.drawable.topbar_chat_notification) - .setAutoCancel(true) - .setLargeIcon(largeIcon) - .setColor(ContextCompat.getColor(context, R.color.primary_color)) - .setCategory(NotificationCompat.CATEGORY_MESSAGE) - .setGroup(CHAT_NOTIFICATIONS_GROUP) - .setVisibility(NotificationCompat.VISIBILITY_PRIVATE) - .setNumber(notifiable.messages.size) - .setWhen(System.currentTimeMillis()) - .setShowWhen(true) - .setStyle(style) - .addAction(getReplyMessageAction(notifiable)) - .addAction(getMarkMessageAsReadAction(notifiable)) - .setShortcutId(id) - .setLocusId(LocusIdCompat(id)) - - for (person in allPersons) { - notificationBuilder.addPerson(person) - } - - if (!corePreferences.preventInterfaceFromShowingUp) { - notificationBuilder.setContentIntent(pendingIntent) - } - - if (corePreferences.markAsReadUponChatMessageNotificationDismissal) { - Log.i( - "[Notifications Manager] Chat room will be marked as read when notification will be dismissed" - ) - notificationBuilder - .setDeleteIntent(getMarkMessageAsReadPendingIntent(notifiable)) - } - - if (!Compatibility.canChatMessageChannelBubble(context)) { - Log.w("[Notifications Manager] This conversation wasn't granted bubble permission yet") - } - // We still need to set the bubbleMetadata, otherwise user won't ever be able to enable bubbles! - notificationBuilder.bubbleMetadata = bubble - return notificationBuilder.build() - } - - /* Notifications actions */ - - fun getCallAnswerPendingIntent(notifiable: Notifiable): PendingIntent { - val answerIntent = Intent(context, NotificationBroadcastReceiver::class.java) - answerIntent.action = INTENT_ANSWER_CALL_NOTIF_ACTION - answerIntent.putExtra(INTENT_NOTIF_ID, notifiable.notificationId) - answerIntent.putExtra(INTENT_REMOTE_ADDRESS, notifiable.remoteAddress) - - return PendingIntent.getBroadcast( - context, - notifiable.notificationId, - answerIntent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE - ) - } - - fun getCallAnswerAction(notifiable: Notifiable): NotificationCompat.Action { - return NotificationCompat.Action.Builder( - R.drawable.call_audio_start, - context.getString(R.string.incoming_call_notification_answer_action_label), - getCallAnswerPendingIntent(notifiable) - ).build() - } - - fun getCallDeclinePendingIntent(notifiable: Notifiable): PendingIntent { - val hangupIntent = Intent(context, NotificationBroadcastReceiver::class.java) - hangupIntent.action = INTENT_HANGUP_CALL_NOTIF_ACTION - hangupIntent.putExtra(INTENT_NOTIF_ID, notifiable.notificationId) - hangupIntent.putExtra(INTENT_REMOTE_ADDRESS, notifiable.remoteAddress) - - return PendingIntent.getBroadcast( - context, - notifiable.notificationId, - hangupIntent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE - ) - } - - fun getCallDeclineAction(notifiable: Notifiable): NotificationCompat.Action { - return NotificationCompat.Action.Builder( - R.drawable.call_hangup, - context.getString(R.string.incoming_call_notification_hangup_action_label), - getCallDeclinePendingIntent(notifiable) - ) - .setShowsUserInterface(false) - .build() - } - - private fun getReplyMessageAction(notifiable: Notifiable): NotificationCompat.Action { - val replyLabel = - context.resources.getString(R.string.received_chat_notification_reply_label) - val remoteInput = - RemoteInput.Builder(KEY_TEXT_REPLY).setLabel(replyLabel).build() - - val replyIntent = Intent(context, NotificationBroadcastReceiver::class.java) - replyIntent.action = INTENT_REPLY_NOTIF_ACTION - replyIntent.putExtra(INTENT_NOTIF_ID, notifiable.notificationId) - replyIntent.putExtra(INTENT_LOCAL_IDENTITY, notifiable.localIdentity) - replyIntent.putExtra(INTENT_REMOTE_ADDRESS, notifiable.remoteAddress) - - // PendingIntents attached to actions with remote inputs must be mutable - val replyPendingIntent = PendingIntent.getBroadcast( - context, - notifiable.notificationId, - replyIntent, - Compatibility.getUpdateCurrentPendingIntentFlag() - ) - return NotificationCompat.Action.Builder( - R.drawable.chat_send_over, - context.getString(R.string.received_chat_notification_reply_label), - replyPendingIntent - ) - .addRemoteInput(remoteInput) - .setAllowGeneratedReplies(true) - .setShowsUserInterface(false) - .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_REPLY) - .build() - } - - private fun getMarkMessageAsReadPendingIntent(notifiable: Notifiable): PendingIntent { - val markAsReadIntent = Intent(context, NotificationBroadcastReceiver::class.java) - markAsReadIntent.action = INTENT_MARK_AS_READ_ACTION - markAsReadIntent.putExtra(INTENT_NOTIF_ID, notifiable.notificationId) - markAsReadIntent.putExtra(INTENT_LOCAL_IDENTITY, notifiable.localIdentity) - markAsReadIntent.putExtra(INTENT_REMOTE_ADDRESS, notifiable.remoteAddress) - - return PendingIntent.getBroadcast( - context, - notifiable.notificationId, - markAsReadIntent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE - ) - } - - private fun getMarkMessageAsReadAction(notifiable: Notifiable): NotificationCompat.Action { - val markAsReadPendingIntent = getMarkMessageAsReadPendingIntent(notifiable) - return NotificationCompat.Action.Builder( - R.drawable.chat_send_over, - context.getString(R.string.received_chat_notification_mark_as_read_label), - markAsReadPendingIntent - ) - .setShowsUserInterface(false) - .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_MARK_AS_READ) - .build() - } -} diff --git a/app/src/main/java/org/linphone/telecom/NativeCallWrapper.kt b/app/src/main/java/org/linphone/telecom/NativeCallWrapper.kt deleted file mode 100644 index 823490e32..000000000 --- a/app/src/main/java/org/linphone/telecom/NativeCallWrapper.kt +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.telecom - -import android.annotation.TargetApi -import android.graphics.drawable.Icon -import android.os.Bundle -import android.telecom.CallAudioState -import android.telecom.Connection -import android.telecom.DisconnectCause -import android.telecom.StatusHints -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R -import org.linphone.core.Call -import org.linphone.core.tools.Log -import org.linphone.utils.AudioRouteUtils - -@TargetApi(29) -class NativeCallWrapper(var callId: String) : Connection() { - init { - val properties = connectionProperties or PROPERTY_SELF_MANAGED - connectionProperties = properties - - val capabilities = connectionCapabilities or CAPABILITY_MUTE or CAPABILITY_SUPPORT_HOLD or CAPABILITY_HOLD - connectionCapabilities = capabilities - - audioModeIsVoip = true - statusHints = StatusHints( - "", - Icon.createWithResource(coreContext.context, R.drawable.linphone_logo_tinted), - Bundle() - ) - } - - override fun onStateChanged(state: Int) { - Log.i( - "[Connection] Telecom state changed [${intStateToString(state)}] for call with id: $callId" - ) - super.onStateChanged(state) - } - - override fun onAnswer(videoState: Int) { - Log.i("[Connection] Answering telecom call with id: $callId") - getCall()?.accept() ?: selfDestroy() - } - - override fun onHold() { - Log.i("[Connection] Pausing telecom call with id: $callId") - getCall()?.let { call -> - if (call.conference != null) { - call.conference?.leave() - } else { - call.pause() - } - } ?: selfDestroy() - setOnHold() - } - - override fun onUnhold() { - Log.i("[Connection] Resuming telecom call with id: $callId") - getCall()?.let { call -> - if (call.conference != null) { - call.conference?.enter() - } else { - call.resume() - } - } ?: selfDestroy() - setActive() - } - - override fun onCallAudioStateChanged(state: CallAudioState) { - Log.i("[Connection] Audio state changed: $state") - - val call = getCall() - if (call != null) { - if (getState() != STATE_ACTIVE && getState() != STATE_DIALING) { - Log.w( - "[Connection] Call state isn't STATE_ACTIVE or STATE_DIALING, ignoring mute mic & audio route directive from TelecomManager" - ) - return - } - - if (state.isMuted != call.microphoneMuted) { - Log.w( - "[Connection] Connection audio state asks for changing in mute: ${state.isMuted}, currently is ${call.microphoneMuted}" - ) - if (state.isMuted) { - Log.w("[Connection] Muting microphone") - call.microphoneMuted = true - } - } - - when (state.route) { - CallAudioState.ROUTE_EARPIECE -> AudioRouteUtils.routeAudioToEarpiece(call, true) - CallAudioState.ROUTE_SPEAKER -> AudioRouteUtils.routeAudioToSpeaker(call, true) - CallAudioState.ROUTE_BLUETOOTH -> AudioRouteUtils.routeAudioToBluetooth(call, true) - CallAudioState.ROUTE_WIRED_HEADSET -> AudioRouteUtils.routeAudioToHeadset( - call, - true - ) - } - } else { - selfDestroy() - } - } - - override fun onPlayDtmfTone(c: Char) { - Log.i("[Connection] Sending DTMF [$c] in telecom call with id: $callId") - getCall()?.sendDtmf(c) ?: selfDestroy() - } - - override fun onDisconnect() { - Log.i("[Connection] Terminating telecom call with id: $callId") - getCall()?.terminate() ?: selfDestroy() - } - - override fun onAbort() { - Log.i("[Connection] Aborting telecom call with id: $callId") - getCall()?.terminate() ?: selfDestroy() - } - - override fun onReject() { - Log.i("[Connection] Rejecting telecom call with id: $callId") - getCall()?.terminate() ?: selfDestroy() - } - - override fun onSilence() { - Log.i("[Connection] Call with id: $callId asked to be silenced") - coreContext.core.stopRinging() - } - - fun stateAsString(): String { - return stateToString(state) - } - - private fun getCall(): Call? { - return coreContext.core.getCallByCallid(callId) - } - - private fun selfDestroy() { - if (coreContext.core.callsNb == 0) { - Log.e("[Connection] No call in Core, destroy connection") - setDisconnected(DisconnectCause(DisconnectCause.LOCAL)) - destroy() - } - } - - private fun intStateToString(state: Int): String { - return when (state) { - STATE_INITIALIZING -> "STATE_INITIALIZING" - STATE_NEW -> "STATE_NEW" - STATE_RINGING -> "STATE_RINGING" - STATE_DIALING -> "STATE_DIALING" - STATE_ACTIVE -> "STATE_ACTIVE" - STATE_HOLDING -> "STATE_HOLDING" - STATE_DISCONNECTED -> "STATE_DISCONNECTED" - STATE_PULLING_CALL -> "STATE_PULLING_CALL" - else -> "STATE_UNKNOWN" - } - } -} diff --git a/app/src/main/java/org/linphone/telecom/TelecomConnectionService.kt b/app/src/main/java/org/linphone/telecom/TelecomConnectionService.kt deleted file mode 100644 index fddfc8bb6..000000000 --- a/app/src/main/java/org/linphone/telecom/TelecomConnectionService.kt +++ /dev/null @@ -1,299 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.telecom - -import android.annotation.TargetApi -import android.content.ComponentName -import android.content.Intent -import android.net.Uri -import android.telecom.* -import org.linphone.LinphoneApplication -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.ensureCoreExists -import org.linphone.core.Call -import org.linphone.core.Core -import org.linphone.core.CoreListenerStub -import org.linphone.core.tools.Log - -@TargetApi(29) -class TelecomConnectionService : ConnectionService() { - private val listener: CoreListenerStub = object : CoreListenerStub() { - override fun onCallStateChanged( - core: Core, - call: Call, - state: Call.State?, - message: String - ) { - Log.i( - "[Telecom Connection Service] call [${call.callLog.callId}] state changed: $state" - ) - when (call.state) { - Call.State.OutgoingProgress -> { - for (connection in TelecomHelper.get().connections) { - if (connection.callId.isEmpty()) { - Log.i( - "[Telecom Connection Service] Updating connection with call ID: ${call.callLog.callId}" - ) - connection.callId = core.currentCall?.callLog?.callId ?: "" - } - } - } - Call.State.Error -> onCallError(call) - Call.State.End, Call.State.Released -> onCallEnded(call) - Call.State.Paused, Call.State.Pausing, Call.State.PausedByRemote -> onCallPaused( - call - ) - Call.State.Connected, Call.State.StreamsRunning -> onCallConnected(call) - else -> {} - } - } - - override fun onLastCallEnded(core: Core) { - val connectionsCount = TelecomHelper.get().connections.size - if (connectionsCount > 0) { - Log.w( - "[Telecom Connection Service] Last call ended, there is $connectionsCount connections still alive" - ) - for (connection in TelecomHelper.get().connections) { - Log.w( - "[Telecom Connection Service] Destroying zombie connection ${connection.callId}" - ) - connection.setDisconnected(DisconnectCause(DisconnectCause.OTHER)) - connection.destroy() - } - } - } - } - - override fun onCreate() { - super.onCreate() - - Log.i("[Telecom Connection Service] onCreate()") - ensureCoreExists(applicationContext) - coreContext.core.addListener(listener) - } - - override fun onUnbind(intent: Intent?): Boolean { - if (LinphoneApplication.contextExists()) { - Log.i("[Telecom Connection Service] onUnbind()") - coreContext.core.removeListener(listener) - } - - return super.onUnbind(intent) - } - - override fun onCreateOutgoingConnection( - connectionManagerPhoneAccount: PhoneAccountHandle?, - request: ConnectionRequest - ): Connection { - if (coreContext.core.callsNb == 0) { - Log.w("[Telecom Connection Service] No call in Core, aborting outgoing connection!") - return Connection.createCanceledConnection() - } - - val accountHandle = request.accountHandle - val componentName = ComponentName(applicationContext, this.javaClass) - return if (accountHandle != null && componentName == accountHandle.componentName) { - Log.i("[Telecom Connection Service] Creating outgoing connection") - - val extras = request.extras - var callId = extras.getString("Call-ID") - val displayName = extras.getString("DisplayName") - if (callId == null) { - callId = coreContext.core.currentCall?.callLog?.callId ?: "" - } - Log.i( - "[Telecom Connection Service] Outgoing connection is for call [$callId] with display name [$displayName]" - ) - - // Prevents user dialing back from native dialer app history - if (callId.isEmpty() && displayName.isNullOrEmpty()) { - Log.e( - "[Telecom Connection Service] Looks like a call was made from native dialer history, aborting" - ) - return Connection.createFailedConnection(DisconnectCause(DisconnectCause.OTHER)) - } - - val connection = NativeCallWrapper(callId) - val call = coreContext.core.calls.find { it.callLog.callId == callId } - if (call != null) { - val callState = call.state - Log.i( - "[Telecom Connection Service] Found outgoing call from ID [$callId] with state [$callState]" - ) - when (callState) { - Call.State.OutgoingEarlyMedia, Call.State.OutgoingInit, Call.State.OutgoingProgress, Call.State.OutgoingRinging -> connection.setDialing() - Call.State.Paused, Call.State.PausedByRemote, Call.State.Pausing -> connection.setOnHold() - Call.State.End, Call.State.Error, Call.State.Released -> connection.setDisconnected( - DisconnectCause(DisconnectCause.ERROR) - ) - else -> connection.setActive() - } - } else { - Log.w( - "[Telecom Connection Service] Outgoing call not found for cal ID [$callId], assuming it's state is dialing" - ) - connection.setDialing() - } - - val providedHandle = request.address - connection.setAddress(providedHandle, TelecomManager.PRESENTATION_ALLOWED) - connection.setCallerDisplayName(displayName, TelecomManager.PRESENTATION_ALLOWED) - Log.i("[Telecom Connection Service] Address is $providedHandle") - - TelecomHelper.get().connections.add(connection) - connection - } else { - Log.e("[Telecom Connection Service] Error: $accountHandle $componentName") - Connection.createFailedConnection( - DisconnectCause( - DisconnectCause.ERROR, - "Invalid inputs: $accountHandle $componentName" - ) - ) - } - } - - override fun onCreateIncomingConnection( - connectionManagerPhoneAccount: PhoneAccountHandle?, - request: ConnectionRequest - ): Connection { - if (coreContext.core.callsNb == 0) { - Log.w("[Telecom Connection Service] No call in Core, aborting incoming connection!") - return Connection.createCanceledConnection() - } - - val accountHandle = request.accountHandle - val componentName = ComponentName(applicationContext, this.javaClass) - return if (accountHandle != null && componentName == accountHandle.componentName) { - Log.i("[Telecom Connection Service] Creating incoming connection") - - val extras = request.extras - val incomingExtras = extras.getBundle(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS) - var callId = incomingExtras?.getString("Call-ID") - val displayName = incomingExtras?.getString("DisplayName") - if (callId == null) { - callId = coreContext.core.currentCall?.callLog?.callId ?: "" - } - Log.i( - "[Telecom Connection Service] Incoming connection is for call [$callId] with display name [$displayName]" - ) - - val connection = NativeCallWrapper(callId) - val call = coreContext.core.calls.find { it.callLog.callId == callId } - if (call != null) { - val callState = call.state - Log.i( - "[Telecom Connection Service] Found incoming call from ID [$callId] with state [$callState]" - ) - when (callState) { - Call.State.IncomingEarlyMedia, Call.State.IncomingReceived -> connection.setRinging() - Call.State.Paused, Call.State.PausedByRemote, Call.State.Pausing -> connection.setOnHold() - Call.State.End, Call.State.Error, Call.State.Released -> connection.setDisconnected( - DisconnectCause(DisconnectCause.ERROR) - ) - else -> connection.setActive() - } - } else { - Log.w( - "[Telecom Connection Service] Incoming call not found for cal ID [$callId], assuming it's state is ringing" - ) - connection.setRinging() - } - - val providedHandle = - incomingExtras?.getParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS) - connection.setAddress(providedHandle, TelecomManager.PRESENTATION_ALLOWED) - connection.setCallerDisplayName(displayName, TelecomManager.PRESENTATION_ALLOWED) - Log.i("[Telecom Connection Service] Address is $providedHandle") - - TelecomHelper.get().connections.add(connection) - connection - } else { - Log.e("[Telecom Connection Service] Error: $accountHandle $componentName") - Connection.createFailedConnection( - DisconnectCause( - DisconnectCause.ERROR, - "Invalid inputs: $accountHandle $componentName" - ) - ) - } - } - - private fun onCallError(call: Call) { - val callId = call.callLog.callId - val connection = TelecomHelper.get().findConnectionForCallId(callId.orEmpty()) - if (connection == null) { - Log.e("[Telecom Connection Service] Failed to find connection for call id: $callId") - return - } - - TelecomHelper.get().connections.remove(connection) - Log.i( - "[Telecom Connection Service] Call [$callId] is in error, destroying connection currently in ${connection.stateAsString()}" - ) - connection.setDisconnected(DisconnectCause(DisconnectCause.ERROR)) - connection.destroy() - } - - private fun onCallEnded(call: Call) { - val callId = call.callLog.callId - val connection = TelecomHelper.get().findConnectionForCallId(callId.orEmpty()) - if (connection == null) { - Log.e("[Telecom Connection Service] Failed to find connection for call id: $callId") - return - } - - TelecomHelper.get().connections.remove(connection) - val reason = call.reason - Log.i( - "[Telecom Connection Service] Call [$callId] ended with reason: $reason, destroying connection currently in ${connection.stateAsString()}" - ) - connection.setDisconnected(DisconnectCause(DisconnectCause.LOCAL)) - connection.destroy() - } - - private fun onCallPaused(call: Call) { - val callId = call.callLog.callId - val connection = TelecomHelper.get().findConnectionForCallId(callId.orEmpty()) - if (connection == null) { - Log.e("[Telecom Connection Service] Failed to find connection for call id: $callId") - return - } - Log.i( - "[Telecom Connection Service] Setting connection as on hold, currently in ${connection.stateAsString()}" - ) - connection.setOnHold() - } - - private fun onCallConnected(call: Call) { - val callId = call.callLog.callId - val connection = TelecomHelper.get().findConnectionForCallId(callId.orEmpty()) - if (connection == null) { - Log.e("[Telecom Connection Service] Failed to find connection for call id: $callId") - return - } - - Log.i( - "[Telecom Connection Service] Setting connection as active, currently in ${connection.stateAsString()}" - ) - connection.setActive() - } -} diff --git a/app/src/main/java/org/linphone/telecom/TelecomHelper.kt b/app/src/main/java/org/linphone/telecom/TelecomHelper.kt deleted file mode 100644 index 3f55f3a89..000000000 --- a/app/src/main/java/org/linphone/telecom/TelecomHelper.kt +++ /dev/null @@ -1,251 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.telecom - -import android.annotation.SuppressLint -import android.annotation.TargetApi -import android.content.ComponentName -import android.content.Context -import android.graphics.drawable.Icon -import android.net.Uri -import android.os.Bundle -import android.telecom.PhoneAccount -import android.telecom.PhoneAccountHandle -import android.telecom.TelecomManager -import android.telecom.TelecomManager.* -import kotlin.Exception -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R -import org.linphone.core.Call -import org.linphone.core.Core -import org.linphone.core.CoreListenerStub -import org.linphone.core.tools.Log -import org.linphone.utils.LinphoneUtils -import org.linphone.utils.PermissionHelper -import org.linphone.utils.SingletonHolder - -@TargetApi(29) -class TelecomHelper private constructor(context: Context) { - companion object : SingletonHolder(::TelecomHelper) - - private val telecomManager: TelecomManager = context.getSystemService(Context.TELECOM_SERVICE) as TelecomManager - - private var account: PhoneAccount = initPhoneAccount(context) - - val connections = arrayListOf() - - private val listener: CoreListenerStub = object : CoreListenerStub() { - override fun onCallStateChanged( - core: Core, - call: Call, - state: Call.State?, - message: String - ) { - Log.i("[Telecom Helper] Call state changed: ${call.state}") - - try { - if (call.dir == Call.Dir.Incoming && call.state == Call.State.IncomingReceived) { - onIncomingCall(call) - } else if (call.dir == Call.Dir.Outgoing && call.state == Call.State.OutgoingProgress) { - onOutgoingCall(call) - } - } catch (se: SecurityException) { - Log.e("[Telecom Helper] Exception while trying to place call: $se") - } - } - } - - init { - coreContext.core.addListener(listener) - Log.i("[Telecom Helper] Created") - } - - fun destroy() { - coreContext.core.removeListener(listener) - Log.i("[Telecom Helper] Destroyed") - } - - fun isIncomingCallPermitted(): Boolean { - val incomingCallPermitted = telecomManager.isIncomingCallPermitted(account.accountHandle) - Log.i("[Telecom Helper] Is incoming call permitted? $incomingCallPermitted") - return incomingCallPermitted - } - - @SuppressLint("MissingPermission") - fun isInManagedCall(): Boolean { - // Don't use telecomManager.isInCall as our own self-managed calls will be considered! - val isInManagedCall = telecomManager.isInManagedCall - Log.i("[Telecom Helper] Is in managed call? $isInManagedCall") - return isInManagedCall - } - - fun isAccountEnabled(): Boolean { - val enabled = account.isEnabled - Log.i("[Telecom Helper] Is account enabled ? $enabled") - return enabled - } - - @SuppressLint("MissingPermission") - fun findExistingAccount(context: Context): PhoneAccount? { - if (PermissionHelper.required(context).hasReadPhoneStateOrPhoneNumbersPermission()) { - try { - var account: PhoneAccount? = null - val phoneAccountHandleList: List = - telecomManager.selfManagedPhoneAccounts - val connectionService = ComponentName(context, TelecomConnectionService::class.java) - for (phoneAccountHandle in phoneAccountHandleList) { - val phoneAccount: PhoneAccount = - telecomManager.getPhoneAccount(phoneAccountHandle) - if (phoneAccountHandle.componentName == connectionService) { - Log.i("[Telecom Helper] Found existing phone account: $phoneAccount") - account = phoneAccount - break - } - } - if (account == null) { - Log.w("[Telecom Helper] Existing phone account not found") - } - return account - } catch (se: SecurityException) { - Log.w("[Telecom Helper] Can't check phone accounts: $se") - } - } else { - Log.e("[Telecom Helper] Can't search for existing phone account, missing permission(s)") - } - return null - } - - fun updateAccount(newAccount: PhoneAccount?) { - if (newAccount != null) { - Log.i("[Telecom Helper] Updating account object: $newAccount") - account = newAccount - } - } - - fun removeAccount() { - if (account.isEnabled) { - Log.w("[Telecom Helper] Unregistering phone account handler from telecom manager") - telecomManager.unregisterPhoneAccount(account.accountHandle) - } else { - Log.w("[Telecom Helper] Account wasn't enabled, skipping...") - } - } - - fun findConnectionForCallId(callId: String): NativeCallWrapper? { - return connections.find { connection -> - connection.callId == callId - } - } - - private fun initPhoneAccount(context: Context): PhoneAccount { - val account: PhoneAccount? = findExistingAccount(context) - if (account == null) { - Log.i("[Telecom Helper] Phone account not found, let's create it") - return createAccount(context) - } - return account - } - - private fun createAccount(context: Context): PhoneAccount { - val accountHandle = PhoneAccountHandle( - ComponentName(context, TelecomConnectionService::class.java), - context.packageName - ) - // Take care that identity may be parsed, otherwise Android OS may crash during startup - // and user will have to do a factory reset... - val identity = coreContext.core.defaultAccount?.params?.identityAddress?.asStringUriOnly() - ?: coreContext.core.createPrimaryContactParsed()?.asStringUriOnly() - ?: "sip:linphone.android@sip.linphone.org" - - val address = Uri.parse(identity) - ?: throw Exception("[Telecom Helper] Identity address for phone account is null!") - val account = PhoneAccount.builder(accountHandle, context.getString(R.string.app_name)) - .setAddress(address) - .setIcon(Icon.createWithResource(context, R.drawable.linphone_logo_tinted)) - .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED) - .setHighlightColor(context.getColor(R.color.primary_color)) - .setShortDescription(context.getString(R.string.app_description)) - .setSupportedUriSchemes(listOf(PhoneAccount.SCHEME_SIP)) - .build() - - try { - telecomManager.registerPhoneAccount(account) - Log.i("[Telecom Helper] Phone account created: $account") - } catch (uoe: UnsupportedOperationException) { - Log.e("[Telecom Helper] Unsupported Operation Exception: $uoe") - } catch (e: Exception) { - Log.e("[Telecom Helper] Exception: $e") - } - return account - } - - private fun onIncomingCall(call: Call) { - Log.i( - "[Telecom Helper] Incoming call received from ${call.remoteAddress.asStringUriOnly()}, using account handle ${account.accountHandle}" - ) - - val extras = prepareBundle(call) - telecomManager.addNewIncomingCall( - account.accountHandle, - Bundle().apply { - putBundle(EXTRA_INCOMING_CALL_EXTRAS, extras) - putParcelable(EXTRA_PHONE_ACCOUNT_HANDLE, account.accountHandle) - } - ) - } - - @SuppressLint("MissingPermission") - private fun onOutgoingCall(call: Call) { - Log.i( - "[Telecom Helper] Outgoing call started to ${call.remoteAddress.asStringUriOnly()}, using account handle ${account.accountHandle}" - ) - - val extras = prepareBundle(call) - telecomManager.placeCall( - Uri.parse(call.remoteAddress.asStringUriOnly()), - Bundle().apply { - putBundle(EXTRA_OUTGOING_CALL_EXTRAS, extras) - putParcelable(EXTRA_PHONE_ACCOUNT_HANDLE, account.accountHandle) - } - ) - } - - private fun prepareBundle(call: Call): Bundle { - val extras = Bundle() - val address = call.remoteAddress - - if (call.dir == Call.Dir.Outgoing) { - extras.putString( - EXTRA_CALL_BACK_NUMBER, - call.remoteAddress.asStringUriOnly() - ) - } else { - extras.putParcelable(EXTRA_INCOMING_CALL_ADDRESS, Uri.parse(address.asStringUriOnly())) - } - - extras.putString("Call-ID", call.callLog.callId) - - val contact = coreContext.contactsManager.findContactByAddress(call.remoteAddress) - val displayName = contact?.name ?: LinphoneUtils.getDisplayName(call.remoteAddress) - extras.putString("DisplayName", displayName) - - return extras - } -} diff --git a/app/src/main/java/org/linphone/utils/ActivityMonitor.kt b/app/src/main/java/org/linphone/utils/ActivityMonitor.kt deleted file mode 100644 index 20f4fe482..000000000 --- a/app/src/main/java/org/linphone/utils/ActivityMonitor.kt +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.utils - -import android.app.Activity -import android.app.Application.ActivityLifecycleCallbacks -import android.os.Bundle -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.core.tools.service.AndroidDispatcher -import org.linphone.core.tools.service.CoreManager - -class ActivityMonitor : ActivityLifecycleCallbacks { - private val activities = ArrayList() - private var mActive = false - private var mRunningActivities = 0 - private var mLastChecker: InactivityChecker? = null - - @Synchronized - override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { - if (!activities.contains(activity)) activities.add(activity) - } - - override fun onActivityStarted(activity: Activity) { - } - - @Synchronized - override fun onActivityResumed(activity: Activity) { - if (!activities.contains(activity)) { - activities.add(activity) - } - mRunningActivities++ - checkActivity() - } - - @Synchronized - override fun onActivityPaused(activity: Activity) { - if (!activities.contains(activity)) { - activities.add(activity) - } else { - mRunningActivities-- - checkActivity() - } - } - - override fun onActivityStopped(activity: Activity) { - } - - @Synchronized - override fun onActivityDestroyed(activity: Activity) { - activities.remove(activity) - } - - private fun startInactivityChecker() { - if (mLastChecker != null) mLastChecker!!.cancel() - AndroidDispatcher.dispatchOnUIThreadAfter( - InactivityChecker().also { mLastChecker = it }, - 2000 - ) - } - - private fun checkActivity() { - if (mRunningActivities == 0) { - if (mActive) startInactivityChecker() - } else if (mRunningActivities > 0) { - if (!mActive) { - mActive = true - onForegroundMode() - } - if (mLastChecker != null) { - mLastChecker!!.cancel() - mLastChecker = null - } - } - } - - private fun onBackgroundMode() { - coreContext.onBackground() - } - - private fun onForegroundMode() { - coreContext.onForeground() - } - - override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {} - internal inner class InactivityChecker : Runnable { - private var isCanceled = false - fun cancel() { - isCanceled = true - } - - override fun run() { - if (CoreManager.isReady()) { - synchronized(CoreManager.instance()) { - if (!isCanceled) { - if (mRunningActivities == 0 && mActive) { - mActive = false - onBackgroundMode() - } - } - } - } - } - } -} diff --git a/app/src/main/java/org/linphone/utils/AppUtils.kt b/app/src/main/java/org/linphone/utils/AppUtils.kt deleted file mode 100644 index 88d61b66e..000000000 --- a/app/src/main/java/org/linphone/utils/AppUtils.kt +++ /dev/null @@ -1,255 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.utils - -import android.app.Activity -import android.content.ActivityNotFoundException -import android.content.Context -import android.content.Intent -import android.media.AudioManager -import android.os.Bundle -import android.text.format.Formatter.formatShortFileSize -import android.util.TypedValue -import androidx.core.content.res.ResourcesCompat -import androidx.emoji2.text.EmojiCompat -import androidx.media.AudioAttributesCompat -import androidx.media.AudioFocusRequestCompat -import androidx.media.AudioManagerCompat -import androidx.recyclerview.widget.DividerItemDecoration -import androidx.recyclerview.widget.LinearLayoutManager -import java.util.* -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.R -import org.linphone.activities.main.viewmodels.SharedMainViewModel -import org.linphone.core.tools.Log - -/** - * Various utility methods for application - */ -class AppUtils { - interface KeyboardVisibilityListener { - fun onKeyboardVisibilityChanged(visible: Boolean) - } - - companion object { - private val emojiCompat: EmojiCompat? - get() = initEmojiCompat() - - private fun initEmojiCompat(): EmojiCompat? { - return try { - EmojiCompat.get() - } catch (ise: IllegalStateException) { - Log.w( - "[App Utils] EmojiCompat.get() triggered IllegalStateException [$ise], trying manual init" - ) - EmojiCompat.init(coreContext.context) - } - } - - fun getString(id: Int): String { - return coreContext.context.getString(id) - } - - fun getStringWithPlural(id: Int, count: Int): String { - return coreContext.context.resources.getQuantityString(id, count, count) - } - - fun getStringWithPlural(id: Int, count: Int, value: String): String { - return coreContext.context.resources.getQuantityString(id, count, value) - } - - fun getDimension(id: Int): Float { - return coreContext.context.resources.getDimension(id) - } - - fun getInitials(displayName: String, limit: Int = 2): String { - if (displayName.isEmpty()) return "" - - val split = displayName.uppercase(Locale.getDefault()).split(" ") - var initials = "" - var characters = 0 - val emoji = emojiCompat - - for (i in split.indices) { - if (split[i].isNotEmpty()) { - try { - if (emoji?.loadState == EmojiCompat.LOAD_STATE_SUCCEEDED && emoji.hasEmojiGlyph( - split[i] - ) - ) { - val glyph = emoji.process(split[i]) - if (characters > 0) { // Limit initial to 1 emoji only - Log.d("[App Utils] We limit initials to one emoji only") - initials = "" - } - initials += glyph - break // Limit initial to 1 emoji only - } else { - initials += split[i][0] - } - } catch (ise: IllegalStateException) { - Log.e("[App Utils] Can't call hasEmojiGlyph: $ise") - initials += split[i][0] - } - - characters += 1 - if (characters >= limit) break - } - } - return initials - } - - fun isTextOnlyContainingEmoji(text: String): Boolean { - val emoji = emojiCompat - emoji ?: return false - - if (emoji.loadState != EmojiCompat.LOAD_STATE_SUCCEEDED) { - Log.w( - "[App Utils] Can't check emoji presence in text due to EmojiCompat library not loaded yet [${emoji.loadState}]" - ) - return false - } - - try { - for (split in text.split(" ")) { - // We only check the first and last chars of the split for commodity - if (emoji.getEmojiStart(split, 0) == -1 || - emoji.getEmojiEnd(split, split.length - 1) == -1 - ) { - Log.d("[App Utils] Found a non-emoji character in [$text]") - return false - } - } - } catch (npe: NullPointerException) { - Log.e( - "[App Utils] Can't check emoji presence in text due to NPE in EmojiCompat library, assuming there is none" - ) - // This can happen in EmojiCompat library, mProcessor can be null (https://issuetracker.google.com/issues/277182750) - return false - } - - Log.d("[App Utils] It seems text [$text] only contains emoji(s)") - return true - } - - fun pixelsToDp(pixels: Float): Float { - return TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, - pixels, - coreContext.context.resources.displayMetrics - ) - } - - fun dpToPixels(context: Context, dp: Float): Float { - return dp * context.resources.displayMetrics.density - } - - fun bytesToDisplayableSize(bytes: Long): String { - return formatShortFileSize(coreContext.context, bytes) - } - - fun shareUploadedLogsUrl(activity: Activity, info: String) { - val appName = activity.getString(R.string.app_name) - val intent = Intent(Intent.ACTION_SEND) - intent.putExtra( - Intent.EXTRA_EMAIL, - arrayOf(activity.getString(R.string.about_bugreport_email)) - ) - intent.putExtra(Intent.EXTRA_SUBJECT, "$appName Logs") - intent.putExtra(Intent.EXTRA_TEXT, info) - intent.type = "text/plain" - - try { - activity.startActivity( - Intent.createChooser( - intent, - activity.getString(R.string.share_uploaded_logs_link) - ) - ) - } catch (ex: ActivityNotFoundException) { - Log.e(ex) - } - } - - fun getDividerDecoration(context: Context, layoutManager: LinearLayoutManager): DividerItemDecoration { - val dividerItemDecoration = DividerItemDecoration(context, layoutManager.orientation) - val divider = ResourcesCompat.getDrawable(context.resources, R.drawable.divider, null) - if (divider != null) dividerItemDecoration.setDrawable(divider) - return dividerItemDecoration - } - - fun createBundleWithSharedTextAndFiles(sharedViewModel: SharedMainViewModel): Bundle { - val bundle = Bundle() - bundle.putString("TextToShare", sharedViewModel.textToShare.value.orEmpty()) - bundle.putStringArrayList("FilesToShare", sharedViewModel.filesToShare.value) - - // Remove values from shared view model - sharedViewModel.textToShare.value = "" - sharedViewModel.filesToShare.value = arrayListOf() - - return bundle - } - - fun isMediaVolumeLow(context: Context): Boolean { - val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager - val currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) - val maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) - Log.i("[Media Volume] Current value is $currentVolume, max value is $maxVolume") - return currentVolume <= maxVolume * 0.5 - } - - fun acquireAudioFocusForVoiceRecordingOrPlayback(context: Context): AudioFocusRequestCompat { - val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager - val audioAttrs = AudioAttributesCompat.Builder() - .setUsage(AudioAttributesCompat.USAGE_MEDIA) - .setContentType(AudioAttributesCompat.CONTENT_TYPE_SPEECH) - .build() - - val request = - AudioFocusRequestCompat.Builder( - AudioManagerCompat.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE - ) - .setAudioAttributes(audioAttrs) - .setOnAudioFocusChangeListener { } - .build() - when (AudioManagerCompat.requestAudioFocus(audioManager, request)) { - AudioManager.AUDIOFOCUS_REQUEST_GRANTED -> { - Log.i("[Audio Focus] Voice recording/playback audio focus request granted") - } - AudioManager.AUDIOFOCUS_REQUEST_FAILED -> { - Log.w("[Audio Focus] Voice recording/playback audio focus request failed") - } - AudioManager.AUDIOFOCUS_REQUEST_DELAYED -> { - Log.w("[Audio Focus] Voice recording/playback audio focus request delayed") - } - } - return request - } - - fun releaseAudioFocusForVoiceRecordingOrPlayback( - context: Context, - request: AudioFocusRequestCompat - ) { - val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager - AudioManagerCompat.abandonAudioFocusRequest(audioManager, request) - Log.i("[Audio Focus] Voice recording/playback audio focus request abandoned") - } - } -} diff --git a/app/src/main/java/org/linphone/utils/AudioRouteUtils.kt b/app/src/main/java/org/linphone/utils/AudioRouteUtils.kt deleted file mode 100644 index fd95886fd..000000000 --- a/app/src/main/java/org/linphone/utils/AudioRouteUtils.kt +++ /dev/null @@ -1,343 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.utils - -import android.telecom.CallAudioState -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.compatibility.Compatibility -import org.linphone.core.AudioDevice -import org.linphone.core.Call -import org.linphone.core.tools.Log -import org.linphone.telecom.TelecomHelper - -class AudioRouteUtils { - companion object { - private fun applyAudioRouteChange( - call: Call?, - types: List, - output: Boolean = true - ) { - val currentCall = if (coreContext.core.callsNb > 0) { - call ?: coreContext.core.currentCall ?: coreContext.core.calls[0] - } else { - Log.w("[Audio Route Helper] No call found, setting audio route on Core") - null - } - val conference = coreContext.core.conference - val capability = if (output) { - AudioDevice.Capabilities.CapabilityPlay - } else { - AudioDevice.Capabilities.CapabilityRecord - } - val preferredDriver = if (output) { - coreContext.core.defaultOutputAudioDevice?.driverName - } else { - coreContext.core.defaultInputAudioDevice?.driverName - } - - val extendedAudioDevices = coreContext.core.extendedAudioDevices - Log.i( - "[Audio Route Helper] Looking for an ${if (output) "output" else "input"} audio device with capability [$capability], driver name [$preferredDriver] and type [$types] in extended audio devices list (size ${extendedAudioDevices.size})" - ) - val foundAudioDevice = extendedAudioDevices.find { - it.driverName == preferredDriver && types.contains(it.type) && it.hasCapability( - capability - ) - } - val audioDevice = if (foundAudioDevice == null) { - Log.w( - "[Audio Route Helper] Failed to find an audio device with capability [$capability], driver name [$preferredDriver] and type [$types]" - ) - extendedAudioDevices.find { - types.contains(it.type) && it.hasCapability(capability) - } - } else { - foundAudioDevice - } - - if (audioDevice == null) { - Log.e( - "[Audio Route Helper] Couldn't find audio device with capability [$capability] and type [$types]" - ) - for (device in extendedAudioDevices) { - // TODO: switch to debug? - Log.i( - "[Audio Route Helper] Extended audio device: [${device.deviceName} (${device.driverName}) ${device.type} / ${device.capabilities}]" - ) - } - return - } - if (conference != null && conference.isIn) { - Log.i( - "[Audio Route Helper] Found [${audioDevice.type}] ${if (output) "playback" else "recorder"} audio device [${audioDevice.deviceName} (${audioDevice.driverName})], routing conference audio to it" - ) - if (output) { - conference.outputAudioDevice = audioDevice - } else { - conference.inputAudioDevice = audioDevice - } - } else if (currentCall != null) { - Log.i( - "[Audio Route Helper] Found [${audioDevice.type}] ${if (output) "playback" else "recorder"} audio device [${audioDevice.deviceName} (${audioDevice.driverName})], routing call audio to it" - ) - if (output) { - currentCall.outputAudioDevice = audioDevice - } else { - currentCall.inputAudioDevice = audioDevice - } - } else { - Log.i( - "[Audio Route Helper] Found [${audioDevice.type}] ${if (output) "playback" else "recorder"} audio device [${audioDevice.deviceName} (${audioDevice.driverName})], changing core default audio device" - ) - if (output) { - coreContext.core.outputAudioDevice = audioDevice - } else { - coreContext.core.inputAudioDevice = audioDevice - } - } - } - - private fun routeAudioTo( - call: Call?, - types: List, - skipTelecom: Boolean = false - ) { - val currentCall = call ?: coreContext.core.currentCall ?: coreContext.core.calls.firstOrNull() - if (currentCall != null && !skipTelecom && TelecomHelper.exists()) { - Log.i( - "[Audio Route Helper] Call provided & Telecom Helper exists, trying to dispatch audio route change through Telecom API" - ) - val connection = TelecomHelper.get().findConnectionForCallId( - currentCall.callLog.callId.orEmpty() - ) - if (connection != null) { - val route = when (types.first()) { - AudioDevice.Type.Earpiece -> CallAudioState.ROUTE_EARPIECE - AudioDevice.Type.Speaker -> CallAudioState.ROUTE_SPEAKER - AudioDevice.Type.Headphones, AudioDevice.Type.Headset -> CallAudioState.ROUTE_WIRED_HEADSET - AudioDevice.Type.Bluetooth, AudioDevice.Type.BluetoothA2DP, AudioDevice.Type.HearingAid -> CallAudioState.ROUTE_BLUETOOTH - else -> CallAudioState.ROUTE_WIRED_OR_EARPIECE - } - Log.i( - "[Audio Route Helper] Telecom Helper & matching connection found, dispatching audio route change through it" - ) - // We will be called here again by NativeCallWrapper.onCallAudioStateChanged() - // but this time with skipTelecom = true - if (!Compatibility.changeAudioRouteForTelecomManager(connection, route)) { - Log.w( - "[Audio Route Helper] Connection is already using this route internally, make the change!" - ) - applyAudioRouteChange(currentCall, types) - } - } else { - Log.w("[Audio Route Helper] Telecom Helper found but no matching connection!") - applyAudioRouteChange(currentCall, types) - } - } else { - applyAudioRouteChange(call, types) - } - } - - fun routeAudioToEarpiece(call: Call? = null, skipTelecom: Boolean = false) { - routeAudioTo(call, arrayListOf(AudioDevice.Type.Earpiece), skipTelecom) - } - - fun routeAudioToSpeaker(call: Call? = null, skipTelecom: Boolean = false) { - routeAudioTo(call, arrayListOf(AudioDevice.Type.Speaker), skipTelecom) - } - - fun routeAudioToBluetooth(call: Call? = null, skipTelecom: Boolean = false) { - routeAudioTo( - call, - arrayListOf(AudioDevice.Type.Bluetooth, AudioDevice.Type.HearingAid), - skipTelecom - ) - } - - fun routeAudioToHeadset(call: Call? = null, skipTelecom: Boolean = false) { - routeAudioTo( - call, - arrayListOf(AudioDevice.Type.Headphones, AudioDevice.Type.Headset), - skipTelecom - ) - } - - fun isSpeakerAudioRouteCurrentlyUsed(call: Call? = null): Boolean { - val currentCall = if (coreContext.core.callsNb > 0) { - call ?: coreContext.core.currentCall ?: coreContext.core.calls[0] - } else { - Log.w("[Audio Route Helper] No call found, checking audio route on Core") - null - } - val conference = coreContext.core.conference - - val audioDevice = if (conference != null && conference.isIn) { - conference.outputAudioDevice - } else if (currentCall != null) { - currentCall.outputAudioDevice - } else { - coreContext.core.outputAudioDevice - } - - if (audioDevice == null) return false - Log.i( - "[Audio Route Helper] Playback audio device currently in use is [${audioDevice.deviceName} (${audioDevice.driverName}) ${audioDevice.type}]" - ) - return audioDevice.type == AudioDevice.Type.Speaker - } - - fun isBluetoothAudioRouteCurrentlyUsed(call: Call? = null): Boolean { - if (coreContext.core.callsNb == 0) { - Log.w("[Audio Route Helper] No call found, so bluetooth audio route isn't used") - return false - } - val currentCall = call ?: coreContext.core.currentCall ?: coreContext.core.calls[0] - val conference = coreContext.core.conference - - val audioDevice = if (conference != null && conference.isIn) { - conference.outputAudioDevice - } else { - currentCall.outputAudioDevice - } - - if (audioDevice == null) return false - Log.i( - "[Audio Route Helper] Playback audio device currently in use is [${audioDevice.deviceName} (${audioDevice.driverName}) ${audioDevice.type}]" - ) - return audioDevice.type == AudioDevice.Type.Bluetooth || audioDevice.type == AudioDevice.Type.HearingAid - } - - fun isBluetoothAudioRouteAvailable(): Boolean { - for (audioDevice in coreContext.core.audioDevices) { - if ((audioDevice.type == AudioDevice.Type.Bluetooth || audioDevice.type == AudioDevice.Type.HearingAid) && - audioDevice.hasCapability(AudioDevice.Capabilities.CapabilityPlay) - ) { - Log.i( - "[Audio Route Helper] Found bluetooth audio device [${audioDevice.deviceName} (${audioDevice.driverName})]" - ) - return true - } - } - return false - } - - private fun isBluetoothAudioRecorderAvailable(): Boolean { - for (audioDevice in coreContext.core.audioDevices) { - if ((audioDevice.type == AudioDevice.Type.Bluetooth || audioDevice.type == AudioDevice.Type.HearingAid) && - audioDevice.hasCapability(AudioDevice.Capabilities.CapabilityRecord) - ) { - Log.i( - "[Audio Route Helper] Found bluetooth audio recorder [${audioDevice.deviceName} (${audioDevice.driverName})]" - ) - return true - } - } - return false - } - - fun isHeadsetAudioRouteAvailable(): Boolean { - for (audioDevice in coreContext.core.audioDevices) { - if ((audioDevice.type == AudioDevice.Type.Headset || audioDevice.type == AudioDevice.Type.Headphones) && - audioDevice.hasCapability(AudioDevice.Capabilities.CapabilityPlay) - ) { - Log.i( - "[Audio Route Helper] Found headset/headphones audio device [${audioDevice.deviceName} (${audioDevice.driverName})]" - ) - return true - } - } - return false - } - - private fun isHeadsetAudioRecorderAvailable(): Boolean { - for (audioDevice in coreContext.core.audioDevices) { - if ((audioDevice.type == AudioDevice.Type.Headset || audioDevice.type == AudioDevice.Type.Headphones) && - audioDevice.hasCapability(AudioDevice.Capabilities.CapabilityRecord) - ) { - Log.i( - "[Audio Route Helper] Found headset/headphones audio recorder [${audioDevice.deviceName} (${audioDevice.driverName})]" - ) - return true - } - } - return false - } - - fun getAudioPlaybackDeviceIdForCallRecordingOrVoiceMessage(): String? { - // In case no headphones/headset/hearing aid/bluetooth is connected, use speaker sound card to play recordings, otherwise use earpiece - // If none are available, default one will be used - var headphonesCard: String? = null - var bluetoothCard: String? = null - var speakerCard: String? = null - var earpieceCard: String? = null - for (device in coreContext.core.audioDevices) { - if (device.hasCapability(AudioDevice.Capabilities.CapabilityPlay)) { - when (device.type) { - AudioDevice.Type.Headphones, AudioDevice.Type.Headset -> { - headphonesCard = device.id - } - AudioDevice.Type.Bluetooth, AudioDevice.Type.HearingAid -> { - bluetoothCard = device.id - } - AudioDevice.Type.Speaker -> { - speakerCard = device.id - } - AudioDevice.Type.Earpiece -> { - earpieceCard = device.id - } - else -> {} - } - } - } - Log.i( - "[Audio Route Helper] Found headset/headphones/hearingAid sound card [$headphonesCard], bluetooth sound card [$bluetoothCard], speaker sound card [$speakerCard] and earpiece sound card [$earpieceCard]" - ) - return headphonesCard ?: bluetoothCard ?: speakerCard ?: earpieceCard - } - - fun getAudioRecordingDeviceForVoiceMessage(): AudioDevice? { - // In case no headphones/headset/hearing aid/bluetooth is connected, use microphone - // If none are available, default one will be used - var bluetoothAudioDevice: AudioDevice? = null - var headsetAudioDevice: AudioDevice? = null - var builtinMicrophone: AudioDevice? = null - for (device in coreContext.core.audioDevices) { - if (device.hasCapability(AudioDevice.Capabilities.CapabilityRecord)) { - when (device.type) { - AudioDevice.Type.Bluetooth, AudioDevice.Type.HearingAid -> { - bluetoothAudioDevice = device - } - AudioDevice.Type.Headset, AudioDevice.Type.Headphones -> { - headsetAudioDevice = device - } - AudioDevice.Type.Microphone -> { - builtinMicrophone = device - } - else -> {} - } - } - } - Log.i( - "[Audio Route Helper] Found headset/headphones/hearingAid [${headsetAudioDevice?.id}], bluetooth [${bluetoothAudioDevice?.id}] and builtin microphone [${builtinMicrophone?.id}]" - ) - return headsetAudioDevice ?: bluetoothAudioDevice ?: builtinMicrophone - } - } -} diff --git a/app/src/main/java/org/linphone/utils/ContactUtils.kt b/app/src/main/java/org/linphone/utils/ContactUtils.kt deleted file mode 100644 index 1c4098aa7..000000000 --- a/app/src/main/java/org/linphone/utils/ContactUtils.kt +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.utils - -import android.content.ContentResolver -import android.net.Uri -import android.provider.ContactsContract -import java.io.* -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.core.tools.Log - -class ContactUtils { - companion object { - fun getContactVcardFilePath(contactUri: Uri): String? { - val contentResolver: ContentResolver = coreContext.context.contentResolver - val lookupUri = ContactsContract.Contacts.getLookupUri(contentResolver, contactUri) - Log.i("[Contact Utils] Contact lookup URI is $lookupUri") - - val contactID = FileUtils.getNameFromFilePath(lookupUri.toString()) - Log.i("[Contact Utils] Contact ID is $contactID") - - val contact = coreContext.contactsManager.findContactById(contactID) - if (contact == null) { - Log.e("[Contact Utils] Failed to find contact with ID $contactID") - return null - } - - val vcard = contact.vcard?.asVcard4String() - if (vcard == null) { - Log.e("[Contact Utils] Failed to get vCard from contact $contactID") - return null - } - - val contactName = contact.name?.replace(" ", "_") ?: contactID - val vcardPath = FileUtils.getFileStoragePath("$contactName.vcf") - val inputStream = ByteArrayInputStream(vcard.toByteArray()) - try { - FileOutputStream(vcardPath).use { out -> - val buffer = ByteArray(4096) - var bytesRead: Int - while (inputStream.read(buffer).also { bytesRead = it } >= 0) { - out.write(buffer, 0, bytesRead) - } - } - } catch (e: IOException) { - Log.e("[Contact Utils] creating vcard file exception: $e") - return null - } - - Log.i("[Contact Utils] Contact vCard path is $vcardPath") - return vcardPath.absolutePath - } - } -} diff --git a/app/src/main/java/org/linphone/utils/DataBindingUtils.kt b/app/src/main/java/org/linphone/utils/DataBindingUtils.kt deleted file mode 100644 index e7bd08092..000000000 --- a/app/src/main/java/org/linphone/utils/DataBindingUtils.kt +++ /dev/null @@ -1,898 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.utils - -import android.annotation.SuppressLint -import android.app.Activity -import android.content.Context -import android.text.Editable -import android.text.TextWatcher -import android.util.Patterns -import android.view.* -import android.view.inputmethod.EditorInfo -import android.view.inputmethod.InputMethodManager -import android.widget.* -import android.widget.SeekBar.OnSeekBarChangeListener -import androidx.appcompat.content.res.AppCompatResources -import androidx.constraintlayout.widget.ConstraintLayout -import androidx.constraintlayout.widget.Guideline -import androidx.core.view.ViewCompat -import androidx.core.view.WindowInsetsCompat -import androidx.core.view.doOnLayout -import androidx.databinding.* -import androidx.emoji2.emojipicker.EmojiPickerView -import androidx.emoji2.emojipicker.EmojiViewItem -import androidx.lifecycle.LifecycleOwner -import coil.dispose -import coil.load -import coil.request.CachePolicy -import coil.request.videoFrameMillis -import coil.transform.CircleCropTransformation -import com.google.android.material.switchmaterial.SwitchMaterial -import kotlinx.coroutines.* -import org.linphone.BR -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.activities.main.settings.SettingListener -import org.linphone.activities.voip.data.ConferenceParticipantDeviceData -import org.linphone.activities.voip.views.ScrollDotsView -import org.linphone.contact.ContactAvatarGenerator -import org.linphone.contact.ContactDataInterface -import org.linphone.contact.getPictureUri -import org.linphone.core.ConsolidatedPresence -import org.linphone.core.tools.Log -import org.linphone.views.VoiceRecordProgressBar - -/** - * This file contains all the data binding necessary for the app - */ - -fun View.hideKeyboard() { - try { - val imm = - context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager - imm.hideSoftInputFromWindow(windowToken, 0) - } catch (_: Exception) {} -} - -fun View.setKeyboardInsetListener(lambda: (visible: Boolean) -> Unit) { - doOnLayout { - var isKeyboardVisible = ViewCompat.getRootWindowInsets(this)?.isVisible( - WindowInsetsCompat.Type.ime() - ) == true - - lambda(isKeyboardVisible) - - // See https://issuetracker.google.com/issues/281942480 - ViewCompat.setOnApplyWindowInsetsListener( - rootView - ) { view, insets -> - val keyboardVisibilityChanged = ViewCompat.getRootWindowInsets(view) - ?.isVisible(WindowInsetsCompat.Type.ime()) == true - if (keyboardVisibilityChanged != isKeyboardVisible) { - isKeyboardVisible = keyboardVisibilityChanged - lambda(isKeyboardVisible) - } - ViewCompat.onApplyWindowInsets(view, insets) - } - } -} - -@BindingAdapter("android:src") -fun ImageView.setSourceImageResource(resource: Int) { - this.setImageResource(resource) -} - -@BindingAdapter("android:contentDescription") -fun ImageView.setContentDescription(resource: Int) { - if (resource == 0) { - Log.w("Can't set content description with resource id 0") - return - } - this.contentDescription = context.getString(resource) -} - -@BindingAdapter("android:textStyle") -fun TextView.setTypeface(typeface: Int) { - this.setTypeface(null, typeface) -} - -@BindingAdapter("android:layout_size") -fun View.setLayoutSize(dimension: Float) { - if (dimension == 0f) return - this.layoutParams.height = dimension.toInt() - this.layoutParams.width = dimension.toInt() -} - -@BindingAdapter("backgroundImage") -fun LinearLayout.setBackgroundImage(resource: Int) { - this.setBackgroundResource(resource) -} - -@Suppress("DEPRECATION") -@BindingAdapter("style") -fun TextView.setStyle(resource: Int) { - this.setTextAppearance(context, resource) -} - -@BindingAdapter("android:layout_marginLeft") -fun setLeftMargin(view: View, margin: Float) { - val layoutParams = view.layoutParams as RelativeLayout.LayoutParams - layoutParams.leftMargin = margin.toInt() - view.layoutParams = layoutParams -} - -@BindingAdapter("android:layout_marginRight") -fun setRightMargin(view: View, margin: Float) { - val layoutParams = view.layoutParams as RelativeLayout.LayoutParams - layoutParams.rightMargin = margin.toInt() - view.layoutParams = layoutParams -} - -@BindingAdapter("android:layout_alignLeft") -fun setLayoutLeftAlign(view: View, oldTargetId: Int, newTargetId: Int) { - val layoutParams = view.layoutParams as RelativeLayout.LayoutParams - if (oldTargetId != 0) layoutParams.removeRule(RelativeLayout.ALIGN_LEFT) - if (newTargetId != 0) layoutParams.addRule(RelativeLayout.ALIGN_LEFT, newTargetId) - view.layoutParams = layoutParams -} - -@BindingAdapter("android:layout_alignRight") -fun setLayoutRightAlign(view: View, oldTargetId: Int, newTargetId: Int) { - val layoutParams = view.layoutParams as RelativeLayout.LayoutParams - if (oldTargetId != 0) layoutParams.removeRule(RelativeLayout.ALIGN_RIGHT) - if (newTargetId != 0) layoutParams.addRule(RelativeLayout.ALIGN_RIGHT, newTargetId) - view.layoutParams = layoutParams -} - -@BindingAdapter("android:layout_toLeftOf") -fun setLayoutToLeftOf(view: View, oldTargetId: Int, newTargetId: Int) { - val layoutParams = view.layoutParams as RelativeLayout.LayoutParams - if (oldTargetId != 0) layoutParams.removeRule(RelativeLayout.LEFT_OF) - if (newTargetId != 0) layoutParams.addRule(RelativeLayout.LEFT_OF, newTargetId) - view.layoutParams = layoutParams -} - -@BindingAdapter("android:layout_gravity") -fun setLayoutGravity(view: View, gravity: Int) { - val layoutParams = view.layoutParams as LinearLayout.LayoutParams - layoutParams.gravity = gravity - view.layoutParams = layoutParams -} - -@BindingAdapter("layout_constraintGuide_percent") -fun setLayoutConstraintGuidePercent(guideline: Guideline, percent: Float) { - val params = guideline.layoutParams as ConstraintLayout.LayoutParams - params.guidePercent = percent - guideline.layoutParams = params -} - -@BindingAdapter("onClickToggleSwitch") -fun switchSetting(view: View, switchId: Int) { - val switch: SwitchMaterial = view.findViewById(switchId) - view.setOnClickListener { switch.isChecked = !switch.isChecked } -} - -@BindingAdapter("onValueChanged") -fun editTextSetting(view: EditText, lambda: () -> Unit) { - view.addTextChangedListener(object : TextWatcher { - override fun afterTextChanged(s: Editable?) { - lambda() - } - - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} - - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} - }) -} - -@BindingAdapter("onSettingImeDone") -fun editTextImeDone(view: EditText, lambda: () -> Unit) { - view.setOnEditorActionListener { _, actionId, _ -> - if (actionId == EditorInfo.IME_ACTION_DONE) { - lambda() - - view.clearFocus() - - val imm = view.context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager - imm.hideSoftInputFromWindow(view.windowToken, 0) - - return@setOnEditorActionListener true - } - false - } -} - -@BindingAdapter("onFocusChangeVisibilityOf") -fun setEditTextOnFocusChangeVisibilityOf(editText: EditText, view: View) { - editText.setOnFocusChangeListener { _, hasFocus -> - view.visibility = if (hasFocus) View.VISIBLE else View.INVISIBLE - } -} - -@BindingAdapter("selectedIndex", "settingListener") -fun spinnerSetting(spinner: Spinner, selectedIndex: Int, listener: SettingListener?) { - spinner.setSelection(selectedIndex, true) - - spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { - override fun onNothingSelected(parent: AdapterView<*>?) {} - - override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { - // From Crashlytics it seems this method may be called with a null listener - listener?.onListValueChanged(position) - } - } -} - -@BindingAdapter("onProgressChanged") -fun setListener(view: SeekBar, lambda: (Any) -> Unit) { - view.setOnSeekBarChangeListener(object : OnSeekBarChangeListener { - override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { - if (fromUser) lambda(progress) - } - - override fun onStartTrackingTouch(seekBar: SeekBar?) {} - - override fun onStopTrackingTouch(seekBar: SeekBar?) {} - }) -} - -@BindingAdapter("inflatedLifecycleOwner") -fun setInflatedViewStubLifecycleOwner(view: View, enable: Boolean) { - val binding = DataBindingUtil.bind(view) - // This is a bit hacky... - if (view.context is LifecycleOwner) { - binding?.lifecycleOwner = view.context as? LifecycleOwner - } -} - -@BindingAdapter("entries") -fun setEntries( - viewGroup: ViewGroup, - entries: List? -) { - viewGroup.removeAllViews() - if (entries != null) { - for (i in entries) { - viewGroup.addView(i.root) - } - } -} - -private fun setEntries( - viewGroup: ViewGroup, - entries: List?, - layoutId: Int, - onLongClick: View.OnLongClickListener?, - parent: Any? -) { - viewGroup.removeAllViews() - if (entries != null) { - val inflater = viewGroup.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater - for (entry in entries) { - val binding = DataBindingUtil.inflate( - inflater, - layoutId, - viewGroup, - false - ) - binding.setVariable(BR.data, entry) - binding.setVariable(BR.longClickListener, onLongClick) - binding.setVariable(BR.parent, parent) - - // This is a bit hacky... - if (viewGroup.context is LifecycleOwner) { - binding.lifecycleOwner = viewGroup.context as? LifecycleOwner - } - - viewGroup.addView(binding.root) - } - } -} - -@BindingAdapter("entries", "layout") -fun setEntries( - viewGroup: ViewGroup, - entries: List?, - layoutId: Int -) { - setEntries(viewGroup, entries, layoutId, null, null) -} - -@BindingAdapter("entries", "layout", "onLongClick") -fun setEntries( - viewGroup: ViewGroup, - entries: List?, - layoutId: Int, - onLongClick: View.OnLongClickListener? -) { - setEntries(viewGroup, entries, layoutId, onLongClick, null) -} - -@BindingAdapter("entries", "layout", "parent") -fun setEntries( - viewGroup: ViewGroup, - entries: List?, - layoutId: Int, - parent: Any? -) { - setEntries(viewGroup, entries, layoutId, null, parent) -} - -@BindingAdapter("android:scaleType") -fun setImageViewScaleType(imageView: ImageView, scaleType: ImageView.ScaleType) { - imageView.scaleType = scaleType -} - -@BindingAdapter("coilRounded") -fun loadRoundImageWithCoil(imageView: ImageView, path: String?) { - if (!path.isNullOrEmpty() && FileUtils.isExtensionImage(path)) { - imageView.load(path) { - transformations(CircleCropTransformation()) - } - } else { - Log.w("[Data Binding] [Coil] Can't load $path") - } -} - -@BindingAdapter("coil") -fun loadImageWithCoil(imageView: ImageView, path: String?) { - if (!path.isNullOrEmpty() && FileUtils.isExtensionImage(path)) { - if (corePreferences.vfsEnabled && path.startsWith(corePreferences.vfsCachePath)) { - imageView.load(path) { - diskCachePolicy(CachePolicy.DISABLED) - listener( - onError = { _, result -> - Log.e( - "[Data Binding] [VFS] [Coil] Error loading [$path]: ${result.throwable}" - ) - } - ) - } - } else { - imageView.load(path) { - listener( - onError = { _, result -> - Log.e("[Data Binding] [Coil] Error loading [$path]: ${result.throwable}") - } - ) - } - } - } else if (path != null) { - Log.w("[Data Binding] [Coil] Can't load $path") - } -} - -private suspend fun loadContactPictureWithCoil( - imageView: ImageView, - contact: ContactDataInterface?, - useThumbnail: Boolean, - size: Int = 0, - textSize: Int = 0, - color: Int = 0, - textColor: Int = 0, - defaultAvatar: String? = null -) { - imageView.dispose() - - val context = imageView.context - if (contact == null) { - if (defaultAvatar != null) { - imageView.load(defaultAvatar) { - transformations(CircleCropTransformation()) - } - } else { - imageView.load(R.drawable.icon_single_contact_avatar) - } - } else if (contact.showGroupChatAvatar) { - imageView.load( - AppCompatResources.getDrawable(context, R.drawable.icon_multiple_contacts_avatar) - ) - } else { - val displayName = contact.contact.value?.name ?: contact.displayName.value.orEmpty() - val source = contact.contact.value?.getPictureUri(useThumbnail) - val sourceStr = source.toString() - val base64 = if (ImageUtils.isBase64(sourceStr)) { - Log.d("[Coil] Picture URI is base64 encoded") - ImageUtils.getBase64ImageFromString(sourceStr) - } else { - null - } - - imageView.load(base64 ?: source) { - transformations(CircleCropTransformation()) - error( - if (displayName.isEmpty() || AppUtils.getInitials(displayName) == "+") { - AppCompatResources.getDrawable(context, R.drawable.icon_single_contact_avatar) - } else { - coroutineScope { - withContext(Dispatchers.IO) { - val builder = ContactAvatarGenerator(context) - builder.setLabel(displayName) - if (size > 0) { - builder.setAvatarSize(AppUtils.getDimension(size).toInt()) - } - if (textSize > 0) { - builder.setTextSize(AppUtils.getDimension(textSize)) - } - if (color > 0) { - builder.setBackgroundColorAttribute(color) - } - if (textColor > 0) { - builder.setTextColorResource(textColor) - } - builder.build() - } - } - } - ) - } - } -} - -@BindingAdapter("coilContact") -fun loadContactPictureWithCoil(imageView: ImageView, contact: ContactDataInterface?) { - val coroutineScope = contact?.coroutineScope ?: coreContext.coroutineScope - coroutineScope.launch { - withContext(Dispatchers.Main) { - loadContactPictureWithCoil(imageView, contact, true) - } - } -} - -@BindingAdapter("coilContactBig") -fun loadBigContactPictureWithCoil(imageView: ImageView, contact: ContactDataInterface?) { - val coroutineScope = contact?.coroutineScope ?: coreContext.coroutineScope - coroutineScope.launch { - withContext(Dispatchers.Main) { - loadContactPictureWithCoil( - imageView, - contact, - false, - R.dimen.contact_avatar_big_size, - R.dimen.contact_avatar_text_big_size - ) - } - } -} - -@BindingAdapter("coilVoipContactAlt") -fun loadVoipContactPictureWithCoilAlt(imageView: ImageView, contact: ContactDataInterface?) { - val coroutineScope = contact?.coroutineScope ?: coreContext.coroutineScope - coroutineScope.launch { - withContext(Dispatchers.Main) { - loadContactPictureWithCoil( - imageView, - contact, - false, - R.dimen.voip_contact_avatar_max_size, - R.dimen.voip_contact_avatar_text_size, - R.attr.voipParticipantBackgroundColor, - R.color.white_color - ) - } - } -} - -@BindingAdapter("coilVoipContact") -fun loadVoipContactPictureWithCoil(imageView: ImageView, contact: ContactDataInterface?) { - val coroutineScope = contact?.coroutineScope ?: coreContext.coroutineScope - coroutineScope.launch { - withContext(Dispatchers.Main) { - loadContactPictureWithCoil( - imageView, - contact, - false, - R.dimen.voip_contact_avatar_max_size, - R.dimen.voip_contact_avatar_text_size, - R.attr.voipBackgroundColor, - R.color.white_color - ) - } - } -} - -@BindingAdapter("coilSelfAvatar") -fun loadSelfAvatarWithCoil(imageView: ImageView, contact: ContactDataInterface?) { - val coroutineScope = contact?.coroutineScope ?: coreContext.coroutineScope - coroutineScope.launch { - withContext(Dispatchers.Main) { - loadContactPictureWithCoil( - imageView, - contact, - false, - R.dimen.voip_contact_avatar_max_size, - R.dimen.voip_contact_avatar_text_size, - R.attr.voipBackgroundColor, - R.color.white_color, - corePreferences.defaultAccountAvatarPath - ) - } - } -} - -@BindingAdapter("coilGoneIfError") -fun loadAvatarWithCoil(imageView: ImageView, path: String?) { - if (path != null) { - imageView.visibility = View.VISIBLE - imageView.load(path) { - transformations(CircleCropTransformation()) - listener( - onError = { _, result -> - Log.e("[Data Binding] [Coil] Error loading [$path]: ${result.throwable}") - imageView.visibility = View.GONE - }, - onSuccess = { _, _ -> - imageView.visibility = View.VISIBLE - } - ) - } - } else { - imageView.visibility = View.GONE - } -} - -@BindingAdapter("coilVideoPreview") -fun loadVideoPreview(imageView: ImageView, path: String?) { - if (!path.isNullOrEmpty() && FileUtils.isExtensionVideo(path)) { - imageView.load(path) { - videoFrameMillis(0) - listener( - onError = { _, result -> - Log.e( - "[Data Binding] [Coil] Error getting preview picture from video? [$path]: ${result.throwable}" - ) - }, - onSuccess = { _, _ -> - // Display "play" button above video preview - LayoutInflater.from(imageView.context).inflate( - R.layout.video_play_button, - imageView.parent as ViewGroup - ) - } - ) - } - } -} - -@BindingAdapter("assistantPhoneNumberValidation") -fun addPhoneNumberEditTextValidation(editText: EditText, enabled: Boolean) { - if (!enabled) return - editText.addTextChangedListener(object : TextWatcher { - override fun afterTextChanged(s: Editable?) { - when { - s?.matches(Regex("\\d+")) == false -> - editText.error = - editText.context.getString( - R.string.assistant_error_phone_number_invalid_characters - ) - } - } - - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} - - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} - }) -} - -@BindingAdapter("assistantPhoneNumberPrefixValidation") -fun addPrefixEditTextValidation(editText: EditText, enabled: Boolean) { - if (!enabled) return - editText.addTextChangedListener(object : TextWatcher { - override fun afterTextChanged(s: Editable?) { - if ((s?.length ?: 0) > 1) { - val dialPlan = PhoneNumberUtils.getDialPlanFromCountryCallingPrefix( - s.toString().substring(1) - ) - if (dialPlan == null) { - editText.error = - editText.context.getString( - R.string.assistant_error_invalid_international_prefix - ) - } - } - } - - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} - - @SuppressLint("SetTextI18n") - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { - if (s.isNullOrEmpty() || !s.startsWith("+")) { - editText.setText("+$s") - } - } - }) -} - -@BindingAdapter("assistantUsernameValidation") -fun addUsernameEditTextValidation(editText: EditText, enabled: Boolean) { - if (!enabled) return - val usernameRegexp = corePreferences.config.getString( - "assistant", - "username_regex", - "^[a-z0-9+_.\\-]*\$" - )!! - val usernameMaxLength = corePreferences.config.getInt("assistant", "username_max_length", 64) - editText.addTextChangedListener(object : TextWatcher { - override fun afterTextChanged(s: Editable?) { - when { - s?.matches(Regex(usernameRegexp)) == false -> - editText.error = - editText.context.getString( - R.string.assistant_error_username_invalid_characters - ) - (s?.length ?: 0) > usernameMaxLength -> { - editText.error = - editText.context.getString(R.string.assistant_error_username_too_long) - } - } - } - - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} - - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} - }) -} - -@BindingAdapter("emailConfirmationValidation") -fun addEmailEditTextValidation(editText: EditText, enabled: Boolean) { - if (!enabled) return - editText.addTextChangedListener(object : TextWatcher { - override fun afterTextChanged(s: Editable?) {} - - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} - - override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { - if (!Patterns.EMAIL_ADDRESS.matcher(s).matches()) { - editText.error = - editText.context.getString(R.string.assistant_error_invalid_email_address) - } - } - }) -} - -@BindingAdapter("urlConfirmationValidation") -fun addUrlEditTextValidation(editText: EditText, enabled: Boolean) { - if (!enabled) return - editText.addTextChangedListener(object : TextWatcher { - override fun afterTextChanged(s: Editable?) {} - - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} - - override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { - if (!Patterns.WEB_URL.matcher(s).matches()) { - editText.error = - editText.context.getString(R.string.assistant_remote_provisioning_wrong_format) - } - } - }) -} - -@BindingAdapter("passwordConfirmationValidation") -fun addPasswordConfirmationEditTextValidation(password: EditText, passwordConfirmation: EditText) { - password.addTextChangedListener(object : TextWatcher { - override fun afterTextChanged(s: Editable?) {} - - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} - - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { - if (passwordConfirmation.text == null || s == null || passwordConfirmation.text.toString() != s.toString()) { - passwordConfirmation.error = - passwordConfirmation.context.getString( - R.string.assistant_error_passwords_dont_match - ) - } else { - passwordConfirmation.error = null // To clear other edit text field error - } - } - }) - - passwordConfirmation.addTextChangedListener(object : TextWatcher { - override fun afterTextChanged(s: Editable?) {} - - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} - - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { - if (password.text == null || s == null || password.text.toString() != s.toString()) { - passwordConfirmation.error = - passwordConfirmation.context.getString( - R.string.assistant_error_passwords_dont_match - ) - } - } - }) -} - -@BindingAdapter("errorMessage") -fun setEditTextError(editText: EditText, error: String?) { - if (error != editText.error) { - editText.error = error - } -} - -@InverseBindingAdapter(attribute = "errorMessage") -fun getEditTextError(editText: EditText): String? { - return editText.error?.toString() -} - -@BindingAdapter("errorMessageAttrChanged") -fun setEditTextErrorListener(editText: EditText, attrChange: InverseBindingListener) { - editText.addTextChangedListener(object : TextWatcher { - override fun afterTextChanged(s: Editable?) { - attrChange.onChange() - } - - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { - editText.error = null - attrChange.onChange() - } - - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} - }) -} - -@BindingAdapter("max") -fun VoiceRecordProgressBar.setProgressMax(max: Int) { - setMax(max) -} - -@BindingAdapter("android:progress") -fun VoiceRecordProgressBar.setPrimaryProgress(progress: Int) { - setProgress(progress) -} - -@BindingAdapter("android:secondaryProgress") -fun VoiceRecordProgressBar.setSecProgress(progress: Int) { - setSecondaryProgress(progress) -} - -@BindingAdapter("secondaryProgressTint") -fun VoiceRecordProgressBar.setSecProgressTint(color: Int) { - setSecondaryProgressTint(color) -} - -@BindingAdapter("android:layout_margin") -fun setConstraintLayoutMargins(view: View, margins: Float) { - val params = view.layoutParams as ConstraintLayout.LayoutParams - val m = margins.toInt() - params.setMargins(m, m, m, m) - view.layoutParams = params -} - -@BindingAdapter("android:layout_marginTop") -fun setConstraintLayoutTopMargin(view: View, margins: Float) { - val params = view.layoutParams as ConstraintLayout.LayoutParams - val m = margins.toInt() - params.setMargins(params.leftMargin, m, params.rightMargin, params.bottomMargin) - view.layoutParams = params -} - -@BindingAdapter("android:layout_marginBottom") -fun setConstraintLayoutBottomMargin(view: View, margins: Float) { - val params = view.layoutParams as ConstraintLayout.LayoutParams - val m = margins.toInt() - params.setMargins(params.leftMargin, params.topMargin, params.rightMargin, m) - view.layoutParams = params -} - -@BindingAdapter("android:layout_marginEnd") -fun setConstraintLayoutEndMargin(view: View, margins: Float) { - val params = view.layoutParams as ConstraintLayout.LayoutParams - val m = margins.toInt() - params.marginEnd = m - view.layoutParams = params -} - -@BindingAdapter("android:onTouch") -fun View.setTouchListener(listener: View.OnTouchListener?) { - if (listener != null) { - setOnTouchListener(listener) - } -} - -@BindingAdapter("entries") -fun Spinner.setEntries(entries: List?) { - if (entries != null) { - val arrayAdapter = ArrayAdapter(context, android.R.layout.simple_spinner_item, entries) - arrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) - adapter = arrayAdapter - } -} - -@BindingAdapter("selectedValueAttrChanged") -fun Spinner.setInverseBindingListener(listener: InverseBindingListener) { - onItemSelectedListener = object : AdapterView.OnItemSelectedListener { - override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) { - if (tag != position) { - listener.onChange() - } - } - - override fun onNothingSelected(parent: AdapterView<*>) {} - } -} - -@BindingAdapter("selectedValue") -fun Spinner.setSelectedValue(value: Any?) { - if (adapter != null) { - val position = (adapter as ArrayAdapter).getPosition(value) - setSelection(position, false) - tag = position - } -} - -@InverseBindingAdapter(attribute = "selectedValue", event = "selectedValueAttrChanged") -fun Spinner.getSelectedValue(): Any? { - return selectedItem -} - -@BindingAdapter("participantTextureView") -fun setParticipantTextureView( - textureView: TextureView, - conferenceParticipantData: ConferenceParticipantDeviceData -) { - conferenceParticipantData.setTextureView(textureView) -} - -@BindingAdapter("itemCount") -fun ScrollDotsView.setItems(count: Int) { - setItemCount(count) -} - -@BindingAdapter("selectedDot") -fun ScrollDotsView.setSelectedIndex(index: Int) { - setSelectedDot(index) -} - -@BindingAdapter("presenceIcon") -fun ImageView.setPresenceIcon(presence: ConsolidatedPresence?) { - if (presence == null) return - - val icon = when (presence) { - ConsolidatedPresence.Online -> R.drawable.led_online - ConsolidatedPresence.DoNotDisturb -> R.drawable.led_do_not_disturb - ConsolidatedPresence.Busy -> R.drawable.led_away - else -> R.drawable.led_not_registered - } - setImageResource(icon) - - val contentDescription = when (presence) { - ConsolidatedPresence.Online -> AppUtils.getString( - R.string.content_description_presence_online - ) - ConsolidatedPresence.DoNotDisturb -> AppUtils.getString( - R.string.content_description_presence_do_not_disturb - ) - else -> AppUtils.getString(R.string.content_description_presence_offline) - } - setContentDescription(contentDescription) -} - -interface EmojiPickedListener { - fun onEmojiPicked(item: EmojiViewItem) -} - -@BindingAdapter("emojiPickedListener") -fun EmojiPickerView.setEmojiPickedListener(listener: EmojiPickedListener) { - setOnEmojiPickedListener { emoji -> - listener.onEmojiPicked(emoji) - } -} diff --git a/app/src/main/java/org/linphone/utils/DialogUtils.kt b/app/src/main/java/org/linphone/utils/DialogUtils.kt deleted file mode 100644 index f88a958c1..000000000 --- a/app/src/main/java/org/linphone/utils/DialogUtils.kt +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.utils - -import android.app.Dialog -import android.content.Context -import android.graphics.drawable.ColorDrawable -import android.graphics.drawable.Drawable -import android.view.LayoutInflater -import android.view.Window -import android.view.WindowManager -import androidx.core.content.ContextCompat -import androidx.databinding.DataBindingUtil -import org.linphone.R -import org.linphone.activities.main.viewmodels.DialogViewModel -import org.linphone.databinding.DialogBinding -import org.linphone.databinding.VoipDialogBinding - -class DialogUtils { - companion object { - fun getDialog(context: Context, viewModel: DialogViewModel): Dialog { - val dialog = Dialog(context) - dialog.requestWindowFeature(Window.FEATURE_NO_TITLE) - - val binding: DialogBinding = DataBindingUtil.inflate( - LayoutInflater.from(context), - R.layout.dialog, - null, - false - ) - binding.viewModel = viewModel - dialog.setContentView(binding.root) - - val d: Drawable = ColorDrawable( - ContextCompat.getColor(dialog.context, R.color.dark_grey_color) - ) - d.alpha = 200 - dialog.window - ?.setLayout( - WindowManager.LayoutParams.MATCH_PARENT, - WindowManager.LayoutParams.MATCH_PARENT - ) - dialog.window?.setBackgroundDrawable(d) - return dialog - } - - fun getVoipDialog(context: Context, viewModel: DialogViewModel): Dialog { - val dialog = Dialog(context, R.style.AppTheme) - dialog.requestWindowFeature(Window.FEATURE_NO_TITLE) - - val binding: VoipDialogBinding = DataBindingUtil.inflate( - LayoutInflater.from(context), - R.layout.voip_dialog, - null, - false - ) - binding.viewModel = viewModel - dialog.setContentView(binding.root) - - val d: Drawable = ColorDrawable( - ContextCompat.getColor(dialog.context, R.color.voip_dark_gray) - ) - d.alpha = 166 - dialog.window - ?.setLayout( - WindowManager.LayoutParams.MATCH_PARENT, - WindowManager.LayoutParams.MATCH_PARENT - ) - dialog.window?.setBackgroundDrawable(d) - return dialog - } - } -} diff --git a/app/src/main/java/org/linphone/utils/Event.kt b/app/src/main/java/org/linphone/utils/Event.kt deleted file mode 100644 index 0e3a53f19..000000000 --- a/app/src/main/java/org/linphone/utils/Event.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.utils - -import java.util.concurrent.atomic.AtomicBoolean - -/** - * This class allows to limit the number of notification for an event. - * The first one to consume the event will stop the dispatch. - */ -open class Event(private val content: T) { - private val handled = AtomicBoolean(false) - - fun consumed(): Boolean { - return handled.get() - } - - fun consume(handleContent: (T) -> Unit) { - if (!handled.get()) { - handled.set(true) - handleContent(content) - } - } -} diff --git a/app/src/main/java/org/linphone/utils/FileUtils.kt b/app/src/main/java/org/linphone/utils/FileUtils.kt deleted file mode 100644 index 7300364c1..000000000 --- a/app/src/main/java/org/linphone/utils/FileUtils.kt +++ /dev/null @@ -1,562 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.utils - -import android.app.Activity -import android.content.ActivityNotFoundException -import android.content.Context -import android.content.Intent -import android.database.CursorIndexOutOfBoundsException -import android.net.Uri -import android.os.Environment -import android.os.ParcelFileDescriptor -import android.os.Process.myUid -import android.provider.OpenableColumns -import android.system.Os.fstat -import android.webkit.MimeTypeMap -import androidx.core.content.FileProvider -import java.io.* -import java.text.SimpleDateFormat -import java.util.* -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.withContext -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.core.tools.Log - -class FileUtils { - enum class MimeType { - PlainText, - Pdf, - Image, - Video, - Audio, - Unknown - } - - companion object { - fun getNameFromFilePath(filePath: String): String { - var name = filePath - val i = filePath.lastIndexOf('/') - if (i > 0) { - name = filePath.substring(i + 1) - } - return name - } - - fun getExtensionFromFileName(fileName: String): String { - var extension = MimeTypeMap.getFileExtensionFromUrl(fileName) - if (extension.isNullOrEmpty()) { - val i = fileName.lastIndexOf('.') - if (i > 0) { - extension = fileName.substring(i + 1) - } - } - - return extension.lowercase(Locale.getDefault()) - } - - fun getMimeType(type: String?): MimeType { - if (type.isNullOrEmpty()) return MimeType.Unknown - return when { - type.startsWith("image/") -> MimeType.Image - type.startsWith("text/plain") -> MimeType.PlainText - type.startsWith("video/") -> MimeType.Video - type.startsWith("audio/") -> MimeType.Audio - type.startsWith("application/pdf") -> MimeType.Pdf - else -> MimeType.Unknown - } - } - - fun isExtensionImage(path: String): Boolean { - val extension = getExtensionFromFileName(path) - val type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) - return getMimeType(type) == MimeType.Image - } - - fun isExtensionVideo(path: String): Boolean { - val extension = getExtensionFromFileName(path) - val type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) - return getMimeType(type) == MimeType.Video - } - - fun clearExistingPlainFiles() { - val dir = File(corePreferences.vfsCachePath) - if (dir.exists()) { - for (file in dir.listFiles().orEmpty()) { - Log.w( - "[File Utils] [VFS] Found forgotten plain file [${file.path}], deleting it" - ) - deleteFile(file.path) - } - } - } - - fun getFileStorageDir(isPicture: Boolean = false): File { - var path: File? = null - if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) { - Log.w("[File Utils] External storage is mounted") - var directory = Environment.DIRECTORY_DOWNLOADS - if (isPicture) { - Log.w("[File Utils] Using pictures directory instead of downloads") - directory = Environment.DIRECTORY_PICTURES - } - path = coreContext.context.getExternalFilesDir(directory) - } - - val returnPath: File = path ?: coreContext.context.filesDir - if (path == null) { - Log.w( - "[File Utils] Couldn't get external storage path, using internal" - ) - } - - return returnPath - } - - private fun getFileStorageCacheDir(fileName: String): File { - val path = coreContext.context.cacheDir - Log.i("[File Utils] Cache directory is: $path") - - var file = File(path, fileName) - var prefix = 1 - while (file.exists()) { - file = File(path, prefix.toString() + "_" + fileName) - Log.w("[File Utils] File with that name already exists, renamed to ${file.name}") - prefix += 1 - } - return file - } - - fun getFileStoragePath(fileName: String): File { - val path = getFileStorageDir(isExtensionImage(fileName)) - var file = File(path, fileName) - - var prefix = 1 - while (file.exists()) { - file = File(path, prefix.toString() + "_" + fileName) - Log.w("[File Utils] File with that name already exists, renamed to ${file.name}") - prefix += 1 - } - return file - } - - suspend fun getFilesPathFromPickerIntent(data: Intent?, temporaryImageFilePath: File?): List { - var filePath: String? = null - if (data != null) { - val clipData = data.clipData - if (clipData != null && clipData.itemCount > 1) { // Multiple selection - Log.i("[File Utils] Found ${clipData.itemCount} elements") - val list = arrayListOf() - for (i in 0 until clipData.itemCount) { - val dataUri = clipData.getItemAt(i).uri - if (dataUri != null) { - filePath = dataUri.toString() - Log.i("[File Utils] Using data URI $filePath") - } - filePath = copyToLocalStorage(filePath) - if (filePath != null) list.add(filePath) - } - return list - } else { // Single selection - val dataUri = if (clipData != null && clipData.itemCount == 1) { - clipData.getItemAt(0).uri - } else { - data.data - } - if (dataUri != null) { - filePath = dataUri.toString() - Log.i("[File Utils] Using data URI $filePath") - } else if (temporaryImageFilePath?.exists() == true) { - filePath = temporaryImageFilePath.absolutePath - Log.i("[File Utils] Data URI is null, using $filePath") - } - filePath = copyToLocalStorage(filePath) - if (filePath != null) return arrayListOf(filePath) - } - } else if (temporaryImageFilePath?.exists() == true) { - filePath = temporaryImageFilePath.absolutePath - Log.i("[File Utils] Data is null, using $filePath") - filePath = copyToLocalStorage(filePath) - if (filePath != null) return arrayListOf(filePath) - } - return arrayListOf() - } - - suspend fun getFilePathFromPickerIntent(data: Intent?, temporaryImageFilePath: File?): String? { - var filePath: String? = null - if (data != null) { - val clipData = data.clipData - if (clipData != null && clipData.itemCount > 1) { // Multiple selection - Log.e("[File Utils] Expecting only one file, got ${clipData.itemCount}") - } else { // Single selection - val dataUri = if (clipData != null && clipData.itemCount == 1) { - clipData.getItemAt(0).uri - } else { - data.data - } - if (dataUri != null) { - filePath = dataUri.toString() - Log.i("[File Utils] Using data URI $filePath") - } else if (temporaryImageFilePath?.exists() == true) { - filePath = temporaryImageFilePath.absolutePath - Log.i("[File Utils] Data URI is null, using $filePath") - } - } - } else if (temporaryImageFilePath?.exists() == true) { - filePath = temporaryImageFilePath.absolutePath - Log.i("[File Utils] Data is null, using $filePath") - } - return copyToLocalStorage(filePath) - } - - suspend fun copyToLocalStorage(filePath: String?): String? { - if (filePath != null) { - val uriToParse = Uri.parse(filePath) - if (filePath.startsWith("content://com.android.contacts/contacts/lookup/")) { - Log.i("[File Utils] Contact sharing URI detected") - return ContactUtils.getContactVcardFilePath(uriToParse) - } else if (filePath.startsWith("content://") || - filePath.startsWith("file://") - ) { - val result = getFilePath(coreContext.context, uriToParse) - Log.i( - "[File Utils] Path was using a content or file scheme, real path is: $result" - ) - if (result == null) { - Log.e("[File Utils] Failed to get access to file $uriToParse") - } - return result - } - } - return filePath - } - - fun deleteFile(filePath: String) { - val file = File(filePath) - if (file.exists()) { - try { - if (file.delete()) { - Log.i("[File Utils] Deleted $filePath") - } else { - Log.e("[File Utils] Can't delete $filePath") - } - } catch (e: Exception) { - Log.e("[File Utils] Can't delete $filePath, exception: $e") - } - } else { - Log.e("[File Utils] File $filePath doesn't exists") - } - } - - suspend fun getFilePath(context: Context, uri: Uri): String? { - var result: String? = null - val name: String = getNameFromUri(uri, context) - - try { - if (fstat( - ParcelFileDescriptor.open( - File(uri.path), - ParcelFileDescriptor.MODE_READ_ONLY - ).fileDescriptor - ).st_uid != myUid() - ) { - Log.e("[File Utils] File descriptor UID different from our, denying copy!") - return result - } - } catch (e: Exception) { - Log.e("[File Utils] Can't check file ownership: ", e) - } - - try { - val localFile: File = createFile(name) - val remoteFile = - context.contentResolver.openInputStream(uri) - Log.i( - "[File Utils] Trying to copy file from " + - uri.toString() + - " to local file " + - localFile.absolutePath - ) - coroutineScope { - val deferred = async { copyToFile(remoteFile, localFile) } - if (deferred.await()) { - Log.i("[File Utils] Copy successful") - result = localFile.absolutePath - } else { - Log.e("[File Utils] Copy failed") - } - withContext(Dispatchers.IO) { - remoteFile?.close() - } - } - } catch (e: IOException) { - Log.e("[File Utils] getFilePath exception: ", e) - } - - return result - } - - private fun getNameFromUri(uri: Uri, context: Context): String { - var name = "" - if (uri.scheme == "content") { - val returnCursor = - context.contentResolver.query(uri, null, null, null, null) - if (returnCursor != null) { - returnCursor.moveToFirst() - val nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) - if (nameIndex != -1) { - try { - val displayName = returnCursor.getString(nameIndex) - if (displayName != null) { - name = displayName - } else { - Log.e( - "[File Utils] Failed to get the display name for URI $uri, returned value is null" - ) - } - } catch (e: CursorIndexOutOfBoundsException) { - Log.e( - "[File Utils] Failed to get the display name for URI $uri, exception is $e" - ) - } - } else { - Log.e("[File Utils] Couldn't get DISPLAY_NAME column index for URI: $uri") - } - returnCursor.close() - } - } else if (uri.scheme == "file") { - name = uri.lastPathSegment ?: "" - } - return name - } - - suspend fun copyFileTo(filePath: String, outputStream: OutputStream?): Boolean { - if (outputStream == null) { - Log.e("[File Utils] Can't copy file $filePath to given null output stream") - return false - } - - val file = File(filePath) - if (!file.exists()) { - Log.e("[File Utils] Can't copy file $filePath, it doesn't exists") - return false - } - - try { - withContext(Dispatchers.IO) { - val inputStream = FileInputStream(file) - val buffer = ByteArray(4096) - var bytesRead: Int - while (inputStream.read(buffer).also { bytesRead = it } >= 0) { - outputStream.write(buffer, 0, bytesRead) - } - } - return true - } catch (e: IOException) { - Log.e("[File Utils] copyFileTo exception: $e") - } - return false - } - - private suspend fun copyToFile(inputStream: InputStream?, destFile: File?): Boolean { - if (inputStream == null || destFile == null) return false - try { - withContext(Dispatchers.IO) { - FileOutputStream(destFile).use { out -> - val buffer = ByteArray(4096) - var bytesRead: Int - while (inputStream.read(buffer).also { bytesRead = it } >= 0) { - out.write(buffer, 0, bytesRead) - } - } - } - return true - } catch (e: IOException) { - Log.e("[File Utils] copyToFile exception: $e") - } - return false - } - - suspend fun copyFileToCache(plainFilePath: String): String? { - val cacheFile = getFileStorageCacheDir(getNameFromFilePath(plainFilePath)) - try { - withContext(Dispatchers.IO) { - FileOutputStream(cacheFile).use { out -> - copyFileTo(plainFilePath, out) - } - } - return cacheFile.absolutePath - } catch (e: IOException) { - Log.e("[File Utils] copyFileToCache exception: $e") - } - return null - } - - private fun createFile(file: String): File { - var fileName = file - - if (fileName.isEmpty()) fileName = getStartDate() - if (!fileName.contains(".")) { - fileName = "$fileName.unknown" - } - - return getFileStoragePath(fileName) - } - - private fun getStartDate(): String { - return try { - SimpleDateFormat("yyyyMMdd_HHmmss", Locale.ROOT).format(Date()) - } catch (e: RuntimeException) { - SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) - } - } - - fun getPublicFilePath(context: Context, path: String): Uri { - val contentUri: Uri - when { - path.startsWith("file://") -> { - val file = File(path.substring("file://".length)) - contentUri = FileProvider.getUriForFile( - context, - context.getString(R.string.file_provider), - file - ) - } - path.startsWith("content://") -> { - contentUri = Uri.parse(path) - } - else -> { - val file = File(path) - contentUri = try { - FileProvider.getUriForFile( - context, - context.getString(R.string.file_provider), - file - ) - } catch (e: Exception) { - Log.e( - "[Chat Message] Couldn't get URI for file $file using file provider ${context.getString( - R.string.file_provider - )}" - ) - Uri.parse(path) - } - } - } - return contentUri - } - - fun openFileInThirdPartyApp( - activity: Activity, - contentFilePath: String, - newTask: Boolean = false - ): Boolean { - val intent = Intent(Intent.ACTION_VIEW) - val contentUri: Uri = getPublicFilePath(activity, contentFilePath) - val filePath: String = contentUri.toString() - Log.i("[File Viewer] Trying to open file: $filePath") - var type: String? = null - val extension = getExtensionFromFileName(filePath) - - if (extension.isNotEmpty()) { - Log.i("[File Viewer] Found extension $extension") - type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) - } else { - Log.e("[File Viewer] Couldn't find extension") - } - - if (type != null) { - Log.i("[File Viewer] Found matching MIME type $type") - } else { - type = if (extension == "linphonerc") { - "text/plain" - } else { - "file/$extension" - } - Log.w( - "[File Viewer] Can't get MIME type from extension: $extension, will use $type" - ) - } - - intent.setDataAndType(contentUri, type) - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - if (newTask) { - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - } - - try { - activity.startActivity(intent) - return true - } catch (anfe: ActivityNotFoundException) { - Log.e("[File Viewer] Can't open file in third party app: $anfe") - } - return false - } - - fun openMediaStoreFile( - activity: Activity, - contentFilePath: String, - newTask: Boolean = false - ): Boolean { - val intent = Intent(Intent.ACTION_VIEW) - val contentUri: Uri = Uri.parse(contentFilePath) - intent.data = contentUri - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - if (newTask) { - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - } - - try { - activity.startActivity(intent) - return true - } catch (anfe: ActivityNotFoundException) { - Log.e("[File Viewer] Can't open media store export in third party app: $anfe") - } - return false - } - - fun writeIntoFile(bytes: ByteArray, file: File) { - val inStream = ByteArrayInputStream(bytes) - val outStream = FileOutputStream(file) - - val buffer = ByteArray(1024) - var read: Int - while (inStream.read(buffer).also { read = it } != -1) { - outStream.write(buffer, 0, read) - } - - inStream.close() - outStream.flush() - outStream.close() - } - - fun countFilesInDirectory(path: String): Int { - val dir = File(path) - if (dir.exists()) { - return dir.listFiles().orEmpty().size - } - return -1 - } - } -} diff --git a/app/src/main/java/org/linphone/utils/ImageUtils.kt b/app/src/main/java/org/linphone/utils/ImageUtils.kt deleted file mode 100644 index 44f0c650b..000000000 --- a/app/src/main/java/org/linphone/utils/ImageUtils.kt +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.utils - -import android.content.Context -import android.graphics.* -import android.net.Uri -import android.util.Base64 -import java.io.FileNotFoundException -import org.linphone.compatibility.Compatibility -import org.linphone.core.tools.Log - -class ImageUtils { - companion object { - private const val BASE64_REGEX = "^data:image\\/(gif|png|jpeg|bmp|webp|svg\\+xml)(;charset=utf-8)?;base64,[A-Za-z0-9+\\/]+={0,2}\$" - - fun isBase64(source: String): Boolean { - return source.matches(Regex(BASE64_REGEX)) - } - - fun getBase64ImageFromString(base64: String): ByteArray? { - val substring = base64.substring(base64.indexOf(",") + 1) - return Base64.decode(substring, Base64.DEFAULT) - } - - fun getRoundBitmapFromUri( - context: Context, - fromPictureUri: Uri? - ): Bitmap? { - var bm: Bitmap? = null - if (fromPictureUri != null) { - bm = try { - // We make a copy to ensure Bitmap will be Software and not Hardware, required for shortcuts - Compatibility.getBitmapFromUri(context, fromPictureUri).copy( - Bitmap.Config.ARGB_8888, - true - ) - } catch (fnfe: FileNotFoundException) { - return null - } catch (e: Exception) { - Log.e("[Image Utils] Failed to get bitmap from URI [$fromPictureUri]: $e") - return null - } - } - if (bm != null) { - val roundBm = getRoundBitmap(bm) - if (roundBm != null) { - bm.recycle() - return roundBm - } - } - return bm - } - - fun rotateImage(source: Bitmap, angle: Float): Bitmap { - val matrix = Matrix() - matrix.postRotate(angle) - val rotatedBitmap = Bitmap.createBitmap( - source, - 0, - 0, - source.width, - source.height, - matrix, - true - ) - source.recycle() - return rotatedBitmap - } - - private fun getRoundBitmap(bitmap: Bitmap): Bitmap? { - val output = - Bitmap.createBitmap(bitmap.width, bitmap.height, Bitmap.Config.ARGB_8888) - val canvas = Canvas(output) - val color = -0xbdbdbe - val paint = Paint() - val rect = - Rect(0, 0, bitmap.width, bitmap.height) - paint.isAntiAlias = true - canvas.drawARGB(0, 0, 0, 0) - paint.color = color - canvas.drawCircle( - bitmap.width / 2.toFloat(), - bitmap.height / 2.toFloat(), - bitmap.width / 2.toFloat(), - paint - ) - paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN) - canvas.drawBitmap(bitmap, rect, rect, paint) - return output - } - } -} diff --git a/app/src/main/java/org/linphone/utils/LinphoneUtils.kt b/app/src/main/java/org/linphone/utils/LinphoneUtils.kt deleted file mode 100644 index 85efa1468..000000000 --- a/app/src/main/java/org/linphone/utils/LinphoneUtils.kt +++ /dev/null @@ -1,346 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.utils - -import android.annotation.SuppressLint -import android.content.Context -import android.net.ConnectivityManager -import android.net.NetworkInfo -import android.telephony.TelephonyManager.* -import java.security.MessageDigest -import java.security.NoSuchAlgorithmException -import java.text.DateFormat -import java.text.SimpleDateFormat -import java.util.* -import okhttp3.internal.and -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.LinphoneApplication.Companion.corePreferences -import org.linphone.R -import org.linphone.core.* -import org.linphone.core.tools.Log - -/** - * Various utility methods for Linphone SDK - */ -class LinphoneUtils { - companion object { - private const val RECORDING_DATE_PATTERN = "dd-MM-yyyy-HH-mm-ss" - - fun getDisplayName(address: Address?): String { - if (address == null) return "[null]" - if (address.displayName == null) { - val account = coreContext.core.accountList.find { account -> - account.params.identityAddress?.asStringUriOnly() == address.asStringUriOnly() - } - val localDisplayName = account?.params?.identityAddress?.displayName - // Do not return an empty local display name - if (!localDisplayName.isNullOrEmpty()) { - return localDisplayName - } - } - // Do not return an empty display name - return address.displayName ?: address.username ?: address.asString() - } - - fun getDisplayableAddress(address: Address?): String { - if (address == null) return "[null]" - return if (corePreferences.replaceSipUriByUsername) { - address.username ?: address.asStringUriOnly() - } else { - val copy = address.clone() - copy.clean() // To remove gruu if any - copy.asStringUriOnly() - } - } - - fun getCleanedAddress(address: Address): Address { - // To remove the GRUU if any - val cleanAddress = address.clone() - cleanAddress.clean() - return cleanAddress - } - - fun getConferenceAddress(call: Call): Address? { - val remoteContact = call.remoteContact - val conferenceAddress = if (call.dir == Call.Dir.Incoming) { - if (remoteContact != null) { - coreContext.core.interpretUrl(remoteContact, false) - } else { - null - } - } else { - call.remoteAddress - } - return conferenceAddress - } - - fun getConferenceSubject(conference: Conference): String? { - return if (conference.subject.isNullOrEmpty()) { - val conferenceAddress = conference.conferenceAddress ?: return conference.subject - val conferenceInfo = coreContext.core.findConferenceInformationFromUri( - conferenceAddress - ) - if (conferenceInfo != null) { - conferenceInfo.subject - } else { - if (conference.me.isFocus) { - AppUtils.getString(R.string.conference_local_title) - } else { - AppUtils.getString(R.string.conference_default_title) - } - } - } else { - conference.subject - } - } - - fun isEndToEndEncryptedChatAvailable(): Boolean { - val core = coreContext.core - return core.isLimeX3DhEnabled && - (core.limeX3DhServerUrl != null || core.defaultAccount?.params?.limeServerUrl != null) && - core.defaultAccount?.params?.conferenceFactoryUri != null - } - - fun isGroupChatAvailable(): Boolean { - val core = coreContext.core - return core.defaultAccount?.params?.conferenceFactoryUri != null - } - - fun isRemoteConferencingAvailable(): Boolean { - val core = coreContext.core - return core.defaultAccount?.params?.audioVideoConferenceFactoryAddress != null - } - - fun createOneToOneChatRoom(participant: Address, isSecured: Boolean = false): ChatRoom? { - val core: Core = coreContext.core - val defaultAccount = core.defaultAccount - - val params = core.createDefaultChatRoomParams() - params.isGroupEnabled = false - params.backend = ChatRoom.Backend.Basic - if (isSecured) { - params.subject = AppUtils.getString(R.string.chat_room_dummy_subject) - params.isEncryptionEnabled = true - params.backend = ChatRoom.Backend.FlexisipChat - } - - val participants = arrayOf(participant) - - return core.searchChatRoom( - params, - defaultAccount?.params?.identityAddress, - null, - participants - ) - ?: core.createChatRoom( - params, - defaultAccount?.params?.identityAddress, - participants - ) - } - - fun getConferenceInvitationsChatRoomParams(): ChatRoomParams { - val chatRoomParams = coreContext.core.createDefaultChatRoomParams() - chatRoomParams.isGroupEnabled = false - if (isEndToEndEncryptedChatAvailable()) { - chatRoomParams.backend = ChatRoom.Backend.FlexisipChat - chatRoomParams.isEncryptionEnabled = true - } else { - chatRoomParams.backend = ChatRoom.Backend.Basic - chatRoomParams.isEncryptionEnabled = false - } - chatRoomParams.subject = "Meeting invitation" // Won't be used - return chatRoomParams - } - - fun deleteFilesAttachedToEventLog(eventLog: EventLog) { - if (eventLog.type == EventLog.Type.ConferenceChatMessage) { - val message = eventLog.chatMessage - if (message != null) deleteFilesAttachedToChatMessage(message) - } - } - - fun deleteFilesAttachedToChatMessage(chatMessage: ChatMessage) { - for (content in chatMessage.contents) { - val filePath = content.filePath - if (!filePath.isNullOrEmpty()) { - Log.i("[Linphone Utils] Deleting file $filePath") - FileUtils.deleteFile(filePath) - } - } - } - - fun getRecordingFilePathForAddress(address: Address): String { - val displayName = getDisplayName(address) - val dateFormat: DateFormat = SimpleDateFormat( - RECORDING_DATE_PATTERN, - Locale.getDefault() - ) - val fileName = "${displayName}_${dateFormat.format(Date())}.mkv" - return FileUtils.getFileStoragePath(fileName).absolutePath - } - - fun getRecordingFilePathForConference(subject: String?): String { - val dateFormat: DateFormat = SimpleDateFormat( - RECORDING_DATE_PATTERN, - Locale.getDefault() - ) - val fileName = if (subject.isNullOrEmpty()) { - "conference_${dateFormat.format(Date())}.mkv" - } else { - "${subject}_${dateFormat.format(Date())}.mkv" - } - return FileUtils.getFileStoragePath(fileName).absolutePath - } - - fun getRecordingDateFromFileName(name: String): Date { - return SimpleDateFormat(RECORDING_DATE_PATTERN, Locale.getDefault()).parse(name) - } - - @SuppressLint("MissingPermission") - fun checkIfNetworkHasLowBandwidth(context: Context): Boolean { - val connMgr = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager - val networkInfo: NetworkInfo? = connMgr.activeNetworkInfo - if (networkInfo != null && networkInfo.isConnected) { - if (networkInfo.type == ConnectivityManager.TYPE_MOBILE) { - return when (networkInfo.subtype) { - NETWORK_TYPE_EDGE, NETWORK_TYPE_GPRS, NETWORK_TYPE_IDEN -> true - else -> false - } - } - } - // In doubt return false - return false - } - - fun isCallLogMissed(callLog: CallLog): Boolean { - return ( - callLog.dir == Call.Dir.Incoming && - ( - callLog.status == Call.Status.Missed || - callLog.status == Call.Status.Aborted || - callLog.status == Call.Status.EarlyAborted - ) - ) - } - - fun getChatRoomId(room: ChatRoom): String { - return getChatRoomId(room.localAddress, room.peerAddress) - } - - fun getChatRoomId(localAddress: Address, remoteAddress: Address): String { - val localSipUri = localAddress.clone() - localSipUri.clean() - val remoteSipUri = remoteAddress.clone() - remoteSipUri.clean() - return "${localSipUri.asStringUriOnly()}~${remoteSipUri.asStringUriOnly()}" - } - - fun getAccountsNotHidden(): List { - val list = arrayListOf() - - for (account in coreContext.core.accountList) { - if (account.getCustomParam("hidden") != "1") { - list.add(account) - } - } - - return list - } - - fun applyInternationalPrefix(): Boolean { - val account = coreContext.core.defaultAccount - if (account != null) { - val params = account.params - return params.useInternationalPrefixForCallsAndChats - } - - return true // Legacy behavior - } - - fun isPushNotificationAvailable(): Boolean { - val core = coreContext.core - if (!core.isPushNotificationAvailable) { - return false - } - - val pushConfig = core.pushNotificationConfig ?: return false - if (pushConfig.provider.isNullOrEmpty()) return false - if (pushConfig.param.isNullOrEmpty()) return false - if (pushConfig.prid.isNullOrEmpty()) return false - - return true - } - - fun isFileTransferAvailable(): Boolean { - val core = coreContext.core - return core.fileTransferServer.orEmpty().isNotEmpty() - } - - fun hashPassword( - userId: String, - password: String, - realm: String, - algorithm: String = "MD5" - ): String? { - val input = "$userId:$realm:$password" - try { - val digestEngine = MessageDigest.getInstance(algorithm) - val digest = digestEngine.digest(input.toByteArray()) - val hexString = StringBuffer() - for (i in digest.indices) { - hexString.append(Integer.toHexString(digest[i].and(0xFF))) - } - return hexString.toString() - } catch (nsae: NoSuchAlgorithmException) { - Log.e("[Side Menu] Can't compute hash using [$algorithm] algorithm!") - } - - return null - } - - fun getTextDescribingMessage(message: ChatMessage): String { - // If message contains text, then use that - var text = message.contents.find { content -> content.isText }?.utf8Text ?: "" - - if (text.isEmpty()) { - val firstContent = message.contents.firstOrNull() - if (firstContent?.isIcalendar == true) { - text = AppUtils.getString( - R.string.conference_invitation_notification_short_desc - ) - } else if (firstContent?.isVoiceRecording == true) { - text = AppUtils.getString( - R.string.chat_message_voice_recording_notification_short_desc - ) - } else { - for (content in message.contents) { - if (text.isNotEmpty()) { - text += ", " - } - text += content.name - } - } - } - - return text - } - } -} diff --git a/app/src/main/java/org/linphone/utils/PatternClickableSpan.kt b/app/src/main/java/org/linphone/utils/PatternClickableSpan.kt deleted file mode 100644 index 19b1f8735..000000000 --- a/app/src/main/java/org/linphone/utils/PatternClickableSpan.kt +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2010-2022 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.utils - -import android.text.SpannableStringBuilder -import android.text.Spanned -import android.text.style.ClickableSpan -import android.view.View -import android.widget.TextView -import java.util.regex.Pattern - -class PatternClickableSpan { - private var patterns: ArrayList = ArrayList() - - inner class SpannablePatternItem( - var pattern: Pattern, - var listener: SpannableClickedListener - ) - - interface SpannableClickedListener { - fun onSpanClicked(text: String) - } - - inner class StyledClickableSpan(var item: SpannablePatternItem) : ClickableSpan() { - override fun onClick(widget: View) { - val tv = widget as TextView - val span = tv.text as Spanned - val start = span.getSpanStart(this) - val end = span.getSpanEnd(this) - val text = span.subSequence(start, end) - item.listener.onSpanClicked(text.toString()) - } - } - - fun add( - pattern: Pattern, - listener: SpannableClickedListener - ): PatternClickableSpan { - patterns.add(SpannablePatternItem(pattern, listener)) - return this - } - - fun build(editable: CharSequence?): SpannableStringBuilder { - val ssb = SpannableStringBuilder(editable) - for (item in patterns) { - val matcher = item.pattern.matcher(ssb) - while (matcher.find()) { - val start = matcher.start() - val end = matcher.end() - val url = StyledClickableSpan(item) - ssb.setSpan(url, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - } - } - return ssb - } -} diff --git a/app/src/main/java/org/linphone/utils/PermissionHelper.kt b/app/src/main/java/org/linphone/utils/PermissionHelper.kt deleted file mode 100644 index e50e7d04c..000000000 --- a/app/src/main/java/org/linphone/utils/PermissionHelper.kt +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.utils - -import android.Manifest -import android.content.Context -import org.linphone.compatibility.Compatibility -import org.linphone.core.tools.Log -import org.linphone.mediastream.Version - -/** - * Helper methods to check whether a permission has been granted and log the result - */ -class PermissionHelper private constructor(private val context: Context) { - companion object : SingletonHolder(::PermissionHelper) - - private fun hasPermission(permission: String): Boolean { - val granted = Compatibility.hasPermission(context, permission) - - if (granted) { - Log.d("[Permission Helper] Permission $permission is granted") - } else { - Log.w("[Permission Helper] Permission $permission is denied") - } - - return granted - } - - fun hasReadContactsPermission(): Boolean { - return hasPermission(Manifest.permission.READ_CONTACTS) - } - - fun hasWriteContactsPermission(): Boolean { - return hasPermission(Manifest.permission.WRITE_CONTACTS) - } - - fun hasReadPhoneStatePermission(): Boolean { - return hasPermission(Manifest.permission.READ_PHONE_STATE) - } - - fun hasReadPhoneStateOrPhoneNumbersPermission(): Boolean { - return Compatibility.hasReadPhoneStateOrNumbersPermission(context) - } - - fun hasReadExternalStoragePermission(): Boolean { - return Compatibility.hasReadExternalStoragePermission(context) - } - - fun hasWriteExternalStoragePermission(): Boolean { - if (Version.sdkAboveOrEqual(Version.API29_ANDROID_10)) return true - return hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) - } - - fun hasCameraPermission(): Boolean { - return hasPermission(Manifest.permission.CAMERA) - } - - fun hasRecordAudioPermission(): Boolean { - return hasPermission(Manifest.permission.RECORD_AUDIO) - } - - fun hasPostNotificationsPermission(): Boolean { - return Compatibility.hasPostNotificationsPermission(context) - } -} diff --git a/app/src/main/java/org/linphone/utils/PhoneNumberUtils.kt b/app/src/main/java/org/linphone/utils/PhoneNumberUtils.kt deleted file mode 100644 index 901c4b3c2..000000000 --- a/app/src/main/java/org/linphone/utils/PhoneNumberUtils.kt +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.utils - -import android.annotation.SuppressLint -import android.content.Context -import android.content.res.Resources -import android.provider.ContactsContract -import android.telephony.TelephonyManager -import org.linphone.core.DialPlan -import org.linphone.core.Factory -import org.linphone.core.tools.Log - -class PhoneNumberUtils { - companion object { - fun getDialPlanForCurrentCountry(context: Context): DialPlan? { - try { - val tm = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager - val countryIso = tm.networkCountryIso - return getDialPlanFromCountryCode(countryIso) - } catch (e: java.lang.Exception) { - Log.e("[Phone Number Utils] $e") - } - return null - } - - @SuppressLint("MissingPermission", "HardwareIds") - fun getDevicePhoneNumber(context: Context): String? { - if (PermissionHelper.get().hasReadPhoneStateOrPhoneNumbersPermission()) { - try { - val tm = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager - return tm.line1Number - } catch (e: java.lang.Exception) { - Log.e("[Phone Number Utils] $e") - } - } - return null - } - - fun getDialPlanFromCountryCallingPrefix(countryCode: String): DialPlan? { - for (c in Factory.instance().dialPlans) { - if (countryCode == c.countryCallingCode) return c - } - return null - } - - fun addressBookLabelTypeToVcardParamString(type: Int, default: String?): String { - return when (type) { - ContactsContract.CommonDataKinds.Phone.TYPE_ASSISTANT -> "assistant" - ContactsContract.CommonDataKinds.Phone.TYPE_CALLBACK -> "callback" - ContactsContract.CommonDataKinds.Phone.TYPE_CAR -> "car" - ContactsContract.CommonDataKinds.Phone.TYPE_COMPANY_MAIN -> "work,main" - ContactsContract.CommonDataKinds.Phone.TYPE_FAX_HOME -> "home,fax" - ContactsContract.CommonDataKinds.Phone.TYPE_FAX_WORK -> "work,fax" - ContactsContract.CommonDataKinds.Phone.TYPE_HOME -> "home" - ContactsContract.CommonDataKinds.Phone.TYPE_ISDN -> "isdn" - ContactsContract.CommonDataKinds.Phone.TYPE_MAIN -> "main" - ContactsContract.CommonDataKinds.Phone.TYPE_MMS -> "text" - ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE -> "cell" - ContactsContract.CommonDataKinds.Phone.TYPE_OTHER -> "other" - ContactsContract.CommonDataKinds.Phone.TYPE_OTHER_FAX -> "fax" - ContactsContract.CommonDataKinds.Phone.TYPE_PAGER -> "pager" - ContactsContract.CommonDataKinds.Phone.TYPE_RADIO -> "radio" - ContactsContract.CommonDataKinds.Phone.TYPE_TELEX -> "telex" - ContactsContract.CommonDataKinds.Phone.TYPE_TTY_TDD -> "textphone" - ContactsContract.CommonDataKinds.Phone.TYPE_WORK -> "work" - ContactsContract.CommonDataKinds.Phone.TYPE_WORK_MOBILE -> "work,cell" - ContactsContract.CommonDataKinds.Phone.TYPE_WORK_PAGER -> "work,pager" - ContactsContract.CommonDataKinds.BaseTypes.TYPE_CUSTOM -> default ?: "custom" - else -> default ?: type.toString() - } - } - - fun vcardParamStringToAddressBookLabel(resources: Resources, label: String): String { - if (label.isEmpty()) return label - val type = labelToType(label) - return ContactsContract.CommonDataKinds.Phone.getTypeLabel(resources, type, label).toString() - } - - private fun labelToType(label: String): Int { - return when (label) { - "assistant" -> ContactsContract.CommonDataKinds.Phone.TYPE_ASSISTANT - "callback" -> ContactsContract.CommonDataKinds.Phone.TYPE_CALLBACK - "car" -> ContactsContract.CommonDataKinds.Phone.TYPE_CAR - "work,main" -> ContactsContract.CommonDataKinds.Phone.TYPE_COMPANY_MAIN - "home,fax" -> ContactsContract.CommonDataKinds.Phone.TYPE_FAX_HOME - "work,fax" -> ContactsContract.CommonDataKinds.Phone.TYPE_FAX_WORK - "home" -> ContactsContract.CommonDataKinds.Phone.TYPE_HOME - "isdn" -> ContactsContract.CommonDataKinds.Phone.TYPE_ISDN - "main" -> ContactsContract.CommonDataKinds.Phone.TYPE_MAIN - "text" -> ContactsContract.CommonDataKinds.Phone.TYPE_MMS - "cell" -> ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE - "other" -> ContactsContract.CommonDataKinds.Phone.TYPE_OTHER - "fax" -> ContactsContract.CommonDataKinds.Phone.TYPE_OTHER_FAX - "pager" -> ContactsContract.CommonDataKinds.Phone.TYPE_PAGER - "radio" -> ContactsContract.CommonDataKinds.Phone.TYPE_RADIO - "telex" -> ContactsContract.CommonDataKinds.Phone.TYPE_TELEX - "textphone" -> ContactsContract.CommonDataKinds.Phone.TYPE_TTY_TDD - "work" -> ContactsContract.CommonDataKinds.Phone.TYPE_WORK - "work,cell" -> ContactsContract.CommonDataKinds.Phone.TYPE_WORK_MOBILE - "work,pager" -> ContactsContract.CommonDataKinds.Phone.TYPE_WORK_PAGER - "custom" -> ContactsContract.CommonDataKinds.BaseTypes.TYPE_CUSTOM - else -> ContactsContract.CommonDataKinds.BaseTypes.TYPE_CUSTOM - } - } - - private fun getDialPlanFromCountryCode(countryCode: String): DialPlan? { - for (c in Factory.instance().dialPlans) { - if (countryCode.equals(c.isoCountryCode, ignoreCase = true)) return c - } - return null - } - - fun arePhoneNumberWeakEqual(number1: String, number2: String): Boolean { - return trimPhoneNumber(number1) == trimPhoneNumber(number2) - } - - private fun trimPhoneNumber(phoneNumber: String): String { - return phoneNumber.replace(" ", "") - .replace("-", "") - .replace("(", "") - .replace(")", "") - } - } -} diff --git a/app/src/main/java/org/linphone/utils/PowerManagerUtils.kt b/app/src/main/java/org/linphone/utils/PowerManagerUtils.kt deleted file mode 100644 index f54c7d44a..000000000 --- a/app/src/main/java/org/linphone/utils/PowerManagerUtils.kt +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.utils - -import android.content.ComponentName -import android.content.Context -import android.content.Intent -import android.content.pm.PackageManager -import android.content.pm.ResolveInfo -import android.net.Uri - -class PowerManagerUtils { - companion object { - // https://stackoverflow.com/questions/31638986/protected-apps-setting-on-huawei-phones-and-how-to-handle-it - private val POWER_MANAGER_INTENTS = arrayOf( - Intent() - .setComponent( - ComponentName( - "com.miui.securitycenter", - "com.miui.permcenter.autostart.AutoStartManagementActivity" - ) - ), - Intent() - .setComponent( - ComponentName( - "com.letv.android.letvsafe", - "com.letv.android.letvsafe.AutobootManageActivity" - ) - ), - Intent() - .setComponent( - ComponentName( - "com.huawei.systemmanager", - "com.huawei.systemmanager.appcontrol.activity.StartupAppControlActivity" - ) - ), - Intent() - .setComponent( - ComponentName( - "com.huawei.systemmanager", - "com.huawei.systemmanager.optimize.process.ProtectActivity" - ) - ), - Intent() - .setComponent( - ComponentName( - "com.coloros.safecenter", - "com.coloros.safecenter.permission.startup.StartupAppListActivity" - ) - ), - Intent() - .setComponent( - ComponentName( - "com.coloros.safecenter", - "com.coloros.safecenter.startupapp.StartupAppListActivity" - ) - ), - Intent() - .setComponent( - ComponentName( - "com.oppo.safe", - "com.oppo.safe.permission.startup.StartupAppListActivity" - ) - ), - Intent() - .setComponent( - ComponentName( - "com.iqoo.secure", - "com.iqoo.secure.ui.phoneoptimize.AddWhiteListActivity" - ) - ), - Intent() - .setComponent( - ComponentName( - "com.iqoo.secure", - "com.iqoo.secure.ui.phoneoptimize.BgStartUpManager" - ) - ), - Intent() - .setComponent( - ComponentName( - "com.vivo.permissionmanager", - "com.vivo.permissionmanager.activity.BgStartUpManagerActivity" - ) - ), - Intent() - .setComponent( - ComponentName( - "com.samsung.android.lool", - "com.samsung.android.sm.ui.battery.BatteryActivity" - ) - ), - Intent() - .setComponent( - ComponentName( - "com.htc.pitroad", - "com.htc.pitroad.landingpage.activity.LandingPageActivity" - ) - ), - Intent() - .setComponent( - ComponentName( - "com.asus.mobilemanager", - "com.asus.mobilemanager.MainActivity" - ) - ), - Intent() - .setComponent( - ComponentName( - "com.asus.mobilemanager", - "com.asus.mobilemanager.autostart.AutoStartActivity" - ) - ), - Intent() - .setComponent( - ComponentName( - "com.asus.mobilemanager", - "com.asus.mobilemanager.entry.FunctionActivity" - ) - ) - .setData(Uri.parse("mobilemanager://function/entry/AutoStart")), - Intent() - .setComponent( - ComponentName( - "com.dewav.dwappmanager", - "com.dewav.dwappmanager.memory.SmartClearupWhiteList" - ) - ) - ) - - fun getDevicePowerManagerIntent(context: Context): Intent? { - for (intent in POWER_MANAGER_INTENTS) { - if (isIntentCallable(context, intent)) { - return intent - } - } - return null - } - - private fun isIntentCallable(context: Context, intent: Intent): Boolean { - val list: List = context.packageManager - .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY) - return list.isNotEmpty() - } - } -} diff --git a/app/src/main/java/org/linphone/utils/RecyclerViewHeaderDecoration.kt b/app/src/main/java/org/linphone/utils/RecyclerViewHeaderDecoration.kt deleted file mode 100644 index 4b598a4e7..000000000 --- a/app/src/main/java/org/linphone/utils/RecyclerViewHeaderDecoration.kt +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.utils - -import android.content.Context -import android.graphics.Canvas -import android.graphics.Rect -import android.util.SparseArray -import android.view.View -import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView - -class RecyclerViewHeaderDecoration(private val context: Context, private val adapter: HeaderAdapter) : RecyclerView.ItemDecoration() { - private val headers: SparseArray = SparseArray() - - override fun getItemOffsets( - outRect: Rect, - view: View, - parent: RecyclerView, - state: RecyclerView.State - ) { - val position = (view.layoutParams as RecyclerView.LayoutParams).bindingAdapterPosition - - if (position != RecyclerView.NO_POSITION && adapter.displayHeaderForPosition(position)) { - val headerView: View = adapter.getHeaderViewForPosition(view.context, position) - headers.put(position, headerView) - measureHeaderView(headerView, parent) - outRect.top = headerView.height - } else { - headers.remove(position) - } - } - - private fun measureHeaderView(view: View, parent: ViewGroup) { - if (view.layoutParams == null) { - view.layoutParams = ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - ) - } - - val widthSpec = View.MeasureSpec.makeMeasureSpec(parent.width, View.MeasureSpec.EXACTLY) - val heightSpec = View.MeasureSpec.makeMeasureSpec(parent.height, View.MeasureSpec.EXACTLY) - val childWidth = ViewGroup.getChildMeasureSpec( - widthSpec, - parent.paddingLeft + parent.paddingRight, - view.layoutParams.width - ) - val childHeight = ViewGroup.getChildMeasureSpec( - heightSpec, - parent.paddingTop + parent.paddingBottom, - view.layoutParams.height - ) - - view.measure(childWidth, childHeight) - view.layout(0, 0, view.measuredWidth, view.measuredHeight) - } - - override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) { - for (i in 0 until parent.childCount) { - val child = parent.getChildAt(i) - val position = parent.getChildAdapterPosition(child) - if (position != RecyclerView.NO_POSITION && adapter.displayHeaderForPosition(position)) { - canvas.save() - val headerView: View = headers.get(position) ?: adapter.getHeaderViewForPosition( - context, - position - ) - canvas.translate(0f, child.y - headerView.height) - headerView.draw(canvas) - canvas.restore() - } - } - } -} - -interface HeaderAdapter { - fun displayHeaderForPosition(position: Int): Boolean - - fun getHeaderViewForPosition(context: Context, position: Int): View -} diff --git a/app/src/main/java/org/linphone/utils/RecyclerViewSwipeUtils.kt b/app/src/main/java/org/linphone/utils/RecyclerViewSwipeUtils.kt deleted file mode 100644 index 764e7dbc0..000000000 --- a/app/src/main/java/org/linphone/utils/RecyclerViewSwipeUtils.kt +++ /dev/null @@ -1,344 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.utils - -import android.graphics.Canvas -import android.graphics.Color -import android.graphics.PorterDuff -import android.graphics.Typeface -import android.graphics.drawable.ColorDrawable -import android.text.TextPaint -import android.util.TypedValue -import androidx.core.content.ContextCompat -import androidx.recyclerview.widget.ItemTouchHelper -import androidx.recyclerview.widget.RecyclerView -import org.linphone.core.tools.Log - -/** - * Helper class to properly display swipe actions in list items. - */ -class RecyclerViewSwipeUtils( - direction: Int, - configuration: RecyclerViewSwipeConfiguration, - listener: RecyclerViewSwipeListener -) : ItemTouchHelper(RecyclerViewSwipeUtilsCallback(direction, configuration, listener)) - -class RecyclerViewSwipeConfiguration { - class Action( - val text: String = "", - val textColor: Int = Color.WHITE, - val backgroundColor: Int = 0, - val icon: Int = 0, - val iconTint: Int = 0, - val preventFor: Class<*>? = null - ) - - val iconMargin = 16f - - val actionTextSizeUnit = TypedValue.COMPLEX_UNIT_SP - - // At least CROSSCALL Action-X3 device doesn't have SANS_SERIF typeface... - val actionTextFont: Typeface? = Typeface.SANS_SERIF - val actionTextSize = 14f - - var leftToRightAction = Action() - var rightToLeftAction = Action() -} - -private class RecyclerViewSwipeUtilsCallback( - val direction: Int, - val configuration: RecyclerViewSwipeConfiguration, - val listener: RecyclerViewSwipeListener -) : ItemTouchHelper.SimpleCallback(0, direction) { - - fun leftToRightSwipe( - canvas: Canvas, - recyclerView: RecyclerView, - viewHolder: RecyclerView.ViewHolder, - dX: Float - ) { - if (configuration.leftToRightAction.backgroundColor != 0) { - val background = ColorDrawable(configuration.leftToRightAction.backgroundColor) - background.setBounds( - viewHolder.itemView.left, - viewHolder.itemView.top, - viewHolder.itemView.left + dX.toInt(), - viewHolder.itemView.bottom - ) - background.draw(canvas) - } - - val horizontalMargin: Int = TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, - configuration.iconMargin, - recyclerView.context.resources.displayMetrics - ).toInt() - var iconWidth = 0 - - if (configuration.leftToRightAction.icon != 0) { - val icon = - ContextCompat.getDrawable( - recyclerView.context, - configuration.leftToRightAction.icon - ) - iconWidth = icon?.intrinsicWidth ?: 0 - if (icon != null && dX > iconWidth) { - val halfIcon = icon.intrinsicHeight / 2 - val top = - viewHolder.itemView.top + ((viewHolder.itemView.bottom - viewHolder.itemView.top) / 2 - halfIcon) - - // Icon won't move past the swipe threshold, thus indicating to the user - // it has reached the required distance for swipe action to be done - val threshold = getSwipeThreshold(viewHolder) * viewHolder.itemView.right - val left = if (dX < threshold) { - viewHolder.itemView.left + dX.toInt() - iconWidth - } else { - viewHolder.itemView.left + threshold.toInt() - iconWidth - } - - icon.setBounds( - left, - top, - left + iconWidth, - top + icon.intrinsicHeight - ) - - @Suppress("DEPRECATION") - if (configuration.leftToRightAction.iconTint != 0) { - icon.setColorFilter( - configuration.leftToRightAction.iconTint, - PorterDuff.Mode.SRC_IN - ) - } - icon.draw(canvas) - } - } - - if (configuration.leftToRightAction.text.isNotEmpty() && dX > horizontalMargin + iconWidth) { - val textPaint = TextPaint() - textPaint.isAntiAlias = true - textPaint.textSize = TypedValue.applyDimension( - configuration.actionTextSizeUnit, - configuration.actionTextSize, - recyclerView.context.resources.displayMetrics - ) - textPaint.color = configuration.leftToRightAction.textColor - textPaint.typeface = configuration.actionTextFont - - val margin = if (iconWidth > 0) horizontalMargin / 2 else 0 - val textX = - (viewHolder.itemView.left + horizontalMargin + iconWidth + margin).toFloat() - val textY = - (viewHolder.itemView.top + (viewHolder.itemView.bottom - viewHolder.itemView.top) / 2.0 + textPaint.textSize / 2).toFloat() - canvas.drawText( - configuration.leftToRightAction.text, - textX, - textY, - textPaint - ) - } - } - - fun rightToLeftSwipe( - canvas: Canvas, - recyclerView: RecyclerView, - viewHolder: RecyclerView.ViewHolder, - dX: Float - ) { - if (configuration.rightToLeftAction.backgroundColor != 0) { - val background = ColorDrawable(configuration.rightToLeftAction.backgroundColor) - background.setBounds( - viewHolder.itemView.right + dX.toInt(), - viewHolder.itemView.top, - viewHolder.itemView.right, - viewHolder.itemView.bottom - ) - background.draw(canvas) - } - - val horizontalMargin: Int = TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, - configuration.iconMargin, - recyclerView.context.resources.displayMetrics - ).toInt() - var iconWidth = 0 - var imageLeftBorder = viewHolder.itemView.right - - if (configuration.rightToLeftAction.icon != 0) { - val icon = - ContextCompat.getDrawable( - recyclerView.context, - configuration.rightToLeftAction.icon - ) - iconWidth = icon?.intrinsicWidth ?: 0 - if (icon != null && dX < viewHolder.itemView.right - iconWidth) { - val halfIcon = icon.intrinsicHeight / 2 - val top = - viewHolder.itemView.top + ((viewHolder.itemView.bottom - viewHolder.itemView.top) / 2 - halfIcon) - - // Icon won't move past the swipe threshold, thus indicating to the user - // it has reached the required distance for swipe action to be done - val threshold = -(getSwipeThreshold(viewHolder) * viewHolder.itemView.right) - val right = if (dX > threshold) { - viewHolder.itemView.right + dX.toInt() - } else { - viewHolder.itemView.right + threshold.toInt() - } - imageLeftBorder = right - icon.intrinsicWidth - - icon.setBounds( - imageLeftBorder, - top, - right, - top + icon.intrinsicHeight - ) - - @Suppress("DEPRECATION") - if (configuration.rightToLeftAction.iconTint != 0) { - icon.setColorFilter( - configuration.rightToLeftAction.iconTint, - PorterDuff.Mode.SRC_IN - ) - } - icon.draw(canvas) - } - } - - if (configuration.rightToLeftAction.text.isNotEmpty() && dX < -horizontalMargin - iconWidth) { - val textPaint = TextPaint() - textPaint.isAntiAlias = true - textPaint.textSize = TypedValue.applyDimension( - configuration.actionTextSizeUnit, - configuration.actionTextSize, - recyclerView.context.resources.displayMetrics - ) - textPaint.color = configuration.rightToLeftAction.textColor - textPaint.typeface = configuration.actionTextFont - - val margin = - if (imageLeftBorder == viewHolder.itemView.right) horizontalMargin else horizontalMargin / 2 - val textX = - imageLeftBorder - textPaint.measureText(configuration.rightToLeftAction.text) - margin - val textY = - (viewHolder.itemView.top + (viewHolder.itemView.bottom - viewHolder.itemView.top) / 2.0 + textPaint.textSize / 2).toFloat() - canvas.drawText( - configuration.rightToLeftAction.text, - textX, - textY, - textPaint - ) - } - } - - fun applyConfiguration( - canvas: Canvas, - recyclerView: RecyclerView, - viewHolder: RecyclerView.ViewHolder, - dX: Float, - actionState: Int - ) { - try { - if (actionState != ItemTouchHelper.ACTION_STATE_SWIPE) return - - if (dX > 0) { - leftToRightSwipe(canvas, recyclerView, viewHolder, dX) - } else if (dX < 0) { - rightToLeftSwipe(canvas, recyclerView, viewHolder, dX) - } - } catch (e: Exception) { - Log.e("[RecyclerView Swipe Utils] $e") - } - } - - override fun getSwipeDirs( - recyclerView: RecyclerView, - viewHolder: RecyclerView.ViewHolder - ): Int { - // Prevent swipe actions for a specific ViewHolder class if needed - // Used to allow swipe actions on chat messages but not events - var dirFlags = direction - if (direction and ItemTouchHelper.RIGHT != 0) { - val classToPrevent = configuration.leftToRightAction.preventFor - if (classToPrevent != null) { - if (classToPrevent.isInstance(viewHolder)) { - dirFlags = dirFlags and ItemTouchHelper.RIGHT.inv() - } - } - } - if (direction or ItemTouchHelper.LEFT != 0) { - val classToPrevent = configuration.rightToLeftAction.preventFor - if (classToPrevent != null) { - if (classToPrevent.isInstance(viewHolder)) { - dirFlags = dirFlags and ItemTouchHelper.LEFT.inv() - } - } - } - return dirFlags - } - - override fun onMove( - recyclerView: RecyclerView, - viewHolder: RecyclerView.ViewHolder, - target: RecyclerView.ViewHolder - ): Boolean { - return false - } - - override fun onSwiped( - viewHolder: RecyclerView.ViewHolder, - direction: Int - ) { - if (direction == ItemTouchHelper.LEFT) { - listener.onRightToLeftSwipe(viewHolder) - } else if (direction == ItemTouchHelper.RIGHT) { - listener.onLeftToRightSwipe(viewHolder) - } - } - - override fun getSwipeThreshold(viewHolder: RecyclerView.ViewHolder): Float { - return .33f // A third of the screen is required to validate swipe move (default is .5f) - } - - override fun onChildDraw( - canvas: Canvas, - recyclerView: RecyclerView, - viewHolder: RecyclerView.ViewHolder, - dX: Float, - dY: Float, - actionState: Int, - isCurrentlyActive: Boolean - ) { - applyConfiguration(canvas, recyclerView, viewHolder, dX, actionState) - super.onChildDraw( - canvas, - recyclerView, - viewHolder, - dX, - dY, - actionState, - isCurrentlyActive - ) - } -} - -interface RecyclerViewSwipeListener { - fun onLeftToRightSwipe(viewHolder: RecyclerView.ViewHolder) - fun onRightToLeftSwipe(viewHolder: RecyclerView.ViewHolder) -} diff --git a/app/src/main/java/org/linphone/utils/ShortcutsHelper.kt b/app/src/main/java/org/linphone/utils/ShortcutsHelper.kt deleted file mode 100644 index 65ed4dd19..000000000 --- a/app/src/main/java/org/linphone/utils/ShortcutsHelper.kt +++ /dev/null @@ -1,247 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.utils - -import android.annotation.TargetApi -import android.content.Context -import android.content.Intent -import android.content.pm.ShortcutInfo -import android.os.Bundle -import androidx.collection.ArraySet -import androidx.core.app.Person -import androidx.core.content.LocusIdCompat -import androidx.core.content.pm.ShortcutInfoCompat -import androidx.core.content.pm.ShortcutManagerCompat -import androidx.core.graphics.drawable.IconCompat -import kotlin.math.min -import org.linphone.LinphoneApplication.Companion.coreContext -import org.linphone.activities.main.MainActivity -import org.linphone.contact.getPerson -import org.linphone.core.Address -import org.linphone.core.ChatRoom -import org.linphone.core.ChatRoom.Capabilities -import org.linphone.core.Friend -import org.linphone.core.tools.Log -import org.linphone.mediastream.Version - -@TargetApi(25) -class ShortcutsHelper(val context: Context) { - companion object { - fun createShortcutsToContacts(context: Context) { - val shortcuts = ArrayList() - if (ShortcutManagerCompat.isRateLimitingActive(context)) { - Log.e("[Shortcut Helper] Rate limiting is active, aborting") - return - } - - val maxShortcuts = min(ShortcutManagerCompat.getMaxShortcutCountPerActivity(context), 5) - var count = 0 - val processedAddresses = arrayListOf() - for (room in coreContext.core.chatRooms) { - // Android can usually only have around 4-5 shortcuts at a time - if (count >= maxShortcuts) { - Log.w("[Shortcut Helper] Max amount of shortcuts reached ($count)") - break - } - - val addresses: ArrayList
= arrayListOf(room.peerAddress) - if (!room.hasCapability(Capabilities.Basic.toInt())) { - addresses.clear() - for (participant in room.participants) { - addresses.add(participant.address) - } - } - for (address in addresses) { - if (count >= maxShortcuts) { - Log.w("[Shortcut Helper] Max amount of shortcuts reached ($count)") - break - } - - val stringAddress = address.asStringUriOnly() - if (!processedAddresses.contains(stringAddress)) { - processedAddresses.add(stringAddress) - val contact: Friend? = - coreContext.contactsManager.findContactByAddress(address) - - if (contact != null && contact.refKey != null) { - val shortcut: ShortcutInfoCompat? = createContactShortcut( - context, - contact - ) - if (shortcut != null) { - Log.i( - "[Shortcut Helper] Creating launcher shortcut for ${shortcut.shortLabel}" - ) - shortcuts.add(shortcut) - count += 1 - } - } else { - Log.w("[Shortcut Helper] Contact not found for address: $stringAddress") - } - } - } - } - ShortcutManagerCompat.setDynamicShortcuts(context, shortcuts) - } - - private fun createContactShortcut(context: Context, contact: Friend): ShortcutInfoCompat? { - try { - val categories: ArraySet = ArraySet() - categories.add(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION) - - val person = contact.getPerson() - val icon = person.icon - val id = contact.refKey ?: return null - - val intent = Intent(Intent.ACTION_MAIN) - intent.setClass(context, MainActivity::class.java) - intent.putExtra("ContactId", id) - - return ShortcutInfoCompat.Builder(context, id) - .setShortLabel(contact.name ?: "") - .setIcon(icon) - .setPerson(person) - .setCategories(categories) - .setIntent(intent) - .build() - } catch (e: Exception) { - Log.e( - "[Shortcuts Helper] createContactShortcut for contact [${contact.name}] exception: $e" - ) - } - - return null - } - - fun createShortcutsToChatRooms(context: Context) { - val shortcuts = ArrayList() - if (ShortcutManagerCompat.isRateLimitingActive(context)) { - Log.e("[Shortcut Helper] Rate limiting is active, aborting") - return - } - Log.i("[Shortcut Helper] Creating launcher shortcuts for chat rooms") - val maxShortcuts = min(ShortcutManagerCompat.getMaxShortcutCountPerActivity(context), 5) - var count = 0 - for (room in coreContext.core.chatRooms) { - // Android can usually only have around 4-5 shortcuts at a time - if (count >= maxShortcuts) { - Log.w("[Shortcut Helper] Max amount of shortcuts reached ($count)") - break - } - - val shortcut: ShortcutInfoCompat? = createChatRoomShortcut(context, room) - if (shortcut != null) { - Log.i("[Shortcut Helper] Created launcher shortcut for ${shortcut.shortLabel}") - shortcuts.add(shortcut) - count += 1 - } - } - ShortcutManagerCompat.setDynamicShortcuts(context, shortcuts) - Log.i("[Shortcut Helper] Created $count launcher shortcuts") - } - - private fun createChatRoomShortcut(context: Context, chatRoom: ChatRoom): ShortcutInfoCompat? { - val localAddress = chatRoom.localAddress.asStringUriOnly() - val peerAddress = chatRoom.peerAddress.asStringUriOnly() - val id = LinphoneUtils.getChatRoomId(chatRoom.localAddress, chatRoom.peerAddress) - - try { - val categories: ArraySet = ArraySet() - categories.add(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION) - - val personsList = arrayListOf() - val subject: String - val icon: IconCompat - if (chatRoom.hasCapability(Capabilities.Basic.toInt())) { - val contact = - coreContext.contactsManager.findContactByAddress(chatRoom.peerAddress) - val person = contact?.getPerson() - if (person != null) { - personsList.add(person) - } - - icon = person?.icon ?: coreContext.contactsManager.contactAvatar - subject = contact?.name ?: LinphoneUtils.getDisplayName(chatRoom.peerAddress) - } else if (chatRoom.hasCapability(Capabilities.OneToOne.toInt()) && chatRoom.participants.isNotEmpty()) { - val address = chatRoom.participants.first().address - val contact = - coreContext.contactsManager.findContactByAddress(address) - val person = contact?.getPerson() - if (person != null) { - personsList.add(person) - } - - subject = contact?.name ?: LinphoneUtils.getDisplayName(address) - icon = person?.icon ?: coreContext.contactsManager.contactAvatar - } else { - for (participant in chatRoom.participants) { - val contact = - coreContext.contactsManager.findContactByAddress(participant.address) - if (contact != null) { - personsList.add(contact.getPerson()) - } - } - subject = chatRoom.subject.orEmpty() - icon = coreContext.contactsManager.groupAvatar - } - - val persons = arrayOfNulls(personsList.size) - personsList.toArray(persons) - - val args = Bundle() - args.putString("RemoteSipUri", peerAddress) - args.putString("LocalSipUri", localAddress) - - val intent = Intent(Intent.ACTION_MAIN) - intent.setClass(context, MainActivity::class.java) - intent.putExtra("Chat", true) - intent.putExtra("RemoteSipUri", peerAddress) - intent.putExtra("LocalSipUri", localAddress) - - return ShortcutInfoCompat.Builder(context, id) - .setShortLabel(subject) - .setIcon(icon) - .setPersons(persons) - .setCategories(categories) - .setIntent(intent) - .setLongLived(Version.sdkAboveOrEqual(Version.API30_ANDROID_11)) - .setLocusId(LocusIdCompat(id)) - .build() - } catch (e: Exception) { - Log.e("[Shortcuts Helper] createChatRoomShortcut for id [$id] exception: $e") - } - - return null - } - - fun removeShortcuts(context: Context) { - Log.w("[Shortcut Helper] Removing all contacts shortcuts") - ShortcutManagerCompat.removeAllDynamicShortcuts(context) - } - - fun isShortcutToChatRoomAlreadyCreated(context: Context, chatRoom: ChatRoom): Boolean { - val id = LinphoneUtils.getChatRoomId(chatRoom.localAddress, chatRoom.peerAddress) - val found = ShortcutManagerCompat.getDynamicShortcuts(context).find { - it.id == id - } - return found != null - } - } -} diff --git a/app/src/main/java/org/linphone/utils/SingletonHolder.kt b/app/src/main/java/org/linphone/utils/SingletonHolder.kt deleted file mode 100644 index 379ba4e3a..000000000 --- a/app/src/main/java/org/linphone/utils/SingletonHolder.kt +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.utils - -/** - * Helper class to create singletons like CoreContext. - */ -open class SingletonHolder(val creator: (A) -> T) { - @Volatile private var instance: T? = null - - fun exists(): Boolean { - return instance != null - } - - fun destroy() { - instance = null - } - - fun get(): T { - // Will throw NPE if needed - return instance!! - } - - fun create(arg: A): T { - val i = instance - if (i != null) { - return i - } - - return synchronized(this) { - val i2 = instance - if (i2 != null) { - i2 - } else { - val created = creator(arg) - instance = created - created - } - } - } - - fun required(arg: A): T { - return instance ?: create(arg) - } -} diff --git a/app/src/main/java/org/linphone/utils/TimestampUtils.kt b/app/src/main/java/org/linphone/utils/TimestampUtils.kt deleted file mode 100644 index baf0e7262..000000000 --- a/app/src/main/java/org/linphone/utils/TimestampUtils.kt +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.utils - -import java.text.DateFormat -import java.text.Format -import java.text.SimpleDateFormat -import java.util.* -import org.linphone.LinphoneApplication - -class TimestampUtils { - companion object { - fun isToday(timestamp: Long, timestampInSecs: Boolean = true): Boolean { - val cal = Calendar.getInstance() - cal.timeInMillis = if (timestampInSecs) timestamp * 1000 else timestamp - return isSameDay(cal, Calendar.getInstance()) - } - - fun isYesterday(timestamp: Long, timestampInSecs: Boolean = true): Boolean { - val yesterday = Calendar.getInstance() - yesterday.roll(Calendar.DAY_OF_MONTH, -1) - val cal = Calendar.getInstance() - cal.timeInMillis = if (timestampInSecs) timestamp * 1000 else timestamp - return isSameDay(cal, yesterday) - } - - fun isSameDay(timestamp1: Long, timestamp2: Long, timestampInSecs: Boolean = true): Boolean { - val cal1 = Calendar.getInstance() - cal1.timeInMillis = if (timestampInSecs) timestamp1 * 1000 else timestamp1 - val cal2 = Calendar.getInstance() - cal2.timeInMillis = if (timestampInSecs) timestamp2 * 1000 else timestamp2 - return isSameDay(cal1, cal2) - } - - fun isSameDay( - cal1: Date, - cal2: Date - ): Boolean { - return isSameDay(cal1.time, cal2.time, false) - } - - fun dateToString(date: Long, timestampInSecs: Boolean = true): String { - val dateFormat: Format = android.text.format.DateFormat.getDateFormat( - LinphoneApplication.coreContext.context - ) - val pattern = (dateFormat as SimpleDateFormat).toLocalizedPattern() - - val calendar = Calendar.getInstance() - calendar.timeInMillis = if (timestampInSecs) date * 1000 else date - - // See https://github.com/material-components/material-components-android/issues/882 - val dateFormatter = SimpleDateFormat(pattern, Locale.getDefault()) - dateFormatter.timeZone = TimeZone.getTimeZone("UTC") - return dateFormatter.format(calendar.time) - } - - fun timeToString(hour: Int, minutes: Int): String { - val use24hFormat = android.text.format.DateFormat.is24HourFormat( - LinphoneApplication.coreContext.context - ) - val calendar = Calendar.getInstance() - calendar.set(Calendar.HOUR_OF_DAY, hour) - calendar.set(Calendar.MINUTE, minutes) - - return if (use24hFormat) { - SimpleDateFormat("HH'h'mm", Locale.getDefault()).format(calendar.time) - } else { - SimpleDateFormat("h:mm a", Locale.getDefault()).format(calendar.time) - } - } - - fun timeToString(time: Long, timestampInSecs: Boolean = true): String { - val use24hFormat = android.text.format.DateFormat.is24HourFormat( - LinphoneApplication.coreContext.context - ) - val calendar = Calendar.getInstance() - calendar.timeInMillis = if (timestampInSecs) time * 1000 else time - - return if (use24hFormat) { - SimpleDateFormat("HH'h'mm", Locale.getDefault()).format(calendar.time) - } else { - SimpleDateFormat("h:mm a", Locale.getDefault()).format(calendar.time) - } - } - - fun durationToString(hours: Int, minutes: Int): String { - val calendar = Calendar.getInstance() - calendar.set(Calendar.HOUR_OF_DAY, hours) - calendar.set(Calendar.MINUTE, minutes) - val pattern = when { - hours == 0 -> "mm'min'" - hours < 10 && minutes == 0 -> "H'h'" - hours < 10 && minutes > 0 -> "H'h'mm" - hours >= 10 && minutes == 0 -> "HH'h'" - else -> "HH'h'mm" - } - return SimpleDateFormat(pattern, Locale.getDefault()).format(calendar.time) - } - - private fun isSameYear(timestamp: Long, timestampInSecs: Boolean = true): Boolean { - val cal = Calendar.getInstance() - cal.timeInMillis = if (timestampInSecs) timestamp * 1000 else timestamp - return isSameYear(cal, Calendar.getInstance()) - } - - fun toString( - timestamp: Long, - onlyDate: Boolean = false, - timestampInSecs: Boolean = true, - shortDate: Boolean = true, - hideYear: Boolean = true - ): String { - val dateFormat = if (isToday(timestamp, timestampInSecs)) { - DateFormat.getTimeInstance(DateFormat.SHORT) - } else { - if (onlyDate) { - DateFormat.getDateInstance(if (shortDate) DateFormat.SHORT else DateFormat.FULL) - } else { - DateFormat.getDateTimeInstance( - if (shortDate) DateFormat.SHORT else DateFormat.MEDIUM, - DateFormat.SHORT - ) - } - } as SimpleDateFormat - - if (hideYear || isSameYear(timestamp, timestampInSecs)) { - // Remove the year part of the format - dateFormat.applyPattern( - dateFormat.toPattern().replace( - "/?y+/?|,?\\s?y+\\s?".toRegex(), - if (shortDate) "" else " " - ) - ) - } - - val millis = if (timestampInSecs) timestamp * 1000 else timestamp - return dateFormat.format(Date(millis)).capitalize(Locale.getDefault()) - } - - private fun isSameDay( - cal1: Calendar, - cal2: Calendar - ): Boolean { - return cal1[Calendar.ERA] == cal2[Calendar.ERA] && - cal1[Calendar.YEAR] == cal2[Calendar.YEAR] && - cal1[Calendar.DAY_OF_YEAR] == cal2[Calendar.DAY_OF_YEAR] - } - - private fun isSameYear( - cal1: Calendar, - cal2: Calendar - ): Boolean { - return cal1[Calendar.ERA] == cal2[Calendar.ERA] && - cal1[Calendar.YEAR] == cal2[Calendar.YEAR] - } - } -} diff --git a/app/src/main/java/org/linphone/views/MarqueeTextView.kt b/app/src/main/java/org/linphone/views/MarqueeTextView.kt deleted file mode 100644 index 55cd5d3eb..000000000 --- a/app/src/main/java/org/linphone/views/MarqueeTextView.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2010-2020 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.views - -import android.content.Context -import android.text.TextUtils -import android.util.AttributeSet -import androidx.appcompat.widget.AppCompatTextView - -/** - * The purpose of this class is to have a TextView automatically configured for marquee ellipsize. - */ -class MarqueeTextView : AppCompatTextView { - constructor(context: Context) : super(context) { - init() - } - - constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { - init() - } - - constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( - context, - attrs, - defStyleAttr - ) { - init() - } - - fun init() { - ellipsize = TextUtils.TruncateAt.MARQUEE - marqueeRepeatLimit = -0x1 - isSelected = true - } -} diff --git a/app/src/main/java/org/linphone/views/SettingTextInputEditText.kt b/app/src/main/java/org/linphone/views/SettingTextInputEditText.kt deleted file mode 100644 index 6340f2896..000000000 --- a/app/src/main/java/org/linphone/views/SettingTextInputEditText.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.views - -import android.app.Activity -import android.content.Context -import android.util.AttributeSet -import android.view.inputmethod.InputMethodManager -import com.google.android.material.textfield.TextInputEditText -import org.linphone.activities.main.settings.SettingListener - -class SettingTextInputEditText : TextInputEditText { - constructor(context: Context) : super(context) - - constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) - - constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( - context, - attrs, - defStyleAttr - ) - - fun fakeImeDone(listener: SettingListener) { - listener.onTextValueChanged(text.toString()) - - // Send IME action DONE to trigger onSettingImeDone binding adapter, but that doesn't work... - // val inputConnection = BaseInputConnection(this, true) - // inputConnection.performEditorAction(EditorInfo.IME_ACTION_DONE) - - // Will make check icon to disappear thanks to onFocusChangeVisibilityOf binding adapter - clearFocus() - - // Hide keyboard - val imm = context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager - imm.hideSoftInputFromWindow(windowToken, 0) - } -} diff --git a/app/src/main/java/org/linphone/views/VoiceRecordProgressBar.kt b/app/src/main/java/org/linphone/views/VoiceRecordProgressBar.kt deleted file mode 100644 index 619717f28..000000000 --- a/app/src/main/java/org/linphone/views/VoiceRecordProgressBar.kt +++ /dev/null @@ -1,352 +0,0 @@ -/* - * Copyright (c) 2010-2021 Belledonne Communications SARL. - * - * This file is part of linphone-android - * (see https://www.linphone.org). - * - * 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, see . - */ -package org.linphone.views - -import android.content.Context -import android.graphics.Canvas -import android.graphics.Rect -import android.graphics.drawable.* -import android.os.Parcel -import android.os.Parcelable -import android.util.AttributeSet -import android.view.View -import org.linphone.R - -class VoiceRecordProgressBar : View { - companion object { - const val MAX_LEVEL = 10000 - } - - private var minWidth = 0 - private var maxWidth = 0 - private var minHeight = 0 - private var maxHeight = 0 - - private var progress = 0 - private var secondaryProgress = 0 - private var max = 0 - private var progressDrawable: Drawable? = null - - private var primaryLeftMargin: Float = 0f - private var primaryRightMargin: Float = 0f - - constructor(context: Context) : this(context, null) - - constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0) - - constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super( - context, - attrs, - defStyle - ) { - max = 100 - progress = 0 - secondaryProgress = 0 - minWidth = 24 - maxWidth = 48 - minHeight = 24 - maxHeight = 48 - - context.theme.obtainStyledAttributes( - attrs, - R.styleable.VoiceRecordProgressBar, - 0, - 0 - ).apply { - - try { - val drawable = getDrawable(R.styleable.VoiceRecordProgressBar_progressDrawable) - if (drawable != null) { - setProgressDrawable(drawable) - } - setPrimaryLeftMargin( - getDimension(R.styleable.VoiceRecordProgressBar_primaryLeftMargin, 0f) - ) - setPrimaryRightMargin( - getDimension(R.styleable.VoiceRecordProgressBar_primaryRightMargin, 0f) - ) - setMax(getInteger(R.styleable.VoiceRecordProgressBar_max, 100)) - } finally { - recycle() - } - } - - setProgress(0) - setSecondaryProgress(0) - } - - override fun onSaveInstanceState(): Parcelable { - val superState: Parcelable? = super.onSaveInstanceState() - val savedState = SavedState(superState) - savedState.max = max - savedState.progress = progress - savedState.secondaryProgress = secondaryProgress - return savedState - } - - override fun onRestoreInstanceState(state: Parcelable) { - val savedState = state as SavedState - super.onRestoreInstanceState(savedState.superState) - setMax(savedState.max) - setProgress(savedState.progress) - setSecondaryProgress(savedState.secondaryProgress) - } - - override fun drawableStateChanged() { - super.drawableStateChanged() - updateDrawableState() - } - - override fun invalidateDrawable(drawable: Drawable) { - if (verifyDrawable(drawable)) { - val dirty: Rect = drawable.bounds - val scrollX: Int = scrollX + paddingLeft - val scrollY: Int = scrollY + paddingTop - invalidate( - dirty.left + scrollX, - dirty.top + scrollY, - dirty.right + scrollX, - dirty.bottom + scrollY - ) - } else { - super.invalidateDrawable(drawable) - } - } - - override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { - val drawable: Drawable? = progressDrawable - var dw = 0 - var dh = 0 - - if (drawable != null) { - dw = minWidth.coerceAtLeast(maxWidth.coerceAtMost(drawable.intrinsicWidth)) - dh = minHeight.coerceAtLeast(maxHeight.coerceAtMost(drawable.intrinsicHeight)) - } - - updateDrawableState() - dw += paddingRight + paddingLeft - dh += paddingBottom + paddingTop - - setMeasuredDimension( - resolveSizeAndState(dw, widthMeasureSpec, 0), - resolveSizeAndState(dh, heightMeasureSpec, 0) - ) - } - - override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { - updateDrawableBounds(w, h) - } - - override fun verifyDrawable(who: Drawable): Boolean { - return who === progressDrawable || super.verifyDrawable(who) - } - - override fun jumpDrawablesToCurrentState() { - super.jumpDrawablesToCurrentState() - progressDrawable?.jumpToCurrentState() - } - - private fun setPrimaryLeftMargin(margin: Float) { - primaryLeftMargin = margin - } - - private fun setPrimaryRightMargin(margin: Float) { - primaryRightMargin = margin - } - - fun setProgress(p: Int) { - var progress = p - if (progress < 0) { - progress = 0 - } - if (progress > max) { - progress = max - } - - if (progress != this.progress) { - this.progress = progress - refreshProgress(android.R.id.progress, this.progress) - } - } - - fun setSecondaryProgress(sp: Int) { - var secondaryProgress = sp - if (secondaryProgress < 0) { - secondaryProgress = 0 - } - if (secondaryProgress > max) { - secondaryProgress = max - } - - if (secondaryProgress != this.secondaryProgress) { - this.secondaryProgress = secondaryProgress - refreshProgress(android.R.id.secondaryProgress, this.secondaryProgress) - } - } - - fun setMax(m: Int) { - var max = m - if (max < 0) { - max = 0 - } - if (max != this.max) { - this.max = max - postInvalidate() - if (progress > max) { - progress = max - } - refreshProgress(android.R.id.progress, progress) - } - } - - fun setSecondaryProgressTint(color: Int) { - val drawable = progressDrawable - if (drawable != null) { - if (drawable is LayerDrawable) { - val secondaryProgressDrawable = drawable.findDrawableByLayerId( - android.R.id.secondaryProgress - ) - secondaryProgressDrawable?.setTint(color) - } - } - } - - private fun setProgressDrawable(drawable: Drawable) { - val needUpdate: Boolean = if (progressDrawable != null && drawable !== progressDrawable) { - progressDrawable?.callback = null - true - } else { - false - } - - drawable.callback = this - // Make sure the ProgressBar is always tall enough - val drawableHeight = drawable.minimumHeight - if (maxHeight < drawableHeight) { - maxHeight = drawableHeight - requestLayout() - } - - progressDrawable = drawable - postInvalidate() - - if (needUpdate) { - updateDrawableBounds(width, height) - updateDrawableState() - - refreshProgress(android.R.id.progress, progress) - refreshProgress(android.R.id.secondaryProgress, secondaryProgress) - } - } - - private fun refreshProgress(id: Int, progress: Int) { - var scale: Float = if (max > 0) (progress.toFloat() / max) else 0f - - if (id == android.R.id.progress && scale > 0) { - if (width > 0) { - // Wait for secondaryProgress to have reached primaryLeftMargin to start primaryProgress at 0 - val leftOffset = primaryLeftMargin / width - if (scale < leftOffset) return - - // Prevent primaryProgress to go further than (width - rightMargin) - val rightOffset = primaryRightMargin / width - if (scale > 1 - rightOffset) { - scale = 1 - rightOffset - } - - // Remove left margin from primary progress - scale -= leftOffset - - // Since we use setBounds() to apply margins to the Bitmaps, - // the width of the bitmap is reduced so we have to adapt the level - val widthScale = width - (primaryLeftMargin + primaryRightMargin) - scale = ((scale * width) / widthScale) - } - } - - val drawable: Drawable? = progressDrawable - if (drawable != null) { - var progressDrawable: Drawable? = null - if (drawable is LayerDrawable) { - progressDrawable = drawable.findDrawableByLayerId(id) - } - (progressDrawable ?: drawable).level = (scale * MAX_LEVEL).toInt() - } else { - invalidate() - } - } - - private fun updateDrawableState() { - val state = drawableState - if (progressDrawable != null && progressDrawable?.isStateful == true) { - progressDrawable?.state = state - } - } - - private fun updateDrawableBounds(w: Int, h: Int) { - val right: Int = w - paddingRight - paddingLeft - val bottom: Int = h - paddingBottom - paddingTop - progressDrawable?.setBounds(0, 0, right, bottom) - } - - @Synchronized - override fun onDraw(canvas: Canvas) { - super.onDraw(canvas) - val drawable = progressDrawable as? LayerDrawable - - if (drawable != null) { - canvas.save() - canvas.translate(paddingLeft.toFloat(), paddingTop.toFloat()) - - for (i in 0 until drawable.numberOfLayers) { - val drawableLayer = drawable.getDrawable(i) - if (i != 1) { - canvas.translate(primaryLeftMargin, 0f) - drawableLayer.draw(canvas) - drawableLayer.setBounds( - 0, - 0, - width - primaryRightMargin.toInt() - primaryLeftMargin.toInt(), - height - ) - canvas.translate(-primaryLeftMargin, 0f) - } else { - drawableLayer.draw(canvas) - } - } - - canvas.restore() - } - } - - internal class SavedState(superState: Parcelable?) : BaseSavedState(superState) { - var max = 0 - var progress = 0 - var secondaryProgress = 0 - - override fun writeToParcel(output: Parcel, flags: Int) { - super.writeToParcel(output, flags) - - output.writeInt(max) - output.writeInt(progress) - output.writeInt(secondaryProgress) - } - } -} diff --git a/app/src/main/res/color/dark_primary_text_color.xml b/app/src/main/res/color/dark_primary_text_color.xml deleted file mode 100644 index a0eaff7cb..000000000 --- a/app/src/main/res/color/dark_primary_text_color.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/color/light_primary_text_color.xml b/app/src/main/res/color/light_primary_text_color.xml deleted file mode 100644 index 13b5f1ece..000000000 --- a/app/src/main/res/color/light_primary_text_color.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/color/voip_extra_button_text_color.xml b/app/src/main/res/color/voip_extra_button_text_color.xml deleted file mode 100644 index 336ccd7b2..000000000 --- a/app/src/main/res/color/voip_extra_button_text_color.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable-hdpi/topbar_call_notification.png b/app/src/main/res/drawable-hdpi/topbar_call_notification.png deleted file mode 100644 index ab24cd5c1f31a91aeae6cc08861b40b0c3c599ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1517 zcmVEX>4Tx04R}tkv&MmKpe$iQ>8^J9qdqwLx$>PK~%(1t5Adrp;l;lcSTOiV`$UK|H-_ z)j97IM_5@_iO-4047wokBi9v|-#C{X7I`oqO^Zh6?)1GS_JiBZWmQL4pVc4OCEtg#@iSDJC+spY-s@9KS>^gNAlAYibdf4jJ_!k4BY|)Yi@6yeVjf3S?Vf%0~{Oz zqb15-_jq@(x3_=Kbo%=NH1u+g1-2oU00006VoOIv0RI600RN!9r;`8x010qNS#tmY z4c7nw4c7reD4Tcy000McNliru*O}WU zn3zmkrPZ`5LQHFw{yzCjHZ5F{;?v{}-h zl0FL|oYm!6KmgVOp8ywtbHFP=@p6Ef4?F}60%iVL=YiLNwo3_S39tv4Fo+po02uf0 zl0g+O1(;ib4=fSDAHaU#7T_`9bi`EaWkEB^i2PEw^bGZSEllMXsc6trwX0v%v(n}$PsXTLfo}_7S5X@HHf+^_*%Y~#P z1~Z!XtuHtaW(}yP4wyvJ4oUZ!&0~^wOZvGLb+I66L{iCst^*1+F^Q!6CAFEIfo3-` z4?v@m&Kl5-zRK~Mm9$UNdB<##^f1ubs@Aa-_{xRuIB-*)m~P-XVA63DZY}Mt0Mrh= znL1-5Xhs{lJw?yi&~6IIt^=d~Q3}W@9=-=o%Csg|@{(pE{2c!a7t%t=ozze*aCbVAZeNmF*4 zJvM+FB(1U&{YcV|5W+|v-vd?vZvx*&^b;{xHj^_^;#Q)VAAr^W8Aksnu*4@{C+T+o zU*<`+-BV47tnjp?Gm?%=dP7owtv3(KY1w*QZ5bE)uV}f-mUP524of;+Z&3IPW=GP+ Ta02o{00000NkvXXu0mjfa_X_| diff --git a/app/src/main/res/drawable-hdpi/topbar_call_paused_notification.png b/app/src/main/res/drawable-hdpi/topbar_call_paused_notification.png deleted file mode 100644 index 8718035efae97a1bb23906fad9f32e51e12f1e41..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 712 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 rtl~Jk|A>8Xc-w}-J+ zaB^>EX>4U6ba`-PAZ2)IW&i+q+Rd6-cHFv>MgK918Uoq~U^r;kdNZiu*A6h%*?zXK zeLu>wlqr%)0MVQVl-vLNpX2_AePSjsC#y!qjYP<|Zz8{$s` zr}r7fuP6En>AU5;1^KLc&VE+cvk*~vRn!f3c)JZ34nm&p$AmBCyYOD`SL3Vt?k6c< ze)_@J3@t>4J}%_2LJueG`#Qs9iaGA6yv8_Vy2onL*y2o~}9k<=_W~hw30++_X%>p0!uV3zGC;!QpJ7-#mg0JtfVqVeenrT?(>?c=|kZ@jk zD>uMzU$6B0PxV8olEHGrTv=fEgZ6NxBE=%-4CI?2kp zP(PY0HPlNgxs)PBOLe7{E=jT!5m7Oa>zXucQBl>ZO?yo>*HWcwt+mzOVoP9PY0|XS z)>`k;bq4C(t8;zl$cQ72Jj#%vqmDNEq6uWa+A_t-gcXAjQtR zY}vZ&Zo409?WB`WIdb&W(@uZS+AFKyef@*1g;&<%&6HiI?^)xhQ+>NdFgPieGcp!) zAmeHoprE7W%-4`}w9Hw~e2X+iiA=IsZrlo%F;bX!h~i&()Ia2q1-hPm^Wjr@;#-1*;nEFKbaZQia@eM4-`n=l2z8v`NS66>{r|8EjqF`Vx zq^!4SSE40i*~$5YcxKp#=B+|7#*sCMR?KH) z;l?}95yP~YsH0)RSvwT+-$yU`^jxU_L6~TWJu9tFQ$Un_j~aHP;`6CwFcNjsUWF!c z_Vxy!@nqR=Cr;J9*BVM=gnGsx?j)|AP+4x_qPnyWS#gfN^a#sy0#fWlcZnn>U6T!e zHklmRhplQ!gKXvC#NPe2cV7S}0O(en(itw<+Kn@C$rIJ(Tj3Kjx7 zN;s!VKVV9>>@4mj>NCVYN(J#;n z%sHjD16~$uszLT>FP*dLUL{4@tM@>e5i`&KFnxDW+V)c)O)oT-x7c*0ye*(q-afxm zP43?*$N&E~8VzQx6hs08ebb7Lj7v?n*LX?h#DU`2KmiQ_K~bq_PrBXdFo-V~8NdkZ zs}&YNYoYF5c2!!(CZh~ro<@sh%D0?UfUmtglbQpZ9`|@QFhw@a zi$+sX9n2WeI}@QnQgdF8+vG?=(C)}1Bc^-XSpZ+qG!2luZ1B+9r^BqQ-n8*Th-e#Q zMH6Qp%5G(uX9Llx&9$M+KZ>~f{=|F4vqhY)|E-9-8}iQ)p9dx%;2G$?5%2Y&HO$nP z2{C0;rvxcKK}r+Jc199$sHoW1YI93qDg?7yx9f`S-q!0YP1Vl5C5j7fB55^(Y~AEu zxwiiDKe~v>V_rTY&*em3zl*&468axU{xnp`4l1DdMR2I)4j6NPu(Gb@MA1qoux8{| zObaSo5j6u2Q{KfOVzZgv6NuakTJ0x zvEZcz(3nchh&!BdFm=`5Mh}zGiBZ(tY9T;6Y*Zo$x6zOI=s0$$oh!~m4Sed28hn4KA&=~sr!NefcEx)3D06k1B#@rq*r5yzx`hl-(5avde^Ii}C&5f(UEq z8&a52ehZBahf!+PJvExqm}ldf?nEJb>gT-1q@k^_GlIbiy9ec>s3z~8&b$K9VL#AZ zykFQ4;4=YvFC1jb)7124qM7>HgqBs~uRzGNG-Zk+mX5$A*jMlb#xtQ$Qz4G5(b7R; z#`8JXx)A9;GZ^)0vX9{qc$g9!&GCwY8oOJ`GUSE-D6KtpONE2ul=~**R<{)i$UHK2 ziysvFmyn6y&R1%eMJ)L{V#BU)eagaM;Z+z``*-T^b)gH-5~s;)md7to0b_{w7+cD6 zm{KcY4@jWHk!-iuFvGKD>=z>4s(5_c8Q?N)e=3SR_7h~gwqPNUu`_7jOQ0j1@U7Nb zNtby)%2LH^r$*)`4A}1Pio*T4Dt=xRdz`nDu0|3FqwdUCGQS`L2XgXgdqtCjgA-(E zKp~~zO2bm05X50p>Yy&!W@)Di63vj;Eg0rOVrG_zITrPp2{5pxgYiDc&qGH;1B*|L z61Pm)fk{#%1{jht$F$)ZkkLlpZLCGvz#pI)T3yKUUbG%o`DLqc~q#Q#KcWr-9bgBvvMv^`eOeLqk-!kN z2RuttS&0)_n*my+Qr8v7y_rH3It{yo+HccVdh1QhiyV4y+D=VMXjmfMN8D}UCb?=B zb4j7ifykHI2N8{ZLpGYGRV40%k8X#aCDGYd9zw@+ci5A5!k-X4(x!MhR%X5xb~0{X zENblrk(Y;>7hK?u$poY4@_;55*O>n?cei$)3x9CkQ*rBI6nD=*XTQ0J_n$BPdR;i* z-roJ)JU(vk{%#&0({T456qIQdQcLIKC{}?*(kKwgI8CHQ+iM~4bwOhGV27fe6knUY z0q^J{@Tf|=*wb5nP^lXY_l_n@XgS2FwRuw1pQizVM}$Jz1)B%>T8=A9!!eLRR)7$7 zsM(5?)yc*qv8Yg(n}>AaCoE^H7%2JD=ESuMAFbik27^iK@?iECfT+lbNib++OEgEnvr`GRp8LjVVCqUE$iAw*xfg|7Uk?~ zU?A0LTsLMY{Td~Vs+L}MM}76Y&~n-jesqBe6Br4(#|T7_3ag8-X~;sDeNHvfUpasU*|7=(JPwFzT?6QjmZJOz|jc zNQjR%f@=$J@FpR+qYt@TMx%Ax^Gj4b74oV^L|JiKZha1P6vJ2WIoYNz4HO^zkK|9YISmoGq>Un_krXDv?0`vQ2i%d>aoRe%i$g~l zivk>ZAysh4$0-s8#+||-DpI?9dL?tVpi!0yfYlpQ4+C|{@D|hi3X`^udBN9R7eroMb9@FoKg|yH)t9hisc!$ru*7J0&Q~REX&$yPut0+hCl@nj?BPx zP&swEtw8reWZ(vn03b3#`tGo4uM;@~2xi+DPe^NA3w6%g;;`qU+nyK-DBka$klAI} z&9?VvjG@mARxn*EkNTqLq&nMwxF~^S$2_ZT4IC;O*^&X%ffoU`a2M0I69Rs;&I@*l zmHpW!OTW)M#8hJ-07Iy30=i_yoz&r5aQZl&47Q_iGjc+oJuYhTN@fH8fJoUXg9NsXH-A0V8XzB^=%&jSz zV|&1;4>fNZLumlR0|xdJWs8=A`>2Cjpj1y!R&lHw_3O04T=DUe~`mKLmUAhOiBWu?nnOfelTH1FPZyHV)8pg1ku zhzBAoo-}m&1<9CV6MF)7MGW3$WI;M($`J|nJOkrOK1wI+AmsHOBtX(K=?a`ONznGB zZcPV++D2(YIbQu@(aC$whmgpiagT4#3K#MX73!vNYc15{Q@F=`21`Q_*;5QFcfyPG zolui)?^Syeh+~PefZDkU-I9H9`+e$uo(XIR1x*BaZv>Z`#=!!L=I7CU%&-8rkwT{d zGY81435RFCzuNWt*R~Iz2U=!z@8V_I=AS)heb$cW3igCHrUqz6=*AvcXNU0}21K@a zfhFWR$8d`RTN~|>7F163X_1UQv|6D%fGG3y5g(?-Q%690q)P>xwOk^MSdQ^Lc zn~=b}%+rhFAkddZ+9?f3l1DYDu|H%G!bi7N2d9~y%l8qutUoRR_YXc0O66V%jf@4%vCl8k~-TrS14G(#yT$U&BOvA_2+g+n;9?bA5YXq(<^ z+B3IZZC}Rrdav^~F-b>3g8FPkre{;vw$-1fcUONz90bzO_ASVIyZ<;f+5A>f%D#?^&sXMU~`&g@}0vJgsfZWz%!^M+&A>(K2ItLwU|O5fi4Oe3uL>JeNL0)yR@u|7c^-|Qd}Gb z*Mfr|i&X~~XI&j!1wrrw#L3Y~(M3x9Us7lh{K$3L#8G0Q)Wvcav$CNQPZ7rzRik`=&Si!3 z7H73uXYG6P7lsSk$}-n!4k3j_EJ1_-8Ff@pg@pvI8Yw0+w4e0w#~i;%E}2{vFmf!Q z1{IRy2mgcL-I~S82{$R61bSa=`(qdg?gFibZGRuzcIyNPJOfu+$6sp#GoPe4I$Hb) zh-?EF*BwpX11@)f!6#iZBu5I+^cRc3`x$*x9vHX<`qtdu+WRy{D4^000SaNLh0L01ejw z01ejxLMWSf00007bV*G`2jmDE4=^7W$8$9R00W{)L_t(o!_AmoXqIId$A9;;ncCV4 zWFwZFImC<%Or;LWk|hO$f+z_(C_1X>pn^_>L`XWS1N#6w3i_gNOsEbrQq(~~CDuqI zN^=>JY;DU;bMF0h&^3A-&)l=^^<96s;pOGNulxU8_kI1Z>$;P=3fKu;0XzeI0eqQK z>L&-a9#{b^0j>eMfSZBDxe(Y3>;}FE4g-f$N@pnpb+y{-e;8EXQ}0om)Qi;R>K63} zb=dMa6>FYV*QoQ#5NcB2E-==oexkmneyff;I~-ECsjX#5J*u8AFj9$8EsUw3skf-< ze1w*&-&i~Js&^KV$^cSIM}W_P3bnyv;6>n$iKMdJ?~j2oYKL~%$Dw~8{txXfdjx$4TuZ@Ujxqr?+1tV^}yf28^Du5AFvO&5m*<=v%QMP zIS3q!FH9+9F^X6A)sr4R$u|pFv*f( z;56_X@D=bfa3G~LIwhg;Oq)`&1hFPyQPkfHLmsjMk8}g4fa5@4N@=Ks?KDnk9dHS- zH>EUO`iA~%U@{L|<^yxK8bb3Y3br1HmDs@jL?Th^Wb96gUAK1bTo^f&KBnIh`t!k_5~HZUSxtRsw5) z=7M182JT8Jok+PacmUW2Ty`GWAB&9r0q{pu3O%7agf6WF<^wIj-BBsj1&N}l8~`3q zDZM(;r3=*OW{@;T)W2ek%wYX1QPUTmbGy2;%H8TzI9ybY33}vG|^{3ps)0=8znx@(|0ZW07(3?fk^EX>4Tx04R}tkv&MmKpe$iQ>8^J9qdqwLx$>PK~%(1t5Adrp;l;lcSTOiV`$UK|H-_ z)j97IM_5@_iO-4047wokBi9v|-#C{X7I`oqO^Zh6?)1GS_JiBZWmQL4pVc4OCEtg#@iSDJC+spY-s@9KS>^gNAlAYibdf4jJ_!k4BY|)Yi@6yeVjf3S?Vf%0~{Oz zqb15-_jq@(x3_=Kbo%=NH1u+g1-2oU00006VoOIv0RI600RN!9r;`8x010qNS#tmY z4c7nw4c7reD4Tcy000McNliru&0vAa{K~z}7?blC;R%I9k@ZUT0 zHBLA|(w5_3(F-Eu6;dwO8+87XM&ZU!0Kv5?YJN zs>T(3TuM2GCv8awi9axhZ%Zlno>${85j?;&9^x2I;P)3bU6)wEQC!DWTw2Ph2S%ur zk|ehgzEl#FonI9*uEs=D@CZZ&Ug>s;YdyRffLe- zrUh)mS2&SmOPj+Wedrfl#K9V&y z`%0rEr5fTKZr~g2tdX*0$$B`Bua|Kif{p*Rrt(*kAL=|bi~Y?3v4_VsaYwPP!}87d zOIQu^Zjw*yK=?@=NA8y-Z&vf#*}**2$8R;q&X-aaTjn{At(d@%!~d=!hFEoY3YotK UAhOh&@Bjb+07*qoM6N<$f=oEty8r+H diff --git a/app/src/main/res/drawable-mdpi/topbar_call_notification.png b/app/src/main/res/drawable-mdpi/topbar_call_notification.png deleted file mode 100644 index adf504c79c2d14ebd2c58e145a4fcb894122251b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1103 zcmV-V1hD&wP)EX>4Tx04R}tkv&MmKpe$iQ>8^J9qdqwLx$>PK~%(1t5Adrp;l;lcSTOiV`$UK|H-_ z)j97IM_5@_iO-4047wokBi9v|-#C{X7I`oqO^Zh6?)1GS_JiBZWmQL4pVc4OCEtg#@iSDJC+spY-s@9KS>^gNAlAYibdf4jJ_!k4BY|)Yi@6yeVjf3S?Vf%0~{Oz zqb15-_jq@(x3_=Kbo%=NH1u+g1-2oU00006VoOIv0RI600RN!9r;`8x010qNS#tmY z4c7nw4c7reD4Tcy000McNlirumWD8~Q*0!&FnK~zY`t(7@wRZ$d0*Zt8% zk~qZ>HK2kv0Xqu?Nfe793Qj?+ENm<+ERrHPf`t}Qw6?bqv{TeV8w-(ujSw>62nHgK z8TpvRWph3}KJOdylLwCn_q==d-S_OXPonHmTY=NS6mT!4G|6&3>TdP9`bV8sZ>TlP zk+@DhslHc>>Qi+{oePl0r6gag_Nl+r8TFFdthTFzOJ<@TRLA4=U1}i{I}jixCJog9 zbVq_KYRZWpQh%u5)Q-jGF92KOJCIVcEO|Z939JF$0Pm_{a95pI_m{2L2CJglueMcF zf&x$kQk=4sJORgmZeSGXNhu9g3-A>X*qT|d0j>f~z(e45wJI$DpMhDREhEKw;65-7 zoB;MNr4M$gW9mz_DYM?FUJHedsGZdUtW#g8-^*pF)&$6uI;`%ikYlsjk=rL~QZI&* zZe<%S;tl^}qPD5eqED{pcD%YNHjk^jbK+ShBkDeNMc7HdIvdSc*}K$pq4`JZ`kZ(< zlfH~O-cb+#*Spk~uAsCGXt9kWr=ABa~)SK$6(%!TPkOZs+wgYXzHee%A2h0FpfI;9@N@=3B?;k<} V0RTW`6r}(F002ovPDHLkV1m*;?Xds= diff --git a/app/src/main/res/drawable-mdpi/topbar_call_paused_notification.png b/app/src/main/res/drawable-mdpi/topbar_call_paused_notification.png deleted file mode 100644 index 63fff65b96c7be739a92b02100186927960d3142..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 644 zcmV-~0(EX>4Tx04R}tkv&MmKpe$iTg4)k4t9{@5TrU;5S8MnRVYG*P%E_RU~=gfG-*gu zTpR`0f`cE6RRU7TlmpZjwRC4rtTL7dpM zbk6(45muHI;&bA0gDyz?$aUG}H_j!8{X8>bWHa-`5n`d##c~(3vY`@B6UP))qkMnP zWrgz=XSG^q?R)YUh6>usGS_JiBZWmQL4*JqbyQG=g*dGmDJC+spY-rY9luB}nOqex zax9<*6_Voz|AXJ%n#HL}Hz||^dS7h&V-)Dy1zHW;{yw(t)(H@J2ClS@zt#k1K1pwM zwAc|4-UcqNJDR))T zjFl*R-Q(TC?%w`A)9&vFz+iI58*}-G00006VoOIv0RI600RN!9r;`8x010qNS#tmY z4c7nw4c7reD4Tcy000McNliru7jOYw z`3BmOJ)(`n7se^*9VJn9b^(4?0uqpb1SB8<{{eWGJFjGKNv=BKQ{U394Bc(Bn6?21 eNrR+58u$R=)Id={5B@~}0000EX>4Tx04R}tkv&MmKpe$iQ>8^J9qdqwLx$>PK~%(1t5Adrp;l;lcSTOiV`$UK|H-_ z)j97IM_5@_iO-4047wokBi9v|-#C{X7I`oqO^Zh6?)1GS_JiBZWmQL4pVc4OCEtg#@iSDJC+spY-s@9KS>^gNAlAYibdf4jJ_!k4BY|)Yi@6yeVjf3S?Vf%0~{Oz zqb15-_jq@(x3_=Kbo%=NH1u+g1-2oU00006VoOIv0RI600RN!9r;`8x010qNS#tmY z4c7nw4c7reD4Tcy000McNliru-znz?1 z6E(rqXfz3=tyr*9ANT-qq-dNplTJFYP$)v^v>*stC$^*NtTW&Qctf5$u$cOoOAX%|62cl*1vaRoKUNPxxivzAQi-CE%`P|YEgZto>9M1YsSJ#WxzGSCg4ZlGvFC;6X*f{ z0-gZX!E7P04EO|?2Al$Rrj&Zd>$;$Rr}n8M>IrqD`hi+KPJXMpS?vy1zo;E*Do;-8 zS0P8gx=k%qAZe?5P<^HTrY8A&(=yqrCwZmZcVDV6)U)cW3|JadaSXT@N!7p}U}gEFjS)k@Uf^gv zUk;qbKzjx(0BV3+DWzg0RRL?FfD;rb0cX{eQZh{Vd*HTbKx1_I_&;-`G^BO_^C!um zH^4`qp8y$9p8>ydeza0XO1J`rr&4T$z2K*Z|+tsR+QVA#kyMfk84p2eu z4i>dQ3s405GT;vI5?BG$$M*0V=n5StiV@&qPNI*1MZiPgPt-Q`hB^?smpgpIt1N$e zc;ABea`Tq)pR?WZ`U>(pGA07hM0|P)4@L{f@Vz~7J za2_}SJj#vg+apu`9QZztzXI?oX1fkJ7suxSFfUk^fakzJG3$e40q3>f5&9L?m+E8n vin>*8WQvj0=Gb!@gZcyDF3=y_#+2d>6o-_G^fkXg00000NkvXXu0mjfsq8Vr diff --git a/app/src/main/res/drawable-mdpi/topbar_missed_call_notification.png b/app/src/main/res/drawable-mdpi/topbar_missed_call_notification.png deleted file mode 100644 index bdb4539460db0d641ecf5c12aed626ffa9600873..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1400 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 diff --git a/app/src/main/res/drawable-mdpi/topbar_videocall_notification.png b/app/src/main/res/drawable-mdpi/topbar_videocall_notification.png deleted file mode 100644 index 87bce3552eabf3bad612903208cc7d446b854bf3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 833 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 diff --git a/app/src/main/res/drawable-xhdpi/backspace_default.png b/app/src/main/res/drawable-xhdpi/backspace_default.png deleted file mode 100644 index 1280f1698a731c43b83b4ef36007a23f40d1171b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2335 zcmV+)3E=jLP) z0-={=_qq4BMANZ@9TgoIB#~;ZfYXeXl+xUNjvsn)nDp6@m%BicJ>Rnb&F;?m-RGRM zXU{I-9GUC7vjJSi%$103L>2_WiHeGf-(<7dm+4%b(6X#Mi0AM$_C1^ ztc9hOG)*J#PNh;e$B@o*jMXw*REZ!#BM&1p)Z|I*9swa0jSo< z2ZRvUWwY6rFLVc3mUWE~BF)T)eBW;ez~w$ib8~aW;Nakm0A?zs+H<+w;n5z0#t3PS z5aKBSb2Rc{rPRlAx!emEIwae+mjn3a>H9q*+BP^ixT;Vn6w7#y8AQ~@%pWdo8ZZo_ zzO%FQ52Gm0_m4XCIgWFg5JHAPdR{4IO$10KFdNRiDQD&zOw-)b*49>*YPi0>{$c>x zGavy#vREvBI?^2Fy6!AymH=us@{5LHSh-y8$V7?z%&ZG@H+6S+KQh*6bJ|*3TB<55 zD?0$pdxO)3pvXX3mNio;3S)pSN8j;`uh5dPn|mTB!H_l@_&U8i=>o)oapx`zVB~h=C6jpy3=u- z`_6WZy1Kf=;NajkXgc2jibS+hO1WpO*Jun=I3!Ke+yh{~Mn0jGYVv*mnaOfP+1J$Tg!1> z#?U1UU>kszkT`pT@B1B?eJg+u>6#5SH8sb2dwchprg=YrJ3?~X=cJSm zkM`Un34LT3#t%c{EHm>bJkvw*Y5)}>W%v%nhMu0DlSK5yFuN#b{uLs4mi2#7rEd3LjaJFbT?-d3{J>NP zJC1QG2_r_=Kz0|fwyL}ED+{aK^G*)WX!REFg^&N^n^inI4cFA_p{uIpYK z8K`VFdz_gUG4tO-U}c+{n%*(hAX%2Rl$n13eFyzj&9*9~RC{A%Nn8C=~typaH-U zP3ie|rBorEPS2U_kX+Yo0q_{~P3zsMRH`+b%^puA5={Vf&*7?-QhQw2of`?L(<_UJ z8~`tB_P<;R;WaikUOHJJrPJwVrBv9&Z+>84pf!`pymm&)NCP;a+4oIKDQVmG`y&Ab z0G{U^3W8uEfTJ4u-9ZrKEz5e#Bn)>ALI^FSuwhau<ZOD0j_umC^@G!wr>^t<7PNry`r0Ny5q$d9)e8@A*u&7Ro+ z`ZX7SWe@~UmPVo`I;7KM*-4GOkD1-G4U?XcGXAM6#)`$F=Qz$~k$@^)z;n#J7{CdQ z{E@1vsyz)24Hr$|oHX26_WokA*yQ{Ee@1ewQp!jJ_?Je07c=L_n%_q>g(RiyVdhr6 z{@`$E?4EC$=C;<>)@c(E(i|Z~2lQjvhm}%Ig+k%qqd8_ipFhaVO91HLz9$HR4%c;) zk$@_}+CxMu01Rs6#r^&LkCz@iP8btHd>4T37e20uUn%9YN~uL5QRwO*2tFSfsM6Bj zNklgR(7lkh%rwodt*xyS=`#ho5x5tWQt9zd{FY`)S^&JF$?tO`1yzETA);FVD2;q$ ze}DhD$AQnG`9uCbDul4d^0q}telHNw^_rC7M-Hk4>u1b-JM?Y+HRBlvKF6W&ivWHM zpcg==VHh9n>gqaB&U1L4*Cm8-@%q;Q4iVAUeBXa`w8t5jGabjdCG3MAM6}uSyxS1r zGnz@-zADdaH$#3cTZrgR9Smk(YgyL!V%L{x^_O93KLkKq#SqbFZQK4v?B)vqR4Hk0 zLipUp-L`FiId=600ICG*9su7Ak?*lAYeVer3jkCJR_N?D5q;aTto5u=w?cW(s|-8@7yi0EGZtFy6{B9C@^ zB<-F;p}?zGuikOw$dOtix*~S}xb0Y^-GjQixPU8nplb002ovPDHLk FV1oCOa?1b! diff --git a/app/src/main/res/drawable-xhdpi/call_add.png b/app/src/main/res/drawable-xhdpi/call_add.png deleted file mode 100644 index db4ca84b49d8ab946719b0890a16cf10370156a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3597 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 diff --git a/app/src/main/res/drawable-xhdpi/call_audio_start.png b/app/src/main/res/drawable-xhdpi/call_audio_start.png deleted file mode 100644 index 3f7b6932dec8b1d03de35a35fafe67308f3c0810..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3142 zcmV-M47u}(P) zv#jyirwi6n>Zf+#5#T%veuGi~Y8>zZ$O_%g1W6Z|JkjM~B;7H)tz@xwyF-O?Tz!41z<_Fvkzz=}uVyTo9ShNYS!jk0iU!L7 z-vZtbfHfc3wjr%z05%YKAOO}9V9!F>QFcBaEvZe?0%HsX6Xpm>!|i|9sN|eoXpC8z zQ2pH`O_g++B-h%sMABD{G4JOogu%dqvM*>B(5FBk{m`nHUM|pes!y{&BK9@JoV4c)K>^}2c`wSRDgMT zg9Y@c!oyHWZvq$AqPbbSYo({&8?x&BP8NhXR0OcDt%C^S?>m7Bi1NKYHW|#uB1v)av&hak#caao~ zvVQFdoB{kT!X7I3I$Tzb*TL5BqSq6Z2`~V-KfwGK)_G}w6FVAM73=N^z+EwXdb5E6 zRn+}wPd(F;V*XIOsQTzFV01;9D6pD4fDt+5N!PM4P1S}(H~1q2T8 zFP~fAewph#GFW9F>eB%kECcq4S=U~6ZQ4SPwKCTaxFaH3=klm|Is&XmIuKahfL{eh z*P^)lP{1!ebxf?qE4(NG(kkHCD(dSLVA;ML9WZ1U#Id)4Ue@0RKg1pJ7{#VC8er~U3OHQZ$j++Q*wvk6$ zXn`@xZtIc3gvuX&!||i#8ER0+X9(6r3)ofGMYJpiSoZ+EV$^-Qr~I)o%Kp<)?yr?U zy4vxhYcf`Mj}PpK-jQQK?u``J>s6ADyL)DFD`{EW)jT zJ%PIeU`>f>*t_`OEru7!6(cyf{70)CKiE45ppNwH!miz7VNH(*V{NQ9kHC+JQT`Q2 z*~?fu#Mrx|0~@&d{Rk6!9{H4Hc;qWB!g zv#2UMB%NZ6c|*ma8IEV;Bg!mt2|PwyZ#te0Y?3=Z zx;z#WFw@4342n^vhxHrYZ}+wO$82k?J$ZjCaMK~bRD$(~z*^n_E50CaatQG1gxd&BgYN;u8%RV8 z@tY3ey_7;b4Y2AI;2=lTO?#XZ2{p7eGH~+zO<+g^k+1+=$8LU{Qd=RgmNmc%3-8ie z;?Yu?2H(vi2(+UB)#6EJyGM#`hDO#>VD|Z=wF$^2eKkyXEi(+4x#`rlSM3qv!a^X&JN~rC{uyp&SMPX935h{gKwgNiBdD<%wLjEz%rbRt5ct05oYFHCFP@C&2v;`0hV>0EvWLOeBr>F>|ylq zjQJRr23Wwp5jhAS?hv_~YzC~Qp5u{C1p{kHmGw=GNUwdR%|Sb%L*yH@wuFn$%Ym0+ zo(;~bz@l{Boi8Wudf*%fsOt-#cGV{3aODizDF#7ggZvg_}h6HlRO@I7F-O$`5G0HmpLQs-2(dvu|vLEu~ua}QM8*^EZk zion<323%N2yC(#4C(X@uWLY=MOb>IH=?8yC+6~)jLnH26}|jo>VH+ z6sE!dOXiW}Mr6b^I6v2E&@QMhEpQFcQs9v_AJ`#B9PNreQ<=4hSeVeI+1>TZf)ZBBx<`7T_OLAxQa=Hv<1HlE~6x$oUr@Be{Wn+_3R zU675*;?tvtcQ;Wvz7;8`AF^YdrmiNzQ+xO|7em|7;Mr&M# z_al6x=(E7EP6pCR9w)%tX&Bi_yL3Xg=X_>kGMYL8NJBgx`i;Ptj?jUgl&(t+(Q{OJ zr)|Od*xTL)eWH| zG%4VrZ&nAMCi;4|^egE#8~xO+;2++no+zeKokc?$6A{Bue(?HYVC#bM-#>8k!6bDi zvhm*2BG#_*Q-_yR!%d|r@%|6r)kU4zn%@h!&0~YO;v8n$3r?DI;&*Z&+^kuhoq$7Z zoIzY#2i$2v8;}u?2YdXPpLV(ATXy}L8FPe{J3JRqx*q(fq&F?Ctu!GJlW}(1axn!4 zY+p$~k#wqR%fr4{(le4?leAE!(#MiU z8Do~HL0-_h1IGe?3D`4f(n?}ngxn=ZR0ME)NIJ%@B?qZ=PgBxf`4y68O1e+doGhlh zP6w3BRb`cc;gW{gzZN@PhGN3oRl4MCl}h(Gy(sB<`|rX$?+)pdKt+JqQ>9B{Dr4-{ gNm^-dMJpWr4-FmO2FdfF#sB~S07*qoM6N<$g4Is`&j0`b diff --git a/app/src/main/res/drawable-xhdpi/call_back_default.png b/app/src/main/res/drawable-xhdpi/call_back_default.png deleted file mode 100644 index 3045bd86669edd68c2690ed2535242337bb92b23..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4666 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 diff --git a/app/src/main/res/drawable-xhdpi/call_hangup.png b/app/src/main/res/drawable-xhdpi/call_hangup.png deleted file mode 100644 index 23fb401726490ec605f0db3234aa932cf046afab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2173 zcmV-@2!i*CP)2~z&c6qmb5|AdP(n;G^>C`9hLNmq=zK^+BtXFKqmvdT+(MGZS>)|O!}{+ z2PFMW(tXak;|6*r++Inam2{(TcU#eO&soTlJ1oBQ|H{cIz_<8fyZcEBft-V8-e~D%H0E818f5B2maGw`CkLR4=kxV z;B~;B1`Klwco_IPu%dwVnFD+b*aqxxpk9-}9l%9Z1N;Hti3SWY2z(8=utarqz#3o& z@KVBho&dH3=a(JuTYz6R5Z1NzwCJsY?Icr;<%Uja4)v&&Y|Uf&j-Oi;-o;H$vA ziZ)m80)FhnbrM^AqAV5r5-^saQhx$I)Ika8LSRe6u>M=%%|)%qV&I;HfYyq4ZvuEuF(xLo$cXw08@4B_8VYP-i7T3z5|@8lVsfj^cdby8(!{1 zb(~%R-j`PamjZvNQ^5Nx*H=u>o}U7a#MttrA5U;u7V4Thq52@O!SFR~*ZII*bt2@w zz?>`!^AR78qx1l9fnjUO#@E-$&L2#*I|_U*rf)n2e6v%t=@e{z3GjFf?5BaHX`J{g zF(D^=ls+|Cnzt4_aop zmsH19d+6?F=iI9q+VAI*J{{ORE@}C+H;3I4yEo%2vJV)iTDm)98!n4TroY=IX+Y9~ zz&=xwzMZibT&vp31Xa@dEKlZ~+b3yz$X8c&Njett#iVoYpBWovaRr;sg*gl`Jsq;` zSeJirdm`|~JfJV*0`^r9w3C$e<2GTpEQhAAw*8Xc9{6&ZYR?vDr7(lsB57xd!0wTB zSzy~DN&n8U3}H{1?wbZZ6asp=FL<6F{k(JTXRFsI zn`tMsvm%<>;Ua;qgh%`2aV#X)$-=9bN(xRA8FkLRmLzl==<tOjsl%|LgpX6 z4UTi4mh@7EJi4z89^IdVH;u>G?zMRq+K~a*(8O`tvk9N+T0{)`b-&E78o=O37mujynTIn~f^Yy>8o#%1R9R&`m zHYAy+2J0E*JqcFoO;fv@T6Cp8=*vUS^=L|wV|~nO1X*ZT@J32^$Q8L8xKz?xbGQnp zBt3I>646G@O}MQcUcC#rMBx(w($=JN?z!N`?udd;Ncyu(t-v1B>+fXcCR;+PLu8;0 zw8=o547ABWn+&wcK${G-$v~S7w1GAmXp@098EBJ%HW_GB$AR{5Z~MJ}1;oYzmTw-g z0~iE$+FSjaGve})7p!e+;drY@MI(F|fbUF3>*l8jMN2?0ua% zd-j%AL7OKR3-%8hft?hzK`tNc+ZqLJkShSY9%zGH5!i8{feV1=BK98xR+**C0efo{ zXoFnNuGh{O*uMzb_K%n@)Htx#EL%>`PAc={4uEZtO9p#q6Ob)jmlXD02H9X6yS1Ag&a2+00000NkvXXu0mjfX=Wxt diff --git a/app/src/main/res/drawable-xhdpi/call_incoming.png b/app/src/main/res/drawable-xhdpi/call_incoming.png deleted file mode 100644 index 89d593a4c7987aa3399f456fac29d730602f38c5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 864 zcmV-m1E2hfP)AD6jX1zKB%87Yt<_z!4dofeN1| z`~d(QcEj5yigOKv#sD0Z$g@OT*`Vsd<$$d#&^38@H(T^ZcHjWavc;8tRi9cJ6n4U+ zW@MOvlM;Cz7GKi5`s8ZCmMwT3$lnz;BSQdAN#uxNuJU>HaoJp92b`3hLjX?8;0#ga z^XOx1&EM1tG>sHyk;ohb^BEbOh%Pp$-$vKVk+gy4U{Pke;phjrOd?0YsP?-Ap@<0htlU2?g!|c zM2-=mMstT>Cz4Yn0(WE?{a{qc;7nZOSHpdYu4r5aIzE={aAXwdTXGTGZCHi8$DvlqZ+iEJ@Zo#qa`*i0`G>%sGdtA`0! zWN={W;?084+xU3R^qYji>> zoSC^^^XO0P{Dz3T4>0(S-S9*V+|%6pgH(J!I^rrGXl#4uY_y5_6U>hqJo>|U4G@4_ z8;i~bQEU5q=Xbvv?h_z3u?@pwQWwtv;nQ{)AmYyeApB)|4;W1VqFcl^K`W`a?0000EKVIx3-^Jz>7 zP8+(@f?d(TKF7p0!R#vF0}gz5T*5KO#C1VU1}m4~keArwB;NdAIzGJL(KwRC{r+&9 z;skDaCIr0>{AeZTw^#7j5V|dx8Ud|^Fye&o3+e<1 zog+JyMJL-X_-z2*I`Fe;8(Gvux8SF<&6zeZ%fuGJR08s_z_!2u)SL+VCSG_gqV#9-_w z5c7Qpi~^eic$L;b0As*LU?^t#U1^)f%Nj>p4u571f_o9pxzJ#-bSOTRrh!WVjO6u1 gr`Q#L1TYNn4_^>8A0t>5w*UYD07*qoM6N<$f*bcO6#xJL diff --git a/app/src/main/res/drawable-xhdpi/call_outgoing.png b/app/src/main/res/drawable-xhdpi/call_outgoing.png deleted file mode 100644 index c6df94bd910798c1a7e9f8761383284a7dae02d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 528 zcmV+r0`L8aP)Yx}0F~xq%s4jim=O2` z4E0%pb@B03;pYC7O1lJ1-ti9BH97pxL>iDOmL`<;P7mC?285gYQ_kc7VYu1SjDotF<;w4~KO*`5H*4JfFOi0Vj{3#{E}xB+1x`9nU7NYjs?HUF ztBwn2QR4td>2!1GN%O722gKIeflF8Oxf20+vsIZfYzg7o+Nm$qeE2i2U$ftkloUS@ Sv2zmu0000%va diff --git a/app/src/main/res/drawable-xhdpi/call_quality_indicator_0.png b/app/src/main/res/drawable-xhdpi/call_quality_indicator_0.png deleted file mode 100644 index 06804eda3313fd1302d0106a63844a1b1bfa58b7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1117 zcmV-j1fu(iP)hN4zUzjO3sPT?|k=Of3{`7xf=n%0pJ0+0Jx8jkKg3;`4LHy zV(E0c0{{g;h=T!5ND6?<=kue1Kpz2< z7#JTP|C2gYMRQL9a3dol!CHYl&x;ci6F%zj;Yt8dm&?Ul1-e`=fx_7v0YD7Huv}a2 z?(PV6IAGyqJw5Go>caC#K>*y$ z%uJl;c~78Y)YYblTU~m$QSSv>7TcEG+zB0-FCD3WY{< zxm?H&YJTZ(<>u$-yL>)hPpzOxBr@<)2B4WrlK%dF(K3(_x6_pb0IKUcv6F>swizG* zQ13ZJ+ws^K(veT5`5MKuR#TF%p(e4LEo%lOsRCta@*QkD%tg4m|M@T2wvgdF-;}Nou4?XJ=7mLOC`T2Rb1z`0- z%$HAum6erFNs^+9q6}YLT=Z4slREikvxLdXNq07z9aI!$_~z#3>r^Twd~OYyOeUl# j%CIcU(W9dykD31u8a-%WmNJse00000NkvXXu0mjfqecX> diff --git a/app/src/main/res/drawable-xhdpi/call_quality_indicator_1.png b/app/src/main/res/drawable-xhdpi/call_quality_indicator_1.png deleted file mode 100644 index faa61c3e2ba156e77f50c8fcbc38fede35635a00..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1172 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 diff --git a/app/src/main/res/drawable-xhdpi/call_status_outgoing.png b/app/src/main/res/drawable-xhdpi/call_status_outgoing.png deleted file mode 100644 index 5ae14394b9e90db5d9553092012127c5d5ca20ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3200 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 diff --git a/app/src/main/res/drawable-xhdpi/call_video_start.png b/app/src/main/res/drawable-xhdpi/call_video_start.png deleted file mode 100644 index a936b88949a5b57ffd61d5c802974ee03d1abd54..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2885 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 diff --git a/app/src/main/res/drawable-xhdpi/camera_switch_default.png b/app/src/main/res/drawable-xhdpi/camera_switch_default.png deleted file mode 100644 index 532cf3187126c07be54753aabc0b0316a28032a5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10246 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 diff --git a/app/src/main/res/drawable-xhdpi/chat_delivered.png b/app/src/main/res/drawable-xhdpi/chat_delivered.png deleted file mode 100644 index b49e4e7e8d8d9f3f1b2b3df41f4cc86f3f1c386c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1759 zcmV<51|a!~P)EX>4Tx04R}tkv&MmP!xqvQ$>+V2P=p;1guULL`5963Pq?8YK2xEOkVm2O&XFE z7e~Rh;NZ_<)xpJCR|i)?5c~mgb8}L3krMAq3N2#1@OU5R-E(;FK0v6KnPzp21DbA| zsYG1NWLL$mR|L?H5QY(!nPtpLQWC!Pbx++?cQKyj-}h(rsyT}R0g-r?8KzCVK|H-_ z8=UuvBdjQ^#OK6gCS8#Dk?V@bZ=4G*3p_Jorc?985n{2>#!4HrqNx#25l2-`r+gvf zvC4UivsSLM<~{ifLpgnAnd`KMkia6AAVGwJDoQBBMvQiy6bmUjkNfxsUB5&wg;ta)>5p5ycZNYkv6H^9Lm zFj}DOb)R>4x6ke0p62|106wO2l4OR7IsgCw24YJ`L;(K){{a7>y{D4^000SaNLh0L z01ejw01ejxLMWSf00007bV*G`2ju}33?nc>kPsyR00i4fL_t(o!|j>NPaAg-$G`K# zU~GuXLqwn!kx~RjJfwmnFcNS`(x!QFbCyk{9?GGr)LS3_LKXE`DVJ1{A_K<9JR6~^ zax57{Qyz&#f}uGCp-82Us0k1Z`hd0VuE- zngno5N_j<9)jLGQIR_pb9Q;fOaS1?cR+%E2mQsGNs_JM?fUjP?THLhsoIb~>Gx0o1;Owc#U8(|)#9 z62V~bQ)a&Vju*A#{r&yFrOhxi6EpvqYZ`~cfzr}a2qBDg`X^?lv>6^A9zG4=W-eh5 z9Xf<#$Bv<}un>tv0;8j&*xK6K3;L9zD0fUVOhf~@gcU-dtE&rzg@phBkH>?`%1Wcl zzcek0Kp@~r^#(b!*49>(mX_*#tE;O};g8eRUs+ih91e$V;G;*6 z!tHjyI@UA|(P$K!rlkwjy^`Hee%~l6KR+KQPMm8tE+Xs znVA`@@gJCFxY#JFs;UYej|TwYa=Fmc(*u{wmG)`H#l>iAYt#9jJ$r^&EM{2+EiKKv zEs;oo`SpO{cDvEr+l&1CeA5#0`Fy$(+T7g4#Kc7AvbgKmaNb&3S;51H4|Tqhk`i=x zcN?x&&CSgyFE7{mCMG7Zv9V!?an(#Emdqk&XJ;`#KdtqrNsF7xD^T8tDlG(0&j0`-%E|+rlx5cB9REz*4A|X z_V#voy84jTUPG$^5si}!(k+o$(`AFmF53! zd3hOgb90%B!VSOQ|H4$(q?A|eNL?fnF?`j@WD;XzV>&ssxnc85+6Ml@g$uU~y{&!n(8D@n>Mn+CD^8kR;0Gw8P10uSa`pe+gjDBdguQU`26>n{A z^#~z+se!S2$Efup)z3aAqA?*vWOr|HUEX>4Tx04R}tkv&MmP!xqvQ$>+V2P=p;1guULL`5963Pq?8YK2xEOkVm2O&XFE z7e~Rh;NZ_<)xpJCR|i)?5c~mgb8}L3krMAq3N2#1@OU5R-E(;FK0v6KnPzp21DbA| zsYG1NWLL$mR|L?H5QY(!nPtpLQWC!Pbx++?cQKyj-}h(rsyT}R0g-r?8KzCVK|H-_ z8=UuvBdjQ^#OK6gCS8#Dk?V@bZ=4G*3p_Jorc?985n{2>#!4HrqNx#25l2-`r+gvf zvC4UivsSLM<~{ifLpgnAnd`KMkia6AAVGwJDoQBBMvQiy6bmUjkNfxsUB5&wg;ta)>5p5ycZNYkv6H^9Lm zFj}DOb)R>4x6ke0p62|106wO2l4OR7IsgCw24YJ`L;(K){{a7>y{D4^000SaNLh0L z01ejw01ejxLMWSf00007bV*G`2ju}33?nMM^66gy00R0+L_t(o!|j<%YZFlv$A1$f zE=-~op$m1RNYzqM@KvFT_^NeLx^v;G^&=Gg1f?!q7F-u8Zq!o551@kG_`tN(#ut=e zLg<4qQ^Axp$Hh#{ByFZM_uh1sKb*y!fqQ>*=FU0i-hsB-s*|NVfItek0`vl1z)@hI zcl!^J1v0=a@WujX*Pl|r61XccEl?7WkX90SDKH=gf)y6_w!oJNkj?C~z|B^I9uRnD z0JPDj1ooST?H2ge0@$kk5IAiL_M*UQ3t_9aB5+uqg#ygY zg^kZD&}YF{)iwJwQj~04w#c?smPgYy9SdN70vwCy&fNWcQOfP0Z& z%ly0>qyGbm>x-`Y0@p(UdkOuK(igh002>>%>r%<}sp;`{#bvt)4*+MCrpRQ#^34&8 zxjr?W0s6xLKTytri;EDC*RJDn*Qc(L9UGa5y+OStlOmN$eU?b2baL}wfkaS*uL5!9 zcsLHMt<|nqR=~D(BD@#q3j)5RT^jvsR(<{YbvX$5r1tRXG?dG(rPC&WyZje>8g3po z65(>$WQ30d0VlMF_h$+-z{wzBj6^sVtHB#qBe4E{YiY@?c9sR%tO-UZ2>7@5@Rf=S zEdraHCV;a+z(y9Gq3lrAw;F97xU$6YI%^IKq%$fkfFgPeqQ((2z*6Sc`D6C^9&x65UCaZGKW96M3@n zaglspB$pEz8&iS(?qw^bQ=bz8g3#?%rXh5!Hn07*qoM6N<$f;E^%P5=M^ diff --git a/app/src/main/res/drawable-xhdpi/chat_file_over.png b/app/src/main/res/drawable-xhdpi/chat_file_over.png deleted file mode 100644 index b36547ca19aec3eb1263d1d6efd030d8e9eaa154..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2109 zcmV-D2*US?P)EX>4Tx04R}tkvmAkP!xv$riu@$4t5Z62w0u$q9Ts93Pq?8YK2xEOm6yuCJjl7 zi=*ILaPYBMb#QUk)xlK|1Ro%-E>4OrQsTKup+$@bF8AZV=l{9)TtKLonPzpw08O{e zR3a{Bva4d(D+1_2KSmLfnPtpLQWC!Pbx++?cTt|@-}h(rsX2=Q0g*Vy4AUmwAfDN@ z4bJ<-VOEq?;&b8&lP*a7$aTfzH_io@1)do;)2VslFtJ!@W2KE*(bR~ii6g3}Q@)V# zSmnIMSu0mr^Pc>Lp`5<5%yn8LNMI35kRU=q6(y8mBTBnYiiH%N$9??6u3sXTLaq`R zITlcX2HEw4|H1FsTKTC-FDVoUI$s>;V+`op1sXNS`95}>#t9I72Cnp$zfuQgKS{5( zw8#-Kunk;Xw=`uBxZDATo($QPT`5RQ$mfCgGy0}1Fn9~}u6c87p5ycZNYkv6H^9Lm zFkYbSb)R>4x6ke0p62|108EQ=ms&IlLjV8(24YJ`L;z<1l>oV$G+_|{000SaNLh0L z01ejw01ejxLMWSf00007bV*G`2j>9^4+90)<_FOL00ucpL_t(|+U=ZMY!p=($N%4% zDNBn;l^|*)rpu*91uw*ebU~*(+uhV!Ns1(DVxp1&@g6XuXi{s467|6+c`2wdni!N@ zsj)jVxCFLUBwmn239^eAQM?qbl-lmje7-cKOix=VyY0H1FWHy>Ir~3nesivy>_-@f zSh{rSgwD>+un?k}nXdzIIe-9wP5{T4`Evj}9mhF=0uCC>#!Sh(k5JtM zu#bpdwJd8J0KO>U)z#Hq*4x|L1YjkAl2KTE#>`JRj`PJu1uqth&2n9LCxFTewCV-$ zh;7@O3I(2)PxELrdb{U&4uETNV5b9ETv1UW(&==fs7GG^i2J;MGmyn0BHH44Uc0XA zKlSzXWi?Hk2H+L|^#F{a`p3)?%K8M%TOp30kB?D9AM@* zif-h^VzH}S*F6kilCLP9&1UNoiA45%JtL9GdH`z%b)KlfvaGG+1y9elq3gPy6i$t3zdAJryy_GdJHPNArYiep%12Fx%rfEAQ)fY(M9V%#e%zRKH*ZYOC zva*jQ(@kgQmPjP>d@L3_lLaUo4$q84BAb}`b%}R89*=)NP%sRm9KI|V{jtBG;c1$- zTOwZ{i^a}L)6vq>a+-)5C3PnNSm(O#iAW@JG!lvYEQI(0fNV-&Xo3h zRaMo2f`->W3etYvb=~ED&9bbG06rMPN(XQ)fUAdT?#Z^cwzJl|#>PfLL{CfVZp&4Y zh`eqX%>1rIemokD&hS_5?(TjF!1nXqgP3`ZZQC3C1?}za_XD_D(xged`357xp0Go;bO6VbYp zCr>`MckkXo#~i~jbRonJB9e`|W$AP}l@EA%Ndae?=Jfuf!4zLn7QkJ$ZMTi&VHn0G z0J4ch;@{!wH8wViWHPw{z;a3PN49O>ldm&u-jWZRrn!uWnk7X!OJ8Xi#wEJ0zX#xM zN%0A0zA2v>Z}XP+GM!F;6$}RF0GKP;Nf!r$!7GBnU^10T^^V3r91d4$nzjwVBH1y9 zh#G9${x+Ytr@SUyH4I~-uImn}3%k|Fxf zMD(y_S#Rh2>Y4X!wY9aimu51V)?okwKsNx3nRgP=J|RRZ9*=h$hM@-nfoa)n_BtX8 z189IeBjlMQB{Q#Z9B1QL{4g*kd6Mhv>&tq2dYS>;m&a!QV&?lC$N2H28lB;~?n^}UKn^ynBLLRgwmo73*P?(o@YfO{#H0OF z`O5S7Y8JpQB5E!#FW<6x^JaIvo?+bJ4Ls!T*|TSXrfG|qc|L$3fNA|xyAQzM0FE;A ncS49{CX@Lzkx2Z30*L{qVm`V-=0!fTlyGB)HiDqyXLt_zL8!_w3oz;U;h5MFcc2FE6F5t7}DX zZ|@?E`Ar7H(E!~+UxSTR)z#H);p8p;fPjW}@J)=rjN(X3c_<@b&!PW*LPEmF+qP}H z9L`o)pjla26FIIO^xMN38w?-7zhgU}-EDHycLfT_1?=MI7>IQ>a&32x;II|Pr;znA zPi8I&_8{}PF#2bZc>=Ke`;+Sz=&V_@mZAJse~Qrmp>Oc{eC6@+@dr4*9+PZQQPIej zmX;aV%7Oy&b_bLPxR>hA6? z0ci`PKbdGx;#$t3s9OXftx`2C8uA4zOLh3$ze2am*btCvVO@D}; zJ*o1%>LNcse-s8vO(2om?DzYp4;7^N1H=(RW)gtDSN+Qz-?xEbhlGsE^Qnt5W5%pO zaJov9IR@8r9E*=1KW>aeOMc$GdA^jCln2I-A0J;=S9jK;o5uS3`pbodg9UkeV@^&^Jn{Dfya^%x79MV) zNmiv56ci-3x3?d{w+{9!c`(+>1NG=7!*!5nVq;_Hh65>%0VxS$ffUlS#Nxv;aciZe zrDq9)$B~glQd?qYXXk4cAqbQNO5=&2GKRn;GyoZQ#8 zMy$R{I0f(UAW#U8A<-8GB8}q7sHmt|$xxbvuJzu%ds}$eFLnDCHg9HT=FKpt>H^Wx(PcVqxYqzR`3!EN z7v^LD*#0G;)m2tj9vg08y8W1#nD3z3j-MU9gMYL;CCf+a3atC?FO^qZTpZQd*f_jidO zzvLx}9loI@N!Hu`Q$sdwaUwLp$A@_T+W-JbWW70jQwWWe9W_3Iu1%nqLyM@a(`{($ z!~8WL)@ZUJjuVz$mKb#aH3?T#R5WvyP3Gwqqm!aLfF`l4C)6Qq)vL0|^-|!&CR_1$6BgYDh}YAm)YQ}tm|2!UXq0+&L0Vc`7q7YdHD1I^ zDHjtH6W0M!!c7M$3G@{2{s&dVx=@hE5q`n0E>s1rIYuh!OXVYiMT=y__H=BoLT8dm zkC0i0?RRvZ5vY8nXl`zn5RmHM(EuuezJlrBli*2#kpW%6h5LNInrdpP zsZc@J5g$V9YdDZ1VL5~q^nM?zPhk)f9_9-Dc}`}NoBDv9v%8P7Lmy%&)y-I>JOKX8 z3B1HL_ER|B0Ra^wp%=U7m@a3h9W`S(M=BqHM*&y@z;9H#+kC)4EhF-Yg|8RS09L*r zXEM5e_O|o@cMjPq#vR1s_WokglGn$GfvN_q&)$T^1OUh`OHZO-rAtytqDg`saQl3Y bOxX4>_4D^V8u%D`00000NkvXXu0mjf=8kg_ diff --git a/app/src/main/res/drawable-xhdpi/chat_group_delete.png b/app/src/main/res/drawable-xhdpi/chat_group_delete.png deleted file mode 100644 index 18b42abed8653a7a0939c345717ced6088ec0faf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 815 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 diff --git a/app/src/main/res/drawable-xhdpi/chat_read.png b/app/src/main/res/drawable-xhdpi/chat_read.png deleted file mode 100644 index 5c00f33e61b03c83c42744ce3055ed605c6aa17b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1853 zcmV-D2g3M?P)EX>4Tx04R}tkv&MmP!xqvQ$>+V2P=p;1guULL`5963Pq?8YK2xEOkVm2O&XFE z7e~Rh;NZ_<)xpJCR|i)?5c~mgb8}L3krMAq3N2#1@OU5R-E(;FK0v6KnPzp21DbA| zsYG1NWLL$mR|L?H5QY(!nPtpLQWC!Pbx++?cQKyj-}h(rsyT}R0g-r?8KzCVK|H-_ z8=UuvBdjQ^#OK6gCS8#Dk?V@bZ=4G*3p_Jorc?985n{2>#!4HrqNx#25l2-`r+gvf zvC4UivsSLM<~{ifLpgnAnd`KMkia6AAVGwJDoQBBMvQiy6bmUjkNfxsUB5&wg;ta)>5p5ycZNYkv6H^9Lm zFj}DOb)R>4x6ke0p62|106wO2l4OR7IsgCw24YJ`L;(K){{a7>y{D4^000SaNLh0L z01ejw01ejxLMWSf00007bV*G`2ju}33?n34O2k+I00lWoL_t(o!|hmYY*S?ve$Lx= z-B$Q$zUV`95ERF1_1a**&)(IoN3LsG^#BS-f9*k9U4Qj01(Se<_^F(7{d1pcHuf8+POw8CCY(f!wJyN14!XSiND!%|T z`OI+q-k);-aX6^o+!Toe&t@Vn)QA>`6FDa;;q=@ZiFCFtKliBD3^R#!!6a)C0^coN zjJQxE0O0W`6PCqKR$Kmt*OG`SsfZfX20_C|^XFh*^n|->6>ZKwwL)GQS6UjWO4uOW z%QOfX%}9@n#hb~q6t?2#%V@afP;)WrWJ9JZVL@_wu*gP*7_dDf4LTa$Hq8zvK0Dvw zLl|PIDq#lA_U0TS1fGbAMuc9knedm37h-C7*wFKUB;l>c#zre` zXX??}@6y82sZN-1%>+}U__*6j)E^Rxv>j0hk4%_GZr=Tsf~eYCI<+Zv%-m9Xbl=t%%>UqSjM z39nb3M*EGM3j3DS`PiDa0G8>BPK2$0HsW0G)j;;xfFwLss)T#jW;+;s#GgoAE*FX_ zYjDjy{QvYsLs`CqQ{8ra-_#nYDlYHN%j@&n)<_n`{)TIDIPqH5X$+`*YKPl{4K=lp zIIt#0j30aocw5ox3IKKf20h(v$J?js6g~P5Hr1X($IbpgAosXmGaU>1P)p?U z_6}@0e^FuE)AkPz{@W4wh{U{cq^m65NQ-a$w6D#yCO-*4ioY+Y?dipJw+EAqMjZXm zip}*G0xktG@9HeHe5L7LV$#8bCjCvh7C_P%&!4Ss$(Y)^Hrt_T%zC% zT8(OLWLWbpjc&=9%0S73zcJxmx57o*l{|BkKg%^=XiwQ+wwz?%24LZ+h!Xw)i4SbA zn19pe7Fbp&et+2tFxCOc0bul38-Q?_WG=QAT8;(wKSznU($YwsCnSpnXABOEiNm87 r_1)I9d#9>zU8N!-BBIvR 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 diff --git a/app/src/main/res/drawable-xhdpi/chat_start_default.png b/app/src/main/res/drawable-xhdpi/chat_start_default.png deleted file mode 100644 index 3bb7892f95c3f59897500cbd0081376021499a0a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2258 zcmb7GXEYlM8xCp(<*TaIA|A?L4LTAB4v%RWVY29p0|lyRnfCMG^%Jz- z;Lv;RrjXH*qPCYsFGoE90a))DCGOP|iUyYD+F6lQDYO7#&N874zo1x^+&wXqpAp0m?1F3B?WdOP^)StIV;Ns{;Cj@*&Nl+)PfyL z1)3+%Uci@HuhtlRq4wR_TdZbH24HTax$KG=z}5tS$7E#6JlsG)(J->hNteFW&>N zNdz38Y@)ei!c~OSbsrrobu83f@ajW%${LN@oQMOCy7sd$z>dQ zT1S=48+1+p(Ba&RhwxhwS@8i`h|_V!coZW>__6dzgHfYol-_P)7NQ<4BT2P^yCdlX zzyZu;_-T{jLP+|=LT(BxW}$47f#)K)R&E%Hb^)?XVcoRFj3Kbn#i zf9nfIk?hO`VFL*0oZL_OG;AaDnFUV=!=7jZVL9YRR@s@02d6Z3cT?vi7(H60GY%s9 zURBrU-OdGk+G#JPInL~W(pHrQ>E7j7iZRY7JzlDQ%;wUxfodkbMZwarer;^pXY1n3 z;fP`)v*+pah8s?BTb>tiW<7WfYz}E9&uLRvAK7&`k)9|ivEnoC!m(L!!1l143lBmT zTVOXn2pf1Q)YHv7za)t)&<6jU=&3HEB`{8E2?QZ7_qQmIn_SA&-1eTl9*Nt#1C0b- zX!w}0z_nT$E~tr+pTk1d4ToV7!%8b~g*k>x(mT5`U%Mj|vCP8!+bh4FeJBXgy^cDvuNy&N&v{_Gd8iclc+92P=!&*p= z6?HxW>j_n$hyIB9pp8cUwc3$CD{W}XuTAWc_o($eC`P7CZ5{vG_TUQ&D}+{2VUd?&EZ$zbXb(u7L#BAqAEACn965cUw}n&wROC3A1OJ%;axBU7~3?*zw#fTcH@ zf;Mvv#a?wuScp0KuqNDR6)u0z?>z#gnP^qLX#jzWOmzycU~GAk_B3_H1={35F3mOF z+d>6w$5JXcgKQG6_EtXBarv(9^bc_AeTaPMyLx%CXzNsJ62wnRLj}2#j)wOC@q-m7 zjRXYP#a4C(|L)$$Si(>pVi=c~v(wxQN9=vUvd-K$ax}l0MJ@)KwA{7tc^z3L;_7c% z4=n^m&&=J$GxX1bQJAk`$mRStvb8y9mUh+dSo85oT*CGByYsH1sEefBfZQA_dM~ih zZ&lqnwu}vm&l_fJe~$JNze8`xs%%RZKaATc!iA*Bp1)LkB|!a-)L3F* zU57tBmZ)r!!SH2;3s-rr{5t(#BJ6}-?vFeJF z@~WII6BF*BusS%pv9sP_d|*$8h=wn zwUVXsR}L2Q_FKq;Wvy>*^{>O2ec<6-s^T%-*6k^W!IG;|<;sc9k*y;-?VzJjeCkDn z&lh|+xX2Zz!j)VKVY?uoQT9}D-y;qytRed$FVpv{(b?9wI9Ok4%D$b0TjYJN{ps)a zvW*ho3GUmR)0aSAp`^?(*B+ll0PEq=9DN)fM$6iZVNZe@+|*XVlVh^HObDu zNE*e>K-=J7>0;+AFER`Ps(Jge(3$L}znVRm0-!X~IBRd+uFcG?g4{$ZrH{|T;v+}L zLuHog-Gor#`%PZVOzZyg!2Q!vVpm~9l=iXkT0Wy7IfJARF-*rFv|y0HYH?cmP(}h$ z5My)$5!1}!9n7a+*P!QdS7t$QeuGDp?{l{D$WY>1s_gI|4Q&dFm8q^?Msw3TBtkQL ztbTdnA!LxRrUUbFEj?l-tbl4!u5-Uw`3h_NT9tMA?e2tet=B5!?x#`hS diff --git a/app/src/main/res/drawable-xhdpi/check_selected.png b/app/src/main/res/drawable-xhdpi/check_selected.png deleted file mode 100644 index 07298e894d7b04d1c89449dba6f1bf93930e7ba0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1300 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 diff --git a/app/src/main/res/drawable-xhdpi/collapse_default.png b/app/src/main/res/drawable-xhdpi/collapse_default.png deleted file mode 100644 index c6895edc8ef7acb0e9433ad1e17168777b54875b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4007 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 N002ovPDHLkV1m!IhkpP7 diff --git a/app/src/main/res/drawable-xhdpi/conference_schedule_calendar_default.png b/app/src/main/res/drawable-xhdpi/conference_schedule_calendar_default.png deleted file mode 100644 index 59fe950fb19eb989b09c5e74e85847332a92a418..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 182 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8K8t*47)h{y5dgav{H2`kv0*t@PW zi2h+eRMmAg;IKkrTgieabsHA_O*sCziNo}zLu|%@mWQ$tvqFCGtq^9FQ9LanVAGU! zfq%t*&t?T%`vBgi8Gc#^7G>X%c9D}2V(WGLzrt}bTd$PK2e!bqaf_pTy|?a?zb2)~ fo0Qobz{6m_X!0?!iA=0OhcI}$`njxgN@xNAQlUMB diff --git a/app/src/main/res/drawable-xhdpi/conference_schedule_participants_default.png b/app/src/main/res/drawable-xhdpi/conference_schedule_participants_default.png deleted file mode 100644 index a58af55e1403888e283eb14c024bfa103f2508ce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 660 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbBSkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6Xz}5igjtE6@fg!ItFh?gFHN;HUHMdLYGF z;1O92q!YmSL$=d-1_s7yo-U3d5r^MiJD(-uD8Tk1KFNo#Nnq~}c1M&_xp`y8wIy5+R+jIfI%s+_TJTBy+dHNa5eMq zyFyL#QaPN95y%^lx}#vD$a z)3-PON5l3C`|OX)H?+C`Ncs{`&v$=o$6Tp%j~Ms$DJI>xb@=atRYzA$yL-s_W$}c^ z0aDMdSEz68y0U9^#8Kbl`MH5xt#9N7&6IwTnqS#+d`It7!$;>^-cRn3{qUt=Z|4GW z$&+GXvnH^FE!JY*_uSWmPwB_x``-6ixPwmrIvCseQ;=l|_xl2_rn(=d+1!WcylUGu zKlIgO5tf6hbJm=;?OPDJ(CGUT`wZQvEkE9W5MH1-`74L^ni61Csg}4#l%ynFVdQ&MBb@0JL8ZNdN!< diff --git a/app/src/main/res/drawable-xhdpi/conference_schedule_time_default.png b/app/src/main/res/drawable-xhdpi/conference_schedule_time_default.png deleted file mode 100644 index 7a953461708206779772642d22c110e5c325840d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 442 zcmV;r0Y(0aP)h#V$p#kW?YTM-a>41Go<$_Cc|5NkGLa z7HK5Eg_Fo$X5EFY^h|g5%r|qcv+xh4K#d+Ak9;uW0gnr+tYw1~IpYn*tQSsLPm4Lk zkA*DPGtD#(ov$|d3~5lt#idMx;p%_|-DD>VTpQCVEu6@AhZa*~ag~Hx-e!brwQ(64 ztFF!DfCWPIDg5Go-#FNe&y;U&6U6M&;V6O{nXnh5pi_jHPzvcg2rpt?VuBDS(d)h< zL_4x29t3SuSD`S}Ov0ViAjgEF8dUcXVi2>z27&=X^nywcA?lexb%eMLD#r*>$poq( z#5ky&Ar#42GN^?IYXy+y)K-kZZLEn;uluxunZt$mEdPo#t~lG|X*an$BG{BwJb ka(p0jeX?`>iTD%p1#$}@xR9YyEdT%j07*qoM6N<$f_fpoJ^%m! diff --git a/app/src/main/res/drawable-xhdpi/contact_add_default.png b/app/src/main/res/drawable-xhdpi/contact_add_default.png deleted file mode 100644 index 83c8f26df926d62ce00a6d72c0318ca5a41db1d5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2332 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 V=EYVIk@Wxo002ovPDHLkV1nYYKhgjI diff --git a/app/src/main/res/drawable-xhdpi/contacts_sip_default.png b/app/src/main/res/drawable-xhdpi/contacts_sip_default.png deleted file mode 100644 index 2dad4eb128c87261c8cd0f81628f78074ab89e4d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4261 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 TL$^r}00000NkvXXu0mjf^4mqB diff --git a/app/src/main/res/drawable-xhdpi/deselect_all_default.png b/app/src/main/res/drawable-xhdpi/deselect_all_default.png deleted file mode 100644 index 69ee8a272ed658bbcf05aaf2979a4b1be8fcabd5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 858 zcmV-g1Eu_lP)= zZBwL*AVL=_IyseEDBOD-0qGV57sW}WSa7K>iU^_|-TV)vQUnK;+%@1Jol?6wiEDIn z$hDB<-p|FwT4{6SynE*N9Ut7~$0zx{ckgi_0Ps9-f{3mGI1M}U!x;0b(P(_hvIz2h zztZdVnnZLKb|oUB%RvxqWmSL^$MIDQ;Q-($fW<5eZ~#og(lGO6)_9VHasM(N5>c3^ zLX9!!i0Fn5cprlxc$TL^Jkf+u(q!4o{e8?GHjU(s4G7-KFGQ8BGT ziD-(McMnf1rJnYApAIu`g<<#_0B4G)wSL0P_lYR&Lx8=8rvNM`xsTTRc@P9knYC@L zRy)khcWrDrX1+Z;J6p~Suib7R2T+8aam(fMge({~FBrQL5z*ruw&^l}@x0@0=j}KP z06fpTMMML|leQH+!4o{elL(&R37+gQQj72Vl{k*a()aA8gP57&FFv==VOxn66UlMHq&6GR1RU_Z|S>vTi*DU{Pxw zWIg9yuywv;=8Ku)5s|eE77-QP9M;tWu${A-#<9n7R_^eOF*h5H#=AV#N8Z+;UH_UL-wgbRx3qwLY$8pvGL_(2Z z&a2n!UzJjGM6|@rr9Q7O0$3R81tSc@Pe}0ZnrgLrqTB6$mjy#2c!DQ*f+rC?!4tgy z?jd==DIVlF3jmn;l;HiIQD$D1ww0uZ7sv5703&z_^6OQrHCZqcCW@l#rBdlL5ltt! zd=9|Lp~c(S*w_K^Fv+!A>$l8Yl?6lQJc-~5p5O_dMDPSp@ML?I(zoe2t4gVE&dM6G z0uR73Y1;#YnPZE)wwE>Drmb?un9WS_3WdT7fH$z~Ky`b%707*qoM6N<$f;sk)n*aa+ diff --git a/app/src/main/res/drawable-xhdpi/dialer_background.png b/app/src/main/res/drawable-xhdpi/dialer_background.png deleted file mode 100644 index ccb0eedd9178b2036ed3dacffa828b5ef57d9365..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 67421 zcmaf5WmuHo)5dj?k`M$0MUa$MQhF5yX%u#s29Yim1f*RR{Q=U_ASjZ%bT=UGl_|-Y$%%-FC{?asy-7p_%?E$SNRNU4 z;VD$i0RKR(FKb*TA}R=_*ttbaL?W@DRxV^W%af%EM)- z4FPBDFShLtbQ~%j`7(>nt9@VXu@a&wt2|e5CVED4a+9)RBzDNvW1ew$cUOVs)Txc{ zg_f-nx^lkrJpc2WYz{UQJgL^MB27hUH8eO_j+-otacXbg;QU?VmJOAb4PU3a?!LbD zq%U8-Bv0*O1z*_e97x%vgYwR!bQ-PpQ1KmSYNM8Ty!plIZes|`(T4fS$w{-?#bfE| z>48IUy=N%rhAUk9c4Z~CWs$NSD^(`^EquKyuoY)N_H~}XAq;X@(`v+iVX;#1Y{tug z2?}`@cWvrWL9jAn&iqQj(bTPvjg9;4G7bCj zvv+y|xGj_`PerYc2#mGuQYa}Y)f&%X++Pb>(&Ckik|U0wF<9Cj+xPw6n`0O=U1i-D z0qqKJ*u|9L?$sW3A7Ka$;u@M;ztkz*a&xQhLy2hcaKtHzslD{!vEs(v_J*$$n-dii zz7bYc3yh&Noym2$is$L0)m4d@iO^qrr|o-$470p`qgAji6Rm8Ov3i@==2okJd-X&9 zqT4%*`nx;BFO#42HdL(I|7hFAr~t8)C?>t^_qJAXNbVoS(lqGh2Uzj-l?Qz;nV-;}w^x>LUG;HG-ZXF2d{SFV?a*=kn)Pm<|i+<&Ot)gvvUL;Y_0%_kJ6Z99>xb zER&BrpOBJ?<+Gpf1bjxae$&01JO&Q06ppG4A*-L%=+PhqsY@M$pjh zKHGCRB+~Su6U~#DYbye}$af zyn>8OZo}20gUwHAhitw)&#fj*3vZm-d)b_Qd-*|rJC--3-iSCbVA`*ICPIhK??w2K z;j}@@{NZ9yc4XJh;OIK5;X%&4kmm0!3Q0eXO_|)Yr`1nZrPI=4$Du0};0x1sEC8Ka zc)~1t9(hZiN%$mC*0in|o;etm-(o%c$)lauZwe(U$xm+v$c#_d%t74e<9R; z#40x@d3x`ISb|sFFDr=!R?;BtklxP1!s2tKclVomUtWvnz&XL`jtv_(J%vKg)!B&{ zy#rXx4~5afl_#?lTCJz%UA(GB)WS1o$E@MhWIZ>`l-C%2BkJ9_N&-J1rUxoHk=F$x zjDPt7Dv%s3toUoXz1wu1ry-TDiO7g2z29!t0Pn80ud7@)FO+^!rJV<6)&beXD9Dnb z6$B&To9fj*PIqF4Zo3Rg&RlW4p7IppVS3WV!8md1^>E$!3^o%nF|nUG=BIp|2Qa9u z1{~hN4R`Aq7s}O{%H$=ydufdl*CKnvs9~u`eP=IuyY|~fpt@Q1u+G(Mo8EOAwCmSi z+x10s)+S8VhQnRITim-3XIo4*ciu22%c;%_99rbcf^$~xoHz^#T)><8^q&=@rKIYm z1WClSY>^)Hd^3&L*zG^PzLqiNJlyak55Hz@6_oc=>S?}*W$lx*eb2M`G)V+^2wCVrJaqtGv00^a_fsPONXUeCrnCJMxdl~wC9Dm zS9XVoqIz1q4Hzw7ZyJa{E_D)R1)?WUzSHSmsLM4Us`@lNXZAArXro`pcmL$}-K3FB9Tu<*HM913 z@kXn?Ym*?-ZnRN{|xyM2LA5Zuq|F09z>zWK!b=rABV(uCF99W`ePx(>m$ z+xE4#_LoMKe2*EZ@E+UQT+uSF_1^p#QKI1j03{4Yi|A7^-`N;x_38c@MQ>M|PRrJH z88v5b6h0x`HuK*9+D(3i6yx5vvnMqUIE5WAi1DpXa&!5d`6gjqqx#)T(kBoXFXNP* zJ-;Ye_wT1AWy0d1q&;eEB(Eu)E8Q+W!|u`z?5=F|t_Et0K}qjxkM~_;N9!FUArWp1 zwTW@h7rI7w5K|2R5Mp1SUZ-X!forwB?P1!kn~3`kEfn31!mOh6k98H&22!;7 z8*H-fr862luoPy9VRsuv;o^^q=g0Srl%DQaGPn1t9x@nxVDTMWYnY$GrzOvp05WCz z6LUm7`;tspoyMibW?B7=SRS+z3;#(73C;D0Y+)otwIa9an5%&qqC)2-PN6>gB|GdP z3<(Wn?OB%kS;77)iWOU%G?bOAi=jPqDiQFC2-oXX@sTV^9!&lvTvIHgxgrTBnr^53 zlBX2o&=bbf**05#k2i3fVV$iFU%!66_HOn>#eUduZl;d6`8ik$0)i_PUT$E2RdV;s zJ7FQiQU}GH*L24ZxZvKRxGwfQD}8ieO;r%zS4t0t$4%UVDHh<(Sn?aVB#7X3t1$b??B`%L!b z#ZK)#4Y*B_1Z`#{EJAl>dBA<@3m2Ti+01s|Mux)dlUe@3EURzts9_Q+N z_H>-M;Z+>o=3%@*Y}HScNwy!N1r3#QtCw4TbiByzQ6#gNxvLYsE@V*rZA2|vK;I?P zwFcOxJw)mPPV4jRDDTt_{j)C{`)qTr8iEI9(x;`Z<5BjhOSUM!F;#*K^cn|-|1GE*82s} z_AHoj5Cgh=QS5Q6S)2v(;TFBb5&H8a2zI=s6eEEz)x1pO7bL?_&(tqurP=GPFc)(K zU_v?P%B*;P%(PROet-^gnYpF1$vuPw4q0gvl zCBC-6P9 z-yH;1lXGBh$0OfYZdI$V?R^B=sFJSR}f&pqu!(b|TOS=Te1{5zn@yf-qVnNLpU) z-E`^i<%ZgEgmq>SH(+(>wC{P2KX$3gmv~F|1UwhZSbm-?r+d0HdH=^$Ys?*t?9eshq~?uFaA!E|u06dKunjalLjcCbdF(%o!K#z!qk;Te?}NFyxnI8QOv$jU zFcE#tzy&OWng1?)4JjY>O{=xGb+cme634*Cs(F<1H9g-6a*dIOeK)-Mi`v@KwI`-S(ve|AlCc%_6l2N*BNfK>+slWcXBZu#(%#5B66-%nf99#{5y-n1NK zMJ@B%7H)lIEn!CWQK!bRR-1KeVS0nG3I3rrDrSbX`mDlAmX^Ak>jr$yAMseHZ4qBXtN)N~+1*^zjuZgUpx(O^YN;8{h8ATCX3|q828ablzr8`=$(JKNdcr1HkHB z8OqOZ3oW+3Sxj!4Uy~-`lfb_uwhDx%;d#C?OC>cU;Z!bP<1-Htxj&d?Por2ZXBI!G zM;jO&X<|i{l1`p{%W<uP5Y7T!(F zDZ;$QdO=!xE9r^%v#0(eO?M+dY~=~$wdBQ_+HgKh8Db9$Pmt8S+}XO^r((o15PgOI z$^j9gIv2JZlU40)5^b*Ai(Ki)#!!Em;sVl&T?6pQD_0}a0T@zv)g1@D{$MIe8K1GX zEdRWg9a=^S<||7@v`w~;)?2X9eNzcf8(O4K(O`cIUURS%4ah*~rfu!BQ3b!foo)n1 zuOLgKf$u4P>-o+DjC`wUpVnN#`~@GxVv27*b>!#2d&>=C8l^)~r$0a{R-Fowg6YM6 zfL}c4b+HbcBW)1(?vQcLL37)D+ms8NWeSB|2IeG6B)~ICdoBZcscJ5@o)RI)DGy9C z6b-k{uAbQN1~ET?DSXv*l}v>*hxFviSIc6oOQEdj)bX;L(T)d?gz*qZe28xe6|lB8 zopv0E+4SYX-8^{e3pMamquozYy%w4Y$eEyhSK};ot~B~4s&T2uk00kX-}UBLRvl7H za@3 z^F*aBm4V;qFLozQkD%r@=G8?NUJmtHMf4=YQ<~%ZkD)jVLbcvy$5Jnzbv&}cp5vFiU50a-x5J0pBM{>g8+AtI(@9b#OFeT#4)dzPe0bYK z$Z5tlck2+0U)z6};Q8~~Oe6HyC+u#}BDJ(8X1TI9wJHs(2JyCrCm^}1>^M`A zcRMVt(SqBSyasO$6mRTzAYKM*e*o=|OHXwW6Io@%Tpv+U^H&$XaoKFA@SshEjx zB(srWtfyLcCXdmP`aFdU{5aj7y&slTOAWI7g115|S9Z zSL=cT(*zwc!feja{MZLKkVRv>Om}pTau_ zzFP7zNhY!jI0uH4Q!}=?5|HFznA?_6$MX8->M}*2>`ZADByMarT}oqVQ3pr2a`x=p zAG27cY1(UJ+}_?cm64V8z9Gj>OANF#3sPA4SZ&e`i`3Lq>ej%S4jW7U6aA*+IFH#W zG%{~*ZBN)EoepwZ!lC@j+We4dB+Ejb>O^)Oo$L9?1NDk&kGrT?)Nrr(`qrTeDDc&g zD3JGMUzK#t4R2@)e*SLD*zVMVk)UdB6)b```Ru7v52m`hy3$-jOOQV-KVV%&CT62N zI`=dNFVrFDwS^CSF0%->X4}!eJosE>KUunw7mvQWH-HKPYkH?8cu=byXHM3ov^VhO z-hvDa@aHbM433YF+ebESX}o{8s8^mKdFO1$Ei^mp(|e@`P^>>&VXF57O~#$7;abktWg*=x@&j>U z<1_#!2I68AMnPoXY71YDRK(2U!JXz?3aB@c+(YZ$r`2nymd?=4Y9BOULYZ1WTl_G$ zlvL|Qk}pEJXr&m5F$nASNKTSA4Y#zE#32|D^viak2DWxPy|A~@d3$RIw!ZScEFYgW zkrwL!iId{#h4?w-qt!JujKgWD^6s9BH!(-e&Fx-jRV#YG-aso^gws%)uru@a-pSMX z5m4c`n=1+P|D^{Ev`|4bZmL)yuY>x2!(NfXfw+%=xQt=wo7a?XCB>IAkbK@Fy?=B3 zkx}|qnnsX?I$qfx~CPl%cumv^dt4ufszwUY5#s+UM~?8Ud?y z74(;}@5`-u59;#n*J-{L!`zkw(c9kMZyyoo83CUamN7ORXaPWTM8n&_Yv}YrfAtSF_=0`NZu8+oy-c#-o5ZvcPhj!{Z*$Y;QvLiuf8+=A&r)!&z$9 z1&zv=D3#B8Om3-`cvA(pM-ktNM@x|+n?XEV;(CW!5<8spelt7u(Hv^LcUM@v;3MI}{y5;mjCn2Qi$l79|K-QfX z?ED4wQ?^6=BGCP|kqoBj@~m!2!NpYn3fQ|9mu>RJtnW3L%Lf<(DtEiFNdZ$FaqBB^ zko4B&s2NS*fmj&CHP0;5&y_1dpS)c1cZVY+w2GM703t40lkNqHe%kKKKIJs3u(B-g zX*9SuEg*?8KR&2_@KDm?Yvb8--PIi15_ejC+oMZgWYT~|i0J~Gdd1?GnJ*f*-46cJ zo*6XSLn}F#22dze%ydla{`^fJL1g3ytZ_vBR>sNu8POCv2j~I{fD|as{ExIv_M!^C zBraoD7fZguF9Txd2;zcM5b{m!r%#_&x_i8kZz|&TZ=>cWHMpy(g-oh9NKhhTOUZaIw#>u3bH;M|`f?1*#5fpCW(2Qp4#8lf!3C71D$?zt5-1^k&9{e^_#7+EW;sK4_#kd2K9rp$RvVwMssm0 z)TKm+p5_Ro1kjFbXSmxwoNL!^fun+J2W%Miia+sqQ6M5WDU*L{J#D8>V|kV*0+~dE;64;K%;$O4wJ&ud zpSO)0&LmvLlLfOoM>z7=)|PBoQQoMtNy6O|DaM5^tf#5fc=Lra=xc@OYLZ_}+TUA! z7roz$(RbO-%F2qo*)?w{q<-Yk{fSUaq!tQRV(1#q)Pj!Kx3dU*J%9r!llfD74sl#K zk2MG}nZ2q7;;02Oi;P7c5aG(B)N~nBD@yN-?R_lX(x)vS!O4gpB(X;zekYsc+v}*Q zsN^Lht4)_1tw#VCgNXuHiXT`k3Y6y1UWhtzp0Ul2gP-m^3dHQ%_@r9KHn$964(j9x zmBAp2Qy78Er{WI)1UQTy+qP&!=JNVvV@2O|V}&4PIt}2Wlz?a1Z79^Mzw7)K=^>S} zZ|fBLvU#iboHjRzC>m*UtVY!jexPFn^b4o89S!SzcLjO4xkr=tSF2d<U`M#$euuF2Xs!M z^?SZ>$L{8KZY*^Egvb26ipj3KNlf)1W7F+FJ~g0R;l%u-05~OuNb|O!=P28OXT=ad zub&lp07nmjrmi+EQJu;*BSQSxJhwI}=eg~bIU4u%SUj~9VzWd5- zDj2j*H}BD-M}=9KnFoiH46q>QLM>1LCOuaDS={PmFSUsI6e5c1%GZN5kuvRvcas0mpN8e-4}*8D|W`cJumt> z{z^P5h0UOd^>%*a+qn-4#gKN}#*Z#le@5BJ9y$hoSzF}Kt(`D4mSVm7tCTk^fZ=G= zlVZ|W9|Zkin88}xEPoedxb1SdHwUkj=E)aH1!c5PdNY%Ot!No!a0HOMOAcV^f_`6m z`cC}H+d(J?9<+b?v^af}t$%2nvu%W)R*vm|@7Q4}mm!+x9JH;=M0>#H?cPkIX-SrR zLBHS{FIETXq(GoBCGZB9hCuqdS#?TPu7i~V8^GL2Qd6ktcpDJ>=LT33bouMbu4v4B z=(6HZP(pz{m3Gb5BNe+^Pl8mGt{k~Ft#;?eQ}%t(BOEj?UH&Z^knvk0OzHNna&(4u z#Co!8E&~_723Z49GArpTx|lXav6v0r{_+tjpiQu#6HTFA?wy>Aj58$PXRw@{w}1kO z0HQE=H7AkZn;$HZOioO!F4mdi|JDCJu%Hg|$!Fh%tvn2rZU?)Sa;M>6Bo6K}Sh4@v z3*x5&RXuUA5)HxTkW*NQ^&S;r#y!JRYdnY2dqK-(SFd2RP(VjTjCaYu%(XidJcHGaPtOD`^m*7#oz<#3Ocf4v0V5`M zmj2SYwY{DEqDZh-=k2t;$PtJ*!v%cCFcjRiOkBp1nlIlUT-kFN{15K}mUndZqK=Nv zew+@uY1H;^QjW%Vpw!rkhbQzz%L=D2LnS~Hy9dNhzn8+7$UZ$jCA42#*^>W}_6Wq0;W|5RLcp)Wd8#G3q{5~F0NMCMV3RCi z4UsMJ&D+?Qlygbm`QvG66MXn3bvmI~A z1G{6*-?SDizO>*gw7=ex(gRs;(zLny0-Ev3JeAxq$^y3OQL@ z19dFM-qWHDO&$pnjs`b65O(xOAaI&6;=C)u@Zw+`7{8Iec=1P-*#1)B)0AHc%z>cA zTH{C%LqeI(o~_OFxyBbjgi0qpg~Gmgxo|mAQBg5LlpOc`tZvR>2k+Mld6szat{IL# zxo5l?pMDKW`ob8{=s7vvA8S)B92vT@P|)W8s|gKY>31)znAgZ;ob=^fXDrRqYdAo* z3>t})NG}$1ZpR(B!MhnIWs19hb_hIFb^P_--kx&E^XEo4vJtkR1($XbsQbmp%{(Q` zlf5ddf!9H^^j9SHjNCNQZx76-kxt{#kM5Do|=_HB>O8|W+VZ}UI`m;vhe|sB|I*c zJG*{sioHpgXLoaIa^Kl&w?*9K@n0mN`IZH{SmDLIG@PipfqE%%bqX*o{Kn=wqcJr7 zKH?`#CH7c;Zz$N=*b>-}#}A?-S5h}lTh|m&f*ryD(}|j|w3C#5@0*wKr7mVB$U0>( z+=dY4YG|86zh>kaRV;t6Ncsjp-J|vp<_>UwHl)a44UfW`#xJGalQZN96!5VAIq~x2 zjYv^i+{%GJe+|Z5e+c6OI^Y{{EU_(a9tHzhki$F=Z**O!L2Y+IMoMaPu|fZAjV;Q6 zjON?ZhdQKHuP`A;$tQ!XOne15^_}iG*VD^_xwt>3ii)Gy@w|I?2?g9=A%2s)mlKx4 zhsWTw47>i0#0+28chXr;Uyi-|BrzcN&JiFEBv=ml(y=G}R|y2H6bNH&4=R#hD6_+67YHG9q#@8jHo&e=)6jpatOv!& zY42Cw#j6o7{yI9KvAe_~v^a#vn!@teP0BSttmq#I?nzl&U0qH3oO0J+_3x&_Vn7c< zn%mmfnpIFf?CVy9FQ4U6Nf}I&Th+RtNK0aU&2WTw`6deE$jVv#lw#G?NOa@up zd%Cg*0C>=-c;e3vfgkxXu<0*?(-S^REm8b+1$`7}fgwl(2vY6iYIoAEl`XZ}?6czk z?-b6jDB*Mpo2XU#G*E|NBUISA5SszxLx96bC{`Z#8nXJ(g>^fAM zFYI;%qm}u(q$|Fa9=2Mb+O=q!&FXD?bBfdahz(i{9B zjz_iLF|gHpI+0EB0*Z#k_@w0bPAF0AC{BOyv`r<3YNaRxQvmO2U{v$%wfQ+Y{@fiu6risg5SVbljeZ}yw1$XG`sA>V19Sow|B_c@pr(xOrjy z0D{%f*aX#2G&eVQas{{+mA*&^B?hX<`eiL`l_QZA$ig8pg3K}!`ZVIh*3AgLKKq+p z=3HI8AYN%U^OdC@Vkq(0ybTxmU$O&A3-%Ddo16t8TP?3HF?Y~-_z)Vsk7^;G6nhuR zKlFf-;Mx>kNTcIe?QT)rxX%q82m=pFle7(Y^;HXJ7bVL3h(HHc&NpU=qv` zqk3y3Ag`I~&qM~UFiCKA=>czI9$Ljx%d+kmMpke2%__4){+bq)nQf`7prAm9nI?mV z?05K_kFE#wl%x_K<4Fk4U9aoA!LZTTusM?iPuF#@TqQRzpGRkznJdLO3ifK`PXrKH zIw0KDvMQLFK3SzNCv3n(0%AyozTXj*sAMCu?v#2^&HJT!loZ9J@`gJeS)~}w6d{x+ zF9diUm8o$$$Ac1An2Ffuz8BmhDDmTWCk6!2>gnl; ziTF^d$?xAE4I2TpYJy_TT4}4= zY^-a$pl=>Cv*s!1@HG2>7NQ=s+#JP^l>hkiyvsz_%-u|Gg+ch?z9ah0R}>?xbgvPXgT)he^DwLY-s^cA3yny+1*J;v6W=ET77yC zKQ)VvU@RS5Vtx8--$X-@!kI@bl-4xz1ai?cUSFO)5=V_o<~#dz zn$AWaY_{bU;ukl}nj^UE0U}0PIwVLC0+U%ZWZyJG&rhb+5u*v_MQQa)W;Nf=@rFU8 zn}^q-_mclYKZ=i{nnwk*4VNNvht_;8MaW9HIO^isE5^uRa3l2DO&0 zU~r1a9c1KtW44G@5ip?$h-8%zeb-AoOe)=)Cz~LYKZ?G|Tdx(I{pmO;U|T-!x(jhR z3C12c65F%S0i3++X;4|hoP#CI;YrUJfW1gCa>c@DCf)X4_5I{Sd4g6D7OiltDD(g+ zr?N!XJq8sCe1lXM!Pbnf1Fu`mb0A{YG@}AbcYzhDlt7(B%DYL?SW5$fU{wDTqScvL z=O?;r*UP3&4dP^9D(kh2!7%y$x&*kBLc5&_!A?JfML|nvNVcm$y6;`58r+M3xSY5E zu@|x{%}N9@n7_{wRM!6J3Ot-bBx`$s+co)Y3o{mQlx$ztK(k|z&1(~FM$VThf9m>p00jFg)hp!- zxNII8uKnm-K44kB@@!{&4aU1mLNxCZ1Q0O<&?OQeS=dJiY4sZ39v01yFhDjhu(q`) z*bpEXEF}aYH62&xGSkuB`0=BYQRBu3kiN9;gAUK~xxF>0XQL97|2l!rnRx*o6+(SA zofySwVO=E*V4<$-lJkw8ha7jpl=GI&eoda zZ4N^K$punl+b@8g@q>wfZmS}WsIJtSKIVIr1_H<1f+w}zz6oUWu8HpP$oBaYUNz%{ zi-0oog|Tj!Mze0Q4t6d18`FUo#NU5&VUI1$lwmZ+t89{>7!BUzN7F>Tv$CwXUk64j zjk%%Oe4zY^ec8RR3l*{5)>)bCUh{fO_eUE1j|z<0sM@OuDCcp}l#g zey!f87*q3ppEfzXss0G4s8nhx-KzOXBpGhlekU4_uzZ(gASv^hs~nCY?k+p`PSzM*A4a{!>P%g-H1_ zw-G%Q4MGuZ*G^wViGY?-RE2x7qXEkMo4g=vy;iK-HGV>)eONDqI-|&vG*xqnR9B^* z_X&Jf&~Ze~?CZy1WjtY-@Lm!cz!CpBt#z;_GXp#`Z=BJ67phhDnx6Cp41{Xh`;WNW zOxRS=dz^V4Vb$?9NrHG^`|Z}+q?5YuA|)OA?{Y9hv)O>!{7pW8+=YJp_;C-Iio)0; zu@=GQ2bqksy6a>Ji;TR%ahljq#}MpY_=5e}3ACpZq+$*??=tngug%fkkPyEg){8?L z>L0cV&TY&x)0F+nYHj*a(B&_cm);u%>5!06{j8iJ6b)w93&{gn(L&K^%Q0vr*i!5@ z2Y8847&X?N)5FyP$HhyKeE`!WN1eI7YMoNA?D7gTb(Qo5{%{3GF4cIOY_jJq$;{)` zf$f@cvcDz_lx71TKUSgL%bUYs?gdGr1!>m{jPnp*KHhhB*zejIQov6U7(KpC7fs7A0|k{h-nCaO=MBOSZSt3Y<(Jh@fG)M4;B>>_wrwcsPe8%ZWqWqXN^Fs(tdZ0m z?wVj`myoK!QhXrpEd~7)L&(Gk(qbX-ipp2C(wt!*u(0VGX^sZg(N}!G=E;7GMnco{ zqaW7?V5R72^&~GS^MKWpRjFFDhdtj9&|eG@)$f!3N3Jde%3msK>Ai|Y^XcL!_$?Ru zrO-=AsUcTgHe_wN3*Q4vW&RtN{!!N%T@b!xtj*q1P*px+B)xyb%!CnllODxHHmT@* zncHbU8RJks`}_mu>TiR@Q=AYo2i)RfT|0h=TL$s?5m_7Fl9|}_)2bj97C5v{>LlEK z0INiJ&rvdgw&4$&T(V)$XMQJc5=pS98GE)$Wgbod6*`-{U z8bI%RK)tW*>_2P2uVHp0q;HQaNcKu!6TCPB6av?K?$%j@6l+8*#-`>HrCxuLQfBNw zLZ#C^@C5{N{XV}(60C~YLUTwkyIP-u^Ra?(B;;j0C0CwRj`dMO8@>-8*8scnEw?vQ z1OoPO`A(M$>UpiFW`v$Q&O=d*KuJKnbUMB2ZQU!@_ceH?R!ad-D~Zy%RYb1`#siWk z-!BRljdX1NyXmJA_1;AmX%y9!^+ay|>9np8 z3Y%Cf1b+jL0_n$5SNie6Q^g1xWMCmy9Ct$#RX2(U9L$U%E}%*lk4~I7pzQ}EdtN&A zfzjjz_#=tGjUANAb=fj#YhA&?yO4R$L5lWFBM6#l*C_kE%j`IZ$kwc|8uonf-&!u- z3@b>F-x(@x_pg{{w@%O}yM%N_7$KbCX78U;wz|#r-?OFF>$5Ep7&;h;{rxi7A~7fy zxVwTeWn)nJ5T5n1FHXtD7u!p$)tHeH$U70$e6}+-48L!{>u#DY>~^&W=&EO^v$a-gc61ajbL0oEefd?kP@!;Tcd~Nj)^urRM z$BrFah$I^kw$Zs4crIV9o>X9;l!dXs)@S?l&45XGV;cNVK*FzVl)~c5`nt7P@6Kr1 zRf<_o5S21U1#St%tijSG{ z(Kk{p`8vo)s6AK~)THFr`=_U)vad1g*aD+gbvLazog6Tf49-^@w}2_@!};l77KEZA z6==CHviKcV5$KQ!a*ES~Ka!|q;?^uT^ZB@)Ob5PKz95+60DGE7|KJ0@%Pi=K6T^t$ zi}~s3Y-|^2-rhOE=;~}lnJr-#5;`cjw2&BAR6gR*2S_BXUOR3jL^-i}%SrUkXm#EE zgKPhYPlPI@7{1W>A5U~m_#pDvf&4Us*f!652H62a{8)Qtf~)`lDtYwMwl@z1auH8UE3 zicyV5krsI;x|;8@vJHgLqf`Yhh>OF|$OxUkM<<^1256LPK9*11{{H=+9h*ebuT&jD zwnG39G~WX1gPo6cj;+42ib5Cw9eo}_hJ3VUuk{m9Rx5t=55n=WEw$iJ8X{AY;`I(0 zfD(Ozh=jyi)AQO&L;9iIcEU%qb@>1?ceVoX>%RMSR^kYKpj7xm&Dk}gn=*ig=3R+55psrj`gpQi2I z81b`N>~2Zfs--(=0m{qSnn`>Ngqpgq&z71$8va%7v3=#7NyF?;q;5f<=kPRGxs#IV zu0Xrq@YnQAg3bZh7Cxb{&HR_uAmBXgEs+;pyK-ClHah(b=^ytV@2Zj<&XjUT&x`?heVh8X1ge<0ye z*25Wo`|#nzPz2MupU#*N0~6u(l|o@CTH&!m%OH>FDk0M(axV7lZlv6u?}H`wi%FB% zqh6yllz;pI{{!^$CiPsJL0jJxflh&TBk*08sG?f`srMFmyIptQwq6as38t32SgD4*4W;FM|gR_9D_{B!vehzvLs z9SX{iTdrh7=_+!xU=7Mk@T}f%=EPrfzM!%54f%Jj!7J|=+-P*1TyNo5Ex1M zOJcyFkF$VuIcRm$+0%u=4Diz+mhU_n%znbnb6}C%p2B0nJ;qDZ*%n7jI} z5GKkndg8zvdio~14W;%jH~`r>rxCSJyouU|z+izIAM1Zagw6#^=>>VUPYXDrjOdlV z{+2}>G%6knw0nxA=ADiGFVr5<^P13bh*0F@0>gkFuJH9cX*l7Jk6f#lc5wO$XYsso zoKT52Pl4UYNw!W{UFC1F2j$h=aGP(AN~R-1uf~dAeW6JB#{;u@^4S{;wen1gO3LX0 zkwE;k%by3O=^%TWr{McZ1SkS#C8Z+25i|K?$5O&hvt~mPkN5G%`tPP1J^CqpTAl4a zp>xn=rq|y9P2Vfx3AtDoz@g)V3^YpPVq+iFK6llS);aZ$$g!{xYtzs9VW(LZs+E+- z#>c@IXUe^|*XHY5PVfD9OqBHB+=ov9U!_b2s6oMZ=23blp7$|b!8(?($hgTGE@wN{ z@DX&zK)XtfZAJRDKQ>HA5`sR*Qez#fV${yc%33E|hFGS*mfRsYzH*Wun#pyML%%nPJ{Zkam0*M z!OY6SKlB42p0Z>@ z%F9n`X4oRr^}y^?SK>H(UDrK7|C3h&_Z3B*+3sjH7g>*AwLcL8MNrDD5}jkZ;z4v$ z>)(~_Lcx&(yJ-K_Ak4rC6*Fx0K7hd3Cl8Y2pYi?!A>af{aJ_bjKdVdo(Xv(i$uMPw zvtohf-z=g%NQ~c7{9{J26kkZk)ok#Q9y2UPas7D4DUrCTEHC*P!dg7I1Jqzc{1+2% z==m9}UrIQc4EiTtUh84a^x{-EZ2o;B7`;?62TlcxvyrN@I0l}J6sOeEBfJt!bA~~rxaH05>}H#v88rt*0Z+|V zN48hKQZj9iZ#(wS#o9cmBpG-4D$O5aQVhrp^sU7_{UR%s(KmJch3(?UwXXkjHO%ZI zzWv}=03ytO?2lszfiwXBX>-xN%u$orNUl5s(c*tdHU1kFjg1}J*_V+(Whr#jG>^_j zkidSN{3q&)X@NtI%6A3|E#G*A_wkzpi&>qy6<58R>)|q*ZFP(6A4UQ`6#+NfgCmVs ze^xj-R5^p{b5ttp-~hQI$52)le)97##AQ~)(g!^d$GnrQehPr}ZBvT*by790@h&{St! zpv!(n_$IclbEq@VjHCJ2YpN6*)-nCZHy#*u@*(fFg0LY-7oG7Ce0XEks_ogdenrps z+sHf}@?#m?Vw#F&9<@g7*LyyGmdu0{M@|p^N#?OrW<8JlC#LmMd zqK3)wQy<`HMDV=+O`(%(@6Pzu-0qyo_EsRPB$?J*34({b%5nt2$}Lw14uC38DW(ZE zqqSy^PW|xW&I@#E`8w~hmlMP&EYH2J5N4UpJ;HOGQ13slvNgg@H5$q^CT8Qd!5zn@ zD%=@T3q3!VQ|ZQkoo;kX(Yxk`KTl23G?MvR>CU@OZk5%3@BcK zddI?!8S2uPGlV$>Y&-=L!+mje{EXp zg807T4!;AiDhn_yhn&>tH=Vj0QM`3U{uFE6(0ozAZIF7&4@|St?K@f*CAkv5q)QBS zM#eZi)->UEo{ozj!n$Pg{yXGMInEU>*@`7)jYgmFEuFn5ma_|zzLdtuYCcU?jOX$! z|0a=7k3%H#+PY=L`byFKeBpfL%F;6P{j7fiFQ{9d5#E(=wRfJoudZa26tP{TYC<2} z?3UM=nIlmaV?V^zHJH=l|Ep1xQ4BfpfPs$oW-G!bm z-z`bt3NOPSb@cFtI_VVU<|6m7IKlorop;1)925i?) zpP{74!IBVgQ|2I2A5}xX&lZW8&qg(4cdAF`HE413y!+SYh021ab={2-&BBY}tEcWv}dc-a;s3 zZz@uby*DMyQsrHWO55`}=FW=U|!EZ(0M|G6)#ibgUlcg~b{ zYG;0XXe_U~Z{P7t*Zrsk;ru>F>j@gj(3l>5r40exNX^`TW2r%RI1aF=)+Rz{)V|_1 zP;F855;jM~}Y>dxR-B{mnh9fX^lEtEmZE6wiKTm3;* zmmGBvEk6QDhqco`34zd6-=py^id3QyDd)1l?by`q-xjs^yW_AlNVPVETNwY(Od6imWR$VPeDYX`@be8f*8s-HJyiT6lTqC!F zs_0_xUOVeF*u^Az$sXT`sAuHSET#%RK3+3QV8l?kZRsi19(}bN0NU8m0?D3L3Ilv` zNRX#Vm&#wqnOdIEzFqbgDQDq0cJL$`$Cfi#px~nu{Xh53bbN;U<9hKF+gGB@t8Mc8 z!>2bk4rTHeHP$+S-ypFPScP)P?0EY;l2$~69&n|3w-*0i9y`=c-|+hpT7*SK5mYIidAgnPea&2D!@R&-`i#RwH!FrDdhrnynT zI$R74{WKY@C1Ua?$C*KNKD)I5iik%aUbbraSa;sEXs3C`Yhhp}I7uUOZ1_~h8Egf+ zpNVQKTpGWYtDL6xUr*s#N4dT<j^>i$QH5yofv>yzT;-yogvF$VeYtWVF?Rcj z*aeFDMnjv#y7wKO-M9^k-|9R~?#yW>jJOw+HC1wTg|{yOu%Dri{hNQveSo|jW-s83 zdQ*SH^w=qqt=`UQNRNfHS>>!?%KCJr_frj4>(`M}*9{zwB?%-3O9(&_)iRc2HKHY` zVS}Z3xfKDBvwrBS$?KDQtDF4b71=Qko~-z+%utl9t=X`9$~6hV51Kyw%hi6Xg)mCV z-AOG<14m&hUhaS{ZNy19pfArc%EZ&Hx=mSO9@sHL<|D0D`oVUPm zue?n`q{)309n1AfXZ{#Zi*r?d>{t0Jh78k>0lT@}iF`xtL>)70Q)!UUi$l~W9Bh2O zS-HvL**uM9ElG{~qVY1xZuA>Gg=_2*0v$EP)rwYZ3awXD_tf?DHsb)e#?EEPg`i=z z^4RpJzdHb=Fxul5yR~CC(3GuqI@SFpwxis}2)kWpIGV1*=4T%3Am<{r07py0gC>zK zB5RF=6;5;D@}Pj~tZZE?+pN-nRe$(4q)4=;rC4v%aE>9J)cegEMB$@2ZkLKGTvlj|Kmy0@xv6dTf zS`p1%*C%4L8Yx*K0?GyXf#Sn2RXO*s`ql!lGEWYJx6jXB&fS?Bq?SrnW^l2jS*QKT*l!bjKwK&68=fWWRQ|bn zIP=z`^Zn#|gG;pWrbnPutSRD)vK}!P9jvKfz;mu~VZQi6mJ7mMb2)9!e~lb(`D!SnxY(uQRm$C|B`!-b=F$!}crR}DlDNEVAF!lEe5Wn#M--+0EdWN)>1NrY15 zY6C7X%u_0>b9^F(zDdw0Tv>MB=t>I$|c6ReQMBlSr(|@P_u0A#Tl>;4zN>a&e zIF{t|!>@0)c0ds2Q8wNha*npW@7x-PdnvkJ!6?$bD3vzt?Ubsngh}tQmHR2L3%m-? zWWtxHy*aX?$|mv}aGpWm)rW_Px;E>n2?a;rI0tlE7kRE*Ni*SinO#929=6#Z$((!r^%T>E0+;a<=6gzc zW;=>(>N&f8cQv*?m16xW?0F=&?2~1cw!D&3DlL>JBfh$9!~5Nb9+>}UKSZxrGKf;l zh)gpkIrwY^zfVEPK*84!FkX$lH=|C^uyO zcHV_n1ZvN|E_|kM^jPh&z^iA+KecS54v1Qmy_614^9&Cx@ivQpyIY!aY@=s8BQ>0s z8eXW6M6(S~h&M~3`N~O#tNQ?ug7=8dn{UI6KzebuUW|+C?OyGjWPQgWM#p@WC#&}; z(rfQy37d|QHvEncOZ&!N=3H#yks*JD*^^>Z>dQM z4M5%%<&4{cdJ4jHII`R<{bSMA&gT*CC>Y z7QB%k*6rRvS*jkH3*zF(v~s>_5zc98vthhLUrq{$KQlSAoGMF#u}fo}ZKIw=pyJd@Tqr+Z&5I*3s= zHYeEI;mQQsCBMJcs*OzS9Lci0atLtfW-bKY7r1VhIzs31GOSknLX@|XSV4VX;T1Bk zhakDLjs|^`)@OHepxb^{!(_Ids1if`xg96zd-gzO}P`b+qnjE`GQyBEXmR z$@+UFY`NKyqNjEm-5ZOaIQ>PC?VU#R4<~B}UVbNhBb((eJr?4Q@(Z zYsVgOOKM{vtKGLJj!jwYj&$d|N$J6S;m-QCnwmdu6+`(Nn(!5AWiOtu_2R)HcKqtE zvEcbS<*VlalvPth$lI#E(L*~qrq`7WQ$8) z6>9L05?(IfKINPRCk)%KSUOKL!wZ08HSt)OG@m}H%KDZF!Q7O~4v(oQgE*xnjb{Cl) zS3Bl0MkTltx;2R?8s$FPi}2F&6>2wGVr2W1g##@60zSJc}au;6gl>-Ag=s*6@1$!$Z?Rsnh`wOr4;C zZ&^5$Mt76TN}A@jO=o3|ziTywB*z5XBnVr=bvM_ag?nu1;Y4q-68ctv4+sH_!S1NLJ{3 zJ&HU((|BCA=j)-Yy|p{4pC!W4dxCL_;bu-06+F#+X>I-HGjh-J=X^Gd^Xjh<%q%`@ zp0m!p9$Z@GZlUX-$Zk$&AeG`1lzS2J#)oBX`*E7tJE2c=E8p0PJ)?jMt;FdHPy zlDFV``$lT*NE3vb^xxRreU2`AZmPYl7=rH7p#WZWI)&@#BQ?~Ts*}X!L z>c`*FaAEC#F;jiKzHVQxptY=exs*i%3-MVmAqN?05+lG^*|Y7#Dz>h&8Wr_OtoSo_ zVm}W2kdD3UVZGFh@0*8aV_e}W+M_tGYQJrn^B_a)AZMccc#*17l-K;#i_SW1o$u0y zAm&>>T4*fw>hqP=2f7LOSMEwj_`D`HWpmobx~OVy*I&v3lITpul7(N@3@6a1vby(` z=CM0V>>(!-&-vtN^4pDzM6-E{Tt~a!oZ0Yp6xLk)VnzL1^@5RN*Tp^fbhFrQzxDCO z3?^*$F9~=6SZ*rv@Uj7Qxo!lG**i9|hr(OWrGmNym_TL(7KO>iAKNzlEmqSlS=I{X z_L~4g1>Fik?~Q-OC)c-tGw}IF{CpI`E8W#yRMYvfcebPUPlK-h!T)+;em@w51~tz@2xoaN>LLUuhJ# z2Auk>oXN=J<2O!@=+R_G9-)CEYpL{05K1u(2mppKs8@$QDx15!|9q^uH^LLtm-kIy zyrYT&$IfI|vqWQ=l28}x^d&8h>)B4{Q|~3>_+Z$v`25WTwPYuK-PZNVp?Ag_|0GCz zLa5m%XR71@&y_ys#gZ#>GYdC0uJ^1!Y+ch8O%^z{)C40T=Q)3N3#rCUv_}*}q0iT_ zqUf)QAXIDRLo*_zp>l^uL*>ap(B38h@2VE=*Tf-x4y&WzkL;+@Sry~C?+Uy~SO5KE zpGIKA9CV%ZW1Wz1_v4Ti70)BYp^~MiBChSXS}9dNPUfnAo0vfNfIQ79^ngJ)C=c)$ zJ%fu7)0!4ny!S(#XnS8StvjwUG?K$-9?IPUbvz6SDDyo!pYH&8o?}=o&+)$t*~S$y z9WnK^!KoM|hY@U6+|lcy0}y&MZZovNpxgXl@4yx_XZ`eOPh z7CLS^-s#)t>lUcoQ7$I49jWx2Sc0^B9%1)sBN_VVzxhcx**pj{KD;chQNwR%4&rck zN7QsB4Qoa1qA5$2#u1LgKd$Nwj=#eex(=>F;8k0YGx})gwfj@n3fbfC10m&C_x=;| zLYxuaRcR)I)tWA7zo88wHJBkkmS9<(L71Rel+Bz6BWi7Ka6hd0LvK|IaBeTYuxif) z6@3(1Q@EX(>0a(NOj(3=J`jQdOaRBxc7M?;fo#*1yfw#jrtZ<}vE_f9fBUXLn$z^l z(Bh#XQ2%aDL`bM76m;GCUOED}VBKKv`1hW&>T!>RY@+KJn+Iggt5Z-TT!1Kjdds5Y zvDF=7{NI}CXewOwx*xcddS_%MM-c$KLe=-LUTG0^Q_8;TsIrUI@~zwwI`^MpvdA29 zcK5K$4dv?S=`tmJ6L&MN@0P-?J{|fjQn5Q(q!d{dU1>HMz&aIpShOtwWxxG`Ue%Fl zk86Bm^4vFVWGGb|6??(v@SyDWy=&MW%n@91jC+pm4_zW@Q+7U{?TwCQ(qA@W&iwX^`==-=1v#U!a@Bf936G+_KIj;2996pF(zoGV| zod378nU&dIEsU)$a+Wugf@8lUl2Nu_mHV7Lll?8Oyw~++O5W5uhTzfq_5=mA zSIfV(gAQ;iSIuUfH~(7UAm=LFCDU{d+^U4N(0cB?()#kavnUyS6Wqn@+ckF#j(YbZ zf3`2=h%C!Au?_;+qnLBT0R_)QV>TVT<%($q^m>sJPL4&*zQSW3`Zs&9;TLf9 zW4L02nED?-?e{M3K=D+{{~)<>vb-L8_!wSxsH3NC*Z3V*fEwU?m8?z)515C|mlQif z=@M3h)@xkzS_*fZiL@gqtF8lrC7&Boixl|kJ5RWk6*SChmrlfFb(k}%@BJQCBbgqI z<&I|WD)=$&q$BrDb5uV+_~PSVl#|F7;ho+YeEjcdYTRCqSkhk3mn6hBM=V^*7KcU^ z$C`C6t&8sbQh5*DOgPPyYVr?J4*}6gt>Ys2?GoJq-xkZKTg|fKJnha+!SLH4lzCFaS%DB`96kubb{)`$U zF(!9sZkle=_ypLv)JXB%UUgiY4TwO@f;F$jHCK~p?|sr1Eso6j?Os~Q12~6eCI+)s z3C)X%kJOt?mccxq_`@PqvKWHZW*&~arcWV{g%=9<;r%*%`I*7P%%OSTCEAs-L}G2JnIrcG zm3P~kYiRQ|v4-V}cFzr+YPQzM+$ssIv;2Kzz1H`b@n}fT<27B2CiC!PxMXYl(DoBy z1nj&o{Us8Qpq^XtB>pdN4c;EPeBrXOzXiWqzolAk(YMt_bjr&aHd!`{?o}2Qu>+CK zjv=RaHEZ*(T?q+0hh|?IFXAy9I-hsR3+?MV)v!u8p7(Ds!bVc1bcD@IA>Bn8UC(-0 zGF8uUM|u1%sW3JtN#apq^s-{umOabtlB>ZyUAi+1HklT0syh4?0~aJf6XNp zD0+I(OFR{Q#PmJ7U^i%EvHEeZ7gSZ^^PD7SqR1Y8%&z#*js~>Thw4yL2N!H1*AGkG z?kG3qAK*X8cJRWzP1-0Z=gL*2aK$l8W_wcR}t9SDxDANT6cFTCvFrWcukp0XPXN zEP(Au1B}3C!Pafs&KAws6D2hk>n+Gl$bu4bt7C_xp@L9G68l?|Bz%ENfTe~tKnG62 zsk{5*(DI;k&V=Knry8h{wQlrH5hffr*vJ;MVI>7<4IJsX>Xjo>01)K6E~YaMNP4MH zyuGHmshW9Wwt-FsQ+;I@c2sJz<)dVtfmE*Z3ve~KBnf~wL6K{gTK3H~g;I)N7yh`k zn3UOp5pFPV?4r)phy3B-CB>vR@RH?uW<#~NDG#LmU@yU5RPhgV?~Y|u)PAv$Il-U% zevcj}$9&$_f4=`Zl{BP^-^0S!B)4RLO+v3^00-Gp?U7?qi%GA4eF24|T;rkUpe4EG;?-AGrI$c(0L`vSN8zCU-5ZwkPzG8I zfPyocO@hGezDv^!w^Z|rTx&!*x5;vz98eq95U04xMs^zqvkO3Cqfhr!3sR3h3&1@| zT7Ke*%Ms<&=cy?}#|;J}Cx5uHSYKpw{6wOW5(1&Kya59G!?!aZ01}CU;cF5&vQ7^0 z)M=z3BMD9jClVqY;Jxxd0_RS@28%6J3Jr=aJq-H_)n*^~!Dexa8fyd^op|Kb#i)L9 z(nb73|0y-+tBNyV*0O*!^;cM~g0C?|^pe-5VD&FqMMtWh9A6AJXJ^?ihZ??S4p`9p zVnF;UIvTbBcuhaOz8)~2J^tcK+X?JHTOW0AZ!Ik_jB#GJo{aMTErPaP)X-H z<~(q8+x>!uDCGu5?F8U58Ms?^7l?>_-{KdBKv3uM#64#bEF2t)AipY>61`3g4RqMnHLeINu1YEIf>k*xfIv|2H@f!GgEiTtD$u zFe(KYwKbQ^6^wc#f>u+Qr>2I#`Dq@nxvYvlD6j zH7^^RT>(u0wls_=hpDto7Z7p{IB2^ZFOpxz28dEjE{=t0Fj`-}1`IpHG(W7^awzv6 z3~dl7o|LjAPmZtk{)7lgu!idD@=4(Mk>ERo;7lx^G|DD{s|cDLc_>~-tMa}5g=oW^ z1uM%JrviyUE`o1KF?qsd;PQ>*LZ=lEGccG)ASckOI2m>st1Q>l?%D=mpKfPxa=z5# z`~xl*AN=*}*PBS$jsEN=4uC!gT{v~9Y#=;_kKX|@LL1Ov!`yE|H~TRbi~h@-Gfr$2 zW%d~21nnk@^p2c;sP{cs{7Eq>Q={P0IG+u`Od+}fCniL_;N<%mSe1=y0gQz5m|QS> zKR>MgGnZ*0`e(%giZZ1tqq+lTyFjUD25u~tdcxI**5K8_o9TYDxzLH=;{&49PbiRL zyId7<^8F9kjz?@bIm)Ha4Z+O00m1ioaHc}k`4a~jx)CQsm+{ZZla)CA^ZObaF~SeN zLaTyZ1EYFFqlVyT_@O#O0y(WuR*w(2mgQoTp#K*<^$+|lKC>ZBOba%c2-t0xs(fa1 zlV7qrhPZ$uS55^l8i3IOcEoo?L+^{BYJy7N@}iFfCmKrh{f4p<>J@qjj+|Nzc4q1Q z119MEf;j4Vy6!d}MtTNazj52zxOxT#vT)V+lBA!355@z5hm?dKmHWJNI6tHQiAdN$!dJy*7pTW2epnLN?`7N0HC}b zh^VtU))AILTppOm01DUE<1)e(!dgtT83jN@PLFw^@JZXXh!i_f0PRlp2D2X_co{n} zSn5r~a)7LE>i}*vw8G^HmdzOv+C99N=XMnzgDc>{WO#L$*MkjMX|N?QTy9bVRPmNc z{E#T8GZRme9?TmfE1LKUSjqB~z#*L`OH=r=*IKsY;o}q0UP}_6m{>%(LSBagBmxTh z!Teyn5L0ct4tR8I>?K_A`@E`r1AmDn2NR&8~=75uk?*OIqAL z^`&2*Ot}Q6@+|wL@9D`@lw8i##93JH=~8$4lRc*3Kb-zLzrB9{y#nFa02*^ofYfzUQhVZ^qSEJX(5vYrSOiFo zU>FX@uiOis8bAMkcP10uG*;)$^Z$1;uruwRe?Ify6u*OkGHq!;{`S)SnI+(rM1NNK zds)%ifP%hv-hd)Z)LIQR(P7KeYNv={5wIh*aF=MPqr{)Fp!t`9FO}M82cP;dN^8u@dGVTR{{bXMJDcBw z)GGCaX<&yj8(#HzK+6Tm7+tW9vzA0(e~PxEgE#N$hMppr^kgjHXYs*iHSc{689Kax?{>CKNuQd}$sFKCfOp1ZGJgIW%mN|< z?ziBxFW6H-);<8GO+Hpict?WCCybT@jQ$q^Q%eJ(`cOz8aIn8l571X7#70kK#emegK&kq9oE6V8ODz_=#sY`a7)X?8NCa=mGr+)EQ0p54RCI|m?H6|^gKJKX zz>MrN=x09pg3Arknn4RfFoiZVVn`(gX$-yReYp=l$p4Bhnv4hv-np}DOakqc5)Igw z#gdqZCk$kbb`|IM)e6F29;@ELUA^b_KGYm~)C@ezQyw-5esc@MH_ZeRpt}m_4t_5k zpplpL>Of(Aav<@uHU04*lM0uB9ke{z+#`3N`Z0g>YwM}ie%#5g=a*YsH zL>#m$kI#q}$c)~(2ks9H0VR7KJe_klL2M?R)U$$CjN)5|*Rw_^ZVl8AtMBN}LR>@$ zA2=hL)nkB;`I5Ya1`jPFypVK-fZhSK4sL(gR~5k~ggKjjr{6Sm|5P7<^S@5W2q6!5 zNT%Q6xGwxNysMNE!|#gnr*ciu#2 z*KW^fUXbviS_t;}fll4=-&g>b-q-qtRt(eyGXl8VC>(&GgVq83FYWn9!zh{=jBxEF zc{pjwLnY27zED%BmCz1BcB3GZ+is){a?gSo$ikxsK=s z#rT>Kp^K@AL-5BQ20(`=hUSwJn1`__pG3}piRRFg^R19jfN|h#qpCdQ6{x%((XM5i zMn(et5ay3VR&Zr>R~a&RiVWFWE&HDB+xBMIn zpKs8c)6kn}ihsaQQk=Z%)heAVKNNE2I7q(3pbMI$(p9)D)E0{yw8b*A;fXvC5n~4s z34Av~Z-QZaF2JEY9Vbvqrp976mQO|1j=7|Z^bx6_#aq{jSaiJ^rfZ#Bq%UHK*aR%^ z#(^8u_vPiK%QQ4cwPZ#5uT0hkpFacM0Pt5e^1+A(gII!+dX@mhn40{(QRuy%DsVHF z%;rwE;2QygDQ7yIz=s5CmHiuA28kR zByHeUgSvy>>%ZanLy4?tRWB)n0mGZrE>$q$AkbX3x#KY#Zsb@Lm@wH>$jMk-7Yj%O zu6;fSq)vSl^xGX_*z6%IwidShnjHzPg9RrKASZdsb9NmY46VvV^W1}2uOCbioBG+- zvoRTH!t(IZ()Ej#g9n?+pZ9xB3Q7{So@3nBZ443j`c%u<%x}IM$knUfKICa)!jz>0 zC{`?W_u+dGiM@XT0d;DCCX?bfaH)M0((N84G~i%Bd4aJuX=xScYN~@wtNhXzE=w8@ z!_ed@DF8Yt3oa~lrx4IZZ8-6c=3jXfiaA>kmWVffM7%Y1%=)vcA8l%1G*w{;ObA@d zJ`1*2&=viT#bS~o0-SwY*n3$lX1OfR1^+@q7;a0jEcbD-Lw914hDBR%a3w|u1Cpj{ z0Jd!3h8Zfkir9v9VpZcZO7<1k+a|GBh~2rD}~(a|8-2lSei z9Zw_cS>9dV=pgBvpItdouNtg(89-9 zRK58dah>!Kism(MMMF!;poP)fzX!U-Yo=64L4$MRWw@0tR4wHlq)=}i-c{Gq6_CA! zklj`XnI&*Rd@>2dCa)L!KQE)gwOsDj->0``RMT0qTP3*3zv#w9ATa9j5-K;Sr7`Z&Oyt8?`(O)E-&kmvEsVJg^o(#JGU5 z2tBvLq!5EeZ*{PO{VV_|PC>Kui*S}FG!vD;G$8{37*m%hs^+IWNiuL{QpaV3lZzyF^_U1$< zM=fck)B_x24#2cdbXHT+f<}Wf3Sx{PEn$cl*RoP+5EA7TWuFc*6B+%6B?oYg06Hv0 zn4i2fQh9;Iv{fKH^l?&-V}Yiqss}b?k%QR>+SiJ^=?lRz<62p>!&*%r0wvaRJm)Ln zSDDr!X46T!t6xXdEj>IBVF>$PXQubrU+tUH|o5?W6rrJ?Oi3>zm zI(3OCeQ&ZdS*2NGzZ3Yx`{0Idq_?Y4AlF3kmo6%#GflilsnOdGf8*p*_(9daBs|449td`-5c-$AhDe8RpQAryZrDY7u zSARbp=fsCFOb^V0a}PqnY9I_wvMlO%@oHoxt7&IJC$4`szQ54BkD%>jBfyFwYuV}P z!#n)fi;^4%L6AbC*=k0HNtP(N3$~T;IR!>UqsV|X(2Mo|azKHTOZ`BMUMn>gP1(<@ zqeV=wL;^>IjH^*`J`|yx`?RzqLlVb*LS~A9%skrcW}$^CF#_%$1~qo_CfT$zuW${V z=x}P!j`r8Y0|3>q5LHhk4Ki2P`smFAR3AtX)~x^mcGPUJ(l2Q&0`}#X4i7aubNn$B z_isN7UNy6sJdyjV3%Trl;1+u-O^<_kBcS#ur;0h8>k2@~l>Aa%RpNm-F|1-BMdRrv z-ie0BQ~)4OK5lGmcxFX7uO)kEl9jF|OjZ;%vCv{+3$aCf`u?DQJ;jTX3mnNJ6uB#vg^_b4iUD^zy5-`*pa9i-1 z3-O*zunXUMxa4QXShXuv^aH(B(o7=Gj*gDLcbC2&`cCDT@q+*-F240c zqeUI}siLFlQSx;u_nMuCV3A+;Y}++pO@z5zI`PiL7dJro32UR3PF3kK0izs^G`*fP zGcV#g++Uk9D3QX+GD51)95Xb=P;pL76kCEO1@nB;kA8(1hyew?d}1aIEf3aH4bXkzhgvNr|6XEms$s1fa& z?KwC*#MAs>-VhOtl<}M}LtcEK;rrNJTv&@(vLtWHee>SAc?vDUsi3$e1q7~pzO91a zME3!zO{9*MQmvkKZ5AOZDVf)bR>{1m{|ha&AsGxgCuc4Ru`+rkE;wkEk;m*|ok`EF zH#n>@TBTORo>%CsTU!((--_dsby+QWYNz*t_@Kf2Hcw=>Zr0)eW4InJd#E^<;IW^$)m5QDnDz>z_Q@P&;8XXIKWm+p@riqJ^03{N@h{yZ zpS~agx03jBoOq)E!7Rx*_BftXVTDauFNxM?-~u7xkx+j1h=J2M5zh$`Z&GM-uQ6&u zGziW72Qwy7E&=Nc9+jkW$CcKZ#)B_S(^m;Y^rSv6kF^UIEuwHQvfO(~yM^8_naFn% z%|_v?)><)JoluQ4xDwgMh_;gk6snr^I1v&+!EeMRtG>u-PmU%s)l8kNAd?B}-reme z=Qj40B7Qz#!ZegRxqmjWP$ZR8VHS1ui@S&rH+O*#WbfY1j&J@A77KD4@ij$_%0ZJO zXs+PKNlda0EDHHJIuG(5dU+d4xMGdMN7FkUkVq|k8eKfUgPWa zb{^?r5O`M6S9o}u-PX=QH0uMqlZURzIC^w4Ros5~Y*zOs>0bQ;VQKw8&i1z~(VGM2 zG)>bh&9t7&sLHbMsAf9fLsWvVdFm!G=YDP3|NjG``2T-5{y)44(OQOUq-&nlxo;2Zq$tXUq z0c7X}Dc9`Z3u{b^$MLq&CjiL9;H_bW;c*buO+jVM%elPNMp%fB9jt)5*D8zNy#*+fV>`F6}u1^I8k9z<$Xgq=F+ox^zA?4E} z0g+IQ^G$2!gW$NBK=Ux$j_ei+rB{M3bZ`ffqOF%A=p_SP2G^SB#)nGMPn959xoXni zc`%{oe{WU?bHwyA&QAs;k}ThBk@<}u-&<|FG}VU1Ty#!<>f+ilmin!uvmh^qOXiwY zmDO|(h2cq1^v^(s+Ta+Bdp8#w_sqY@qGm3S;*84f>OX2=HF`@;{S7#_w(FlBh`2h0 zXzmQ5pZ$%F3G6WwIOx^>5~L0_QLBjCzGHDZvm+1kqQ&EX@^zz5vWSYc4?u<}`Q9ZO zuQQdCUO&3Jo90rv5draks!uUnd|MO3whbF8x8qv9?UvtoYo;_br1}!=s&n^_0+4mC z+r|1$Vj?PgmpZ=(Yz!vnAHb8#4?byebv*m)pkV1nSc3jPuR(QK4k&3U=`mD(7}j@0 z9C?8UmTlQXD*(aOZ?`=w9Z?bA#dF; zT)5Ej-F@asv3o565IlM?+h$heUE%e6t3m8M`Qn0fZQV8im8|a?m&f0zf1r&)`ohes z=@|jj!!H-kO38|wi_6ckXXVq`2<7icrZ3b^=VM{*6>xOn=2u+oFOb5~AqBgtRp3Tf=$oeHNV-OV~nBVc@e>Un3s1K-X2dK<-Uu8Bd4YCb#pR~O^uX6@*uQM5| zU&D-Qby3b$!Ip5pMoYnt=kNQVo-P01d0Lc39dj;}@!aOMf0Zq+3H4B{9?&N4f)Iu^AC%4Od ze-*Fdocd1AeC<16ThCUm%)j&E)?pQy^WW{ul#LT}0cpES^EA(J{+t`cdEq2@Gne-& zlHpsaFS_HcGzD#sZS#Dc<0`xC>beL@RL}qPy3=pZ00;08x$Z~8Qi*2o29Ve4)_~q$ z|B7g9%-mxPGB8gu@$?W#6%xADTxF1?xRKo=jFZEqdJE0el;fvX*xwc8*q=pA8$I0V zBr~aj{CM2xpSp9>g#N!V-Y3Jt?A8Rt#C$BmqUOh(Sex7PeASSyzz@}Wb>YW4K>VB2 zZeXP-y-n9fG=Km2n^!}MmmZ%_@TRGlW?5KONhb=5J;n#a$$vRNK|II7T z__zyLGS2Y)RMvjk|}VOiJC^EZehXq(=!u&^Ni zjd(g#F!gAYKfnE_zsxb9$m? zI9BEC8}q4;k~hc3dHk(>+VoPZ+-Bxm`FYehNKQf35zdi(oJU?85ssrWPrPY>b(ZCE zEoT;Kop^X$*X@^ogNXZqqLh&7Ug|6#`lZ92sftq4r7p+%TOuMgxuor79ot~5L3>y> zSttX_S?jCmioTXug`$jNPAKo_yRr7N|ADXK)`3q|+-^e1IwtRb9_r$4{yFR(*h_t% zd&C-We6zQzpDezeT}oDX7Q+=JOi8|=OS!ZoROr=y8mtSPn-ob4i+{ZVb(~qEO4w~Z zVSTGjMvr0QNK!cQ|V&1G+>n(}5%^nRyDk))g|%o~{5b^&M=a(P}rY=lw5i!j=7A4#}R& zh0S6XKoP$xTuIyh%Q8H^3;nm8 z%T*Aw`(y>Iqd84Kz4b1j2f-evR1$nnQpwc+-h|Tr8)FjmUv7?(bu8R2r)*A;UKym# zaP0)0$96mm^byHWOqP1Gje7(DEan4`vx0zeYg|V$_=N;`U!B#J`b&_%{J$*+jvs5# zOJ)0~yR&qWyGK4xTj7>qt#&!8OCIFE*xH&VA}HyqJXeQ`=aW9fidV>8di+-a8tzDt zasM-&qMV%9jg%X!qwNoieX@wQi_Xjy*h1+$F&43Pb#?mQ3xO(y$GUZ1`jJF+JC72^ z$^IG!q~|)0-tlfT{DO|@30rb-t9D{P3ZQK^L~-D=KXLGgij$+KI}Z5iQF(28R`kM3 z=-#@xg^7uY9+7bY@P3?Phs>{G4sC`WdfvBRrzi)mf1{c{gutvaE)+gD+Q#ze>KM76oJ{`Sz;qTD+kKKa?l zj~cRdqiC+!he(N{*BJx(f|p0V1dmi`cj&HULNUlcI}7?S339UcviJ);h7Nh}*49fb zWOoi|aCL|6oui`aC^0vs8d@v(O%0U#DP1W$l`l&c1@eqfk3oQCDr~x*9_?G^vNB9c zY#(%F@Hvc~C&a{b*Av@m)~9Uc3r^+Cuav+A=@2(`gUs_kjejb)q=> zc)@kChjqAga08ISYY^>Q8YzXf|4ao@%(3Ftg+xb!9xij75#g%HO0L7>0|gcl@6}N5 zoBz=$^dnlH-h50%;Fh=O{h?3|oH6$h4V?RYXpjjrWE=D#MdiHfd@C0<@}7GtGvF@P zb))*RoJnVA@nlb2gTE=hC%XYS(ImfZE-nL4HoB#zxlG8H>z(2nLUHSHZT}E06fL?F z`Xuk>!ROejy%Z)M-7(^7XcXUYYgKli(?0nNXnMar_(Oj`X@bFfThJYZ~U@YaQ&Q z6}a>6ISS(n8bsIpt;R$vVsuv)eQ#0oJ`wR|$C&DCx{t*WUfI_zp*R6~F1}n6ttMSA zEMDMQJlCt6tK+o?!eE9wfj31@kt5JuzMY?OTM_i!Ufz=on^;v&OxM0(vN6?^=iB#2 zYlumsfmJ88Lp(J+>p34Ey6LGi-c2(b$~oDMG-SZwL?55dUx>vZ1_?P=UsGp!^E6x7 zbzsYyCBMf~oV&cI1Dd*4y~BEqzi}R{R;uB{lq>*;t>iS|G|1ClmY8I14E@H4YEKfd zt26DJqWnvlS%jqZeZaom-NSl)>d(0gdKa8~`QA^>UsB{w!045&4*xVn8+y;NW@mTT z%>lk6o-NZcby}9e^Z;5<8FRC7zKmE}`*hOxYxw~Ll&k=z?HZfwWa*W7SCIgyux93y zM}142d?rEk&$+#0I2CC>~Ndy^q#>Qag-}q^Bhp))UxBHoMeJ|hmX2ct8Da; z7LK$roc6aXC&Qu-AZER}uMvJ*o?O2@ZjNsD9=r)>d`%_5t?6{WA3zG78AOQ3Iqiv4 z8E9us_0^TxZ)~&Hv6w&CI%9P%T1X^@)yY)xhDZxK;}D%mRv1E1!zHr7hsJnof8VDD z5p3m(DtQNvspO{3e65Z*?(Eq)#X{Z%IUK!@brr5z6X&o-*#V*O43wp%Wi6;A@hd!9 zElO72%>vEFJ=H95PcngjjR(@Qn+hM}Yjn3(RE+n18VnO9Uhn%jF^#cv?z zY%{;|;T>9jm3d=Ms4Ok{G)L~?ge*$JwZX#aPC+p*>*`o7#acfCPf=`tal!Fv=yqIv*Z|-sBMuF{E6{i19$T{-- zFzLy~`ym=Ak5D$yL`&4X?Oj+F@i=q07hanFQ@H%KrEt5ge|`U&ks;>Jw>W&OzP}!P z;fDO1hZ}7_{rRlct5UVqi_H=}g&^C&0igROR&u@8cAAr2%z>;EE1(g*?F-Q@IW0V& zq`~ktvgVtQ%guuY>?V}iiZj;oL1_A7Ot5--F-gx^D|NZPlqY@JY6DFB`5FTL{nxuG zw~Ryb3ipb98eein-DUc`_i7V>baR2ZaYg44k;@SK5PB&D&tdN4nZH2 zgOU4kM?u9&Z|hWMFRvF-qf^u;E+i!-&}tGsIRGv4j&IXV+40l&-B*@V%t=~adWm}F z^+90qO+b*L+bJ^|bXJ>r`tfF_o3k2;l@~Bnr@>8fSwo_wAD%(HBlNV_W|^u%|CmqQ z@h;rJUT%o;z@|ywrs0M2*>9 zTZ!pN@q3?c?KvvY)l3jGZ72ueY&B%tPnVMHgJKs+-lL)14wV6mO-A%Oh-t263iLZ6 zHHpjf2sya^NGCp0<~+OPslGkwrQ8q=UjDpZTyv8T%~AN8s3*-@rmwgvTstj4os%OA zHKiJ_0D!{;Jfmf2SY)Bnl3#QN68dKbGr0l~^=+M%c@Oc4vY%X`Ar!97-)7e;nt4m3 z7aXvo3R{axMY_7q?tQ!ZF#oNASuUrUkMVxWUXkYp@YY!Iq}FW69-l;1PKJf8LG7JZ z<9Q#Br%qRT?r?@6^97q7(^`J|hqL%*Tu(*-UBf8`@xyBmrf9O8j~_VxGa}B!c)g8m zhqKE*P9eVod0P9k2k@P5*;|LMqa&yK%E85a=iG7YmgASNUlUkG186m4Z0q{{Cji_G zOSS9TvF(x*zi}AZ?lwcOMutFtJJQXSwQGAD<&kg-@utS zoPFxDAxRYzatH3!|1mB3C;wES& z?{lr*tHvC_<1|VojJBV?_VLBAFmZ~R!Ra9?UvUEWPsxTFKCB@MSsld zVsX-JyfLR9xSFDOzZ-xeoc;2RYgE9ZcT`C|{CtheZOR@CvH5A_Tgpy*6W`~H0-A0|Ps&Z{?|hId*?E`i07{nlZ>_~7pt_VUVLF7(Nq6Jl3ulWHTOaV^Eo+xN`HE}q@zDuI z3b@J9@KbfFS=k~k(GZ{54ZPySj}LGObVmU41|>-JGp9TJ_b;~xcT__l;Z=o}QWt}` z=>lFe;GW?i@M6v)5(7S=4kc+I`dEjoH;IJC*t3wCd*gdt9|~pf+`5{I?#gGPW8=T~ zQZ3?ihbbxYmcjwpD(CJY^?hByRaEn|6n|`e)Kcktd{4B%^6LvH+3REKc5z%i07}r- z3BVG>&6Qa{(~l0VL7USmuKJGuRhJ;R>R}jGeauVZ`sS+`oZ9o~F_6ojUt|rJtIDz# zgIi&0n{u%^ws#`0E9eM8qc6WgnKso|#IQG<9O-W1VSHq^LK;hdY7eEzFJHY{tpzMB z<|YJ}$>S!(+mKTJyJ4miZ{7M5wfeFE@p%w*@g9>IjEiW(cjxe)icOQ{ZJTkdtsW-kyp{uiaO^5pTlBF{ z&Glm6v{&T}>!F4h(zw2J4B6K*WRcg2h2I>mcoKTbs7=qnV$l^9X+F~=F>I&v?UEI- zdRSpscVYj1%U3TL%s_1>D92e8G*Y-R8cT$3UQEziD{4DuCDDI8yafzq4-?S}*H@an zr`DW8(c**7Et)_Bo8dz@v%a&or^lY%Vw-kOhAPldGcCi$v3mg(=8fl65?j?87ae-> zKQXrjq4Cd>ChVt@stJi77FtSgqtJrWYmBoKRu6|o<#}niePTk(eT%a0YPu@|my5?I z{k)iq081o-3r-iumhB-CML0|aFsC(4r?=mTh?VINwf=z+1bbLy=PMbADDp*zw~mYX z?2KdCwQnHWOP+RD-?mb2V3k+d;*9Xx-At+8LDhz)-?GvtnfGPocch~2jn85HBZtx@ zu_`xrSBk}b^|x2L<+Qts79Nkx4LTPA1Jy=PH&ZRhZ!gV16+^gN3`Vf3Bv!+{hB#f}@> z&Hem$gJ~WCu5bf+d%g)YFSBcn`{%vDY^E#^6bp)9%`-+gl5}4>0X^b#s5H% z=3s@u3BHXkwHnByn=J)H)3ATRErev9l4J|VUdhbf zn@%NVWM<|`mF2oe!uJLQKPeZz>&{B z2v5BoU0CE1A0J-@=J%@WEaxSoxX;EhA*2n=;p}uBFuQ-&A&aanygOV4j#l`zD3V2x zk9<3GZ6Qd^=(^Z+{RJiC4uFm99GN_iOzv<;H2l|7`wgGChAra4RN{XQ?dLZ}Xf6$w zQ|g0eg}>$|2oI?0r6w9_Q=2g!@U)<0JiWKxCsCrkWsP5KEh0hB0*5li&a1=vQz9mrq=*qfVzt}PRI@B+8_ zgpym!R$`gL2(-Bpo2?{Ol|wgK$0~sXX0-4{4bEM4s|Dy_E}+6@`81#_kRU|kztb|M zAI~D`k9fMBJ`xHZj_~+f<7(ZD+$3o${^aAV^fShbr#W93%fEYpwm5}zCgdJhr;m(y zoVBhCdE19l>32dxnD9Qooj|WTGkZlr>Qmzv!Z%~|xj~?<+T-YUsgb3h!>AiNFg@># z*%sZt21W5Bf%##zdQuWtWZj?r5^V6sI|i*Uc`Kf_Zf;UsG>#{vZs5O9&1d^7>Bc_= zw@vappBDW&_3B05z1h@{sut&rxS!V9x3^fPQuwX}br~S9g}=T*-31CM4_q2p4pAuX z@{(b^Q3<*sjD?Jkb#ZY@_kX8RU6UWMoxL7iVbM~NHim||o^JP(1P|lx_aAZGH!xjn zz*Z?l(eG(Dv;1=aYe3aQ&*%Ew#xrScKPmx7z&o-a&QsFQStM-&J>4#$n^DU$(qe(A zLv<+YGiHL<1%tDm^Y24F7@cnNi`)d;{q|LtC3zwuB7c5kviVA>9I+bKh_Ka6Az%KX z*4^IyM`hXiOXZ-)=+A~nkbMDh+Tt>Qyu4z$o zwYt3TNzAPoKA@%fsQ%SA!de-CM0Ufg;0qZ8%=&pC&NyJ&;VDp5QSM+i-q*JL)rj@l zRCGwTSw8JQ^NAIb?}`j!8PR@i<)-ISQs|bQrVApJwZZE$?d3dR4|SS#C*GUwc$uXB z%hR~Dq)@eD_~8U!CGO!J|G=87bX!4#>_Q2D(l~(3n2QHBf&P*-AERxbkQ;yqz#(|( zGdpEMnIc$hclkk2F*;Fu33JTq*hGO-u(HAmVfDwo3BVZTS1&K$iEXAnUTQ$z-y)!} z%JnPCZ0id}A^p%`u2(;DYQtb}u~1k+p34p-VJPJYHzTFh2OhE;d!{KawrcjoiNLlzn35xI!swp1LwDbTdR^Qct**8m^ zpA_Um6B`JfLreHPG^GWm$Ap#d>TQzzc$p_O)>Xo+<6VZNyYvvPM@*vfJ&?t6m?{*7HELYf!O0tNy z@8ilHA-eEZ%XjM;?mV4>9qbVoWIr)49ndshZS>yH^(77ddvgm)u+?lKw`bMTwaOk?@`R6{!(V$|GXOcnUMUu24$hbgn`|KE3KCu+SXf*E8H@uw75ad zPfyr#K3e|l(!xUE)^-oNoz|N-;zHXkciKbl={R$ljcB9R`~S3swoalddy${b%K=in zx_o&K*@8*(>DRV(KyYdYROsARl+Zpe;GoxS*6(8!T!bUUnaJD8d$?Oa@zeX4lwY*l zyd6Fqy^25#TTuLoCjlS`RLeXl_G_V-uKz+5XHdEP6p#hN=-fB(E<2& zj9AK;r`BbtM%yExd+-Z4($BCm#JJf%LZ7O!zUtBqv}v)?-?IbQrRCQ30ToEFb_*+a zaQ_Di^x~&7Udzb)U2gz1UAs&olZKWZoT2GN-$VSI^kNpeq*pfcKlbA+@Db!~=Pgv% z3FXIe(QRm!!bn89~I#qNc zw!gxbMN?l+0U2cJ9l2m_gu|!pmbCkw0d^C8Ja2liEDzOg;_>?aV!Ekey}c#hbU(cv z&~bHx_Nd(3oZXq7u^GVEef@VAq)>rw*ZKhe{rw<%(d6+3H(mcNsPlhLDlaZyGO7n~ zWkPJxE5qoq8?f^B-ErMZHW9%TUzvZ@4s-52@JG~MdH3#X=^e$tw<)=K@KvM3AIsGK z<^YNTX+h;xn9fi42+TIR`tuH|QG>@V_C-my-3J&R8FBYzj>5q5GF((;2N|K6%+G@V z_!&VftLFy|RwBedi5NSwI~jTWl7*L&oIXu2uG;D4w|oXfU--r&yYM0dvl zCEfj_-jq>oJx`%vs8%P0vCgN`-KOvraG40(a5P{SPn|lW=mwCDF>fGc^Yw9KM zYn-Kytu&V@&-DHF{NGtcAb(cq#XcGk(kR+Rb@_y7&5QcE;5aKe>b|(3VpI6AYn}-- zo>B{Mj^2Samem1KFU#@o@<$}`x+XHI{$rrrK3-+o5*#L^I`tR+3aAQZo~np;hJWc* z3=N2#J|pVJ<#PY!)e4HDLqLlX{_?8Cy_cGx0{;4fMplt}Q;ply!3`0k14OWXz9i+B z$gsg$p?z$B2(b_C^Ot{PG0>*Eci_?eBp~|R`Y^`h`7_-&30CKkD1f*pCF>LQUbJLzXl5ur zJUdfXmxypD8|e+MBX!~h8QsX9m2Xh z=T!LfJp4JcIY7Nn^{ni*-fcBd8^Bq=!LG93R2hzF zn;@YUoDnw0lCZcl)sC^zNuf>mK+_wM;&k8+o-HUgdA7b_kZ%9b$DlNK&U!o+%aoCv zFrKiU)W^Lf$IIXJ-*1D8Bp`h4=;qAVaTqsD?d`nX5{I}(nK1;1t-agaJhv(H02ogN z09Zggx}9r-C)Rq%I&9TYRk+iR8ifq&P*u)L=3J|H1lJ3tI{|s4q#=K}0FcHX%;QL_1j5(!x_;g@0eY-ISc@cl0-z z3)%D0H*c2ilcx{v4q5mkSdl7>ZeYr9XB$K~4VKvcqql!T)D9lC(FGq6eyXa5s`g54D|gbPzTlY| z@0|l`-{E&ita3oJkGL?e&28jp{v3-1p$FxpR$^>>9rFAY>P{IW} zr@QT)(eD^g$w6P}E;}j{=&W@W_)jDdHXfCb(kTA+FMtc}(@0)b1!6lh#72L?4pBoz z_AwG#B7Knfvtf5aF7bm6bndq|DC3EM3}%+DOQL3Jy5Z@#km2tta|gS_`MY+*zfukN zCmvtP|NU622>7<-Hm1T`t4~VusI~&}_-iBI8;eFS4f|wQI#HE8#gJHTVw`;Ea=C)c zcAPPOl}C-bODwF~z|cGic=^o`G-Z0XcD&>c(5r>A{-$>SsR`h-1J2*RMe+_IJgbw5X!CH!6z5>d)4?&R4?`}FAE1%vE8NO)Q=)8tgoz&q| zS}5dFpT8ttuhRKDpIYQB0=G@yRz@n1?r!s^MjeE_dN>;nXMh$euP@Hjp<9_=@D z;7qmoZ0g91rzan(z#ic@B@Logzbrx}csYQw=P7GwF-QJvzAo&+vVU9T%ec>gmEhe= zvM{>EDbuwz(9__#HaR(oc_COGcVf)|g&W0QqZnZZhn^(oJGFOxarQFM5>>#RUgV?q z!G|uYpv8=sI zCd%2|0Gu|n@8~l>+;yK8f}jE;km=uB+(h);Se^x-J+1({0lsF}lmS?sIJ|+I~L5gz|ew!NZ^>Hzs0SrINqZ4;LA0W`2Hs z0&%D1SEG2dF}?iw-1__f!rPBJ>B1rDcR`%QTZrR>S)Ml<&wbB2LJk~6YLWf|=*7We z$55q_{59Hp%pWO`X>6Z}=T)9Es`TbNh_YIH9j9B!>-c6zZa2L3#zd#tEj$gJ=?XRc zoiBe32K;4ffOPFmLDzjfpcN1Bv8d#oIo=3kRB9C8;94T)5zj+74o*q&(T(OqHGoVB z0$|6&JQISOC#_LoWNi&gV;IVU)QI;EB0oNK$4ys;={XtUPg-I6J$1xhD zsZkV?9j&0^AIIanf{3XwAkJ?{+x@{#tm32zn1#o(`xA&`GHX)|1#4~X7U6is4 zsS{CM$|l>8;Pa)Gg62U`(2tGj;n{t|pjX#Imirp+etw(n2b!uSe_0~nYNNfOcKfXp zcrla)hbX)pPRpUzQ0KwHn5+AOJP7?{i9HP{*epr6p31i5{25WLZUI<|| zHMOh9Q0#_q_%j7q1k<3I6eG2TuSKEIkM?Dr{XFPB{FA0eO%R2pSE@k5yG6D`ZSSwW zu|h1&QTS5hYiDm71{hqtCu#5(LzjKV%QfHPKZWB!OOC49rE^oG7&Xy_ z@T!~nSf-+G+OoU_GhEYq!8&p5{?x7c514~%wISLRf%$$d`3_rLcuObM0&~i?z%Q!Z z4HkEcxSmFL(&kj*Y2JYCC8I&EK&^RMOJES&KI?C%Hyo-Cz?b5b^I9Tjk>ua&o62`& zm-C-EY57y`DY#$B_GXMaDb&I}a7v`Bv7Gyu9=(u91NVib-qe!n&>_7E%qBK!My%lbfCD&t_Cw6+%?GHGKgT=89Rnp`D3&?L}fR25j-Uv>=jFlCKB z;fd&gMI5xYaOz3Iiue7WPMI!?a>^K(!hjy-vbL0P9NN<(#W_l`9K?8e14e4Y5hQ=O z0LsHfaPsxdgQh!JYO1!|6(ZnqP!N^G_6}H<)Tr_r*8Cg={Q2Q-)&p3qE^u_$*&+@; z2oHZau&;0m?^3vwxW3MypMXOS`>~aL-4pFQ7ra!w%lz@r(T;>}EDd*NXJ;FEzP7Hr z{jg~iTGD?vSwK^?Zrke5e7{v_%;CI^Z9M#L z+2Yz-BL+*3%6vHh>diJu*l77v%l8^``58}lPdl+-!|=E4bs*5SLfS^u&R_bvcV^Sw zy(Ta%&Hmd^rDc2UiCLQCk67uC0sw!BYtr^q_4&cg1NfW9d8`1)91P#4o``0YOzZ2A z$Y%YB4-TbDuHIZ7j&o%^yArrCbQ~uvIRnsxq+e1PVhAymr9CPFOL7HWwkK^GABn)t zoJyPjq$@v)XYDObY}x(l*c(_vv0>5C7)7;Fe{2MzpEg1d{R$$M0V7pKn@o72jT-%( z33OOU8OJefk*ITkCfmm{D}F0WVtlN5`8Q?}T-6MmsslEe2Uu?d4ls1;up+ z>bAp)Qc&(Kz}7EF;sImYAWwu+A03jSXf;tdKuBERp#FK29zMwz{)m*gsn5;)X1+!$ z!u!63$R7s>Xe^KyG%{)7^o<dlx<}7Q~)GzQZ$oH5_*$! z>5X!!OTTS=P7B1l&n^~X762d{!>XP0}nY#i4v zmzND*^7jf;5*k)PL1)OCcD3pJ+@_cBI8eoKs`>Wf!gU;WKVZ?L6$_|n&(%A>s$BVx?zk~gaRRL!ilGjW z-}Dmy_g}IoU|Q*3hAN8~;b6D=1HOny11svUy{(@1M=zceGSb?N$&yPigbcm*bYqvk zGg$70N{WMMxs4RwjhxqPR<&UNj-h*5W`+;)Kym4$=niVZOkly0mAX z<7@678apI`SRl1})by01s3Ey`GtT+OFT&EtKU4Ol2cEp21+7tlYLtT7NEQ8S!4$E< zL7*l2##sum(oL!%m!ti7>d}XjAGIZ9(lNl1)jOOc?NW!;7p1;r$P(>!c8&(HWK!fZ zhh4Yj9yh0jA1Cf@YKxpV*Jd46Wt*wlu+st90!SRB#l&bKF^P={1X-$x}*1Vyp06;V@~o!vRok2yiv|L?xfji}k+u$|CIX zDX4L~UkrGZG~BN(S6KoSw)2o$jiz1ir~Ir@Xg&~aPhl^Bmc29~U~|I*aW)=L_EF^f zGf(fm#@IMjHkn?`b5yv=4mY_38SdS+2-e1QeKBX4`(`zG(w9HfCk~4+(g{doD2VgG zz8i2FLQ7oS+-}xaUEKIJWgxdyOQWjE;(HH7CFx7Ya06>!-#QNUG4_eLmS2V7QqNVA zgd2?46|$oeue64m=sNr?sz{yu5RV>YZEk0t1`UvKxK{v<+N;yG2xil}fb$3kIaT(~ z!Aw&2?$RrXhHH&Xg-gxe1=!x2Eb=y~zNuyIl-Q1p3P5l$UH+e3-*QcBlt#(g8Asv(PF!*1GM!-xUG||8<;k=pJmP~d4=ThCb5vcU_=gs zW@pkZhiM|kaUMerWnl(3!>>hBWYnDBg1cN{pZ9a0PhfThw!zITPe8gN{&6(v0#MQ^VSOM3TThT zT22|9lJL#w<&jabX@;PNMHJ!h;mN1S?9y0}dl74UNa#2%iR1!_B$fkvRoXBAblSy>TuPX{PQU!Z6snv8vY zQ2(Dcg=~T8we2tW(K^_k*>hsAm9X52!~D7tiVUM%s+6*MwaawW;SSM5qfgjK09if| zBi+^>k6$u6kaZZeFM!Sgf-GSVahKRTFltw6X7R_Cu^cQJz+u`Vx8#q44`l9s>EKV% zo{8(R>$ZrL1i_3gfOlAd^YC5XZGM#1E1-B$rG2}~+k%H1P;Y{5xGk^ZWc`Go(&Vv> zcGCmL=^@@Niejl$hSt{4y!<-=#LbONV@-OMqveEo$2hY-urpqBz44KeHj0N;CjIF# zySm)K^PS~!DcQVeAt|w5%IOiyd}_}NBL)gkAPBOS-(Nc6WW?YSt#JTM(c;91uy#iv zPRur(Q70%K$xRns2DWT&Al#j;a;|o1F%+$GvtPEr;QrF${>|NY29`E9yiqYwWH6pB z7AFRP5-L6OYcuBv5%@jr!%#mKz$`8m#qhC} z!I~CJSq2LG!=${cp}$c74N=G(rd^sh@!fUuzjvUUIubp!PimD6-#&NVxn_b_u)#d4 z(cGJ1)cI%{AnuL&uTcQv}lktQ-0WV z1DHWymqMSdVhb2hf2_4&xqtTviJZ{QDzPu2G5^pxAlq`Zt|CcIW9T@tR%%@d)4f5aLhm0y76LwuR!tF|Rb8!rn}M;KrqT zZSR>pq!lMlj^*eD)I!4+s%CV46M&)^t&O$l*8pkgV5DE2-aX(|`Fr@Q;w*gU334vG z0(Yk=YvSP^Bg*pS!p=DFhs?dB3DHZ+Z%t*^uSwS4E)_DAa3|f(D_i}~DLW;+(|Ye6 zP@Z~cJ@QRSFnQkoDqpIi_*#Jd@P8pE!tbtkU=xk}J#QW#jb^my^B@bcW#^Ppf?7^6 z$dIXlESk~nnxr3tKqLdHi)YsAt3b5UdZT&Q3nV#@k>&t+mB4I{V1D z#H@XAI$YY{W^VZ9{C@Xc*oW4+pj66YL()!4&RYd-uYy}UH6jxI3x7%XuLRTy9-+IA z`f=V!7)8B3#{9>q!6s&@=Rd!l=|2a)zA8hTO6;Uemt}xlY z7!c~1eQocOe)(wFY`y9iU?GdAzjTR@==?UZMuLaKp~C`y=St^)EwUeSf|B*(2DUMl z2Y$LnN8ls}x3*k~aO3OPjWbJSO?Z8gQD35`uD{njalvW*=y1LybfI8UKuasLkd09s)=;#f&hK>n)~H0v}J+!Ir*#PZo* zurlMEd&EO0k{n@N#2+3=_|9AAPsI|HY5<^A6#yaj+>}*}x~Vw)DFI8yHl$kT2Y-Lq zwr;tq<|8nnCmog`#zdGLIJGihLbKWU*LJ~<7-$SLuTq80KTJ!|5FI~v%pLXT=WgU} zZe(f2So#>ZPDLr`Hu(KY+!gyLaPNDPqR$V@NZS?uUUz|AbQ^CbXt#2XjXBN!R!Nj? z{zDsVXq}Yw%WcTe1KitWgnSplNWxsOD}3@n!eqY=6NPP7f!4w{?JNr=BnS}vyWu@& zU*wD6$Drpr4~&;mbFhSQe;Oxq>zfMcMQYKx-((L z^6>Saiv#c)z+w)X?t6%TI=t~^0vZ(9`$h|-S=286Oo%@dwb=f&S$2}$L8A8UzuyFX zzz-C@!?}zG8WLDNe{+0`D8i`4>aNoqp)JgL_5Te3OB+8mpo$ed=*pYF><=jbH|6E8 z%)frc&j&r>b~_sZ#w%peBqycd<3W^Dw5@lhCM$0|!8V+{F@F@r~f(QFkY*)0wA5?cFphX}%IQQlJ-~M_WS~}@vpxXaa1%S039jrhAQVX1C zy-$L6D>!}ROE|+<3^ey{0Wv{s(saCpfB_YRUexor7_=sv36kvl!=^_>JpD}gntE9D zjRh7nqZM^A&z0$z==%4crEfrXkbWA$+A=h%2yFLud=R)h-E0!Fk;FL<0A7({@1kvC zi^B~ie{QzGgt3A>q4$z;m{Bb&zC|IO9Gs&RweqeG!hGG*xjL40)t9QbPAwx)7cc=A zVZ$#~Zvy%r&Pj@9FGym217s&F(*aawEG_rNx4P9W{%sOGO}GgLoFtD;^Ul};G(7(% z%STeMxvn=q$Z#X!P`PXX%q1gxT-v~8MnFONXZym`bdj@GFfujR7*JZX;e+?H#_{^NPzy<~jO47z#sX`D6_qAb|0!2MdE< ziVBxFeMV9NC0Zpd1B07a^rCRevk?|hSYW#o@}?4QufO-y>{o`jC8zkQ(Rc7fFvW&c z$5kWW58eeE<;8}cX9~2Ba4DgEKJWT03-zhraxTXg!)u^ufde99C>aZkRSNXftX$0`8u%^4M+Ctuyiq3Q}CjfJNtKz3&DTl??)QvZ-6 ztN|Z9_3=M%!lbiRdG*S^4<6suP%nWxWjz*ONWD0?b5!ZU?ih>MN@>c-t3jbgj8J*C zO*r(T2KbL|kLpc7)mQuUkn}NAQf3wUhBA}FGn)Hs_d^9TZ<}7FxxmIolll1ebKQvd zxumb!pWVJ&6KiDkI__)RnyF0UYGHP4<>;(_TJOPV<)}~3PJn7XW_54Er56gO3?-)o{#snY2n{7DOXSz3!`Xzy(-=HdNqgb*H{>o zcnQmtdqS_nZRS(FD^-nFLQknqPN*V}xYuz>-Q?H$b{?q8i@;OG;eZ49m z;H1Y%mva~T6$Yfw&TGnwsKTOa0+{X|Id3vipT^dWk;Nw@h&AfFq2k~>gC zdr>;v3jMGPmp`%r1#gkJTou(2ng$;kG976;q+vILMC{a`FpyoO2b53sGH!34!iS(N zpyv-gb)C>m9TT8JwiKwlFL*CSTo0@E@O!g?k~gUin+yL9X{(z)SVgfV)X(#TW>Wc` zn>RLM&y(Z*?Ysf5lL&9dr?nj`Y^{cyr;Yt8o7-Z-&HSJsIXG_ursb^3GEJ%n{jTQ=*0~cVmZXJME|k= zRtfOAalbBb9o{%v0-?MGjMZ%ux9W46sxU^b`CH%k<^TN_%nSk|CT}l@(YnW#LjivH z@)h0HR|}9GM;a`+fN4oiWbEetzx{?+cFUx>~u}qObOp2}{-vK~apQlS2KOu3Wq3>1S*_5$)o@4aA-P4Vp(5rV%sh0gk(> zjsnd#s~Q#9LnS|-xC2#`D_5`nEMO^4O|7j>KRt32PDn!Gd$U!=vtzUBFSLQVW3@+QjWowW{M%dB1feZB+(b&^m}a%r@pL^6xm zkN!%yQg@B<;V9K-@Ir}5+H|AB=ibeg<4P?5 z5Ay;QlcRlu_OHW65`mFel_qJ&g_>KB@kZ~O0KkYO_|KtcI`e-&Y#h=W8Xle|xK?0y zC;bV~oM&C8P-b#Whx4)TmrJ;RLWbA=>6E~J8gd7(tEVTi%TG$Jg0tYE{HWrHFzxM0 z6Rb>KrN#HP5n}%j%%`T8BwiopveYv()DzA?PhaQ1S^z4!b=cif=};C5QtSlsr77PZ z`yDq_J^uw_zyt=7wmttbzOb;cNmTkg2M?$_Kv18@GM%7ARCmah;)K`-#sB}SuH`ok z%Q7t$>3c@O<-w^?_$rVnJL!mh1+Uj(-#fc$vC}KR%pWl`VA%TMgUZ;EKqbDWEO-N# zWL64xdeP(!VsYe$yJv}OPdL1P@5Xod$pKaDl5*+!5H=@%n_eqwY@>iF#$#To!)C6! zy8GY8BC#rht2~+jYQF(!P%rSSeEZ)`bUH9;l*?C@*@_y$$@aDbr#o#=dJZdX_4hA{ zSRjJeli{3k{? z!2Tt0Ph=y%76V>UbLAZ}U|y})>$gVT$@bdAx9!qo$yxE3P=;htgU9<(Tf;!b(2QlU zGwS_r(>|u?H1h2N<&^{g7ih@4m-N0qPcml=I!D4$XwAsTc+|AKye#HoJ9Y2W0PyHP zjg0t=0v}y@mn8QQ7&78Js~tcuh zWF7tnCy}5oqJVxc;y{YxEK&>yC{DJZ$+v(0th?V6 zCUMHl!tdimy%$XIx$o1chXmu+JIE@BQt=jncv_o1V{6qH?ls1*_vyvIgs#46c=Wen%*Er;#Mz~#UGx@0s;5^@;%s!aD7u%Mx;XOU%O$}J zY(-bGN6FHXVz5<1;iylcMD*0&a3g7xUrj2ZPZ4tsFbMK|ucitd#o{pf71Na*_|x+N zT%hxyZR(x13ajcrdu8Hr^0L)hLP+39~85my8tIA5ub54s9vXBz- zQ}5X#xrV-U@7b$DU=Q3@be%}6$gv_;s91jE1~KMh`>JSV$y3@fpcg|BvXlXe@x*Aa z_-0fQ@T-C1s{Qyp2ouoEKA}Vt4q}j+EWr4DYMTITxTK`tjdJFXyVnWh-`DrlbBjO# zW&}hr4=gO^T!!~=Y>u{^&S^fYnrIjHUAxX+W2E zskuYXk8du{J`djFd^yF(gv=xO2^jNguVt)+!>vICqtH)DdlRUK&^+IIn~d~&5T)a< zP?>D_8u4#0f4vMG32wY-Ekj9%LCizw;Hxjox6*a7f3gDr1#)bQYr@>lpZ*QTDjOc9 zjcE@Tm&{1`6jBokc8>Wnr5%&nO)LwRjdp?FR=@RE1gmW-+v)x94fcQKxaRW^iYxSv zk&f=nAdE?VPSchU-&x{suW{l1zipO|4{0%|(p`}eO!y!vXf~5- za-To9JYRBH-Y2Z`_|(FHm_|j9KPe?eGRGW;>sb>S7=P?TOKpfU<(-YiSU|j7Z#}_J zp&~kPWAxwfX4KO%hqNkz z5Yg_r`InTYDByWwm2%HOx~bakaKiv&gj%;Emcj3em3 z*9cPpzsjn+``}_?1Q*bYMnt~oQvxD<;QtO9b13=rcH1?o>AZZ=j}_wkZC!#$!5roS z<}?jrtdlN70NY(q2Lf0FtRBSC81{7Gg$irmIAJY5cJL2aO$0G|ZkFYb1fKhp?<(5h zYpTGct9^zH_>bC?i@k2m(xTt~eFGG4luymTE>axRK3X|ZeQ-k*&-D%%Jc#n2!ZmO} z#mOHaxIc976zz>=(?0tb_JS5WXZ4zk(EAix4A)B^2>`QK)PFSrJ^%$yqDwcC&{_W5 zu=QSQUb(T)sr2C2a}e0`ca$XKB2|E`aMLsJgVORuMCvfEuB`aE;vHrJH~!DBsF@?b z0dqaCC3eORoej>T@SZXXUsKBjNOGrP)#uc2n;YI8hF$&Vy?{)FGtZh}^Lb1_sn0g9Kz>vw_B>yg>C|$@=$p8^51K!jU45lj z-oyH&d{N~sNp29_KNce|mbtlpOGqdD*{y$mI6W02cGmAEAM3uJWF|Qx0oYY84F@YA zOjk(&!j+>Xz@0C^T-*?Rsx*w;AeebydRJ$5B~`Dy3ol>KEyo1tdcqi=as!5e@oAZ2 zqXRD5Kie3&i#PLnGrW}k1tbPjVL9weaVJ8B(<2&&z>In&ChTTW-x9h@1!N z9tX;$t3c!px-G5_vewtvhxe>3Em9{caYjm#VI*7V7HbFT0$7gV13vfIl^f87!(vTJuDt! zgoMbJ@_9rDc%|0=z6PB`&NIsvc7VlEG_VlJUUFx9+{e&+6};W zbtR((pKG`XE+P>vM*S8LC=nT`t!U7MZDq5dQ)5P?uUnlvtqu3l9Z4Ua)K$OI(VgGK z5oobpn~VQh&t$$QMFvYVqV~wwqTn1Q{oe@(Fb*XEI$g8}y)T#ejr+@TJHU)1sxf6i z-dKhJ6lX24*n3MM-<@XLqWK6quZX{^dj{!_$J5(BLtU9^VlJW&OyTeW5PvMEYAeUA zDL*9cWeq%1UxMTOBXb6lY4Ztj6$T9IL4#FKijEH)l!!ZmxOWx+X&t(2;+auvMRY!= zFELmNXIM5g1m#xdV+|r8c+2EhAf1hXO17N3Eok2C_WgR{r6BVi@0D{@uz#akB}|?; zV6(cB0#-Cn!y!2E3{sr34KLpufMy+fc3;`n?W=ec9jie7URFY?w&&9eUGhF%KYkP$ z;~5vFG7zzvKnF84C2l{2+u>~7xc)Mu%R%)a`|P&#fQTCMpHu(-3NGN*3i1NQnlDhq zkP@)giEg<-JukiyzMhd-U2K#_JZRiubg((mf1=ualK6GWOyY`p`SvDXOHF945V`EA z1A<$k&S?O>U*w4(?px>z+d_dn-N$OGF0ZFJ_%{b{ZUg*%UVIF3vYLOVDl z`nB~G9)hBhGs&0ej2`qIx3#X45}AR#Kg#` z)HKe9d4MI!cycGsMA}H{=Gk_qPV&;RT9@UGh#gx8Iv^b$-^E(gIuqcnh&2OY&@Ln* zvxfr6gcMM=>AqSs^YWFlHBNop%2-+9n)PSliKyBHTGQs7QMfY~XZ$vmjVQCs_PC?y zuU&5Coi~$~fArW|vd<7XD_>~zaf~guei3`H>T%1{&LCgl*-kVZd;v}MnAyjeHw~gowQX-~fOe(I{`<|hZBr0 zmLp!NO&MSh5M@mX0CV3Z1H*lckr!MFF!$<^5UIrfeRwBDko7xE0rIzB86wxIwmr-D$V%O-tmT^}k5_~pZML#M-wLZv#au&HL;qt=QT#9sz6T_2^sa|KQogF~ zK|idAf8u(SRG+jq0-Gf_Q}r!asn5t48Q{Ynt&MX4(skq4=)2x3U)X&twS zuxkNjsyt8J2Y?QBmV|?kyK7&^LD|MAy2U(j_B&Ou-ok(A$Wk~ z3sswL_xvpEhbV9n!(^{cVxexzwz!|FGebDWNygMX2b$-wCG3wtf3MbgdE@ikc6^&S zYsAn8?dYcK#Tr|0axQ`AZGD%!3vTFcBI(Z*DljeCS${>j_~_y%<;wrwlyp!)7-sD4 z?QIk(mecU%>C}kc%=`E6d(CbQqUXQNX4n~9P;#8Pf`c= z+DRuKPGscJlaM7!MP6>v%g;_LlHp6@+h-U^ zBbpZRN)^CUet<0m9cRTwI<#+eNR&J}5m|?U*wc6C&^gxh^T1FZL;GmKM1$X64LHA| zv%h93haZ@%@=D5>+=F=xR=; zlhirLx5AkN9xS#eivmqJ&j7T|Nrz*vNkfkUj#@|A968Qf9uS`#P0*-fncR>%8$*9a znzK{dBUf+WkCUWmoByd4JJA_lIx22lF%dNqres8E3+Ty*%w~p#qCGur=@0FZF(T}a z=}jTTzD<6RAhIivB{i-~pwSOb4HYCM3*raO>Xd1T`RcUoiswHEFr<96ptDr$69 zBB7bt^mH`r(6sv<#W)!@kHxbBdhd8;iY62Mhx?{P5Pl^F|KcLUj~%SXcLunGzCy;i zA*9*?PCqm*i2(*`eJu+1sSuJ4rbcX(Tc0-TkP8)mr&U7)THv-;uypk)81Y zu5jg20!MjOeoJ;34P+^qeCXz_snBUFM*XZx= zzKwX}g|M{I)*pawc#kAh&)zN>H!QEPahM0T^txpM8~9uvdGNVZ4czki?EG(F;=fOq z2MHiqTJf5U@H*POZtFa|*S=gik-DQI zSwxc_Sy~E6$3A)?>G7mE%c-8{r!D%fJU9Vjm6Lr*c{1R@YuvEkx3x61mWwcBCA0u= zNz5o96&vZ#PZNDrXO^OoI=An|$GkthaaB&SgAF9t6_-9?i9xVt-@o#}^qv#Uv)60c z^!L?-Ng6y#OK6AZ-~iNGjA_B;)aCdd{F%OHT=NG{LZ*-Mdj|5dgYlHMF&Kv^rNTQ7 zZW#$=fHPhjTea0=jd-cmQw?mbM=9Cu;3QpBgW(3$@3f?) z^-fdIZXXc&j$`$&z}%Q2Sqihsk6KIoY{jVYN+F6l)_JSoOQP6-2clq)3`-H8PuU&i=^K{-3m3zrbc zAXyO1Os=&hfAX~-1m-Kkg6l-HFOe&P|E-3uR8%|YC#|#}^GI4$E3d*ZG$zNA>bB6Q zVbFSe<(0y~c%`O2*~Hb;NK{{B0ZW!MPJuOlL?j$UP?y`ApC$GyMy0+z%o#g53vT4` zU2zk{{Q4EkSg3|?qy1Z9)>Qh^pL4fH6XeYf)J+>KHKwTMRiPWS4NUm?Q`(gWq*aeW zX^r99DA@TxWyl%!D^w|Vx8l~XqTS>6XJFEDo5pj6?$1@0Mrz(McB@ zbS;PvS@#;aq&G@5rrf_$v{h8oD#*|#y6=Q}usXTPL_-{$^TjN5 z6K)NHGM*p((vqAhp40#DmI; z3T?1o>l+k+`v=`Tf=sBY*FS`HQ&Y~p8}GPkAagwYZO9JUo&)}dLv#|lslDo@eD!G3UT6Cm;rPe@L# zyHX&<{ye{Y;6eQ$*380Fpi%iXf@!jAJz2K`Qb#I3bBYv~sbJlm4Y(9GP9QUmK8kZI z;MSD~sBxRa1Fx0FvLrh-k7{7OItCc^u)>0UcxEr2*S;38oQMCQ)Llg+KD9oU`~0j2 zGuE@*sNQVfe>GW)g#s1=HAkB_STf{66=mi6>wyhiM=~nS<(ie;fqwkt)f1)M!l<$s z5YuEWQ-y)4XgJxPBS; zQHyT3kowNX3sHC=);fT8e3pVjRkg;K?+0)})2-wE6$pje5Y)bI{lf)dCH)N#TaEZ4 z2g>br)LZ0D>Lu{=CydlDyBCImXWP}N!@xtnC&S5fncu9K?`)+JmRwpi=zf50rb5=> zvAp?F{(fT>FnCXorsgRcOmU>aBip`~TB-`jX$kN?5-aH#l69%zSi6wcBcYSiQ;lS& zWd)&p?kW|C8ligJ5s#ZFtLuJr#q8$s5Zl@Tbub8-n+W_yTnrH`S# z!vjTD0=on_AG5D*lukfDxQ*A;J!^c0Ys8Hf+xP2C01D3ZqC!F%N{PG8WsV3j{Wr(e z`;YcWmVA48h87MKX1;SiZT8T$c1cqT8HhKdV{QVd2S)Ra(`M}nhleUs$lH8cN1SQC zwWOf#Dw|V+Ye4LUCirwNKDGb)vEi9N5niu62RlN7z8f1)gBwEIZt9+JGfpyXe@WXz zdtmC|FA%Yow+2H>{WTIDZQ~L%zKTINj5dZ@ic}J&t%*hNfb)1Gg;x`d+*J`4M=kaG zj@j&)nXddH4-~^9)^sAb@n<9t*EDw5Sgg06kB;{@B*rMEOG3nMFLXZ3pVFD7E*;I0 zEaz~`fOtt)3#@U|m82SeZ>T`Wf&EdxtATC5<|u~~oez^~Z-K%oGdCd8z~1mmbu6D{g4SE{Z$yU-f?job97M(v*qyG^oBN~UvoEYF#af{4ME zyWg*=g6j$1xejl=Rg7Et4X)#v76$gwR<;to&`mN%0WY2|WI z@>E4xKg%NGUXx~&hb+lv*}wi!ZU`oOeyG{a#LxyDM^J=V0I4d0Kp+$m5T%2JCLoT5VhJq)DM1BA2qF+l5QI@8 zC6rM~D4|Gy5JNOb4GHhu;Jkm~<;Udv?!7s8pMCb;YwfkEKnSG10!TjruywaUk}B7oy+oA$hgTm~rHfsXm|oX3x0 zaZ<&5vco+Yn)Xn6N&%eRcl!?gF!{=4zVU~OI(PMd$!WeN3dF(<>p(jRerC_qyRQay z1$hAt%3EZ}x{0N@->f`nl`disksS1_@{?k&*wbfM)54>7$||9IG)$o%9&R7XUGAkK zCcO6!f4eiZbYyY=lp_kLh@&g@5eFJnTMvW3dU;OTF?+nNsPoEX?A#-2uX?~N5!m3B zMw$Q&(w1&hySq#P%p4VpTsx6lUF<}Gj@u<+ZkoOOxdPKqbDW_j^3o{@EL8Kc{)Cqa zeDR|G*zp0n^N$8c*W9F`QTm`=CQ=Oq%CJRT4P;}@2gE`PRuEh>A0B$A{08H|F%DGN zQvd|yc=ug@w?@arY(SD3_atY2H3n@_K&{aDaI0E*r(gL>ruS$gUPG~!3ewwvBuand zJ7Kr8a`Q$u0t@>~OkbaQiPA>gr(vwyGs63#Z3Jqx31;HxJl$A=bxVAjZrtcq`^j5s zVDKl}IdU}DH^%#+O}Aw_GP4ks>1Q@VeVe*mF#*96f!Saks2XwtBA&OjM)!3eFS<&V zvC1~1p04OZYu0CIDl+MeGCP8@4;fEbZaDArZi0HuzyBK#)B6mJ9GBO*u>QME9QoT& zgRy&lBfH|8^y5bJkUWJ>K6aimc+g|A5TzHhiaSjK02KSa+PfN>zS(!94}3 zI%P0~^EUi#Z`UNK_7Y99EgqU21=F(6s0i;?g#?Qi2Xii>dfL}$DBD-Qd-exmzPsV0 zxH#`vt5!|NONYS$Z23`AK>$4DNw+0<>XGMUDs|MIV$E=4wzM)RS|?d5yMK9ZN1s`6 zTRNoa_-g$#NUE(#CH_;uWDOH?Jz^rC8%H`^(=toZWum)i#KY`}_ap3*frv8SYEh)7 zY^LDyU=(YD=Xr4DNHZ;?leH;Rw=K6m1!c-VOg)069SJtCzw*q$ZZ=kOoh?n@TCa4o+xLdf-#u)UmeVvudrgtKsTcQU71i;O)tk%gTrP+^|de zgsH+3(i;S;)1tMDeE(aAE$u~qD2UcFOHD|Ex}+uWhHZTGwK_xQ3$~T-aH*=z=N$m0 z)WR<3WZ#Z(7>P0d%wTY{p_Y9E3;t2Ls z{SvVbco%p<05l@gHQh>*fL5)tYe!U#^wxC9THl1TMDJIYxZ<}b!dpc_=NTul;ht;~ zalF-c>v3d=1Iq0e=QsPg_xi6C5v7M~R2w5gJ8El)G)NnZfy`i~JE zQLbTn=4P{<7-z&IgJvhiI)Ag7B@Ecr?#F-xsKnlE2ejwPO$MQO^#R&B7CkMzj)4;I zePAKNnj<$AfVIp#K*`E4y)*jyP7NjeS^9LWc-aUg!&^yMv_uoz7pFI{HtNHpXgOw4 zOX_1^PKkEtU=2^Cyp+^w3w0i>fBKc>YRL^19~_#y`EQ;>2AKHHWeM)fiyR>DCi2MS~qsy7YYHAHgK_;zsyR4aX_#S(OC}CMShKEKzf{4gEG`xrY=|`^*>-#1 z%L#k4=gDHC7EjH;L|w|azBw}Wbq7#rJ+N-~_H68t+Muh)lUKlC;#&Aa?PxP|b0q?j ze`5sr6fU!4c%1BlPZwhkf%L*V?k#?r2 z2|eIuKCujc{DE$#?Ed3bkKgO8_j6X+VMTSS-v*tx!a%pYf(mw-G-ctFmv)mtn94>W z9S*Oc^Ld^3F}xI5HOj?B69lKu=Qqph3ApXD+j90k3BO_Ho0p~giL#D5RCH^bPk>?> z&Qg;&CM**b%e%beRrg+gX*!&5Y33x?r0^f;@jw?xNJ~lX?=e0j?0h`K)XvYlU1UF% z%Nbs9JBi|8uDSgNe8ym6sV}tMlz|MuKHsHLTyF;}J-G!rcR0;CmQ{Q{TNI4q=%YT8 z>2a*Op;lWC)PYxNAerF?=6MQ|VflmDiF*n6JEKEKWC(c-aF|F+3^bi#yge#Y*Ypf< zk)IcWH>1Yd)3w?AAR*PZWhVKIYPF*B%j#cE8T1PU1_*cSS8?GLlbrNY=sYMV2UIR9 zU}~_SR$31a8NyB^wfcA=^^TWUA(fL$Lc?{fwk7yl9dROO=>blFdL-Z^ zCY!^i`um+ij$CZs)Kef1CojHydDsNY1_IY{HmKlgV zJ3$%>#CcEa9cQ+5QdhHvVy)**V9dYA$(Z#iUgOGv2QxlDXgEwpdFLGPy$AXtW_A;*5Y*5sl?W(~u!ZDntl`?;f4V}X2T90>2*VP~G zV!6!Uh;V_*mhcl|p}E16P&h`oO>d8i*Rg%bv!9DOr|ABsKYF>bX~UJyZsYRYwQKfq zX$aus@_8`WrjY;@$J^n20Le&E?xw&Uu4DKijIawhW6e8z1** zx79po!FD$Uli#O6-&JytSUf;@vCYR5TSOo;1>w+*&Abt`;*bMckg2_}z|mR*r!GDv z;iZZ2o#uS4QEI3w-z51gEu4#Et6vNs2Ec|5nfvVgltNl6M>80) zte^)9fa__T-7#?)Ra2;xEPlP#tEIP$IgY`7)H>!RO50UgM)-5r-Je{bf>^MXSXB=C zg3Bwv29!pdX#F%lgZ9*e72)>tyTbw{y-#Me-c4qpwx)0?EFPW#>GJ$C3&dc%wK5D}Veu zWB$sh=Z0(;)n3{fVNqbCR`G``5z%d5qp6tFo%q5f2yo1IGiL!GR}McvTwiFUQgCbR sU(?vUlfcY2U*XSH`2T$amk{O4gf)J~rQ?$4guutb)b{MF(;m0}3&|6S+W-In diff --git a/app/src/main/res/drawable-xhdpi/edit_default.png b/app/src/main/res/drawable-xhdpi/edit_default.png deleted file mode 100644 index 5550b05a2365c19e9bb0d0e32ab3a00f4bddcd45..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2895 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 diff --git a/app/src/main/res/drawable-xhdpi/file_default.png b/app/src/main/res/drawable-xhdpi/file_default.png deleted file mode 100644 index 086edeacc699d4d67f7882fe4293f89aed1eb5c6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1821 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 diff --git a/app/src/main/res/drawable-xhdpi/footer_chat.png b/app/src/main/res/drawable-xhdpi/footer_chat.png deleted file mode 100644 index 08baaa4bc54bde063e5a8e48af0af9108cb49795..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1994 zcmV;*2Q~PKP)!q-IMhgwdp}tu{AT)?~Iew8&_)_uOc$&CIDGPB2o- zoJR_bbU-ShBIqIU0H6Li7kPNN?&sxx&hwo6JkR-V&p-El|E}wLzW4R^``zE`7bpnS zQ-Eo}6rel)#>d;Q0=>X~;6>nHz|IiDk)dvP7$UN!0q=>wxskXDH5StcJPm9GHUkd= zkAx5o(P~gfsq@vF)!jA@uc~*dSH&JZ?1?*5{jU0)&Btqz%nz#_!;QF$W8;rl3_PWN zRUKdGIZ&MotOnkn!@L*R4r~Js00)7A^Vvk86L>3dHgI~5I-Uov2iArV`ij)!CaJ$t zk5<_udex2Ux7EwksX6K#r@mWVss38sRYQ;5u3lOc@;;@$P(`B;s_WH{tKAK1%N+GP z>W(UjyiJ`jRK!h;gjB-Q>Z)coR1NAyaUQNTDt4(CG@HD$)NK_SwoUznI%KsuVhalAx52%XOkg?+KR8OUa5o4Aq++2$r^yfqYht>De;!sDc4=4Wrk*bDr zbiY!#o;HDchWc`9`l@E(*qShWTpdFjAqGHFSWwPrNruryv>DY)!=uop$55FVh;dyOLU{D`RaB@rP;U6U)e1tZK`h^59`;UvNs~zgz#DiCi5a`&9 zPZog z5S>(V;}Gy<&PMhWn83&DuTG~-1mT`Kw&<-tDAvOh7fk=EFvXe+0#M)Ig3)9Rfn$ca$tFp z$cvZO1X!D+zT1JBMVkZZKMrw&s*r!3>Q!JH?6WVtDh-z-0So8 z7JfmQQ<`g9fV?FcJx_J*U{!KeMhcU@;dvs*@|HpT`8Bc;ExSc=Em$lNvZ2FAa0g=R|Ns)FB;PG1~lT+iAsMm42J8i zWmef^Ta|0`lwa$H_-NwV=z0q*b_o{ ztpTK+3tR>)1gZ%H-v+DIxkOFqj;8Wrh2vdvv`)d z>AAB)4Go?M>;y_rZqExLJXppq05<}wDpc_-@KhX9{|5dG9204F(Jy!!M}LaYX~5JR zBWUQdx2ZQK-sE%Tk{iYhYqyt4)YB~NR=?JW{DIYy_r8o(?51*)ct&))SvacxDf+oX z=d;ISU@vBT+B-Gze7Aa28UOi}29vf$y;glo5w-#go0}9F?xJM3`aHHFAo{Sb;XH4>s-Zsz=a`%qcJYH82CDA1=BJ74OkLFcp_2X z__!!c0Zya;jNKSs*KZHZBfSsU0qkhn_M?U#ce47G3R!pmuAUMz^nN4bSbQnA6kw!Nex@LvaJ$^5`Ue0;lHp z0Ne}Q5JK2UJ3;MIAIh1x{$6#3I)yexh$M~%J^;LpetGVUxP>^mWE-&qcno+Hcpz^| cfL6l)06*gz48_dbGynhq07*qoM6N<$g2g?>=l}o! diff --git a/app/src/main/res/drawable-xhdpi/footer_contacts.png b/app/src/main/res/drawable-xhdpi/footer_contacts.png deleted file mode 100644 index fe35fecbf74232e085701f7912a57754a9a98717..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1959 zcmV;Y2Uz%tP)SBfaL&QS4y>I<6GL7=Mb5J}0o};s7Ts_;0se(#pKO7ePqNmbuxm+Hq%glqlItAu+eorU zHh}%mP{1~l3)7hI%Q_E9?*<*}Y(v4WkgV|`|5-Y(WOb078C0mZl57?V)k1QDk9JHj zz-{VIot{as9~s`(7cyYW0)s8@2My|NBwK}otk0lL-wF0yB{?KWVBZr8ah_y(25qV& z85QjMASh4^G{j`RW`S*Q7#?^*5WvnAj6X}V*aF-7>%mB}o=kOKku=ypOGsOzl)8{< zuu7>W0BeO)Gi`rcmBFw2s6{r86YOgwscZ&2!r%wUx5&m`27kg(o2!E1LfUqvRI4pu zl~Pv$?C@y2HDINuzSqJ(u6iMDn9X4O3u%Wf1S{Dzz-F*TLR!>9u;)G6ZVg!JejOHq zZ5Pt=YzBKtNV9RKRGRj=XfxPzLRy)HU?rc$S)0Kg6w<0J1Y70Nc5A>&-I&ofjcQ0n z0E`w+?X!90)zdJlxz{2a9}w(|rh0Uk(l=Hqbq&Ba;nZx4p1Y&3t+oQ4l+zTKVyMy&l;xTgAtub@++YL$1)j(qC=dH3wC{hporvX!>s6= z8MJAMA--S~f+WMNXj>+n-XtG0R8LNZ`CwB&m!RjwZr4hZCALAGtLNt3R8MkVhU}~N zU|(Z8qGGyYaD~S=;xCf(NakhG);y9=licIM-i;*7GG*vElBZk@+)Z+hO)aqQsXRsU zPZ!%ykbKzhC%@FCE20Upduby;=(L(08#I_QCh1p_rKxDBwm7WSnu9Er?jBxs7~n*@bMs|}8%J#lCQc*VNqX-CNlUtV z(&sVVJrQIVzH`7?^zH z^#+d{Zm=3TE|O~uY^J+iSU~cCq4QrDf@QH`FPjFEy__z5u}e=Rx8T-vYVo!9`s|Zj zXd%vOurSlW?Lu)&56%yT6!)m)`LU?n*WvQ)?VeqvZUdVT&YKKufJI>bJfBdG$wbNXl}xf{TCee3|R zM=8~wWf1cK+=G~Nsv5vUh<$g#OGJ9z^{#>rzwGL4!6g7YHRN50?XppC!zPg)Ar>RH zmC0}b_iK=&(DhA%CM%`3sDuoj2e1f0s+K{X(BtTOeH;UDQaddg{n~gz?~^bHK&9^O z4AvmyFjKYGU_XH6N~slM=T>i?I8`5G0rXBsc>$aP(1;t~=ZYWa4A=D3|6Vhk?}ylT zzv4%c20AcJ1D+NDKLe-*P^*+`@I1oO^EYD?K;N0;oq_#vkN|#CO0{%qIOYJlJgGvj zHaKyzLFW=0=Ny@!>8S`T4IQ{|JiII4i tcE#*Sa2!dBBDRfQ{AdQyq^D1O*MEtAfV(SJ$EW}R002ovPDHLkV1iJC>UY{ z5h4KukpYHv*km1MzWjLg%;Qbfz1{cS$*=j&;Rk)I`g`5?c6U{Eb&o>~NS**-0D#i~ zw3OnV06qb*$T?SqltJ=z0DXn80e~FtA-SD4c9NV)vSSL3XhQN%lItV*4v>7F0k)gyTW$z>6Il_VFC93E1!Bl#2g zvA#G&auUf*!p4jxxh;UNisTZ#XGl3Tc$Qtb0gSC*)f$ z!!M-s#*sXnkguHN0IT8*$=NI(ugbjX)0oLA@U0=))RXV3B3@Pw`$+cA)#>sozYV}N zgZnUm*8!}R|91m06hK2m{toBd-7$^16u`>>v|(Qj;2i)T05}4m1At)wni&+Ojp=19$<2n|t@*+a7!&nak702I$zzKC zqY*k2j2|_{gRd{i3PpEGgw9#U(ETgP1~GiCNPeW~R*-C*oBPTYfoTB~^Ay9xsb|Q~ zSLElDbe?=)GhRSfLw>3v`-$&@N3s{mBT5~kL9)N{;4_jZ2XtU#lDn09E>dJqGJecC zfvf2frH+Y)>`F!Ux)5{(JhYE_D zl5CX*U&a^;Lr9heOt2>dxYTo)eJX}|KH38q7~%o~xYBc&SyRNAzz)>;#9-X0tS}D; zG)OZOwpzgjl7Ca=7Y4Y1V-2IfgCT#HBEOkrGf%$W5}7H)$eh5vhFvwmqw=c_Z{&dp zoo^U?GvbCa$$ljFYA+zB1F8($^TZgw_9WLTy4y%L%n7v+Cs})|4@S6hGYkv=0LeB{ zL0ZE$Bgr{2uG~RG?@KWq@wB1$evB(uZs`4>Fqm))fawN*C4hGUtPmTjugqB`MFbOu zN-*IBW2IgW;C*rKdjJ>&ph*$Igf0?Hup&1fOB=TV=q!AvdIb~mNFGTcy!2SW+<9#Z zeD8ifl;Zg=EFu<|LmA1o7C{Ng$BU@^$vEd+;k?ZVM)=^JP;-RjC}Xq?F{ZNUn={BU6Jaw)q;6oG6pJ zg&icH&=#2le7Qy9&4>~3nRMLwA?}xI1_0=p7opt*V6}5@1!@7Z%XbEVPU6Rw16T*( zU$wN2+8n@nV$hcg-$npSoO6dzhdR`u4s|GM5vec1$_}El3BYFO+|gQEL^1$cOZ2*7 zZc?w!37uq1X=$-(&wvcOy@ooS_kHI8Y}>xS-XS(r+cq4%eVM97{oeA6QQ>+cnQ zRV3Sx{I!t;z~ep=lD8T{4@T`%NLt8rL-&pdy&;Cqe06vnulsG|>v%3gr=5IZg;*7r zUr2J0M4Pl1eL_xXrIC2!Jk6bF6#cakI)7B;SH^v{dHO##zQR&N{#GLd*V&WrGUJ60 z0?F>iirO_`G3a2d+OD`XV|qryMuk}9oLleh z&Nx>F;7#SxnMzqF)J&AD05CUz?!M8msYrx?AO%Z&DVk6;lZui;1imPQV2|j)*xq|m5 z2&^u&GEzM_ua)^d1Zzq53*F#(HKt+5Bmg-eg$o{Kj<1mz`wC;5S-6Q7qNUt^MAC;4Xy zP<)oR;j~K9KZXV8{>U8)Gd*)hj*L;@0_e5V*YY(AJCd7h8t$cUYIA5@YY9nhFw((%P$QUG7m zh$Cjj<>E-}Yi|nCiv2!^QKT`eQ{b!id1*3i=P@1pQVTd*fy1qGny9CQ|4YPgy(Qux z-qN^=8rMe(Rh0LhmR7D}2IJ z@|7%A^xuon(K=z5oT&7f%bqr-j)oE8EA7i0;>l;_P4>xc(rDRE7X(bs{ghoY^#r+0 z7g{RaO#yt(jr^KXhWty4>=OZe8S3{8{ydjhS%~Ec%mZx>y-JbmsK^}*-RLIys8Y{8 zhOD;IhhFrfZQZ_X<;E;ZV4p5l$~5Cz<6AD5_oU7z!@6*RIV>Ad#qzbyKtI8~J3$9( zca1jlEUQhvrb^&=k8_SG@Reqez2LPFtr9e-wNkdjvn&Z;8_&9Tp=K&cb9&wo633U3 z@O&YXocYjnHY;UiO4;f3yn-YT7b#^m+Txt6M9s^7>I1kSh4yXAIr3WmaEMDbA~5Av z^L-}=@O3v_&cTNKlUk;I0AC}+a+sJCm}i^@YUNRRb7t@2vklFfI#Z36_-qfpdWMG( zbzh<7C|vE?fe#oiWS5+Pl{Nnn$-Xg>6zd-55fOSFjP1)EBrl7Jq*xc_{wzYT#JIZP zuy`8I(5oM@5&vAFu;pkU6`O0g_`^LVYyBZ(=#%6EkGFbvgtPvFd^NpEc9W!{$|NBM zeD#^^hNenye_cqfmsnsNwux*BiU6}W3i*7Ehh!BmEt2m%&vd>jM*P8rxU1%x0Qd@k zQ2@H4=AXlugKWOaKBNG734;M#0iX|R{yB_S0L*dDZB2>KA$woU<;eayjFve5&td!> gz-;H7dB^L20h7hoBtiHh1poj507*qoM6N<$g6dEbSpWb4 diff --git a/app/src/main/res/drawable-xhdpi/footer_history.png b/app/src/main/res/drawable-xhdpi/footer_history.png deleted file mode 100644 index 96bfde0b6cb841abdfac96499224ced1162b1715..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1918 zcmV-^2Z8vBP)FJ&6mrOS4I$qUpU%#%du6kvZFaeCDo|1Y?I>P?i zOFGnMX_T~E(suh>V~lA?T0cwzK%O9Ju%rtm^|L?^%d?tYlD?C)TGA3pi;XcGstPQS zk#v!yntbtIuuw&a%34WwAH!- z;5peM-`}Z5b!v#5NOO%cM~uTI5NpV4KNDWl0%0YfjPj{K)VvOr5$h$@K%nH z{{Zf?L%l>n?FhV^LkHFZqk)bwx9whFT@LMg7wDW6sAm8_L^OCcFdS%8#&*^KBYY40 zp35BdUsQy8y8tsH`tWGn52?D4|Mu^|)kW0zghR)pMXdi?dpG!`K9+|X@M1_KJ}icn z2J}sG=$TMt9bJI=A^m!x)KDJ|Q9cu>DS^(CgUx!ly%y5HaixNKM~FXqu2i>6Twt@@ z-18wmcSNxjo&)^jQ8=;0cS&quvvf}M^mPw#Rxw{ueKS&ZG5GY@!CJlbAv0x>uicp( z=1ylx>h4IME@`|fS00fx!*RWbq*({$`t0D|CChSVZI3)pT$0{mR}csku+A-E8`?>4lv6oBg z8pym`(wiyH7BXASi}SLjyPB6wwE%mgqk?(WJQ4`8+8vVSJCbg2fpx7|c|FDG6&IWT zTP)C9(s6-Ijgl6m{JgS2(w@Mzs4R)X-!_pLA zq%3h<8<-8YuOq`JDYr;*G3qNx8T*x>wz# zFOq`8(OOB}GHPD98o#_UgUc~y4 zw5dS%=Xx`(t+76#wSm3Y{)#D0z*I>ObWl+&1*9 zq-TvW>+>vE>jRr~i~dQ0zkG zT@HT7?oZt5NbH)Tiv-)l!R=2;%GklMYi^)ViY`+6_~s7w&&RHIWH>+N7AY4vuC2}n z>-t(SB;^(ak?TJ&#PJ{ar`TIKE5url@wkDDU76beKRYr%kfLnC>OlvmP2PA6z!XP; zhPbvKwGya@*iCdnnH*MVfM>t#ttrw2j7EC)+4hSnIL}e=>vZm*d8a0@%E9OTTneA& zvD(ooSnl^&>#4a`@R6f}tw5J5hT6@uQ}cs-Dmm9~sf@eZZOdR`il*bMEz3 z2=z7(i>ZZF*2!*24$3y*#43W?$1`938_=oHO7AK6w)!$dZ3}$nVQ}}sRzBNP@q48R zu9G0BHNad?-`^;Pf?8mWN5RV#4E7aIpVt6~7fVT4u$?C>5Nhdz?PMtn3lH^dCA^v0 z53A3GgohegD#c*|&1SS_pKE>Utk?sZWi?<@i1N>YlUgCvUceV2{hAop?J^~ek>2LI79AL1{@j z+V2uIFXAoqXurQCtx+`}?U&22P5B*0`JJSOqy=6354OvOVQ@1?4FCWD07*qoM6N<$ Eg7}l1KL7v# diff --git a/app/src/main/res/drawable-xhdpi/forward_message_default.png b/app/src/main/res/drawable-xhdpi/forward_message_default.png deleted file mode 100644 index b51542ec8cbeb4c074a3bfba2864aab3cbecbb69..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3732 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 diff --git a/app/src/main/res/drawable-xhdpi/history_all_default.png b/app/src/main/res/drawable-xhdpi/history_all_default.png deleted file mode 100644 index 867e2cf707ce227aaf318bf6ea77f9bb408094aa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5529 zcmWky1ymGW6rKfF!u*T$EB!N+lMQ5=3_Clw2gFq(l&e1>^@@ z0SQ5N2?1g0{Kq%v%{yn#+%xaqckg$+ux;uPiPR#)>q9{1OR^Qb0c-oeP1lNRCZrYF3W-czTDAZ;n*a2*=g zFN<4ENg+_;aL!W$9i2W^d>HEO=guHrk_X_>pC!$($RPZLx2q?mGYC4GgAb#OGELax z10?$Voz#(<(uRR_Q96+9LawtkQYd}e*J5kIEwUx>m{OGKaL^uG>d5o5*C|K!=^nyx z%mK)=I(g6pEtKR>u2Egsy8!MM@?Q1=05lsAcBwffZ1UNoC`&00g1G?1j`9e(^b4}q z8j@d9vLs+OU<=GzorE{1|0l`wGF9IN7gFUS?^e+Aa$!K{86_zBSvZ+4Ba zx1UZ+)h(7_D)dr}qUDZLH5_}Dk&zL(6BGg3_mSIZm`@FDySZqM@HOnLq-sRX{hXR=6#yIv;jb`KlqpsxkseApkJeUK#gAJb&g~nU z@kSIW5WWSUS_4GU*hXLM-h33``sq0p)*G9M81Kf5#Gb5EITGGAXOQS)AE!oAHsr1P<4RE>D-9-R=8W9$8pVAMy41V>3h50QRSOt6zMOML-u(<-RZNS0dzP)BN zI`3A;W+$D?qyEs3@m{&8X$5cyEPJyMg5R>5szyEP{afC#DIzhbRAAjImpVX42!vMG zLr#R<4mkS!k#Q3?mF4APwGwN7!gd$u&4oxRQqh^n={;CGrXM_Le3)8PGTY}oS7L_l z)c~ZFWQ#cjUzTg(D{M#MlCtnqnXj*9|6%h@1m~f8Sz+PA2>xUjdL=1-nZ(Y{?g7@? z)Z4FMWcj-|wG>f70p7t^Gmidhvu0(Dj`A_R=+(2dKqf*xF7%Hyam0Ol6F&Q;oA>RpHFXi3bD zVoQfCB%RP4y}!!;AzZdL18%50yePh`1c%$G*QP1};{+UJBmcZXN=9RnsifhE+y_jd z`Sy^uvWFXv(@Y!6v-_tAI8XJ;XH*Kf)%{|NAWGNW)rot|SMLM|_uon~Z^+IT?BA|J z@&SLl3i@qmPObd^3>+OD{g>OEjP`(-Osc#w)9-^1)d#l6dBLB&_QsAtLWcHFcT2t1 zxZ)8z*&2UUjF*~hlPRc4Or}>^xJ$k5SffT080#m=ZCQ0Wo1wG9G3;s!^PgTg4{BOE zff>pC^TBmS%6Xnx*CjxsI(?fRaO@?@wc@0{UV9`Q4Hbtra?(c0IuhQ>*6YW1ew&?* z@HhVT%}sXl(!3Z|RFlW#H<&gye%Qt0I^S)Cv2BLC+m^QDwGxMmF3re;FvBta&G;Ks zuu6ySO?C+7f5E2&?gt|U)Xj$apqQ-cA)^L`8IiQYJXMPR#s$pM3`NXcTD{-m%m!i+!dpgC4VA(8 zlQQITR!g%6MbeM2cvGO_XOSkrfhi6BCA8RBR~JAVwHDy8&;o)nh8yO=Kx=L)*PCT& zqMqPx>wKkfv(iCD3*CZS>-g^5D0~EI)!B*-$=x@}hY%@sclt|gUa3TBpu3c=>8GsMG!Rrn)u$w{BsVCQ!}>{dCn zY_GLAiC_#i#Bbh|i(kIh`;a#3BM{wUel{OEYvFMw47@1{Y{jh^r4;7}%?F29BAx+? zd|jIsKXnl5!H)Z*XAY6y@-^oy57K~nqtDXAL%T|bhK8tCoc*$a&tOa#(4>@9FO4yH z1ZFK<$$Z<=b%saSM~2~^n>AF8#&l%cj~+ln6pcPnFikgx+eebT{QS(1-fuctA#yDmm7S`Bs?!B#ke z4+=HoY-Sc96on5-02u#7dhTb#7DdsRF-_`9y)!`exqfdPi_del?)DCOJtvs2j; z?@-?rQ|}g89QgO%{8_B5;aJ->1pJSu#}z1F%${EL#r%QaDfpAShGTv=g5=(LW78;f z?v>`u%aCsg+h~;1+s=LPwYKCZcHaC=Secrdf|1v; zVp39(iaodxynpsbwMJI7u6sd5M;^UP5XVE&aMf7XllL|nJ;>U1a4-YOo}sRiY+5J1 z@S&0n_o4nY&v`3R{SZi-Q1pQ!u>hiBX*nS++gRu#CZVjNdElNQ5R zwG*5#TwdZ5ej@nt{9Tr~A!6jNSe_^G&F46#IiAu6I2>-ls}k~138wyP!}b+#ak5%k zyvjH3jOo-+78s!wH~pGv(hEy$2mBbgJLmkfYuH(70FWFGB6h9-;d+lJwGqmhEk!S# zS3=4vZ!?YZB`pRG?yx%kZmOn`H{Kuobob4(~VTsv3X|2?}+#=v-vwsO-G2Ac~ z4Yib$lN)I}BxY$ZqKC2+1}(&Q2{QM-oFt&X(+b4`irk@p9zhk^Nh&+vZXb6yJiomW z!`zD7?9cwWe>!GoXLpw2i8(<5>hyugC%KRHWHEobTnqovm5xnkx_Mc^Q_C?g^~QRJ z*&WvSp25kWLmZPQW0XOse$k?0sI|z=!K23>XWso;WD9rqrIOPRKgc`Xyqs72hTMpO zqqdKphHpfde^o~OKui+t{;WO%A4)VEcr*ql*k#BSEDJU?Hg1R%&WS4iR&H@?*iE}~#^55! zw$zAMS?_vAS^30%Y0^H?P#?35)Xe*eucXT<;mbg6sW0;aO?O!M_R z%!GhFbsU3V;dr^L>?tp8#z~3ZaSLLv5VMpYI}ur)EngY~Nj0B#euzr0hUa}dcELQh z@rImnW96kFM}qSJ1jw%!v#j}V>6r2PqGBY|(SUnG#q{>gOLbMQ?iu1EAoXEyA!5aU zpN9dJkmwkHOREiOU<)J&PPK{<#Ln%QDaCY5q=m$8b)5J}g~I>}_Fz!@)imaiw(O;u z3ZMW{WLyn86fzrdpZsr$+$h2;{8@VAS|R>9y|aF(5hxYP(Q4Uaqbq*aj~+8?ajAZo zcy*`f1?JGzqvPt!pXW$HI#=88wD><-d{;J7%$(A7uVU#3XaN%|t4ALfZjMJoJ4bY= zYn;%fgkVSY%TX1t)-H%Gt%88&Z3`T{O5a|R0IE{WBKDxbGr4m=Z|{{`LV6VLT4)fn z98DiWu~_UBTK7#eT!tY0Dr|InX;8rz%R!azQ|%xWZ9>fA0fk5U_O0lqSN3(0=MlFp zSx3jm$FG;D2&@K$CSoFeu|!UKItl<}ryn6FmNGl-vsWjHFOaoAE;&OAjHh1j~kb3&0}d(URIBeFIGB<$&qNz~McbRzdY4e1)6L^+Nmm(;Xk1 z_Fo6~h`!NZufpN@>E^QyWCTli-{_ZQu(3uPX34MTNV|5AX?{Tv?VYp`Z;N0Xm4!Ef zRldH^4uwFReUqeW#L0n3NxCPi>6?WZ++_v7pEwKg52b>FKv$;ByN#C28?BZoi75e@HR%^kf;pxq6qr}qR%2FW~fK3n;kFMPgDhN`imk3c+2mPbvc z2Vy;Io=<*tt9yZwva+qkhb6mmkxkru{QM&F&>qaAtGs7td7uJGJ(K6Y|F0*Tn)*Xv z=cXv9(a5aQ!?CtaTJG(Giu@pWn%X#gy^UY|FTJ}CFLge5*AA5OPexdi^*>ouURCA5 zR=USBnv9x0i*V{S+rQNhJJcET{yaO) zO|P2`o)OtrCGD+GFLlSz=ly66UW;GG5A+w?PE1S`aY#D+$Tqlss|Jk6kJ@{zT4-;& z7MnW=&g_5zF=Nz^_pZh=&up6f9G`6e{%U;Cp-z!=%~=Z4+$?pir5!rj_V8D?XVd-3 z0G9EYfZ?e+cY3pO3)z~5vPv4kLQ5eO^Xy)Dr{vp?2_4#0Z|pv#S<~G|hP?c~qBz1G z12(*8o5#UIv12xdV^$u4*LdA~z*s57r2oyJ5fUdC@|BB%ised4L%tuEoEFWmJ4K@{ z=M18MChJLknwB2Ki|td5>$_BxLIAR8^Vl>fBI|lu(i<~L8HUBUmPQ3dmao#ULh&a8 z2?hI{Sxi3-u6i zy{)&h0J*ZVvd;($UuoH0BRuB)#AOm3yt|pdEH;QYZowT7J-5ZThupW}(pg#6ILt=%14*`X_2Yuls~H+`zDJu!`%j>tqCX@{69Q0zI0 zmV^z}Rk@FPHE>War}gHNW~wO!mvNE4d5`u#i<*<5qwTKp+85ik5ljp`@a6q~6g$q8 zA)IQ}qj~d5u63Hh^*QB24M3^WsSXr^HL_%s3B1_sTLf?ybEr4BpOs*%&z^Y4w>bUA z=;`G^Z!!0)sk&7gYaggDWR2t~?`NJK@6XZ9D=$k748|xuPgEJk`xy`A9ew@qIo|8$ zczC|w>Vx-Q8sYqEEb-}Y($gJ=@N-)GirU6IelF!TLWOcGK~GSTioMdWQn96mX=ZkK z73-x;%D?DE#Z#j*9uq;{!Y^O$)CMdK90E8U(mwR4b$A^x!?(qw8<|HX#g-(6dir9} zX8MWfJNflmk%+(V-)YR0RorN|a&l5r70^zkWQoSH!PO5H0PCgZ2W&6)ys+|e94}G* zFE7y)ue8SMNLrvxTPR%(G>{M= zc90){Y-G*ckq3}$!>XXkq7Dx;BV?yes3S~lS&zAMcYo+Q9gof(X`UKO?6v&E_Z;1G z@Auua_dfgVvxhJPBoGK($IKT1=njX&JyZ1@EiEm!BS((hXc)#h0Cq+qkzdht2jA!O z{eXzRYyOu3tk*Q{A4~ll!C?c0}hxm>jXnk3EsV$Xz3 z-~&hkxWhKxKlNBF*5z`!>H%DCK>}j2*vo|;x1ph-T2Yi20W6U;Cji`}Y1$6kOwc#6 zSZrqo5fXq4BjTel+v33=OuiF7_Q-x`TTHla?bA zUDt!rX!KYaO$Jg`^8>HM*|vBqG1Xm%l0!iEOj_ z`#Z2=3EVK&NIN{@zlr(<-sbT=QPuKOY@7lHN<9Iy2f8M-#y+rgSNq>hB;;M483Fcz0GlNa|<6^Cl z;7^Kx=H}-4MAQKwVA16$fOVRt{aasOpK;=1JRaZgcDvsJuu`Iq972di0JRof_L?z`eye+Uz24w7Q%5oxA)-~`aQLOH z2eXQ0)YQ~G24Icl9}+#HD9ZYdj*gLu2ASq0kk5@ZSM&Wwzi0Kv!2+2NA6Spj)h%H+wvus&ZN}A3JvJUI4$z z&}VBt;GblKeLml%MAQXfmW9gMG&D4{{NQa3~OV ztWEPF!FD39)8F6!BZ;>YC!%0D9Dbp|K6lxYkR|welVAgYii(QMnE9@>wn3ZC_SsM< z^nQ^RK4nLO$K$CYA}jE9X1;}p1}rp1P9z8bFbqQ$$%}M$b`BKtg~Q=+8-QCZ40E%h zC|g=vTSckBx7+Pot-uFxOC%E6q-ok;0C-$#wV5CA`~COi;ssqSY1>(f7uPiHxeO#M z?e6YgQ4Zjn&j|orND@vEaM8Hnj$!ncYU-OW_q4?e%_}4DQD_oGFc>@?fGh2KvZkiy ztx^%7s_OLs)>#C%c9t3VlO{n`)q5u-V03hJo@7P*etUcS@e+Z5F@R?v-`Kf55{c|9 z7Wj&yY|9b+Ns$2H?-2-z&8wIWe(v5jzl8g z$w0zARwM{!{*>hU{}vN`)3tmOzy*@#ULnMvV=R@t7;2}@0(>37+ODpyj$C6$kx1k_ z8Pw4|GIb=F*=}L{iYDFN-9H4dO49VcuIo!eq0nHd)sA5p%d=2B1ye_Yh=fG{8pQ(N z=kwhT;4Vqi$IQGu8jT*F3bm8Rm!Fq9k`iDT1{VW-RaKV}(IXbq|E6MiJS7G{YZ3%A zCnOyz3k`(d?>`T~&miB2{u@oxI*UQN-EM!>O6L>Nnj(OI5(z}~m=y^=pKrAwqK_mh zsIHJt(%jr!&&=Ba)JqmDKQ7jpt%in%YG%IG0{kzVv8ZAShr{99GLY~M!OXINTYaIP zV9}yQ_GB`-1;F_d@Vky2IdXfk-hOm+RF^mur2>C4Bn%1wG6H57YV^9gx*q}XNSfZM zs;c@@Z*Om+Sa06j+nX>?NS?51!_0hh3Bf;!1OShwTsy+d-xr2q$X31X0(m^^0enr; z^n07lcI}QGJ3cD*n}@^UhXGtfL^m3Su`m*eY{t|GYnpba5aJ4EZV^H(=<4cvoje|o zqA1F704J>nhK7bt@9F6|nupO{%*?MzVmzZl2!ALPdIg^dL^K-J0mR3-L6w!~>w^UVa{sj6I0*L4hN!z7)`rl6hknN9F0eIf$ z^DX%V{{P<-eqGXbc|JGp=;#=!uC5L+bC-++B6`v1^R4)V004@j$hIFp*Vx!NE1#RU zx3?d!sHj*;M6#2Ib|Tv7^?HLdo`BBI&O-n`NC&98y4suX03972e>i^p_!0{es)P_* zXEF(?UP`AVBvBAxdU|?}R#jE`0YofFc){=Yubj~YFmqVqN(KrwP7gG84M3W`e4!%Vwet05flt^!ak3(*w)21_{nlX_3AbHsqg_ctb0L!QkwvBB4|^p|P=Xmf!DR>GS!nDOFbXxIxZ1H9 zkw|>KSPyZ61ZIB4!X|7fHVIy@_mb-B>i=futwhw--QB${7z|dHoPf5rwoxK_Lelr! z#XLk8jYf|plgVXML4wccTOova1;9BemzV3hUUU+q(=3@xK8Er6`g0UT`D!sACK`<% zv)ODVoe8R{-c3YX0MtlSZ!nQaY%L}Ul;Odi!PvoxREd4`(MJo5xsMBs6~izLV>{$~ z$$6j61;&yE>g((OotY=2T3@o;?W-npD7_#v!1R0`!1(-3SABi`H&J8>g+hZuh-(47 zY_VeAnoroGxw*N%uCA_glHdct(qhd7kpYCa0JKY{=RrH z4Gs=2w}>6tiRd}2?M;uzqo}I-7=TAC!2cZpZ+fzpS=n|q#z%-jL|55twjCair#9br zo3LP0EEYTJbUJH@C_TaTGyVPjAH-s@|0)Fu2M-?99S+AfA;gsc&XvTDRydu`-grE| z|HQ@XuDk9u!!Ujh;8u&^Gg*siAJNx1_uYNqNr6w^n!Wf5?-(O+@YbN*C0O#0sz}} zU0)Oqhu_OIrdfFJM%hdNuyk~EG?dRwm~_!TGnYqpWc1a#y1HxH+S-z(BEro1?|{54 z{V;&_M6{lnKVzX*pBNYzxGkIYjL+v=O+?RFXpNlL36m~c8H>fD-~A zAw3?Cmn_?@udi=(?%cVX?RNW>037M|2|y5l%vCm+`Cd)a?m2YmP*$ga;_>)fPN(x- znK}Y+o?#f5FGH-G&Hmn@;zpm3^MZ#kx1mBT*r(= zBAW{BqGm-vG#Wi(mZD6bx;7!ibAdqMbEPIB!ya?0+t??BxMCt#b+RN(WD^2`Kuy6_ zP>L%0AD7D|BypMwX7)N9j$ick^&Kx23Gcr9Zes4-x!de^`&ANk1ORU^vtQG+f|cEg z#bUp9yWRT%tdMy}%sgrDC?`L@sj2CH0NW)^3l&A#X&%BZwK@t)lE0o8LR{6=)%BY~ zj~Nb!H<~r@@=`}R5zyM&YM8$DJF-9{fbH2#!$OfzRaNCDqWb}~5z)7rnwoAX#+lI0ZA+Vivf4TBg#45ROnNbfBO!ASGD#HpZ%l z%VHu8BO@bGkH<4-x)WelSCnZ37=}RwS3zkplf6QS>&*pFx_n+ylx|g3KRcZX7#$t` zA>?&0+A@`OE(ihUOz0w_n=D9h1K2ZN4`i4tx0sKH9un=RLRu$>t5W_H*?Ao;}UQ{D&5i`2jj2WF} z(d9KFS~F1)tlT9S44$4yB>oeC>@efkG)?=-WbRY&c)q4-Z<>p={T5v=XXe-ae*cn@)lLydJ>A7{9N3l9(an2Z`wEV#af_c<#FaJX8q1Kcxhi{&S6C7~jKK?YM~| zhJOI?EP($oJ*=r%Y+Y&oxJA0&|JX2$MO|H8IfuGwD!-~FOP0*jb^Y%!mi{r}voP~7 ziRf7&#HLIEt^$MqX|t5W+3Al%L^tH}b1r2=K*~#AYKD;~Tg1Zvum`|S!!SZ;o_S_( zF_HpWT3T!a0|Ox<@=A>EKZV2Lzs}pRl-bgL5i{S*%&W3oBn|-BLqxAL^J}`U_vTvu zG8hcb9vK-~Ohi8Op!x+8@OKXk40sBWC0wTC^^2p?=)r=xR^`0tX=rGuuC1-TiJAWlz!%Hl zc?|$d3t3{btO-bS105L4Mz{&U;;DEBX1*^Hi4?S`_$fm`nyom;FpMC^60lTQ|JG$8 zL}%vRMD)|9rlzON{qUJhK&qHWjV>|_<1!({WdIg4^L*r9{dW|=TSWAxVHjFfRaIv( z_q9)X0%X$K+A8+$-RsbGeLlu=h0erSG1~Nkw4==Yp%CH_5&e#c`VSvIysy}2;8NxP Y02kY1x2LbMmH+?%07*qoM6N<$f>%3m%m4rY diff --git a/app/src/main/res/drawable-xhdpi/info.png b/app/src/main/res/drawable-xhdpi/info.png deleted file mode 100644 index 22ba01c965bd04d55c1ff228ebd44deaff8ff552..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2935 zcmY*bcUaTO7X1Z5Dbjm2QY0WDfI)hI5I{<3A_9sD-H0JEKYs~(0Yve*D% z7^crk@xH;H;4u8$&vn+$Nd97QFi{@{3k?lb4?UqyAo;^I_4M>$8d@+dEhvit4GIqo_6&mt z1}Xe4@*f?PZ;&?$M-0Xh0>S&bo?e8IU?X|?eWQQJ-*pD#{Qm0{81zqDtOjBGCooNQ z4cNchtgD9msJ=Z3=gV^5*GFg?{$l!2&j>{UzDZjs4IJ-$dTWuv_~5yy?^NW?C0=p6)h)F?W?{;WDlMbKH^mEPFl)~9fhy)R<2CLdruwqLKI@~ z63-F2y&*V=vw`H{a`@WD($dn8s&kEYN-8=hlj5EeeTm9+zeC)La;1t?6mmT6)pWHY zT}v!U*)IP@N=nME%ye7+LpIa-8I3gYB@AKdc4sKGn5)`XZQ~O-QE@gs>;2Ob&&4DSAo9s<3^u{h9$%Boxxjaw5zOreiRmd-kjE5kI zK0cNF)BvPSs>O!8{n#$WGqpK)ci|Itm5;Nv#>LZMaOptC)7|a$8r5zAPI;qceoq=zCg?_Z;SJTB z8Abfe<|}5-bct2HXYj(bAuE9zSQ7m6^kGDsaHn&^YRmfkyVLKvIhAGSTCyu_*;zDOx%luPF>?K>>Y;Txycls z*#j?>IUqHL5nk^qn)>>uW9*M}843L8wIbb$Ip@07GJf1U)84cv10o&HwkVprDp#Ra zl--Z{IY78Op%o*$Q_aZ2-ESNp8yjnVS#?dk6T>zoKE|Fh_l^^rBHZQmNuMluy&yk_ z5w)@OsbnKpPPtfcTLCOvjXOEEFi-%x;ze5duDy6?EaKH`?Sg)>#*P!J~tSPm03|u)PBqfa! z(=&#eC6R0&y(oUOZI(`2*#x;!1{5c^Mx7q=7LDcSAgg*%Y5MBv7q(8i6YHIyE>mPf z&KKG`!(L(oADObhRMK|m?fJd#wVKEg`tU))*e69U6-QrJI#|>#RGm8z>sq1x?8aL9 z2_sPoLl9I@Yt%1T_#}$X>^jhTiOv*q;+Hlc1IgjVx`Gfn~rCP*Eq%HeH6nQ=4 zkFDd}nrjt2kEvAZ70D^MiS5!8-T8koKIexk>&}2@T6v%Q5irB26w3`|KeQ!y z7OG>mfN3euho5?EL`yi8#axvU?!V`(y}1{2tIWa0&8ePe{E37|)!ay0FW)<0-btr8QwqLmA#@HSsUD!=zX;5VS8JTYX`osv%sZGWJ;i3-e! z7`M9IIW35lUDi!phh4vP5aptfdRuqO2R?;mXJ+_IemtBcBbugEOUI5{48Ym8bd~)M zja}@IgHS35_pbdIcf|xNd}(5!UvOVvBTr*Ei2Ml>g?I+=i3*I7Om@G z{_zkMuN2zToF3b_x|!|wjMIz4b$r=DCY@40k!N=+%^dn9u17!4oHkeCsgQz-m1#U| z^5jcGb`0I!rd%379U1-dd`P_ws8cq^1tPV174l8kTXe;@!~y_CeX7W7ZYktrdnS%L z$~f~D)4nqYer}^A%j47Fg%4r$#H{hLU&-xhK2#dID*?-ZTO424tI#$L;bV7{u%5Uv zJB<_hja_YNwYA|zq01i%CTIC*y1NSHWS3G~iwB3jJw&*geOo0G5b_Ii4bRWyD`fFw zGI<}Eh;RX$kfFvt>vQ0o2HFVlovTtK{WuS=eCs3H{f)!AV=X2RSE>#IbhFSXgX(!G zhOMx(4l#tjg{we5SPB{58k^;r-}3NvD@tkLD|g6b=xCi%vuIiUtVt*0`Rb3_bDxs5F_K`h&zwf&wz#;>sCEex2Y&)4sC;3?PNFXsND4D zmhHPSQH}WnXE=sbva>YA>&?i@{_2ZQxndK8w~Ydnji)!z$IeFKtKW8)p#w-i>W+b% zP1yzmNKJkNgRTL!gM1>l$BnFRe;cH;5oLPoQb&4PJGg_jIw#kF03`WlvtVgCkDLpMOEEkkg>cQgfR46{ofn(spYjqL0eh{Rxtb Wl%on*bENKnZ>`MjP}NB6)xQAKV>qt> diff --git a/app/src/main/res/drawable-xhdpi/linphone_app_name_logo.png b/app/src/main/res/drawable-xhdpi/linphone_app_name_logo.png deleted file mode 100644 index bfef888d2c90e62ea31a1e319cbc8b95f34362c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8422 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 diff --git a/app/src/main/res/drawable-xhdpi/linphone_logo.png b/app/src/main/res/drawable-xhdpi/linphone_logo.png deleted file mode 100644 index 533a934505fb98865512a50b233314d9ca12985b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15831 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 Mr>mdKI;Vst0M-YJH~;_u diff --git a/app/src/main/res/drawable-xhdpi/menu_about_default.png b/app/src/main/res/drawable-xhdpi/menu_about_default.png deleted file mode 100644 index 37b24bbaa96e8022ce14d3d6ae5365976abb07e8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3435 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 diff --git a/app/src/main/res/drawable-xhdpi/menu_contact.png b/app/src/main/res/drawable-xhdpi/menu_contact.png deleted file mode 100644 index 8bbe253df5f5639e6007a97998efc66eb850cddc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1606 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 diff --git a/app/src/main/res/drawable-xhdpi/menu_delete_default.png b/app/src/main/res/drawable-xhdpi/menu_delete_default.png deleted file mode 100644 index 65d612aae0e598aa867f64a31184b487d2456a73..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9869 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 Sx_thHqpfbBR(ah%;(q}1FC4`H diff --git a/app/src/main/res/drawable-xhdpi/menu_ephemeral_messages_default.png b/app/src/main/res/drawable-xhdpi/menu_ephemeral_messages_default.png deleted file mode 100644 index bbe180afd4d5bd54f0ed590f9b81f05c290de803..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11727 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 C!mn)6w6KI_?Y>^)-)^fbtbZxRCl05Yhini1w- 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 NP<1`EN|n12{|`5nfz$v1 diff --git a/app/src/main/res/drawable-xhdpi/menu_group_info_default.png b/app/src/main/res/drawable-xhdpi/menu_group_info_default.png deleted file mode 100644 index 249d74a56d218fc04d8437e4bab216b773d1bfd9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12813 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 CA;NV4 diff --git a/app/src/main/res/drawable-xhdpi/menu_meeting_schedule.png b/app/src/main/res/drawable-xhdpi/menu_meeting_schedule.png deleted file mode 100644 index 64ba077925ab5a720eeca6388f710449e63ceec3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13214 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 E15Z#uYybcN diff --git a/app/src/main/res/drawable-xhdpi/menu_security_default.png b/app/src/main/res/drawable-xhdpi/menu_security_default.png deleted file mode 100644 index 477de590a694066a29c1cca44eb2c08d47d02723..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14046 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 diff --git a/app/src/main/res/drawable-xhdpi/next_default.png b/app/src/main/res/drawable-xhdpi/next_default.png deleted file mode 100644 index 3cea216fb5ec63c04a2e09c4214a3182cc002518..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8512 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%0D&) zJUWlJZm>dI`o?PX><|;^_uKD-NF>e7XvJAe7;JIq_3L=%YAPnun*B@$AEWgMgSmuZ z^~RL!C;9j#}iXCN1~6>rTS1{{11vL)uKDfPZ44m0kE zIL(DePWp8fJ?+jIu^b}0OT4Wl4fW!VB_vPU66j)Q8yd#~|Ail4sw0+QzGS)|-_QW( zY1fWVBNu;Fk+rqJQ+IS~eGn(j^=|BnR_uZiUGx4at3g&;SXe}#s{nJtix`i?7DNEN zj;RJva{D!a^Ax^&Z;bs1_)rnkBPl-KT}fHF)7ZCTy{WlwX75!sB8S3{2x#${Xe{}L z<|@T#omQMj1k5(4nMb_?%~N+aCK}xk4}2B5hv>`a$YgSfEWX?L z2v3yP=L6up2s_I(kcw?Ufu<{>O*?cDSUzK)s5bkJ@Yp7gu-ISKcWF5 z*V|A}DM^*;w!CUuVZLkfLGR z&+H;&4cJ9WoI+n|=m`FVLlJJ|jlT$@iHXE^$Bap_SG@hal9r|#h^km6^|M_1n5!<} zJmx~nr#=(M6f&YBjQ88XsG9E;nwKO40!_$m}`XOtMCwLF|uXq z!O!8>N2)hoGPwAGCupN#5WdNfQ0djGk>PW6ZM>G?jyWCfwJ$Sp^R6>pE&r*nA3_Bp zliXjvSYo*}>uf)UL)F}IKi;U&WnG{A@HWZ*ElGKnFtLoY_i;9(uEE%3Q1ei^;IRc{ zJu>Ut`!_FS(&nP9*==|=E7cUvMw^5dOo2W-0Pk_H@|Iw@Oa7oJj8XASZtp^7$bExP zqxW>nhEHm1YY$h4jh72#h^5lP(bk;e!DbWe2~nE?NbuayQt$673kd4LwK^zuRTd$F z5+xB~=r4x?p?hulX)!BL9HlKhNJKsHSa<4Ba~i$Nu?uZ$3q0H)kN%=C*ie=gxXB8k z+a|(V3xq!0!~Hg3MWkZ?-Mn$fA#3SFzNg;!qCw!*Xg&1UR}_khyO{;IsaUtL{2%9J{|K~@ptsc(5a-fGG$h*t18 z?6<_k#J(PVZv9W*u1SI*sC385O^uYM|&-O>9jyp$o(}C#xUiJiUXfLR|jEEP=RgZ3$|X2%{CGt92=xx&~a} zb{OW&h9Dt;i9gz|Fr|)3__J5}Z45@3SN_q|85{hwAmQ1O-HAZ* z_<}V(+P~>fUlV$W$>iw10i2be>Ivdv=kg8;OP-4u?wQw#OeuYWLPlIWo-!6wA|0N# z=~L-kgULUEnymr{T(Ga7ha@lPot#!&5em1PEte(^>Pl+(@OFrXCwSl5`g*6%8I3cc z-G%4b(9@6}TMdv|sS9;5ABqhC6?{(r5DF}6^_}oCFMHbFp58ILUdNQo&HAQJykQ+Y zc3~Gh$bI|W9O0{A3Irr)y diff --git a/app/src/main/res/drawable-xhdpi/numpad_3.png b/app/src/main/res/drawable-xhdpi/numpad_3.png deleted file mode 100644 index 0c9976674bbde23c4a43be89f81669cd183883d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2450 zcmbVO2Un9>6MiWMM2fhONLw^2u#zZ*Ds_Va+(i*EL2yO7w1gf|npq-Cf)o)%>KZ@= z!cuf861pr9I*5@HA`yf@0D~Y^fqnP;3E#bEX3m{+=AM~3&olQ@?QE^3Bork80Fe6S z62<|vxu850hk$z3D>MW&5Dyz`3~=z@rZtyjfS$v_mz=}Eh_U}f$U-CU4(LPufRDlA!u{zlF27B*!leUV=4orLwKL_D*xF=)4X(mF7CrlnE!yvK*j zN>dkJ!k)IO_Ruvs1%kN!`3Bzp_Kzfr#<}}v00x6`Y-MFd2?#Isw>~Ob0o=fxK1{R3 zWprg{twp=y&QqCZfG~LIPvY@5S>6LXEe8&Tc6SQo30&Iq(ZzB+yG5l-9m;B| zoDhS$7Q>gDgGdDS=mr!8*s+Et>h|jK`qAcOM=>>Zb=N~QB4bwxw=sd&Z%jo0HfLpR zZ3FNb;A!V;llH?Ig97**EuBPEL*~eb)sjqr?uz?!@d{ODZNr2%5J7H%$Z@q@`2+`X3pe zQzR8F9}V2Dr}OOrC3t@Xd5Hm=aS-V^j&(JAsw!4(;2)V*VxWNK zG7vlz3M*p3uWFl0b4L>_+>7j!hDMcY^)%tG%ZrPPAuf?c>H7I%B6>yii*!5~rx4z5osPluI(o`yy`URAdI{ZXvp>mxDY$^jD zI@=J&^9WPPMBb3fR6W?2%=%+W`U*~PUQXiPCwujTM?<`BWZz`M^N-{=^}_JzSqG6i zU%kv8ug`Mnp5g}q8*S@NN=cnF<@gbe8jC_L2zNwdI?dZz<{x*qLXG0OiGT;}E4Xh> zbkpp;Iz7H1f$21@J{1tIr_(Pfzj-^f^k(5w$>^AtuiV7Pj>PlA;(Gz%eXl;dff)BB zi|^Hdw8HVR+#q~!_LkqNT=c0>~O8pmThtO>_`%W}_&_$g!Q>ydTMS+TmM;9-E7_Yjx-H4ip zL7{^n%yR_a8c$u0D{?|a{YPor5`yfG<9(Oqu#oXUNJST-FG{>EMK(Sn8C`q{0)bS5 ztxwakDQjrB0QI!^LQT_q%i$Ct9{+j~(6`K9Sr{I+Lv-PM9yp4)xVTXD0^Wazwj~z# zm(b0VZKSpv2hVDzJzLWyHNpp@dWjsFf1XQ6NVYJ^@J9|Tow>0^5Mt|OGI>9`;Z|d$ z;8xhq+MFrSB6ebpU`F318}7BUVuvwaKjVTlTN5d6QBhItA^EK$hNs{@K)xeOr^jpU zx&PK(utww9>FKb!-p7fUXBBTco0{VGcH0dPReX*tNS*BmUK`;r3UP%=qrhMivxYwC z?kATqvG&cGUB?6ZA!x0!(a|u!gZ;gBHVmAIxwn0M*d{}4d~$OY6h%@ zv=En_T8E2YoAT6WkH2D=H=l%$buTn(yl^OMrj#+HbLoLe?U6KiC|DljUT|=*b|t!e zt9URfXyn#4))1po>05gtzuB@;nCqZY$&leb)V>{EXy;3jW%BpGTAh3HH#lMgw_@*! zk}6(>FMcD&`qWLtr0!2^S5sLup`Q~U$EAj9A*9Vy8b#6&;9iR_HVk8 zl4v{LsNm1&5>0W;|FwwdR2Ad*<@Cr`a#&=Rcgi?mb_O2pBu$6n_8z$_ltS^~<6F&i fu&WhCYi+9fj=+(m*SHJdmkRjB(iT%`;THcd)F!5+ diff --git a/app/src/main/res/drawable-xhdpi/numpad_4.png b/app/src/main/res/drawable-xhdpi/numpad_4.png deleted file mode 100644 index ac4f7811a12167f846a66e27a787c00d20831b45..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1436 zcma)6i#yYM7@yp>M4OyK&S9fCI5=&ZI&=H=b4ycb$|WjfF3((+%fuqf%C(3xxon}w zy<8eC=Amb9(N1x!EXvp(M{bFv(V5Qq1J3vRyr1`Z-{*NRpU?MszBf;~IjQc{-U)#~ zR9&1w51D7kEC;G2!~QnJcA#|8)d_@r-@fG?5A$UiSd_CjSynN&ZE}E7aRLOQh;sq$ zPba*ce}|7eP0;LGmfR_N(&=k|u5G0Jz3b)F3}Q7pJ{?n+RIPV|q>vTRm7Qs}sc>8# zX08HqmMZhA&|juvk=gL9-!)b3_f@H9vnn2U2Q75us^I{RF>vZ4iIr#4a;4vxb(O04{a&WPX0WBHD>)mhC9ABqaA(Nh5T6YaPI%_N&+~ z4Zp4To}(o>%buVkF*vT7aYGoYLviSXjrRF-~eam#}- z+bJaFjc;%Wuu?rXXW}sJEm{h=TUa=85CM=?kO^vX$`XmhJP*oT{RA(s83@kjOIb#V ze&Ao4bVHZC`^eqE+zja;s@qkYCT54euJQ$gy4P_ee^%Fm`rWeR z^P&a@24XY&_KKF)ghHXWx*mod*821u*kGw9f!qC{{hb;VG_7A)#uqY%#QLaDDbec_ zv$M1JXACjeUgb27cj0{CENtkv3|281R2#j1C6uw%_iB%i)_5Hq(kT;cEAt$$uQQB2 zUiRoCP3+Y;Hg>ZwjpzBRIbYqUKj8fNu8sH|)JCsb`q>zOykA$B>rRl-=xJ_l&NMy3 zY!CK+f(IMWHnyTYZZxkPQAx_zIL@ZxAf8BS^_jnbp!BsBFXt&S3v3jgMd)mhD?N%X zRm18>kal1xcHz>ukdTl+Of8x1GFj_UF&!6(%ov9EVI-gISjEPk@X zUAEbExSJ;Zsy1Mi+`851;9dtqW@+nch)b}vN9@tClD{9u&h;)%2&ObS_5`e7t5ftN z+Z;kHTU6!-Rx|>09M=%&2PHEQ=OhnRr6~bFQ2Joe-$?~oQS;XPYSffJ;tf$JytBvP z&hxbcecHXtO`T7pW)+yJdTgraD|9Li?t*FxV zpDCq{7mfKQX=!OH1uP@~-NZPU;}Xe+CS=9mO=mk diff --git a/app/src/main/res/drawable-xhdpi/numpad_5.png b/app/src/main/res/drawable-xhdpi/numpad_5.png deleted file mode 100644 index 1edd6d68c0d8fcfa240f80ce33c008e4fd8ae605..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2253 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 S+D@>x7>IpIKzT<3Y7`#$$czhr9;z0@(g#V*&e3 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 diff --git a/app/src/main/res/drawable-xhdpi/numpad_hash.png b/app/src/main/res/drawable-xhdpi/numpad_hash.png deleted file mode 100644 index 48ca9dae2ff396eaf40aec27c1f961efa81f15a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 708 zcmeAS@N?(olHy`uVBq!ia0vp^vw*mtgAGVBJvn3#q}Y|gW!U_%O?XxI14-? zi-9_>gD|6$#_S59;uKF8$B>F!Z|`o*5(yM(cvw7LKsjr&Mxek!b^)c1gv9NhI<<_B z+!y_{=c`T=l-?-dqElw1AoPYOOY>WjgLZIQe=B#=X64CuAEfN(-?n?Qwe|P&ex~J0 z4+N$}{+RT)?tiFDOs2%QjO^^!CtIGcReE%3hvCeh3)a0(cdzArmi9zG%TQ^WwAh~? zBF|#8W|=FAr6n>})m;&ka$;VnXwt&w-$KpfF|LT9P>+jeb)*XK8@TygDC5eBnKF6e|>A4?i z)%4qF@k?gg(u>P`h4bdZ&EB;o_v&$GncYHy%cYLhbat#4wtIeBuy9l2ndcopu6=s{ zzAW`)TB`ECa>2sfJ2TGgiNF5!G3WW+#$CC0_H2K>>t4xg7NC29?gG0PB5-ue}! z8W@VXE-~M4e+XZ)ebuU}SJyZZ#{0xhkIUG2^YzzzTWtRLrOnHi_`GY%r@BMWYs~BR z+osN$zI001?nu7&!~U_6_SSa)IuG^r=gr0LHdLnrQw(m<*RW~t-rDt3iViPL$eK8H zlcBhv_RPx5PZPS>;!4jhoN@Y0Z->MV5uell{{3r@Pt9(a;w2UP6=a^LtDnm{r-UW| D_oOl@ diff --git a/app/src/main/res/drawable-xhdpi/numpad_star.png b/app/src/main/res/drawable-xhdpi/numpad_star.png deleted file mode 100644 index 5eda5fb98124ecbf74d380ff9c439ee9c2ca6121..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1583 zcmcIk{WsGK7}q^{&3naUX^QJ5q{z#Xd*;sgqG5!#6}9pn*)U@279Am7ifQw9nTa*7 zYa^yNWxJ&rlPRSYv6i;oYhJ2}yZ(s#Jm>TIob#OXob&wfd_GshkO2mIJM=U(Gz@@1 zI7+P)HL7&9)ZExMJE#_|Q^5gnjg24Oc(3(_dZK$S@EAe8G5G_V{OqYY8XB4c01k`H znUcLpDaM?!;{7Wo;QOF}-iEgqr$2d-at9chEv2K$hZ7EX7I}*WoTr4v zkVOeuf=424G-4d}ZEH9X9XUC(iIB!ns6s`_Y(bkI3=>urG{l%(XaJz?t6@&k?YQSr zxaU3gCB7{|o6lM!x=#X!wo*8K*t9HWl&Ugl=ezJreHkOitG`GO&L8wcjGCSP*_Df> zm=4-Gn82vL6%`e3Hs8J54wHuu`&S(RvmiQGwiFG9D`j@%;cG4K526_pzPr&D2IxP+ zyrP5=_Z1J0J5~+mb_lqr`A9ndj@H?6`VnvOYHuJEB}|sQDTNuYHHX=M>6S4f9qXK= zoq%jQjhy7hfTeg)w*wL|^q;_f{_6L-=H})Uvt;)hEFvC{_w0HWH#+DFuE@;uQy!~B zO85{RZv5C>OpuNR|1S{BRX>`SF_3#@HMfw)gc2^};});siv5M<>N!g%xKbL&Jw z6l21rA_yewUPP1*&4|nPUk9N5g4Wa|Hrg{JH1xtAz|u1fE0$&d5^~F9-`o2nl^fjg zF$W1yYHD(xdJ`)N@>jkM_@RdnAIe?zLvPT$%EuAiiYZw(ycRT8V5@V9YBT*B^O*l3 z0O$q!{e%{}ywq+{xrrTu72759{aAY_LZ~8v*R_GwmclZ|rp?Y-j--URxObVr^^tBy zHfOq^+6!`cWcQ(=1#st6sBOmFocvw>k|5D3KjOzzuw;MWJ^!FPpd zPEi}-Dz&!u4OJi!xN+aKtdI1aG{fI~XP&q6IpZ2)wuPNC*>i3Gkob!@J;i;8!ewIW z+mi)z^>0z@>+e7ICfZFv5=+^LL-Z4!>D8H56LHsi><3*3MwJ7-x)z)f|8N&FnFy`B z0F|WG2Y3qRlwX7ARp|DKWT&SlEL(bvlT|`lE>`G30$a6~t*bChQTt_c@uL^HiGO!^ za6il4PB$#kUxb=Ykvn6$W`1gdj}~~iyD#w|I#zDnwE*FJlFytJ>q0DH_qZfsgs>zc za}O>e0d%ExC}Zs|ZJ7kVA1h>vrnC)xT_2~PW%&p&+x^Fga7WG_ii25c?~{zRbemJQo3y9x96Nzo~?qe+|qfpVQH=mu&(bu)KwPUN7{QZ>g_eOVsZBZ7-o1HN|pLYPeY}_{(tB zrc6TZX^&z|v#5oyNbz2Yv$M0YdG($ms+G~VAmN;7YN3@*FQ_>~P8yeJ&f#;tyPnN= zM1*gg%D z+0;b!&79#AU|vYWhIZ^+{bf{JW(7emrNKI1rFO_QO^;cQnV}T%@6)$Zu7ki}{oAJM zczCNd^Od$@QE>id<4Lm=L!2qGVWe-%-YPw|ZQ>SFmbnevF!v*7r=qqoc?%c!O sB|ga)#g`Pdd2kfm;m?@ zaB^>EX>4U6ba`-PAZ2)IW&i+q+O3=EaU40eh5y5fF#-Ex4%UeA;F&+)K^7^JqNHxc zlUgEKs>(#(iw zZr=aF^HShzcz(?L_5RN5>BEKM>-F*ZbyMc+I`ws-*FU^nFzC*nZ`Ny(uL}kLyzX8< zo93Tydj9Y@uRrJQ5zF)C>(8+ejFq@v@W$`pg1vwH=T71I)AQYXa8|UDZ6Pu{`wfd zv+6nfdAc66AzbqeVgvb%_?_Z zj(2*;#mMpJ-{$KJfBXB>h0alIowM~lR;-lzk>S)$87TrnviFpZxPO@IoR|$TUeML7im9m|~{iDkaoS3Mr
y0K+>lyDs1ny&^oIHHU${-MR9Qdby1WzNQzu<}9KUkph0imEgA<|M%2!R0k z)xw*s z85tooJ5hF$&Tcu&k_tG*|(Ll>^JjV4$)X zMT`PkM&TDIJ)$=mMMPuwp{U?ecq$GW4TZ}n!qHSiv=dksg@b{eFw)XsSp|8tELstY zhRezQ0%1bJQ>zl?{;OB}P#7wdf|EQ>8Y+(g%Q#8Pfn{;jo1%=OJQxR6bb?`^Pz6O4 z<^T$VMreDG2qq!jinHmAH>74V8k)9*pd#1wjplN-S!>PN@I~ za%wCH4H6bb_8?h$c(^Nr_FV$*TOO1*Q0aS8bn)I)4gdX$|55W6Sg-GI-Q6cTza&crg~7mOFw_J)L8&Ri%D}-2 zGBPq?I1DQ%Cxb%E%Sq!7!uSi_+XF}TLy@p*&eTY$(NGKMAR3_f0hJPe*Y~2 zwO#$-Y@f#e;qON-{)aPAq5qxaAMyJyUH{Vcj~Mtz%Kui^zjXa02L6%qzt#2sMibhMCW9nqj}I20lsK7hESgvJ#}rosr}Q~{zaoMo=%|(>B)LV zTJ-abd<+ntDoa8-0C1RJPgBj3GO?O&WMFB=dp%??H63i&t%_8Am#uZ`;}P5O=i;=| zM+H zGU??a;z{ztMcKL$JEMAhA5Og6ayHgA?r0P*-iTEYGvF%WfUru!jY_#puwQK?#trjp zPZg9u(aC9a)7I_CPhDp2@$Jah(SDi3Oa1m$8*PUS)4?qzlU1yR6DufRBXbv=)o;Y7 zZ6@7KUC!ha2nb?7+}jK|e5**xyT5r#1}@8)6Pr@tzd`B>81H`-3E^eZ-r6PSoe~>P zDe#5N`5Txd)W3&kZS7imC4~djj~`-CV*t_$h8|aCPzMMy(1ZhNkB3oT4rD(S_GMi~ zqT=&U=NU61bY+pg^e&=9FI>8`nWZoWTBFmkP#b^w3c>}W72wi%rGEOY}}!bn*{jAsW3frOkJY5KJw|Fel$YX zmc%)mo4>K##G?R~wSp_dDpin*3XZseXFlv}^nxMjCQ3()bC_-=&1SEOcygb2Nnq7@ zJVNt}_ta2AkN@;uuyf#1Gkv6`$@y?}&*WAqW+5+Vfxw$TnBx%gOnqf|Mkslj7}>A$ z?eXtkH4*)BR;0=Q!^0)Jmt-XHb*U5-HexF=k!gr zUuM+oOdJu|@)?sfh<@%fMm(FkQX5QosgVq@&PRS;0fJvK_UG#KdaT46G_9G^o1<6_TtID NR8PxTvrOGF>_2Ny diff --git a/app/src/main/res/drawable-xhdpi/voip_conference_audio_only.png b/app/src/main/res/drawable-xhdpi/voip_conference_audio_only.png deleted file mode 100644 index fd57a3f27e6395ad6cbf7ed19ea01cc010864ba9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1260 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 diff --git a/app/src/main/res/drawable-xhdpi/voip_conference_play_big.png b/app/src/main/res/drawable-xhdpi/voip_conference_play_big.png deleted file mode 100644 index 303d05faa5f5fce96c7b0533a1dd37f07f111ba6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15790 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 y>-^N}?WcGJqKxgQ_)hkNXdEZU=Gz diff --git a/app/src/main/res/drawable-xhdpi/voip_dropdown.png b/app/src/main/res/drawable-xhdpi/voip_dropdown.png deleted file mode 100644 index d9fccac919fc541961a0f66f115f143f7ee3b9ed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1256 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 diff --git a/app/src/main/res/drawable-xhdpi/voip_edit.png b/app/src/main/res/drawable-xhdpi/voip_edit.png deleted file mode 100644 index c24930212f56d7147112a52e8075d26276d4463e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1834 zcma)6`8OMg7EWsmErU@^dA7z`3yO#x#7z@-G7l@$_hWu+7nhWEt<`TzhL>Cx$CcJJ=W_jp?{JT%2f;-Nsd z0_~&}L;q_jNaZn+8e`gnKLs4GQ>A2VJKuc%+=m|T>~VJ$a)pYY073}_Ct|wfdA5{C zHznj_8u>thya|Xc>2ih`idE$LFkT5O=XqYUd=0#jqyYO-TMt$EeUFrB-=tYoRO>&# zX>Sr@Qy;40!5!-2qu|-Pq&rFFcWxDYGY`$k1SKBkQ-+GJNLqw>k&b>Pynk#l`to|t zu4!n7n+B=wV`nv{q8rt%XFaYlR~YX8rKQKcX3TA!ev+8p!fF@_&$Y5cOW2Dm-?b6- zaekzXsIo~(*56P!J&8~C24y?cf@O`Qk%=Px+wtd}o590f3ki8raDbOC>s0S@0_nN$ zEUP^&h#hg6pZ;8{X5;nkXF2yd>p{0j{JTMXGLu1$pZIjh&z`G$fkCz+|Jjuh={Wu& z8YQ;UF3=SajIW8#HwXN%rWwvBFMqkjdf?#!g-pic&*`|m13o!QSb{&9affG%Allv* z##0`Stk|(U5e~6)C-5Acyx=8EjXVmkDMW-jS_>_T@C#qnYc-hp1ON!A!(mWo%Jjyp zm!D_2Y~S`AfkSWmz#swtL z$;_XBLiTkyZQ0~2Sw%f=Dt>4P#YaJZs?!2bPVEPp#p*8U_j4G!>pPsyWlbYtkkQf3 z$*3?~>(a%le*@1z{T~Ee`<5sTH+Occ^A)JX5(b2d=*M-);V32S$U#GBaZ#31#X;Zc zWC6~L&OTkSX1!%Y&J3WBcPgd$CvA#jojIqdreh=BhO)3Urb?VYKK6}Hwc?CYc{r6z zB@WA5EWA#C5P%OmI6gw>0t3wTn~xiU!>BRojWDO6i69FKRxT_1+s<-kq(cqcX|QX$ z{#Do-mAL7W1g8dXW1=BRxT1U9GCBsmina(O`UX+Y5bV?aipQ8}qjp7?#h56`E3-mW zu6EbEwEJ3W<$Pw9hqwFlK?2YaD5sc3;q((VHs}GMJxOPY!>?y**z6u6lrz;F^#J=P ze23-m1iV+;mk}K5csPw*X;-P_&O?*fdBQHBDJ%l9kBD{~e@os>CG;2q3I{L)()^u? zEZEL>aQkz!>LBgq1IEX+V(!0X9b>{H_0axPQxUTcXey&7IaLlco5flst2u9vm3yy# zM>p|$Nn@h9ek2D3>m2Ft8reqEsU0OLeG8m1_q7~GMLfADomzQ|{s*=3Hi6k36Q@#^ z(}S7LY8%Gj_ZX6W!YMT0f4pLR9WODcS!}}t{ROdvW;FG`Z8v)|R#sX9!8vU&RU%ue z0*?KfHw!f-bGTCvxU{EE+Z(79I16G@mMk2}_#JYFRsG|J+L(5>5n4q9tx_?N0_TKQ zi7D1L(tu1my@;4YKeG6VOY-r{qNL>i1x}$GhWvl{D4>2TpaIuJdNUODe3&X_RWc}h zd+#^xE5v_+63b;ocMtG}qX|se5$2QkpBMTZ1kfq!>@t%ari`c}so@=h)a3tVYeqZb z8YAd{t_Ih_Mh#_rJ$693Ezw(uoeuXUoUKJG{2V8;+M076a3_s}2K+9{aY|dDnh~0# zbm>wEI5f+(|Ha6pxyI^JV{Y5ZD&-4yWC`eRqn(lqsm+s9Qj5#o4qJAp0%w_6$9EHb zhS;OUFZxb}Fz32tDq_Kd>p?Ef>S@u?alfJ`F}9#ip0cRcZ;f1?@152Cv}WYFh#97O zD(6Ui4_)-)zUUfS9O*Zyzxlp?ygzrbvCHwy_5<%MB06NSO!T}?AuIEhxJ83=&-!V+P)sk>wXH-8Y@IXfM~3rq30=Ibz?t%B;5d1-zVUN4h^< zR(@-DlBG(;+OOvz^nEu22*#F3)n3IK6WNJh&2Hc#CIUQ=(meYw!!!*S6IegyhWQD~3m_^^if=+pVj%^~&m=k2re+C#Rk_WkVZYn>J^+ pr1wt5N0P4+%z=O70bPs~b)E7p*EO%93Z+snYPdB5R%`hv?k|ZMP4oZ& diff --git a/app/src/main/res/drawable-xhdpi/voip_export.png b/app/src/main/res/drawable-xhdpi/voip_export.png deleted file mode 100644 index 3fdfa078aff91f4aa957b48684a1b9119377244d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2383 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 A2mk;8 diff --git a/app/src/main/res/drawable-xhdpi/voip_info.png b/app/src/main/res/drawable-xhdpi/voip_info.png deleted file mode 100644 index ae8dd86f8a3121f7b4f80822360cde78c31c62a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13635 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 diff --git a/app/src/main/res/drawable-xhdpi/voip_meeting_schedule.png b/app/src/main/res/drawable-xhdpi/voip_meeting_schedule.png deleted file mode 100644 index 168e3460f62d0f9f3d272d0b44666c2cd0bbae6b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3841 zcmV+c5B~6pP)EX>4Tx04R}tkv&MmKpe$i(@I4u4i*#<$xxjvh>AFB6^c+H)C#RSm|XfHG-*gu zTpR`0f`cE6RR=bb;{*sk16O*>U#SDrpQP7X zTI>ku*#<7ITbi;5TxpQako$sDG_nbTD-XWwY z?(&L(%(f|D9tlu?06PJ!laf}{MZE8#17(I9>K6m9Cg5B!9RcERK=i7@{ODbpZ)-OD zLdl=X!w_4NCPO;f&u`5gMf>NjU3~6XYJ~LWTQ_a zDQ)p;69_z`O^*!Wg6*+lZx%IqJV0%0kKuKzp$l^sG!y(5oEt&nGF{R@A!sas*UYNT zb7s%3?v{-kV$f}t$La$KzC_W*@nHNMz=y`e*7`YPquu2d16kU(7;bF`$LvFDtLJ`w zup|%&IJQR}ZvhzkT}$G!no!xQjG7Ez;5|Esd7qWU~h0Pe#a1prb@|kEx!Nr(|4fm`=46J#K^y087H^b`YL0mgzSA zl$Y$8RnTs)HbD|iqbQHD z9dwmA>9!_G$&-+a!cZl%N+3jIMbf2)UI*~NwoQXptXw%s&zR|O&RNjk<<(X*VHJVL zx8jd>MiBrF2k5?jgEnuNriM<-2+QMu;eFhF0UB2 zxN`ocw1AGq98~~LGC2=~>V}xD^8N+$_5gs>7ru&^$0VU0f2-Ae^D>D!>An%&R~u3v zJro1P98NI80E(Nc^wOp60RY2O2JI=YI8jQPV)je%+Zs*1uXTXpbhj^jWwUZAhom%T zm6k_8ao;54bZTK#4+H{H0C&24k@vvz1pq}&ht73*wM%Nk>a!_Yk4Ky&&@XT$i(|qqI3<{)J2cWV2&oQsbG-#dT8vw-H zd0JHJNce(c;lerf%y`{U)O{T{KfIk=T0Dw3T;K3OJO<&Y#>P-d6fYYblON^937feUN zMi)RAqyV}Ax&XR>phNxW&MBTiphsiW-mOT;uJO3S3h^GLp?L42MYrrPfKF1T1*-ch zvH1CteZMxxiD$uiMS9jZQ zk6i)adPHOAF+kL|w1?vW97l|?5Ii^9rJ=#;)mDM&eqz{C$?WV@pr_7@^t21<*-x>V z0Q#iilEp-r4C2X12j%<+&E)omr!dK?HUMrQU^9bvF+@Fq6&zR^*B8JEXnl3r_ZPI& zS2=)Kdx$|JTd1-dgr^~~S~9KeTh#DbFc?gjDC6-gE<(5ZQ*|Ln5z#m>E@+936w-aY z-|uSg+Q~z|mP^%)kH~v9n)>6MgzL?{qJ2eAB|D%w=Pc+il=yWZ z%w*uG1XbRXlCG?ac(-&~&@MIn8-VX_{;2au1l*d=M4jvX{^F9D<0b|L(QJJTc1cO& z+PVxouL2gAPrC)+WfsVB2K}zo5gVHBY^deIV8c?)cNfQ$lL3Cxa=5n;{G!XNo!zNG zyS>^CU@Wu@PG2&K^J_J4MN_(MMx?fS?$~kj%fgnHWYmr-_bjgmTL>8mqinMC3BsXD<|nJlxap!gn?f;)zHwq0PXQC zE@Hwgi%p%mY}rk*T%xTpqNW_A6OoC{kF7U!K#Tr}`HjNnoR=vGYiK<1;xQRj~;) z7`P&*K`YVdC}VKK;FXySuhS(pVegwTZ4yK0IOi`D}I=9cLY(z0Qz+>88^J%+yz?PQZvZ^7k( z%~=g&31!Qz>*w@pt$H^^&r)s>h90Iu&qQV2pmdTG%dE?kIjW++wuO$L)jA(2f23m+UW={kY@`3Sz9p6n8Vh#K3(Knib zCgX-ojQ$ZUOm_`Ty@o|J58l$ax*u>4z=<&zv>ujxM>%*yGhk z5HP|}^lfp}u__^Gn@xWd#% z`Tf14v7&bX{J>IgH^47~>2narvZE(N#Fz=gZtTo>vDDFcVM|LSvMtkmmB-kaHW*hP zFgR4J`O2+zO##cIU~rdfeq;i4`2x&YLOlVx{J_tWon3Sj#ezh*VCR9a6oon(^31E*~6q`OsLOF`SS^t8C1RTVCJB&L512HgetO=_RX472QOLzVrj9*(!+6&R`eJ*J& zH;|AgGF=UwEQbfA7Yqj5yv==Hfm0`S2J|6w;;TFhQryCXiLGdOdu2C3 z_bQOpseazN(}1?v*lQv2GZ01qu($IPdjPz|M31GiLqewqZImfEX>4Tx04R}tkv&MmKpe$iQ%gmv4t6NwkfAzR@e6U(DionYs1;guFuC+YXws0h zxHt-~1qVMCs}3&Cx;nTDg5U>;qmz@Oi!ES_(b9vW|$S?b>gYb ziotoGILwN&N_z1bM0hc>KV!Z24YJ`L;z<1l>oV$G+_|{000SaNLh0L z01ejw01ejxLMWSf00007bV*G`2j&GB4lx1hK)4YA00aO@L_t(o!|j+&Ok7nI$A9O} zP$+3B_>ma2%A%#l*oBb*w!i{Rnkq4EFj`|9cOncD7sfIVj7igZL#i8GNdzL%<0Yfb0i~fmG(!Bye1j=bGK%sac@v z`f#bJ9t3V=aT-P06$v|^PJsf>{=31v2V|3+eno6~B-GF^0Vk9W@;2}QOVj&mQm)MUti}ZlljGEzX9byT9S~9C{Hzqo$n05=;hUIKrJwl1mtf~p3GSH`^Nx2fbV(9<>BU5%#7dD8d0Dfal8h|x+LPcU?iAEZOmKCk&zbu(=U~A<|a@X~jvn{d?z@Qhu zy0GS4S4*uf3RL+_bE@Az_89PRqFi6KmI+`4cqbamdgHYNRg-gqsu~)7K8+WtN)vGB z0$*}kL~Fp;)fB3;Ot%UXw>wnj&>|oJn^omqH2&a>H>wG`MJjv%WF)!A<0#uBVdsPd zf}spVT7hj#Jg_#g@{mz68-cAJPJW9T-CzVQ1lD)}tpy4U=*DG;##{rUo(rv_24Qd+ z(qqt|bOYlaIO8Ba#;6$rPIv&F6qTVXJ!H_`g7D!I0`gKKAW!-L`nuXj3QF!BO!U?1 zi-epBFw<9ZmhLWOS@++5*npu)yEG{22J-JI%QHMR%Xm9UZ3SDOZ41m$2X zn)TMJFU(^jvw$tTCFSfL*%rx27~7@Ttv{S=dE!lFMJxJWmh3=X*obI~g5UaV8i3E5 z35A%A`J%5J*lQOARXc8u%xsUdrnSdCs2mm1V@<9zJSQj+bPa|Ilzl+%0uB2h%09nn z>x*uEe5&&g>~#gh9^iH0rfVg0Vu2eF?zd{r+*4e7x~Ho{udM20JMeOnmzp$0n=G;o zKx!JonQHZjhYRv&h}mgSy&cH&aPs-8c9T)1FbSWYOX(ubc3rflc6SwTONkV=RsEmZ2GX|h3}A0000 z!b&#?eDVFh|G+!v&VA0AGr!z3GxwP%#z0?#f|QXI000UtP1UD2nEcO4h;D8lSzrtR z@LoBos2FIesIa~CggZF7!T^9L5s@gT*{;ac2{r8Gqa%)smb5R{r&m>D`a-H!OywM5 z%&aSP8OBAS6&shoVE%H~?(^ot(x=k!QZ|whWL>=!Z-ZvcwGOGEt*qpBcVp0-Mn9y1 zu=v2{9b$>OhlVkAX+O$iEh~O^0m{*AY~p=sm=6zvyC>!FgPY^_1g^b8pKkPsNV1GoGqy=KtFBjWM?b^ zS>jzP{jfAHSQv@T%SGpqP-Igy^p?uMw(C*L}Uqp~3twQKVF ztVju;lqS5}w^76Ru!@T&nKA6lH^+?2OMrmLGf**&0Pm(OM7BB_syBFJXDW@MHvoF6 zY36lP!O4G)r_7V-e-kA3*3wfWUIo#U(2|Ooa2f!>?Q<DHz#ILtrRLyBBy>L;`g$*iqVSWPyf%IdIeutQIMmrqp!nMde*w3 z8r?Je)J$&FKl_3qpr8;!$6{4D?E-R*-b62fdff?V9n^w*Y zw-Y#BB_ODwLkMB}u8ETvX<&X;N@V8EfN^(y^AP?vpDRpTXR2~t%tjEYm|EKLJ#Bt1 z?M?46jZ%H5y}F~wZ!@5U$zV5dYUg{^%^Nt8t&yW@dBz z#GB$PmZ(pfE_O;7wYNHI!s)?`oZ~3+>@|>UKPSU`PB=uf7`!}y{IBP^Zv`=Rd* zQ)XG*t&HEgAm+@s_GtB-Dq9}8R~~Ic@)?jKu!IIxgMxUX=G5GchNLfn1;dr_{(_b- zWnFl{H!a?e$DPYRbCe%=CP?T1^v|yq!h=C53!r_npC3bFcjK%((&tzp**&@U=?HS= z3bF%i+OhOr{Td^JED!(G_ov~etMaC(h#4Tjm13(B(ax>0iq@vp-4;;A-xY4Cj&>v; z$9K&>PQqD{9@`Mxc$f@#O=ZBZf)BeX5^lIl0`HX_9JH(2)7kTahyf2+7k$a8=m)rl zS0ZL%7g&n);faX2w@`sFJ`pmC#)tOmli*SWFwn=b`dgq{HGiUIGJUV$3P1OfAlfFB zF=ei8x>knCV~ycOtuX$Jm~!nFW{G?ka9 z-P?n{@`8)|`7(ZY)mIemF%8Xg#tYBZd4vyJ^I;#gvVeXYexxZr11uQOH`1^f5Re3^ zI2_)CN%->#t_?!hPbQh1h=1>RnY}?lG3n{;?JJ? z+QPF6O>0G7+GX57_qP~JlS`;nzz%MH&a%t#wtQMzN+*&9s>MH5jWn9zj7MA6g->RiStBH314N(_`NKGazF=umyDBP(ff6(T7Gd^^>Q{)SepT=Ue zMJ_Y3m@tH)-abVS!E zB;Yizo?OUT1hd-V*4GyYjS;b-ZlxZ`bcaSY^fy;mPx-9J%`Sg>%I#Yf=vrN}1;(l3 zC7}#Turx;NA!oS5zEi*HRvI&Q6|&3%5Ch&K!?WY@XPKxIgF!8~>GZv}P5R{s)JDdfj-;tj>0{$~TXwzvgl|z1g&Hn_TA@ED^ITtAPf(MnhMw77BEOb@NFQ)KaxaUFW+kLbBKHYA zo$~}KddarGDkvFvj+geE`X#k6-Oh{6x3U+W0l6Qo>@28RBwF_;5wu?;y3=B zv(Z%Ain&wA*s5VIaHqW1BT~=c=;$7zU9HKF0!2wF`EgA1Jzw=sv^>H`V14 zLfqS0ToywLFHD=Hua-GnH{Aft(~KBaBr;(mm}KgjqKk#+5BF<=591*Yo0@!x{jlnW z_z+o~yIE@B^|PZF?~NU4JOueIi(+sj&xjXDTk9iHqAa2)(9Q~tf4MIR#CzhC zXW6TCBCZE>d@h=WH;$a%H$OI<>Kza%J zl-&5oD0gy|q+`caqDd5Tm*kF=f-Fhfek?ATW^quK^V$r(b&8RXid?*fkMC>3Y2=b^ z(q6A(>S9u+fVJ|t8+-R{k=06UQe*7rELI`-IWw}9vHy1pX_}HMlyF%ZhzR+-phC38 zw#1M#cJLXd2VbesZehsLI4>k6Quq`L7E)G_cuVHwPm+F|IQGPVgR0mPj#wv``!hH; zH7k~w)KW?-!qVZx_%S}Go8S<1*hD2N%^B`;*c5jHM&`3Iv8@qUPgAO0m>uBU;`fXf zrOyzvqfLLr23kxT9BW734Rp`st!1T1yDc=RgtF^1$%IdWE1bSt0YPLYMP|vGCSL>) zWUc6gyO)fLlz2T7JKo95MsZO=EY6bL4-FmLD9uypq7o7x%2e&y*Y1cDnuBs=^n9zP zztx^L_tHI^$HYYiUJH}Ay)hP$VyS+8Ve}M%*V@Nx$S&i2xo67T!NWxfBQxxI=?+^u z0BioOnXA(85d{+}-SN=NzWF=vKanHL*wOUC!G@Fo8M*@uWPDGrU}?g_ea|>Kr^*+{ z`8OjsynXAxfiMT}+o%b4;#w=B`8jtD7L31|fzZAOZhT>8Vk*&*6T4(ck0ok$Z@%7? zl4_VN^Xu()Z`KXuQ@pp}fiud{82n%XukW}^AIt}l1?Ya+H~F%~<%z>EsmktQt^ zZR3d(to7#?P!SbvU!BlFuc2lGXY#rRBL~ZBXP3%El#UvX3b9mjfqByMzrEH`m~jOl zA+Y@nU3Zjf(L~41!#e7J!V~t@t@4v$cv-FI>Ls_*)Y|kfmRhTr+N4{nHxFH+^fbLh zH~xQqv8NYqfZNJ8xc3X6VHC;7*5pi!9fM^GA@e;gqH;e_|J&L7H;lNtHX$?S|Kq)P zr%vm(GGkzL&g?+s`l4tCgG9cU*njxBXSD1D-A(3uS$;9j9>zzqxCZkvs4EK%4c{IN zu0QeHA4ktb+9td|s8PZkI8iyQ-V^4Xofuj{Gbqs| zj#bB7w;7M;I?*P)AS8#&A5=uf@K)Xqec{SBK zDn=Q+{>0xMdPvlma!1ERW=*)d#3|Qieh8;$deBeHT1mP%WUt9B-nay#O!x zA!cD(Qm{2#=vF`t?*#GLPhFF=`N?&5x7HO6h_duuBE@d$1wBL==PJ54mpcfBH;RNa ze4SZaE})02f%ur5&hV?JH#TONhxh{eGExfynv?IkT3y{8`LS-GSc}!&ZZqd}Oto#I zo8)yg=9d+1em^E5H;*9zwgh#K6SVb(Z#qfAZ!z2dI`7$XSur`?4F@GG%{SOB34i&O z2&I)E%NR~4KM(jA|D_P*V*c81|FsEkL;X->WkhW4k{Ioo|5uF#VS002fU5fbAYDh< z33)vkg8(+xWnZE&VD0roTD_(Y%Erv;*||Wv7a5I6ZE0+8p8-qvtb5s4I(WrJ{gHk@ z!n2)OnB8n-&-h)=)N)#>g5)ver$-b?Z+7~Bi;kT#_9_tfQ zI(m}i-n{xp=s8%=%IN8rRE!1OtdWf2QOn4ZZsXC{)s6qm=ewMytPxKSoZz7Ut^bOQ zJ08zIJU(mc?9<4)H-zy?DiRs?A$41d^%$wO<0p-L=??)Ns zsT5x#SiGiMUh#(U(S(67pfz2Voj*I-#m^~_S0I2-YV`FBr3msMjyry^xvQWS>1>LF z^@;;xXBk%euB1`RXwskF_stEG53?3N+8g(Y?LN!2lYf_Q&5hY~wL1>Us_}esKVaRK zKa~l8*MVLIbJ9S75`dmLkY)^Tu#bi?zIr387fK0?+sSl$izBqbq4cjx0C(mbDH)P3X1DlyL+RD~$4R*i-(}NsoIzuN zGJ)a{;i*gDEC>RmbT*sW$}U#KN12#U5EomkH|2O!TO=z}*M|DDhs8_h_@C%bV#Jz*!BcsffHhd7b* z_BHXDe678~OAY}o=-~bJi0#n_P8E?Y4u1pS|NBcT!x`86eR W6E)utFgWp_YSvQIS1nhz4*Ne*KOR;9 diff --git a/app/src/main/res/drawable-xhdpi/voip_micro_off.png b/app/src/main/res/drawable-xhdpi/voip_micro_off.png deleted file mode 100644 index 57569b4f263db9b4df79e2f5678a52a471fa018e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4208 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 MxrwDQ$+6obR40OU{Npt0KBFqXsbia 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 diff --git a/app/src/main/res/drawable-xhdpi/voip_numpad_0.png b/app/src/main/res/drawable-xhdpi/voip_numpad_0.png deleted file mode 100644 index 115bdb17d03552a9694fc6c9ff8d96b224f7e15c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14607 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 sg75MpN2Ob%7 diff --git a/app/src/main/res/drawable-xhdpi/voip_numpad_1.png b/app/src/main/res/drawable-xhdpi/voip_numpad_1.png deleted file mode 100644 index 4d8b7f5cc10b9bb08008007c0da83b552f7cee02..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10598 zcmeHrby$>J*ZvF$2uK(p5&{EA4ms4&At{p52uuJ23^752ln4q)OLvGMAt4=tbV{cv zEhUJA7{9@D&U?;#{l0(R>-xU`-nr(P+0Wi<-FvNjuf6uOo(R3W>Qoer6aWB#N>fAC zfbfqaTviuJ&s);UGXMZhzK@Y9&cN1_%?;yfhjK=;;k@0DY)CJZ9RT1p`!WUPVIe~u z`U^|fd?8ruSqD(I$aAEBk0xP-^QLICeRspr1RYtn-aVW2pL?y{KOU7Q__*^<8T;^N zmX90KgjlCO@QXU*lV6xRyEpbl?sUJU|Ljz4$#{j4^+D}{-%q^tBB_@pY3u49spHmZ z*Z2LXpXt)op2Tw8k85+=goYp1{bc3|>t;G&wvnw})pq@Q2EOBOY0Xq`yVO0?@BKDk zUU2W%-NcQZ0a=j?QCH;3*$caga({$B39*KjH?@ac>2DtMN;J*lXJ^ZL+xy@(-?2Qu zYio0V-!~9%rEI3#{E0~N>dv=K|K2RJ_Y1TSB(avm@83K4Cb`S+72Zle$AK7yKK8$Y1InDrgc2Ck@x*Pjuzq*vuHr#F9R zdT&jQi&nbd8Latfbxl2(j-iA5k--$EiG_1a|{lhvuJY_Wk z+@aqT5iOfsFYl=HDS%orJXxlSAp+5MJ;Lnq38OL{dvw(IOQep*O!3z=hHR_)VqgNC zMI>oy!$$SPVazvQe-OA5%Uh`B$RmVFWRv)8Xh^KW(VdPN;+mSr4Ci&mX$cmL!&W36 zON-ac9bdjYf|@%QuNs@92WuA9XylZm>!02C-D-Z?o*xoRmUUBBxa*SG5af$M)sVy) zGv!HX^#NZ=*_yF6{1J=3j zwjS-hA5Lw0MekC$-To~}T;6=k>7od7ydlvJwX?53PEube&dy{n)+$6d#fd8eomR_*?WWax5j@xXmP@-SmQw)K0 zQ2H8suna_VRNLkr?Lf1YcHVSVBclQ!SHs6;;-v0k7#l%hu$;|ClGEdtA{MrJn{}5W)&i4K4#sSuBr-OrI}eNE-giEj=tU98f^f^ zRy4z*bTpl*NAAumi{?i^`NZhv`k~p%y-HM_r9Pmm1rzaL%vGQC7mXf+xA@stF_xB> z=pG-&9L?9nG2!#7piyf4?|N^JXuek(x|I! zIBXA=f8e3X({W`tw#91bPlvi+%_+Vi#BP2eggux9wyRXp7|cc+V@}LYHzLz&J`fee zn^~bYo%7OcqzT*SaDj8YWis84c+4!m&uYc(Sy)8zvT_obY>M=D5W`1qT>+;q15iR< zXl-`kLe2c;g|$GVH&>?UW<>n3vkmm1-!fy4%|S)YzQap6{%y$-M~r3= zR|#vlRBByAP^EzY)0=Bo1VDAB$(G>5l)SYOFpY(ePpWXq8yER8jE~^gC zCCZ&V`_L12%M>&rT0E7Q{g_mo;W{L)@}u@*#aTbh>!y~gkgJ}Q}3*JHmduBIp@+L1(|R}<(#krq^pdD3X?D4y6XQCcd%gi6Vf~3S_2Lj!CR1?X!|cVCj9fSXEZ2#9YLlI-986D^XPIU{Z>f zqSq+lcy(K4&j(xh*$>pputFS`ZBs$AF6No%QwhD>e`yln8s`rK%_y?-C~fcPk@3QlW_Un&}iRjY;0Cn;R}%D6s7pV1-{R2BShbV zCvODm-e39V92fM374M1&e{T5m3{j!1^s2^>kE&w(E_swV`$J{%vG-3m*@gVn=XzD? z8U(3{Mf*S*vZU^D&ZZ-s4~~FmW1$ffbKzA=n^=|bN>E0ckI1v^TiSKRQ!iKC%(t^s zCbA(gn)YpjHlj+8t*}!pi{%C9RFM!=HR`75T_du5PuBSX`cLg24IP!OHVKbefV&CL=ul8k? zmkik#n5j7)wZ!N~MQa6a8AAFjvGHDjAbp|=prNz*yC2(=Lu-f@67!5VHsU1kvS>Xs zrls#(uQ;E64@`D0;y|+oQntfhx^=M7QKzyM6_7uk_DV3@zR~1xznDlPAJZ?;hncWG zir4O-#YniC@B;-LSQ9NU+G~1lA*xUORSX1%maZ+dRQkO=bRTr%)KgRF4Xl@CFlEz8 zj)EC*OgJAsXfx`^(XFrfwJ?L%y4xlr>{7>?VfDQDhtXwkY)nm=6R($v0Y~jbKIOli zn$IxXYs#xRNIPxXpDdk8|5ZQ6iwTM?Y8wFEs8&ezE*mQO*nnP%$TV;cg(^qKCJRD8 zR>o{R9L(ztiiG~yH&D7R+<3_6nufh|Tw9MmGxRf)t(Y$i`^@WLzPa=?Td$yvOgn#a zNL2=u;$WaA`?`Pbl0e0qv(yIH1C>5K|lY9|X>I}#7eP+3S;x^p%LWd+yk(NW^x*B)KcT)kgr~CTT z{u-(enTnV~>U4*d3r`oMP@znl6ve5gwBONkU}_L(dgEfFx)ZHH`zqYd-DpjtDR@RASmO48^VqQ@tB?J)HHk~cQZ0{3At#{&?c zdg$t#^|1@t>;1B!UF29rp@ERRlEVbZ-q7dZ9*$NG{)ZQQw+CK0UaTH&tfVPjSp0e? z{}sDx#Z+!kZdJBbKZWf(4twPMlLZ-Pwhv5r4+P$LbHApIqqmuv6!^ zR}VV5|G-z7B1bBwjwT<;qF`w%A?NJa&K8;KT-dn$&FL7Qlf)pkr3*08R2fkWQ0UMP zIQ)hLQ3ua58B#~;NqKNmO@`624nfP6=I#47h!56kxZ+>qkg0RK$=?yc-R5(h$(CA1SOKFUokxRfD(TFMYZ9ozB9j#?a|no>9hg zM1>&P%gg3uPHjZB^xEd%!LeOlH_jZ8G9=ij$#R2<3Mdr#+u@H`kG`2*ZlID|wlr+Y%y=bfmTS$wJevH$&G(XN_%is8ZSV4i94x5^ zRVa8lN1Ej*skA)$NV{T@?0PE~k%_XIfq1|2oO|c;=jn|RWL;<4-KY9eH(ZET4q6OwpkV+z&kjv$h|w`-h2-6L;3@Qm-oa%eE>SC4vk$7LiEnX8 z-vaoDQuujT9O9+6l{)~M!0Yy~N6n8x&dyrsw)-Q{BZh^va=zZH~mcjv;Z z0T#ya%U$pbk2XIlX$rI3C|COBNveJsrIG=Uc3q=X$(a_1sC$;02weWH}!JPsX@%`56>#} z)CY{0}ioK8UAW<{baN4~2((PpX8-5|@&RcP`32z7>84LK;&NZ7e6ge!i@jgo( zYg&Q=p;k`^r;UPRnCHtNPdA8p3Y}yY5kG%&Q{jjvuEE2E^J<;mQY%;Nx2_z($n$!T zs^br}k?fm?=1A3@g7l!4J(Jj`x>jAER|1J)jT-~LX2=pFj zY=h)w#C(`xHJ0yRH#zpm^8;SE<#j|k?9v`xUf<+IKc8pOW*FvaU!V2l?K6|(BG!}1 zo8KJ()7pa$Y%?O7zkG0YY>)^ zsmj~%NITY9p4tvPxN`;B{qx0SX4)gKAJ!Ayk}j0~5J)LT((txZ(IIFym&NVs#Rde9 z;lu4KDaOMi0#w3FY~l1jL&;jEGtbWIDr&15&lFCAnO-D6v0bm;c02{xla;KTB}DuT_MW>O^wHOzGN4_ijI|*kt>d&#S+7xq-?*K z{_*&Js7N3HCR!CKt*P|}>$lL+J=MrFd?0_kzF~r;C-wvF3y9rG^9qrwMKPrxN2rwY zlyt6lF5j|>If>oUihnP4-ICtRuIEnCT7*g`EGHz9+U~X?N9L{Q=c#v3l?!|M*1<#O z^?l~YD?*pn4ik}OW6VlKPfUX^T&8%zlXHnl-yClsknfK;l?Z8#<;A)m|@~Q`$HWwn+^I63#X&W+3ro^n8LC+$+mWOVdLy= z{8P_k+|kiqitz&ld@$y@XKH^i;k^nYitzr#ROb#9?ur()MYzI{f?jAh!uu5fKvu!a z%@*#2#IeDU_9z!Q&|YIJhz*611DS~H2}gmJdN>_fZh===-b1^Xup1U`hkY~6%J z1cimrXrX^!ygba{aS3CxeyzPN>!EyW(0s;S*z8fCn{M#J_ zTnOomL=!}@gjq%YZAx`b9ld{PoKs+rLc9IeB9Q%Wk~oyz-(>xpZRaDu-T9{>1oeO6 z{+sk)zW){`Na^T6RbAoubMrJ+whgFjCZ30t}H96#>KS zB!t1@wo=kCI80hh93u7)2we<{5S6yh|Fr5H3PFH^!KB25;dZuQDTJLg0m>EzmJ$&W z151fXz(hqQghlOaA-|yzaHyIq25n1NP88bK9x3GJV*h*KoN%a;o~9f~R8aWu9zADU zoEU6WBJmtkL|9Pt z4-?Lp1xj#+K&kg%LBjhy z;{S|!L!|p3cYkaFXVmW@Hn!hk3$=y+F$vbz1Bv)O5kc>dA-IFBi#?LCzyA!Vzxq-C z(_)EBii=4h5Cn1&gvCNgN`Y-{k+xtFF*|8E!EY%EJLJEkV_ofVp0*gIl0Cswf;WT! z`t1!H&u=Pu|6SVC0eQ}oFd+zprNN>QBM}LxuoP58l3!R9Dl7~V`qN>d^IiRy$Ff5I zA1AWE1^$r+2zr0?5z-4GTM7LuUH!?~IgS5^pFh{)|8NEZ^nWM$kNEvBUH_%)KVslN z68>*={g4KDNg^3D6px*Yl1~pB;FYKI?^k z$m!V7>xhhExijOt`|49zf^tEvBAy}P(Rkm8K(*$Wv&ZnTf=_?v;0(gEDorBgOGdB4 z`aE`cFMaqlqkAti1E6N81sSEAtwj@u6FtANHKWK@QIVI&VWB10ZsTT*$A5jORa`}= zEC3KGg?z83UsHmt6-?|b( z!%sE(N+2yL8wxJ_ixrTTXpokgnwlweZ8sD^^-!5!OaZub2HuTR2wiPY&wz%Nq3RHA_i zP_egmCk-%9ZDV)i&Z4&JluY!OGfegSSEYszX>~=g`FE`NA~yKC-H+}_jkrn8mnQhU zGA$Rl=D>L+MFIjjWC-N%cZL$z6;)R5*BVS#*b28^%#IHq;;HiYO)%j9xPMYh4*cZ3 zDhFthSv+1Ro}Ha#)F>LWNcL`LD4zz}FkNmYW}~!W43r-Djs#%4cp61$FZ#Vq&@sbe zv8oqGMY>8w081o*@~flrLOZGj=yf#2M!!a)7;2-_-sW|1vzyFd`Gc#8IU!B zXw_Av3@b0ry+rxzO6&X9t8HHs7ZKi3W9_m%1J5*hr|5~V0`e2~w0K*Imed!sdnPy* z-5u@}jBToaY4f|yTa#^?oasM|6ip!k_&{cgyo<;pnH;2;Gf`%f9jYrtQdRHNvyP5D zSOf8?rR?mTqEq-RmSHswEpP6?J*R_0U|n7y{tFCbp401d@8adi?&ZB_j+UU<>9k%% zD$scXAw-C($+|%Eq#9M;ntqE1w^jtwvloYuD$}tYHTCd?xmJ~bCYVy=gF=`lXY(d+T=<66e`TF|eGhzMs(TuLHF8e-o)@y3d^+NuF zr0W+T%+$lx=reY}zPs0|0uZ9jTh5)^0;9CAGP8R0gNyzNy1er+bI|SqRkkGeY3SnO z;#=>>)hN4!v+k?O4DebX#dtq*03)3P&o%SXgjsSMWW@dA@QwD(v7#*Qf;*9URNCUG zj70PD=Dc^Mf;hc9C6!q%riQ>fHH$47FjPh%E0#Yl`(LCY6+EqfJLoyS6f>f z)J~9&6VODy#Mtc~hji$WPSu~i*Q>*D887lh5@Yw!o6$A1h__KktG?Wz7VqiFT7i}! zZ|mz#<%SqKj}ZXw?lqajnTUSz|MBC;x;Ae&RaHw%3q@OLtjME*G`dC8y~)6LEW7qj zNg=JeZC%z*Sr6(=ZbESzE(POJ%b)ue0A++!;8b6lDeh#vvt+Zo9ZdYzj~hIyax8)s ze7-FH*-`whFE`-%?%B>htj@&8;-i6$9k8_g#fwy-f_d}1C1+=6_|de-S^i=vkuDE% zAWLS%^&gK!szzo$k|}BxKLYxdb$Yg{slLZCuP0K>EUvS7hk3qpCnP;{6O$>M0_ec{ zmyGnS9Kewi)T6Z!u3HAt13_=cf3F0zsp8-fz+f4oAVq%?+nY1)Yc~)530d(A-@N_0 z<5Kg>&1p{xUKxGDws&J**98PvboP7Djy^Q#WA()0^10hitQtZB9&R~$W1k!Q)ATHuOXquO1fJwNOJ7-utPSox0VH2#X`apmaYA7fUf!wiQ zD`2EyaIZa-w`xvx5*2%W&&}UDEcX2?&+g^@*PRu`Xd&wNgS|yNfR+`hA>G%m@8v_u zjU>_*^7n&%`d=w0K6hOkNZ|*r?B4%1M>qwV8%$KVC{=F`^Xv=7~!Mxa=!Yf)+@obkOfz+f_OD7I|afr1+!XfH6g)iUP-IW)ft@>F!vXq&b2?T6`Vta(%ZQ@if zR?eT1zbB7RWWU@@$b=WN$M6f16yS|4EsmM9h#zVIrmd~5l24yLtq~=ws20(Vx_4uI zQ@j-}`ya%AH`wFU;mz(?^;evZh^^e@1p_&V$9n+cqKZ+M^a#zrTZGO(`+g5oosa${ z_;>4nMdemWbeF>AX<&BtuhScsNbiASg%ctpBMZZSk`?;sa`iHKi=IEqYTmx9TBd9h G{Qm&j{$XYS diff --git a/app/src/main/res/drawable-xhdpi/voip_numpad_2.png b/app/src/main/res/drawable-xhdpi/voip_numpad_2.png deleted file mode 100644 index 6b561c468652c2c924e5ed12acae9421e4292004..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8603 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 diff --git a/app/src/main/res/drawable-xhdpi/voip_numpad_3.png b/app/src/main/res/drawable-xhdpi/voip_numpad_3.png deleted file mode 100644 index 386715586a9396732fe466e8137399039c1141f2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8978 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 b344$yB!X(GpwgtWd7NR4%ngh5&z%1sJ(}BX diff --git a/app/src/main/res/drawable-xhdpi/voip_numpad_5.png b/app/src/main/res/drawable-xhdpi/voip_numpad_5.png deleted file mode 100644 index a18af28e5399e74ba046bbd78b3b695eb1856854..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8747 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 diff --git a/app/src/main/res/drawable-xhdpi/voip_numpad_6.png b/app/src/main/res/drawable-xhdpi/voip_numpad_6.png deleted file mode 100644 index 79279cb998a9b1ef453e4821888e2776fd979376..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10150 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 diff --git a/app/src/main/res/drawable-xhdpi/voip_numpad_7.png b/app/src/main/res/drawable-xhdpi/voip_numpad_7.png deleted file mode 100644 index c68656fd34d1e3bc4aec4ec71a512a0f845a8f30..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7606 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 diff --git a/app/src/main/res/drawable-xhdpi/voip_numpad_8.png b/app/src/main/res/drawable-xhdpi/voip_numpad_8.png deleted file mode 100644 index 8d84c96ba0ceb5bf8716c69c8f932df8cc1bc325..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10857 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 diff --git a/app/src/main/res/drawable-xhdpi/voip_numpad_9.png b/app/src/main/res/drawable-xhdpi/voip_numpad_9.png deleted file mode 100644 index af3e0e0bf371fa36f44236dc7bf40ae4662516a7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10035 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 OtD&x`PASYO{C@z>K{*=$ diff --git a/app/src/main/res/drawable-xhdpi/voip_numpad_hash.png b/app/src/main/res/drawable-xhdpi/voip_numpad_hash.png deleted file mode 100644 index 790e7d12e914b9ef6c562c4515179132ef9ac816..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12417 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 diff --git a/app/src/main/res/drawable-xhdpi/voip_numpad_star.png b/app/src/main/res/drawable-xhdpi/voip_numpad_star.png deleted file mode 100644 index 5a2649de4f42e33f68a270be9093e010319e7df7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10163 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 diff --git a/app/src/main/res/drawable-xhdpi/voip_pause.png b/app/src/main/res/drawable-xhdpi/voip_pause.png deleted file mode 100644 index e888da937c47ee8cb5d4d7ffe25a8172ffd247ed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 830 zcmeAS@N?(olHy`uVBq!ia0vp^Q$U!54M>*SFkE3^U~I{Bb`J1#c2+1T%1_J8No8Qr zm{>c}*5j~)%+dJZqgtY@)*de>EVPOg5G}l-C9=?Ig=kc%!Okz-vrJSp_4=B!4<774 zx~jQ*^E$qDO$t94A3b}v3r@d!PR%gpxD7o#U`Y1_l zx7N2ki^Q8b6q+`i(Mk53H0g!KzIP8FzmDxmFSYyr{6%=N7;}x%M;&9I*%i$dRhP{y z-V3)!1cr5z3aOW~vPbrM^|O{C+we zOP=~!EPZWE>e=c~;%vpgP8nU#benKMX_|s#OOuwD#$mUfu(=}Knz#DPKeWg0chPH& zQ8g&iS-`}*={5WR=KFV}exBsi*OXx26)*EVndQYzRw>itKkdrq9OHaY&$jN%wEaQs zX?C9Lw{CeMXt04dCT6=yD*ui;q zl9j8U@D>zs^oVIx?>)Sm`R(4n)yr<)=XX;qn%cI()BqSQY)RhkE`yrbIAjbYUuv2GW768w#WAGf*4rzFd50VX+Ac=6 zypfyu+Eqp|Bp{D-yTeX7jlFk$x-|Amr>y_B_uTD2QZFYfgRH>-4c|3vWg@d><33lM zK5_Z}#ixZb%d*#&<`t%&{$?xlb52|Jls~`zpF8onSjx*~PV}9dn_|`fKRoZ6zxEAB zR%v@Hh~{!^Tp%FQ#N?=`!P2_GA%La=P64$)B7a;v)wdvQ>c_n=)=r(vd;5FAlj9cg vai3>MR(5r01j-#R+_h~NGlpM50$*N;vsk$Psww`q5XAL#^>bP0l+XkKpZiHz diff --git a/app/src/main/res/drawable-xhdpi/voip_remote_recording.png b/app/src/main/res/drawable-xhdpi/voip_remote_recording.png deleted file mode 100644 index 3e4e940093ca57f4eb605944c60461665c6e35c5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2673 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 diff --git a/app/src/main/res/drawable-xhdpi/voip_single_contact_avatar.png b/app/src/main/res/drawable-xhdpi/voip_single_contact_avatar.png deleted file mode 100644 index e8806b0e453e7e03ffca79575c25c96e1c52e99e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1602 zcma)+`#aMM9LK-fT#`$ZOGe0%+YugHvxdxNjTFNa>tZF-+@{7P#9XI~j?Tz9XCpdi z9MmbwB&O-cY-MV)w$@c{L+dWiS%1Mf&+~bI-p})TK0kfl&*xKt3&$GjL-hdwFboX| zj?y^uhxBwbJ`=a&o-RtIe_M28b zTsj$x%5K|vm7hwXwX(gmg(O8bB-d1hJC>xT$LRIh7alBu2x1PYNL?z z4x#^DE1^hmK{bn7_>m9dqlhNmM(cG&pg-vz^n8B0Qo!h*y+1V{Kwz@=dZ}Yqe6U_> zme4xe^kY)3&9HFY*MF?VLhOc-o?^H+?8cjFDsQ=fYAyeE8(zbrBze;k-}+*CY)=fS zuTDPkB1F25+rHI$@AvVB@1DD~II9FmF%R5!mI3NILpkeoPHecCBi3eThdbZ!X&0Y- zmQwC2`GY;ZZE7n%M^3g`fL&=Qe?M7R!B{`uICOMR#KBeF=CN;>dum#SW~{hB&)#Lv zczELy-!@>{$m_iEl)!W{S$lv@-$7^zgVhin;) zc>{co;$4fX{&8}$o>9AymynON>hnebWNi+p`mma;lJYeVZ?)H;5E zvTP&M$LJXZl9X)ueA;{*GZ?|&PV9LV1>?WkQreQt;>$A%3?@uWUB>q1#CJMS0P<;1r;&IMWqKq(%^wuk#k zUesKY`+M%0^je!0S}ECY!Td@F*mfmHS;aUmMvqi_%Ac2!Q%g8^Dg0u{ zBAq_%y>pnp%n#POd{zR#*_G$h8bK5>%E`Zx9_?GL*|*I&iG{^H}?n)(J}yE=$p4g zJvktzd5=gvt(<2BB1&PltX~c}Dnc9j6TNQ?5OmwLcaOHY)XnL@tsa~-m<4?b9;}Mo zW{y!2rL-$Ce5jJ9+)Xrz9epK0Y3FHKrw4U%NZHDZimoTu$ulnzvEGkTE4tfm?GL2- zJgYh0JCQV2BqMc;2CjN`a`kw!D er4FI;ub}%EkF*cR2t7651qj822X_P#i~a>EPxU1L diff --git a/app/src/main/res/drawable-xhdpi/voip_single_contact_avatar_alt.png b/app/src/main/res/drawable-xhdpi/voip_single_contact_avatar_alt.png deleted file mode 100644 index 58a69d43bdfa356fadefe2e5e80f1e264bea354c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4848 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 Qf4`5mrmjYfnr+1Y0ME-N^Z)<= diff --git a/app/src/main/res/drawable-xhdpi/voip_speaker_on.png b/app/src/main/res/drawable-xhdpi/voip_speaker_on.png deleted file mode 100644 index 07f13c9b62dc3813fb24c677dc1838f7fe060c8e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2599 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 PzZbe%Mw+!6PBH%jae3XB diff --git a/app/src/main/res/drawable-xhdpi/voip_spinner.png b/app/src/main/res/drawable-xhdpi/voip_spinner.png deleted file mode 100644 index 8238de56bb93bfba96162300ceec55aa69e827e6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2499 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 RD!>1JE3+%66(-&Z{{rv#z>NR^ diff --git a/app/src/main/res/drawable-xxhdpi/topbar_call_notification.png b/app/src/main/res/drawable-xxhdpi/topbar_call_notification.png deleted file mode 100644 index ada072d3f55e554657743f2ba523d0acd004501a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2418 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 diff --git a/app/src/main/res/drawable-xxhdpi/topbar_call_paused_notification.png b/app/src/main/res/drawable-xxhdpi/topbar_call_paused_notification.png deleted file mode 100644 index ef886c53c0cea27712d117a092445eafc8412a27..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 896 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@Zgyv2U~I{Bb`J1#c2+1T%1_J8No8Qr zm{>c}*5j~)%+dJhqe@fx$|gJ#SYj0^F!j-jPz|Tn0PQWW5{h1mn`C+f#mKN;Z)%qB z4rLclkClyKb^O45_XO;)5~-A0I5VzjwF#&R*s}XSIUQ&f|2rs-ryFi)-hf zRTsW@DJ2GvWJ%Y-aUN$I(ANa<^12pJQ}Q%8SNS?roC))-Qzgt#+H(E zzmEupO%r)k{HU;5&*RUD(wk4s&#AWmwJ}agG5&l+FlphHqebGIH!T$M7d|c$y11tP zTx#oQvGlD`>2Iq)iMMgxI%RY{lTW$TRmZV~L9#R?ptaRvt(2yk(U$pNKDf*7_t2Xe zBWh5jvw%%>(qs1j&G+xlI=a%+TyqLbRea9xWRVxQS)+Jw|Fhfn_8O-^1>3qUefzJm zq}h4qmu$HqC~%KWZ=Ln5J?t^>7@YlgrFis-EO@Ce*7)7{*D@P}3hpoKckbQ#z3>-9 z%F5MWcnbx` z7I;J!17q+y2s1iq%&q_mvX^-Jy0Smvkm6U-%hW!9 zA!uc?n8mw`g=|}9^L=5icv1M%J%M|MMA1L<1m+!!%j>SyoKLSgzEov#?Jlv&KD?Io zN}kpyBj$g;x{TSkuD~p8VX?}|ib+Ne^~w$VLRf@66dECf5fi7%1P3tT1``uH!2uIv z;!Hs(?t!QSDHenq08t#z`r=#j=G`j(&gbfVKG}0Je=%ozp>wi!etYTUKe9#t_NzRv zwVL!~#l?7`8EoI;%ct!lWr^dE>mQ9YC8ZgTe~DWM4fmfLb? diff --git a/app/src/main/res/drawable-xxhdpi/topbar_chat_notification.png b/app/src/main/res/drawable-xxhdpi/topbar_chat_notification.png deleted file mode 100644 index c00ce5b01b873ba36e00e747062f2862c3a38a77..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2818 zcmV+d3;pzoP)EX>4Tx04R}tkv&MmKpe$iQ>8^J9qdqwLx$>PK~%(1t5Adrp;l;lcSTOiV`$UK|H-_ z)j97IM_5@_iO-4047wokBi9v|-#C{X7I`oqO^Zh6?)1GS_JiBZWmQL4pVc4OCEtg#@iSDJC+spY-s@9KS>^gNAlAYibdf4jJ_!k4BY|)Yi@6yeVjf3S?Vf%0~{Oz zqb15-_jq@(x3_=Kbo%=NH1u+g1-2oU00006VoOIv0RI600RN!9r;`8x010qNS#tmY z4c7nw4c7reD4Tcy000McNliru(e$K~#9!?VM?homCmff9Kxm zY;CDiN@-bIL6*{0EYikS0ck4LRxl9ih5-}PkR~pPwj_Q~M2QkVSl6J57!s5)Au&n> zloA^0IF&-Nb_3dCr+e)(owl=e=KlTgKDS;j?|JXJox9D1CwViOeum;F9!~qk4+krcP4*>52#u*sJNNq{6>3M>bf0UtG+Qn3>ZyaN0gXm!rL zL9K|SCQ0{6+A676)s?f7c1rq=q;YoCtD`}ZMgboN9tIWwbw%2fGiHo?fwOjG2JJYe z?R{BK*IcCC$^ct|2Y?rybG;P>NYcf?3ScELG4N|gfz7}Ud-j@l`Y&+G>`AY={RUvP z*|W*OrRM#v1Lm0b3z1HFmHCECD zN!LjFqNKk_I%DG`$$+E{lIE4-$R$lMEor`_Z2`dSm-Muxn=2zHJ%#`#aNpA;B?ad;Ne2=J9yB$+ zNm6r_ylK6p*^*id9RK5zR+^J3$-p1Cm-?jBc5h4#g&hwR*w8LXcMtjKOS)dtj)Z~s zO8P=s&h^lNx>eGv33ERrY0*#ym@Mgq1TXQ1q#GoqsX;MAK21`4!W#Z9Y3BL7yiU?X z30`W8q&a1En1s)Bp7r!dx+i$E?P&SV z?rBL&7-nL}ZGof?&${+mvK{2ozu`$b&r14gNn?o019h*YZcmu2gW0drpB;Egmg;#2Y|LYLxVf$I}0n5h7+rR!i!O zS?gv=Q+;1t>`C~JOS*~?B$B3B92Y0cb8S(s!M!K%|E#nEyNleRsyx9(Fy}g~JR4WP zUY?7Cvqg;uUbcc%e&z2L=UiJzvS`Ju1w^f!N{S=ucDxL1cFy$#%y$~_s#Pm<3j!xq37Kq#{z7&rV@EO* zb6jWUqBRqDdyh-+g{k~A(0P!sW;i8DY);6MYw*NJ-P zgH?PwfbRoG0_W{pV-s*OM7^gv)1rLoGtCHbn*!P-EdXvXTTp2))=kuYK3YVdzTdOP zbC_fSYW8SFcTux+?kMowFp3!mOpEd*oy8<&Pn=uK7{Vi220d#`XE90Hhx$ApY8c@F zrHOB851JRJ&i?84oWo{|EQ3-E;Cf(Q6>Lz};BEriim*Wi%}X5-ao~(6_?tk~KQ9SwM)*yqR}XfJ8*oIcKB*wphTF&%2omT<8hM^8>C`z%MN-uSPR*&}`7p ziHMyc9&D*6iG4A@9yHbxx7jhq>XX0=g*=1p$SmN-kYuq8(hqEM&TR*6 z;A-bwSN<1%?dd-Lp;p_=LoB=g#Q{FaB6HLpg1sgtzS_?2f9z&U8wD$=DKyvdT;xVsN`RvDPF zc9W;I05Dmy<{ibnKa?TgOLX5WuBOsZC1551i-@j(-bP{K#6N*m&bf8v0LXcYV2*gw zRdx}hfKLF^txC6^sNO3;A>D_Z8`RD=V?J;<(Y@Hqh=>z~3Te**UjyDQb|XRo@)fuH zfMcG8O#*%b+-V>R=mOe^TEv?q?IUU$^b<8IGraRkW;4-k!|A|617|${{}F;jY>5i* zYlvQami@Yf zd552RCeof?Co(eVwg7O-b?dp^QiqmG7{XDO-S>NTe|64hFOl?Z^K6xt<3<;}Ptur* zT(Yr&R@;R+UnDxgYAm0X^imbHWuxU%OV2Bx3iM8KUSlTkL*N$TI}{@7V4ftp9{#}s z-lPlIMZ|@y&+$qWFt*D1D>LY?11|x8vv(d1qzMiUAUm^H67i+)wFUbof7)WB77F*k z{sTM+JW13Gp9Oq~=pOu3qF!8mj`zy4#Re?)dJEVIYzHb-{>fGoTf{9(rC1$ zl;+JVEqJP`jLSos2Nak{9ZL&jFt&A|Z_? zVu_{Ay9}77brbzs%RfwG+JU}`dX#DalxLHgElbY+)>s`8$?6nv=pDCU-jny_e;|_F U^mgU{;Q#;t07*qoM6N<$f^~~2g#Z8m diff --git a/app/src/main/res/drawable-xxhdpi/topbar_missed_call_notification.png b/app/src/main/res/drawable-xxhdpi/topbar_missed_call_notification.png deleted file mode 100644 index c75fae05038f7dbe6c2c33f5625076ad3ebcb87a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4609 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 D5vqv< diff --git a/app/src/main/res/drawable-xxxhdpi/topbar_call_notification.png b/app/src/main/res/drawable-xxxhdpi/topbar_call_notification.png deleted file mode 100644 index 130558d24921f5b01481804d81606a3105d7d0f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2482 zcmV;j2~GBiP)EX>4Tx04R}tkv&MmKpe$iQ>8^J9qdqwLx$>PK~%(1t5Adrp;l;lcSTOiV`$UK|H-_ z)j97IM_5@_iO-4047wokBi9v|-#C{X7I`oqO^Zh6?)1GS_JiBZWmQL4pVc4OCEtg#@iSDJC+spY-s@9KS>^gNAlAYibdf4jJ_!k4BY|)Yi@6yeVjf3S?Vf%0~{Oz zqb15-_jq@(x3_=Kbo%=NH1u+g1-2oU00006VoOIv0RI600RN!9r;`8x010qNS#tmY z4c7nw4c7reD4Tcy000McNliru4 z?7fauNugp{TomY(v_w+3q`i_>n%Q3}xN{KTb3h;Om)ilW5Xu((Yk&icEPnvTR7k}K z{zJfn_{p7BB*X>(MBsJ8q>6+{;9mi}Lt|2r&;;-uz}+-2zpI9$aPU6{yh=RzeH97* z;9m^v2>|zH0P!}^9eEYCii8A= z#SQ=-114u+w~B-Wh(}XyydM~sq3sY7mXwu%%_%p%G6(y&gA?aaBGaafgN&1$VQAPr$rRZ$W5d1Nc9*{KGq1r3y zLNj}}Y@<(|fQ}TMT^ZV6F6knN&K^ng&1_p4)BidFV;z%l7Ek|GlD_Lu8ET~?JYyVC64p2`D zC@)8{QAI)uIKOceOEV+csUl$nVCFjjSrpM$6$!(DG7;FH0>;yk?NyO53@Cq10b&rC zRUiq|+kt>f9YFjb1Dn-wbjUE2cEGw97@L9JmK~0A--` znq#Hj5DCCS;9bxE-2%)#WXLL%gd06VbzTdS@CGn5Nojq1-n{}GJ;J>4d?CXTFdkU% z@zhLhK@$EKFqy6bCORhX!bteM$3D+hc^)MVAi+m&^%**fCgBbbqc`QedfO}s?*ZQq zv{?)ZiDB1z$pCQvC?TOAIHw6L3L)VO9;N#(;Di<@;jL!Zvx+3ar@vfZ?03i!N%%*s z22_QRkYq(`J#+D?Ek?p0fx{x(3?bpSv5d}0`pEg@)vZ9nAh0w;n;|4Dh-Gw6QnLF* zLf*sC#YvR*0#5*6%)zEF2@l0I`VG%2vp4UFxd@@wHQv>_|B2W}uk#GOU3sUd1rYEH z4;*gJ!tf70Nf-c@mazpTu)t$9=%c}|!#)n&5|D@g4VYFU@Q(*}dsN-sSv#^cfP`(p zg(U!g0`RiO{VvYSq3iJf{=g8m&st3$#RS~tllkO2>;jhryZ`~%o}AjZQ3n2%9{1Km z?IWkTJ9lcpW~aKmkTi7~4d9>Tk#g?Kawnm0#k`hqc^Ejcg~9(I@Gp=1_+~3OJ1$us z2;BfR2&@J^+kyn#>*4=>t>*L@4Gc+jSp$3}i}MK$+{Yphf7g@pVWT3Q?@X3u2OA{f z?WE}YNJ0M3_1G)5hgyHUmY%@6Jzn&?(${B_gA_;R`S#~$_Y0=~)PtsMA4$r{EK1rKY>yy{7_qXiqk~Cele%hhy zR68Yo#>{$0T?7n6#576QO1eQ2dzWVEvZSw?*(#Mul6|Q+cosZGO1b;qB?kx32JUwp zohm|}YP2@7QA$RKr1_FAk#t_9NxMJgFtGbL0ssI207*qoM6N<$f|TlVSO5S3 diff --git a/app/src/main/res/drawable-xxxhdpi/topbar_call_paused_notification.png b/app/src/main/res/drawable-xxxhdpi/topbar_call_paused_notification.png deleted file mode 100644 index 16c60ecdde4d7c142a80299b2914174412d55533..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 998 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7Ro>U~I{Bb`J1#c2+1T%1_J8No8Qr zm{>c}*5j~)%+dJhqe@fx$|gJ#SYj0^F!j-jPz|Tn0PQWW5{h1mn`C+f#mKN;Z)%qB z4rLclkClyKb^O45_XO;)5~-A0I5VzjwF#&R*s}XSIUQ&f|2rs-ryFi)-hf zRTsW@DJ2GvWJ%Y-aUN$I(ANa<^12pJQ}Q%8SNS?roC))-Qzgt#+H(E zzmEupO%r)k{HU;5&*RUD(wk4s&#AWmwJ}agG5&l+FlphHqebGIH!T$M7d|c$y11tP zTx#oQvGlD`>2Iq)iMMgxI%RY{lTW$TRmZV~L9#R?ptaRvt(2yk(U$pNKDf*7_t2Xe zBWh5jvw%%>(qs1j&G+xlI=a%+TyqLbRea9xWRVxQS)+Jw|Fhfn_8O-^1>3qUefzJm zq}h4qmu$HqC~%KWZ=Ln5J?t^>7@YlgrFis-EO@Ce*7)7{*D@P}3hpoKckbQ#z3>-9 z%F5MWcnbx` z7I;J!17q+y2s1iq%&q_mvX^-Jy0Smvkm6U7Sk7yB7#Nd}JzX3_DsH{Kx-lyxP{i%w ze~BwcCa$<4;j0=kQ;A<;;}-A9e(_2fli5#j-}uRO@SGNhRz!xfo6edk0+(0}HI+^% zSe#DNk}S%fSUo4^+A8xicE6jgJ)btrKESqt`458~lYc{Nm-QKD{>JME${%Qd5Q*@b z&-{)de#RZX4H1d$j2~ov7$>$fC#+q6d_T{g$4~AtJ-5D>yg9MG_Cc9E<2%N~ZO8c= z*=6hh@nz=iy%u{e)Fi^MXk+5DLlx^*Pn#5zWw`y4d&6qoG_OgH?bbX|`+j=KGcH?L zbgGzX&Q}%z2L@qUB1W4=2BsOe>_i}w9; zjQI_(MayQe&SN~xdHg=>yX$*AfAVu&pR`6T(tFd46#J85limiM3S26iHhGOo<&-Dy me(X=QJ{hK&y8fB%KPIKur`pdjv)l)!00vK2KbLh*2~7Y_a+Cf5 diff --git a/app/src/main/res/drawable-xxxhdpi/topbar_chat_notification.png b/app/src/main/res/drawable-xxxhdpi/topbar_chat_notification.png deleted file mode 100644 index 144d25d0c37a7dfc10506674aae834f21f54a61e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2835 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 diff --git a/app/src/main/res/drawable-xxxhdpi/topbar_missed_call_notification.png b/app/src/main/res/drawable-xxxhdpi/topbar_missed_call_notification.png deleted file mode 100644 index 1eb46e47b72381e9451f7cf5ebd144cc6b968d8d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4079 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 diff --git a/app/src/main/res/drawable-xxxhdpi/topbar_service_notification.png b/app/src/main/res/drawable-xxxhdpi/topbar_service_notification.png deleted file mode 100644 index bb12c773dacd39abb2c6d261d23571bcabace6fa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8636 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 O0000 diff --git a/app/src/main/res/drawable-xxxhdpi/topbar_videocall_notification.png b/app/src/main/res/drawable-xxxhdpi/topbar_videocall_notification.png deleted file mode 100644 index 23184c99e89c399901a3bf1424ec690957a520fa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1517 zcmVEX>4Tx04R}tkv&MmKpe$iQ>8^J9qdqwLx$>PK~%(1t5Adrp;l;lcSTOiV`$UK|H-_ z)j97IM_5@_iO-4047wokBi9v|-#C{X7I`oqO^Zh6?)1GS_JiBZWmQL4pVc4OCEtg#@iSDJC+spY-s@9KS>^gNAlAYibdf4jJ_!k4BY|)Yi@6yeVjf3S?Vf%0~{Oz zqb15-_jq@(x3_=Kbo%=NH1u+g1-2oU00006VoOIv0RI600RN!9r;`8x010qNS#tmY z4c7nw4c7reD4Tcy000McNlirupE-!n5{j4{R-V~jDz z7-Nhv#u#IaF{WFI>=#K(fC1pL^Ei_ez&jD~QO5*G3gAuPAzzc@yc`kh%V{grudwVc_NlyIx}fs;alFCyu~c&QwBzLN0kCzFZ3lrH+7-S901@#%@MKy{WfxX20*fQ! zSUb<}5>%7$yrk`%`+t9LM2vRg+!k<7!YDA>>GOIFs2KNvgrqBgCF$$_h&b*Z(7Ete z14pPm-LM5XB58ekXxReV9R57ub)Yx%b1(1`uuIYm3urH^CduIr;4?`#SwQ=k)nNP8 zzyV3?+EJ1$p#Ia#6S)xB2<(tF-2$rPPT+G%3oM{It^!6RJ!S#baS`yEr0tTXSwMB% zol4St3#g8>v!t~aP(Hr`%2|>&Ng9%Li3OC$2(_0te#*K33g8n-vn`;kMMA_dFbBx{ z=KOWQp}Zx@0`f^X3M>X*$yt(mfla`@7Eqpq(-E-_xCi(-=V2FHK=~qerjm5X3$f}X ze49$rs}@k5gufzUEpQ+3TdVElxg|L$XB@OzF!lojx|BP>nC5HJt;vcaxLvjV - - - - - - diff --git a/app/src/main/res/drawable/assistant_button_text_color.xml b/app/src/main/res/drawable/assistant_button_text_color.xml deleted file mode 100644 index 1797de9ce..000000000 --- a/app/src/main/res/drawable/assistant_button_text_color.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/avatar_border.xml b/app/src/main/res/drawable/avatar_border.xml deleted file mode 100644 index 045c7bfcb..000000000 --- a/app/src/main/res/drawable/avatar_border.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/back.xml b/app/src/main/res/drawable/back.xml deleted file mode 100644 index a298e171c..000000000 --- a/app/src/main/res/drawable/back.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/background_round_primary_color.xml b/app/src/main/res/drawable/background_round_primary_color.xml deleted file mode 100644 index 0907fcd1e..000000000 --- a/app/src/main/res/drawable/background_round_primary_color.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/background_round_secondary_color.xml b/app/src/main/res/drawable/background_round_secondary_color.xml deleted file mode 100644 index 4b8a04dad..000000000 --- a/app/src/main/res/drawable/background_round_secondary_color.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/backspace.xml b/app/src/main/res/drawable/backspace.xml deleted file mode 100644 index 70117222f..000000000 --- a/app/src/main/res/drawable/backspace.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/button_background.xml b/app/src/main/res/drawable/button_background.xml deleted file mode 100644 index 1b672fdfc..000000000 --- a/app/src/main/res/drawable/button_background.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/button_background_dark.xml b/app/src/main/res/drawable/button_background_dark.xml deleted file mode 100644 index 8f13c7bd7..000000000 --- a/app/src/main/res/drawable/button_background_dark.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/drawable/button_background_light.xml b/app/src/main/res/drawable/button_background_light.xml deleted file mode 100644 index c0df18fdd..000000000 --- a/app/src/main/res/drawable/button_background_light.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/drawable/button_background_reverse.xml b/app/src/main/res/drawable/button_background_reverse.xml deleted file mode 100644 index 9b7432f25..000000000 --- a/app/src/main/res/drawable/button_background_reverse.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/button_call_answer_background.xml b/app/src/main/res/drawable/button_call_answer_background.xml deleted file mode 100644 index e7f8c8abd..000000000 --- a/app/src/main/res/drawable/button_call_answer_background.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/button_call_context_menu_background.xml b/app/src/main/res/drawable/button_call_context_menu_background.xml deleted file mode 100644 index c20f7aa56..000000000 --- a/app/src/main/res/drawable/button_call_context_menu_background.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/button_call_numpad_background.xml b/app/src/main/res/drawable/button_call_numpad_background.xml deleted file mode 100644 index b30990ebf..000000000 --- a/app/src/main/res/drawable/button_call_numpad_background.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/button_call_recording_background.xml b/app/src/main/res/drawable/button_call_recording_background.xml deleted file mode 100644 index 1f3680b7c..000000000 --- a/app/src/main/res/drawable/button_call_recording_background.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/button_call_terminate_background.xml b/app/src/main/res/drawable/button_call_terminate_background.xml deleted file mode 100644 index f584585fe..000000000 --- a/app/src/main/res/drawable/button_call_terminate_background.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/button_conference_info.xml b/app/src/main/res/drawable/button_conference_info.xml deleted file mode 100644 index d60f4e6fa..000000000 --- a/app/src/main/res/drawable/button_conference_info.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/drawable/button_green_background.xml b/app/src/main/res/drawable/button_green_background.xml deleted file mode 100644 index 8d4cfbb68..000000000 --- a/app/src/main/res/drawable/button_green_background.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/button_orange_background.xml b/app/src/main/res/drawable/button_orange_background.xml deleted file mode 100644 index bff93f486..000000000 --- a/app/src/main/res/drawable/button_orange_background.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/button_round_background.xml b/app/src/main/res/drawable/button_round_background.xml deleted file mode 100644 index afc56ca3b..000000000 --- a/app/src/main/res/drawable/button_round_background.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/button_toggle_background.xml b/app/src/main/res/drawable/button_toggle_background.xml deleted file mode 100644 index c2292dae4..000000000 --- a/app/src/main/res/drawable/button_toggle_background.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/drawable/button_toggle_background_reverse.xml b/app/src/main/res/drawable/button_toggle_background_reverse.xml deleted file mode 100644 index 3ef75d708..000000000 --- a/app/src/main/res/drawable/button_toggle_background_reverse.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/drawable/call.xml b/app/src/main/res/drawable/call.xml deleted file mode 100644 index 9ed0f637e..000000000 --- a/app/src/main/res/drawable/call.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/drawable/call_alt_start.xml b/app/src/main/res/drawable/call_alt_start.xml deleted file mode 100644 index 43d9d8be7..000000000 --- a/app/src/main/res/drawable/call_alt_start.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/call_back.xml b/app/src/main/res/drawable/call_back.xml deleted file mode 100644 index 1442cb11c..000000000 --- a/app/src/main/res/drawable/call_back.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/camera_switch.xml b/app/src/main/res/drawable/camera_switch.xml deleted file mode 100644 index a79f649b7..000000000 --- a/app/src/main/res/drawable/camera_switch.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/cancel.xml b/app/src/main/res/drawable/cancel.xml deleted file mode 100644 index 67bd03f05..000000000 --- a/app/src/main/res/drawable/cancel.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/drawable/cancel_shape.xml b/app/src/main/res/drawable/cancel_shape.xml deleted file mode 100644 index fef6bb93a..000000000 --- a/app/src/main/res/drawable/cancel_shape.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/chat_bubble_incoming_full.xml b/app/src/main/res/drawable/chat_bubble_incoming_full.xml deleted file mode 100644 index a8d9a3b32..000000000 --- a/app/src/main/res/drawable/chat_bubble_incoming_full.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/chat_bubble_incoming_reactions.xml b/app/src/main/res/drawable/chat_bubble_incoming_reactions.xml deleted file mode 100644 index 31e85ee7a..000000000 --- a/app/src/main/res/drawable/chat_bubble_incoming_reactions.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/chat_bubble_incoming_split_1.xml b/app/src/main/res/drawable/chat_bubble_incoming_split_1.xml deleted file mode 100644 index d531a9917..000000000 --- a/app/src/main/res/drawable/chat_bubble_incoming_split_1.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/chat_bubble_incoming_split_2.xml b/app/src/main/res/drawable/chat_bubble_incoming_split_2.xml deleted file mode 100644 index 277b01a2f..000000000 --- a/app/src/main/res/drawable/chat_bubble_incoming_split_2.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/chat_bubble_incoming_split_3.xml b/app/src/main/res/drawable/chat_bubble_incoming_split_3.xml deleted file mode 100644 index 7bc997138..000000000 --- a/app/src/main/res/drawable/chat_bubble_incoming_split_3.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/chat_bubble_outgoing_full.xml b/app/src/main/res/drawable/chat_bubble_outgoing_full.xml deleted file mode 100644 index 2117a9a5e..000000000 --- a/app/src/main/res/drawable/chat_bubble_outgoing_full.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/chat_bubble_outgoing_reactions.xml b/app/src/main/res/drawable/chat_bubble_outgoing_reactions.xml deleted file mode 100644 index d06e116ae..000000000 --- a/app/src/main/res/drawable/chat_bubble_outgoing_reactions.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/chat_bubble_outgoing_split_1.xml b/app/src/main/res/drawable/chat_bubble_outgoing_split_1.xml deleted file mode 100644 index 42f873e03..000000000 --- a/app/src/main/res/drawable/chat_bubble_outgoing_split_1.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/chat_bubble_outgoing_split_2.xml b/app/src/main/res/drawable/chat_bubble_outgoing_split_2.xml deleted file mode 100644 index 97bd2976e..000000000 --- a/app/src/main/res/drawable/chat_bubble_outgoing_split_2.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/chat_bubble_outgoing_split_3.xml b/app/src/main/res/drawable/chat_bubble_outgoing_split_3.xml deleted file mode 100644 index e1c6f9d03..000000000 --- a/app/src/main/res/drawable/chat_bubble_outgoing_split_3.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/chat_bubble_reply_background.xml b/app/src/main/res/drawable/chat_bubble_reply_background.xml deleted file mode 100644 index 3725abfdf..000000000 --- a/app/src/main/res/drawable/chat_bubble_reply_background.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/chat_bubble_reply_file_background.xml b/app/src/main/res/drawable/chat_bubble_reply_file_background.xml deleted file mode 100644 index 5b2b91dca..000000000 --- a/app/src/main/res/drawable/chat_bubble_reply_file_background.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/drawable/chat_bubble_reply_incoming_indicator.xml b/app/src/main/res/drawable/chat_bubble_reply_incoming_indicator.xml deleted file mode 100644 index 3bb03b37b..000000000 --- a/app/src/main/res/drawable/chat_bubble_reply_incoming_indicator.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/chat_bubble_reply_outgoing_indicator.xml b/app/src/main/res/drawable/chat_bubble_reply_outgoing_indicator.xml deleted file mode 100644 index 3cd2b9b0b..000000000 --- a/app/src/main/res/drawable/chat_bubble_reply_outgoing_indicator.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/chat_file.xml b/app/src/main/res/drawable/chat_file.xml deleted file mode 100644 index 5a820aa57..000000000 --- a/app/src/main/res/drawable/chat_file.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/chat_group_add.xml b/app/src/main/res/drawable/chat_group_add.xml deleted file mode 100644 index 9ee4744df..000000000 --- a/app/src/main/res/drawable/chat_group_add.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/drawable/chat_group_new.xml b/app/src/main/res/drawable/chat_group_new.xml deleted file mode 100644 index 16fac5aa7..000000000 --- a/app/src/main/res/drawable/chat_group_new.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/chat_message_audio_record_preview_progress.xml b/app/src/main/res/drawable/chat_message_audio_record_preview_progress.xml deleted file mode 100644 index f6419f6e9..000000000 --- a/app/src/main/res/drawable/chat_message_audio_record_preview_progress.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/chat_message_audio_record_progress.xml b/app/src/main/res/drawable/chat_message_audio_record_progress.xml deleted file mode 100644 index c004038dc..000000000 --- a/app/src/main/res/drawable/chat_message_audio_record_progress.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/chat_message_voice_recording_background.xml b/app/src/main/res/drawable/chat_message_voice_recording_background.xml deleted file mode 100644 index 3725abfdf..000000000 --- a/app/src/main/res/drawable/chat_message_voice_recording_background.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/chat_new.xml b/app/src/main/res/drawable/chat_new.xml deleted file mode 100644 index fa5e61254..000000000 --- a/app/src/main/res/drawable/chat_new.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/chat_room_menu_add_contact.xml b/app/src/main/res/drawable/chat_room_menu_add_contact.xml deleted file mode 100644 index 474e025f6..000000000 --- a/app/src/main/res/drawable/chat_room_menu_add_contact.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/chat_room_menu_delete.xml b/app/src/main/res/drawable/chat_room_menu_delete.xml deleted file mode 100644 index cb9f81675..000000000 --- a/app/src/main/res/drawable/chat_room_menu_delete.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/chat_room_menu_devices.xml b/app/src/main/res/drawable/chat_room_menu_devices.xml deleted file mode 100644 index f4453ccda..000000000 --- a/app/src/main/res/drawable/chat_room_menu_devices.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/drawable/chat_room_menu_ephemeral.xml b/app/src/main/res/drawable/chat_room_menu_ephemeral.xml deleted file mode 100644 index 55433de52..000000000 --- a/app/src/main/res/drawable/chat_room_menu_ephemeral.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/drawable/chat_room_menu_group_info.xml b/app/src/main/res/drawable/chat_room_menu_group_info.xml deleted file mode 100644 index d15e39140..000000000 --- a/app/src/main/res/drawable/chat_room_menu_group_info.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/chat_room_menu_meeting.xml b/app/src/main/res/drawable/chat_room_menu_meeting.xml deleted file mode 100644 index 88f9dfd62..000000000 --- a/app/src/main/res/drawable/chat_room_menu_meeting.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/drawable/chat_room_menu_mute_notifications.xml b/app/src/main/res/drawable/chat_room_menu_mute_notifications.xml deleted file mode 100644 index 34f6f8794..000000000 --- a/app/src/main/res/drawable/chat_room_menu_mute_notifications.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/chat_room_menu_unmute_notifications.xml b/app/src/main/res/drawable/chat_room_menu_unmute_notifications.xml deleted file mode 100644 index e53b378eb..000000000 --- a/app/src/main/res/drawable/chat_room_menu_unmute_notifications.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/chat_room_menu_view_contact.xml b/app/src/main/res/drawable/chat_room_menu_view_contact.xml deleted file mode 100644 index 248584507..000000000 --- a/app/src/main/res/drawable/chat_room_menu_view_contact.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/chat_send_message.xml b/app/src/main/res/drawable/chat_send_message.xml deleted file mode 100644 index 47af131b5..000000000 --- a/app/src/main/res/drawable/chat_send_message.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/chevron_list_close.xml b/app/src/main/res/drawable/chevron_list_close.xml deleted file mode 100644 index d826e42e9..000000000 --- a/app/src/main/res/drawable/chevron_list_close.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/chevron_list_open.xml b/app/src/main/res/drawable/chevron_list_open.xml deleted file mode 100644 index 37bcf729b..000000000 --- a/app/src/main/res/drawable/chevron_list_open.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/contact.xml b/app/src/main/res/drawable/contact.xml deleted file mode 100644 index 3a33fc108..000000000 --- a/app/src/main/res/drawable/contact.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/contact_add.xml b/app/src/main/res/drawable/contact_add.xml deleted file mode 100644 index d3e79717c..000000000 --- a/app/src/main/res/drawable/contact_add.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/contacts_all.xml b/app/src/main/res/drawable/contacts_all.xml deleted file mode 100644 index b6ecf35ff..000000000 --- a/app/src/main/res/drawable/contacts_all.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/contacts_sip.xml b/app/src/main/res/drawable/contacts_sip.xml deleted file mode 100644 index 33d9524f0..000000000 --- a/app/src/main/res/drawable/contacts_sip.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/delete.xml b/app/src/main/res/drawable/delete.xml deleted file mode 100644 index a3000e1f3..000000000 --- a/app/src/main/res/drawable/delete.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/deselect_all.xml b/app/src/main/res/drawable/deselect_all.xml deleted file mode 100644 index 2011e9ade..000000000 --- a/app/src/main/res/drawable/deselect_all.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/dialog_delete_icon.xml b/app/src/main/res/drawable/dialog_delete_icon.xml deleted file mode 100644 index bf1d1d574..000000000 --- a/app/src/main/res/drawable/dialog_delete_icon.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/divider.xml b/app/src/main/res/drawable/divider.xml deleted file mode 100644 index 1ac7d6320..000000000 --- a/app/src/main/res/drawable/divider.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/edit.xml b/app/src/main/res/drawable/edit.xml deleted file mode 100644 index 180eea212..000000000 --- a/app/src/main/res/drawable/edit.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/emoji.xml b/app/src/main/res/drawable/emoji.xml deleted file mode 100644 index 3785640c2..000000000 --- a/app/src/main/res/drawable/emoji.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/ephemeral_messages.xml b/app/src/main/res/drawable/ephemeral_messages.xml deleted file mode 100644 index 3b8fb391d..000000000 --- a/app/src/main/res/drawable/ephemeral_messages.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/event_decoration_gray.xml b/app/src/main/res/drawable/event_decoration_gray.xml deleted file mode 100644 index 626663ad7..000000000 --- a/app/src/main/res/drawable/event_decoration_gray.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/event_decoration_red.xml b/app/src/main/res/drawable/event_decoration_red.xml deleted file mode 100644 index f9e4927bf..000000000 --- a/app/src/main/res/drawable/event_decoration_red.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/field_add.xml b/app/src/main/res/drawable/field_add.xml deleted file mode 100644 index d5ab69493..000000000 --- a/app/src/main/res/drawable/field_add.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/drawable/field_add_default.xml b/app/src/main/res/drawable/field_add_default.xml deleted file mode 100644 index 29cf8f588..000000000 --- a/app/src/main/res/drawable/field_add_default.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/field_add_disabled.xml b/app/src/main/res/drawable/field_add_disabled.xml deleted file mode 100644 index 4421590c7..000000000 --- a/app/src/main/res/drawable/field_add_disabled.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/field_add_over.xml b/app/src/main/res/drawable/field_add_over.xml deleted file mode 100644 index 496327588..000000000 --- a/app/src/main/res/drawable/field_add_over.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/field_add_shape.xml b/app/src/main/res/drawable/field_add_shape.xml deleted file mode 100644 index a335e3ba3..000000000 --- a/app/src/main/res/drawable/field_add_shape.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/field_clean.xml b/app/src/main/res/drawable/field_clean.xml deleted file mode 100644 index 0400c9278..000000000 --- a/app/src/main/res/drawable/field_clean.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/field_clean_default.xml b/app/src/main/res/drawable/field_clean_default.xml deleted file mode 100644 index b29f380c1..000000000 --- a/app/src/main/res/drawable/field_clean_default.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/field_clean_over.xml b/app/src/main/res/drawable/field_clean_over.xml deleted file mode 100644 index a5c707556..000000000 --- a/app/src/main/res/drawable/field_clean_over.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/field_clean_shape.xml b/app/src/main/res/drawable/field_clean_shape.xml deleted file mode 100644 index 1c9e1349b..000000000 --- a/app/src/main/res/drawable/field_clean_shape.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/field_remove.xml b/app/src/main/res/drawable/field_remove.xml deleted file mode 100644 index 3fc616dd3..000000000 --- a/app/src/main/res/drawable/field_remove.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/drawable/field_remove_default.xml b/app/src/main/res/drawable/field_remove_default.xml deleted file mode 100644 index 214b742f1..000000000 --- a/app/src/main/res/drawable/field_remove_default.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/field_remove_disabled.xml b/app/src/main/res/drawable/field_remove_disabled.xml deleted file mode 100644 index 5c582da40..000000000 --- a/app/src/main/res/drawable/field_remove_disabled.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/field_remove_over.xml b/app/src/main/res/drawable/field_remove_over.xml deleted file mode 100644 index b8a988b7d..000000000 --- a/app/src/main/res/drawable/field_remove_over.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/field_remove_shape.xml b/app/src/main/res/drawable/field_remove_shape.xml deleted file mode 100644 index 75f5c4f69..000000000 --- a/app/src/main/res/drawable/field_remove_shape.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/file.xml b/app/src/main/res/drawable/file.xml deleted file mode 100644 index f874e16f6..000000000 --- a/app/src/main/res/drawable/file.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/drawable/file_audio.xml b/app/src/main/res/drawable/file_audio.xml deleted file mode 100644 index 2adba2380..000000000 --- a/app/src/main/res/drawable/file_audio.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/drawable/file_pdf.xml b/app/src/main/res/drawable/file_pdf.xml deleted file mode 100644 index a932850e1..000000000 --- a/app/src/main/res/drawable/file_pdf.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/drawable/file_picture.xml b/app/src/main/res/drawable/file_picture.xml deleted file mode 100644 index 6f49ac200..000000000 --- a/app/src/main/res/drawable/file_picture.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/drawable/file_video.xml b/app/src/main/res/drawable/file_video.xml deleted file mode 100644 index 1b178b560..000000000 --- a/app/src/main/res/drawable/file_video.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/drawable/footer_button.xml b/app/src/main/res/drawable/footer_button.xml deleted file mode 100644 index b23178869..000000000 --- a/app/src/main/res/drawable/footer_button.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/generated_avatar_bg.xml b/app/src/main/res/drawable/generated_avatar_bg.xml deleted file mode 100644 index 87e6684b1..000000000 --- a/app/src/main/res/drawable/generated_avatar_bg.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/hidden_unread_message_count_bg.xml b/app/src/main/res/drawable/hidden_unread_message_count_bg.xml deleted file mode 100644 index 6c1145f56..000000000 --- a/app/src/main/res/drawable/hidden_unread_message_count_bg.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/history_all.xml b/app/src/main/res/drawable/history_all.xml deleted file mode 100644 index b3f33e5c1..000000000 --- a/app/src/main/res/drawable/history_all.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/history_detail_background.xml b/app/src/main/res/drawable/history_detail_background.xml deleted file mode 100644 index c1c0dbff4..000000000 --- a/app/src/main/res/drawable/history_detail_background.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/history_detail_background_pressed.xml b/app/src/main/res/drawable/history_detail_background_pressed.xml deleted file mode 100644 index 50f07b529..000000000 --- a/app/src/main/res/drawable/history_detail_background_pressed.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/history_missed.xml b/app/src/main/res/drawable/history_missed.xml deleted file mode 100644 index 4a634f2ca..000000000 --- a/app/src/main/res/drawable/history_missed.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/icon_apply.xml b/app/src/main/res/drawable/icon_apply.xml deleted file mode 100644 index 3be5be72e..000000000 --- a/app/src/main/res/drawable/icon_apply.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/icon_audio_routes.xml b/app/src/main/res/drawable/icon_audio_routes.xml deleted file mode 100644 index b694e1b51..000000000 --- a/app/src/main/res/drawable/icon_audio_routes.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/icon_bluetooth.xml b/app/src/main/res/drawable/icon_bluetooth.xml deleted file mode 100644 index 42ad75065..000000000 --- a/app/src/main/res/drawable/icon_bluetooth.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/icon_call_add.xml b/app/src/main/res/drawable/icon_call_add.xml deleted file mode 100644 index f559fbf98..000000000 --- a/app/src/main/res/drawable/icon_call_add.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/icon_call_answer.xml b/app/src/main/res/drawable/icon_call_answer.xml deleted file mode 100644 index 86a06a63b..000000000 --- a/app/src/main/res/drawable/icon_call_answer.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/icon_call_answer_video.xml b/app/src/main/res/drawable/icon_call_answer_video.xml deleted file mode 100644 index 849f6050e..000000000 --- a/app/src/main/res/drawable/icon_call_answer_video.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/icon_call_camera_switch.xml b/app/src/main/res/drawable/icon_call_camera_switch.xml deleted file mode 100644 index d8b0d6de6..000000000 --- a/app/src/main/res/drawable/icon_call_camera_switch.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/icon_call_chat.xml b/app/src/main/res/drawable/icon_call_chat.xml deleted file mode 100644 index ad97ebc4f..000000000 --- a/app/src/main/res/drawable/icon_call_chat.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/icon_call_chat_rooms.xml b/app/src/main/res/drawable/icon_call_chat_rooms.xml deleted file mode 100644 index 584556375..000000000 --- a/app/src/main/res/drawable/icon_call_chat_rooms.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/icon_call_forward.xml b/app/src/main/res/drawable/icon_call_forward.xml deleted file mode 100644 index 7caa899f1..000000000 --- a/app/src/main/res/drawable/icon_call_forward.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/icon_call_hangup.xml b/app/src/main/res/drawable/icon_call_hangup.xml deleted file mode 100644 index 35cc67534..000000000 --- a/app/src/main/res/drawable/icon_call_hangup.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/icon_call_more.xml b/app/src/main/res/drawable/icon_call_more.xml deleted file mode 100644 index 488e9e3bf..000000000 --- a/app/src/main/res/drawable/icon_call_more.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/icon_call_numpad.xml b/app/src/main/res/drawable/icon_call_numpad.xml deleted file mode 100644 index 6768b101a..000000000 --- a/app/src/main/res/drawable/icon_call_numpad.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/icon_call_participants.xml b/app/src/main/res/drawable/icon_call_participants.xml deleted file mode 100644 index 31f960464..000000000 --- a/app/src/main/res/drawable/icon_call_participants.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/icon_call_record.xml b/app/src/main/res/drawable/icon_call_record.xml deleted file mode 100644 index 2d3fdbdf9..000000000 --- a/app/src/main/res/drawable/icon_call_record.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/icon_call_screenshot.xml b/app/src/main/res/drawable/icon_call_screenshot.xml deleted file mode 100644 index 6fe24f800..000000000 --- a/app/src/main/res/drawable/icon_call_screenshot.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/icon_call_stats.xml b/app/src/main/res/drawable/icon_call_stats.xml deleted file mode 100644 index 3cf57a966..000000000 --- a/app/src/main/res/drawable/icon_call_stats.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/icon_calls_list.xml b/app/src/main/res/drawable/icon_calls_list.xml deleted file mode 100644 index 937a6ff20..000000000 --- a/app/src/main/res/drawable/icon_calls_list.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/icon_calls_list_menu.xml b/app/src/main/res/drawable/icon_calls_list_menu.xml deleted file mode 100644 index 3a4602cee..000000000 --- a/app/src/main/res/drawable/icon_calls_list_menu.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/icon_cancel.xml b/app/src/main/res/drawable/icon_cancel.xml deleted file mode 100644 index 052b79e93..000000000 --- a/app/src/main/res/drawable/icon_cancel.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/icon_cancel_alt.xml b/app/src/main/res/drawable/icon_cancel_alt.xml deleted file mode 100644 index 07d6cc058..000000000 --- a/app/src/main/res/drawable/icon_cancel_alt.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/icon_conference_layout_active_speaker.xml b/app/src/main/res/drawable/icon_conference_layout_active_speaker.xml deleted file mode 100644 index eb53c13ea..000000000 --- a/app/src/main/res/drawable/icon_conference_layout_active_speaker.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/icon_conference_layout_audio_only.xml b/app/src/main/res/drawable/icon_conference_layout_audio_only.xml deleted file mode 100644 index 3736e6500..000000000 --- a/app/src/main/res/drawable/icon_conference_layout_audio_only.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/icon_conference_layout_grid.xml b/app/src/main/res/drawable/icon_conference_layout_grid.xml deleted file mode 100644 index 1f3bbc190..000000000 --- a/app/src/main/res/drawable/icon_conference_layout_grid.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/icon_copy.xml b/app/src/main/res/drawable/icon_copy.xml deleted file mode 100644 index 278fa0562..000000000 --- a/app/src/main/res/drawable/icon_copy.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/icon_delete.xml b/app/src/main/res/drawable/icon_delete.xml deleted file mode 100644 index bd647fa54..000000000 --- a/app/src/main/res/drawable/icon_delete.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/icon_earpiece.xml b/app/src/main/res/drawable/icon_earpiece.xml deleted file mode 100644 index 69b03ecc0..000000000 --- a/app/src/main/res/drawable/icon_earpiece.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/icon_edit.xml b/app/src/main/res/drawable/icon_edit.xml deleted file mode 100644 index d9a3f921a..000000000 --- a/app/src/main/res/drawable/icon_edit.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/icon_edit_alt.xml b/app/src/main/res/drawable/icon_edit_alt.xml deleted file mode 100644 index 74da8157b..000000000 --- a/app/src/main/res/drawable/icon_edit_alt.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/icon_ephemeral_message.xml b/app/src/main/res/drawable/icon_ephemeral_message.xml deleted file mode 100644 index 299072bab..000000000 --- a/app/src/main/res/drawable/icon_ephemeral_message.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/icon_forwarded_message.xml b/app/src/main/res/drawable/icon_forwarded_message.xml deleted file mode 100644 index c210b3dce..000000000 --- a/app/src/main/res/drawable/icon_forwarded_message.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/icon_info.xml b/app/src/main/res/drawable/icon_info.xml deleted file mode 100644 index 99fd6a38f..000000000 --- a/app/src/main/res/drawable/icon_info.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/icon_info_selected.xml b/app/src/main/res/drawable/icon_info_selected.xml deleted file mode 100644 index 67b628ef2..000000000 --- a/app/src/main/res/drawable/icon_info_selected.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/icon_meeting_schedule.xml b/app/src/main/res/drawable/icon_meeting_schedule.xml deleted file mode 100644 index 9fcd02f66..000000000 --- a/app/src/main/res/drawable/icon_meeting_schedule.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/icon_menu_more.xml b/app/src/main/res/drawable/icon_menu_more.xml deleted file mode 100644 index 19c7e24b7..000000000 --- a/app/src/main/res/drawable/icon_menu_more.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/icon_merge_calls_local_conference.xml b/app/src/main/res/drawable/icon_merge_calls_local_conference.xml deleted file mode 100644 index 2fdd2e68a..000000000 --- a/app/src/main/res/drawable/icon_merge_calls_local_conference.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/icon_mic_muted.xml b/app/src/main/res/drawable/icon_mic_muted.xml deleted file mode 100644 index 1ab7fc636..000000000 --- a/app/src/main/res/drawable/icon_mic_muted.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/icon_multiple_contacts_avatar.xml b/app/src/main/res/drawable/icon_multiple_contacts_avatar.xml deleted file mode 100644 index 15fac96a6..000000000 --- a/app/src/main/res/drawable/icon_multiple_contacts_avatar.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/icon_next.xml b/app/src/main/res/drawable/icon_next.xml deleted file mode 100644 index 968192312..000000000 --- a/app/src/main/res/drawable/icon_next.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/icon_pause.xml b/app/src/main/res/drawable/icon_pause.xml deleted file mode 100644 index 09787beba..000000000 --- a/app/src/main/res/drawable/icon_pause.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/icon_replied_message.xml b/app/src/main/res/drawable/icon_replied_message.xml deleted file mode 100644 index dcc78b5c6..000000000 --- a/app/src/main/res/drawable/icon_replied_message.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/icon_schedule_date.xml b/app/src/main/res/drawable/icon_schedule_date.xml deleted file mode 100644 index 5f29ac622..000000000 --- a/app/src/main/res/drawable/icon_schedule_date.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/icon_schedule_participants.xml b/app/src/main/res/drawable/icon_schedule_participants.xml deleted file mode 100644 index 45741792a..000000000 --- a/app/src/main/res/drawable/icon_schedule_participants.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/icon_schedule_time.xml b/app/src/main/res/drawable/icon_schedule_time.xml deleted file mode 100644 index f165174c6..000000000 --- a/app/src/main/res/drawable/icon_schedule_time.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/icon_share.xml b/app/src/main/res/drawable/icon_share.xml deleted file mode 100644 index 4b7a252ee..000000000 --- a/app/src/main/res/drawable/icon_share.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - diff --git a/app/src/main/res/drawable/icon_single_contact_avatar.xml b/app/src/main/res/drawable/icon_single_contact_avatar.xml deleted file mode 100644 index 0af513c72..000000000 --- a/app/src/main/res/drawable/icon_single_contact_avatar.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/icon_speaker.xml b/app/src/main/res/drawable/icon_speaker.xml deleted file mode 100644 index ab3fd031b..000000000 --- a/app/src/main/res/drawable/icon_speaker.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/icon_spinner.xml b/app/src/main/res/drawable/icon_spinner.xml deleted file mode 100644 index f3537e7bd..000000000 --- a/app/src/main/res/drawable/icon_spinner.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/icon_spinner_background.xml b/app/src/main/res/drawable/icon_spinner_background.xml deleted file mode 100644 index f50b58b1b..000000000 --- a/app/src/main/res/drawable/icon_spinner_background.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/icon_spinner_rotating.xml b/app/src/main/res/drawable/icon_spinner_rotating.xml deleted file mode 100644 index 6b8ed48ca..000000000 --- a/app/src/main/res/drawable/icon_spinner_rotating.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/icon_toggle_camera.xml b/app/src/main/res/drawable/icon_toggle_camera.xml deleted file mode 100644 index 80a10a84f..000000000 --- a/app/src/main/res/drawable/icon_toggle_camera.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/drawable/icon_toggle_mic.xml b/app/src/main/res/drawable/icon_toggle_mic.xml deleted file mode 100644 index 40b18e3f8..000000000 --- a/app/src/main/res/drawable/icon_toggle_mic.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/drawable/icon_toggle_speaker.xml b/app/src/main/res/drawable/icon_toggle_speaker.xml deleted file mode 100644 index e9f43e1a1..000000000 --- a/app/src/main/res/drawable/icon_toggle_speaker.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/drawable/icon_video_conf_history_filter.xml b/app/src/main/res/drawable/icon_video_conf_history_filter.xml deleted file mode 100644 index 65b3aa6ed..000000000 --- a/app/src/main/res/drawable/icon_video_conf_history_filter.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/icon_video_conf_incoming.xml b/app/src/main/res/drawable/icon_video_conf_incoming.xml deleted file mode 100644 index 57bca35d7..000000000 --- a/app/src/main/res/drawable/icon_video_conf_incoming.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/icon_video_conf_new.xml b/app/src/main/res/drawable/icon_video_conf_new.xml deleted file mode 100644 index 2c1dd4e58..000000000 --- a/app/src/main/res/drawable/icon_video_conf_new.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/led_away.xml b/app/src/main/res/drawable/led_away.xml deleted file mode 100644 index 53b2810ea..000000000 --- a/app/src/main/res/drawable/led_away.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/led_background.xml b/app/src/main/res/drawable/led_background.xml deleted file mode 100644 index d46d2c20a..000000000 --- a/app/src/main/res/drawable/led_background.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/led_do_not_disturb.xml b/app/src/main/res/drawable/led_do_not_disturb.xml deleted file mode 100644 index e2ce8ac4b..000000000 --- a/app/src/main/res/drawable/led_do_not_disturb.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/led_error.xml b/app/src/main/res/drawable/led_error.xml deleted file mode 100644 index e2ce8ac4b..000000000 --- a/app/src/main/res/drawable/led_error.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/led_not_registered.xml b/app/src/main/res/drawable/led_not_registered.xml deleted file mode 100644 index f95c698ac..000000000 --- a/app/src/main/res/drawable/led_not_registered.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/led_online.xml b/app/src/main/res/drawable/led_online.xml deleted file mode 100644 index 7c701e958..000000000 --- a/app/src/main/res/drawable/led_online.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/led_registered.xml b/app/src/main/res/drawable/led_registered.xml deleted file mode 100644 index 7c701e958..000000000 --- a/app/src/main/res/drawable/led_registered.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/led_registration_in_progress.xml b/app/src/main/res/drawable/led_registration_in_progress.xml deleted file mode 100644 index 53b2810ea..000000000 --- a/app/src/main/res/drawable/led_registration_in_progress.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/linphone_logo_tinted.xml b/app/src/main/res/drawable/linphone_logo_tinted.xml deleted file mode 100644 index e04a976a4..000000000 --- a/app/src/main/res/drawable/linphone_logo_tinted.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/list_detail.xml b/app/src/main/res/drawable/list_detail.xml deleted file mode 100644 index 271ded33e..000000000 --- a/app/src/main/res/drawable/list_detail.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/menu.xml b/app/src/main/res/drawable/menu.xml deleted file mode 100644 index 035606757..000000000 --- a/app/src/main/res/drawable/menu.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/drawable/menu_about.xml b/app/src/main/res/drawable/menu_about.xml deleted file mode 100644 index 9fbb4ec59..000000000 --- a/app/src/main/res/drawable/menu_about.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/menu_assistant.xml b/app/src/main/res/drawable/menu_assistant.xml deleted file mode 100644 index 44b13bd10..000000000 --- a/app/src/main/res/drawable/menu_assistant.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/menu_background.xml b/app/src/main/res/drawable/menu_background.xml deleted file mode 100644 index 414e4d2eb..000000000 --- a/app/src/main/res/drawable/menu_background.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/menu_background_color.xml b/app/src/main/res/drawable/menu_background_color.xml deleted file mode 100644 index b0f1a4a64..000000000 --- a/app/src/main/res/drawable/menu_background_color.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/menu_copy_text.xml b/app/src/main/res/drawable/menu_copy_text.xml deleted file mode 100644 index 1e3a90666..000000000 --- a/app/src/main/res/drawable/menu_copy_text.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/menu_delete.xml b/app/src/main/res/drawable/menu_delete.xml deleted file mode 100644 index 95753c8e2..000000000 --- a/app/src/main/res/drawable/menu_delete.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/menu_forward.xml b/app/src/main/res/drawable/menu_forward.xml deleted file mode 100644 index 15c32f263..000000000 --- a/app/src/main/res/drawable/menu_forward.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/menu_imdn_info.xml b/app/src/main/res/drawable/menu_imdn_info.xml deleted file mode 100644 index d15e39140..000000000 --- a/app/src/main/res/drawable/menu_imdn_info.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/menu_options.xml b/app/src/main/res/drawable/menu_options.xml deleted file mode 100644 index 056ff36da..000000000 --- a/app/src/main/res/drawable/menu_options.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/menu_pressed_background_color.xml b/app/src/main/res/drawable/menu_pressed_background_color.xml deleted file mode 100644 index ae56471dd..000000000 --- a/app/src/main/res/drawable/menu_pressed_background_color.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/menu_recordings.xml b/app/src/main/res/drawable/menu_recordings.xml deleted file mode 100644 index e4d5f5e5e..000000000 --- a/app/src/main/res/drawable/menu_recordings.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/menu_reply.xml b/app/src/main/res/drawable/menu_reply.xml deleted file mode 100644 index c3099128f..000000000 --- a/app/src/main/res/drawable/menu_reply.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/numpad_eight.xml b/app/src/main/res/drawable/numpad_eight.xml deleted file mode 100644 index eba2595e3..000000000 --- a/app/src/main/res/drawable/numpad_eight.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - diff --git a/app/src/main/res/drawable/numpad_five.xml b/app/src/main/res/drawable/numpad_five.xml deleted file mode 100644 index bd19a35a0..000000000 --- a/app/src/main/res/drawable/numpad_five.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/drawable/numpad_four.xml b/app/src/main/res/drawable/numpad_four.xml deleted file mode 100644 index 791010381..000000000 --- a/app/src/main/res/drawable/numpad_four.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/drawable/numpad_nine.xml b/app/src/main/res/drawable/numpad_nine.xml deleted file mode 100644 index 4fcf8ba72..000000000 --- a/app/src/main/res/drawable/numpad_nine.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/drawable/numpad_one.xml b/app/src/main/res/drawable/numpad_one.xml deleted file mode 100644 index 392b53e3f..000000000 --- a/app/src/main/res/drawable/numpad_one.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/drawable/numpad_seven.xml b/app/src/main/res/drawable/numpad_seven.xml deleted file mode 100644 index a8aad2d4f..000000000 --- a/app/src/main/res/drawable/numpad_seven.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/drawable/numpad_sharp.xml b/app/src/main/res/drawable/numpad_sharp.xml deleted file mode 100644 index 84bbed502..000000000 --- a/app/src/main/res/drawable/numpad_sharp.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/drawable/numpad_six.xml b/app/src/main/res/drawable/numpad_six.xml deleted file mode 100644 index de101da62..000000000 --- a/app/src/main/res/drawable/numpad_six.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/drawable/numpad_star_digit.xml b/app/src/main/res/drawable/numpad_star_digit.xml deleted file mode 100644 index 985778188..000000000 --- a/app/src/main/res/drawable/numpad_star_digit.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/drawable/numpad_three.xml b/app/src/main/res/drawable/numpad_three.xml deleted file mode 100644 index ed428bb70..000000000 --- a/app/src/main/res/drawable/numpad_three.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/drawable/numpad_two.xml b/app/src/main/res/drawable/numpad_two.xml deleted file mode 100644 index 3762419f4..000000000 --- a/app/src/main/res/drawable/numpad_two.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/drawable/numpad_zero.xml b/app/src/main/res/drawable/numpad_zero.xml deleted file mode 100644 index c43fe66e8..000000000 --- a/app/src/main/res/drawable/numpad_zero.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/drawable/record_audio_message.xml b/app/src/main/res/drawable/record_audio_message.xml deleted file mode 100644 index a3b70050b..000000000 --- a/app/src/main/res/drawable/record_audio_message.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/record_pause_dark.xml b/app/src/main/res/drawable/record_pause_dark.xml deleted file mode 100644 index 7e7a59878..000000000 --- a/app/src/main/res/drawable/record_pause_dark.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/drawable/record_pause_light.xml b/app/src/main/res/drawable/record_pause_light.xml deleted file mode 100644 index 5af5e7552..000000000 --- a/app/src/main/res/drawable/record_pause_light.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/drawable/record_play_dark.xml b/app/src/main/res/drawable/record_play_dark.xml deleted file mode 100644 index 33721dc80..000000000 --- a/app/src/main/res/drawable/record_play_dark.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/drawable/record_play_light.xml b/app/src/main/res/drawable/record_play_light.xml deleted file mode 100644 index f17480c37..000000000 --- a/app/src/main/res/drawable/record_play_light.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/drawable/record_stop_light.xml b/app/src/main/res/drawable/record_stop_light.xml deleted file mode 100644 index 1d80da938..000000000 --- a/app/src/main/res/drawable/record_stop_light.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/drawable/recording_play_pause.xml b/app/src/main/res/drawable/recording_play_pause.xml deleted file mode 100644 index cb4e8130a..000000000 --- a/app/src/main/res/drawable/recording_play_pause.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/drawable/rect_orange_button.xml b/app/src/main/res/drawable/rect_orange_button.xml deleted file mode 100644 index 683e0fb36..000000000 --- a/app/src/main/res/drawable/rect_orange_button.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/resizable_assistant_button.xml b/app/src/main/res/drawable/resizable_assistant_button.xml deleted file mode 100644 index eeef6ce02..000000000 --- a/app/src/main/res/drawable/resizable_assistant_button.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/round_button_background.xml b/app/src/main/res/drawable/round_button_background.xml deleted file mode 100644 index 5a67aa4b6..000000000 --- a/app/src/main/res/drawable/round_button_background.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - diff --git a/app/src/main/res/drawable/round_button_background_default.xml b/app/src/main/res/drawable/round_button_background_default.xml deleted file mode 100644 index 63eba8a5c..000000000 --- a/app/src/main/res/drawable/round_button_background_default.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/round_orange_button_background.xml b/app/src/main/res/drawable/round_orange_button_background.xml deleted file mode 100644 index 5c7e546c9..000000000 --- a/app/src/main/res/drawable/round_orange_button_background.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - diff --git a/app/src/main/res/drawable/round_orange_button_background_default.xml b/app/src/main/res/drawable/round_orange_button_background_default.xml deleted file mode 100644 index 211384486..000000000 --- a/app/src/main/res/drawable/round_orange_button_background_default.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/round_orange_button_background_disabled.xml b/app/src/main/res/drawable/round_orange_button_background_disabled.xml deleted file mode 100644 index 0ec6324be..000000000 --- a/app/src/main/res/drawable/round_orange_button_background_disabled.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/round_orange_button_background_over.xml b/app/src/main/res/drawable/round_orange_button_background_over.xml deleted file mode 100644 index abad4e866..000000000 --- a/app/src/main/res/drawable/round_orange_button_background_over.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/round_recording_button_background_dark.xml b/app/src/main/res/drawable/round_recording_button_background_dark.xml deleted file mode 100644 index 70fdb4900..000000000 --- a/app/src/main/res/drawable/round_recording_button_background_dark.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/round_recording_button_background_light.xml b/app/src/main/res/drawable/round_recording_button_background_light.xml deleted file mode 100644 index d6194f718..000000000 --- a/app/src/main/res/drawable/round_recording_button_background_light.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/scroll_to_bottom.xml b/app/src/main/res/drawable/scroll_to_bottom.xml deleted file mode 100644 index 30ca8234c..000000000 --- a/app/src/main/res/drawable/scroll_to_bottom.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/select_all.xml b/app/src/main/res/drawable/select_all.xml deleted file mode 100644 index 294ddea41..000000000 --- a/app/src/main/res/drawable/select_all.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/settings_advanced.xml b/app/src/main/res/drawable/settings_advanced.xml deleted file mode 100644 index 4d26576b3..000000000 --- a/app/src/main/res/drawable/settings_advanced.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - diff --git a/app/src/main/res/drawable/settings_audio.xml b/app/src/main/res/drawable/settings_audio.xml deleted file mode 100644 index bd1f54345..000000000 --- a/app/src/main/res/drawable/settings_audio.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - diff --git a/app/src/main/res/drawable/settings_call.xml b/app/src/main/res/drawable/settings_call.xml deleted file mode 100644 index 1eaeea4cd..000000000 --- a/app/src/main/res/drawable/settings_call.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - diff --git a/app/src/main/res/drawable/settings_chat.xml b/app/src/main/res/drawable/settings_chat.xml deleted file mode 100644 index 91f3146d7..000000000 --- a/app/src/main/res/drawable/settings_chat.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - diff --git a/app/src/main/res/drawable/settings_conferences.xml b/app/src/main/res/drawable/settings_conferences.xml deleted file mode 100644 index 1c4d4d873..000000000 --- a/app/src/main/res/drawable/settings_conferences.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - diff --git a/app/src/main/res/drawable/settings_contacts.xml b/app/src/main/res/drawable/settings_contacts.xml deleted file mode 100644 index 134672d7d..000000000 --- a/app/src/main/res/drawable/settings_contacts.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - diff --git a/app/src/main/res/drawable/settings_network.xml b/app/src/main/res/drawable/settings_network.xml deleted file mode 100644 index 718e0d587..000000000 --- a/app/src/main/res/drawable/settings_network.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - diff --git a/app/src/main/res/drawable/settings_video.xml b/app/src/main/res/drawable/settings_video.xml deleted file mode 100644 index 9d840fda9..000000000 --- a/app/src/main/res/drawable/settings_video.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - diff --git a/app/src/main/res/drawable/shape_audio_only_me_background.xml b/app/src/main/res/drawable/shape_audio_only_me_background.xml deleted file mode 100644 index 86c42db08..000000000 --- a/app/src/main/res/drawable/shape_audio_only_me_background.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_audio_only_remote_background.xml b/app/src/main/res/drawable/shape_audio_only_remote_background.xml deleted file mode 100644 index ec1d610aa..000000000 --- a/app/src/main/res/drawable/shape_audio_only_remote_background.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_audio_routes_background.xml b/app/src/main/res/drawable/shape_audio_routes_background.xml deleted file mode 100644 index 25038835b..000000000 --- a/app/src/main/res/drawable/shape_audio_routes_background.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_button_background.xml b/app/src/main/res/drawable/shape_button_background.xml deleted file mode 100644 index b12bba2b6..000000000 --- a/app/src/main/res/drawable/shape_button_background.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_button_disabled_background.xml b/app/src/main/res/drawable/shape_button_disabled_background.xml deleted file mode 100644 index b9a64291c..000000000 --- a/app/src/main/res/drawable/shape_button_disabled_background.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_button_pressed_background.xml b/app/src/main/res/drawable/shape_button_pressed_background.xml deleted file mode 100644 index adcc6ce8b..000000000 --- a/app/src/main/res/drawable/shape_button_pressed_background.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_call_answer_background.xml b/app/src/main/res/drawable/shape_call_answer_background.xml deleted file mode 100644 index f456f09ef..000000000 --- a/app/src/main/res/drawable/shape_call_answer_background.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_call_answer_pressed_background.xml b/app/src/main/res/drawable/shape_call_answer_pressed_background.xml deleted file mode 100644 index 72b56a9a5..000000000 --- a/app/src/main/res/drawable/shape_call_answer_pressed_background.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_call_numpad_background.xml b/app/src/main/res/drawable/shape_call_numpad_background.xml deleted file mode 100644 index d5a2c3cd7..000000000 --- a/app/src/main/res/drawable/shape_call_numpad_background.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_call_numpad_pressed_background.xml b/app/src/main/res/drawable/shape_call_numpad_pressed_background.xml deleted file mode 100644 index 8e2fd88e2..000000000 --- a/app/src/main/res/drawable/shape_call_numpad_pressed_background.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_call_popup_background.xml b/app/src/main/res/drawable/shape_call_popup_background.xml deleted file mode 100644 index 9c9303055..000000000 --- a/app/src/main/res/drawable/shape_call_popup_background.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_call_recording_off_background.xml b/app/src/main/res/drawable/shape_call_recording_off_background.xml deleted file mode 100644 index 8fb61094a..000000000 --- a/app/src/main/res/drawable/shape_call_recording_off_background.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_call_recording_on_background.xml b/app/src/main/res/drawable/shape_call_recording_on_background.xml deleted file mode 100644 index 35da9f606..000000000 --- a/app/src/main/res/drawable/shape_call_recording_on_background.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_call_terminate_background.xml b/app/src/main/res/drawable/shape_call_terminate_background.xml deleted file mode 100644 index a8b903a58..000000000 --- a/app/src/main/res/drawable/shape_call_terminate_background.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_call_terminate_pressed_background.xml b/app/src/main/res/drawable/shape_call_terminate_pressed_background.xml deleted file mode 100644 index 622eae85f..000000000 --- a/app/src/main/res/drawable/shape_call_terminate_pressed_background.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_conference_active_speaker_border.xml b/app/src/main/res/drawable/shape_conference_active_speaker_border.xml deleted file mode 100644 index 98a0c1b7f..000000000 --- a/app/src/main/res/drawable/shape_conference_active_speaker_border.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_conference_audio_only_border.xml b/app/src/main/res/drawable/shape_conference_audio_only_border.xml deleted file mode 100644 index d276b1039..000000000 --- a/app/src/main/res/drawable/shape_conference_audio_only_border.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_conference_invite_background.xml b/app/src/main/res/drawable/shape_conference_invite_background.xml deleted file mode 100644 index 041c8baa4..000000000 --- a/app/src/main/res/drawable/shape_conference_invite_background.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_dialog_background.xml b/app/src/main/res/drawable/shape_dialog_background.xml deleted file mode 100644 index 1321d5ec0..000000000 --- a/app/src/main/res/drawable/shape_dialog_background.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_dialog_handle.xml b/app/src/main/res/drawable/shape_dialog_handle.xml deleted file mode 100644 index 030e5bcf1..000000000 --- a/app/src/main/res/drawable/shape_dialog_handle.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_edittext_background.xml b/app/src/main/res/drawable/shape_edittext_background.xml deleted file mode 100644 index 825ef744b..000000000 --- a/app/src/main/res/drawable/shape_edittext_background.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_extra_buttons_background.xml b/app/src/main/res/drawable/shape_extra_buttons_background.xml deleted file mode 100644 index 294ee7cb4..000000000 --- a/app/src/main/res/drawable/shape_extra_buttons_background.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_form_field_background.xml b/app/src/main/res/drawable/shape_form_field_background.xml deleted file mode 100644 index fce78c8c5..000000000 --- a/app/src/main/res/drawable/shape_form_field_background.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_me_background.xml b/app/src/main/res/drawable/shape_me_background.xml deleted file mode 100644 index 1efdf15a6..000000000 --- a/app/src/main/res/drawable/shape_me_background.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_orange_circle.xml b/app/src/main/res/drawable/shape_orange_circle.xml deleted file mode 100644 index d894e038a..000000000 --- a/app/src/main/res/drawable/shape_orange_circle.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_own_reaction_background.xml b/app/src/main/res/drawable/shape_own_reaction_background.xml deleted file mode 100644 index 9821f0651..000000000 --- a/app/src/main/res/drawable/shape_own_reaction_background.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_rect_gray_button.xml b/app/src/main/res/drawable/shape_rect_gray_button.xml deleted file mode 100644 index 7bd9f74ca..000000000 --- a/app/src/main/res/drawable/shape_rect_gray_button.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_rect_green_button.xml b/app/src/main/res/drawable/shape_rect_green_button.xml deleted file mode 100644 index 0a51a8db5..000000000 --- a/app/src/main/res/drawable/shape_rect_green_button.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_rect_light_gray_button.xml b/app/src/main/res/drawable/shape_rect_light_gray_button.xml deleted file mode 100644 index 6e2eaa7ba..000000000 --- a/app/src/main/res/drawable/shape_rect_light_gray_button.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_rect_orange_button.xml b/app/src/main/res/drawable/shape_rect_orange_button.xml deleted file mode 100644 index e239fdbfc..000000000 --- a/app/src/main/res/drawable/shape_rect_orange_button.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_rect_orange_disabled_button.xml b/app/src/main/res/drawable/shape_rect_orange_disabled_button.xml deleted file mode 100644 index 7928e4f48..000000000 --- a/app/src/main/res/drawable/shape_rect_orange_disabled_button.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_remote_background.xml b/app/src/main/res/drawable/shape_remote_background.xml deleted file mode 100644 index f77318418..000000000 --- a/app/src/main/res/drawable/shape_remote_background.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_remote_paused_background.xml b/app/src/main/res/drawable/shape_remote_paused_background.xml deleted file mode 100644 index 8663b555c..000000000 --- a/app/src/main/res/drawable/shape_remote_paused_background.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_remote_recording_background.xml b/app/src/main/res/drawable/shape_remote_recording_background.xml deleted file mode 100644 index 7247f620a..000000000 --- a/app/src/main/res/drawable/shape_remote_recording_background.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_remote_video_background.xml b/app/src/main/res/drawable/shape_remote_video_background.xml deleted file mode 100644 index 6b924bcef..000000000 --- a/app/src/main/res/drawable/shape_remote_video_background.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_round_button.xml b/app/src/main/res/drawable/shape_round_button.xml deleted file mode 100644 index e4607cb25..000000000 --- a/app/src/main/res/drawable/shape_round_button.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_round_button_disabled.xml b/app/src/main/res/drawable/shape_round_button_disabled.xml deleted file mode 100644 index b9a64291c..000000000 --- a/app/src/main/res/drawable/shape_round_button_disabled.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_round_button_pressed.xml b/app/src/main/res/drawable/shape_round_button_pressed.xml deleted file mode 100644 index adcc6ce8b..000000000 --- a/app/src/main/res/drawable/shape_round_button_pressed.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_round_dark_gray_background.xml b/app/src/main/res/drawable/shape_round_dark_gray_background.xml deleted file mode 100644 index d1cf0fab4..000000000 --- a/app/src/main/res/drawable/shape_round_dark_gray_background.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_round_dark_gray_background_with_orange_border.xml b/app/src/main/res/drawable/shape_round_dark_gray_background_with_orange_border.xml deleted file mode 100644 index dfd68923b..000000000 --- a/app/src/main/res/drawable/shape_round_dark_gray_background_with_orange_border.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_round_gray_background.xml b/app/src/main/res/drawable/shape_round_gray_background.xml deleted file mode 100644 index adc21b355..000000000 --- a/app/src/main/res/drawable/shape_round_gray_background.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_round_gray_background_with_orange_border.xml b/app/src/main/res/drawable/shape_round_gray_background_with_orange_border.xml deleted file mode 100644 index 2dd378d80..000000000 --- a/app/src/main/res/drawable/shape_round_gray_background_with_orange_border.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_round_red_background.xml b/app/src/main/res/drawable/shape_round_red_background.xml deleted file mode 100644 index 9cc11d1e7..000000000 --- a/app/src/main/res/drawable/shape_round_red_background.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_round_red_background_with_orange_border.xml b/app/src/main/res/drawable/shape_round_red_background_with_orange_border.xml deleted file mode 100644 index 033bc92ea..000000000 --- a/app/src/main/res/drawable/shape_round_red_background_with_orange_border.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_toggle_disabled_background.xml b/app/src/main/res/drawable/shape_toggle_disabled_background.xml deleted file mode 100644 index b9a64291c..000000000 --- a/app/src/main/res/drawable/shape_toggle_disabled_background.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_toggle_off_background.xml b/app/src/main/res/drawable/shape_toggle_off_background.xml deleted file mode 100644 index b12bba2b6..000000000 --- a/app/src/main/res/drawable/shape_toggle_off_background.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_toggle_on_background.xml b/app/src/main/res/drawable/shape_toggle_on_background.xml deleted file mode 100644 index e4607cb25..000000000 --- a/app/src/main/res/drawable/shape_toggle_on_background.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_toggle_pressed_background.xml b/app/src/main/res/drawable/shape_toggle_pressed_background.xml deleted file mode 100644 index adcc6ce8b..000000000 --- a/app/src/main/res/drawable/shape_toggle_pressed_background.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_transparent_reaction_background.xml b/app/src/main/res/drawable/shape_transparent_reaction_background.xml deleted file mode 100644 index 15e76c3b7..000000000 --- a/app/src/main/res/drawable/shape_transparent_reaction_background.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable/status_led_background.xml b/app/src/main/res/drawable/status_led_background.xml deleted file mode 100644 index 7147daf3c..000000000 --- a/app/src/main/res/drawable/status_led_background.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/unread_message_count_bg.xml b/app/src/main/res/drawable/unread_message_count_bg.xml deleted file mode 100644 index 940a12693..000000000 --- a/app/src/main/res/drawable/unread_message_count_bg.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/valid.xml b/app/src/main/res/drawable/valid.xml deleted file mode 100644 index 3be5be72e..000000000 --- a/app/src/main/res/drawable/valid.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/drawable/vector_linphone_logo.xml b/app/src/main/res/drawable/vector_linphone_logo.xml deleted file mode 100644 index 9c1d994f9..000000000 --- a/app/src/main/res/drawable/vector_linphone_logo.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - diff --git a/app/src/main/res/layout-land/about_fragment.xml b/app/src/main/res/layout-land/about_fragment.xml deleted file mode 100644 index 3a351bf9f..000000000 --- a/app/src/main/res/layout-land/about_fragment.xml +++ /dev/null @@ -1,191 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout-land/assistant_welcome_fragment.xml b/app/src/main/res/layout-land/assistant_welcome_fragment.xml deleted file mode 100644 index d89ae4936..000000000 --- a/app/src/main/res/layout-land/assistant_welcome_fragment.xml +++ /dev/null @@ -1,166 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout-land/conference_waiting_room_fragment.xml b/app/src/main/res/layout-land/conference_waiting_room_fragment.xml deleted file mode 100644 index 64c9d3c78..000000000 --- a/app/src/main/res/layout-land/conference_waiting_room_fragment.xml +++ /dev/null @@ -1,312 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout-land/dialer_fragment.xml b/app/src/main/res/layout-land/dialer_fragment.xml deleted file mode 100644 index a911071a9..000000000 --- a/app/src/main/res/layout-land/dialer_fragment.xml +++ /dev/null @@ -1,141 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout-land/main_activity_content.xml b/app/src/main/res/layout-land/main_activity_content.xml deleted file mode 100644 index 016fb5554..000000000 --- a/app/src/main/res/layout-land/main_activity_content.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout-land/tabs_fragment.xml b/app/src/main/res/layout-land/tabs_fragment.xml deleted file mode 100644 index 72e54530a..000000000 --- a/app/src/main/res/layout-land/tabs_fragment.xml +++ /dev/null @@ -1,152 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout-land/voip_conference_active_speaker.xml b/app/src/main/res/layout-land/voip_conference_active_speaker.xml deleted file mode 100644 index df24c7f74..000000000 --- a/app/src/main/res/layout-land/voip_conference_active_speaker.xml +++ /dev/null @@ -1,363 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout-land/voip_conference_grid.xml b/app/src/main/res/layout-land/voip_conference_grid.xml deleted file mode 100644 index ecac814f9..000000000 --- a/app/src/main/res/layout-land/voip_conference_grid.xml +++ /dev/null @@ -1,139 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout-land/voip_numpad.xml b/app/src/main/res/layout-land/voip_numpad.xml deleted file mode 100644 index 3e21ebf53..000000000 --- a/app/src/main/res/layout-land/voip_numpad.xml +++ /dev/null @@ -1,225 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout-sw533dp-land/dialer_fragment.xml b/app/src/main/res/layout-sw533dp-land/dialer_fragment.xml deleted file mode 100644 index 50137dd74..000000000 --- a/app/src/main/res/layout-sw533dp-land/dialer_fragment.xml +++ /dev/null @@ -1,182 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout-sw600dp/dialer_fragment.xml b/app/src/main/res/layout-sw600dp/dialer_fragment.xml deleted file mode 100644 index 4c513ca49..000000000 --- a/app/src/main/res/layout-sw600dp/dialer_fragment.xml +++ /dev/null @@ -1,182 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/about_fragment.xml b/app/src/main/res/layout/about_fragment.xml deleted file mode 100644 index 6198b5301..000000000 --- a/app/src/main/res/layout/about_fragment.xml +++ /dev/null @@ -1,197 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/assistant_account_login_fragment.xml b/app/src/main/res/layout/assistant_account_login_fragment.xml deleted file mode 100644 index b6051be74..000000000 --- a/app/src/main/res/layout/assistant_account_login_fragment.xml +++ /dev/null @@ -1,282 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/assistant_activity.xml b/app/src/main/res/layout/assistant_activity.xml deleted file mode 100644 index 3b2192ec4..000000000 --- a/app/src/main/res/layout/assistant_activity.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/assistant_country_picker_cell.xml b/app/src/main/res/layout/assistant_country_picker_cell.xml deleted file mode 100644 index ccc3d74d9..000000000 --- a/app/src/main/res/layout/assistant_country_picker_cell.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/assistant_country_picker_fragment.xml b/app/src/main/res/layout/assistant_country_picker_fragment.xml deleted file mode 100644 index a387259a4..000000000 --- a/app/src/main/res/layout/assistant_country_picker_fragment.xml +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/assistant_echo_canceller_calibration_fragment.xml b/app/src/main/res/layout/assistant_echo_canceller_calibration_fragment.xml deleted file mode 100644 index acf86c157..000000000 --- a/app/src/main/res/layout/assistant_echo_canceller_calibration_fragment.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/assistant_email_account_creation_fragment.xml b/app/src/main/res/layout/assistant_email_account_creation_fragment.xml deleted file mode 100644 index e690c83d5..000000000 --- a/app/src/main/res/layout/assistant_email_account_creation_fragment.xml +++ /dev/null @@ -1,169 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/assistant_email_account_validation_fragment.xml b/app/src/main/res/layout/assistant_email_account_validation_fragment.xml deleted file mode 100644 index 6a487897e..000000000 --- a/app/src/main/res/layout/assistant_email_account_validation_fragment.xml +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/assistant_generic_account_login_fragment.xml b/app/src/main/res/layout/assistant_generic_account_login_fragment.xml deleted file mode 100644 index 86376a5eb..000000000 --- a/app/src/main/res/layout/assistant_generic_account_login_fragment.xml +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/assistant_generic_account_warning_fragment.xml b/app/src/main/res/layout/assistant_generic_account_warning_fragment.xml deleted file mode 100644 index b4ab8d0c1..000000000 --- a/app/src/main/res/layout/assistant_generic_account_warning_fragment.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - - - - - - - - - - -

a+XuZ$Y9*gr{msp_ak$^%$p&)|2A*_yUZD-?*Aim zMydOdxu5g)P1aVwa2sxd)P=+pgbcJFZ`if_+Qm!1{JM9Tt{mITjBSTDLfgkrm+gpV zqD+dz?sahe-vh!d$fuEeGkSW6g<+7AskDaCZGQ-#eYNT?Y zmUf>b@MAXfgmKF9v5i5_^I0c{Ix_{(_DBnOqk;JA<5x!{YCF>f_~y8JRx>d_op`&pY_-Zoqs| z$mRf0+96morR^M2X5W5!EXY;its$&81tra17uysZf7fw^bJSjwupcz*_+p`Ma zwljl8(!_h8#C^tEDTmQVU!jKRQOfC)_tqIHl&4;7l(NQcD3z4WDNF%D)BHd9}`ZH91^A)J|od}6Qc84l_Y70<~ zWIPWK_e@}c+(cm}>=F;@hZK3%dq*2o<~f;z$eRJwi;{N(-M^^!<-bt~JJsv~(}u#RbK z@)63SfWe18ff3>Zu1@+7blK$C=i{N}F*ny$(PcV#>u|6f;+Y%RJHWl*gL+K`rPxdJ1gyK7l9H8?^#dnJlaVU;7RI_g{4% zTi_uA0QYfGi;*=xck@d22&81I&hI zcP7E=V{!^F(Jue!qn|%%o{uj3uS@yMSM$w3%maq9s4(b0;(U7CedULXhhr%RHIUB* z!c>7L6oOI1gA^n`<5|}Ys~#%SlV~pA9{xthxm^!BBy=>^QJcVe+$<_OfDnZ~Lraw8rtzjz(;`t`wNlzsD>jG@GPRPa z70{6`B+>_M1EVz9kM0S10UUNC*I+$wq?^7iNsw*^8c=`;AN9#?AxIYE2_MzcU2F6V zNlS<=Qws7&ENGD~SQhCIfEw1(NL%w3(-&nypE7;H~qH zKyTYfFCcbbItE|1o8{m@TSwS#Um*<8ik3k9+QWl`#KHA7=FJ)y2D(Wk@yMW7)&1-r zSnBIn!^d{7gFAuGC-HD{2{3d5f+A3^W|AplRlmEI`SJqrzk_?l2U13Z04-kdKMz1i zyF|3TL`Vd&P<;!P467S(k5)y+>>^f7;&jl6EA&=sMyfOjaRCyDMMRNSK4gIV?A=i| z2en9$P4p>}BUXM!-x2~l0D%0#k08M_orso4R{BPirR1P8Tmk?YNGW-)b5W2))g!Dh zt{p}Su=MfuUFpH}^vswZm4z!fKrJSOhbz*csqS<^oAxm1DGETIypb{>7$r^>QsjQ% zU`}t6=n|m=M8Q=ojLqrj*`%(8A&HGk#hejs!x>MJjMc71LPGWe-y<1mpkxOSISR5D z&d&T0>X|Hu1vT$#f~3Nbtf46==_Z|CQXil7>z}`JnH?$`QgJ_e5HIoU$uWpWzO95` zc64{)AM(O66?wq}a}2GjC(kUyGk6w5UMg=IXxhL{l_Y91!mAVlWyvk0v?~3Ny-eH@Xls*}L$wfc zY7jx%={2tIALu}R0uR4sxcjwZ0l@a&=ut#kWMPPSw4NT1ds3=>Q=avDNaIQ_5Nx}r{3c1SPrGwOl*yY%6 zb;5gd>%kX7@n&rn-{YHO1FV&@_Ofg;Jn*XEJK;KMYrNq_yP}#ExYIYh(`b9ZEg58O zMj(CWo3XdS~COD=?m3%G~wslUz6(AlU9Yso3 zB~SqbQuYU>VV-YtpH*U;F7+~wj);bd8uSY(K$mt*p0B9ui0A~Q5yR0fY%B3cv(f$3 ztFQ-{1bZM(v~NN`&w-o}NgX7Losaw`=n#i2P_n>L^L)SHVk}BFy>aE7d3NY|;aPCA zRyD*#FaF)$p94WoVvea-8DI8@crxuj)N@KI06Nu2 zppM)Xk(zo0QcPC;)dO5P-wWZEwE)m@N<_jx;5`Vrpe|aJz`%>Z?enpcPLom}+Bi7N zJur|qkA`Oj)9>yhks4C?I0ft^>&o~sbYySzLf*+H8e98DQOQBl(ouY>5N@+d1mQ?A ziKF5NjgJL#3+Fj}pg?TM7Uvv?! zFVdk2ZGlowFtrq5oizvrkP!rel6kfcC=!H`TQoGmpe|zfu^as6?{wxsjwK$uQ6exc zPPq*+h1IJZ%VMbjZD(eO-kNirQc-Le`L=^0gH&k`c)jzS5S3 ztZs0I!W^_IDGx#5F@XHAw*VaPY$3%%ovdLg3(4x)&zxn5;7|jsIMcg8pD9Xb5U6yq z2}%`NS|Rpb!$7Y! z%TxX*qE0$gh%lKkDdf;QRzO`uzwR1OGNkbj)Ccf26d(~lDe89a?$d1iKce@oJI&AS z`A2pOy6TxiW=gA!LfpN1ngTq7CuM`Fps1Y?2|ch8@TZz!s)f^|>+IAPX{5^b(y32VXBkSOd#dPBO(+w0FFh;Yvmq_Plh5M<|x zA?RkYCJFJE42FU1%Nx{D6mr6iy8#>|Q9C3dKvBY79CUjuFeBD7B<>HkX+xCSiiRW~ z6eAb?{lt*E8|O(j4TFe=&;75zIHSbV?R zFx&!PNaGhk60^hWT|4VK{b8A+3^?5d@G!+eFkI9rf*!{e{1g5Qcd&}-@{X9e`@E%O+0B=T*&+ju>H-@N9ndvTFOiRwLza+2JF0V@s{GgARIG=Pok)zIr{=$;CKyfnqm z{7k+-xVItgxi;}4l|1y?>-2ZQNteZlm~g@K{vD{9k=0l5Sk_hvja?w zw`oL3UW6%9GM!H1La zFZtD`!To_bL>y32rT?oD9g|y}g~R36L3$)=hsoV&rY}|3)> z?9Rv{YT!rWrs!-{d+()7f1-(qjZ`9UXZQq+6_L z$!Bkb2%@Fj{AI~;T}bfe&BHd}%Ni8WZc$DhI?^qyu{ym&5w?TF3Ne6*C{|Pw!CD>P z6s^wWGCmYVLX@x9A>7yNq~GrPAy+pHNinzWGBODXYQ@noH*94aXv~ula2{utqB@@n6d&LNp;C6e3i|^1F4+O8KFsbeN%ox?&M}8>Op?7cfGMt;44}lIT7o zZb5XU1xSd{=u5IuUJQ*{4}cQ34vLS#r5&9EdL-^g2>q2;q?90HEc=J#sbgMikxPq4g|E=^V9WN(vl$&*Dcgcs#C+F4hQP1@shl11VO zJJ^R=dNg`Z;W3 z_iEPTS5d&}(%yhV&hi+av2YNx9l~2`^Hc5%)KyJ!GrSbd3#Nr3dI#8$hrP8MX7G@+XcQSdqg7~cyvpI7avk39_8fRtXEQNGLy-$w+Si6Uqad4h`63Aw z1)~Qq5>mby(I+?*Q6!e_TW?ci4V9LIJ0}oJ`O^bP-C}$I7I?lQHc(9x@p3AlHeqm* zi9=Yb4b0KJ?2T{@m`W>w8F3Qo$BAIq)_auqMoGGkhB{Pnl~n|ieQ}M7P6{j)B8?(D$(a(XLU_iqK8Tbklq*{aY%7N^`nxr#%AQ|~uvPVSF@Yvrh zU4?K49$47KT^5fLsMK9Tc#rl{huRh^Ka@Nz#9L!726Nq6xy~O6OiGvlp0g6Rdut>) zHgX}ned!f8gKFssZF0}7v+YLp2j`CQ8GwZ*MoS`5>PbRR`*Rj+=T5xdbzHWN!KMa^ zPKv}WFHVO7z};R^{&*?sv(ZpFcx`9#-Pquu6EXAtQ7L>7031ab+laLe)eRlP`>Cn{ z<>|Gxch(lTNJrwmdIbdW0#ugiDORj<>;o~4CulxRerR_umv(>==mUvBfGrUT%)QRq ziO>~#Kq<8g!iaLR?{|D5_Kw%LiG0%R-0g^85deLZ~ zbU_6isk4+bT6nF;glQb;^Kpj0c{UlB3lC)6O~$NgKccQpM(r`8o4Ij%?J?H&g?G(s zlkt61=t4$X&!&(jXM!5@eIy}ydLM?>)J%LENZ=9iB2@SlF2(71?na}^9;5#?h;l7o z(2NNjFaVuNFW{0$F#b+^_*)}5)9fPoQtb6M(yE%tcDmq)ctDtOx>AZIM9X~-?zRIaK7?Rji?n|RN zv4ih^YgiNcM|`|?U~g?2YA;v>F$%M4%0nANFb;A7ftM3hPR&zYC<-ddk2nBD$eTvV zTwa^p3z8l>@k`oHI8x8l6QP7Wuh7$Ws0MH})Hdm@dT3?!5x`RFI>ILF&~g-3GH%_v zfD}nh)D(qgo;3J3i{>H@E5By2Wt?*WpJ`5&Lq%eTDt#WTvbH)HAmc{&V=wRh(tGIv@9hkIrEo?Pu4C`@gJX-O1yLXdQ37)tk`@6ql@Dfj3Y zDY&?F&Vu7sVuW5M@JoqO+2{_OH!k~8P*g0%?Nrriw+S)sa7(p{rb*mauXE0QDo|UWgMEL;6~s!@^`2XihLN1v_oxry4Ut$V8I=Hd509%4O*A% z3t`EWTe>wLPo#@n_k=(B%oxr3Ni`N4m)_br2Y{%qb!{Y1wzuYw{(ivY^VeToh-W-H z2)NOMvcd0r;(nil^l7C)Y^xz$|d5zP#*z7vc(FPiT&O3d+NA+0YhRQxVs)zA0Q)H0lPK)M4@&}Ki z&@9A^%V3RDw3Lq~9L1UAXN&es4e3z+H3{N$Ancw=kUKRroAEooMnQNgn#w+-AXgo| zCP7qk^l^+272nwun@MiOE4^k@mf7ICbe4uwyz*`}oPzPKn%vPThi3FJNc0CxyoWsn zo|UA~n9ZF%4YPMcXXvaDblI9GV>JgM59{8b?uv_rZB%M*+m&eIp>3X2X=qJhv>Fme^`JuNPAw�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 IM6N<$f{vSzCjbBd diff --git a/app/src/main/res/drawable-xhdpi/record_audio_message_default.png b/app/src/main/res/drawable-xhdpi/record_audio_message_default.png deleted file mode 100644 index 715ff2bb181f1e6a3aac407e9157c1d84ee78e1f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2579 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 diff --git a/app/src/main/res/drawable-xhdpi/record_pause_default.png b/app/src/main/res/drawable-xhdpi/record_pause_default.png deleted file mode 100644 index 15e1af51fba4f25257194580085bf31fe7d2f8aa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7268 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%0Dg^-q z0l}at8;pwjC8UiV(eZDOWeWUg!ORseknCS9IW*sY$oeI>Wz9-CKPLjZ|Bd^L^U~uRNlB0je(+je%nbBr+U>!;;}-j2;P2!l6-E zG8sul2N7j!UE-A1E_a{p1d(1L*- z9MUqID4Z@@7mdQ9bTL>o4vYH7XeYp8gPpjHi9+h=VpcTE%c2h!0}@MG?o$w8MGn@Y zZ@~gc90tpU!3Z)^T}}zM?77n3FvIVQVoPI#77@!G|D)%303qMszHfmb+KLJWTWMQ; z66JdkHYp6Ct^@-8zN;wyB)T5}?(ZKB^_QIX9~KM1>QN|oJsg}u#(?Whq9EZUlr9pk zho!bvQ^%nz&-tOjhLm1>^sfzefFk*RE|5dRe;(u{sxMJ|rF#!5~mx0F%cv>O;KCXUnwoK!H z@$+LX{)-Vn=-)y96~Djf`c2orV&GpH|L(5ebp0y^{+03X?)pEYOZ=Y;4?qW}pm6Xy zW9iiT6}$=wlkIIRS1v`6FBVlX;FB2B)}0OBpE{SneC8UNTu>;&u_IcEycHG`(~wj{ zSd>8^f@ke4&0M%W6W!bWl>z1b(|*OhZcC2lc3W%hBce2{&BWXUn>RepEbz%wz)sYv zc3hnvs-1mm@d@UgoWFHOR^}q?*3)?62Z^YzT-D?Dht8+p6qLJp@#NEv_WI=gXDKo* zEln*i$LC^=CaV|jPWk!G0%}`FyiA~rcC82e?6PL=y9DiCSD9W}O2GIj<#|qXn{XQ38Kb194kEWq#nsb!bzMS5^>MXzUqv_@)lRYOZpnEZl z*>FI#dX53<=RXqazi(IUT+{e4@7^K;(hu1l>i?)puDZm3c3+iZ!Q0d04K-`d_0?k6 zLsG}XCt1o3fqeX2vC|^i;{!KBeHsb3F2PhRR-S*;|962)`yMY`k*wjl{>q?Njj{=bgDE;|Z$DjZjPoH3h#|td z%M&Metrko%RgXf%B_4X{4Ve_G)Jf5m^%SHFIQ&W+ zDfZj|)8;xrL@%J~(eC2s`Km=FIsRQ$HFD^ME4>oWSq_n*_t+&h>j%0!Uf(>~5!P?yxn{NWSA%6 z8+La8mZ?PuQ}i^yb^E85lA+x-3Iki1eN9EHXXeJmMf6K0KK~#x9 j>}}W8OJ@|m#XN{H>r}zWR^5BQe6-nFIan5%`^5bZV8^#h diff --git a/app/src/main/res/drawable-xhdpi/record_play_default.png b/app/src/main/res/drawable-xhdpi/record_play_default.png deleted file mode 100644 index 134279fc09606a867110b535df514e143e0ebf52..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8311 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 NTAMqVm7DmU{2y@~(c}OC diff --git a/app/src/main/res/drawable-xhdpi/record_stop_default.png b/app/src/main/res/drawable-xhdpi/record_stop_default.png deleted file mode 100644 index 14f700c3dc3acf8ccb4e205555e3100bcf182796..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 915 zcmeAS@N?(olHy`uVBq!ia0vp^jUddy1|**^xEa8}z}TAU>>S|f?5t2wl%JNFlghxL zF|l@{t;b;piMIROvet&kyis^GVPV-O1+7ObLN%OP1GKliN+^0MZ<6T|6eGiWy{Xy0 zJCt2KJyte`)$s%Sk+Ww#FS>gD(bL@0@v-l*+`ZrTrtf~wP`~(4$mVRB0MpYR>cUQD z+qGEYFE$IDm2eU={%RTMbFjGP?&0I#cUzqQwC;ED7wyW)jCPF`(_Xf@?s2r)oB49i z_fF+CDw;>0A4z75o$zDg>rJQg=XCS^u1q^}V#)JPW*mQU^L3oSj@y-c*``%XUR*OhY#h;{wVEC zDVlol#Ks1#5RX3jKm2vyuj%;s?eKiaa^Y{%ZTZCx1>zaka)1B6v2C`x!vo13mu~&b zVxI9+W$(Rfi`)+!Fe@m$d+mwsgCcf`s!OWQMN1fT>lZTYnSaszX~V(j1^H*+M!%L> z&hOAxHFXywA75j{#7)<2@7=w9b9;Su)ZgEXa_U8`#_#s!0ON%%$=lrpNCUx7=iT){ zinG8YvKSbJ*Fl)kNn>^eP>{XE)7O>#DH|uJI+yR2U;eU8h$J6IckeCqqU zyUN|xx3>MA_xz_~#ME8u$`2dlOn-j!t@~vY@x@X5H|f}a`~COxd=8cg^9p4)cXY%s zynOZh-%}~Z%vsA`>MALH=bF%|kn0*)6Up9bE!V%4<3n->hXA9J14{>kpaPRi!_eXU zNS}T7SujhdwG5yCPm8{pff4K~tJb}~-qG<|`+~{YqY;-r?PmS-l<{?V=+)nEYwz#< tvF`P`<(FSx+!6NuPkwvi4c+tg48dLI-)`RPz5|Rc22WQ%mvv4FO#t9YYmWc` diff --git a/app/src/main/res/drawable-xhdpi/replied_message_default.png b/app/src/main/res/drawable-xhdpi/replied_message_default.png deleted file mode 100644 index de2fe76492795788ce0445d09701d7c8378736dd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8230 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 diff --git a/app/src/main/res/drawable-xhdpi/resizable_assistant_button_default.9.png b/app/src/main/res/drawable-xhdpi/resizable_assistant_button_default.9.png deleted file mode 100644 index cc63e9cdd6a7b9877c2f8d790099cbdc18fe40bc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 538 zcmV+#0_FXQP)S5XW0Dg5LZ(>AM8dHssJ#OCKRE(Tj+B5n2jWya=}Ri!Hn0P8=oA#2VA*DxCJcu|tELP5){_?R=7dgcHk)7=1_W)SX1!i_ zLm#@2!X5_4c0#Y$gRrgJ?b5(wK@fHwU_9X0_M)n)*5e7=2t%PzprNlEVEpiAFN{Vb zV8zIprhoEiU@Mr*ti6 z!o-0&FbC$q9GER&;2q|`9GC-hU=GZInFNm9OK9X?^CK`wiRh?`#bF>ByXNzG?_r=U z%i9?EvKMQ$TIj&lYL$ll6a-=G&x{y@$E*yriHzV9YmPi61EMJ~C>cEXiZet2LFk)l zqD-gLS*TAFWw5jN>2!K+ru2o$2k0Lm%3kjXyZo7^$PYNH~;_u07*qoM6N<$g2;3GU;qFB diff --git a/app/src/main/res/drawable-xhdpi/resizable_assistant_button_disabled.9.png b/app/src/main/res/drawable-xhdpi/resizable_assistant_button_disabled.9.png deleted file mode 100644 index df69f9217f0474c43e157024abce87ca4946fadf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 419 zcmV;U0bKrxP)Yl&Z`LCNJ#rZ9b(klGMY(f0^cs? z{Y>6_kT0jxzJq^D-Jq1dx~}`?`+g9HAv;otfmn$7CrCobH3))_qX8F09mlzD0>*Lt z@-_J;FOVb&D~f_uRmJMM-bGjoYhvvM;2T(0L1oA+%UIv{jB|dd5CgGf*wy0}C*qHX zVK^Le6k?TSIf3;&?=mYKu4r^ZdN&ZV;=wj N002ovPDHLkV1lJO!4Lod diff --git a/app/src/main/res/drawable-xhdpi/resizable_assistant_button_over.9.png b/app/src/main/res/drawable-xhdpi/resizable_assistant_button_over.9.png deleted file mode 100644 index d8c4b0ee276f9018e712be710aefc8b9ea4ac1c2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 506 zcmVAs=6bEqUQcCwu{TQ}OE}8=sThPtbOx5Bf4t3T+)G6SmbWw1s#l=0~)HaZ%6$JY+ zTFUD?X~IRLOEtN`1^$bk@be{0{x23g&IPd`u4FQKi}}PrNcq7$DvENt7pki2!{1@| z$LVw$DwPT}8VxWE1A4vQ!P4n;pkA*-p-}KczsBS7i(M1F=MU#{IT#EEuvjc$wOSoL zrfEX8TJ?Z6O|w&})R_lL2)Rxq65r0Da=8q)Z3lwf%y2kd-%Sqv2wN|QhvfW{3EdbFq6rEWm%y@oiv+G7mLGu*}`_aT_K<>>@j4+b8K7K`Ft)6mZes! z@mO%N`Fvg|C`-TJ_s8nGE*y3;nfPN%rIK*i*=#1U5-YJ1E3pA%K~7d;C01f3R$?VK z5NxDg7$fy69)Xf0!u63z#T+ZIeTz9w(@f`!VzC%lfxIi@@tD<|E>=}l%GdK(sQ(h7 zOdgF!o-!F-&s#M;{u5oanjWF=ilUtR?iC0jcYIyHxd3Um+Xqdn)!MlL!MpuNDZSjg wu|X`s!yjR-me{Z`MmC#0`OgwAi0r+70V>k07*qoM6N<$fR diff --git a/app/src/main/res/drawable-xhdpi/resizable_text_field.9.png b/app/src/main/res/drawable-xhdpi/resizable_text_field.9.png deleted file mode 100644 index 51ad75cf4f31940a9af089ecde7f16b03195d942..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 223 zcmeAS@N?(olHy`uVBq!ia0vp^S|H591|*LjJ{b+9*pj^6T^Rm@;DWu&Cj&(|3p^r= z85p>QL70(Y)*K0-AbW|YuPgg~Zee~o?jZi00H9Egr;B4q#NoF$47m>DmM}25aFwA@J87(X?*U=HWVx`9?=_t{kN93-lI5aY`a(%Hjk+zt8 znVFSKL?d7USg1t!inK-M=LRO$C@HO#IzKj@wfnuFd+Wo$>e|jci|6$!PXgM@;OXk; Jvd$@?2>_-&MVbHr diff --git a/app/src/main/res/drawable-xhdpi/scroll_to_bottom_default.png b/app/src/main/res/drawable-xhdpi/scroll_to_bottom_default.png deleted file mode 100644 index 3be410ae19403a8e7c8f51cdddb080cae1ff202f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15058 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 bCjj^tEx%14hStZh00000NkvXXu0mjfy@Y$? diff --git a/app/src/main/res/drawable-xhdpi/security_1_indicator.png b/app/src/main/res/drawable-xhdpi/security_1_indicator.png deleted file mode 100644 index 0aa4a2e045c69d7cd8232435aa2b3537a692c86f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2368 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 diff --git a/app/src/main/res/drawable-xhdpi/security_alert_indicator.png b/app/src/main/res/drawable-xhdpi/security_alert_indicator.png deleted file mode 100644 index 54a340667740e911eec3c4991f17e7823574b3ff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1299 zcmV+u1?>8XP)?okYRp0n|8c+w4QJO~D2`BaU3%Tq^&zHi;g;&yHBeva0 zPO|N&2|ccbbj=XDh`vWZ12oHWvB+i7dTJeL_mW;_xmL>ziT{W$p(6q}NM#qQw(@Na zL|dwjYKiALfh*eYssy%1;HtG$HC#@v#NAHI&Q4<*8;fVh)RZx&PZ!(uEH#FsrJVVb zo=nU48y)o`OFmz|Z@Rn&WS@Jex3MBTJ$Mjd#ALHaw@zZcCvX!zm6KiGPMYw-g+LXW9AZ&KM@YO$Z)jBOFWS_gHHxnW}*`}Y(9^oQ^ zNp5@o6u94z+$y*!HVB(tCyPJ0GMTH95Bv@5_>AykgRt2nTqH0p^2o?Z`RV_KgU{*d zXs2%BMkJ^YCwH%IiVebM*U4hV$W;^EUAE7!vW*r;j{WYYZxEwuv5k<;o<4X#JPYE+ zpO#WTAs_0+24UM5( zs{l?;M#Jen+B;RqI%W;9?Wy7F>O60DHX3exTw~-G$njMhQqex=H)IaW#dNx>mic*p z7L?xwBg(+?g+#>4*rq=6%*SpWY{*vGjAl}eU~{wQ4Gpavz5agBYide6a`JLcy0?#T z@M$VIIcMf_p0{DOQxk|u)Xuoas(JIh`MCCqE{wEM;#4gfqLj}@I9+HIRh3{h}0A>IB99f?R zB#)j^Pm+HZ-9y1q{G0DPGD^X17(g~6`5TNteudV#jrat3{|~=jK3D8MDfR#W002ov JPDHLkV1kSbXHozF diff --git a/app/src/main/res/drawable-xhdpi/security_ko.png b/app/src/main/res/drawable-xhdpi/security_ko.png deleted file mode 100644 index 9bd476a714cfd5ebcfefbaf4a7fd215cbcaadccb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1009 zcmVmwgDH6-iZU@Sxs}^+%+G@r z+p^A_z0clzoRP0~owfH`|L?!Qwbr-3@7u>Hh5&6OO_Wq6X^5mA_R30nX|Ebdr;RbS zYHk5af$hL6!plQoCD6J#2pV8*M1ro|116_QvLkSsWTX+;oEFKeIh57|dw?lG1<(yB z2P%ODz)9eJj-gIx!-DgYr%>Yns#EpCnOZ%u7jU^61R_wk6KAHv)Z9@XytbMQ=p~80-K% zNa?gIcl5l7I`zGs_LG!O<`pe&n5 z_m7)4KCF_|KX8Az({BW@BPF6`PJ0#bCh!t>j4_uCa9GkJ6}PN0K!c>VMNaf;rUj6c zq;|fAyR!(0^^vsLb-KSvF_;e+>(IKHQ45iOk0>zHCUW&ab%9el4tQYGk=0QLhgze+ z(?GW@urQ*Y@lH1S1?ZZ_8V$ZgD@g~#M*P57N#`Y%G=Z6zMQ2$n;8-)B-LAzJ;jXZH;?`#y;zTWqHVMPvWdkBq#y4 zV&h$k+nR?kT8?GD;mJNZeo<7fkVX5zWpgVBK zL*TftwrRgbx$+Dck{VI_wd1x&@!6)z+Q2T)m^xr!T0~EI6kY~8bEEkbJ1BB-RYsJKE!ih^Yi zDkb~1Xcos|?)98=&%Iv3Z#Q#izWL3W_x~DYA)r>$K1q8dHAfDNJejgbx8v@0YhmTdfGsSa|pN=7z;jW)0CT`qC9@V z1{=Hz-U;f%JZFpY1XwNUuB2w?r!)5QJGaAagIo_hZ|&|3b!I3OBybA&1Ehc*5de#U zTOPn0nF0)vA%WG-@4v!C9PC5uk_WIKD4V5jWXL8P7;xi^U9&WF+5`9uSQ?^(v4;DY z2Nd9}Z*>A)p0X*RF61T zl7X~lmG(B{>SUIuRC18kumrdqsDB}WqcmB_fN>kpme6@WLnR4@@?I`H9~K}JkZ(>l zMg6xc=fR+P5LKH$b8@J_8B@kS(0iPxW})b-q$B?T@YV^7D`pem00000NkvXXu0mjf DwC{cH diff --git a/app/src/main/res/drawable-xhdpi/security_pending.png b/app/src/main/res/drawable-xhdpi/security_pending.png deleted file mode 100644 index aa7081af8eaa1b2df5c9c3e615d82ee52d8e34ea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 882 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 diff --git a/app/src/main/res/drawable-xhdpi/security_toggle_icon_green.png b/app/src/main/res/drawable-xhdpi/security_toggle_icon_green.png deleted file mode 100644 index 368e30c79ebda242dfa4cef453ed21ca41c8e0ea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1870 zcmV-U2eJ5xP)OcZ0JA5<_FiADm&(z^;M5^7AK_UmrPH;a4O zyYAiX-tO)7ZYL=_`)1z9@7{dAnFUB1(xD5}a&C3s*$9kOF#=!Yj~=zMa>g$`cAElr zE39vE&yX0~PZ)btrMm+_tnzuUw5n(+&)NolufI6A?{`>7fURPTxrQJEB2gf9F34T; zN_qS3A>}C`*aE(;x%f#!*vE{KNr^C^LDvWfD}3J0qlxO8El(-z5OKr?-`vaBhm@Nn zSPH(OZE|7g-+`4VbUF6xdraD$vX+4Vw7GOl9}B*L&HHt< zCf}4r@tA@10)cuW=6qm9OE3kVPg{&h%NZdrVH3}bXSc;=K>~1;3T%5#S=X6lWg7;6 zip>`{n{tNXjrE?hxE(Qh_2zsm$ zC3e)snE}@Z>=;mTU`m<$n+4DqOr$q1_+70fvqM3)m;tN66xMgdQmr z#J=3dYTLk#}=F&$949=Gr%*TRx=<)ltm@&O>lU=^n7F zwRmPINY*eQ3(&h`G6`66w&Ju2>;xTI__7!blD*hhX5wh=kiElwCI9iSS*|%isw~I`(as!B*+Pjofu#*_E4Ip1h?MMR1Jg zaXBwH`7rob%lut9@mqEVW|fyxAqT@Zjfz}#FylLgnAK00a6-Ws29MFK!wNB5pE#wr z*UahsO~DriKca}7#KNrJ-r_M^*z5cU2!oeTF&K#`RDPjsd1Ame0$&)s43M%T*#cAp zM|XjDR>W_m$Feylc)a#`5WU|P zi!l@E7^B;$TtU&YHzs&o>M{Y~O)aL3paFCh6y)!YXKPIGoT=IyXu@gl%Xk8YZ5bo$ zE2drGmyu$oxZpXL=pw7|k?>AiObux$z*gJr^ZNHEQk@X|Ymawb#H*foZ9Fp)0v`}v ztcG{nL>4Cm&jl1eC~m+o=fy<;yAGqr@zz5DmTXmtIYuCx|0Uanfd&mBj!JgKSVo!w=DtP z>hlKPH>)q{X0KNK$=TBJ!GXa>J)euJ`I5>5)Yp0g{9{Ga$rA9~icgy+74<=|2}7T5 z(^kb40Q+&nQkBfet|ih`vsVjTI`7KW9Dy#tMNmLZw+c3xn$c{!01EC%2Uy7x973Nlh2sm|cr-EZpOs_}Gf zHOQ%}x@FB?t!>F;fqx3caZliUcu-9@nD4<$;m@TCIJYGwo>zn1+2mdaAiaw-fqq>; z{1iCw)O|Z|3wW-iuBGG|!b1D-2PjbgFxq)IkvED|^46nWZXH#>H%#i>-Rhn=7-W1o zJV#3u&JuliKW>fBf9o!p)|I-jOqD>H7Rn_Gk z7Zk(nc-jhH0o3q)$@EZA+JPa)sp#!Zg`U|vqR-Bix~Y8r2Zjpu*1S}+djJ3c07*qo IM6N<$g6eXLg#Z8m diff --git a/app/src/main/res/drawable-xhdpi/security_toggle_icon_grey.png b/app/src/main/res/drawable-xhdpi/security_toggle_icon_grey.png deleted file mode 100644 index c772357943de9d370117e4625d9c6b57218f0bd2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2420 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 Z_#bGA|4xqDg~I><002ovPDHLkV1lX2+3f%T diff --git a/app/src/main/res/drawable-xhdpi/settings_advanced_default.png b/app/src/main/res/drawable-xhdpi/settings_advanced_default.png deleted file mode 100644 index 099a7770e39767f405a08a15d67d01fdfd1f0467..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2853 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 D)c{D@ diff --git a/app/src/main/res/drawable-xhdpi/settings_audio_default.png b/app/src/main/res/drawable-xhdpi/settings_audio_default.png deleted file mode 100644 index 4072d173fa47cccbd59090b98e3b3bb5d5da2a0e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1750 zcmV;{1}XW8P)EX>4Tx04R}tkv&MmP!xqvTcs*34tA*GkfAzR5EXIMDionYs1;guFnQ@8G-*gu zTpR`0f`dPcRRb`7 zkz)Z>sE`~#_#gb9ty!3yaFap_p!>zPKSqGyF3_yo_V=-EH&1}TGjOG~{nZ9A^GSNW zt;LRj{%zpmx~<83z~v4QebOaEa-;w)f1v=ppV2qvfPq_}Z_S-s>l~*KK!#?Ox&aOj zfzcvmuY0_^r*m%q_O#~r10Z;EhQIJLGynhq24YJ`L;(K){{a7>y{D4^000SaNLh0L z01ejw01ejxLMWSf00007bV*G`2ju|`2^cG6A&Y7N00h!WL_t(&-tC!PY!p=#$Ny({ zhK&}n<42<)AM&IzL1m+ht`l5t1A&nRx{D7jVCZZ;; z6(k|c?#zxLVvPZ!KA9K<6;dH88&SIL%-rM4ZoN#Zv9zSZe*oxV=Jz8HlFeolrfI$jU>Ja|s7}OUvHNV>{wVT3Vw&b+*L6n$ zv<6hgL?ZF%{{8#UN8Z^xJ3Ci1^Bw@p0;*k}=RIlLwil*9YV+jO>xt+M0BSy{5Ya}< zvfi#oh@TTkDwWbSO&bKzA5fiT_o12^aa=F}87%yv@W;-)K4d5%oFa~qEoIef1%y$yaxptf) zqNgm&+8NdtPbQN(Gw%h^3}9WkTwVlV^K>4exl?@0%pyz(0BC7xi2+zJT^=w$YntZc z|K#i++qS(f3J0a`?(P+pN~L`8;K4Egb;cmhrkrIHXM&6NGg?T1~8zOK$S}6kaCP(31Eq`wN6DfYV8DYtMa1n96562g5n=N zfu9tjy0to#>tB4|e?hU-u&8Ey~>2^ZEQ_ zBtdN3-U47bfLkofQhj5iQoaDd`9vZyG*k6^mk?rjmUYpIix)4xGB!3gG0!kR2HQ-o z>uwKZ^uAhWCJ3?|K<_N;A~ZEMJ(9^}))kAzpTe@zxpU{FVHlGDuC5NfXj#_o>9PgD zeDs=DODT`0)9DAo0y%c0363T9*@Uc0elhk8^bWHY&Lsi zLx2E4p->ptbv*@OB%o?{UH5Y$x_#ao7GdvQAw(~LS8?gTSHWYgQES9S$|3>3*~QFh z0B0M<+5asgYns-|%wIP&2ml;7aNtxt9={*J2SJ_dx_;z+WTrLK>GS{*4OTzom~Go@ s8pa}O(l3=t+lVL$V7=#gnfZ9=UxZ)Nbz081NB{r;07*qoM6N<$g4k^&m;e9( diff --git a/app/src/main/res/drawable-xhdpi/settings_call_default.png b/app/src/main/res/drawable-xhdpi/settings_call_default.png deleted file mode 100644 index 9514527808164a9541755511b77435017a89a5a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2378 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 diff --git a/app/src/main/res/drawable-xhdpi/settings_chat_default.png b/app/src/main/res/drawable-xhdpi/settings_chat_default.png deleted file mode 100644 index 8a3f155ee866e618a3f7f76731b12d72841de0f7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2391 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 JPDHLkV1n!eU^D;# diff --git a/app/src/main/res/drawable-xhdpi/settings_conferences_default.png b/app/src/main/res/drawable-xhdpi/settings_conferences_default.png deleted file mode 100644 index 97a08dba05ef59bebd6d3519e169d3f8b587b068..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12641 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/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 V)K!=@>ZJgnEUzwCDPtD&e*g)d_?iF! diff --git a/app/src/main/res/drawable-xhdpi/settings_contacts_default.png b/app/src/main/res/drawable-xhdpi/settings_contacts_default.png deleted file mode 100644 index 3f034f8e344cf0fccfdcb677664b35d65921f0c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2154 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 diff --git a/app/src/main/res/drawable-xhdpi/settings_network_default.png b/app/src/main/res/drawable-xhdpi/settings_network_default.png deleted file mode 100644 index dbafc78d7bffe62d36239085e2372912718ef446..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2188 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 T+%u87!5w(k>aW7n&UgMF%uR<8 diff --git a/app/src/main/res/drawable-xhdpi/topbar_call_notification.png b/app/src/main/res/drawable-xhdpi/topbar_call_notification.png deleted file mode 100644 index eedae47f2e983348e4917889f854a0012d80cd68..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1739 zcmV;+1~mDJP)EX>4Tx04R}tkv&MmKpe$iQ>8^J9qdqwLx$>PK~%(1t5Adrp;l;lcSTOiV`$UK|H-_ z)j97IM_5@_iO-4047wokBi9v|-#C{X7I`oqO^Zh6?)1GS_JiBZWmQL4pVc4OCEtg#@iSDJC+spY-s@9KS>^gNAlAYibdf4jJ_!k4BY|)Yi@6yeVjf3S?Vf%0~{Oz zqb15-_jq@(x3_=Kbo%=NH1u+g1-2oU00006VoOIv0RI600RN!9r;`8x010qNS#tmY z4c7nw4c7reD4Tcy000McNliru}eTl>RF? zTLfOL86-^kJAiKk#dE+{z^%aUz=&BhqOKM~=21&jpQeFft3m?Oz*{wi1aLm^zIoH9 z3E&~1HMpM!c10jjotyx+m=}He2IvP|0iN1G0%$Vt=jjYE1Y912H(eknDu)En4IJ|n zI0kel;7?nQgh*_zg#IZ>?UFjYvk^&|xB{GW8A-25S_tH>sgwa7l3Kj8-<)%E2@vR% zv|Z9U-tjMz_EyS(Rg#+W`bhdcf%0{do|SaFcRVI(n{)2_N*GXY%KHhIiK%>}q^Bi4 zY>tqmX*1|hrHW8WQdlN)aXPl7dnG;YDL-$@4>{*@l{8>M(xP|P7DM^lB<+@To)>xt zB<*s}ovCCGOG%oMl+70vNnIs)`b_z>clMd2C!KSX)#$=E0;jwh{x*;*a5n`U2s-w6 zKyS5XxRj)EOTIjHNh&l7I7!zBXXBD4s%gNaq*3qe5-+wDk>EQe&3b3+C2a>fsx^t$ z1N*&@7zg@`+;0J%35LH#;Js=Z@t8eY@{gCTCGMxpi0QzHH>+X`eEw4sAC4L01NPu7z5^uyY z!uan5e)f0=!>4SVCDy(mj2pl69 zJ!G@laDi<{YusEI3WOg{lu*TyI077x2$k3Dd0K4S5f2HV3pf$17Iy&6%YZ~k|Ja?n zn0u$exJcX$oc5R}fq}SHsRR;v$^iEPO$ql-rI7%dZGD&vytT2^m{9}?zry_hY$)ZB zB1k+Bw3PbecHpDHYk!y#30$V|&stZU@T92?x|AMbLQr zgO}qJa93RBt0OAMB=Azw>a>LI`IkXBAIh)%;|l2mzK;0tal|@!!wM=X9F` zeCHgG+CRBvFrrb?wUV|-8VFYP@>Y+hC7rVE_n7@gB~96#w4{sew_4IwwtSXt5ATt5 z&^gCSzN>En-m!PY0;MSz{Gw>i8lY1p$fns|;C|o}Qy@oqv9C002ovPDHLkV1mFq9k&1g diff --git a/app/src/main/res/drawable-xhdpi/topbar_call_paused_notification.png b/app/src/main/res/drawable-xhdpi/topbar_call_paused_notification.png deleted file mode 100644 index dc53b138253c944533e117a67cff8f9beecffc20..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 756 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezr3(Ft%hmI|q0=J1Z0v<)>xlq%tsQ zOst(~>v7mY=4gEMQKhMTWfL9=EU}6dnEGf%sD@K(fcBPG2}Li(O)@=#Vq{paH#N(5 zhq8;O$I8aAI(}e2a`K|*MOUvs@j)4aj}I2w-@99VXD{=gvs%Gt=W#k*)lr`8#kKR# zstez{6c%wLvv79K{53~o*+a{H?;bvW9Xltza{ljP9u3yXjCPF`(_Xf@?s1%RV@t`o z-$w+)rinZ%epJ}3=ke!6>CLC+=TzJO+88IL7=Jz@n6&W9(IWB9n-&WB3m+E=U0hRt zF17WuSo+qe^taWY#M`)Toie(f$*0`vs^i$gAXyp`(AsLTR!URNXv_RBAKYd4d+5!K z5j7~%S->Vb=`s8N=KFVN9bM^Zt~rIJDn93TvdD|utWmtT|JiMOdyP||f^FTFzWvu& z((F9*OSaq)6u8Hxx6XRj9`=}b49@<$Qat)Z7QECKYy58fYnhEf1^1WrJNIt=Uigb4 zW##HGyafduJySKR?>@eJ@ZG(CwwK?%XFrfobWFM!6dw$1N#5=*4F5rJ!QSPQfg+p* z9+Abs7`zU`j7}P}D}aLRC7!;n>`yqP_*L1B<#@}1LLWU{978hhy}jkgV>Yow73wg4wPmGn*+$Z(uBD=AN*LX=#rKn^{7`e$L9hBHyP@KWMpm{k@&F`>U7x zdkHRJ>1j~j!0^k#Zh<_bYy#h$2F`%+Fh;fmt>M=kj2FbSg>C%DE3y2h>hwz&ritwT z*tI0eVZYK!!P&~5dvzFFkI1mjn_B<5R?+*w{UZ#_JO&2{5&KSB8Y;h(y0G74SFOe8 zeI+;z_u#0Y$tP?de4*t Th3C%#JgTe~DWM4f{ERnF diff --git a/app/src/main/res/drawable-xhdpi/topbar_chat_notification.png b/app/src/main/res/drawable-xhdpi/topbar_chat_notification.png deleted file mode 100644 index b21cc706c5bcc326a588c02741461527d3b001f9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2032 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 diff --git a/app/src/main/res/drawable-xhdpi/topbar_videocall_notification.png b/app/src/main/res/drawable-xhdpi/topbar_videocall_notification.png deleted file mode 100644 index ca72e9d318a94bdd783bf720787c65ea6e6b91bc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1176 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 diff --git a/app/src/main/res/drawable-xhdpi/voicemail.png b/app/src/main/res/drawable-xhdpi/voicemail.png deleted file mode 100644 index 63d88099319e93e4056d6a6b592ef2d10d0ffa2d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1323 zcmV+`1=RY9P);kY3KnH+w zw7dY|Cjet2avaMO6*_r0Hy$ZEh4pM0O=*e7f3Tj8m4@cWVn-LZ0HD93Z)aWW@)VTS;>$gOP0CA(FTF@+Oi)q?yTy?FvxrQ_owwRGo$6C_NT6F9pS!w{^0BQbIbQ_YJY(iB_Hk>FTvqj$f zodwi}bop?%#aPc2lyNs(lE^pb-71non!^h736kvvJ_FiG^PA%@)Rf4-d@&%U&BD-u0FxrnVN3&wQcXvLv8a6{dqlo?gP6k1F2siiN+d zCk;OAsdBhc_hxp-t$Ivhc)(-b*RN$v3$Nz=vm`I{htG^E_80}YTG7hbrbg1iuYM%ZBALc+;jJ~wAgt$A4?GR;4=i;!W~E3L&1Wd zm9-wv;5q?ZkXSn*B7Xz`wBTDMx!U6iR-&F+hG4EG2X6LkFirnB00biPBY@heS$`*o@`$u!Og* z2lyg@P5(hd{SI&!5WFlRvw3<=F2nT#W*Z>){mZZFxhPJ(2VjqgOgGY;%W6FBCz~ss hPUG*NZ04F-`~@#1FwdKtr|$p&002ovPDHLkV1jmYX(<2z diff --git a/app/src/main/res/drawable-xhdpi/voip_audio_routes.png b/app/src/main/res/drawable-xhdpi/voip_audio_routes.png deleted file mode 100644 index fae4e84e3472a1f503c6af38e8d2f708eace4118..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3729 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 aJJd>j>Wg`g&As*SyJTWuu20l;jr)K6n-x+3 diff --git a/app/src/main/res/drawable-xhdpi/voip_call_add.png b/app/src/main/res/drawable-xhdpi/voip_call_add.png deleted file mode 100644 index 8237f3305006cb821191df94a5a40462c0a54aaf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14157 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 TnL_or{{|?@sms<#TRi_?#`x`Z diff --git a/app/src/main/res/drawable-xhdpi/voip_call_chat.png b/app/src/main/res/drawable-xhdpi/voip_call_chat.png deleted file mode 100644 index be4a138de24fbea3ee93f74dbd7f65c19ac4201b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13663 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 diff --git a/app/src/main/res/drawable-xhdpi/voip_call_header_active.png b/app/src/main/res/drawable-xhdpi/voip_call_header_active.png deleted file mode 100644 index 3ef0106bc98c795e193286178cfd14d36ae83b82..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1787 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 diff --git a/app/src/main/res/drawable-xhdpi/voip_call_header_outgoing.png b/app/src/main/res/drawable-xhdpi/voip_call_header_outgoing.png deleted file mode 100644 index 474abe754b0051cfdd73021362c92862781495db..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1274 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 E1$P2pyZ`_I diff --git a/app/src/main/res/drawable-xhdpi/voip_call_more.png b/app/src/main/res/drawable-xhdpi/voip_call_more.png deleted file mode 100644 index 73bb13b363d3bc0cab33b8fe1ce80dde319c8aa6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10488 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 diff --git a/app/src/main/res/drawable-xhdpi/voip_call_numpad.png b/app/src/main/res/drawable-xhdpi/voip_call_numpad.png deleted file mode 100644 index f6e562387d0a4941a80b00094c63dfc1f7a6e5ba..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19610 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 ikX) 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 Qe&^k?l1dVlVur#03nY?;dH?_b diff --git a/app/src/main/res/drawable-xhdpi/voip_call_record.png b/app/src/main/res/drawable-xhdpi/voip_call_record.png deleted file mode 100644 index 9c392dc4777ce1e62c22ae43db8a6b234bebed97..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3890 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 V>Ed?GC2Fq%=xG_EOEqjm{tKd>sGR@+ diff --git a/app/src/main/res/drawable-xhdpi/voip_calls_list.png b/app/src/main/res/drawable-xhdpi/voip_calls_list.png deleted file mode 100644 index a3d69b84eed51b5a68c5e211ab2d0251c220d932..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14703 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 diff --git a/app/src/main/res/drawable-xhdpi/voip_camera_off.png b/app/src/main/res/drawable-xhdpi/voip_camera_off.png deleted file mode 100644 index dd41c338be4dc0a32759dce59bb48f26f1a35c75..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2971 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 V;L|R3regpA002ovPDHLkV1nCSOc4M8 diff --git a/app/src/main/res/drawable-xhdpi/voip_chat_rooms_list.png b/app/src/main/res/drawable-xhdpi/voip_chat_rooms_list.png deleted file mode 100644 index edf722c7e5edda4a64fc62f01a43e61aaa3e6136..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4105 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 diff --git a/app/src/main/res/drawable-xhdpi/voip_conference.png b/app/src/main/res/drawable-xhdpi/voip_conference.png deleted file mode 100644 index a2bbc39e68e7681c206a8e611d1f00ec1dfe8d80..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9289 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 diff --git a/app/src/main/res/drawable-xhdpi/voip_conference_active_speaker.png b/app/src/main/res/drawable-xhdpi/voip_conference_active_speaker.png deleted file mode 100644 index 18f21106aafa24bd39aef95cbcd10a7bd55f0d99..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7818 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