├── app ├── .gitignore ├── analytics-noop │ ├── consumer-rules.pro │ ├── .gitignore │ ├── src │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ └── com │ │ │ └── studio4plus │ │ │ └── homerplayer │ │ │ └── analytics │ │ │ └── StatsLogger.java │ ├── proguard-rules.pro │ └── build.gradle ├── crashreporting-noop │ ├── .gitignore │ ├── consumer-rules.pro │ ├── src │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ └── com │ │ │ └── studio4plus │ │ │ └── homerplayer │ │ │ └── crashreporting │ │ │ └── CrashReporting.java │ ├── build.gradle │ └── proguard-rules.pro ├── crashreporting-sentry │ ├── .gitignore │ ├── consumer-rules.pro │ ├── src │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── studio4plus │ │ │ │ └── homerplayer │ │ │ │ └── crashreporting │ │ │ │ └── CrashReporting.java │ │ ├── test │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── studio4plus │ │ │ │ └── homerplayer │ │ │ │ └── crashreporting │ │ │ │ └── ExampleUnitTest.java │ │ └── androidTest │ │ │ └── java │ │ │ └── com │ │ │ └── studio4plus │ │ │ └── homerplayer │ │ │ └── crashreporting │ │ │ └── ExampleInstrumentedTest.java │ ├── proguard-rules.pro │ └── build.gradle ├── src │ ├── main │ │ ├── res │ │ │ ├── xml │ │ │ │ ├── device_admin_info.xml │ │ │ │ ├── filepaths.xml │ │ │ │ ├── preferences_kiosk.xml │ │ │ │ ├── preferences_playback.xml │ │ │ │ ├── preferences_ui.xml │ │ │ │ └── preferences_main.xml │ │ │ ├── raw │ │ │ │ └── rewind_sound.wav │ │ │ ├── drawable-xxxhdpi │ │ │ │ ├── ff.png │ │ │ │ ├── logo.png │ │ │ │ ├── rewind.png │ │ │ │ ├── battery_0.png │ │ │ │ ├── battery_1.png │ │ │ │ ├── battery_2.png │ │ │ │ ├── battery_3.png │ │ │ │ ├── hint_tap.png │ │ │ │ ├── battery_red_0.png │ │ │ │ ├── battery_red_1.png │ │ │ │ ├── hint_flip_to_stop.png │ │ │ │ └── hint_horizontal_swipe.png │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── values-v21 │ │ │ │ └── styles.xml │ │ │ ├── values │ │ │ │ ├── styleable-attrs.xml │ │ │ │ ├── integer.xml │ │ │ │ ├── dimen.xml │ │ │ │ ├── button_styles.xml │ │ │ │ ├── attrs.xml │ │ │ │ ├── arrays.xml │ │ │ │ └── color.xml │ │ │ ├── layout │ │ │ │ ├── preference_dialog_confirm.xml │ │ │ │ ├── item_audiobooks_folder_add.xml │ │ │ │ ├── fragment_init.xml │ │ │ │ ├── settings_activity.xml │ │ │ │ ├── folders_activity.xml │ │ │ │ ├── fragment_book_list.xml │ │ │ │ ├── item_audiobooks_folder.xml │ │ │ │ ├── main_activity.xml │ │ │ │ ├── fragment_book_item.xml │ │ │ │ ├── fragment_no_books.xml │ │ │ │ ├── hint_horizontal_image.xml │ │ │ │ └── hint_vertical_image.xml │ │ │ ├── drawable │ │ │ │ ├── classic_volume_indicator_background.xml │ │ │ │ ├── high_contrast_volume_indicator_background.xml │ │ │ │ ├── battery_charging_2.xml │ │ │ │ ├── battery_critical.xml │ │ │ │ ├── battery_charging_1.xml │ │ │ │ └── battery_charging_0.xml │ │ │ ├── values-xlarge │ │ │ │ └── dimen.xml │ │ │ ├── values-zh-rCN │ │ │ │ └── arrays.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── values-large │ │ │ │ └── dimen.xml │ │ │ ├── values-fr │ │ │ │ └── arrays.xml │ │ │ ├── values-pl │ │ │ │ └── arrays.xml │ │ │ ├── drawable-v26 │ │ │ │ └── ic_launcher_mono.xml │ │ │ ├── drawable-v24 │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ └── ic_launcher_foreground.xml │ │ │ └── animator │ │ │ │ └── bounce.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── studio4plus │ │ │ │ └── homerplayer │ │ │ │ ├── events │ │ │ │ ├── DemoSamplesInstallationStartedEvent.java │ │ │ │ ├── PlaybackStoppedEvent.java │ │ │ │ ├── SettingsEnteredEvent.java │ │ │ │ ├── MediaStoreUpdateEvent.java │ │ │ │ ├── DeviceAdminChangeEvent.java │ │ │ │ ├── PlaybackStoppingEvent.java │ │ │ │ ├── PlaybackFatalErrorEvent.java │ │ │ │ ├── BatteryStatusChangeEvent.java │ │ │ │ ├── CurrentBookChangedEvent.java │ │ │ │ ├── AudioBooksChangedEvent.java │ │ │ │ ├── DemoSamplesInstallationFinishedEvent.java │ │ │ │ ├── KioskModeChanged.java │ │ │ │ ├── PlaybackErrorEvent.java │ │ │ │ └── PlaybackProgressedEvent.java │ │ │ │ ├── util │ │ │ │ ├── Callback.java │ │ │ │ ├── Predicate.java │ │ │ │ ├── DirectoryFilter.java │ │ │ │ ├── DebugUtil.java │ │ │ │ ├── VersionUtil.java │ │ │ │ ├── OrFilter.java │ │ │ │ ├── LifecycleAwareRunnable.java │ │ │ │ ├── ViewUtils.java │ │ │ │ ├── MediaScannerUtil.java │ │ │ │ ├── CollectionUtils.java │ │ │ │ ├── TlsSSLSocketFactory.java │ │ │ │ └── FilesystemUtil.java │ │ │ │ ├── battery │ │ │ │ ├── ChargeLevel.java │ │ │ │ ├── BatteryStatus.java │ │ │ │ └── BatteryStatusProvider.java │ │ │ │ ├── ui │ │ │ │ ├── MainUiComponent.java │ │ │ │ ├── BookListUi.java │ │ │ │ ├── SpeakerProvider.java │ │ │ │ ├── ActivityScope.java │ │ │ │ ├── ColorTheme.java │ │ │ │ ├── ActivityComponent.java │ │ │ │ ├── NoBooksUi.java │ │ │ │ ├── MainUi.java │ │ │ │ ├── settings │ │ │ │ │ ├── UiSettingsFragment.java │ │ │ │ │ ├── OnFolderSelected.java │ │ │ │ │ ├── ConfirmDialogFragmentCompat.java │ │ │ │ │ ├── ConfirmDialogPreference.java │ │ │ │ │ ├── OpenDocumentTreeUtils.java │ │ │ │ │ └── BaseSettingsFragment.java │ │ │ │ ├── ActivityModule.java │ │ │ │ ├── SimpleAnimatorListener.java │ │ │ │ ├── classic │ │ │ │ │ ├── ClassicMainUiComponent.java │ │ │ │ │ ├── ClassicMainUiModule.java │ │ │ │ │ ├── ClassicInitUi.java │ │ │ │ │ ├── BookListChildFragment.java │ │ │ │ │ ├── ClassicMainUi.java │ │ │ │ │ ├── ClassicPlaybackUi.java │ │ │ │ │ └── FragmentBookItem.java │ │ │ │ ├── PlaybackUi.java │ │ │ │ ├── HomeActivity.java │ │ │ │ ├── PressReleaseDetector.java │ │ │ │ ├── OrientationActivityDelegate.java │ │ │ │ ├── Speaker.java │ │ │ │ ├── PermissionUtils.java │ │ │ │ ├── SnippetPlayer.java │ │ │ │ ├── FFRewindTimer.java │ │ │ │ ├── HintOverlay.java │ │ │ │ ├── KioskModeHandler.java │ │ │ │ ├── BatteryStatusIndicator.java │ │ │ │ └── SoundBank.java │ │ │ │ ├── ApplicationScope.java │ │ │ │ ├── concurrency │ │ │ │ ├── SimpleDeferred.java │ │ │ │ ├── SimpleFuture.java │ │ │ │ ├── BackgroundExecutor.java │ │ │ │ ├── BackgroundDeferred.java │ │ │ │ └── BaseDeferred.java │ │ │ │ ├── player │ │ │ │ ├── DurationQueryController.java │ │ │ │ ├── ExoLogger.java │ │ │ │ └── PlaybackController.java │ │ │ │ ├── model │ │ │ │ ├── LibraryContentType.java │ │ │ │ └── ColourScheme.java │ │ │ │ ├── service │ │ │ │ ├── AudioBookPlayerModule.java │ │ │ │ └── NotificationUtil.java │ │ │ │ ├── deviceadmin │ │ │ │ ├── AdminPolicyComplianceActivity.java │ │ │ │ ├── GetProvisioningModeActivity.java │ │ │ │ └── HomerPlayerDeviceAdmin.java │ │ │ │ ├── logging │ │ │ │ ├── LoggingSetup.java │ │ │ │ └── UncaughtExceptionLogger.java │ │ │ │ ├── demosamples │ │ │ │ ├── DemoSamplesFolderProvider.java │ │ │ │ └── Unzipper.java │ │ │ │ ├── AudioBookManagerModule.java │ │ │ │ ├── filescanner │ │ │ │ ├── FileSet.java │ │ │ │ ├── LegacyFolderProvider.java │ │ │ │ └── FileScanner.java │ │ │ │ ├── SamplesMap.java │ │ │ │ ├── content │ │ │ │ ├── ConfigurationCursor.java │ │ │ │ └── ConfigurationContentProvider.java │ │ │ │ ├── MediaStoreUpdateObserver.java │ │ │ │ ├── CrashLoopProtection.java │ │ │ │ └── ApplicationComponent.java │ │ └── templates │ │ │ └── provisioning-qrcode.txt │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── studio4plus │ │ │ └── homerplayer │ │ │ └── ApplicationTest.java │ └── gitHub │ │ └── AndroidManifest.xml ├── lint.xml └── proguard-rules.pro ├── icon_512.png ├── fastlane └── metadata │ └── android │ ├── en-US │ ├── video.txt │ ├── short_description.txt │ ├── images │ │ ├── icon.png │ │ ├── featureGraphic.jpg │ │ └── phoneScreenshots │ │ │ ├── 1.jpg │ │ │ ├── 2.jpg │ │ │ ├── 3.jpg │ │ │ ├── 4.jpg │ │ │ └── 5.jpg │ └── full_description.txt │ └── de │ └── short_description.txt ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── settings.gradle ├── .gitignore ├── .codeclimate.yml ├── findbugs_excludes.xml ├── .travis.yml ├── gradle.properties ├── LICENSE ├── README.md ├── PRIVACY.md └── gradlew.bat /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/analytics-noop/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/analytics-noop/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/crashreporting-noop/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/crashreporting-noop/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/crashreporting-sentry/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/crashreporting-sentry/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msimonides/homerplayer/HEAD/icon_512.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/video.txt: -------------------------------------------------------------------------------- 1 | https://www.youtube.com/watch?v=RfLkoLtxzng -------------------------------------------------------------------------------- /fastlane/metadata/android/de/short_description.txt: -------------------------------------------------------------------------------- 1 | Hörbuch-Player für ältere und sehbehinderte Menschen -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/short_description.txt: -------------------------------------------------------------------------------- 1 | audio book player for the elderly and visually impaired -------------------------------------------------------------------------------- /app/src/main/res/xml/device_admin_info.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msimonides/homerplayer/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/raw/rewind_sound.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msimonides/homerplayer/HEAD/app/src/main/res/raw/rewind_sound.wav -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msimonides/homerplayer/HEAD/app/src/main/res/drawable-xxxhdpi/ff.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msimonides/homerplayer/HEAD/app/src/main/res/drawable-xxxhdpi/logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/rewind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msimonides/homerplayer/HEAD/app/src/main/res/drawable-xxxhdpi/rewind.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msimonides/homerplayer/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msimonides/homerplayer/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msimonides/homerplayer/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | include ':app:analytics-noop' 3 | include ':app:crashreporting-noop' 4 | include ':app:crashreporting-sentry' 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/battery_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msimonides/homerplayer/HEAD/app/src/main/res/drawable-xxxhdpi/battery_0.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/battery_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msimonides/homerplayer/HEAD/app/src/main/res/drawable-xxxhdpi/battery_1.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/battery_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msimonides/homerplayer/HEAD/app/src/main/res/drawable-xxxhdpi/battery_2.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/battery_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msimonides/homerplayer/HEAD/app/src/main/res/drawable-xxxhdpi/battery_3.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/hint_tap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msimonides/homerplayer/HEAD/app/src/main/res/drawable-xxxhdpi/hint_tap.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msimonides/homerplayer/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msimonides/homerplayer/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msimonides/homerplayer/HEAD/fastlane/metadata/android/en-US/images/icon.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /app/app.iml 3 | /app/*.apk 4 | /local.properties 5 | /.idea/* 6 | .DS_Store 7 | /build 8 | 9 | *~ 10 | *.swp 11 | *.swo 12 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msimonides/homerplayer/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msimonides/homerplayer/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/battery_red_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msimonides/homerplayer/HEAD/app/src/main/res/drawable-xxxhdpi/battery_red_0.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/battery_red_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msimonides/homerplayer/HEAD/app/src/main/res/drawable-xxxhdpi/battery_red_1.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msimonides/homerplayer/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msimonides/homerplayer/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/hint_flip_to_stop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msimonides/homerplayer/HEAD/app/src/main/res/drawable-xxxhdpi/hint_flip_to_stop.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msimonides/homerplayer/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/featureGraphic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msimonides/homerplayer/HEAD/fastlane/metadata/android/en-US/images/featureGraphic.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/hint_horizontal_swipe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msimonides/homerplayer/HEAD/app/src/main/res/drawable-xxxhdpi/hint_horizontal_swipe.png -------------------------------------------------------------------------------- /app/analytics-noop/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msimonides/homerplayer/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/1.jpg -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msimonides/homerplayer/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/2.jpg -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msimonides/homerplayer/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/3.jpg -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msimonides/homerplayer/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/4.jpg -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msimonides/homerplayer/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/5.jpg -------------------------------------------------------------------------------- /app/crashreporting-noop/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /app/src/main/res/xml/filepaths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/events/DemoSamplesInstallationStartedEvent.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.events; 2 | 3 | public class DemoSamplesInstallationStartedEvent { 4 | } 5 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/util/Callback.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.util; 2 | 3 | public interface Callback { 4 | void onFinished(ResultType result); 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/events/PlaybackStoppedEvent.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.events; 2 | 3 | /** 4 | * An event sent when playback is stopped. 5 | */ 6 | public class PlaybackStoppedEvent { 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/events/SettingsEnteredEvent.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.events; 2 | 3 | /** 4 | * Posted when the settings screen is entered. 5 | */ 6 | public class SettingsEnteredEvent { 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/util/Predicate.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.util; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | public interface Predicate { 6 | boolean isTrue(@NonNull T argument); 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/battery/ChargeLevel.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.battery; 2 | 3 | public enum ChargeLevel { 4 | CRITICAL, 5 | LEVEL_1, 6 | LEVEL_2, 7 | LEVEL_3, 8 | FULL 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/styleable-attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/layout/preference_dialog_confirm.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/events/MediaStoreUpdateEvent.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.events; 2 | 3 | /** 4 | * Posted by the MediaScannerFinishedReceiver when the media scanner finishes scanning. 5 | */ 6 | public class MediaStoreUpdateEvent { 7 | } 8 | -------------------------------------------------------------------------------- /app/lint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Oct 11 10:36:52 CEST 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-7.4-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/ui/MainUiComponent.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.ui; 2 | 3 | import androidx.appcompat.app.AppCompatActivity; 4 | 5 | public interface MainUiComponent { 6 | AppCompatActivity activity(); 7 | void inject(MainActivity mainActivity); 8 | } 9 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | engines: 3 | fixme: 4 | enabled: true 5 | pmd: 6 | enabled: true 7 | channel: "beta" 8 | ratings: 9 | paths: 10 | - "**.java" 11 | exclude_paths: 12 | - spec/**/* 13 | - "**/vendor/**/*" 14 | - app/src/main/java/sonic/** 15 | - app/src/main/java/com/github/** 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/ui/BookListUi.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.ui; 2 | 3 | import com.studio4plus.homerplayer.model.AudioBook; 4 | 5 | import java.util.List; 6 | 7 | public interface BookListUi { 8 | void initWithController(UiControllerBookList uiControllerBookList); 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/classic_volume_indicator_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/events/DeviceAdminChangeEvent.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.events; 2 | 3 | public class DeviceAdminChangeEvent { 4 | public final boolean isEnabled; 5 | 6 | public DeviceAdminChangeEvent(boolean isEnabled) { 7 | this.isEnabled = isEnabled; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/events/PlaybackStoppingEvent.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.events; 2 | 3 | /** 4 | * Sent when playback is being stopped. The operation may take a while, PlaybackStoppedEvent 5 | * will be sent when the player is stopped and released. 6 | */ 7 | public class PlaybackStoppingEvent { 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/ui/SpeakerProvider.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.ui; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.studio4plus.homerplayer.concurrency.SimpleFuture; 6 | 7 | public interface SpeakerProvider { 8 | @NonNull SimpleFuture obtainTts(); 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/high_contrast_volume_indicator_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/events/PlaybackFatalErrorEvent.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.events; 2 | 3 | import android.net.Uri; 4 | 5 | public class PlaybackFatalErrorEvent { 6 | public final Uri uri; 7 | 8 | public PlaybackFatalErrorEvent(Uri uri) { 9 | this.uri = uri; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/ApplicationScope.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | import javax.inject.Scope; 7 | 8 | @Scope 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface ApplicationScope { 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/ui/ActivityScope.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.ui; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | import javax.inject.Scope; 7 | 8 | @Scope 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface ActivityScope { 11 | } 12 | -------------------------------------------------------------------------------- /app/crashreporting-sentry/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/util/DirectoryFilter.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.util; 2 | 3 | import java.io.File; 4 | import java.io.FileFilter; 5 | 6 | public class DirectoryFilter implements FileFilter { 7 | 8 | @Override 9 | public boolean accept(File pathname) { 10 | return pathname.isDirectory(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /findbugs_excludes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/util/DebugUtil.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.util; 2 | 3 | import android.os.Looper; 4 | 5 | public class DebugUtil { 6 | 7 | public static void verifyIsOnMainThread() { 8 | if (Looper.myLooper() != Looper.getMainLooper()) 9 | throw new IllegalStateException("This code must run on the main thread."); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/battery/BatteryStatus.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.battery; 2 | 3 | public class BatteryStatus { 4 | public final ChargeLevel chargeLevel; 5 | public final boolean isCharging; 6 | 7 | public BatteryStatus(ChargeLevel chargeLevel, boolean isCharging) { 8 | this.chargeLevel = chargeLevel; 9 | this.isCharging = isCharging; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/events/BatteryStatusChangeEvent.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.events; 2 | 3 | import com.studio4plus.homerplayer.battery.BatteryStatus; 4 | 5 | public class BatteryStatusChangeEvent { 6 | public final BatteryStatus batteryStatus; 7 | 8 | 9 | public BatteryStatusChangeEvent(BatteryStatus batteryStatus) { 10 | this.batteryStatus = batteryStatus; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/battery_charging_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/battery_critical.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/integer.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 500 4 | 250 5 | 1000 6 | 500 7 | 300 8 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/studio4plus/homerplayer/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/util/VersionUtil.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.util; 2 | 3 | import com.studio4plus.homerplayer.BuildConfig; 4 | 5 | public class VersionUtil { 6 | 7 | private static final String OFFICIAL_VERSION_PATTERN = "^\\d+\\.\\d+\\.\\d+$"; 8 | 9 | public static boolean isOfficialVersion() { 10 | return !BuildConfig.DEBUG && BuildConfig.VERSION_NAME.matches(OFFICIAL_VERSION_PATTERN); 11 | } 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/concurrency/SimpleDeferred.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.concurrency; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | public class SimpleDeferred extends BaseDeferred { 6 | 7 | public void setResult(@NonNull V result) { 8 | super.setResult(result); 9 | } 10 | 11 | public void setException(@NonNull Throwable exception) { 12 | super.setException(exception); 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/events/CurrentBookChangedEvent.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.events; 2 | 3 | import com.studio4plus.homerplayer.model.AudioBook; 4 | 5 | /** 6 | * Sent when the current audio book is changed in the AudioBookManager. 7 | */ 8 | public class CurrentBookChangedEvent { 9 | public final AudioBook audioBook; 10 | 11 | public CurrentBookChangedEvent(AudioBook audioBook) { 12 | this.audioBook = audioBook; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/player/DurationQueryController.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.player; 2 | 3 | import android.net.Uri; 4 | 5 | public interface DurationQueryController { 6 | 7 | interface Observer { 8 | void onDuration(Uri uri, long durationMs); 9 | void onFinished(); 10 | void onPlayerReleased(); 11 | void onPlayerError(Uri uri); 12 | } 13 | 14 | void start(Observer observer); 15 | void stop(); 16 | } 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | android: 3 | components: 4 | # The BuildTools version used by your project 5 | - tools 6 | - platform-tools 7 | - build-tools-25.0.2 8 | 9 | # The SDK version used to compile your project 10 | - android-25 11 | 12 | # Additional components 13 | - extra-android-m2repository 14 | - extra-android-support 15 | - extra-google-google_play_services 16 | - extra-google-m2repository 17 | script: 18 | ./gradlew build check 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/model/LibraryContentType.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.model; 2 | 3 | public enum LibraryContentType { 4 | EMPTY(0), 5 | SAMPLES_ONLY(1), 6 | USER_CONTENT(2); 7 | 8 | private final int priority; 9 | 10 | LibraryContentType(int priority) { 11 | this.priority = priority; 12 | } 13 | 14 | public boolean supersedes(LibraryContentType other) { 15 | return priority > other.priority; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_audiobooks_folder_add.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/events/AudioBooksChangedEvent.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.events; 2 | 3 | import com.studio4plus.homerplayer.model.LibraryContentType; 4 | 5 | /** 6 | * Posted when audio books are added or removed. 7 | */ 8 | public class AudioBooksChangedEvent { 9 | 10 | public final LibraryContentType contentType; 11 | 12 | public AudioBooksChangedEvent(LibraryContentType contentType) { 13 | this.contentType = contentType; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/templates/provisioning-qrcode.txt: -------------------------------------------------------------------------------- 1 | { 2 | "android.app.extra.PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME":"com.studio4plus.homerplayer/com.studio4plus.homerplayer.deviceadmin.HomerPlayerDeviceAdmin", 3 | "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION":"https://github.com/msimonides/homerplayer/releases/download/${version}/Homer.Player.${version}.apk", 4 | "android.app.extra.PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM":"${apkChecksum}", 5 | "android.app.extra.PROVISIONING_SKIP_ENCRYPTION":true 6 | } 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/ui/ColorTheme.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.ui; 2 | 3 | import androidx.annotation.StyleRes; 4 | 5 | import com.studio4plus.homerplayer.R; 6 | 7 | public enum ColorTheme { 8 | CLASSIC_REGULAR(R.style.AppThemeClassic), 9 | CLASSIC_HIGH_CONTRAST(R.style.AppThemeHighContrast); 10 | 11 | @StyleRes 12 | public final int styleId; 13 | 14 | ColorTheme(@StyleRes int styleId) { 15 | this.styleId = styleId; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/events/DemoSamplesInstallationFinishedEvent.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.events; 2 | 3 | import androidx.annotation.Nullable; 4 | 5 | public class DemoSamplesInstallationFinishedEvent { 6 | 7 | public final boolean success; 8 | public final @Nullable String errorMessage; 9 | 10 | public DemoSamplesInstallationFinishedEvent(boolean success, @Nullable String errorMessage) { 11 | this.success = success; 12 | this.errorMessage = errorMessage; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/service/AudioBookPlayerModule.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.service; 2 | 3 | import android.content.Context; 4 | 5 | import com.studio4plus.homerplayer.player.Player; 6 | 7 | import dagger.Module; 8 | import dagger.Provides; 9 | import de.greenrobot.event.EventBus; 10 | 11 | @Module 12 | public class AudioBookPlayerModule { 13 | @Provides 14 | Player provideAudioBookPlayer(Context context, EventBus eventBus) { 15 | return new Player(context, eventBus); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/crashreporting-sentry/src/test/java/com/studio4plus/homerplayer/crashreporting/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.crashreporting; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/battery_charging_1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_init.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/ui/ActivityComponent.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.ui; 2 | 3 | import androidx.appcompat.app.AppCompatActivity; 4 | 5 | import com.studio4plus.homerplayer.ApplicationComponent; 6 | import com.studio4plus.homerplayer.ui.settings.SettingsActivity; 7 | 8 | import dagger.Component; 9 | 10 | @ActivityScope 11 | @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) 12 | public interface ActivityComponent { 13 | AppCompatActivity activity(); 14 | void inject(SettingsActivity settingsActivity); 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/ui/NoBooksUi.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.ui; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | public interface NoBooksUi { 6 | 7 | interface InstallProgressObserver { 8 | void onDownloadProgress(int transferredBytes, int totalBytes); 9 | void onInstallStarted(); 10 | void onFailure(); 11 | } 12 | 13 | void initWithController(@NonNull UiControllerNoBooks controller); 14 | @NonNull InstallProgressObserver showInstallProgress(boolean isAlreadyInstalling); 15 | void shutdown(); 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/concurrency/SimpleFuture.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.concurrency; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | /** 6 | * A very simple future that has listeners for notifying when the result is available. 7 | */ 8 | public interface SimpleFuture { 9 | 10 | interface Listener { 11 | void onResult(@NonNull V result); 12 | void onException(@NonNull Throwable t); 13 | } 14 | 15 | void addListener(@NonNull Listener listener); 16 | void removeListener(@NonNull Listener listener); 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/ui/MainUi.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.ui; 2 | 3 | import android.net.Uri; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | import java.io.File; 8 | 9 | /** 10 | * The main UI part that handles switching between the main states: no books, list of books, 11 | * book playback. 12 | */ 13 | public interface MainUi { 14 | 15 | @NonNull BookListUi switchToBookList(boolean animate); 16 | @NonNull NoBooksUi switchToNoBooks(boolean animate); 17 | @NonNull PlaybackUi switchToPlayback(boolean animate); 18 | void onPlaybackError(Uri uri); 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/ui/settings/UiSettingsFragment.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.ui.settings; 2 | 3 | import android.os.Bundle; 4 | 5 | import com.studio4plus.homerplayer.R; 6 | 7 | public class UiSettingsFragment extends BaseSettingsFragment { 8 | 9 | @Override 10 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 11 | setPreferencesFromResource(R.xml.preferences_ui, rootKey); 12 | } 13 | 14 | @Override 15 | protected int getTitle() { 16 | return R.string.pref_ui_options_screen_title; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/util/OrFilter.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.util; 2 | 3 | import java.io.File; 4 | import java.io.FileFilter; 5 | 6 | public class OrFilter implements FileFilter { 7 | 8 | private final FileFilter[] filters; 9 | 10 | public OrFilter(FileFilter... filters) { 11 | this.filters = filters; 12 | } 13 | 14 | @Override 15 | public boolean accept(File pathname) { 16 | for (FileFilter filter : filters) { 17 | if (filter.accept(pathname)) 18 | return true; 19 | } 20 | return false; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/ui/ActivityModule.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.ui; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.appcompat.app.AppCompatActivity; 5 | 6 | import dagger.Module; 7 | import dagger.Provides; 8 | 9 | @Module 10 | public class ActivityModule { 11 | private final AppCompatActivity activity; 12 | 13 | public ActivityModule(@NonNull AppCompatActivity activity) { 14 | this.activity = activity; 15 | } 16 | 17 | @Provides @ActivityScope 18 | AppCompatActivity activity() { 19 | return activity; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/ui/SimpleAnimatorListener.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.ui; 2 | 3 | import android.animation.Animator; 4 | 5 | public class SimpleAnimatorListener implements Animator.AnimatorListener { 6 | @Override 7 | public void onAnimationStart(Animator animator) { 8 | } 9 | 10 | @Override 11 | public void onAnimationEnd(Animator animator) { 12 | } 13 | 14 | @Override 15 | public void onAnimationCancel(Animator animator) { 16 | } 17 | 18 | @Override 19 | public void onAnimationRepeat(Animator animator) { 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/deviceadmin/AdminPolicyComplianceActivity.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.deviceadmin; 2 | 3 | import android.os.Bundle; 4 | 5 | import androidx.annotation.Nullable; 6 | import androidx.annotation.RequiresApi; 7 | import androidx.appcompat.app.AppCompatActivity; 8 | 9 | @RequiresApi(29) 10 | public class AdminPolicyComplianceActivity extends AppCompatActivity { 11 | 12 | @Override 13 | protected void onCreate(@Nullable Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | 16 | setResult(RESULT_OK); 17 | finish(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/ui/classic/ClassicMainUiComponent.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.ui.classic; 2 | 3 | import com.studio4plus.homerplayer.ApplicationComponent; 4 | import com.studio4plus.homerplayer.ui.ActivityModule; 5 | import com.studio4plus.homerplayer.ui.MainUiComponent; 6 | import com.studio4plus.homerplayer.ui.ActivityScope; 7 | 8 | import dagger.Component; 9 | 10 | @ActivityScope 11 | @Component(dependencies = ApplicationComponent.class, 12 | modules ={ActivityModule.class, ClassicMainUiModule.class}) 13 | public interface ClassicMainUiComponent extends MainUiComponent { 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/player/ExoLogger.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.player; 2 | 3 | import androidx.annotation.Nullable; 4 | 5 | import com.google.android.exoplayer2.trackselection.MappingTrackSelector; 6 | import com.google.android.exoplayer2.util.EventLogger; 7 | 8 | import timber.log.Timber; 9 | 10 | public class ExoLogger extends EventLogger { 11 | public ExoLogger() { 12 | super(); 13 | } 14 | 15 | @Override 16 | protected void logd(String msg) { 17 | Timber.d(msg); 18 | } 19 | 20 | @Override 21 | protected void loge(String msg) { 22 | Timber.e(msg); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/res/values-xlarge/dimen.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 70sp 4 | 180sp 5 | 20dp 6 | 50dp 7 | 40sp 8 | 60sp 9 | 100dp 10 | 140dp 11 | 80dp 12 | 25dp 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/events/KioskModeChanged.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.events; 2 | 3 | import android.app.Activity; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | public class KioskModeChanged { 8 | public enum Type { FULL, SIMPLE } 9 | 10 | public final Activity activity; // TODO: remove Activity from this class. 11 | public final Type type; 12 | public final boolean isEnabled; 13 | 14 | public KioskModeChanged(@NonNull Activity activity, Type type, boolean isEnabled) { 15 | this.activity = activity; 16 | this.type = type; 17 | this.isEnabled = isEnabled; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/battery_charging_0.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values-zh-rCN/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 正常 5 | 高对比度 6 | 7 | 8 | 随设备转动自动调整横屏方向 9 | 锁定当前横屏方向 10 | 将横屏方向旋转180度并锁定 11 | 12 | 13 | 非常快 14 | 15 | 正常 16 | 较慢 17 | 18 | 非常慢 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/ui/settings/OnFolderSelected.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.ui.settings; 2 | 3 | import android.net.Uri; 4 | 5 | import androidx.annotation.NonNull; 6 | import androidx.annotation.Nullable; 7 | 8 | import javax.inject.Inject; 9 | 10 | public class OnFolderSelected { 11 | 12 | private final AudiobooksFolderManager folderManager; 13 | 14 | @Inject 15 | public OnFolderSelected(@NonNull AudiobooksFolderManager folderManager) { 16 | this.folderManager = folderManager; 17 | } 18 | 19 | public void onSelected(@Nullable Uri uri) { 20 | if (uri != null) folderManager.addAudiobooksFolder(uri.toString()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/events/PlaybackErrorEvent.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.events; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | public class PlaybackErrorEvent { 6 | @NonNull 7 | public final String errorMessage; 8 | @NonNull 9 | public final String format; 10 | public final long durationMs; 11 | public final long positionMs; 12 | 13 | public PlaybackErrorEvent(@NonNull String errorMessage, long durationMs, long positionMs, @NonNull String format) { 14 | this.errorMessage = errorMessage; 15 | this.durationMs = durationMs; 16 | this.positionMs = positionMs; 17 | this.format = format; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/logging/LoggingSetup.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.logging; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | import com.michaelflisar.lumberjack.FileLoggingSetup; 8 | 9 | public class LoggingSetup { 10 | 11 | @NonNull 12 | public static FileLoggingSetup createLoggingSetup(@NonNull Context applicationContext) { 13 | return new FileLoggingSetup.NumberedFiles( 14 | applicationContext.getFileStreamPath("").getAbsolutePath(), 15 | true, 16 | "500KB", 17 | new FileLoggingSetup.Setup(2, "%d %marker%-5level %msg%n", "log", "log")); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/values-large/dimen.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 50sp 4 | 120sp 5 | 15dp 6 | 40dp 7 | 30sp 8 | 40sp 9 | 100dp 10 | 140dp 11 | 60dp 12 | 10dp 13 | 2dp 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/util/LifecycleAwareRunnable.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.util; 2 | 3 | import android.os.Handler; 4 | 5 | import androidx.annotation.NonNull; 6 | import androidx.lifecycle.DefaultLifecycleObserver; 7 | import androidx.lifecycle.LifecycleOwner; 8 | 9 | public abstract class LifecycleAwareRunnable implements DefaultLifecycleObserver, Runnable { 10 | 11 | @NonNull 12 | protected final Handler handler; 13 | 14 | public LifecycleAwareRunnable(@NonNull Handler handler) { 15 | this.handler = handler; 16 | } 17 | 18 | @Override 19 | public void onStop(@NonNull LifecycleOwner owner) { 20 | handler.removeCallbacks(this); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/res/values-fr/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Standard 5 | Fort contraste 6 | 7 | 8 | Paysage, auto 9 | Paysage vérouillé 10 | Paysage inversé vérouillé 11 | 12 | 13 | Très rapide 14 | Rapide 15 | Normale 16 | Modérée 17 | Lente 18 | Très lente 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/values-pl/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Standardowy 5 | Wysoki kontrast 6 | 7 | 8 | Pozioma, automatyczne obracanie 9 | Zablokowana poziomo 10 | Zablokowana poziomo, odwrotnie 11 | 12 | 13 | Bardzo duża 14 | Duża 15 | Zwykła 16 | Umiarkowana 17 | Mała 18 | Bardzo mała 19 | 20 | -------------------------------------------------------------------------------- /app/analytics-noop/src/main/java/com/studio4plus/homerplayer/analytics/StatsLogger.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.analytics; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | 6 | import androidx.annotation.NonNull; 7 | 8 | import java.util.Map; 9 | 10 | public class StatsLogger { 11 | 12 | private final static String TAG = "StatsLogger"; 13 | 14 | public StatsLogger(@NonNull Context ignored) { 15 | } 16 | 17 | public void logEvent(@NonNull String eventName) { 18 | Log.i(TAG, "Event: " + eventName); 19 | } 20 | 21 | public void logEvent(@NonNull String eventName, @NonNull Map eventData) { 22 | Log.i(TAG, "Event: " + eventName + " params: " + eventData); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/events/PlaybackProgressedEvent.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.events; 2 | 3 | import com.studio4plus.homerplayer.model.AudioBook; 4 | 5 | /** 6 | * Sent to sync elapsed time, right after playback has started and then some time after each full 7 | * second of playback (in playback time, i.e. affected by playback speed). 8 | * Contains the audiobook and the current playback position. 9 | */ 10 | public class PlaybackProgressedEvent { 11 | public final AudioBook audioBook; 12 | public final long playbackPositionMs; 13 | 14 | public PlaybackProgressedEvent(AudioBook audioBook, long playbackPositionMs) { 15 | this.audioBook = audioBook; 16 | this.playbackPositionMs = playbackPositionMs; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/crashreporting-noop/src/main/java/com/studio4plus/homerplayer/crashreporting/CrashReporting.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.crashreporting; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.annotation.NonNull; 6 | import androidx.annotation.Nullable; 7 | 8 | import timber.log.Timber; 9 | 10 | public class CrashReporting { 11 | 12 | public static void init(@NonNull Context context) {} 13 | public static void log(@NonNull String message) {} 14 | public static void log(int priority, @NonNull String tag, @NonNull String msg) {} 15 | public static void logException(@NonNull Throwable e) { 16 | Timber.e(e); 17 | } 18 | @Nullable 19 | public static String statusForDiagnosticLog() { 20 | return null; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/settings_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/crashreporting-noop/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion rootProject.ext.compileSdkVersion 5 | 6 | defaultConfig { 7 | minSdkVersion 17 8 | targetSdkVersion rootProject.ext.targetSdkVersion 9 | 10 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 11 | consumerProguardFiles "consumer-rules.pro" 12 | } 13 | 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | implementation 'androidx.annotation:annotation:1.4.0' 24 | implementation 'com.jakewharton.timber:timber:4.7.1' 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/ui/PlaybackUi.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.ui; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | public interface PlaybackUi { 6 | 7 | enum SpeedLevel { 8 | STOP, 9 | REGULAR, 10 | FAST, 11 | FASTEST 12 | } 13 | 14 | void initWithController(@NonNull UiControllerPlayback controller); 15 | void onPlaybackProgressed(long playbackPositionMs); 16 | void onPlaybackStopping(); 17 | 18 | /** 19 | * Notify that fast-forward/rewind is taking place and at what speed level. 20 | * Must be called with SpeedLevel.STOP when ff/rewind is finished. 21 | */ 22 | void onFFRewindSpeed(SpeedLevel speedLevel); 23 | 24 | void onVolumeChanged(int min, int max, int current); 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimen.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 30sp 4 | 70sp 5 | 10dp 6 | 30dp 7 | 15sp 8 | 30sp 9 | 50dp 10 | 60dp 11 | 3dp 12 | 1dp 13 | 30dp 14 | 5dp 15 | 10dp 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/folders_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v26/ic_launcher_mono.xml: -------------------------------------------------------------------------------- 1 | 6 | 13 | 14 | -------------------------------------------------------------------------------- /app/crashreporting-sentry/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/ui/settings/ConfirmDialogFragmentCompat.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.ui.settings; 2 | 3 | import android.os.Bundle; 4 | import androidx.annotation.NonNull; 5 | import androidx.preference.PreferenceDialogFragmentCompat; 6 | 7 | public class ConfirmDialogFragmentCompat extends PreferenceDialogFragmentCompat { 8 | 9 | static ConfirmDialogFragmentCompat newInstance(@NonNull String key) { 10 | ConfirmDialogFragmentCompat fragment = new ConfirmDialogFragmentCompat(); 11 | Bundle b = new Bundle(1); 12 | b.putString("key", key); 13 | fragment.setArguments(b); 14 | return fragment; 15 | } 16 | 17 | @Override 18 | public void onDialogClosed(boolean isPositive) { 19 | ((ConfirmDialogPreference) getPreference()).onDialogClosed(isPositive); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/demosamples/DemoSamplesFolderProvider.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.demosamples; 2 | 3 | import android.content.Context; 4 | 5 | import java.io.File; 6 | 7 | import javax.inject.Inject; 8 | 9 | public class DemoSamplesFolderProvider { 10 | 11 | private static final String DEMO_FOLDER = "audiobook_samples"; 12 | 13 | private final Context context; 14 | 15 | @Inject 16 | public DemoSamplesFolderProvider(Context context) { 17 | this.context = context; 18 | } 19 | 20 | public File demoFolder() { 21 | File cacheFolder = context.getCacheDir(); 22 | File demoSamplesFolder = new File(cacheFolder, DEMO_FOLDER); 23 | if (!demoSamplesFolder.exists()) { 24 | demoSamplesFolder.mkdirs(); 25 | } 26 | return demoSamplesFolder; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/crashreporting-noop/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/analytics-noop/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/concurrency/BackgroundExecutor.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.concurrency; 2 | 3 | import android.os.Handler; 4 | import androidx.annotation.NonNull; 5 | 6 | import java.util.concurrent.Callable; 7 | 8 | public class BackgroundExecutor { 9 | 10 | private final @NonNull Handler mainThreadHandler; 11 | private final @NonNull Handler taskHandler; 12 | 13 | public BackgroundExecutor(@NonNull Handler mainThreadHandler, @NonNull Handler taskHandler) { 14 | this.mainThreadHandler = mainThreadHandler; 15 | this.taskHandler = taskHandler; 16 | } 17 | 18 | public SimpleFuture postTask(@NonNull Callable task) { 19 | BackgroundDeferred deferred = new BackgroundDeferred<>(task, mainThreadHandler); 20 | taskHandler.post(deferred); 21 | return deferred; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/util/ViewUtils.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.util; 2 | 3 | import android.graphics.Rect; 4 | import androidx.annotation.NonNull; 5 | import android.view.View; 6 | import android.view.ViewParent; 7 | 8 | public class ViewUtils { 9 | 10 | public static Rect getRelativeRect(@NonNull View ancestor, @NonNull View view) { 11 | Rect rect = new Rect(view.getLeft(), view.getTop(), view.getRight(), view.getBottom()); 12 | ViewParent parent = view.getParent(); 13 | while (parent instanceof View && parent != ancestor) { 14 | view = (View) parent; 15 | rect.left += view.getLeft(); 16 | rect.right += view.getLeft(); 17 | rect.top += view.getTop(); 18 | rect.bottom += view.getTop(); 19 | parent = view.getParent(); 20 | } 21 | return rect; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/res/xml/preferences_kiosk.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | 12 | 13 | 17 | -------------------------------------------------------------------------------- /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 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | android.enableJetifier=true 20 | android.useAndroidX=true -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/ui/classic/ClassicMainUiModule.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.ui.classic; 2 | 3 | import androidx.appcompat.app.AppCompatActivity; 4 | 5 | import com.studio4plus.homerplayer.ui.MainActivity; 6 | import com.studio4plus.homerplayer.ui.MainUi; 7 | import com.studio4plus.homerplayer.ui.ActivityScope; 8 | import com.studio4plus.homerplayer.ui.SpeakerProvider; 9 | 10 | import dagger.Module; 11 | import dagger.Provides; 12 | 13 | @Module 14 | public class ClassicMainUiModule { 15 | private final MainActivity activity; 16 | 17 | public ClassicMainUiModule(MainActivity activity) { 18 | this.activity = activity; 19 | } 20 | 21 | @Provides @ActivityScope 22 | MainUi mainUi(AppCompatActivity activity) { 23 | return new ClassicMainUi(activity); 24 | } 25 | 26 | @Provides @ActivityScope 27 | SpeakerProvider speakProvider() { 28 | return activity; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/crashreporting-sentry/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | } 4 | 5 | android { 6 | compileSdk rootProject.ext.compileSdkVersion 7 | 8 | defaultConfig { 9 | minSdk 21 10 | targetSdk rootProject.ext.targetSdkVersion 11 | 12 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 13 | consumerProguardFiles "consumer-rules.pro" 14 | } 15 | 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | compileOptions { 23 | sourceCompatibility JavaVersion.VERSION_1_8 24 | targetCompatibility JavaVersion.VERSION_1_8 25 | } 26 | } 27 | 28 | dependencies { 29 | implementation 'io.sentry:sentry-android-core:6.9.1' 30 | implementation 'io.sentry:sentry-android-timber:6.9.1' 31 | implementation 'androidx.annotation:annotation:1.5.0' 32 | } 33 | -------------------------------------------------------------------------------- /app/crashreporting-sentry/src/androidTest/java/com/studio4plus/homerplayer/crashreporting/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.crashreporting; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | assertEquals("com.studio4plus.homerplayer.crashreporting.test", appContext.getPackageName()); 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/AudioBookManagerModule.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | import com.studio4plus.homerplayer.model.Storage; 8 | 9 | import javax.inject.Named; 10 | import javax.inject.Singleton; 11 | 12 | import dagger.Module; 13 | import dagger.Provides; 14 | import de.greenrobot.event.EventBus; 15 | 16 | @Module 17 | public class AudioBookManagerModule { 18 | 19 | private final String audioBooksDirectoryName; 20 | 21 | public AudioBookManagerModule(String audioBooksDirectoryName) { 22 | this.audioBooksDirectoryName = audioBooksDirectoryName; 23 | } 24 | 25 | @Provides @Named("AUDIOBOOKS_DIRECTORY") 26 | String provideAudioBooksDirectoryName() { 27 | return this.audioBooksDirectoryName; 28 | } 29 | 30 | @Provides @Singleton 31 | Storage provideStorage(@NonNull Context context, @NonNull EventBus eventBus) { 32 | return new Storage(context, eventBus); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/ui/classic/ClassicInitUi.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.ui.classic; 2 | 3 | import android.view.View; 4 | import android.view.animation.AccelerateDecelerateInterpolator; 5 | import android.view.animation.Interpolator; 6 | 7 | import androidx.fragment.app.Fragment; 8 | 9 | import com.studio4plus.homerplayer.R; 10 | 11 | import java.util.Objects; 12 | 13 | public class ClassicInitUi extends Fragment { 14 | 15 | public ClassicInitUi() { 16 | super(R.layout.fragment_init); 17 | } 18 | 19 | @Override 20 | public void onStart() { 21 | super.onStart(); 22 | View logo = requireView().findViewById(R.id.logo); 23 | logo.setAlpha(0f); 24 | logo.setScaleX(0.8f); 25 | logo.setScaleY(0.8f); 26 | logo.animate() 27 | .alpha(1f) 28 | .scaleX(1f) 29 | .scaleY(1f) 30 | .setDuration(1500) 31 | .setInterpolator(new AccelerateDecelerateInterpolator()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/analytics-noop/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion rootProject.ext.compileSdkVersion 5 | 6 | 7 | defaultConfig { 8 | minSdkVersion 17 9 | targetSdkVersion rootProject.ext.targetSdkVersion 10 | 11 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 12 | consumerProguardFiles 'consumer-rules.pro' 13 | } 14 | 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | 22 | } 23 | 24 | dependencies { 25 | implementation fileTree(dir: 'libs', include: ['*.jar']) 26 | 27 | implementation "androidx.appcompat:appcompat:${rootProject.ext.version_appcompat}" 28 | testImplementation 'junit:junit:4.12' 29 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 30 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 7 | 9 | 14 | 15 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_book_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | 12 | 18 | 19 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/filescanner/FileSet.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.filescanner; 2 | 3 | import android.net.Uri; 4 | import androidx.annotation.NonNull; 5 | 6 | public class FileSet { 7 | 8 | public final String id; 9 | public final String name; 10 | public final Uri[] uris; 11 | public final boolean isDemoSample; 12 | 13 | public FileSet(@NonNull String id, @NonNull String name, @NonNull Uri[] uris, boolean isDemoSample) { 14 | this.id = id; 15 | this.name = name; 16 | this.uris = uris; 17 | this.isDemoSample = isDemoSample; 18 | } 19 | 20 | /** 21 | * Compares only the id field. 22 | */ 23 | @Override 24 | public boolean equals(Object o) { 25 | if (this == o) return true; 26 | if (o == null || getClass() != o.getClass()) return false; 27 | 28 | FileSet fileSet = (FileSet) o; 29 | 30 | return id.equals(fileSet.id); 31 | } 32 | 33 | @Override 34 | public int hashCode() { 35 | return id.hashCode(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/full_description.txt: -------------------------------------------------------------------------------- 1 |

