├── .gitmodules
├── app
├── .gitignore
├── src
│ └── main
│ │ ├── ic_launcher-web.png
│ │ ├── res
│ │ ├── mipmap-hdpi
│ │ │ └── ic_launcher.png
│ │ ├── mipmap-mdpi
│ │ │ └── ic_launcher.png
│ │ ├── mipmap-xhdpi
│ │ │ └── ic_launcher.png
│ │ ├── mipmap-xxhdpi
│ │ │ └── ic_launcher.png
│ │ ├── mipmap-xxxhdpi
│ │ │ └── ic_launcher.png
│ │ ├── drawable-hdpi
│ │ │ ├── ic_notification.png
│ │ │ ├── ic_clear_white_24dp.png
│ │ │ ├── ic_star_black_24dp.png
│ │ │ ├── ic_star_white_24dp.png
│ │ │ ├── ic_history_black_18dp.png
│ │ │ ├── ic_history_white_24dp.png
│ │ │ ├── ic_search_white_24dp.png
│ │ │ ├── ic_settings_white_24dp.png
│ │ │ ├── ic_visibility_black_18dp.png
│ │ │ ├── ic_visibility_white_24dp.png
│ │ │ └── ic_star_border_black_24dp.png
│ │ ├── drawable-mdpi
│ │ │ ├── ic_notification.png
│ │ │ ├── ic_clear_white_24dp.png
│ │ │ ├── ic_star_black_24dp.png
│ │ │ ├── ic_star_white_24dp.png
│ │ │ ├── ic_history_black_18dp.png
│ │ │ ├── ic_history_white_24dp.png
│ │ │ ├── ic_search_white_24dp.png
│ │ │ ├── ic_settings_white_24dp.png
│ │ │ ├── ic_visibility_black_18dp.png
│ │ │ ├── ic_visibility_white_24dp.png
│ │ │ └── ic_star_border_black_24dp.png
│ │ ├── drawable-xhdpi
│ │ │ ├── ic_notification.png
│ │ │ ├── ic_star_black_24dp.png
│ │ │ ├── ic_star_white_24dp.png
│ │ │ ├── ic_clear_white_24dp.png
│ │ │ ├── ic_search_white_24dp.png
│ │ │ ├── ic_history_black_18dp.png
│ │ │ ├── ic_history_white_24dp.png
│ │ │ ├── ic_settings_white_24dp.png
│ │ │ ├── ic_star_border_black_24dp.png
│ │ │ ├── ic_visibility_black_18dp.png
│ │ │ └── ic_visibility_white_24dp.png
│ │ ├── drawable-xxhdpi
│ │ │ ├── ic_notification.png
│ │ │ ├── ic_clear_white_24dp.png
│ │ │ ├── ic_star_black_24dp.png
│ │ │ ├── ic_star_white_24dp.png
│ │ │ ├── ic_history_black_18dp.png
│ │ │ ├── ic_history_white_24dp.png
│ │ │ ├── ic_search_white_24dp.png
│ │ │ ├── ic_settings_white_24dp.png
│ │ │ ├── ic_visibility_black_18dp.png
│ │ │ ├── ic_visibility_white_24dp.png
│ │ │ └── ic_star_border_black_24dp.png
│ │ ├── drawable-xxxhdpi
│ │ │ ├── ic_star_black_24dp.png
│ │ │ ├── ic_star_white_24dp.png
│ │ │ ├── ic_clear_white_24dp.png
│ │ │ ├── ic_history_black_18dp.png
│ │ │ ├── ic_history_white_24dp.png
│ │ │ ├── ic_search_white_24dp.png
│ │ │ ├── ic_settings_white_24dp.png
│ │ │ ├── ic_star_border_black_24dp.png
│ │ │ ├── ic_visibility_black_18dp.png
│ │ │ └── ic_visibility_white_24dp.png
│ │ ├── values
│ │ │ ├── strings.xml
│ │ │ ├── dimens.xml
│ │ │ └── styles.xml
│ │ ├── drawable
│ │ │ ├── ic_history_black_18dp_tint.xml
│ │ │ ├── ic_visibility_black_18dp_tint.xml
│ │ │ ├── book_starred_selector.xml
│ │ │ ├── ic_baseline_save_alt_24.xml
│ │ │ ├── baseline_favorite_18.xml
│ │ │ ├── baseline_favorite_24.xml
│ │ │ ├── ic_baseline_settings_backup_restore_24.xml
│ │ │ └── ic_baseline_duration_18.xml
│ │ ├── values-w820dp
│ │ │ └── dimens.xml
│ │ ├── drawable-anydpi-v24
│ │ │ └── ic_notification.xml
│ │ ├── layout
│ │ │ ├── activity_play_audiobook.xml
│ │ │ ├── database_management_fragment.xml
│ │ │ ├── activity_books.xml
│ │ │ ├── item_book.xml
│ │ │ ├── activity_indexing.xml
│ │ │ └── exo_styled_player_control_view.xml
│ │ ├── menu
│ │ │ └── list_menu.xml
│ │ └── layout-sw600dp
│ │ │ └── item_book.xml
│ │ ├── java
│ │ └── net
│ │ │ └── bloople
│ │ │ └── audiobooks
│ │ │ ├── IndexingMetrics.kt
│ │ │ ├── PlayerState.kt
│ │ │ ├── Indexable.kt
│ │ │ ├── AudiobooksApplication.kt
│ │ │ ├── Util.kt
│ │ │ ├── FileParser.kt
│ │ │ ├── IndexViewModel.kt
│ │ │ ├── DescriptionAdapter.kt
│ │ │ ├── BooksSearcher.kt
│ │ │ ├── DatabaseHelper.kt
│ │ │ ├── IndexingTask.kt
│ │ │ ├── DatabaseManagementFragment.kt
│ │ │ ├── PlayAudiobookActivity.kt
│ │ │ ├── BooksAdapter.kt
│ │ │ ├── Book.kt
│ │ │ ├── IndexingActivity.kt
│ │ │ ├── CursorRecyclerAdapter.kt
│ │ │ ├── BooksActivity.kt
│ │ │ └── PlayerService.kt
│ │ └── AndroidManifest.xml
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── gradlew
├── .gitignore
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── LICENSE
├── gradlew.bat
└── LICENSE-APACHE-2.0
/.gitmodules:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/gradlew
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea
5 | .DS_Store
6 | /build
7 | /captures
8 | /app/release/
9 |
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/ic_launcher-web.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-hdpi/ic_notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-mdpi/ic_notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xhdpi/ic_notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_clear_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-hdpi/ic_clear_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_star_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-hdpi/ic_star_black_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_star_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-hdpi/ic_star_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_clear_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-mdpi/ic_clear_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_star_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-mdpi/ic_star_black_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_star_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-mdpi/ic_star_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_star_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xhdpi/ic_star_black_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_star_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xhdpi/ic_star_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_notification.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxhdpi/ic_notification.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_history_black_18dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-hdpi/ic_history_black_18dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_history_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-hdpi/ic_history_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_search_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-hdpi/ic_search_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_history_black_18dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-mdpi/ic_history_black_18dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_history_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-mdpi/ic_history_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_search_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-mdpi/ic_search_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_clear_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xhdpi/ic_clear_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_search_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xhdpi/ic_search_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_clear_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxhdpi/ic_clear_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_star_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxhdpi/ic_star_black_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_star_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxhdpi/ic_star_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_star_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxxhdpi/ic_star_black_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_star_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxxhdpi/ic_star_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_settings_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-hdpi/ic_settings_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_visibility_black_18dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-hdpi/ic_visibility_black_18dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_visibility_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-hdpi/ic_visibility_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_settings_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-mdpi/ic_settings_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_visibility_black_18dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-mdpi/ic_visibility_black_18dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_visibility_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-mdpi/ic_visibility_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_history_black_18dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xhdpi/ic_history_black_18dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_history_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xhdpi/ic_history_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_settings_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xhdpi/ic_settings_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_history_black_18dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxhdpi/ic_history_black_18dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_history_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxhdpi/ic_history_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_search_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxhdpi/ic_search_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_settings_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxhdpi/ic_settings_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_clear_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxxhdpi/ic_clear_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_history_black_18dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxxhdpi/ic_history_black_18dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_history_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxxhdpi/ic_history_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_search_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxxhdpi/ic_search_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_star_border_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-hdpi/ic_star_border_black_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_star_border_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-mdpi/ic_star_border_black_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_star_border_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xhdpi/ic_star_border_black_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_visibility_black_18dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xhdpi/ic_visibility_black_18dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_visibility_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xhdpi/ic_visibility_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_visibility_black_18dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxhdpi/ic_visibility_black_18dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_visibility_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxhdpi/ic_visibility_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_settings_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxxhdpi/ic_settings_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_star_border_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxhdpi/ic_star_border_black_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_star_border_black_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxxhdpi/ic_star_border_black_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_visibility_black_18dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxxhdpi/ic_visibility_black_18dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_visibility_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxxhdpi/ic_visibility_white_24dp.png
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Audiobooks
3 | Radio Streaming
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/java/net/bloople/audiobooks/IndexingMetrics.kt:
--------------------------------------------------------------------------------
1 | package net.bloople.audiobooks
2 |
3 | data class IndexingMetrics(var created: Int = 0, var updated: Int = 0, var skipped: Int = 0, var deleted: Int = 0)
4 |
--------------------------------------------------------------------------------
/app/src/main/java/net/bloople/audiobooks/PlayerState.kt:
--------------------------------------------------------------------------------
1 | package net.bloople.audiobooks
2 |
3 | data class PlayerState(
4 | var window: Int = 0,
5 | var position: Long = 0,
6 | var whenReady: Boolean = true
7 | )
--------------------------------------------------------------------------------
/app/src/main/java/net/bloople/audiobooks/Indexable.kt:
--------------------------------------------------------------------------------
1 | package net.bloople.audiobooks
2 |
3 | interface Indexable {
4 | fun onIndexingProgress(progress: Int, max: Int)
5 | fun onIndexingComplete(metrics: IndexingMetrics)
6 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_history_black_18dp_tint.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_visibility_black_18dp_tint.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/book_starred_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_save_alt_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/baseline_favorite_18.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/baseline_favorite_24.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_settings_backup_restore_24.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_baseline_duration_18.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/java/net/bloople/audiobooks/AudiobooksApplication.kt:
--------------------------------------------------------------------------------
1 | package net.bloople.audiobooks
2 |
3 | import android.annotation.SuppressLint
4 | import android.app.Application
5 | import android.content.Context
6 | import android.os.StrictMode
7 |
8 | class AudiobooksApplication: Application() {
9 | init {
10 | if(BuildConfig.DEBUG) StrictMode.enableDefaults()
11 | }
12 |
13 | override fun onCreate() {
14 | super.onCreate()
15 | context = applicationContext
16 | }
17 |
18 | companion object {
19 | @SuppressLint("StaticFieldLeak")
20 | lateinit var context: Context
21 | private set
22 | }
23 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-anydpi-v24/ic_notification.xml:
--------------------------------------------------------------------------------
1 |
7 |
11 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in C:\Android\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_play_audiobook.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
16 |
--------------------------------------------------------------------------------
/app/src/main/java/net/bloople/audiobooks/Util.kt:
--------------------------------------------------------------------------------
1 | package net.bloople.audiobooks
2 |
3 | import android.database.Cursor
4 |
5 | inline operator fun Cursor.get(columnName: String): T {
6 | val columnIndex = getColumnIndexOrThrow(columnName)
7 | return when (T::class) {
8 | ByteArray::class -> getBlob(columnIndex) as T
9 | Double::class -> getDouble(columnIndex) as T
10 | Float::class -> getFloat(columnIndex) as T
11 | Int::class -> getInt(columnIndex) as T
12 | Long::class -> getLong(columnIndex) as T
13 | Short::class -> getShort(columnIndex) as T
14 | String::class -> getString(columnIndex) as T
15 | Boolean::class -> (getInt(columnIndex) == 1) as T
16 | else -> throw IllegalArgumentException("Unsupported type")
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/database_management_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
14 |
15 |
21 |
22 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
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
21 | org.gradle.unsafe.configuration-cache=true
22 | org.gradle.jvmargs=-XX:+UseParallelGC
23 | android.defaults.buildfeatures.buildconfig=true
24 | android.nonTransitiveRClass=false
25 | android.nonFinalResIds=false
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016 Brenton Fletcher (http://bloople.net i@bloople.net)
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | Some files in this software have an Apache 2.0 license header;
25 | those files are licensed under the Apache 2.0 license, instead of this license.
--------------------------------------------------------------------------------
/app/src/main/java/net/bloople/audiobooks/FileParser.kt:
--------------------------------------------------------------------------------
1 | package net.bloople.audiobooks
2 |
3 | import android.media.MediaMetadataRetriever
4 | import android.util.Log
5 | import java.io.Closeable
6 | import java.io.File
7 |
8 | data class ParsedFile(val path: String, val title: String, val mtime: Long, val size: Long)
9 |
10 | class FileParser: Closeable {
11 | private val retriever = MediaMetadataRetriever()
12 |
13 | override fun close() {
14 | retriever.close()
15 | }
16 |
17 | fun parse(file: File): ParsedFile {
18 | return ParsedFile(
19 | file.canonicalPath,
20 | file.nameWithoutExtension,
21 | file.lastModified(),
22 | getDuration(file.canonicalPath).toLong()
23 | )
24 | }
25 |
26 | private fun getDuration(path: String): Int {
27 | return try {
28 | retriever.setDataSource(path)
29 | val duration = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
30 | duration?.toInt() ?: 0
31 | }
32 | catch (e: RuntimeException) {
33 | Log.e(TAG, "Error while trying to extract duration for $path", e)
34 | 0
35 | }
36 | }
37 |
38 | companion object {
39 | private const val TAG = "FileParser"
40 | }
41 | }
42 |
43 |
--------------------------------------------------------------------------------
/app/src/main/java/net/bloople/audiobooks/IndexViewModel.kt:
--------------------------------------------------------------------------------
1 | package net.bloople.audiobooks
2 |
3 | import android.app.Application
4 | import android.database.Cursor
5 | import androidx.lifecycle.AndroidViewModel
6 | import androidx.lifecycle.MutableLiveData
7 | import java.util.concurrent.Executors
8 |
9 | class IndexViewModel(application: Application) : AndroidViewModel(application) {
10 | private val searcher = BooksSearcher()
11 |
12 | val searchResults: MutableLiveData by lazy {
13 | MutableLiveData().also { resolve() }
14 | }
15 |
16 | val sorterDescription: MutableLiveData by lazy {
17 | MutableLiveData(searcher.description())
18 | }
19 |
20 | val sortMethod: Int
21 | get() = searcher.sortMethod
22 | val sortDirectionAsc: Boolean
23 | get() = searcher.sortDirectionAsc
24 |
25 | fun setSearchText(searchText: String) {
26 | searcher.setSearchText(searchText)
27 | resolve()
28 | }
29 |
30 | fun setSort(sortMethod: Int, sortDirectionAsc: Boolean) {
31 | searcher.sortMethod = sortMethod
32 | searcher.sortDirectionAsc = sortDirectionAsc
33 | sorterDescription.value = searcher.description()
34 | resolve()
35 | }
36 |
37 | fun refresh() {
38 | resolve()
39 | }
40 |
41 | private fun resolve() {
42 | val service = Executors.newSingleThreadExecutor()
43 | service.submit { searchResults.postValue(searcher.search()) }
44 | }
45 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
21 |
22 |
26 |
27 |
29 |
30 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/list_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_books.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
21 |
31 |
32 |
43 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 |
4 | android {
5 | compileSdk 33
6 |
7 | defaultConfig {
8 | applicationId "net.bloople.audiobooks"
9 | minSdk 33
10 | targetSdk 33
11 | final def version = 15
12 | versionCode version
13 | versionName version.toString()
14 | }
15 | compileOptions {
16 | sourceCompatibility JavaVersion.VERSION_17
17 | targetCompatibility JavaVersion.VERSION_17
18 | }
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
23 | }
24 | debug {
25 | applicationIdSuffix ".debug"
26 | }
27 | }
28 | lint {
29 | abortOnError false
30 | }
31 | namespace 'net.bloople.audiobooks'
32 | }
33 |
34 | dependencies {
35 | implementation fileTree(dir: 'libs', include: ['*.jar'])
36 |
37 | implementation "androidx.core:core-ktx:1.10.1"
38 |
39 | def appcompat_version = "1.6.1"
40 | def lifecycle_version = "2.6.1"
41 | def arch_version = "2.1.0"
42 |
43 | implementation "androidx.appcompat:appcompat:$appcompat_version"
44 | // For loading and tinting drawables on older versions of the platform
45 | implementation "androidx.appcompat:appcompat-resources:$appcompat_version"
46 |
47 | // ViewModel
48 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
49 | // LiveData
50 | //implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
51 |
52 | // Saved state module for ViewModel
53 | implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"
54 |
55 | // Annotation processor
56 | implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
57 |
58 | implementation 'androidx.recyclerview:recyclerview:1.3.1'
59 | implementation 'com.google.android.material:material:1.9.0'
60 |
61 | implementation 'com.google.android.exoplayer:exoplayer-core:2.19.1'
62 | implementation 'com.google.android.exoplayer:exoplayer-dash:2.19.1'
63 | implementation 'com.google.android.exoplayer:exoplayer-ui:2.19.1'
64 | }
65 | repositories {
66 | mavenCentral()
67 | }
68 |
--------------------------------------------------------------------------------
/app/src/main/java/net/bloople/audiobooks/DescriptionAdapter.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2018 Filippo Beraldo. All rights reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | //Changed by Brenton Fletcher
17 |
18 | @file:Suppress("DEPRECATION")
19 |
20 | package net.bloople.audiobooks
21 |
22 | import android.app.PendingIntent
23 | import android.content.Intent
24 | import android.graphics.Bitmap
25 | import com.google.android.exoplayer2.Player
26 | import com.google.android.exoplayer2.ui.PlayerNotificationManager
27 |
28 | /**
29 | * Created by Filippo Beraldo on 15/11/2018.
30 | * http://github.com/beraldofilippo
31 | */
32 | class DescriptionAdapter(private val context: PlayerService) :
33 | PlayerNotificationManager.MediaDescriptionAdapter {
34 |
35 | override fun getCurrentContentTitle(player: Player): String =
36 | player.mediaMetadata.title.toString()
37 |
38 | override fun getCurrentContentText(player: Player): String? = null
39 |
40 | override fun getCurrentLargeIcon(
41 | player: Player,
42 | callback: PlayerNotificationManager.BitmapCallback
43 | ): Bitmap? = null
44 |
45 | /**
46 | * Specify the PendingIntent which is fired whenever there's a click on the notification.
47 | * The intent will start the player activity.
48 | * */
49 | override fun createCurrentContentIntent(player: Player): PendingIntent? {
50 | val explicit = Book.idTo(Intent(context, PlayAudiobookActivity::class.java), context.bookId)
51 | explicit.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP
52 |
53 | return PendingIntent.getActivity(
54 | context,
55 | 1,
56 | explicit,
57 | PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
58 | )
59 | }
60 | }
--------------------------------------------------------------------------------
/app/src/main/java/net/bloople/audiobooks/BooksSearcher.kt:
--------------------------------------------------------------------------------
1 | package net.bloople.audiobooks
2 |
3 | import android.database.Cursor
4 | import net.bloople.audiobooks.DatabaseHelper.instance
5 | import java.lang.IllegalStateException
6 |
7 | class BooksSearcher {
8 | private var searchText = ""
9 | var sortMethod = SORT_AGE
10 | var sortDirectionAsc = false
11 |
12 | fun setSearchText(inSearchText: String) {
13 | searchText = inSearchText
14 | }
15 |
16 | fun flipSortDirection() {
17 | sortDirectionAsc = !sortDirectionAsc
18 | }
19 |
20 | fun description(): String {
21 | return "Sorted by ${sortMethodDescription().lowercase()} ${sortDirectionDescription().lowercase()}"
22 | }
23 |
24 | private fun sortMethodDescription(): String {
25 | return when (sortMethod) {
26 | SORT_ALPHABETIC -> "Title"
27 | SORT_AGE -> "Published Date"
28 | SORT_SIZE -> "Size"
29 | SORT_LAST_OPENED -> "Last Opened At"
30 | SORT_OPENED_COUNT -> "Opened Count"
31 | SORT_STARRED -> "Starred"
32 | else -> throw IllegalStateException("sort_method not in valid range")
33 | }
34 | }
35 |
36 | private fun sortDirectionDescription(): String {
37 | return if (sortDirectionAsc) "Ascending" else "Descending"
38 | }
39 |
40 | private fun orderBy(): String {
41 | var orderBy = when (sortMethod) {
42 | SORT_ALPHABETIC -> "title"
43 | SORT_AGE -> "mtime"
44 | SORT_SIZE -> "size"
45 | SORT_LAST_OPENED -> "last_opened_at"
46 | SORT_STARRED -> "starred"
47 | SORT_OPENED_COUNT -> "opened_count"
48 | else -> throw IllegalArgumentException("Invalid sort method")
49 | }
50 | orderBy += if (sortDirectionAsc) " ASC" else " DESC"
51 | orderBy += ", title ASC"
52 | return orderBy
53 | }
54 |
55 | fun search(): Cursor {
56 | val db = instance()
57 | val cursor: Cursor = if (searchText != "") {
58 | db.query("books", null, "title LIKE ?", arrayOf("%$searchText%"), null, null, orderBy())
59 | }
60 | else {
61 | db.query("books", null, null, null, null, null, orderBy())
62 | }
63 | cursor.moveToFirst()
64 | return cursor
65 | }
66 |
67 | companion object {
68 | const val SORT_ALPHABETIC = 0
69 | const val SORT_AGE = 1
70 | const val SORT_SIZE = 2
71 | const val SORT_LAST_OPENED = 3
72 | const val SORT_STARRED = 4
73 | const val SORT_OPENED_COUNT = 5
74 | }
75 | }
--------------------------------------------------------------------------------
/app/src/main/java/net/bloople/audiobooks/DatabaseHelper.kt:
--------------------------------------------------------------------------------
1 | package net.bloople.audiobooks
2 |
3 | import android.content.Context
4 | import android.database.sqlite.SQLiteDatabase
5 | import java.io.FileInputStream
6 | import java.io.FileOutputStream
7 | import java.io.InputStream
8 | import java.io.OutputStream
9 |
10 | internal object DatabaseHelper {
11 | private const val DB_NAME = "books"
12 | private lateinit var database: SQLiteDatabase
13 |
14 | private fun obtainDatabase(): SQLiteDatabase {
15 | val db = AudiobooksApplication.context.openOrCreateDatabase(DB_NAME, Context.MODE_PRIVATE, null)
16 | loadSchema(db)
17 | return db
18 | }
19 |
20 | private fun loadSchema(db: SQLiteDatabase) {
21 | db.execSQL("CREATE TABLE IF NOT EXISTS books ( " +
22 | "_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
23 | "path TEXT, " +
24 | "title TEXT, " +
25 | "mtime INT DEFAULT 0, " +
26 | "size INTEGER DEFAULT 0, " +
27 | "last_opened_at INTEGER DEFAULT 0, " +
28 | "last_read_position INTEGER DEFAULT 0, " +
29 | "starred INTEGER DEFAULT 0" +
30 | ")")
31 |
32 | if (!hasColumn(db, "books", "opened_count")) {
33 | db.execSQL("ALTER TABLE books ADD COLUMN opened_count INTEGER")
34 | db.execSQL("UPDATE books SET opened_count=0")
35 | }
36 | }
37 |
38 | @JvmStatic
39 | @Synchronized
40 | fun instance(): SQLiteDatabase {
41 | if (!::database.isInitialized) {
42 | database = obtainDatabase()
43 | }
44 | return database
45 | }
46 |
47 | @JvmStatic
48 | @Synchronized
49 | fun deleteDatabase() {
50 | AudiobooksApplication.context.deleteDatabase(DB_NAME)
51 | database = obtainDatabase()
52 | }
53 |
54 | @JvmStatic
55 | @Synchronized
56 | fun exportDatabase(outputStream: OutputStream) {
57 | val path = instance().use { it.path }
58 | outputStream.use { FileInputStream(path).use { inputStream -> inputStream.copyTo(it) } }
59 | }
60 |
61 | @JvmStatic
62 | @Synchronized
63 | fun importDatabase(inputStream: InputStream) {
64 | val path = instance().use { it.path }
65 | inputStream.use { FileOutputStream(path).use { outputStream -> inputStream.copyTo(outputStream) } }
66 | database = obtainDatabase()
67 | }
68 |
69 | private fun hasColumn(db: SQLiteDatabase, tableName: String, columnName: String): Boolean {
70 | db.rawQuery("PRAGMA table_info($tableName)", null).use {
71 | while (it.moveToNext()) {
72 | if (it.getString(it.getColumnIndexOrThrow("name")).equals(columnName)) {
73 | return true
74 | }
75 | }
76 | }
77 | return false
78 | }
79 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_book.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
26 |
27 |
31 |
32 |
45 |
46 |
60 |
61 |
62 |
63 |
69 |
--------------------------------------------------------------------------------
/app/src/main/java/net/bloople/audiobooks/IndexingTask.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("DEPRECATION")
2 |
3 | package net.bloople.audiobooks
4 |
5 | import android.os.AsyncTask
6 | import java.io.File
7 | import java.util.*
8 |
9 | @Suppress("OVERRIDE_DEPRECATION")
10 | internal class IndexingTask(private val indexable: Indexable) :
11 | AsyncTask() {
12 | private var progress = 0
13 | private var max = 0
14 | private val metrics = IndexingMetrics()
15 | private val fileParser = FileParser()
16 |
17 | override fun doInBackground(vararg params: String) {
18 | destroyDeleted()
19 | fileParser.use { indexDirectory(File(params[0])) }
20 | publishProgress(progress, max)
21 | }
22 |
23 | override fun onProgressUpdate(vararg args: Int?) {
24 | indexable.onIndexingProgress(args[0]!!, args[1]!!)
25 | }
26 |
27 | override fun onPostExecute(result: Unit?) {
28 | indexable.onIndexingComplete(metrics)
29 | }
30 |
31 | private fun destroyDeleted() {
32 | Book.findAll {
33 | max += it.count
34 | while (it.moveToNext()) {
35 | val book = Book(it)
36 | if (!book.file.exists()) {
37 | book.destroy()
38 | metrics.deleted++
39 | }
40 |
41 | progress++
42 | publishProgress(progress, max)
43 | }
44 | }
45 | }
46 |
47 | private fun indexDirectory(directory: File) {
48 | val (directories, files) = directory.listFiles()?.partition { it.isDirectory } ?: return
49 | for(d in directories) { indexDirectory(d) }
50 |
51 | files.filter { AUDIOBOOK_EXTENSIONS.contains(it.extension.lowercase()) }.also {
52 | max += it.size
53 | publishProgress(progress, max)
54 | }.forEach(::indexFile)
55 | }
56 |
57 | private fun indexFile(file: File) {
58 | val parsedFile by lazy { fileParser.parse(file) }
59 | val book = Book.findByPathOrNull(file.canonicalPath)
60 | if(book != null) updateBook(parsedFile, book)
61 | else createBook(parsedFile)
62 |
63 | progress++
64 | publishProgress(progress, max)
65 | }
66 |
67 | private fun createBook(parsedFile: ParsedFile) {
68 | NewBook(parsedFile.path, parsedFile.title, parsedFile.mtime, parsedFile.size).save()
69 | metrics.created++
70 | }
71 |
72 | private fun updateBook(parsedFile: ParsedFile, book: Book) {
73 | if(parsedFile.mtime == book.mtime) {
74 | metrics.skipped++
75 | return
76 | }
77 |
78 | book.edit {
79 | title = parsedFile.title
80 | mtime = parsedFile.mtime
81 | size = parsedFile.size
82 | }
83 | metrics.updated++
84 | }
85 |
86 | companion object {
87 | val AUDIOBOOK_EXTENSIONS = listOf("m4a", "mkv", "mp3", "mp4", "wav", "webm")
88 | }
89 | }
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_indexing.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
19 |
26 |
27 |
28 |
33 |
40 |
41 |
45 |
46 |
53 |
54 |
62 |
63 |
64 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/app/src/main/java/net/bloople/audiobooks/DatabaseManagementFragment.kt:
--------------------------------------------------------------------------------
1 | package net.bloople.audiobooks
2 |
3 | import android.app.Activity
4 | import android.content.Intent
5 | import android.os.Bundle
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import android.widget.ImageButton
10 | import android.widget.Toast
11 | import androidx.fragment.app.Fragment
12 | import java.io.IOException
13 |
14 | @Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
15 | class DatabaseManagementFragment : Fragment() {
16 | private lateinit var importDatabaseButton: ImageButton
17 | private lateinit var exportDatabaseButton: ImageButton
18 |
19 | override fun onCreateView(inflater: LayoutInflater, parent: ViewGroup?, savedInstanceState: Bundle?): View? {
20 | return inflater.inflate(R.layout.database_management_fragment, parent, false)
21 | }
22 |
23 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
24 | super.onViewCreated(view, savedInstanceState)
25 | exportDatabaseButton = view.findViewById(R.id.export_database)
26 | exportDatabaseButton.setOnClickListener { startExport() }
27 | importDatabaseButton = view.findViewById(R.id.import_database)
28 | importDatabaseButton.setOnClickListener { startImport() }
29 | }
30 |
31 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
32 | if(requestCode == REQUEST_CODE_EXPORT && resultCode == Activity.RESULT_OK) completeExport(data!!)
33 | else if(requestCode == REQUEST_CODE_IMPORT && resultCode == Activity.RESULT_OK) completeImport(data!!)
34 | }
35 |
36 | private fun startExport() {
37 | val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
38 | intent.addCategory(Intent.CATEGORY_OPENABLE)
39 | intent.type = "application/vnd.sqlite3"
40 | intent.putExtra(Intent.EXTRA_TITLE, "Audiobooks.db")
41 | startActivityForResult(intent, REQUEST_CODE_EXPORT)
42 | }
43 |
44 | private fun completeExport(data: Intent) {
45 | try {
46 | requireContext().contentResolver.openOutputStream(data.data!!).use { DatabaseHelper.exportDatabase(it!!) }
47 |
48 | Toast.makeText(context, "Database exported successfully", Toast.LENGTH_LONG).show()
49 | }
50 | catch(e: IOException) {
51 | Toast.makeText(context, "Error", Toast.LENGTH_LONG).show()
52 | }
53 | }
54 |
55 | private fun startImport() {
56 | val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
57 | intent.addCategory(Intent.CATEGORY_OPENABLE)
58 | intent.type = "*/*"
59 | startActivityForResult(intent, REQUEST_CODE_IMPORT)
60 | }
61 |
62 | private fun completeImport(data: Intent) {
63 | try {
64 | requireContext().contentResolver.openInputStream(data.data!!).use { DatabaseHelper.importDatabase(it!!) }
65 |
66 | Toast.makeText(context, "Database imported successfully", Toast.LENGTH_LONG).show()
67 | }
68 | catch(e: IOException) {
69 | Toast.makeText(context, "Error", Toast.LENGTH_LONG).show()
70 | }
71 | }
72 |
73 | companion object {
74 | private const val REQUEST_CODE_EXPORT = 0
75 | private const val REQUEST_CODE_IMPORT = 2
76 | }
77 | }
--------------------------------------------------------------------------------
/app/src/main/java/net/bloople/audiobooks/PlayAudiobookActivity.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("DEPRECATION")
2 |
3 | package net.bloople.audiobooks
4 |
5 | import android.Manifest
6 | import android.annotation.SuppressLint
7 | import android.app.Activity
8 | import android.content.ComponentName
9 | import android.content.Intent
10 | import android.content.ServiceConnection
11 | import android.content.pm.PackageManager
12 | import android.os.Bundle
13 | import android.os.IBinder
14 | import android.widget.TextView
15 | import com.google.android.exoplayer2.ui.StyledPlayerControlView
16 |
17 | @Suppress("OVERRIDE_DEPRECATION")
18 | class PlayAudiobookActivity : Activity() {
19 | private var bookId: Long = -1
20 | private var playerView: StyledPlayerControlView? = null
21 |
22 | /**
23 | * Create our connection to the service to be used in our bindService call.
24 | */
25 | private var serviceBound = false
26 | private val connection = object : ServiceConnection {
27 | override fun onServiceDisconnected(name: ComponentName?) {
28 | serviceBound = false
29 | playerView?.let { playerView!!.player = null }
30 | }
31 |
32 | /**
33 | * Called after a successful bind with our PlayerService.
34 | */
35 | override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
36 | serviceBound = true
37 | if (service is PlayerService.PlayerServiceBinder) {
38 | val audioFocusPlayer = service.getPlayerInstance() // use the player and call methods on it to start and stop
39 | playerView?.let { playerView!!.player = audioFocusPlayer }
40 | }
41 | }
42 | }
43 |
44 | @SuppressLint("MissingInflatedId")
45 | override fun onCreate(savedInstanceState: Bundle?) {
46 | super.onCreate(savedInstanceState)
47 | setContentView(R.layout.activity_play_audiobook)
48 |
49 | bookId = Book.idFrom(intent)
50 |
51 | val book = Book.find(bookId)
52 | book.edit {
53 | lastOpenedAt = System.currentTimeMillis()
54 | openedCount += 1
55 | }
56 |
57 | val titleView: TextView = findViewById(R.id.exo_title)
58 | titleView.text = book.title
59 |
60 | playerView = findViewById(R.id.player)
61 |
62 | val permission = checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS)
63 | if (permission == PackageManager.PERMISSION_GRANTED) completeCreate()
64 | else requestPermissions(arrayOf(Manifest.permission.POST_NOTIFICATIONS), REQUEST_CODE_POST_NOTIFICATIONS)
65 | }
66 |
67 | private fun completeCreate() {
68 | val playerIntent = Book.idTo(Intent(this.applicationContext, PlayerService::class.java), bookId)
69 | startService(playerIntent)
70 | bindService(playerIntent, connection, BIND_AUTO_CREATE)
71 | }
72 |
73 | override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
74 | super.onRequestPermissionsResult(requestCode, permissions, grantResults)
75 | if(requestCode == REQUEST_CODE_POST_NOTIFICATIONS && grantResults.isNotEmpty()
76 | && grantResults[0] == PackageManager.PERMISSION_GRANTED) completeCreate()
77 | }
78 |
79 | override fun onStop() {
80 | super.onStop()
81 | Book.edit(bookId) { lastOpenedAt = System.currentTimeMillis() }
82 | }
83 |
84 | override fun onBackPressed() {
85 | super.onBackPressed()
86 | finish()
87 | }
88 |
89 | override fun onDestroy() {
90 | super.onDestroy()
91 | if(serviceBound) {
92 | serviceBound = false
93 | unbindService(connection)
94 | }
95 | }
96 |
97 | companion object {
98 | private const val REQUEST_CODE_POST_NOTIFICATIONS = 1
99 | }
100 | }
--------------------------------------------------------------------------------
/app/src/main/java/net/bloople/audiobooks/BooksAdapter.kt:
--------------------------------------------------------------------------------
1 | package net.bloople.audiobooks
2 |
3 | import android.content.Intent
4 | import android.database.Cursor
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import android.widget.ImageButton
9 | import android.widget.TextView
10 | import androidx.recyclerview.widget.RecyclerView
11 | import java.text.SimpleDateFormat
12 | import java.util.Date
13 | import java.util.Formatter
14 | import java.util.Locale
15 |
16 | class BooksAdapter(cursor: Cursor?) : CursorRecyclerAdapter(cursor) {
17 | class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
18 | var titleView: TextView
19 | var sizeView: TextView
20 | var ageView: TextView?
21 | var lastOpenedView: TextView
22 | var openedCountView: TextView?
23 | var starView: ImageButton
24 |
25 | init {
26 | view.setOnClickListener { v: View ->
27 | val context = v.context
28 | val intent = Book.idTo(Intent(context, PlayAudiobookActivity::class.java), itemId)
29 | context.startActivity(intent)
30 | }
31 |
32 | titleView = view.findViewById(R.id.story_title)
33 | sizeView = view.findViewById(R.id.story_size)
34 | ageView = view.findViewById(R.id.story_age)
35 | lastOpenedView = view.findViewById(R.id.story_last_opened)
36 | openedCountView = view.findViewById(R.id.story_opened_count)
37 | starView = view.findViewById(R.id.story_star)
38 |
39 | starView.setOnClickListener { v: View ->
40 | val book = Book.find(itemId)
41 | val starred = !book.starred
42 |
43 | book.starred = starred
44 | book.save()
45 |
46 | v.isActivated = starred
47 | }
48 | }
49 | }
50 |
51 | private val DATE_FORMAT = SimpleDateFormat("d MMMM yyyy", Locale.getDefault())
52 |
53 | // Create new views (invoked by the layout manager)
54 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
55 | val view = LayoutInflater.from(parent.context).inflate(R.layout.item_book, parent, false)
56 | return ViewHolder(view)
57 | }
58 |
59 | // Replace the contents of a view (invoked by the layout manager)
60 | override fun onBindViewHolder(holder: ViewHolder, cursor: Cursor) {
61 | val book = Book(cursor)
62 |
63 | holder.titleView.text = book.title
64 |
65 | holder.sizeView.text = getReadableTimeDuration(book.size)
66 |
67 | if (holder.ageView != null) {
68 | val age = DATE_FORMAT.format(Date(book.mtime))
69 | holder.ageView!!.text = age
70 | }
71 |
72 | val lastOpenedMillis = book.lastOpenedAt
73 | if (lastOpenedMillis > 0L) {
74 | val lastOpened = DATE_FORMAT.format(Date(lastOpenedMillis))
75 | holder.lastOpenedView.text = lastOpened
76 | }
77 | else {
78 | holder.lastOpenedView.text = "Never"
79 | }
80 |
81 | if (holder.openedCountView != null) {
82 | holder.openedCountView!!.text = book.openedCount.toString()
83 | }
84 |
85 | holder.starView.isActivated = book.starred
86 | }
87 |
88 | companion object {
89 | //Based on https://stackoverflow.com/a/63327131/14819
90 | fun getReadableTimeDuration(timeMs: Long): String {
91 | val formatter = Formatter()
92 | val totalSeconds = timeMs / 1000
93 | val seconds = totalSeconds % 60
94 | val minutes = totalSeconds / 60 % 60
95 | val hours = totalSeconds / 3600
96 |
97 | return if (hours > 0) {
98 | formatter.format("%d:%d:%02d", hours, minutes, seconds).toString()
99 | }
100 | else {
101 | formatter.format("%d:%02d", minutes, seconds).toString()
102 | }
103 | }
104 | }
105 | }
--------------------------------------------------------------------------------
/app/src/main/java/net/bloople/audiobooks/Book.kt:
--------------------------------------------------------------------------------
1 | package net.bloople.audiobooks
2 |
3 | import android.content.ContentValues
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.database.Cursor
7 | import android.net.Uri
8 | import java.io.File
9 |
10 | open class NewBook(
11 | var path: String,
12 | var title: String,
13 | var mtime: Long = 0,
14 | var size: Long = 0,
15 | var lastOpenedAt: Long = 0,
16 | var openedCount: Int = 0,
17 | var lastReadPosition: Long = 0,
18 | var starred: Boolean = false
19 | ) {
20 | val file
21 | get() = File(path)
22 |
23 | val uri
24 | get() = Uri.fromFile(file).toString()
25 |
26 | fun toValues(): ContentValues {
27 | return ContentValues().apply {
28 | put("path", path)
29 | put("title", title)
30 | put("mtime", mtime)
31 | put("size", size)
32 | put("last_opened_at", lastOpenedAt)
33 | put("opened_count", openedCount)
34 | put("last_read_position", lastReadPosition)
35 | put("starred", if (starred) 1 else 0)
36 | }
37 | }
38 |
39 | open fun save(): Book {
40 | val db = DatabaseHelper.instance()
41 | val id = db.insert("books", null, toValues())
42 | return Book(id, path, title, mtime, size, lastOpenedAt, openedCount, lastReadPosition, starred)
43 | }
44 | }
45 |
46 | class Book(
47 | private val _id: Long,
48 | path: String,
49 | title: String,
50 | mtime: Long,
51 | size: Long,
52 | lastOpenedAt: Long,
53 | openedCount: Int,
54 | lastReadPosition: Long,
55 | starred: Boolean
56 | ): NewBook(path, title, mtime, size, lastOpenedAt, openedCount, lastReadPosition, starred) {
57 | constructor(result: Cursor) : this(
58 | result["_id"],
59 | result["path"],
60 | result["title"],
61 | result["mtime"],
62 | result["size"],
63 | result["last_opened_at"],
64 | result["opened_count"],
65 | result["last_read_position"],
66 | result["starred"]
67 | )
68 |
69 | override fun save(): Book {
70 | val db = DatabaseHelper.instance()
71 | db.update("books", toValues(), "_id=?", arrayOf(_id.toString()))
72 | return this
73 | }
74 |
75 | fun edit(block: Book.() -> Unit): Book {
76 | return apply(block).apply { save() }
77 | }
78 |
79 | fun destroy() {
80 | val db = DatabaseHelper.instance()
81 | db.delete("books", "_id=?", arrayOf(_id.toString()))
82 | }
83 |
84 | companion object {
85 | fun edit(id: Long, block: Book.() -> Unit): Book {
86 | return find(id).edit(block)
87 | }
88 |
89 | @JvmStatic
90 | fun find(id: Long): Book {
91 | val db = DatabaseHelper.instance()
92 | db.rawQuery("SELECT * FROM books WHERE _id=?", arrayOf(id.toString())).use {
93 | it.moveToFirst()
94 | return if (it.count > 0) Book(it) else throw NoSuchElementException("Book with id $id not found")
95 | }
96 | }
97 |
98 | @JvmStatic
99 | fun findByPathOrNull(path: String): Book? {
100 | val db = DatabaseHelper.instance()
101 | db.rawQuery("SELECT * FROM books WHERE path=?", arrayOf(path)).use {
102 | it.moveToFirst()
103 | return if (it.count > 0) Book(it) else null
104 | }
105 | }
106 |
107 | fun findAll(block: (Cursor) -> Unit) {
108 | val db = DatabaseHelper.instance()
109 | db.query("books", null, null, null, null, null, null).use(block)
110 | }
111 |
112 | fun idFrom(intent: Intent?): Long {
113 | return intent!!.getLongExtra("_id", -1).also {
114 | if(it == -1L) throw IllegalArgumentException("Intent missing extra _id of type long")
115 | }
116 | }
117 |
118 | fun idTo(intent: Intent, id: Long): Intent {
119 | return intent.apply { putExtra("_id", id) }
120 | }
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/app/src/main/res/layout-sw600dp/item_book.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
25 |
26 |
30 |
31 |
44 |
45 |
59 |
60 |
74 |
75 |
88 |
89 |
93 |
94 |
95 |
96 |
102 |
--------------------------------------------------------------------------------
/app/src/main/java/net/bloople/audiobooks/IndexingActivity.kt:
--------------------------------------------------------------------------------
1 | package net.bloople.audiobooks
2 |
3 | import net.bloople.audiobooks.DatabaseHelper.deleteDatabase
4 | import android.os.Bundle
5 | import android.view.inputmethod.EditorInfo
6 | import android.content.Intent
7 | import android.content.SharedPreferences
8 | import android.net.Uri
9 | import android.os.Environment
10 | import android.provider.Settings
11 | import android.view.KeyEvent
12 | import android.widget.*
13 | import androidx.appcompat.app.AppCompatActivity
14 | import androidx.appcompat.widget.Toolbar
15 |
16 | @Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
17 | class IndexingActivity : AppCompatActivity(), Indexable {
18 | private lateinit var progressBar: ProgressBar
19 | private lateinit var indexButton: Button
20 | private lateinit var indexRoot: String
21 | private var canAccessFiles = false
22 |
23 | override fun onCreate(savedInstanceState: Bundle?) {
24 | super.onCreate(savedInstanceState)
25 | setContentView(R.layout.activity_indexing)
26 |
27 | val toolbar = findViewById(R.id.toolbar)
28 | setSupportActionBar(toolbar)
29 |
30 | if(Environment.isExternalStorageManager()) {
31 | canAccessFiles = true
32 | }
33 | else {
34 | val uri = Uri.parse("package:${BuildConfig.APPLICATION_ID}")
35 | startActivityForResult(
36 | Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION, uri),
37 | REQUEST_CODE_MANAGE_EXTERNAL_STORAGE
38 | )
39 | }
40 |
41 | progressBar = findViewById(R.id.indexing_progress)
42 | indexButton = findViewById(R.id.index_button)
43 | indexButton.setOnClickListener {
44 | indexButton.isEnabled = false
45 |
46 | if (canAccessFiles) {
47 | val indexer = IndexingTask(this@IndexingActivity)
48 | indexer.execute(indexRoot)
49 | }
50 | }
51 |
52 | val deleteIndexButton = findViewById