├── .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 |
21 |
22 |
36 |
37 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/number_preference.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
15 |
16 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/room_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #cc2b33
4 | #a52229
5 | #b0bec5
6 | #de000000
7 | #8a000000
8 |
9 |
10 | #80ffffff
11 |
12 | #757575
13 | #424242
14 | #9E9E9E
15 |
16 | #66000000
17 | #e0e0e0
18 | #80ffffff
19 | #1fffffff
20 | #80ffffff
21 | #CC9B9B9B
22 | #CC2B33
23 |
24 | #ccffffff
25 | #CC000000
26 | #cc2b33
27 |
28 |
29 | #66000000
30 | #cc000000
31 | #80000000
32 |
33 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 | 16dp
6 | 90dp
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/audio_preferences.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
12 |
17 |
18 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/internal_preferences.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
13 |
20 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/preferences.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
10 |
14 |
18 |
19 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/release/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Video
4 |
5 |
--------------------------------------------------------------------------------
/app/src/test/java/com/twilio/video/app/BaseUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.twilio.video.app
2 |
3 | import net.lachlanmckee.timberjunit.TimberTestRule
4 | import org.junit.Rule
5 |
6 | open class BaseUnitTest {
7 | @get:Rule
8 | var logAllAlwaysRule = TimberTestRule.logAllAlways()
9 | }
10 |
--------------------------------------------------------------------------------
/app/src/test/java/com/twilio/video/app/data/api/AuthServiceErrorTest.kt:
--------------------------------------------------------------------------------
1 | package com.twilio.video.app.data.api
2 |
3 | import com.twilio.video.app.BaseUnitTest
4 | import com.twilio.video.app.data.api.AuthServiceError.EXPIRED_PASSCODE_ERROR
5 | import com.twilio.video.app.data.api.AuthServiceError.INVALID_PASSCODE_ERROR
6 | import junitparams.JUnitParamsRunner
7 | import junitparams.Parameters
8 | import org.hamcrest.CoreMatchers.equalTo
9 | import org.hamcrest.MatcherAssert.assertThat
10 | import org.junit.Test
11 | import org.junit.runner.RunWith
12 |
13 | @RunWith(JUnitParamsRunner::class)
14 | class AuthServiceErrorTest : BaseUnitTest() {
15 |
16 | fun params() =
17 | arrayOf(
18 | arrayOf("passcode incorrect", INVALID_PASSCODE_ERROR),
19 | arrayOf("passcode expired", EXPIRED_PASSCODE_ERROR),
20 | arrayOf("bad input", null),
21 | arrayOf(null, null),
22 | )
23 |
24 | @Parameters(method = "params")
25 | @Test
26 | fun `value should return the corresponding AuthServiceError enum`(
27 | input: String?,
28 | expectedValue: AuthServiceError?,
29 | ) {
30 | assertThat(AuthServiceError.value(input), equalTo(expectedValue))
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/test/java/com/twilio/video/app/ui/room/UriRoomParserTest.kt:
--------------------------------------------------------------------------------
1 | package com.twilio.video.app.ui.room
2 |
3 | import android.net.Uri
4 | import com.twilio.video.app.BaseUnitTest
5 | import org.hamcrest.CoreMatchers.equalTo
6 | import org.hamcrest.CoreMatchers.`is`
7 | import org.hamcrest.CoreMatchers.nullValue
8 | import org.hamcrest.MatcherAssert.assertThat
9 | import org.junit.Test
10 | import org.mockito.kotlin.mock
11 | import org.mockito.kotlin.whenever
12 |
13 | class UriRoomParserTest : BaseUnitTest() {
14 |
15 | @Test
16 | fun `parseRoom should return the room name`() {
17 | val expectedRoomName = "test"
18 | val pathSegments = listOf("room", expectedRoomName)
19 | val uri = mock {
20 | whenever(mock.pathSegments).thenReturn(pathSegments)
21 | }
22 | val uriWrapper = UriWrapper(uri)
23 |
24 | val actualRoom = UriRoomParser(uriWrapper).parseRoom()
25 |
26 | assertThat(actualRoom, equalTo(expectedRoomName))
27 | }
28 |
29 | @Test
30 | fun `parseRoom should return the room name prior to subsequent to path segments`() {
31 | val expectedRoomName = "test"
32 | val pathSegments = listOf("room", expectedRoomName, "blah", "something_else")
33 | val uri = mock {
34 | whenever(mock.pathSegments).thenReturn(pathSegments)
35 | }
36 | val uriWrapper = UriWrapper(uri)
37 |
38 | val actualRoom = UriRoomParser(uriWrapper).parseRoom()
39 |
40 | assertThat(actualRoom, equalTo(expectedRoomName))
41 | }
42 |
43 | @Test
44 | fun `parseRoom should return null if the room name is not in the path`() {
45 | val pathSegments = listOf("room")
46 | val uri = mock {
47 | whenever(mock.pathSegments).thenReturn(pathSegments)
48 | }
49 | val uriWrapper = UriWrapper(uri)
50 |
51 | val actualRoom = UriRoomParser(uriWrapper).parseRoom()
52 |
53 | assertThat(actualRoom, `is`(nullValue()))
54 | }
55 |
56 | @Test
57 | fun `parseRoom should return null if the path segments are null`() {
58 | val uri = UriWrapper(null)
59 | val actualRoom = UriRoomParser(uri).parseRoom()
60 |
61 | assertThat(actualRoom, `is`(nullValue()))
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/app/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker:
--------------------------------------------------------------------------------
1 | mock-maker-inline
--------------------------------------------------------------------------------
/app/src/test/resources/robolectric.properties:
--------------------------------------------------------------------------------
1 | sdk=28
--------------------------------------------------------------------------------
/app/src/testCommunity/java/com/twilio/video/app/fake/FakeSecurityModule.kt:
--------------------------------------------------------------------------------
1 | package com.twilio.video.app.fake
2 |
3 | import com.twilio.video.app.security.SecurePreferences
4 | import com.twilio.video.app.security.SecurePreferencesFake
5 | import com.twilio.video.app.security.SecurityModule
6 | import dagger.Module
7 | import dagger.Provides
8 | import dagger.hilt.components.SingletonComponent
9 | import dagger.hilt.testing.TestInstallIn
10 | import javax.inject.Singleton
11 |
12 | @Module
13 | @TestInstallIn(
14 | components = [SingletonComponent::class],
15 | replaces = [SecurityModule::class],
16 | )
17 | class FakeSecurityModule {
18 |
19 | @Provides
20 | @Singleton
21 | fun providesSecurePreferences(): SecurePreferences = SecurePreferencesFake()
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/testCommunity/java/com/twilio/video/app/fake/MockCommunityAuthModule.kt:
--------------------------------------------------------------------------------
1 | package com.twilio.video.app.fake
2 |
3 | import android.content.SharedPreferences
4 | import com.twilio.video.app.auth.Authenticator
5 | import com.twilio.video.app.auth.CommunityAuthModule
6 | import com.twilio.video.app.auth.CommunityAuthenticator
7 | import com.twilio.video.app.data.api.TokenService
8 | import com.twilio.video.app.security.SecurePreferences
9 | import dagger.Module
10 | import dagger.Provides
11 | import dagger.hilt.components.SingletonComponent
12 | import dagger.hilt.testing.TestInstallIn
13 | import kotlinx.coroutines.Dispatchers
14 |
15 | @Module
16 | @TestInstallIn(
17 | components = [SingletonComponent::class],
18 | replaces = [CommunityAuthModule::class],
19 | )
20 | class MockCommunityAuthModule {
21 | @Provides
22 | fun providesCommunityAuthenticator(
23 | preferences: SharedPreferences,
24 | securePreferences: SecurePreferences,
25 | tokenService: TokenService,
26 | ): Authenticator {
27 | return CommunityAuthenticator(preferences, securePreferences, tokenService, Dispatchers.Main)
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/testCommunity/java/com/twilio/video/app/fake/MockCommunityAuthServiceModule.kt:
--------------------------------------------------------------------------------
1 | package com.twilio.video.app.fake
2 |
3 | import android.content.SharedPreferences
4 | import com.twilio.video.app.android.SharedPreferencesWrapper
5 | import com.twilio.video.app.data.CommunityAuthServiceModule
6 | import com.twilio.video.app.data.api.AuthService
7 | import com.twilio.video.app.data.api.AuthServiceRepository
8 | import com.twilio.video.app.data.api.TokenService
9 | import com.twilio.video.app.security.SecurePreferences
10 | import dagger.Module
11 | import dagger.Provides
12 | import dagger.hilt.components.SingletonComponent
13 | import dagger.hilt.testing.TestInstallIn
14 | import org.mockito.kotlin.mock
15 | import javax.inject.Singleton
16 |
17 | @Module
18 | @TestInstallIn(
19 | components = [SingletonComponent::class],
20 | replaces = [CommunityAuthServiceModule::class],
21 | )
22 | class MockCommunityAuthServiceModule {
23 |
24 | @Provides
25 | @Singleton
26 | fun providesAuthService(): AuthService = mock()
27 |
28 | @Provides
29 | fun providesTokenService(
30 | authService: AuthService,
31 | securePreferences: SecurePreferences,
32 | sharedPreferences: SharedPreferences,
33 | ): TokenService {
34 | return AuthServiceRepository(authService, securePreferences, SharedPreferencesWrapper(sharedPreferences))
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/testCommunity/java/com/twilio/video/app/screen/CommunityLoginScreen.kt:
--------------------------------------------------------------------------------
1 | package com.twilio.video.app.screen
2 |
3 | import androidx.test.espresso.Espresso.onView
4 | import androidx.test.espresso.action.ViewActions.clearText
5 | import androidx.test.espresso.action.ViewActions.click
6 | import androidx.test.espresso.action.ViewActions.scrollTo
7 | import androidx.test.espresso.action.ViewActions.typeText
8 | import androidx.test.espresso.assertion.ViewAssertions.matches
9 | import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
10 | import androidx.test.espresso.matcher.ViewMatchers.isEnabled
11 | import androidx.test.espresso.matcher.ViewMatchers.withId
12 | import androidx.test.espresso.matcher.ViewMatchers.withText
13 | import com.twilio.video.app.R
14 | import com.twilio.video.app.util.isTextInputLayoutError
15 | import org.hamcrest.CoreMatchers.equalTo
16 | import org.hamcrest.CoreMatchers.`is`
17 | import org.hamcrest.CoreMatchers.not
18 | import org.hamcrest.CoreMatchers.notNullValue
19 | import org.hamcrest.MatcherAssert.assertThat
20 | import org.robolectric.shadows.ShadowDialog.getLatestDialog
21 |
22 | fun enterYourName(name: String) {
23 | onView(withId(R.id.name)).perform(clearText(), typeText(name))
24 | }
25 |
26 | fun clickLoginButton() {
27 | onView(withId(R.id.login)).perform(scrollTo(), click())
28 | }
29 |
30 | fun assertLoadingIndicatorIsDisplayed() {
31 | onView(withId(R.id.progress_bar)).check(matches(isDisplayed()))
32 | }
33 |
34 | fun assertLoadingIndicatorIsNotDisplayed() {
35 | onView(withId(R.id.progress_bar)).check(matches(not(isDisplayed())))
36 | }
37 |
38 | fun assertLoginButtonIsEnabled() {
39 | onView(withId(R.id.login)).check(matches(isEnabled()))
40 | }
41 |
42 | fun assertLoginButtonIsDisabled() {
43 | onView(withId(R.id.login)).check(matches(not(isEnabled())))
44 | }
45 |
46 | fun assertInvalidPasscodeErrorIsDisplayed() {
47 | onView(withText(R.string.login_screen_invalid_passcode_error)).check(matches(isDisplayed()))
48 | }
49 |
50 | fun assertExpiredPasscodeErrorIsDisplayed() {
51 | onView(withText(R.string.login_screen_expired_passcode_error)).check(matches(isDisplayed()))
52 | }
53 |
54 | fun assertThatPasscodeErrorIsDisabled() {
55 | onView(withId(R.id.passcode_input)).check(matches(not(isTextInputLayoutError())))
56 | }
57 |
58 | fun assertErrorDialogIsDisplayed() {
59 | getLatestDialog().let { dialog ->
60 | assertThat(dialog, `is`(notNullValue()))
61 | assertThat(dialog.isShowing, equalTo(true))
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/app/src/testCommunity/java/com/twilio/video/app/security/SecurePreferencesFake.kt:
--------------------------------------------------------------------------------
1 | package com.twilio.video.app.security
2 |
3 | class SecurePreferencesFake : SecurePreferences {
4 |
5 | private var preferences: MutableMap = mutableMapOf()
6 |
7 | override fun putSecureString(key: String, value: String) {
8 | preferences[key] = value
9 | }
10 |
11 | override fun getSecureString(key: String) = preferences[key]
12 | }
13 |
--------------------------------------------------------------------------------
/app/src/testCommunity/java/com/twilio/video/app/util/AuthServiceTestData.kt:
--------------------------------------------------------------------------------
1 | package com.twilio.video.app.util
2 |
3 | import com.twilio.video.app.data.api.AuthServiceResponseDTO
4 | import okhttp3.ResponseBody
5 | import org.mockito.kotlin.mock
6 | import org.mockito.kotlin.whenever
7 | import retrofit2.HttpException
8 | import retrofit2.Response
9 |
10 | const val INVALID_PASSCODE_ERROR =
11 | """{
12 | "error": {
13 | "message": "passcode incorrect",
14 | "explanation": "The passcode used to validate application users is incorrect."
15 | }
16 | }"""
17 |
18 | const val EXPIRED_PASSCODE_ERROR =
19 | """{
20 | "error": {
21 | "message": "passcode expired",
22 | "explanation": "The passcode used to validate application users has expired. Re-deploy the application to refresh the passcode."
23 | }
24 | }"""
25 |
26 | const val UNKNOWN_ERROR_MESSAGE =
27 | """{
28 | "error": {
29 | "message": "Unknown",
30 | "explanation": "Something went wrong ¯\_(ツ)_/¯"
31 | }
32 | }"""
33 |
34 | fun getMockHttpException(errorBody: String?): HttpException {
35 | val responseBody: ResponseBody = mock {
36 | val errorString = errorBody
37 | whenever(mock.string()).thenReturn(errorString)
38 | }
39 | val response: Response = mock {
40 | whenever(mock.errorBody()).thenReturn(responseBody)
41 | }
42 | return HttpException(response)
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/testCommunity/java/com/twilio/video/app/util/TextInputLayoutMatcher.kt:
--------------------------------------------------------------------------------
1 | package com.twilio.video.app.util
2 |
3 | import android.view.View
4 | import com.google.android.material.textfield.TextInputLayout
5 | import org.hamcrest.Description
6 | import org.hamcrest.Matcher
7 | import org.hamcrest.TypeSafeMatcher
8 |
9 | fun isTextInputLayoutError(): Matcher = object : TypeSafeMatcher() {
10 |
11 | override fun describeTo(description: Description?) { }
12 |
13 | override fun matchesSafely(item: View?): Boolean =
14 | if (item is TextInputLayout) item.isErrorEnabled else false
15 | }
16 |
--------------------------------------------------------------------------------
/app/video-android-app.keystore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/app/video-android-app.keystore
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | versionCode=164
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 | android.nonTransitiveRClass=false
5 | android.nonFinalResIds=false
6 | org.gradle.jvmargs=-Xmx2048m
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Jun 01 16:21:34 MDT 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip
7 |
--------------------------------------------------------------------------------
/images/community-variant/community-variant.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/images/community-variant/community-variant.png
--------------------------------------------------------------------------------
/images/emulator/emulator_avd_settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/images/emulator/emulator_avd_settings.png
--------------------------------------------------------------------------------
/images/emulator/emulator_navigate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/images/emulator/emulator_navigate.png
--------------------------------------------------------------------------------
/images/emulator/emulator_select_hardware.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/images/emulator/emulator_select_hardware.png
--------------------------------------------------------------------------------
/images/emulator/emulator_select_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/images/emulator/emulator_select_image.png
--------------------------------------------------------------------------------
/images/emulator/emulator_virtual_device.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/twilio/twilio-video-app-android/81c49da74a37bc1eb02c3c6fb4a44d26a3647e0b/images/emulator/emulator_virtual_device.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/ui-test-args.yaml:
--------------------------------------------------------------------------------
1 | e2e-tests:
2 | timeout: 30m
3 | type: instrumentation
4 | app: app/build/outputs/apk/internal/debug/app-internal-debug.apk
5 | test: app/build/outputs/apk/androidTest/internal/debug/app-internal-debug-androidTest.apk
6 | device:
7 | - {model: hammerhead, version: 23}
8 | - {model: redfin, version: 30}
9 | test-targets:
10 | - "annotation com.twilio.video.app.e2eTest.E2ETest"
11 |
12 | integration-tests:
13 | timeout: 30m
14 | type: instrumentation
15 | app: app/build/outputs/apk/internal/debug/app-internal-debug.apk
16 | test: app/build/outputs/apk/androidTest/internal/debug/app-internal-debug-androidTest.apk
17 | device:
18 | - {model: hammerhead, version: 23}
19 | - {model: griffin, version: 24}
20 | - {model: starqlteue, version: 26}
21 | - {model: pettyl, version: 27}
22 | - {model: redfin, version: 30}
23 | test-targets:
24 | - "annotation com.twilio.video.app.integrationTest.IntegrationTest"
--------------------------------------------------------------------------------