├── .circleci └── config.yml ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── ACKNOWLEDGEMENTS.md ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── Twilions.md ├── app ├── build.gradle ├── gradle.properties ├── lint-baseline.xml ├── proguard-rules.pro ├── src │ ├── androidTest │ │ ├── AndroidManifest.xml │ │ ├── assets │ │ │ └── Credentials │ │ │ │ └── TestCredentials.json.example │ │ └── java │ │ │ └── com │ │ │ └── twilio │ │ │ └── video │ │ │ └── app │ │ │ ├── TestCredentials.kt │ │ │ ├── e2eTest │ │ │ ├── AppLinkRoomTest.kt │ │ │ ├── BackgroundSupportTest.kt │ │ │ ├── BaseE2ETest.kt │ │ │ ├── E2ETest.kt │ │ │ ├── LoginTest.kt │ │ │ ├── PermissionTest.kt │ │ │ └── RoomTest.kt │ │ │ ├── espresso │ │ │ ├── DrawableMatcher.kt │ │ │ └── HiddenView.kt │ │ │ ├── integrationTest │ │ │ ├── BaseIntegrationTest.kt │ │ │ ├── CameraCapturerCompatTest.kt │ │ │ ├── IntegrationTest.kt │ │ │ └── PreferenceIntegrationTest.kt │ │ │ ├── screen │ │ │ ├── LobbyScreen.kt │ │ │ └── LoginScreen.kt │ │ │ └── util │ │ │ ├── EspressoUtil.kt │ │ │ ├── TestUtil.kt │ │ │ └── UiAutomatorExtensions.kt │ ├── community │ │ ├── AndroidManifest.xml │ │ ├── google-services.json │ │ ├── ic_launcher-web.png │ │ ├── java │ │ │ └── com │ │ │ │ └── twilio │ │ │ │ └── video │ │ │ │ └── app │ │ │ │ ├── auth │ │ │ │ ├── CommunityAuthModule.kt │ │ │ │ ├── CommunityAuthenticator.kt │ │ │ │ └── CommunityLoginResult.kt │ │ │ │ ├── data │ │ │ │ ├── CommunityAuthServiceModule.kt │ │ │ │ ├── CommunityPreferences.kt │ │ │ │ └── api │ │ │ │ │ ├── AuthService.kt │ │ │ │ │ └── AuthServiceRepository.kt │ │ │ │ ├── security │ │ │ │ ├── SecurePreferences.kt │ │ │ │ ├── SecurePreferencesImpl.kt │ │ │ │ └── SecurityModule.kt │ │ │ │ └── ui │ │ │ │ ├── CommunityScreenSelector.kt │ │ │ │ ├── CommunityScreenSelectorModule.java │ │ │ │ └── login │ │ │ │ └── CommunityLoginActivity.java │ │ └── res │ │ │ ├── drawable │ │ │ └── ic_launcher_background.xml │ │ │ ├── layout │ │ │ └── community_login_activity.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ └── values │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ ├── internal │ │ ├── AndroidManifest.xml │ │ ├── debug │ │ │ └── .gitkeep │ │ ├── java │ │ │ └── com │ │ │ │ └── twilio │ │ │ │ └── video │ │ │ │ └── app │ │ │ │ ├── auth │ │ │ │ └── AuthModule.java │ │ │ │ ├── data │ │ │ │ └── api │ │ │ │ │ └── AuthServiceModule.java │ │ │ │ └── ui │ │ │ │ └── ScreenSelectorModule.java │ │ └── release │ │ │ └── .gitkeep │ ├── internalDebug │ │ ├── ic_launcher-web.png │ │ └── res │ │ │ ├── drawable │ │ │ └── ic_launcher_background.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ └── values │ │ │ └── strings.xml │ ├── internalRelease │ │ ├── ic_launcher-web.png │ │ └── res │ │ │ ├── drawable │ │ │ └── ic_launcher_background.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ └── values │ │ │ └── strings.xml │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── ic_launcher-web.png │ │ ├── java │ │ │ └── com │ │ │ │ └── twilio │ │ │ │ └── video │ │ │ │ └── app │ │ │ │ ├── ApplicationScope.java │ │ │ │ ├── TreeModule.java │ │ │ │ ├── VideoApplication.kt │ │ │ │ ├── adapter │ │ │ │ └── StatsListAdapter.kt │ │ │ │ ├── android │ │ │ │ └── SharedPreferencesWrapper.kt │ │ │ │ ├── auth │ │ │ │ ├── AuthenticationException.kt │ │ │ │ ├── AuthenticationProvider.kt │ │ │ │ ├── Authenticator.kt │ │ │ │ ├── EmailAuthProvider.kt │ │ │ │ ├── FirebaseAuthenticator.kt │ │ │ │ ├── FirebaseWrapper.kt │ │ │ │ ├── GoogleAuthProvider.kt │ │ │ │ ├── GoogleAuthProviderWrapper.kt │ │ │ │ ├── GoogleAuthWrapper.kt │ │ │ │ ├── GoogleSignInOptionsWrapper.kt │ │ │ │ ├── GoogleSignInWrapper.kt │ │ │ │ ├── InternalLoginResult.kt │ │ │ │ ├── LoginEvent.kt │ │ │ │ └── LoginResult.kt │ │ │ │ ├── data │ │ │ │ ├── DataModule.kt │ │ │ │ ├── NumberPreference.java │ │ │ │ ├── NumberPreferenceDialogFragmentCompat.java │ │ │ │ ├── Preferences.kt │ │ │ │ └── api │ │ │ │ │ ├── AuthServiceError.kt │ │ │ │ │ ├── AuthServiceException.kt │ │ │ │ │ ├── FirebaseAuthInterceptor.java │ │ │ │ │ ├── InternalTokenApi.kt │ │ │ │ │ ├── InternalTokenService.kt │ │ │ │ │ ├── TokenService.kt │ │ │ │ │ ├── TwilioApiEnvironment.kt │ │ │ │ │ └── dto │ │ │ │ │ ├── AuthServiceErrorDTO.kt │ │ │ │ │ ├── AuthServiceRequestDTO.kt │ │ │ │ │ ├── AuthServiceResponseDTO.kt │ │ │ │ │ └── Topology.kt │ │ │ │ ├── model │ │ │ │ └── StatsListItem.java │ │ │ │ ├── participant │ │ │ │ ├── ParticipantManager.kt │ │ │ │ └── ParticipantViewState.kt │ │ │ │ ├── sdk │ │ │ │ ├── ConnectOptionsFactory.kt │ │ │ │ ├── LocalParticipantListener.kt │ │ │ │ ├── LocalParticipantManager.kt │ │ │ │ ├── RemoteParticipantListener.kt │ │ │ │ ├── RoomManager.kt │ │ │ │ ├── RoomStats.kt │ │ │ │ ├── StatsScheduler.kt │ │ │ │ ├── VideoClient.kt │ │ │ │ ├── VideoSdkModule.kt │ │ │ │ └── VideoTrackViewState.kt │ │ │ │ ├── ui │ │ │ │ ├── ProductionScreenSelector.kt │ │ │ │ ├── ScreenSelector.kt │ │ │ │ ├── login │ │ │ │ │ └── LoginActivity.kt │ │ │ │ ├── room │ │ │ │ │ ├── ClearableEditText.java │ │ │ │ │ ├── ParticipantAdapter.kt │ │ │ │ │ ├── ParticipantPrimaryView.kt │ │ │ │ │ ├── ParticipantThumbView.java │ │ │ │ │ ├── ParticipantView.java │ │ │ │ │ ├── ParticipantViewHolder.kt │ │ │ │ │ ├── PrimaryParticipantController.kt │ │ │ │ │ ├── RoomActivity.kt │ │ │ │ │ ├── RoomEvent.kt │ │ │ │ │ ├── RoomNotification.kt │ │ │ │ │ ├── RoomViewEffect.kt │ │ │ │ │ ├── RoomViewEvent.kt │ │ │ │ │ ├── RoomViewModel.kt │ │ │ │ │ ├── RoomViewModelModule.kt │ │ │ │ │ ├── RoomViewState.kt │ │ │ │ │ ├── UriRoomParser.kt │ │ │ │ │ ├── UriWrapper.kt │ │ │ │ │ └── VideoService.kt │ │ │ │ ├── settings │ │ │ │ │ ├── AdvancedSettingsFragment.kt │ │ │ │ │ ├── AudioSettingsFragment.kt │ │ │ │ │ ├── BandwidthProfileSettingsFragment.kt │ │ │ │ │ ├── BaseSettingsFragment.kt │ │ │ │ │ ├── InternalSettingsFragment.kt │ │ │ │ │ ├── SettingsActivity.kt │ │ │ │ │ └── SettingsFragment.kt │ │ │ │ └── splash │ │ │ │ │ └── SplashActivity.kt │ │ │ │ └── util │ │ │ │ ├── BuildConfigUtils.kt │ │ │ │ ├── CameraCapturerCompat.kt │ │ │ │ ├── CompositeDisposableExtensions.kt │ │ │ │ ├── CrashlyticsTreeRanger.java │ │ │ │ ├── DebugTree.java │ │ │ │ ├── EnvUtil.java │ │ │ │ ├── FragmentManagerExtensions.kt │ │ │ │ ├── InputUtils.java │ │ │ │ ├── PermissionUtil.kt │ │ │ │ ├── ReleaseTree.java │ │ │ │ ├── SharedPreferencesUtil.kt │ │ │ │ └── TreeRanger.java │ │ └── res │ │ │ ├── drawable-anydpi │ │ │ └── ic_videocam_notification.xml │ │ │ ├── drawable-hdpi │ │ │ ├── twilio_name_white.png │ │ │ └── video_logo_splash.png │ │ │ ├── drawable-mdpi │ │ │ ├── network_quality_level_0.png │ │ │ ├── network_quality_level_1.png │ │ │ ├── network_quality_level_2.png │ │ │ ├── network_quality_level_3.png │ │ │ ├── network_quality_level_4.png │ │ │ ├── network_quality_level_5.png │ │ │ ├── twilio_name_white.png │ │ │ └── video_logo_splash.png │ │ │ ├── drawable-xhdpi │ │ │ ├── network_quality_level_0.png │ │ │ ├── network_quality_level_1.png │ │ │ ├── network_quality_level_2.png │ │ │ ├── network_quality_level_3.png │ │ │ ├── network_quality_level_4.png │ │ │ ├── network_quality_level_5.png │ │ │ ├── twilio_name_white.png │ │ │ └── video_logo_splash.png │ │ │ ├── drawable-xxhdpi │ │ │ ├── network_quality_level_0.png │ │ │ ├── network_quality_level_1.png │ │ │ ├── network_quality_level_2.png │ │ │ ├── network_quality_level_3.png │ │ │ ├── network_quality_level_4.png │ │ │ ├── network_quality_level_5.png │ │ │ ├── twilio_name_white.png │ │ │ └── video_logo_splash.png │ │ │ ├── drawable-xxxhdpi │ │ │ ├── twilio_name_white.png │ │ │ └── video_logo_splash.png │ │ │ ├── drawable │ │ │ ├── badge_background.xml │ │ │ ├── edit_text_cursor.xml │ │ │ ├── ic_account_circle_white_24dp.xml │ │ │ ├── ic_account_circle_white_48px.xml │ │ │ ├── ic_add_circle_white_24px.xml │ │ │ ├── ic_bluetooth_white_24dp.xml │ │ │ ├── ic_call_black_24dp.xml │ │ │ ├── ic_call_end_white_24px.xml │ │ │ ├── ic_call_white_24px.xml │ │ │ ├── ic_close_white_24dp.xml │ │ │ ├── ic_error_outline.xml │ │ │ ├── ic_exit_to_app_white_24px.xml │ │ │ ├── ic_headset_mic_white_24dp.xml │ │ │ ├── ic_launcher_background.xml │ │ │ ├── ic_mic_green_24px.xml │ │ │ ├── ic_mic_off_gray_24px.xml │ │ │ ├── ic_mic_off_red_24px.xml │ │ │ ├── ic_mic_red_24px.xml │ │ │ ├── ic_mic_white_24px.xml │ │ │ ├── ic_more_vert_white_24dp.xml │ │ │ ├── ic_pause_green_24px.xml │ │ │ ├── ic_pause_red_24px.xml │ │ │ ├── ic_phonelink_ring_white_24dp.xml │ │ │ ├── ic_pin.xml │ │ │ ├── ic_play_white_24px.xml │ │ │ ├── ic_recording.xml │ │ │ ├── ic_screen_share_white_24dp.xml │ │ │ ├── ic_stats_disabled_image.xml │ │ │ ├── ic_stop_screen_share_white_24dp.xml │ │ │ ├── ic_switch_camera_512dp.xml │ │ │ ├── ic_switch_camera_white_24dp.xml │ │ │ ├── ic_thumbnail_no_audio.xml │ │ │ ├── ic_videocam_green_24px.xml │ │ │ ├── ic_videocam_off_gray_24px.xml │ │ │ ├── ic_videocam_off_red_24px.xml │ │ │ ├── ic_videocam_white_24px.xml │ │ │ ├── ic_volume_down_gray_24px.xml │ │ │ ├── ic_volume_down_green_24px.xml │ │ │ ├── ic_volume_down_white_24px.xml │ │ │ ├── ic_volume_up_white_24dp.xml │ │ │ ├── join_button.xml │ │ │ ├── participant_background.xml │ │ │ ├── participant_selected_background.xml │ │ │ ├── participant_stroke.xml │ │ │ ├── roundbutton.xml │ │ │ ├── splash_screen.xml │ │ │ └── twilio_badge_white.xml │ │ │ ├── layout │ │ │ ├── content_room.xml │ │ │ ├── join_room.xml │ │ │ ├── number_preference.xml │ │ │ ├── participant_primary_view.xml │ │ │ ├── participant_view.xml │ │ │ ├── room_activity.xml │ │ │ └── stats_view.xml │ │ │ ├── menu │ │ │ └── room_menu.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ │ ├── values-w820dp │ │ │ └── dimens.xml │ │ │ ├── values │ │ │ ├── attrs.xml │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ │ └── xml │ │ │ ├── advanced_preferences.xml │ │ │ ├── audio_preferences.xml │ │ │ ├── bandwidth_profile_preferences.xml │ │ │ ├── internal_preferences.xml │ │ │ └── preferences.xml │ ├── release │ │ └── res │ │ │ └── values │ │ │ └── strings.xml │ ├── test │ │ ├── java │ │ │ └── com │ │ │ │ └── twilio │ │ │ │ └── video │ │ │ │ └── app │ │ │ │ ├── BaseUnitTest.kt │ │ │ │ ├── auth │ │ │ │ └── GoogleAuthProviderTest.kt │ │ │ │ ├── data │ │ │ │ └── api │ │ │ │ │ ├── AuthServiceErrorTest.kt │ │ │ │ │ └── VideoAppServiceDelegateTest.kt │ │ │ │ ├── participant │ │ │ │ └── ParticipantManagerTest.kt │ │ │ │ ├── ui │ │ │ │ └── room │ │ │ │ │ ├── RoomViewModelTest.kt │ │ │ │ │ └── UriRoomParserTest.kt │ │ │ │ └── util │ │ │ │ └── MainCoroutineScopeRule.kt │ │ └── resources │ │ │ ├── mockito-extensions │ │ │ └── org.mockito.plugins.MockMaker │ │ │ └── robolectric.properties │ └── testCommunity │ │ └── java │ │ └── com │ │ └── twilio │ │ └── video │ │ └── app │ │ ├── data │ │ └── api │ │ │ └── AuthServiceRepositoryTest.kt │ │ ├── fake │ │ ├── FakeSecurityModule.kt │ │ ├── MockCommunityAuthModule.kt │ │ └── MockCommunityAuthServiceModule.kt │ │ ├── screen │ │ └── CommunityLoginScreen.kt │ │ ├── security │ │ └── SecurePreferencesFake.kt │ │ ├── ui │ │ └── login │ │ │ └── CommunityLoginActivityTest.kt │ │ └── util │ │ ├── AuthServiceTestData.kt │ │ └── TextInputLayoutMatcher.kt └── video-android-app.keystore ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── community-variant │ └── community-variant.png └── emulator │ ├── emulator_avd_settings.png │ ├── emulator_navigate.png │ ├── emulator_select_hardware.png │ ├── emulator_select_image.png │ └── emulator_virtual_device.png ├── settings.gradle └── ui-test-args.yaml /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: 5 | labels: "bug" 6 | assignees: 7 | 8 | --- 9 | 10 | 11 | > Before filing an issue please check that the issue is not already addressed by the following: 12 | > * [Video Guides](https://www.twilio.com/docs/api/video) 13 | > * [Github Issues](https://github.com/twilio/twilio-video-app-android/issues) 14 | > * [Changelog](https://github.com/twilio/twilio-video-app-android/blob/master/CHANGELOG.md) 15 | 16 | > Please ensure that you are not sharing any 17 | [Personally Identifiable Information(PII)](https://www.twilio.com/docs/glossary/what-is-personally-identifiable-information-pii) 18 | or sensitive account information (API keys, credentials, etc.) when reporting an issue. 19 | 20 | **Describe the bug** 21 | A clear and concise description of what the bug is. 22 | 23 | **To Reproduce** 24 | Steps to reproduce the behavior: 25 | 1. Go to '...' 26 | 2. Click on '....' 27 | 3. Scroll down to '....' 28 | 4. See error 29 | 30 | **Expected behavior** 31 | A clear and concise description of what you expected to happen. 32 | 33 | **Screenshots** 34 | If applicable, add screenshots to help explain your problem. 35 | 36 | **Android Device (please complete the following information):** 37 | - Device: (e.g. Pixel 4) 38 | - API Version: (e.g. API 29) 39 | 40 | **Video Android SDK (please complete the following information):** 41 | - Version: (e.g. 5.1.0) 42 | 43 | **Additional context** 44 | Add any other context about the problem here. 45 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: 5 | labels: "feature-request" 6 | assignees: 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | [Description of the Pull Request] 4 | 5 | ## Breakdown 6 | 7 | - [Bulleted summary of changes] 8 | - [eg. Add new public function to `Authenticator.kt` ] 9 | - [eg. Add new string resources to `strings.xml`] 10 | 11 | ## Validation 12 | 13 | - [Bulleted summary of validation steps] 14 | - [eg. Add new unit tests to validate changes] 15 | - [eg. Verified all CI checks pass on the feature branch] 16 | 17 | ## Additional Notes 18 | 19 | [Any additional comments, notes, or information relevant to reviewers.] 20 | 21 | **Contributing to Twilio** 22 | 23 | > All third-party contributors acknowledge that any contributions they provide will be made under the same open-source license that the open-source project is provided under. 24 | 25 | - [ ] I acknowledge that all my contributions will be made under the project's license. 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | sdk/local.properties 2 | sdk/libs/armeabi-v7a 3 | sdk/obj 4 | sdk/gen 5 | sdk/.settings 6 | sdk/bin 7 | output 8 | 9 | dist/ 10 | target/ 11 | docs/ 12 | 13 | # Files for the Dalvik VM 14 | *.dex 15 | 16 | # Java class files 17 | *.class 18 | 19 | # Generated files 20 | bin/ 21 | gen/ 22 | 23 | # Gradle files 24 | .gradle/ 25 | build/ 26 | 27 | # Local configuration file (sdk path, etc) 28 | local.properties 29 | 30 | # Proguard folder generated by Eclipse 31 | proguard/ 32 | 33 | tags 34 | obj 35 | 36 | .vagrant 37 | depot_tools 38 | 39 | .gradle 40 | local.properties 41 | .idea 42 | *.iml 43 | build 44 | .DS_Store 45 | sdk/target/* 46 | sdk/libs/ 47 | *~ 48 | *.swp 49 | *.swo 50 | **/**/**/libs 51 | 52 | # Screenshots 53 | .gradletasknamecache 54 | 55 | # Cmake 56 | **/.externalNativeBuild 57 | 58 | # Google Play Services 59 | **/google-services.json 60 | !app/src/community/google-services.json 61 | 62 | # CI Secrets 63 | secrets.tar.gz 64 | secrets.tar 65 | secrets/ 66 | 67 | # CXX directories 68 | **/.cxx/ 69 | 70 | # Test files 71 | captures/ 72 | app/src/androidTest/assets/Credentials/TestCredentials.json -------------------------------------------------------------------------------- /ACKNOWLEDGEMENTS.md: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | 3 | This software includes the following software dependencies under their corresponding licenses: 4 | 5 | - Twilio Video Android - https://www.twilio.com/legal/tos. 6 | 7 | - Generic [Apache 2.0 License](https://www.apache.org/licenses/LICENSE-2.0.txt). 8 | 9 | - Timber 10 | - MaterialRangeBar 11 | - Butter Knife 12 | - Guava 13 | - Dagger 14 | - RxJava 15 | - Retrofit 16 | - Kotlin 17 | 18 | Android SDK and Google Libraries - https://developer.android.com/studio/terms.html. 19 | 20 | Firebase - https://cloud.google.com/terms/. 21 | 22 | Crashlytics - https://firebase.google.com/terms/crashlytics/. 23 | -------------------------------------------------------------------------------- /Twilions.md: -------------------------------------------------------------------------------- 1 | # Twilions 2 | 3 | Twilio employees should follow these instructions for building and testing the Twilio and Internal build variants. 4 | 5 | ## Build 6 | 7 | Download the google-services.json files here: 8 | * [Internal Debug (default)](https://console.firebase.google.com/project/video-app-79418/settings/general/android:com.twilio.video.app.internal.debug) - Download to `app/src/internal/debug` 9 | * [Internal Release](https://console.firebase.google.com/project/video-app-79418/settings/general/android:com.twilio.video.app.internal) - Download to `app/src/internal/release` 10 | 11 | After copying the above files the internal variant should build with no errors. However, when building the Twilio flavor, the app signing keystore is required. Please reach out to a developer on the team to get access to it. 12 | 13 | ## UI Tests 14 | 15 | ### Credentials 16 | 17 | 1. Make `Credentials` directory within the ```app/src/androidTest/assets``` directory. 18 | 1. Copy `TestCredentials.json.example` to `TestCredentials.json` within the ```Credentials``` directory and insert correct values for `email_sign_in_user`. 19 | 20 | ### Run 21 | 22 | * Android Studio - Right click and run unit tests on package ```app/src/androidTest/java/com/twilio/video/app``` 23 | * Terminal - ```./gradlew app:connectedInternalDebugAndroidTest``` 24 | -------------------------------------------------------------------------------- /app/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | org.gradle.jvmargs=-Xmx20496m 15 | 16 | # When configured, Gradle will run in incubating parallel mode. 17 | # This option should only be used with decoupled projects. More details, visit 18 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 19 | # org.gradle.parallel=true 20 | 21 | VERSION_NAME=0.8.0 22 | VERSION_CODE=298 23 | android.useAndroidX=true 24 | android.enableJetifier=true 25 | android.useFullClasspathForDexingTransform = true -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | -keep class tvi.webrtc.** { *; } 2 | -dontwarn tvi.webrtc.** 3 | -keep class com.twilio.video.** { *; } 4 | -keep class com.twilio.common.** { *; } 5 | -keepattributes InnerClasses 6 | 7 | # https://github.com/firebase/firebase-android-sdk/issues/4900#issuecomment-1520001376 8 | -keep class com.google.android.gms.internal.** { *; } 9 | 10 | # Facebook Conceal proguard config 11 | -keep class com.facebook.crypto.** { *; } 12 | -keep class com.facebook.jni.** { *; } 13 | -keepclassmembers class com.facebook.cipher.jni.** { *; } 14 | 15 | # Keep generic signature of Call, Response (R8 full mode strips signatures from non-kept items). 16 | -keep,allowobfuscation,allowshrinking interface retrofit2.Call 17 | -keep,allowobfuscation,allowshrinking class retrofit2.Response 18 | 19 | # With R8 full mode generic signatures are stripped for classes that are not 20 | # kept. Suspend functions are wrapped in continuations where the type argument 21 | # is used. 22 | -keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation -------------------------------------------------------------------------------- /app/src/androidTest/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/androidTest/assets/Credentials/TestCredentials.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "email_sign_in_user": { 3 | "email": null, 4 | "password": null 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/twilio/video/app/TestCredentials.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app 2 | 3 | data class TestCredentials( 4 | val email_sign_in_user: EmailCredentials, 5 | ) 6 | 7 | data class EmailCredentials( 8 | val email: String, 9 | val password: String, 10 | ) 11 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/twilio/video/app/e2eTest/AppLinkRoomTest.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.e2eTest 2 | 3 | import android.content.Intent 4 | import android.net.Uri 5 | import androidx.test.ext.junit.runners.AndroidJUnit4 6 | import androidx.test.filters.LargeTest 7 | import androidx.test.rule.ActivityTestRule 8 | import androidx.test.rule.GrantPermissionRule 9 | import com.twilio.video.app.R 10 | import com.twilio.video.app.screen.assertRoomNameIsDisplayed 11 | import com.twilio.video.app.screen.loginWithEmail 12 | import com.twilio.video.app.ui.splash.SplashActivity 13 | import com.twilio.video.app.util.getString 14 | import com.twilio.video.app.util.retrieveEmailCredentials 15 | import com.twilio.video.app.util.retryEspressoAction 16 | import org.junit.Rule 17 | import org.junit.Test 18 | import org.junit.runner.RunWith 19 | 20 | @RunWith(AndroidJUnit4::class) 21 | @LargeTest 22 | @E2ETest 23 | class AppLinkRoomTest { 24 | 25 | @get:Rule 26 | var permissionRule = GrantPermissionRule.grant( 27 | android.Manifest.permission.CAMERA, 28 | android.Manifest.permission.RECORD_AUDIO, 29 | ) 30 | 31 | @get:Rule 32 | var rule: ActivityTestRule = 33 | ActivityTestRule( 34 | SplashActivity::class.java, 35 | true, 36 | false, 37 | ) 38 | 39 | @Test 40 | fun `room_app_link_should_navigate_to_room_screen_with_room_name_populated`() { 41 | val roomName = "test" 42 | val intent = Intent( 43 | Intent.ACTION_VIEW, 44 | Uri.parse("https://${getString(R.string.web_app_domain)}/room/$roomName"), 45 | ) 46 | rule.launchActivity(intent) 47 | 48 | val emailCredentials = retrieveEmailCredentials() 49 | 50 | loginWithEmail(emailCredentials) 51 | 52 | retryEspressoAction { assertRoomNameIsDisplayed(roomName) } 53 | 54 | restartActivity(intent) 55 | 56 | retryEspressoAction { assertRoomNameIsDisplayed(roomName) } 57 | } 58 | 59 | private fun restartActivity(intent: Intent) { 60 | rule.finishActivity() 61 | rule.launchActivity(intent) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/twilio/video/app/e2eTest/BackgroundSupportTest.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.e2eTest 2 | 3 | import androidx.test.ext.junit.rules.activityScenarioRule 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | import androidx.test.filters.LargeTest 6 | import androidx.test.uiautomator.By 7 | import androidx.test.uiautomator.UiSelector 8 | import androidx.test.uiautomator.Until 9 | import com.twilio.video.app.R 10 | import com.twilio.video.app.screen.assertRoomIsConnected 11 | import com.twilio.video.app.screen.clickDisconnectButton 12 | import com.twilio.video.app.screen.clickJoinRoomButton 13 | import com.twilio.video.app.screen.enterRoomName 14 | import com.twilio.video.app.ui.splash.SplashActivity 15 | import com.twilio.video.app.util.getString 16 | import com.twilio.video.app.util.randomUUID 17 | import com.twilio.video.app.util.retryEspressoAction 18 | import com.twilio.video.app.util.uiDevice 19 | import org.junit.Rule 20 | import org.junit.Test 21 | import org.junit.runner.RunWith 22 | 23 | @RunWith(AndroidJUnit4::class) 24 | @LargeTest 25 | @E2ETest 26 | class BackgroundSupportTest : BaseE2ETest() { 27 | 28 | @get:Rule 29 | var scenario = activityScenarioRule() 30 | 31 | @Test 32 | fun it_should_show_a_notification_when_the_app_is_in_the_background() { 33 | enterRoomName(randomUUID()) 34 | clickJoinRoomButton() 35 | 36 | retryEspressoAction { assertRoomIsConnected() } 37 | 38 | uiDevice().run { 39 | pressHome() 40 | 41 | openNotification() 42 | 43 | // Click notification 44 | wait(Until.hasObject(By.pkg("com.android.systemui")), 10000) 45 | val notificationStackScroller = UiSelector() 46 | .resourceId("com.android.systemui:id/notification_stack_scroller") 47 | findObject(notificationStackScroller) 48 | .getChild( 49 | UiSelector().textContains( 50 | getString(R.string.room_notification_message), 51 | ), 52 | ) 53 | .click() 54 | } 55 | 56 | retryEspressoAction { assertRoomIsConnected() } 57 | 58 | clickDisconnectButton() 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/twilio/video/app/e2eTest/BaseE2ETest.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.e2eTest 2 | 3 | import android.app.Activity 4 | import android.util.Log 5 | import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation 6 | import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry 7 | import androidx.test.runner.lifecycle.Stage.RESUMED 8 | import com.twilio.video.app.screen.loginWithEmail 9 | import com.twilio.video.app.util.allowAllPermissions 10 | import com.twilio.video.app.util.retrieveEmailCredentials 11 | import com.twilio.video.app.util.uiDevice 12 | import org.junit.Before 13 | import java.util.concurrent.TimeoutException 14 | 15 | @E2ETest 16 | open class BaseE2ETest { 17 | @Before 18 | open fun setUp() { 19 | // wait for google/firebase auth activity to overlay 20 | waitUntilActivityVisible(5000) 21 | // start test 22 | loginWithEmail(retrieveEmailCredentials()) 23 | uiDevice().run { 24 | try { 25 | allowAllPermissions() 26 | } catch (e: TimeoutException) { 27 | Log.w("VideoApiUtils", "Permissions dialog not detected") 28 | // try running the test anyway 29 | } 30 | } 31 | } 32 | 33 | open fun getActivityInstance(): Activity? { 34 | var currentActivity: Activity? = null 35 | getInstrumentation().runOnMainSync { 36 | val resumedActivities: Collection<*> = 37 | ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(RESUMED) 38 | if (resumedActivities.iterator().hasNext()) { 39 | currentActivity = resumedActivities.iterator().next() as Activity 40 | } 41 | } 42 | return currentActivity 43 | } 44 | 45 | inline fun isVisible(): Boolean { 46 | return T::class.java.name == getActivityInstance()!!::class.java.name 47 | } 48 | 49 | inline fun waitUntilActivityVisible(timeout: Long) { 50 | val startTime = System.currentTimeMillis() 51 | while (!isVisible()) { 52 | Thread.sleep(100) 53 | if (System.currentTimeMillis() - startTime >= timeout) { 54 | throw AssertionError( 55 | "Activity ${T::class.java.simpleName} not visible after $timeout milliseconds", 56 | ) 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/twilio/video/app/e2eTest/E2ETest.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.e2eTest 2 | 3 | annotation class E2ETest 4 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/twilio/video/app/e2eTest/LoginTest.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.e2eTest 2 | 3 | import androidx.test.ext.junit.rules.activityScenarioRule 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | import androidx.test.filters.LargeTest 6 | import androidx.test.rule.GrantPermissionRule 7 | import com.twilio.video.app.R 8 | import com.twilio.video.app.screen.clickSettingsMenuItem 9 | import com.twilio.video.app.screen.loginWithEmail 10 | import com.twilio.video.app.ui.splash.SplashActivity 11 | import com.twilio.video.app.util.assertTextIsDisplayed 12 | import com.twilio.video.app.util.getString 13 | import com.twilio.video.app.util.retrieveEmailCredentials 14 | import com.twilio.video.app.util.retryEspressoAction 15 | import com.twilio.video.app.util.scrollAndClickView 16 | import org.junit.Rule 17 | import org.junit.Test 18 | import org.junit.runner.RunWith 19 | 20 | @RunWith(AndroidJUnit4::class) 21 | @LargeTest 22 | @E2ETest 23 | class LoginTest { 24 | 25 | @get:Rule 26 | var permissionRule = GrantPermissionRule.grant( 27 | android.Manifest.permission.CAMERA, 28 | android.Manifest.permission.RECORD_AUDIO, 29 | ) 30 | 31 | @get:Rule 32 | var scenario = activityScenarioRule() 33 | 34 | @Test 35 | fun `it_should_login_successfully_with_email_and_then_logout`() { 36 | val emailCredentials = retrieveEmailCredentials() 37 | loginWithEmail(emailCredentials) 38 | retryEspressoAction { clickSettingsMenuItem() } 39 | scrollAndClickView(getString(R.string.settings_screen_logout), R.id.recycler_view) 40 | 41 | retryEspressoAction { assertTextIsDisplayed(getString(R.string.fui_sign_in_with_email)) } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/twilio/video/app/e2eTest/PermissionTest.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.e2eTest 2 | 3 | import androidx.test.ext.junit.rules.activityScenarioRule 4 | import com.twilio.video.app.screen.assertMicButtonIsDisabled 5 | import com.twilio.video.app.screen.assertMicButtonIsEnabled 6 | import com.twilio.video.app.screen.assertParticipantStubIsHidden 7 | import com.twilio.video.app.screen.assertVideoButtonIsDisabled 8 | import com.twilio.video.app.screen.assertVideoButtonIsEnabled 9 | import com.twilio.video.app.screen.loginWithEmail 10 | import com.twilio.video.app.ui.splash.SplashActivity 11 | import com.twilio.video.app.util.allowAllPermissions 12 | import com.twilio.video.app.util.denyAllPermissions 13 | import com.twilio.video.app.util.retrieveEmailCredentials 14 | import com.twilio.video.app.util.retryEspressoAction 15 | import com.twilio.video.app.util.uiDevice 16 | import org.junit.Rule 17 | import org.junit.Test 18 | 19 | @E2ETest 20 | class PermissionTest { 21 | 22 | @get:Rule 23 | var scenario = activityScenarioRule() 24 | 25 | @Test 26 | fun `it_should_render_the_local_video_track_before_connecting_to_a_room`() { 27 | loginWithEmail(retrieveEmailCredentials()) 28 | 29 | uiDevice().run { 30 | allowAllPermissions() 31 | } 32 | 33 | retryEspressoAction { assertParticipantStubIsHidden() } 34 | assertVideoButtonIsEnabled() 35 | assertMicButtonIsEnabled() 36 | } 37 | 38 | @Test 39 | fun `it_should_display_disabled_mic_and_camera_buttons_when_permissions_are_denied`() { 40 | loginWithEmail(retrieveEmailCredentials()) 41 | 42 | uiDevice().run { 43 | denyAllPermissions() 44 | } 45 | 46 | retryEspressoAction { assertVideoButtonIsDisabled() } 47 | assertMicButtonIsDisabled() 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/twilio/video/app/espresso/DrawableMatcher.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.espresso 2 | 3 | import android.content.Context 4 | import android.content.res.Resources 5 | import android.graphics.Bitmap 6 | import android.graphics.Canvas 7 | import android.graphics.drawable.BitmapDrawable 8 | import android.graphics.drawable.Drawable 9 | import android.view.View 10 | import android.widget.ImageView 11 | import androidx.annotation.DrawableRes 12 | import androidx.appcompat.view.menu.ActionMenuItemView 13 | import org.hamcrest.Description 14 | import org.hamcrest.TypeSafeMatcher 15 | 16 | class DrawableMatcher( 17 | private val targetContext: Context, 18 | @param:DrawableRes private val expectedId: Int, 19 | ) : TypeSafeMatcher(View::class.java) { 20 | 21 | override fun matchesSafely(target: View): Boolean { 22 | val drawable: Drawable? = when (target) { 23 | is ActionMenuItemView -> target.itemData.icon 24 | is ImageView -> target.drawable 25 | else -> null 26 | } 27 | requireNotNull(drawable) 28 | 29 | val resources: Resources = target.context.resources 30 | val expectedDrawable: Drawable? = resources.getDrawable(expectedId, targetContext.theme) 31 | return if (expectedDrawable != null) { 32 | makeBitmap(drawable)?.sameAs(makeBitmap(expectedDrawable)) ?: false 33 | } else { 34 | false 35 | } 36 | } 37 | 38 | override fun describeTo(description: Description) { 39 | description.appendText("with drawable from resource id: $expectedId") 40 | targetContext.resources.getResourceEntryName(expectedId)?.let { description.appendText("[$it]") } 41 | } 42 | 43 | private fun makeBitmap(drawable: Drawable): Bitmap? { 44 | if (drawable is BitmapDrawable) { 45 | return drawable.bitmap 46 | } 47 | val bitmap = Bitmap.createBitmap( 48 | drawable.intrinsicWidth, 49 | drawable.intrinsicHeight, 50 | Bitmap.Config.ARGB_8888, 51 | ) 52 | val canvas = Canvas(bitmap) 53 | drawable.setBounds(0, 0, canvas.width, canvas.height) 54 | drawable.draw(canvas) 55 | return bitmap 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/twilio/video/app/espresso/HiddenView.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.espresso 2 | 3 | import android.view.View 4 | import androidx.test.espresso.NoMatchingViewException 5 | import androidx.test.espresso.ViewAssertion 6 | import java.lang.RuntimeException 7 | 8 | class HiddenView : ViewAssertion { 9 | 10 | override fun check(view: View?, noViewFoundException: NoMatchingViewException?) { 11 | noViewFoundException?.let { throw it } 12 | val isNotVisible = view?.let { it.visibility == View.GONE } ?: false 13 | if (isNotVisible) return else throw RuntimeException() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/twilio/video/app/integrationTest/BaseIntegrationTest.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.integrationTest 2 | 3 | import com.twilio.video.app.util.CameraCapturerCompat 4 | import com.twilio.video.app.util.getTargetContext 5 | import org.junit.Assume.assumeTrue 6 | import org.junit.Before 7 | 8 | open class BaseIntegrationTest { 9 | 10 | @Before 11 | fun setUp() { 12 | // Skip any devices that don't have a front or back camera 13 | assumeTrue(CameraCapturerCompat.newInstance(getTargetContext()) != null) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/twilio/video/app/integrationTest/CameraCapturerCompatTest.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.integrationTest 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import androidx.test.filters.MediumTest 5 | import com.twilio.video.Camera2Capturer 6 | import com.twilio.video.app.util.CameraCapturerCompat 7 | import com.twilio.video.app.util.getTargetContext 8 | import org.hamcrest.CoreMatchers.equalTo 9 | import org.hamcrest.CoreMatchers.`is` 10 | import org.hamcrest.CoreMatchers.not 11 | import org.hamcrest.CoreMatchers.nullValue 12 | import org.junit.Assert.assertThat 13 | import org.junit.Assume.assumeTrue 14 | import org.junit.Test 15 | import org.junit.runner.RunWith 16 | import tvi.webrtc.Camera1Enumerator 17 | import tvi.webrtc.Camera2Enumerator 18 | import tvi.webrtc.CameraEnumerator 19 | 20 | @RunWith(AndroidJUnit4::class) 21 | @MediumTest 22 | @IntegrationTest 23 | class CameraCapturerCompatTest { 24 | 25 | private var cameraEnumerator: CameraEnumerator = 26 | if (Camera2Capturer.isSupported(getTargetContext())) { 27 | Camera2Enumerator(getTargetContext()) 28 | } else { 29 | Camera1Enumerator() 30 | } 31 | 32 | @Test 33 | fun it_should_return_a_null_camera_capturer_when_no_device_cameras_are_available() { 34 | assumeTrue(cameraEnumerator.deviceNames.isEmpty()) 35 | val capturerCompat = CameraCapturerCompat.newInstance(getTargetContext()) 36 | 37 | assertThat(capturerCompat, `is`(nullValue())) 38 | } 39 | 40 | @Test 41 | fun it_should_switch_the_camera() { 42 | assumeTrue(cameraEnumerator.deviceNames.isNotEmpty()) 43 | val capturerCompat = CameraCapturerCompat.newInstance(getTargetContext()) 44 | assertThat(capturerCompat, `is`(not(nullValue()))) 45 | 46 | capturerCompat?.run { 47 | var isFrontFacing = cameraEnumerator.isFrontFacing(cameraId) 48 | assertThat(isFrontFacing, equalTo(true)) 49 | switchCamera() 50 | val isBackFacing = cameraEnumerator.isBackFacing(cameraId) 51 | assertThat(isBackFacing, equalTo(true)) 52 | switchCamera() 53 | isFrontFacing = cameraEnumerator.isFrontFacing(cameraId) 54 | assertThat(isFrontFacing, equalTo(true)) 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/twilio/video/app/integrationTest/IntegrationTest.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.integrationTest 2 | 3 | annotation class IntegrationTest 4 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/twilio/video/app/screen/LoginScreen.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.screen 2 | 3 | import android.os.Build 4 | import android.view.View 5 | import androidx.test.espresso.Espresso.onView 6 | import androidx.test.espresso.UiController 7 | import androidx.test.espresso.ViewAction 8 | import androidx.test.espresso.action.ViewActions.click 9 | import androidx.test.espresso.action.ViewActions.typeText 10 | import androidx.test.espresso.matcher.ViewMatchers.withId 11 | import com.twilio.video.app.EmailCredentials 12 | import com.twilio.video.app.R 13 | import com.twilio.video.app.util.retryEspressoAction 14 | import org.hamcrest.Matcher 15 | import org.hamcrest.Matchers 16 | 17 | // TODO Move to common module as part of https://issues.corp.twilio.com/browse/AHOYAPPS-197 18 | 19 | class DisableAutofillAction : ViewAction { 20 | override fun getConstraints(): Matcher? { 21 | return Matchers.any(View::class.java) 22 | } 23 | 24 | override fun getDescription(): String { 25 | return "Marking view not important for autofill" 26 | } 27 | 28 | override fun perform(uiController: UiController?, view: View?) { 29 | // Required to disable autofill suggestions during tests on API 26+ 30 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 31 | view?.importantForAutofill = View.IMPORTANT_FOR_AUTOFILL_NO 32 | } 33 | } 34 | } 35 | 36 | fun loginWithEmail(emailCredentials: EmailCredentials) { 37 | loginWithEmail(emailCredentials.email, emailCredentials.password) 38 | } 39 | 40 | private fun loginWithEmail(email: String, password: String) { 41 | onView(withId(R.id.email_button)).perform(click()) 42 | onView(withId(R.id.email)).perform( 43 | DisableAutofillAction(), 44 | typeText(email), 45 | ) 46 | onView(withId(R.id.button_next)).perform(click()) 47 | retryEspressoAction { 48 | onView(withId(R.id.password)).perform( 49 | DisableAutofillAction(), 50 | typeText(password), 51 | ) 52 | } 53 | onView(withId(R.id.button_done)).perform(click()) 54 | } 55 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/twilio/video/app/util/EspressoUtil.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.util 2 | 3 | import androidx.annotation.IdRes 4 | import androidx.recyclerview.widget.RecyclerView 5 | import androidx.test.espresso.Espresso.onView 6 | import androidx.test.espresso.action.ViewActions.click 7 | import androidx.test.espresso.assertion.ViewAssertions.matches 8 | import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItem 9 | import androidx.test.espresso.matcher.ViewMatchers.hasDescendant 10 | import androidx.test.espresso.matcher.ViewMatchers.isDisplayed 11 | import androidx.test.espresso.matcher.ViewMatchers.withId 12 | import androidx.test.espresso.matcher.ViewMatchers.withText 13 | import org.hamcrest.CoreMatchers.not 14 | 15 | fun clickView(viewText: String) { 16 | onView(withText(viewText)).perform(click()) 17 | } 18 | 19 | fun clickView(@IdRes id: Int) { 20 | onView(withId(id)).perform(click()) 21 | } 22 | 23 | fun scrollAndClickView(viewText: String, recyclerViewId: Int) { 24 | onView(withId(recyclerViewId)) 25 | .perform(actionOnItem(hasDescendant(withText(viewText)), click())) 26 | } 27 | 28 | fun assertTextIsDisplayed(text: String) { 29 | onView(withText(text)).check(matches(isDisplayed())) 30 | } 31 | 32 | fun assertTextIsDisplayedRetry(text: String) { 33 | retryEspressoAction { onView(withText(text)).check(matches(isDisplayed())) } 34 | } 35 | 36 | fun assertTextIsNotDisplayedRetry(text: String) { 37 | retryEspressoAction { onView(withText(text)).check(matches(not(isDisplayed()))) } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/twilio/video/app/util/TestUtil.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.util 2 | 3 | import android.content.Context 4 | import androidx.annotation.IdRes 5 | import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation 6 | import com.google.gson.Gson 7 | import com.google.gson.stream.JsonReader 8 | import com.twilio.video.app.EmailCredentials 9 | import com.twilio.video.app.TestCredentials 10 | import junit.framework.AssertionFailedError 11 | import java.io.InputStreamReader 12 | import java.util.UUID 13 | 14 | fun retryEspressoAction(timeoutInMillis: Long = 10000L, espressoAction: () -> Unit) { 15 | val startTime = System.currentTimeMillis() 16 | var currentTime = 0L 17 | var exception: Throwable? = null 18 | while (currentTime <= timeoutInMillis) { 19 | currentTime = try { 20 | espressoAction() 21 | return 22 | } catch (e: Exception) { 23 | exception = e 24 | countDown(startTime) 25 | } catch (e: AssertionFailedError) { 26 | exception = e 27 | countDown(startTime) 28 | } 29 | } 30 | throw AssertionError("Timeout occurred while attempting to find a matching view", exception) 31 | } 32 | 33 | fun getTargetContext(): Context = getInstrumentation().targetContext 34 | 35 | fun getString(@IdRes stringId: Int) = getTargetContext().getString(stringId) 36 | 37 | fun getStringArray(@IdRes stringArrayId: Int) = getTargetContext().resources.getStringArray(stringArrayId) 38 | 39 | fun retrieveEmailCredentials(): EmailCredentials { 40 | val reader = InputStreamReader(getInstrumentation().context.assets.open("Credentials/TestCredentials.json")) 41 | val jsonReader = JsonReader(reader) 42 | return (Gson().fromJson(jsonReader, TestCredentials::class.java) as TestCredentials).email_sign_in_user 43 | } 44 | 45 | fun randomUUID() = UUID.randomUUID().toString() 46 | 47 | private fun countDown(startTime: Long): Long { 48 | Thread.sleep(10) 49 | return System.currentTimeMillis() - startTime 50 | } 51 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/twilio/video/app/util/UiAutomatorExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.util 2 | 3 | import android.os.Build 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import androidx.test.uiautomator.UiDevice 6 | import androidx.test.uiautomator.UiSelector 7 | import java.util.concurrent.TimeoutException 8 | 9 | fun uiDevice(): UiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) 10 | 11 | fun UiDevice.allowAllPermissions() { 12 | clickThroughDialogs("Allow|ALLOW|While using the app") 13 | } 14 | 15 | fun UiDevice.denyAllPermissions() { 16 | clickThroughDialogs("Deny|DENY|Don\\p{Punct}t allow") 17 | } 18 | 19 | private fun UiDevice.clickThroughDialogs(text: String) { 20 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 21 | var allowPermissions = findObject(UiSelector().textMatches(text)) 22 | if (allowPermissions.waitForExists(5000)) { 23 | while (allowPermissions.exists()) { 24 | allowPermissions.click() 25 | allowPermissions = findObject(UiSelector().textMatches(text)) 26 | } 27 | } else { 28 | throw TimeoutException("Timed out while waiting for permission dialog.") 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/community/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/community/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/community/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/community/java/com/twilio/video/app/auth/CommunityAuthModule.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Twilio, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.twilio.video.app.auth 17 | 18 | import android.content.SharedPreferences 19 | import com.twilio.video.app.data.api.TokenService 20 | import com.twilio.video.app.security.SecurePreferences 21 | import dagger.Module 22 | import dagger.Provides 23 | import dagger.hilt.InstallIn 24 | import dagger.hilt.components.SingletonComponent 25 | import javax.inject.Singleton 26 | 27 | @Module 28 | @InstallIn(SingletonComponent::class) 29 | class CommunityAuthModule { 30 | @Provides 31 | @Singleton 32 | fun providesCommunityAuthenticator( 33 | preferences: SharedPreferences, 34 | securePreferences: SecurePreferences, 35 | tokenService: TokenService, 36 | ): Authenticator { 37 | return CommunityAuthenticator(preferences, securePreferences, tokenService) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/community/java/com/twilio/video/app/auth/CommunityLoginResult.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.auth 2 | 3 | import com.twilio.video.app.data.api.AuthServiceError 4 | 5 | sealed class CommunityLoginResult : LoginResult { 6 | data class CommunityLoginFailureResult(val error: AuthServiceError? = null) : CommunityLoginResult() 7 | object CommunityLoginSuccessResult : CommunityLoginResult() 8 | } 9 | -------------------------------------------------------------------------------- /app/src/community/java/com/twilio/video/app/data/CommunityPreferences.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.data 2 | 3 | const val PASSCODE: String = "passcode" 4 | -------------------------------------------------------------------------------- /app/src/community/java/com/twilio/video/app/data/api/AuthService.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.data.api 2 | 3 | import retrofit2.http.Body 4 | import retrofit2.http.POST 5 | import retrofit2.http.Url 6 | 7 | interface AuthService { 8 | 9 | @POST 10 | suspend fun getToken( 11 | @Url url: String, 12 | @Body authServiceRequestDTO: AuthServiceRequestDTO, 13 | ): AuthServiceResponseDTO 14 | } 15 | -------------------------------------------------------------------------------- /app/src/community/java/com/twilio/video/app/security/SecurePreferences.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.security 2 | 3 | interface SecurePreferences { 4 | 5 | fun putSecureString(key: String, value: String) 6 | 7 | fun getSecureString(key: String): String? 8 | } 9 | -------------------------------------------------------------------------------- /app/src/community/java/com/twilio/video/app/security/SecurePreferencesImpl.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.security 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | import android.util.Base64 6 | import com.facebook.android.crypto.keychain.AndroidConceal 7 | import com.facebook.android.crypto.keychain.SharedPrefsBackedKeyChain 8 | import com.facebook.crypto.Crypto 9 | import com.facebook.crypto.CryptoConfig 10 | import com.facebook.crypto.Entity 11 | import com.facebook.soloader.SoLoader 12 | 13 | class SecurePreferencesImpl( 14 | context: Context, 15 | private val preferences: SharedPreferences, 16 | ) : SecurePreferences { 17 | 18 | private val entity: Entity = Entity.create(context.packageName) 19 | private val crypto: Crypto 20 | 21 | init { 22 | SoLoader.init(context, false) 23 | val keyChain = SharedPrefsBackedKeyChain(context, CryptoConfig.KEY_256) 24 | crypto = AndroidConceal.get().createCrypto256Bits(keyChain) 25 | } 26 | 27 | override fun putSecureString(key: String, value: String) { 28 | return preferences.edit().putString(key, encrypt(value)).apply() 29 | } 30 | 31 | override fun getSecureString(key: String): String? { 32 | val encryptedText: String? = preferences.getString(key, null) 33 | return if (encryptedText != null) decrypt(encryptedText) else null 34 | } 35 | 36 | private fun encrypt(plainText: String): String { 37 | val cipherText = crypto.encrypt(plainText.toByteArray(), entity) 38 | return Base64.encodeToString(cipherText, Base64.DEFAULT) 39 | } 40 | 41 | private fun decrypt(encryptedText: String): String { 42 | return String(crypto.decrypt(Base64.decode(encryptedText, Base64.DEFAULT), entity)) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/community/java/com/twilio/video/app/security/SecurityModule.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.security 2 | 3 | import android.app.Application 4 | import android.content.SharedPreferences 5 | import dagger.Module 6 | import dagger.Provides 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.components.SingletonComponent 9 | import javax.inject.Singleton 10 | 11 | @Module 12 | @InstallIn(SingletonComponent::class) 13 | class SecurityModule { 14 | 15 | @Provides 16 | @Singleton 17 | fun providesSecurePreferences(app: Application, preferences: SharedPreferences): SecurePreferences { 18 | return SecurePreferencesImpl(app.applicationContext, preferences) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/community/java/com/twilio/video/app/ui/CommunityScreenSelector.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.ui 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import com.twilio.video.app.ui.login.CommunityLoginActivity 5 | 6 | class CommunityScreenSelector : ScreenSelector { 7 | 8 | override val loginScreen: Class 9 | get() = CommunityLoginActivity::class.java 10 | } 11 | -------------------------------------------------------------------------------- /app/src/community/java/com/twilio/video/app/ui/CommunityScreenSelectorModule.java: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.ui; 2 | 3 | import dagger.Module; 4 | import dagger.Provides; 5 | import dagger.hilt.InstallIn; 6 | import dagger.hilt.android.components.ActivityComponent; 7 | 8 | @Module 9 | @InstallIn(ActivityComponent.class) 10 | public class CommunityScreenSelectorModule { 11 | 12 | @Provides 13 | ScreenSelector providesScreenSelector() { 14 | return new CommunityScreenSelector(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/community/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/community/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/community/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/community/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/community/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/community/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/community/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/community/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/community/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/community/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/community/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/community/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/community/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/community/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/community/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/community/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/community/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/community/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/community/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/community/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/community/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/community/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/community/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/community/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/community/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/community/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/community/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/community/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/community/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/community/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/community/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/community/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/community/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #80fffffe 4 | 5 | -------------------------------------------------------------------------------- /app/src/community/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 94.5dp 4 | 5 | -------------------------------------------------------------------------------- /app/src/community/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Video Community 4 | 5 | 6 | Your name 7 | Passcode 8 | Log In Error 9 | Passcode incorrect. 10 | Passcode expired. 11 | There was an issue logging in. 12 | LOG IN 13 | Video Logo 14 | -------------------------------------------------------------------------------- /app/src/community/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/internal/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /app/src/internal/debug/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/internal/debug/.gitkeep -------------------------------------------------------------------------------- /app/src/internal/java/com/twilio/video/app/auth/AuthModule.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Twilio, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twilio.video.app.auth; 18 | 19 | import android.app.Application; 20 | import android.content.Context; 21 | import com.google.android.gms.auth.api.signin.GoogleSignInOptions; 22 | import com.twilio.video.app.R; 23 | import dagger.Module; 24 | import dagger.Provides; 25 | import dagger.hilt.InstallIn; 26 | import dagger.hilt.components.SingletonComponent; 27 | import java.util.ArrayList; 28 | import java.util.List; 29 | import javax.inject.Singleton; 30 | 31 | @Module 32 | @InstallIn(SingletonComponent.class) 33 | public class AuthModule { 34 | 35 | @Provides 36 | @Singleton 37 | Authenticator providesAuthenticator(FirebaseWrapper firebaseWrapper, Application application) { 38 | Context context = application.getApplicationContext(); 39 | List authProviders = new ArrayList<>(); 40 | String acceptedDomain = "twilio.com"; 41 | GoogleSignInOptions googleSignInOptions = 42 | new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) 43 | .requestIdToken(context.getString(R.string.default_web_client_id)) 44 | .requestEmail() 45 | .setHostedDomain(acceptedDomain) 46 | .build(); 47 | 48 | authProviders.add( 49 | GoogleAuthProvider.Companion.newInstance( 50 | context, googleSignInOptions, acceptedDomain)); 51 | authProviders.add(new EmailAuthProvider(firebaseWrapper)); 52 | return new FirebaseAuthenticator(firebaseWrapper, authProviders); 53 | } 54 | 55 | @Provides 56 | FirebaseWrapper providesFirebaseWrapper() { 57 | return new FirebaseWrapper(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/internal/java/com/twilio/video/app/ui/ScreenSelectorModule.java: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.ui; 2 | 3 | import dagger.Module; 4 | import dagger.Provides; 5 | import dagger.hilt.InstallIn; 6 | import dagger.hilt.android.components.ActivityComponent; 7 | 8 | @Module 9 | @InstallIn(ActivityComponent.class) 10 | public class ScreenSelectorModule { 11 | 12 | @Provides 13 | ScreenSelector providesScreenSelector() { 14 | return new ProductionScreenSelector(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/src/internal/release/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/internal/release/.gitkeep -------------------------------------------------------------------------------- /app/src/internalDebug/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/internalDebug/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/internalDebug/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/internalDebug/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/internalDebug/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/internalDebug/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/internalDebug/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/internalDebug/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/internalDebug/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/internalDebug/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/internalDebug/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/internalDebug/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/internalDebug/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/internalDebug/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/internalDebug/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/internalDebug/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/internalDebug/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/internalDebug/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/internalDebug/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/internalDebug/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/internalDebug/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/internalDebug/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/internalDebug/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/internalDebug/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/internalDebug/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/internalDebug/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/internalDebug/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/internalDebug/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/internalDebug/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/internalDebug/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/internalDebug/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/internalDebug/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/internalDebug/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/internalDebug/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/internalDebug/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Video Internal Debug 4 | 5 | -------------------------------------------------------------------------------- /app/src/internalRelease/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/internalRelease/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/internalRelease/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/internalRelease/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/internalRelease/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/internalRelease/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/internalRelease/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/internalRelease/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/internalRelease/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/internalRelease/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/internalRelease/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/internalRelease/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/internalRelease/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/internalRelease/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/internalRelease/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/internalRelease/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/internalRelease/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/internalRelease/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/internalRelease/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/internalRelease/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/internalRelease/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/internalRelease/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/internalRelease/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/internalRelease/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/internalRelease/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/internalRelease/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/internalRelease/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/internalRelease/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/internalRelease/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/internalRelease/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/internalRelease/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/internalRelease/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/internalRelease/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/internalRelease/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/internalRelease/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Video Internal 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/ApplicationScope.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Twilio, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twilio.video.app; 18 | 19 | import java.lang.annotation.Retention; 20 | import java.lang.annotation.RetentionPolicy; 21 | import javax.inject.Scope; 22 | 23 | @Scope 24 | @Retention(RetentionPolicy.RUNTIME) 25 | public @interface ApplicationScope {} 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/TreeModule.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Twilio, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twilio.video.app; 18 | 19 | import com.twilio.video.LogLevel; 20 | import com.twilio.video.Video; 21 | import com.twilio.video.app.util.BuildConfigUtilsKt; 22 | import com.twilio.video.app.util.CrashlyticsTreeRanger; 23 | import com.twilio.video.app.util.DebugTree; 24 | import com.twilio.video.app.util.ReleaseTree; 25 | import dagger.Module; 26 | import dagger.Provides; 27 | import dagger.hilt.InstallIn; 28 | import dagger.hilt.components.SingletonComponent; 29 | import javax.inject.Singleton; 30 | import timber.log.Timber; 31 | 32 | @Module 33 | @InstallIn(SingletonComponent.class) 34 | public class TreeModule { 35 | @Provides 36 | @Singleton 37 | Timber.Tree providesTree(CrashlyticsTreeRanger treeRanger) { 38 | if (BuildConfig.DEBUG || BuildConfigUtilsKt.isInternalFlavor()) { 39 | Video.setLogLevel(LogLevel.DEBUG); 40 | return new DebugTree(treeRanger); 41 | } else { 42 | return new ReleaseTree(treeRanger); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/VideoApplication.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Twilio, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twilio.video.app 18 | 19 | import android.app.Application 20 | import android.content.Context 21 | import androidx.multidex.MultiDex 22 | import dagger.hilt.android.HiltAndroidApp 23 | import timber.log.Timber 24 | import javax.inject.Inject 25 | 26 | @HiltAndroidApp 27 | class VideoApplication : Application() { 28 | @Inject 29 | lateinit var tree: Timber.Tree 30 | 31 | override fun attachBaseContext(base: Context) { 32 | super.attachBaseContext(base) 33 | MultiDex.install(this) 34 | } 35 | 36 | override fun onCreate() { 37 | super.onCreate() 38 | 39 | Timber.plant(tree) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/android/SharedPreferencesWrapper.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.android 2 | 3 | import android.content.SharedPreferences 4 | import androidx.core.content.edit 5 | 6 | class SharedPreferencesWrapper(private val sharedPreferences: SharedPreferences) : 7 | SharedPreferences by sharedPreferences { 8 | 9 | fun edit(action: SharedPreferences.Editor.() -> Unit) { 10 | sharedPreferences.edit(action = action) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/auth/AuthenticationException.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.auth 2 | 3 | // TODO Provide more detailed error handling as part of https://issues.corp.twilio.com/browse/AHOYAPPS-153 4 | class AuthenticationException : Exception() 5 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/auth/AuthenticationProvider.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.auth 2 | 3 | import io.reactivex.Observable 4 | 5 | interface AuthenticationProvider { 6 | 7 | fun login(loginEventObservable: Observable): Observable 8 | 9 | fun logout() 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/auth/Authenticator.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Twilio, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twilio.video.app.auth 18 | 19 | import io.reactivex.Observable 20 | 21 | interface Authenticator { 22 | 23 | fun login(loginEventObservable: Observable): Observable 24 | 25 | fun login(loginEvent: LoginEvent): Observable 26 | 27 | fun loggedIn(): Boolean 28 | 29 | fun logout() 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/auth/FirebaseAuthenticator.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.auth 2 | 3 | import io.reactivex.Observable 4 | 5 | class FirebaseAuthenticator( 6 | private val firebaseWrapper: FirebaseWrapper, 7 | private val authenticationProviders: List, 8 | ) : Authenticator { 9 | 10 | override fun login(loginEventObservable: Observable): Observable { 11 | // TODO Figure out a better way to only subscribe to one authenticator at a time 12 | val observables: MutableList> = mutableListOf() 13 | authenticationProviders.forEach { 14 | observables.add(it.login(loginEventObservable)) 15 | } 16 | return Observable.merge(observables) 17 | } 18 | 19 | override fun login(loginEvent: LoginEvent): Observable { 20 | return login(Observable.just(loginEvent)) 21 | } 22 | 23 | override fun loggedIn() = firebaseWrapper.instance.currentUser != null 24 | 25 | override fun logout() { 26 | authenticationProviders.forEach { it.logout() } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/auth/FirebaseWrapper.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.auth 2 | 3 | import com.google.firebase.auth.FirebaseAuth 4 | 5 | class FirebaseWrapper { 6 | 7 | val instance: FirebaseAuth 8 | get() = FirebaseAuth.getInstance() 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/auth/GoogleAuthProviderWrapper.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.auth 2 | 3 | import com.google.firebase.auth.GoogleAuthProvider 4 | 5 | class GoogleAuthProviderWrapper { 6 | 7 | fun getCredential(idToken: String) = GoogleAuthProvider.getCredential(idToken, null) 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/auth/GoogleAuthWrapper.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.auth 2 | 3 | import android.content.Intent 4 | import com.google.android.gms.auth.api.Auth 5 | import com.google.android.gms.auth.api.signin.GoogleSignInResult 6 | 7 | class GoogleAuthWrapper { 8 | 9 | fun getSignInResultFromIntent(intent: Intent): GoogleSignInResult? = Auth.GoogleSignInApi.getSignInResultFromIntent(intent) 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/auth/GoogleSignInOptionsWrapper.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.auth 2 | 3 | import com.google.android.gms.auth.api.signin.GoogleSignInOptions 4 | 5 | class GoogleSignInOptionsWrapper(val googleSignInOptions: GoogleSignInOptions) 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/auth/GoogleSignInWrapper.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.auth 2 | 3 | import android.content.Context 4 | import com.google.android.gms.auth.api.signin.GoogleSignIn 5 | 6 | class GoogleSignInWrapper { 7 | 8 | fun getClient(context: Context, googleSignInOptionsWrapper: GoogleSignInOptionsWrapper) = 9 | GoogleSignIn.getClient(context, googleSignInOptionsWrapper.googleSignInOptions) 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/auth/InternalLoginResult.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.auth 2 | 3 | import android.content.Intent 4 | import com.google.android.gms.auth.api.signin.GoogleSignInAccount 5 | 6 | sealed class InternalLoginResult : LoginResult { 7 | data class GoogleLoginIntentResult(val intent: Intent) : InternalLoginResult() 8 | data class GoogleLoginSuccessResult(val googleSignInAccount: GoogleSignInAccount) : InternalLoginResult() 9 | data class EmailLoginSuccessResult(val email: String) : InternalLoginResult() 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/auth/LoginEvent.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.auth 2 | 3 | import android.content.Intent 4 | 5 | sealed class LoginEvent { 6 | object GoogleLoginIntentRequestEvent : LoginEvent() 7 | data class EmailLoginEvent(val email: String, val password: String) : LoginEvent() 8 | data class GoogleLoginEvent(val signInResultIntent: Intent) : LoginEvent() 9 | data class CommunityLoginEvent(val identity: String, val passcode: String) : LoginEvent() 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/auth/LoginResult.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.auth 2 | 3 | interface LoginResult 4 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/data/DataModule.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Twilio, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twilio.video.app.data 18 | 19 | import android.app.Application 20 | import android.content.SharedPreferences 21 | import com.twilio.video.app.util.getSharedPreferences 22 | import dagger.Module 23 | import dagger.Provides 24 | import dagger.hilt.InstallIn 25 | import dagger.hilt.components.SingletonComponent 26 | 27 | @Module 28 | @InstallIn(SingletonComponent::class) 29 | class DataModule { 30 | @Provides 31 | internal fun provideSharedPreferences(app: Application): SharedPreferences { 32 | return getSharedPreferences(app) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/data/api/AuthServiceError.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.data.api 2 | 3 | import timber.log.Timber 4 | 5 | enum class AuthServiceError { 6 | INVALID_PASSCODE_ERROR, 7 | EXPIRED_PASSCODE_ERROR, 8 | ; 9 | 10 | companion object { 11 | fun value(value: String?): AuthServiceError? = 12 | when (value) { 13 | "passcode incorrect" -> INVALID_PASSCODE_ERROR 14 | "passcode expired" -> EXPIRED_PASSCODE_ERROR 15 | else -> { 16 | Timber.d("Unrecognized Auth Service error message: %s", value) 17 | null 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/data/api/AuthServiceException.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.data.api 2 | 3 | class AuthServiceException( 4 | throwable: Throwable? = null, 5 | val error: AuthServiceError? = null, 6 | message: String? = null, 7 | ) : RuntimeException(message, throwable) 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/data/api/InternalTokenApi.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Twilio, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.twilio.video.app.data.api 17 | 18 | import retrofit2.http.Body 19 | import retrofit2.http.POST 20 | 21 | interface InternalTokenApi { 22 | @POST("/token") 23 | suspend fun getToken(@Body request: AuthServiceRequestDTO): AuthServiceResponseDTO 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/data/api/TokenService.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Twilio, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twilio.video.app.data.api 18 | 19 | interface TokenService { 20 | suspend fun getToken( 21 | identity: String? = null, 22 | roomName: String? = null, 23 | ): String 24 | 25 | suspend fun getToken( 26 | identity: String? = null, 27 | roomName: String? = null, 28 | passcode: String? = null, 29 | ): String { return "" } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/data/api/TwilioApiEnvironment.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Twilio, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twilio.video.app.data.api 18 | 19 | const val TWILIO_API_DEV_ENV = "dev" 20 | const val TWILIO_API_STAGE_ENV = "stage" 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/data/api/dto/AuthServiceErrorDTO.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.data.api 2 | 3 | data class AuthServiceErrorDTO( 4 | val error: ErrorDTO? = null, 5 | ) 6 | 7 | data class ErrorDTO( 8 | val message: String? = null, 9 | val explanation: String? = null, 10 | ) 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/data/api/dto/AuthServiceRequestDTO.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.data.api 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class AuthServiceRequestDTO( 6 | @SerializedName("passcode") val passcode: String? = null, 7 | @SerializedName("user_identity") val user_identity: String? = null, 8 | @SerializedName("room_name") val room_name: String? = null, 9 | @SerializedName("create_room") val create_room: Boolean = false, 10 | ) 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/data/api/dto/AuthServiceResponseDTO.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.data.api 2 | 3 | import com.google.gson.annotations.SerializedName 4 | import com.twilio.video.app.data.api.dto.Topology 5 | 6 | data class AuthServiceResponseDTO( 7 | val token: String? = null, 8 | @SerializedName("room_type") val topology: Topology? = null, 9 | ) 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/data/api/dto/Topology.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Twilio, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.twilio.video.app.data.api.dto 17 | 18 | import com.google.gson.annotations.SerializedName 19 | 20 | private const val GROUP_ROOM_NAME = "group" 21 | private const val GROUP_SMALL_ROOM_NAME = "group-small" 22 | private const val PEER_TO_PEER_ROOM_NAME = "peer-to-peer" 23 | private const val GO_ROOM_NAME = "go" 24 | 25 | enum class Topology(val value: String) { 26 | @SerializedName(GROUP_ROOM_NAME) 27 | GROUP(GROUP_ROOM_NAME), 28 | 29 | @SerializedName(GROUP_SMALL_ROOM_NAME) 30 | GROUP_SMALL(GROUP_SMALL_ROOM_NAME), 31 | 32 | @SerializedName(PEER_TO_PEER_ROOM_NAME) 33 | PEER_TO_PEER(PEER_TO_PEER_ROOM_NAME), 34 | 35 | @SerializedName(GO_ROOM_NAME) 36 | GO(GO_ROOM_NAME), 37 | ; 38 | 39 | override fun toString() = value 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/participant/ParticipantViewState.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.participant 2 | 3 | import com.twilio.video.NetworkQualityLevel 4 | import com.twilio.video.NetworkQualityLevel.NETWORK_QUALITY_LEVEL_UNKNOWN 5 | import com.twilio.video.Participant 6 | import com.twilio.video.RemoteVideoTrack 7 | import com.twilio.video.app.sdk.VideoTrackViewState 8 | 9 | data class ParticipantViewState( 10 | val sid: String? = null, 11 | val identity: String? = null, 12 | val videoTrack: VideoTrackViewState? = null, 13 | val screenTrack: VideoTrackViewState? = null, 14 | val isMuted: Boolean = false, 15 | val isMirrored: Boolean = false, 16 | val isPinned: Boolean = false, 17 | val isDominantSpeaker: Boolean = false, 18 | val isLocalParticipant: Boolean = false, 19 | val networkQualityLevel: NetworkQualityLevel = NETWORK_QUALITY_LEVEL_UNKNOWN, 20 | ) { 21 | val isScreenSharing: Boolean get() = screenTrack != null 22 | 23 | fun getRemoteVideoTrack(): RemoteVideoTrack? = 24 | if (!isLocalParticipant) videoTrack?.videoTrack as RemoteVideoTrack? else null 25 | 26 | fun getRemoteScreenTrack(): RemoteVideoTrack? = 27 | if (!isLocalParticipant) screenTrack?.videoTrack as RemoteVideoTrack? else null 28 | } 29 | 30 | fun buildParticipantViewState(participant: Participant): ParticipantViewState { 31 | val videoTrack = participant.videoTracks.firstOrNull()?.videoTrack 32 | return ParticipantViewState( 33 | participant.sid, 34 | participant.identity, 35 | videoTrack?.let { VideoTrackViewState(it) }, 36 | networkQualityLevel = participant.networkQualityLevel, 37 | isMuted = participant.audioTracks.firstOrNull() == null, 38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/sdk/LocalParticipantListener.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.sdk 2 | 3 | import com.twilio.video.LocalAudioTrack 4 | import com.twilio.video.LocalAudioTrackPublication 5 | import com.twilio.video.LocalDataTrack 6 | import com.twilio.video.LocalDataTrackPublication 7 | import com.twilio.video.LocalParticipant 8 | import com.twilio.video.LocalVideoTrack 9 | import com.twilio.video.LocalVideoTrackPublication 10 | import com.twilio.video.NetworkQualityLevel 11 | import com.twilio.video.TwilioException 12 | import com.twilio.video.app.ui.room.RoomEvent.RemoteParticipantEvent.NetworkQualityLevelChange 13 | import timber.log.Timber 14 | 15 | class LocalParticipantListener(private val roomManager: RoomManager) : LocalParticipant.Listener { 16 | 17 | override fun onNetworkQualityLevelChanged(localParticipant: LocalParticipant, networkQualityLevel: NetworkQualityLevel) { 18 | Timber.i( 19 | "LocalParticipant NetworkQualityLevel changed for LocalParticipant sid: %s, NetworkQualityLevel: %s", 20 | localParticipant.sid, 21 | networkQualityLevel, 22 | ) 23 | 24 | roomManager.sendRoomEvent(NetworkQualityLevelChange(localParticipant.sid, networkQualityLevel)) 25 | } 26 | 27 | override fun onVideoTrackPublished(localParticipant: LocalParticipant, localVideoTrackPublication: LocalVideoTrackPublication) {} 28 | 29 | override fun onVideoTrackPublicationFailed(localParticipant: LocalParticipant, localVideoTrack: LocalVideoTrack, twilioException: TwilioException) {} 30 | 31 | override fun onDataTrackPublished(localParticipant: LocalParticipant, localDataTrackPublication: LocalDataTrackPublication) {} 32 | 33 | override fun onDataTrackPublicationFailed(localParticipant: LocalParticipant, localDataTrack: LocalDataTrack, twilioException: TwilioException) {} 34 | 35 | override fun onAudioTrackPublished(localParticipant: LocalParticipant, localAudioTrackPublication: LocalAudioTrackPublication) {} 36 | 37 | override fun onAudioTrackPublicationFailed(localParticipant: LocalParticipant, localAudioTrack: LocalAudioTrack, twilioException: TwilioException) {} 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/sdk/RoomStats.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.sdk 2 | 3 | import com.twilio.video.RemoteParticipant 4 | import com.twilio.video.StatsReport 5 | 6 | data class RoomStats( 7 | val remoteParticipants: List, 8 | val localVideoTrackNames: Map, 9 | val statsReports: List? = null, 10 | ) 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/sdk/StatsScheduler.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Twilio, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.twilio.video.app.sdk 17 | 18 | import android.os.Handler 19 | import android.os.HandlerThread 20 | import com.twilio.video.Room 21 | import com.twilio.video.StatsListener 22 | import timber.log.Timber 23 | 24 | class StatsScheduler(private val roomManager: RoomManager, private val room: Room) { 25 | private var handlerThread: HandlerThread? = null 26 | private var handler: Handler? = null 27 | private val statsListener: StatsListener = StatsListener { statsReports -> 28 | roomManager.sendStatsUpdate(statsReports) 29 | } 30 | private val isRunning: Boolean 31 | get() = handlerThread?.isAlive ?: false 32 | 33 | fun start() { 34 | if (isRunning) { 35 | stop() 36 | } 37 | val handlerThread = HandlerThread("StatsSchedulerThread") 38 | this.handlerThread = handlerThread 39 | handlerThread.start() 40 | val handler = Handler(handlerThread.looper) 41 | this.handler = handler 42 | val statsRunner: Runnable = object : Runnable { 43 | override fun run() { 44 | room.getStats(statsListener) 45 | handler.postDelayed(this, 1000) 46 | } 47 | } 48 | handler.post(statsRunner) 49 | Timber.d("Stats scheduler thread started") 50 | } 51 | 52 | fun stop() { 53 | if (isRunning) { 54 | handlerThread?.let { handlerThread -> 55 | handlerThread.quit() 56 | this.handlerThread = null 57 | handler = null 58 | Timber.d("Stats scheduler thread closed") 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/sdk/VideoClient.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.sdk 2 | 3 | import android.content.Context 4 | import com.twilio.video.Room 5 | import com.twilio.video.Video 6 | 7 | class VideoClient( 8 | private val context: Context, 9 | private val connectOptionsFactory: ConnectOptionsFactory, 10 | ) { 11 | 12 | suspend fun connect( 13 | identity: String, 14 | roomName: String, 15 | roomListener: Room.Listener, 16 | ): Room { 17 | return Video.connect( 18 | context, 19 | connectOptionsFactory.newInstance(identity, roomName), 20 | roomListener, 21 | ) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/sdk/VideoSdkModule.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.sdk 2 | 3 | import android.app.Application 4 | import android.content.SharedPreferences 5 | import com.twilio.video.app.data.api.TokenService 6 | import dagger.Module 7 | import dagger.Provides 8 | import dagger.hilt.InstallIn 9 | import dagger.hilt.components.SingletonComponent 10 | import javax.inject.Singleton 11 | 12 | @Module 13 | @InstallIn(SingletonComponent::class) 14 | class VideoSdkModule { 15 | 16 | @Provides 17 | @Singleton 18 | fun providesRoomManager( 19 | application: Application, 20 | sharedPreferences: SharedPreferences, 21 | tokenService: TokenService, 22 | ): RoomManager { 23 | val connectOptionsFactory = ConnectOptionsFactory(application, sharedPreferences, tokenService) 24 | val videoClient = VideoClient(application, connectOptionsFactory) 25 | return RoomManager(application, videoClient, sharedPreferences) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/sdk/VideoTrackViewState.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.sdk 2 | 3 | import com.twilio.video.VideoTrack 4 | 5 | data class VideoTrackViewState constructor( 6 | val videoTrack: VideoTrack, 7 | val isSwitchedOff: Boolean = false, 8 | ) 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/ui/ProductionScreenSelector.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.ui 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | import com.twilio.video.app.ui.login.LoginActivity 5 | 6 | class ProductionScreenSelector : ScreenSelector { 7 | 8 | override val loginScreen: Class 9 | get() = LoginActivity::class.java 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/ui/ScreenSelector.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.ui 2 | 3 | import androidx.appcompat.app.AppCompatActivity 4 | 5 | interface ScreenSelector { 6 | 7 | val loginScreen: Class 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/ui/room/ParticipantAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.ui.room 2 | 3 | import android.view.ViewGroup 4 | import androidx.lifecycle.LiveData 5 | import androidx.lifecycle.MutableLiveData 6 | import androidx.recyclerview.widget.DiffUtil 7 | import androidx.recyclerview.widget.ListAdapter 8 | import com.twilio.video.app.participant.ParticipantViewState 9 | 10 | internal class ParticipantAdapter : ListAdapter( 11 | ParticipantDiffCallback(), 12 | ) { 13 | 14 | private val mutableViewHolderEvents = MutableLiveData() 15 | val viewHolderEvents: LiveData = mutableViewHolderEvents 16 | 17 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ParticipantViewHolder = 18 | ParticipantViewHolder(ParticipantThumbView(parent.context)) 19 | 20 | override fun onBindViewHolder(holder: ParticipantViewHolder, position: Int) = 21 | holder.bind(getItem(position)) { mutableViewHolderEvents.value = it } 22 | 23 | class ParticipantDiffCallback : DiffUtil.ItemCallback() { 24 | override fun areItemsTheSame( 25 | oldItem: ParticipantViewState, 26 | newItem: ParticipantViewState, 27 | ): Boolean = 28 | oldItem.sid == newItem.sid 29 | 30 | override fun areContentsTheSame( 31 | oldItem: ParticipantViewState, 32 | newItem: ParticipantViewState, 33 | ): Boolean = 34 | oldItem == newItem 35 | 36 | override fun getChangePayload(oldItem: ParticipantViewState, newItem: ParticipantViewState): Any? { 37 | return newItem 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/ui/room/ParticipantPrimaryView.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2021 Twilio, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.twilio.video.app.ui.room 17 | 18 | import android.content.Context 19 | import android.util.AttributeSet 20 | import android.view.LayoutInflater 21 | import com.twilio.video.app.databinding.ParticipantPrimaryViewBinding 22 | 23 | internal class ParticipantPrimaryView @JvmOverloads constructor( 24 | context: Context, 25 | attrs: AttributeSet? = null, 26 | defStyleAttr: Int = 0, 27 | ) : ParticipantView(context, attrs, defStyleAttr) { 28 | 29 | private val binding: ParticipantPrimaryViewBinding = 30 | ParticipantPrimaryViewBinding.inflate(LayoutInflater.from(context), this, true) 31 | init { 32 | videoLayout = binding.videoLayout 33 | videoIdentity = binding.videoIdentity 34 | videoView = binding.video 35 | selectedLayout = binding.selectedLayout 36 | stubImage = binding.stub 37 | selectedIdentity = binding.selectedIdentity 38 | setIdentity(identity) 39 | setState(state) 40 | setMirror(mirror) 41 | setScaleType(scaleType) 42 | } 43 | 44 | fun showIdentityBadge(show: Boolean) { 45 | binding.videoIdentity.visibility = if (show) VISIBLE else GONE 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/ui/room/PrimaryParticipantController.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 Twilio, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.twilio.video.app.ui.room 17 | 18 | import com.twilio.video.VideoTrack 19 | import com.twilio.video.app.sdk.VideoTrackViewState 20 | 21 | internal class PrimaryParticipantController( 22 | private val primaryView: ParticipantPrimaryView, 23 | ) { 24 | private var primaryItem: Item? = null 25 | 26 | fun renderAsPrimary( 27 | identity: String?, 28 | screenTrack: VideoTrackViewState?, 29 | videoTrack: VideoTrackViewState?, 30 | muted: Boolean, 31 | mirror: Boolean, 32 | ) { 33 | val old = primaryItem 34 | val newItem = Item( 35 | identity, 36 | screenTrack?.videoTrack ?: videoTrack?.videoTrack, 37 | muted, 38 | mirror, 39 | ) 40 | primaryItem = newItem 41 | primaryView.setIdentity(newItem.identity) 42 | primaryView.showIdentityBadge(true) 43 | primaryView.setMuted(newItem.muted) 44 | primaryView.setMirror(newItem.mirror) 45 | val newVideoTrack = newItem.videoTrack 46 | 47 | // Only update sink for a new video track 48 | if (newVideoTrack != old?.videoTrack) { 49 | old?.let { removeSink(it.videoTrack, primaryView) } 50 | newVideoTrack?.let { if (it.isEnabled) it.addSink(primaryView.videoTextureView) } 51 | } 52 | 53 | newVideoTrack?.let { 54 | primaryView.setState(ParticipantView.State.VIDEO) 55 | } ?: primaryView.setState(ParticipantView.State.NO_VIDEO) 56 | } 57 | 58 | private fun removeSink(videoTrack: VideoTrack?, view: ParticipantView) { 59 | if (videoTrack == null || !videoTrack.sinks.contains(view.videoTextureView)) return 60 | videoTrack.removeSink(view.videoTextureView) 61 | } 62 | 63 | internal class Item( 64 | var identity: String?, 65 | var videoTrack: VideoTrack?, 66 | var muted: Boolean, 67 | var mirror: Boolean, 68 | ) 69 | } 70 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/ui/room/RoomEvent.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.ui.room 2 | 3 | import com.twilio.video.NetworkQualityLevel 4 | import com.twilio.video.Participant 5 | import com.twilio.video.Room 6 | import com.twilio.video.VideoTrack 7 | import com.twilio.video.app.data.api.AuthServiceError 8 | import com.twilio.video.app.sdk.RoomStats 9 | 10 | sealed class RoomEvent { 11 | 12 | object Connecting : RoomEvent() 13 | data class Connected( 14 | val participants: List, 15 | val room: Room, 16 | val roomName: String, 17 | ) : RoomEvent() 18 | object Disconnected : RoomEvent() 19 | object ConnectFailure : RoomEvent() 20 | object MaxParticipantFailure : RoomEvent() 21 | object RecordingStarted : RoomEvent() 22 | object RecordingStopped : RoomEvent() 23 | data class TokenError(val serviceError: AuthServiceError? = null) : RoomEvent() 24 | data class DominantSpeakerChanged(val newDominantSpeakerSid: String?) : RoomEvent() 25 | data class StatsUpdate(val roomStats: RoomStats) : RoomEvent() 26 | 27 | sealed class RemoteParticipantEvent : RoomEvent() { 28 | 29 | data class RemoteParticipantConnected(val participant: Participant) : RemoteParticipantEvent() 30 | data class VideoTrackUpdated(val sid: String, val videoTrack: VideoTrack?) : RemoteParticipantEvent() 31 | data class TrackSwitchOff(val sid: String, val videoTrack: VideoTrack, val switchOff: Boolean) : RemoteParticipantEvent() 32 | data class ScreenTrackUpdated( 33 | val sid: String, 34 | val screenTrack: VideoTrack?, 35 | ) : RemoteParticipantEvent() 36 | data class MuteRemoteParticipant(val sid: String, val mute: Boolean) : RemoteParticipantEvent() 37 | data class NetworkQualityLevelChange( 38 | val sid: String, 39 | val networkQualityLevel: NetworkQualityLevel, 40 | ) : RemoteParticipantEvent() 41 | data class RemoteParticipantDisconnected(val sid: String) : RemoteParticipantEvent() 42 | } 43 | 44 | sealed class LocalParticipantEvent : RoomEvent() { 45 | data class VideoTrackUpdated(val videoTrack: VideoTrack?) : LocalParticipantEvent() 46 | object VideoEnabled : LocalParticipantEvent() 47 | object VideoDisabled : LocalParticipantEvent() 48 | object AudioOn : LocalParticipantEvent() 49 | object AudioOff : LocalParticipantEvent() 50 | object AudioEnabled : LocalParticipantEvent() 51 | object AudioDisabled : LocalParticipantEvent() 52 | object ScreenCaptureOn : LocalParticipantEvent() 53 | object ScreenCaptureOff : LocalParticipantEvent() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/ui/room/RoomNotification.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.ui.room 2 | 3 | import android.app.Notification 4 | import android.app.NotificationChannel 5 | import android.app.NotificationManager 6 | import android.app.PendingIntent 7 | import android.content.Context 8 | import android.content.Intent 9 | import android.os.Build 10 | import androidx.core.app.NotificationCompat 11 | import com.twilio.video.app.R 12 | 13 | private const val VIDEO_SERVICE_CHANNEL = "VIDEO_SERVICE_CHANNEL" 14 | const val ONGOING_NOTIFICATION_ID = 1 15 | 16 | class RoomNotification(private val context: Context) { 17 | 18 | private val pendingIntent 19 | get() = 20 | Intent(context, RoomActivity::class.java).let { notificationIntent -> 21 | notificationIntent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP 22 | // Android 12/S requires a flag to be set and FLAG_IMMUTBALE isn't available 23 | // before Android M (23) 24 | var flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0 25 | PendingIntent.getActivity(context, 0, notificationIntent, flags) 26 | } 27 | 28 | init { 29 | createDownloadNotificationChannel( 30 | VIDEO_SERVICE_CHANNEL, 31 | context.getString(R.string.room_notification_channel_title), 32 | context, 33 | ) 34 | } 35 | 36 | fun buildNotification(roomName: String): Notification = 37 | NotificationCompat.Builder(context, VIDEO_SERVICE_CHANNEL) 38 | .setContentTitle(context.getString(R.string.room_notification_title, roomName)) 39 | .setContentText(context.getString(R.string.room_notification_message)) 40 | .setContentIntent(pendingIntent) 41 | .setUsesChronometer(true) 42 | .setSmallIcon(R.drawable.ic_videocam_notification) 43 | .setTicker(context.getString(R.string.room_notification_message)) 44 | .build() 45 | 46 | private fun createDownloadNotificationChannel(channelId: String, channelName: String, context: Context) { 47 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 48 | val notificationChannel = NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_LOW).apply { 49 | lockscreenVisibility = Notification.VISIBILITY_PUBLIC 50 | } 51 | val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager 52 | notificationManager.createNotificationChannel(notificationChannel) 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/ui/room/RoomViewEffect.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.ui.room 2 | 3 | import com.twilio.video.Room 4 | import com.twilio.video.app.data.api.AuthServiceError 5 | import io.uniflow.core.flow.data.UIEvent 6 | 7 | sealed class RoomViewEffect : UIEvent() { 8 | 9 | object PermissionsDenied : RoomViewEffect() 10 | data class Connected(val room: Room) : RoomViewEffect() 11 | object Disconnected : RoomViewEffect() 12 | 13 | object ShowConnectFailureDialog : RoomViewEffect() 14 | object ShowMaxParticipantFailureDialog : RoomViewEffect() 15 | data class ShowTokenErrorDialog(val serviceError: AuthServiceError? = null) : RoomViewEffect() 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/ui/room/RoomViewEvent.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.ui.room 2 | 3 | import android.content.Intent 4 | import com.twilio.audioswitch.AudioDevice 5 | 6 | sealed class RoomViewEvent { 7 | object OnResume : RoomViewEvent() 8 | object OnPause : RoomViewEvent() 9 | object ToggleLocalVideo : RoomViewEvent() 10 | object EnableLocalVideo : RoomViewEvent() 11 | object DisableLocalVideo : RoomViewEvent() 12 | object ToggleLocalAudio : RoomViewEvent() 13 | object EnableLocalAudio : RoomViewEvent() 14 | object DisableLocalAudio : RoomViewEvent() 15 | data class StartScreenCapture(val captureResultCode: Int, val captureIntent: Intent) : RoomViewEvent() 16 | object StopScreenCapture : RoomViewEvent() 17 | object SwitchCamera : RoomViewEvent() 18 | data class SelectAudioDevice(val device: AudioDevice) : RoomViewEvent() 19 | object ActivateAudioDevice : RoomViewEvent() 20 | object DeactivateAudioDevice : RoomViewEvent() 21 | data class Connect(val identity: String, val roomName: String) : RoomViewEvent() 22 | data class PinParticipant(val sid: String) : RoomViewEvent() 23 | data class VideoTrackRemoved(val sid: String) : RoomViewEvent() 24 | data class ScreenTrackRemoved(val sid: String) : RoomViewEvent() 25 | object Disconnect : RoomViewEvent() 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/ui/room/RoomViewModelModule.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.ui.room 2 | 3 | import android.app.Application 4 | import com.twilio.audioswitch.AudioDevice 5 | import com.twilio.audioswitch.AudioSwitch 6 | import com.twilio.video.app.participant.ParticipantManager 7 | import com.twilio.video.app.util.PermissionUtil 8 | import dagger.Module 9 | import dagger.Provides 10 | import dagger.hilt.InstallIn 11 | import dagger.hilt.android.components.ViewModelComponent 12 | import dagger.hilt.android.scopes.ViewModelScoped 13 | 14 | @Module 15 | @InstallIn(ViewModelComponent::class) 16 | class RoomViewModelModule { 17 | 18 | @Provides 19 | @ViewModelScoped 20 | fun providesPermissionUtil(application: Application) = PermissionUtil(application) 21 | 22 | @Provides 23 | @ViewModelScoped 24 | fun providesParticipantManager() = ParticipantManager() 25 | 26 | @Provides 27 | @ViewModelScoped 28 | fun providesInitialViewState(participantManager: ParticipantManager) = RoomViewState(participantManager.primaryParticipant) 29 | 30 | @Provides 31 | @ViewModelScoped 32 | fun providesAudioSwitch(application: Application): AudioSwitch = 33 | AudioSwitch( 34 | application, 35 | loggingEnabled = true, 36 | preferredDeviceList = listOf( 37 | AudioDevice.BluetoothHeadset::class.java, 38 | AudioDevice.WiredHeadset::class.java, 39 | AudioDevice.Speakerphone::class.java, 40 | AudioDevice.Earpiece::class.java, 41 | ), 42 | ) 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/ui/room/RoomViewState.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.ui.room 2 | 3 | import com.twilio.audioswitch.AudioDevice 4 | import com.twilio.video.app.participant.ParticipantViewState 5 | import com.twilio.video.app.sdk.RoomStats 6 | import com.twilio.video.app.sdk.VideoTrackViewState 7 | import com.twilio.video.app.ui.room.RoomViewConfiguration.Lobby 8 | import io.uniflow.core.flow.data.UIState 9 | 10 | data class RoomViewState( 11 | val primaryParticipant: ParticipantViewState, 12 | val title: String? = null, 13 | val participantThumbnails: List? = null, 14 | val selectedDevice: AudioDevice? = null, 15 | val availableAudioDevices: List? = null, 16 | val configuration: RoomViewConfiguration = Lobby, 17 | val isCameraEnabled: Boolean = false, 18 | val localVideoTrack: VideoTrackViewState? = null, 19 | val isMicEnabled: Boolean = false, 20 | val isAudioMuted: Boolean = false, 21 | val isAudioEnabled: Boolean = true, 22 | val isVideoEnabled: Boolean = true, 23 | val isVideoOff: Boolean = false, 24 | val isScreenCaptureOn: Boolean = false, 25 | val isRecording: Boolean = false, 26 | val roomStats: RoomStats? = null, 27 | ) : UIState() 28 | 29 | sealed class RoomViewConfiguration { 30 | object Connecting : RoomViewConfiguration() 31 | object Connected : RoomViewConfiguration() 32 | object Lobby : RoomViewConfiguration() 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/ui/room/UriRoomParser.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.ui.room 2 | 3 | class UriRoomParser(private val uri: UriWrapper) { 4 | 5 | fun parseRoom(): String? = 6 | uri.pathSegments?.let { 7 | if (it.size >= 2) { 8 | it[1] 9 | } else { 10 | null 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/ui/room/UriWrapper.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.ui.room 2 | 3 | import android.net.Uri 4 | 5 | class UriWrapper(uri: Uri?) { 6 | 7 | val pathSegments: MutableList? = uri?.pathSegments 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/ui/settings/AudioSettingsFragment.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.ui.settings 2 | 3 | import android.os.Bundle 4 | import com.twilio.video.app.R 5 | 6 | class AudioSettingsFragment : BaseSettingsFragment() { 7 | 8 | override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { 9 | addPreferencesFromResource(R.xml.audio_preferences) 10 | 11 | setHasOptionsMenu(true) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/ui/settings/BandwidthProfileSettingsFragment.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.ui.settings 2 | 3 | import android.os.Bundle 4 | import com.twilio.video.app.R 5 | import com.twilio.video.app.data.Preferences 6 | 7 | class BandwidthProfileSettingsFragment : BaseSettingsFragment() { 8 | override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { 9 | addPreferencesFromResource(R.xml.bandwidth_profile_preferences) 10 | 11 | setHasOptionsMenu(true) 12 | 13 | setListPreferenceValue( 14 | R.array.settings_screen_bandwidth_profile_mode_values, 15 | Preferences.BANDWIDTH_PROFILE_MODE, 16 | Preferences.BANDWIDTH_PROFILE_MODE_DEFAULT, 17 | ) 18 | setNumberPreferenceValue( 19 | Preferences.BANDWIDTH_PROFILE_MAX_SUBSCRIPTION_BITRATE, 20 | Preferences.BANDWIDTH_PROFILE_MAX_SUBSCRIPTION_BITRATE_DEFAULT, 21 | ) 22 | setListPreferenceValue( 23 | R.array.settings_screen_bandwidth_profile_dominant_speaker_priority_values, 24 | Preferences.BANDWIDTH_PROFILE_DOMINANT_SPEAKER_PRIORITY, 25 | Preferences.BANDWIDTH_PROFILE_DOMINANT_SPEAKER_PRIORITY_DEFAULT, 26 | ) 27 | setListPreferenceValue( 28 | R.array.settings_screen_bandwidth_profile_track_switch_mode_values, 29 | Preferences.BANDWIDTH_PROFILE_TRACK_SWITCH_OFF_MODE, 30 | Preferences.BANDWIDTH_PROFILE_TRACK_SWITCH_OFF_MODE_DEFAULT, 31 | ) 32 | setListPreferenceValue( 33 | R.array.settings_screen_bandwidth_media_optimizations_controls, 34 | Preferences.BANDWIDTH_PROFILE_TRACK_SWITCH_OFF_CONTROL, 35 | Preferences.BANDWIDTH_PROFILE_TRACK_SWITCH_OFF_CONTROL_DEFAULT, 36 | ) 37 | setListPreferenceValue( 38 | R.array.settings_screen_bandwidth_media_optimizations_controls, 39 | Preferences.BANDWIDTH_PROFILE_VIDEO_CONTENT_PREFERENCES_MODE, 40 | Preferences.BANDWIDTH_PROFILE_VIDEO_CONTENT_PREFERENCES_MODE_DEFAULT, 41 | ) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/ui/settings/InternalSettingsFragment.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.ui.settings 2 | 3 | import android.os.Bundle 4 | import androidx.preference.ListPreference 5 | import com.twilio.video.app.R 6 | import com.twilio.video.app.data.Preferences 7 | import com.twilio.video.app.data.api.dto.Topology 8 | 9 | class InternalSettingsFragment : BaseSettingsFragment() { 10 | 11 | override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { 12 | addPreferencesFromResource(R.xml.internal_preferences) 13 | 14 | findPreference(Preferences.ENVIRONMENT)?.run { 15 | value = sharedPreferences.getString( 16 | Preferences.ENVIRONMENT, 17 | Preferences.ENVIRONMENT_DEFAULT, 18 | ) 19 | } 20 | 21 | findPreference(Preferences.TOPOLOGY)?.run { 22 | val roomTypes = Topology.values().map { it.value }.toTypedArray() 23 | entries = roomTypes 24 | entryValues = roomTypes 25 | value = sharedPreferences.getString( 26 | Preferences.TOPOLOGY, 27 | Preferences.TOPOLOGY_DEFAULT, 28 | ) 29 | } 30 | 31 | setHasOptionsMenu(true) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/ui/settings/SettingsActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Twilio, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twilio.video.app.ui.settings 18 | 19 | import android.content.SharedPreferences 20 | import android.os.Bundle 21 | import androidx.appcompat.app.AppCompatActivity 22 | import androidx.preference.Preference 23 | import androidx.preference.PreferenceFragmentCompat 24 | import com.twilio.video.app.util.replaceFragment 25 | import dagger.hilt.android.AndroidEntryPoint 26 | import javax.inject.Inject 27 | 28 | @AndroidEntryPoint 29 | class SettingsActivity : 30 | AppCompatActivity(), 31 | PreferenceFragmentCompat.OnPreferenceStartFragmentCallback { 32 | 33 | @Inject 34 | internal lateinit var sharedPreferences: SharedPreferences 35 | 36 | override fun onCreate(savedInstanceState: Bundle?) { 37 | super.onCreate(savedInstanceState) 38 | 39 | val settingsFragment = SettingsFragment() 40 | 41 | supportFragmentManager.replaceFragment(settingsFragment, android.R.id.content) 42 | } 43 | 44 | override fun onPreferenceStartFragment( 45 | caller: PreferenceFragmentCompat?, 46 | pref: Preference, 47 | ): Boolean { 48 | val args = pref.extras 49 | val fragment = supportFragmentManager.fragmentFactory.instantiate( 50 | classLoader, 51 | pref.fragment, 52 | ) 53 | fragment.arguments = args 54 | fragment.setTargetFragment(caller, 0) 55 | 56 | supportFragmentManager.replaceFragment(fragment, android.R.id.content) 57 | return true 58 | } 59 | 60 | override fun onBackPressed() { 61 | super.onBackPressed() 62 | 63 | if (supportFragmentManager.fragments.size == 0) finish() 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/ui/settings/SettingsFragment.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.ui.settings 2 | 3 | import android.content.Intent 4 | import android.os.Bundle 5 | import androidx.preference.Preference 6 | import androidx.preference.PreferenceManager 7 | import com.twilio.video.Video 8 | import com.twilio.video.app.BuildConfig 9 | import com.twilio.video.app.R 10 | import com.twilio.video.app.auth.Authenticator 11 | import com.twilio.video.app.data.Preferences 12 | import com.twilio.video.app.ui.ScreenSelector 13 | import dagger.hilt.android.AndroidEntryPoint 14 | import javax.inject.Inject 15 | 16 | @AndroidEntryPoint 17 | class SettingsFragment : BaseSettingsFragment() { 18 | 19 | @Inject 20 | internal lateinit var screenSelector: ScreenSelector 21 | 22 | @Inject 23 | internal lateinit var authenticator: Authenticator 24 | 25 | override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { 26 | // Add our preference from resources 27 | addPreferencesFromResource(R.xml.preferences) 28 | 29 | setHasOptionsMenu(true) 30 | 31 | val versionCode = BuildConfig.VERSION_CODE.toString() 32 | findPreference(Preferences.VERSION_NAME)?.summary = "${BuildConfig.VERSION_NAME} ($versionCode)" 33 | findPreference(Preferences.VIDEO_LIBRARY_VERSION)?.summary = Video.getVersion() 34 | findPreference(Preferences.LOGOUT)?.onPreferenceClickListener = Preference.OnPreferenceClickListener { logout(); true } 35 | } 36 | 37 | private fun logout() { 38 | requireActivity().let { activity -> 39 | val loginIntent = Intent(activity, screenSelector.loginScreen) 40 | 41 | // Clear all preferences and set defaults 42 | sharedPreferences.edit().clear().apply() 43 | PreferenceManager.setDefaultValues(activity, R.xml.preferences, true) 44 | 45 | // Return to login activity 46 | loginIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP 47 | authenticator.logout() 48 | startActivity(loginIntent) 49 | activity.finishAffinity() 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/ui/splash/SplashActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Twilio, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twilio.video.app.ui.splash 18 | 19 | import android.annotation.SuppressLint 20 | import android.content.Intent 21 | import android.os.Bundle 22 | import androidx.appcompat.app.AppCompatActivity 23 | import com.twilio.video.app.auth.Authenticator 24 | import com.twilio.video.app.ui.ScreenSelector 25 | import com.twilio.video.app.ui.room.RoomActivity 26 | import dagger.hilt.android.AndroidEntryPoint 27 | import javax.inject.Inject 28 | 29 | @SuppressLint("CustomSplashScreen") 30 | @AndroidEntryPoint 31 | class SplashActivity : AppCompatActivity() { 32 | 33 | @Inject lateinit var authenticator: Authenticator 34 | 35 | @Inject lateinit var screenSelector: ScreenSelector 36 | 37 | override fun onCreate(savedInstanceState: Bundle?) { 38 | super.onCreate(savedInstanceState) 39 | val newIntent = if (authenticator.loggedIn()) { 40 | Intent(this, RoomActivity::class.java) 41 | } else { 42 | Intent(this, screenSelector.loginScreen) 43 | } 44 | startActivity(newIntent.apply { data = intent.data }) 45 | finish() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/util/BuildConfigUtils.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Twilio, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.twilio.video.app.util 17 | 18 | import com.twilio.video.app.BuildConfig 19 | 20 | val isCommunityFlavor: Boolean get() = BuildConfig.FLAVOR == "community" 21 | 22 | val isInternalFlavor: Boolean get() = BuildConfig.FLAVOR == "internal" 23 | 24 | val isReleaseBuildType: Boolean get() = BuildConfig.BUILD_TYPE == "release" 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/util/CompositeDisposableExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.util 2 | 3 | import io.reactivex.disposables.CompositeDisposable 4 | import io.reactivex.disposables.Disposable 5 | 6 | operator fun CompositeDisposable.plus(newDisposable: Disposable) = add(newDisposable) 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/util/CrashlyticsTreeRanger.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Twilio, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twilio.video.app.util; 18 | 19 | import com.google.firebase.crashlytics.FirebaseCrashlytics; 20 | import javax.inject.Inject; 21 | 22 | public class CrashlyticsTreeRanger implements TreeRanger { 23 | 24 | FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance(); 25 | 26 | @Inject 27 | public CrashlyticsTreeRanger() {} 28 | 29 | @Override 30 | public void inform(String message) { 31 | crashlytics.log(message); 32 | } 33 | 34 | @Override 35 | public void caution(String message) { 36 | crashlytics.log(message); 37 | } 38 | 39 | @Override 40 | public void alert(Throwable throwable) { 41 | crashlytics.recordException(throwable); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/util/DebugTree.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Twilio, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twilio.video.app.util; 18 | 19 | import android.util.Log; 20 | import org.jetbrains.annotations.NotNull; 21 | import timber.log.Timber; 22 | 23 | public class DebugTree extends Timber.DebugTree { 24 | private final TreeRanger treeRanger; 25 | 26 | public DebugTree(TreeRanger treeRanger) { 27 | this.treeRanger = treeRanger; 28 | } 29 | 30 | @Override 31 | protected void log(int priority, String tag, @NotNull String message, Throwable throwable) { 32 | // Always log in debug 33 | super.log(priority, tag, message, throwable); 34 | 35 | // Allow the ranger to act accordingly 36 | switch (priority) { 37 | case Log.VERBOSE: 38 | case Log.DEBUG: 39 | case Log.INFO: 40 | treeRanger.inform(message); 41 | break; 42 | case Log.WARN: 43 | treeRanger.caution(message); 44 | break; 45 | case Log.ERROR: 46 | case Log.ASSERT: 47 | if (throwable == null) { 48 | treeRanger.alert(new Exception(message)); 49 | } else { 50 | treeRanger.alert(throwable); 51 | } 52 | break; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/util/EnvUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Twilio, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twilio.video.app.util; 18 | 19 | import static com.twilio.video.app.data.api.TwilioApiEnvironmentKt.TWILIO_API_DEV_ENV; 20 | import static com.twilio.video.app.data.api.TwilioApiEnvironmentKt.TWILIO_API_STAGE_ENV; 21 | 22 | public class EnvUtil { 23 | private static final String TWILIO_DEV_ENV = "Development"; 24 | private static final String TWILIO_STAGE_ENV = "Staging"; 25 | private static final String TWILIO_PROD_ENV = "Production"; 26 | public static final String TWILIO_ENV_KEY = "TWILIO_ENVIRONMENT"; 27 | 28 | public static String getNativeEnvironmentVariableValue(String environment) { 29 | if (environment != null) { 30 | if (environment.equals(TWILIO_API_DEV_ENV)) { 31 | return TWILIO_DEV_ENV; 32 | } else if (environment.equals(TWILIO_API_STAGE_ENV)) { 33 | return TWILIO_STAGE_ENV; 34 | } 35 | } 36 | 37 | return TWILIO_PROD_ENV; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/util/FragmentManagerExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.util 2 | 3 | import androidx.annotation.IdRes 4 | import androidx.fragment.app.Fragment 5 | import androidx.fragment.app.FragmentManager 6 | import androidx.fragment.app.commit 7 | import kotlin.reflect.KClass 8 | 9 | fun FragmentManager.replaceFragment( 10 | fragment: Fragment, 11 | @IdRes fragmentContainer: Int, 12 | ) { 13 | commit { 14 | addToBackStack(null) 15 | replace(fragmentContainer, findFragment(fragment::class) ?: fragment) 16 | } 17 | } 18 | 19 | fun FragmentManager.findFragment(fragment: KClass): Fragment? = 20 | fragments.find { it::class == fragment } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/util/InputUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Twilio, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twilio.video.app.util; 18 | 19 | import android.app.Activity; 20 | import android.content.Context; 21 | import android.view.View; 22 | import android.view.inputmethod.InputMethodManager; 23 | 24 | public final class InputUtils { 25 | 26 | public static void hideKeyboard(Activity activity) { 27 | View view = activity.getCurrentFocus(); 28 | if (view != null) { 29 | InputMethodManager imm = 30 | (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); 31 | imm.hideSoftInputFromWindow(view.getWindowToken(), 0); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/util/PermissionUtil.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.util 2 | 3 | import android.content.Context 4 | import android.content.pm.PackageManager.PERMISSION_GRANTED 5 | import androidx.core.content.ContextCompat.checkSelfPermission 6 | 7 | class PermissionUtil(private val context: Context) { 8 | 9 | fun isPermissionGranted(permission: String) = 10 | checkSelfPermission(context, permission) == PERMISSION_GRANTED 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/util/ReleaseTree.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Twilio, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twilio.video.app.util; 18 | 19 | import android.util.Log; 20 | import org.jetbrains.annotations.NotNull; 21 | import timber.log.Timber; 22 | 23 | public class ReleaseTree extends Timber.Tree { 24 | private final TreeRanger treeRanger; 25 | 26 | public ReleaseTree(TreeRanger treeRanger) { 27 | this.treeRanger = treeRanger; 28 | } 29 | 30 | @Override 31 | protected void log(int priority, String tag, @NotNull String message, Throwable throwable) { 32 | // No logging in release, but we allow the ranger to still act 33 | switch (priority) { 34 | case Log.VERBOSE: 35 | case Log.DEBUG: 36 | case Log.INFO: 37 | treeRanger.inform(message); 38 | break; 39 | case Log.WARN: 40 | treeRanger.caution(message); 41 | break; 42 | case Log.ERROR: 43 | case Log.ASSERT: 44 | if (throwable == null) { 45 | treeRanger.alert(new Exception(message)); 46 | } else { 47 | treeRanger.alert(throwable); 48 | } 49 | break; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/util/SharedPreferencesUtil.kt: -------------------------------------------------------------------------------- 1 | package com.twilio.video.app.util 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | import android.preference.PreferenceManager 6 | 7 | /* 8 | * Utility method that allows getting a shared preference with a default value. The return value 9 | * type is inferred by the default value type. 10 | */ 11 | inline fun SharedPreferences.get(key: String, defaultValue: T): T { 12 | return when (defaultValue) { 13 | is Boolean -> getBoolean(key, defaultValue) as T 14 | is Float -> getFloat(key, defaultValue) as T 15 | is Int -> getInt(key, defaultValue) as T 16 | is Long -> getLong(key, defaultValue) as T 17 | is String -> getString(key, defaultValue) as T 18 | else -> throw IllegalArgumentException("Attempted to get preference with unsupported type") 19 | } 20 | } 21 | 22 | fun getSharedPreferences(context: Context) = PreferenceManager.getDefaultSharedPreferences(context) 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/twilio/video/app/util/TreeRanger.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 Twilio, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.twilio.video.app.util; 18 | 19 | interface TreeRanger { 20 | void inform(String message); 21 | 22 | void caution(String message); 23 | 24 | void alert(Throwable throwable); 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-anydpi/ic_videocam_notification.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/twilio_name_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/drawable-hdpi/twilio_name_white.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/video_logo_splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/drawable-hdpi/video_logo_splash.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/network_quality_level_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/drawable-mdpi/network_quality_level_0.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/network_quality_level_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/drawable-mdpi/network_quality_level_1.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/network_quality_level_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/drawable-mdpi/network_quality_level_2.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/network_quality_level_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/drawable-mdpi/network_quality_level_3.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/network_quality_level_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/drawable-mdpi/network_quality_level_4.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/network_quality_level_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/drawable-mdpi/network_quality_level_5.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/twilio_name_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/drawable-mdpi/twilio_name_white.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/video_logo_splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/drawable-mdpi/video_logo_splash.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/network_quality_level_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/drawable-xhdpi/network_quality_level_0.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/network_quality_level_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/drawable-xhdpi/network_quality_level_1.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/network_quality_level_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/drawable-xhdpi/network_quality_level_2.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/network_quality_level_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/drawable-xhdpi/network_quality_level_3.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/network_quality_level_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/drawable-xhdpi/network_quality_level_4.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/network_quality_level_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/drawable-xhdpi/network_quality_level_5.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/twilio_name_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/drawable-xhdpi/twilio_name_white.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/video_logo_splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/drawable-xhdpi/video_logo_splash.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/network_quality_level_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/drawable-xxhdpi/network_quality_level_0.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/network_quality_level_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/drawable-xxhdpi/network_quality_level_1.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/network_quality_level_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/drawable-xxhdpi/network_quality_level_2.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/network_quality_level_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/drawable-xxhdpi/network_quality_level_3.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/network_quality_level_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/drawable-xxhdpi/network_quality_level_4.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/network_quality_level_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/drawable-xxhdpi/network_quality_level_5.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/twilio_name_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/drawable-xxhdpi/twilio_name_white.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/video_logo_splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/drawable-xxhdpi/video_logo_splash.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/twilio_name_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/drawable-xxxhdpi/twilio_name_white.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/video_logo_splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/drawable-xxxhdpi/video_logo_splash.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/badge_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/edit_text_cursor.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_account_circle_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_account_circle_white_48px.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add_circle_white_24px.xml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 9 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_bluetooth_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_call_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_call_end_white_24px.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_call_white_24px.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_close_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_error_outline.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_exit_to_app_white_24px.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 10 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_headset_mic_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_mic_green_24px.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_mic_off_gray_24px.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 10 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_mic_off_red_24px.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 10 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_mic_red_24px.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_mic_white_24px.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_more_vert_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_pause_green_24px.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_pause_red_24px.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_phonelink_ring_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_pin.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_play_white_24px.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_recording.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_screen_share_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_stats_disabled_image.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 9 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_stop_screen_share_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_switch_camera_512dp.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_switch_camera_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_thumbnail_no_audio.xml: -------------------------------------------------------------------------------- 1 | 6 | 12 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_videocam_green_24px.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 10 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_videocam_off_gray_24px.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 10 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_videocam_off_red_24px.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 10 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_videocam_white_24px.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 10 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_volume_down_gray_24px.xml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 11 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_volume_down_green_24px.xml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 11 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_volume_down_white_24px.xml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 11 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_volume_up_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/join_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/participant_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/participant_selected_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/participant_stroke.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/roundbutton.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/splash_screen.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | 10 | 11 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/content_room.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | 17 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/layout/join_room.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 |