Homer Player is an audiobook player for the elderly. Its simple user interface makes it easy to operate for seniors and people with poor vision (or both). It features simplicity so far you can even use it in "single application mode" (kiosk), to make the Android device a dedicated Audio Book Reader the user cannot "accidentally close and get lost on".


Features:

  • simplicity: just a list of audiobooks and a "start" button,
  • flip-to-stop: there's no need to press any buttons to pause playback, just put the device face down on a table,
  • low-vision friendly interface: book titles are read aloud and high contrast, large UI elements are used,
  • adjust speed: slow down playback for those hard of hearing.


Single application mode (kiosk):

With this mode enabled the user cannot exit the application so they don't need to know how to use a tablet.

Perfect for building a dedicated audiobook player for your grandparents.


Let me know if you need help, like the app or hate it.

-------------------------------------------------------------------------------- /app/src/main/res/xml/preferences_playback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 15 | 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/logging/UncaughtExceptionLogger.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.logging; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.annotation.Nullable; 5 | 6 | import timber.log.Timber; 7 | 8 | public class UncaughtExceptionLogger implements Thread.UncaughtExceptionHandler { 9 | 10 | @Nullable 11 | private final Thread.UncaughtExceptionHandler previousHandler; 12 | 13 | public UncaughtExceptionLogger(@Nullable Thread.UncaughtExceptionHandler previousHandler) { 14 | this.previousHandler = previousHandler; 15 | } 16 | 17 | public static void install() { 18 | UncaughtExceptionLogger handler = 19 | new UncaughtExceptionLogger(Thread.getDefaultUncaughtExceptionHandler()); 20 | Thread.setDefaultUncaughtExceptionHandler(handler); 21 | } 22 | 23 | @Override 24 | public void uncaughtException(@NonNull Thread thread, @NonNull Throwable throwable) { 25 | Timber.e(throwable, "Crash!\n"); 26 | if (previousHandler != null) previousHandler.uncaughtException(thread, throwable); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_audiobooks_folder.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 19 | 20 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/ui/HomeActivity.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.ui; 2 | 3 | import android.app.Activity; 4 | import android.content.ComponentName; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.content.pm.PackageManager; 8 | import android.os.Bundle; 9 | 10 | public class HomeActivity extends Activity { 11 | 12 | @Override 13 | protected void onCreate(Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | 16 | startActivity(new Intent(this, MainActivity.class)); 17 | } 18 | 19 | public static void setEnabled(Context context, boolean isEnabled) { 20 | ComponentName componentName = new ComponentName(context, HomeActivity.class); 21 | int enabledState = isEnabled 22 | ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED 23 | : PackageManager.COMPONENT_ENABLED_STATE_DISABLED; 24 | context.getPackageManager().setComponentEnabledSetting( 25 | componentName, enabledState, PackageManager.DONT_KILL_APP); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Marcin Simonides 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/deviceadmin/GetProvisioningModeActivity.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.deviceadmin; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | 6 | import androidx.annotation.Nullable; 7 | import androidx.annotation.RequiresApi; 8 | import androidx.appcompat.app.AppCompatActivity; 9 | 10 | @RequiresApi(29) 11 | public class GetProvisioningModeActivity extends AppCompatActivity { 12 | 13 | @Override 14 | protected void onCreate(@Nullable Bundle savedInstanceState) { 15 | super.onCreate(savedInstanceState); 16 | 17 | finishWithDeviceOwnerIntent(); 18 | } 19 | 20 | private void finishWithDeviceOwnerIntent() { 21 | Intent intent = new Intent(); 22 | intent.putExtra( 23 | android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_MODE, 24 | android.app.admin.DevicePolicyManager.PROVISIONING_MODE_FULLY_MANAGED_DEVICE 25 | ); 26 | intent.putExtra( 27 | android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_SKIP_EDUCATION_SCREENS, 28 | true 29 | ); 30 | setResult(RESULT_OK, intent); 31 | finish(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/SamplesMap.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import android.net.Uri; 7 | 8 | import javax.inject.Inject; 9 | 10 | public class SamplesMap { 11 | private static final String DEFAULT_LOCALE = "en"; 12 | private static final String EN_SAMPLES_URL = "https://homer-player.firebaseapp.com/samples.zip"; 13 | private static final String FR_SAMPLES_URL = "https://homer-player.firebaseapp.com/samples-fr.zip"; 14 | 15 | private Map map; 16 | 17 | @Inject 18 | public SamplesMap(){ 19 | this.map = new HashMap(); 20 | // Language codes must match those returned by Locale.getLanguage(), which may not be the ones in the newest ISO 639 standard. 21 | this.map.put("en", EN_SAMPLES_URL); 22 | this.map.put("fr", FR_SAMPLES_URL); 23 | } 24 | 25 | public Uri getSamples(String language){ 26 | String url; 27 | if(this.map.containsKey(language)){ 28 | url = this.map.get(language); 29 | } else { 30 | url = this.map.get(DEFAULT_LOCALE); 31 | } 32 | return Uri.parse(url); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/filescanner/LegacyFolderProvider.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.filescanner; 2 | 3 | import android.content.Context; 4 | import android.os.Environment; 5 | 6 | import androidx.annotation.NonNull; 7 | 8 | import com.studio4plus.homerplayer.util.CollectionUtils; 9 | import com.studio4plus.homerplayer.util.FilesystemUtil; 10 | 11 | import java.io.File; 12 | import java.util.List; 13 | 14 | public class LegacyFolderProvider implements ScanFilesTask.FolderProvider { 15 | 16 | private static final String AUDIOBOOKS_FOLDER_NAME = "AudioBooks"; 17 | 18 | private final Context appContext; 19 | 20 | public LegacyFolderProvider(@NonNull Context appContext) { 21 | this.appContext = appContext; 22 | } 23 | 24 | @Override 25 | @NonNull 26 | public List getFolders() { 27 | List rootDirs = FilesystemUtil.listRootDirs(appContext); 28 | File defaultStorage = Environment.getExternalStorageDirectory(); 29 | if (!CollectionUtils.containsByValue(rootDirs, defaultStorage)) 30 | rootDirs.add(defaultStorage); 31 | 32 | return CollectionUtils.map(rootDirs, (rootDir) -> new File(rootDir, AUDIOBOOKS_FOLDER_NAME)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/ui/classic/BookListChildFragment.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.ui.classic; 2 | 3 | import android.animation.Animator; 4 | import android.animation.ValueAnimator; 5 | import androidx.fragment.app.Fragment; 6 | 7 | import com.studio4plus.homerplayer.R; 8 | 9 | /** 10 | * A class implementing a workaround for https://code.google.com/p/android/issues/detail?id=55228 11 | * 12 | * Inspired by http://stackoverflow.com/a/23276145/3892517 13 | */ 14 | public class BookListChildFragment extends Fragment { 15 | 16 | @Override 17 | public Animator onCreateAnimator(int transit, boolean enter, int nextAnim) { 18 | final Fragment parent = getParentFragment(); 19 | 20 | // Apply the workaround only if this is a child fragment, and the parent 21 | // is being removed. 22 | if (!enter && parent != null && parent.isRemoving()) { 23 | ValueAnimator nullAnimation = new ValueAnimator(); 24 | nullAnimation.setIntValues(1, 1); 25 | nullAnimation.setDuration(R.integer.flip_animation_time_half_ms); 26 | return nullAnimation; 27 | } else { 28 | return super.onCreateAnimator(transit, enter, nextAnim); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/ui/PressReleaseDetector.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.ui; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import android.annotation.SuppressLint; 6 | import android.view.MotionEvent; 7 | import android.view.View; 8 | 9 | public class PressReleaseDetector implements View.OnTouchListener { 10 | 11 | public interface Listener { 12 | void onPressed(View v, float x, float y); 13 | void onReleased(View v, float x, float y); 14 | } 15 | 16 | private final Listener listener; 17 | private boolean isPressed; 18 | 19 | public PressReleaseDetector(@NonNull Listener listener) { 20 | this.listener = listener; 21 | } 22 | 23 | @SuppressLint("ClickableViewAccessibility") 24 | @Override 25 | public boolean onTouch(View v, MotionEvent event) { 26 | if (event.getAction() == MotionEvent.ACTION_DOWN) { 27 | isPressed = true; 28 | listener.onPressed(v, event.getX(), event.getY()); 29 | return true; 30 | } else if (isPressed && event.getAction() == MotionEvent.ACTION_UP) { 31 | listener.onReleased(v, event.getX(), event.getY()); 32 | return true; 33 | } 34 | return false; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/player/PlaybackController.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.player; 2 | 3 | import android.net.Uri; 4 | 5 | public interface PlaybackController { 6 | 7 | interface Observer { 8 | void onDuration(Uri uri, long durationMs); 9 | 10 | /** 11 | * Playback position progressed. Called more or less once per second of playback in media 12 | * time (i.e. affected by the playback speed). 13 | */ 14 | void onPlaybackProgressed(long currentPositionMs); 15 | 16 | /** 17 | * Playback ended because it reached the end of track 18 | */ 19 | void onPlaybackEnded(); 20 | 21 | /** 22 | * Playback stopped on request. 23 | */ 24 | void onPlaybackStopped(long currentPositionMs); 25 | 26 | /** 27 | * Error playing file. 28 | */ 29 | void onPlaybackError(Uri uri); 30 | 31 | /** 32 | * The player has been released. 33 | */ 34 | void onPlayerReleased(); 35 | } 36 | 37 | void setObserver(Observer observer); 38 | void start(Uri uri, long positionPosition); 39 | void pause(); 40 | void stop(); 41 | void release(); 42 | long getCurrentPosition(); 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/res/layout/main_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 14 | 21 | 22 | 23 | 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/concurrency/BackgroundDeferred.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.concurrency; 2 | 3 | import android.os.Handler; 4 | import androidx.annotation.NonNull; 5 | 6 | import java.util.concurrent.Callable; 7 | 8 | public class BackgroundDeferred extends BaseDeferred implements Runnable { 9 | 10 | private final @NonNull Callable task; 11 | private final @NonNull Handler mainThreadHandler; 12 | 13 | BackgroundDeferred(@NonNull Callable task, @NonNull Handler mainThreadHandler) { 14 | this.task = task; 15 | this.mainThreadHandler = mainThreadHandler; 16 | } 17 | 18 | @Override 19 | public void run() { 20 | try { 21 | final @NonNull V newResult = task.call(); 22 | mainThreadHandler.post(new Runnable() { 23 | @Override 24 | public void run() { 25 | setResult(newResult); 26 | } 27 | }); 28 | } catch (final Exception e) { 29 | mainThreadHandler.post(new Runnable() { 30 | @Override 31 | public void run() { 32 | setException(e); 33 | } 34 | }); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/studio4plus/homerplayer/util/MediaScannerUtil.java: -------------------------------------------------------------------------------- 1 | package com.studio4plus.homerplayer.util; 2 | 3 | import android.content.Context; 4 | import android.media.MediaScannerConnection; 5 | import android.net.Uri; 6 | 7 | import java.io.File; 8 | 9 | public class MediaScannerUtil { 10 | 11 | public static void scanAndDeleteFile(final Context context, final File file) { 12 | MediaScannerConnection.OnScanCompletedListener listener = 13 | new MediaScannerConnection.OnScanCompletedListener() { 14 | @Override 15 | public void onScanCompleted(String path, Uri uri) { 16 | if (path == null) 17 | return; 18 | 19 | File scannedFile = new File(path); 20 | if (scannedFile.equals(file)) { 21 | //noinspection ResultOfMethodCallIgnored 22 | file.delete(); 23 | MediaScannerConnection.scanFile(context, new String[]{file.getAbsolutePath()}, null, null); 24 | } 25 | } 26 | }; 27 | 28 | MediaScannerConnection.scanFile(context, new String[] { file.getAbsolutePath() }, null, listener); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/gitHub/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/xml/preferences_ui.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 19 | 20 | 26 | 27 | 33 | -------------------------------------------------------------------------------- /app/src/main/res/values/button_styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 17 | 18 | 23 | 24 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_book_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 27 | 28 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_no_books.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 |