From 8875c3f9bb49c9f5d1cbe0eb9b62c183613e36a7 Mon Sep 17 00:00:00 2001 From: Ivan Iskandar <12537387+ivaniskandar@users.noreply.github.com> Date: Sat, 28 Aug 2021 23:06:29 +0700 Subject: [PATCH] Add IME_FLAG_NO_PERSONALIZED_LEARNING flag to text input when incognito is enabled (#5801) * Add IME_FLAG_NO_PERSONALIZED_LEARNING flag to text input when incognito is enabled Tested with Gboard only. * Revert "Add IME_FLAG_NO_PERSONALIZED_LEARNING flag to text input when incognito is enabled" This reverts commit 068399db * Add IME_FLAG_NO_PERSONALIZED_LEARNING flag to text inputs when incognito is enabled Source preference is not affected. * Source preference stuff (cherry picked from commit 3ea84cf0ce23d37a1787663834ab9922f10577f8) # Conflicts: # app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/SourcePreferencesController.kt --- .../details/SourcePreferencesController.kt | 8 ++ .../tachiyomi/widget/TachiyomiSearchView.kt | 47 ++++++++ .../widget/TachiyomiTextInputEditText.kt | 60 ++++++++++ .../java/exh/md/handlers/ComikeyHandler.kt | 103 ++++++++++++++++++ .../main/res/layout/dialog_stub_textinput.xml | 2 +- .../res/layout/download_custom_amount.xml | 2 +- app/src/main/res/layout/edit_manga_dialog.xml | 8 +- .../main/res/layout/eh_fragment_batch_add.xml | 2 +- .../main/res/layout/navigation_view_text.xml | 2 +- .../main/res/layout/pref_account_login.xml | 4 +- .../pref_site_login_two_factor_auth.xml | 7 +- .../main/res/layout/track_search_dialog.xml | 2 +- app/src/main/res/menu/browse_extensions.xml | 2 +- app/src/main/res/menu/browse_sources.xml | 2 +- app/src/main/res/menu/global_search.xml | 2 +- app/src/main/res/menu/history.xml | 2 +- app/src/main/res/menu/library.xml | 2 +- app/src/main/res/menu/settings_main.xml | 2 +- app/src/main/res/menu/settings_sources.xml | 2 +- app/src/main/res/menu/source_browse.xml | 2 +- 20 files changed, 241 insertions(+), 22 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiSearchView.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiTextInputEditText.kt create mode 100644 app/src/main/java/exh/md/handlers/ComikeyHandler.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/SourcePreferencesController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/SourcePreferencesController.kt index 5f99a937f..db009066f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/SourcePreferencesController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/details/SourcePreferencesController.kt @@ -27,6 +27,7 @@ import eu.kanade.tachiyomi.source.ConfigurableSource import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.getPreferenceKey import eu.kanade.tachiyomi.ui.base.controller.NucleusController +import eu.kanade.tachiyomi.widget.TachiyomiTextInputEditText.Companion.setIncognito import exh.source.EnhancedHttpSource import timber.log.Timber @@ -124,6 +125,13 @@ class SourcePreferencesController(bundle: Bundle? = null) : pref.isIconSpaceReserved = false pref.order = Int.MAX_VALUE // reset to default order + // Apply incognito IME for EditTextPreference + if (pref is EditTextPreference) { + pref.setOnBindEditTextListener { + it.setIncognito(viewScope) + } + } + newScreen.removePreference(pref) screen.addPreference(pref) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiSearchView.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiSearchView.kt new file mode 100644 index 000000000..80e7850c6 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiSearchView.kt @@ -0,0 +1,47 @@ +package eu.kanade.tachiyomi.widget + +import android.content.Context +import android.util.AttributeSet +import androidx.appcompat.widget.SearchView +import androidx.core.view.inputmethod.EditorInfoCompat +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.data.preference.asImmediateFlow +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.launchIn +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +/** + * A custom [SearchView] that sets [EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING] to imeOptions + * if [PreferencesHelper.incognitoMode] is true. Some IMEs may not respect this flag. + */ +class TachiyomiSearchView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = R.attr.searchViewStyle +) : SearchView(context, attrs, defStyleAttr) { + + private var scope: CoroutineScope? = null + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + scope = CoroutineScope(SupervisorJob() + Dispatchers.Main) + Injekt.get().incognitoMode().asImmediateFlow { + imeOptions = if (it) { + imeOptions or EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING + } else { + imeOptions and EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING.inv() + } + }.launchIn(scope!!) + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + scope?.cancel() + scope = null + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiTextInputEditText.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiTextInputEditText.kt new file mode 100644 index 000000000..5e1ebc7f5 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/TachiyomiTextInputEditText.kt @@ -0,0 +1,60 @@ +package eu.kanade.tachiyomi.widget + +import android.content.Context +import android.util.AttributeSet +import android.widget.EditText +import androidx.core.view.inputmethod.EditorInfoCompat +import com.google.android.material.textfield.TextInputEditText +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.data.preference.asImmediateFlow +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.launchIn +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +/** + * A custom [TextInputEditText] that sets [EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING] to imeOptions + * if [PreferencesHelper.incognitoMode] is true. Some IMEs may not respect this flag. + * + * @see setIncognito + */ +class TachiyomiTextInputEditText @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = R.attr.editTextStyle +) : TextInputEditText(context, attrs, defStyleAttr) { + + private var scope: CoroutineScope? = null + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + scope = CoroutineScope(SupervisorJob() + Dispatchers.Main) + setIncognito(scope!!) + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + scope?.cancel() + scope = null + } + + companion object { + /** + * Sets Flow to this [EditText] that sets [EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING] to imeOptions + * if [PreferencesHelper.incognitoMode] is true. Some IMEs may not respect this flag. + */ + fun EditText.setIncognito(viewScope: CoroutineScope) { + Injekt.get().incognitoMode().asImmediateFlow { + imeOptions = if (it) { + imeOptions or EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING + } else { + imeOptions and EditorInfoCompat.IME_FLAG_NO_PERSONALIZED_LEARNING.inv() + } + }.launchIn(viewScope) + } + } +} diff --git a/app/src/main/java/exh/md/handlers/ComikeyHandler.kt b/app/src/main/java/exh/md/handlers/ComikeyHandler.kt new file mode 100644 index 000000000..fbef3f8b9 --- /dev/null +++ b/app/src/main/java/exh/md/handlers/ComikeyHandler.kt @@ -0,0 +1,103 @@ +package exh.md.handlers + +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.await +import eu.kanade.tachiyomi.source.model.Page +import exh.md.dto.MangaPlusSerializer +import kotlinx.serialization.protobuf.ProtoBuf +import okhttp3.Headers +import okhttp3.Interceptor +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import okhttp3.ResponseBody.Companion.toResponseBody +import java.util.UUID + +class MangaPlusHandler(currentClient: OkHttpClient) { + val baseUrl = "https://jumpg-webapi.tokyo-cdn.com/api" + val headers = Headers.Builder() + .add("Origin", WEB_URL) + .add("Referer", WEB_URL) + .add("User-Agent", USER_AGENT) + .add("SESSION-TOKEN", UUID.randomUUID().toString()).build() + + val client: OkHttpClient = currentClient.newBuilder() + .addInterceptor { imageIntercept(it) } + .build() + + suspend fun fetchPageList(chapterId: String): List { + val response = client.newCall(pageListRequest(chapterId)).await() + return pageListParse(response) + } + + private fun pageListRequest(chapterId: String): Request { + return GET( + "$baseUrl/manga_viewer?chapter_id=$chapterId&split=yes&img_quality=super_high", + headers + ) + } + + private fun pageListParse(response: Response): List { + val result = ProtoBuf.decodeFromByteArray(MangaPlusSerializer, response.body!!.bytes()) + + if (result.success == null) { + throw Exception("error getting images") + } + + return result.success.mangaViewer!!.pages + .mapNotNull { it.page } + .mapIndexed { i, page -> + val encryptionKey = + if (page.encryptionKey == null) "" else "&encryptionKey=${page.encryptionKey}" + Page(i, "", "${page.imageUrl}$encryptionKey") + } + } + + private fun imageIntercept(chain: Interceptor.Chain): Response { + var request = chain.request() + + if (!request.url.queryParameterNames.contains("encryptionKey")) { + return chain.proceed(request) + } + + val encryptionKey = request.url.queryParameter("encryptionKey")!! + + // Change the url and remove the encryptionKey to avoid detection. + val newUrl = request.url.newBuilder().removeAllQueryParameters("encryptionKey").build() + request = request.newBuilder().url(newUrl).build() + + val response = chain.proceed(request) + + val image = decodeImage(encryptionKey, response.body!!.bytes()) + + val body = image.toResponseBody("image/jpeg".toMediaTypeOrNull()) + return response.newBuilder().body(body).build() + } + + private fun decodeImage(encryptionKey: String, image: ByteArray): ByteArray { + val keyStream = HEX_GROUP + .findAll(encryptionKey) + .map { it.groupValues[1].toInt(16) } + .toList() + + val content = image + .map { it.toInt() } + .toIntArray() + + val blockSizeInBytes = keyStream.size + + content.forEachIndexed { i, value -> + content[i] = value xor keyStream[i % blockSizeInBytes] + } + + return ByteArray(content.size) { pos -> content[pos].toByte() } + } + + companion object { + private const val WEB_URL = "https://mangaplus.shueisha.co.jp" + private const val USER_AGENT = + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36" + private val HEX_GROUP = "(.{1,2})".toRegex() + } +} diff --git a/app/src/main/res/layout/dialog_stub_textinput.xml b/app/src/main/res/layout/dialog_stub_textinput.xml index 1a4b99e61..4bbc29d20 100644 --- a/app/src/main/res/layout/dialog_stub_textinput.xml +++ b/app/src/main/res/layout/dialog_stub_textinput.xml @@ -11,7 +11,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> - diff --git a/app/src/main/res/layout/download_custom_amount.xml b/app/src/main/res/layout/download_custom_amount.xml index 384052130..a38b54681 100644 --- a/app/src/main/res/layout/download_custom_amount.xml +++ b/app/src/main/res/layout/download_custom_amount.xml @@ -25,7 +25,7 @@ android:tint="?attr/colorAccent" app:srcCompat="@drawable/ic_chevron_left_black_24dp" /> - - - - - - - - - - - - + - diff --git a/app/src/main/res/menu/browse_sources.xml b/app/src/main/res/menu/browse_sources.xml index 51767187b..371748877 100644 --- a/app/src/main/res/menu/browse_sources.xml +++ b/app/src/main/res/menu/browse_sources.xml @@ -5,7 +5,7 @@ android:id="@+id/action_search" android:icon="@drawable/ic_travel_explore_24dp" android:title="@string/action_global_search" - app:actionViewClass="androidx.appcompat.widget.SearchView" + app:actionViewClass="eu.kanade.tachiyomi.widget.TachiyomiSearchView" app:iconTint="?attr/colorOnToolbar" app:showAsAction="collapseActionView|ifRoom" /> diff --git a/app/src/main/res/menu/global_search.xml b/app/src/main/res/menu/global_search.xml index efbd32c55..743391d80 100644 --- a/app/src/main/res/menu/global_search.xml +++ b/app/src/main/res/menu/global_search.xml @@ -5,7 +5,7 @@ android:id="@+id/action_search" android:icon="@drawable/ic_search_24dp" android:title="@string/action_search" - app:actionViewClass="androidx.appcompat.widget.SearchView" + app:actionViewClass="eu.kanade.tachiyomi.widget.TachiyomiSearchView" app:iconTint="?attr/colorOnToolbar" app:showAsAction="collapseActionView|ifRoom" /> diff --git a/app/src/main/res/menu/history.xml b/app/src/main/res/menu/history.xml index 426bc8889..bc04d8389 100644 --- a/app/src/main/res/menu/history.xml +++ b/app/src/main/res/menu/history.xml @@ -6,7 +6,7 @@ android:id="@+id/action_search" android:icon="@drawable/ic_search_24dp" android:title="@string/action_search" - app:actionViewClass="androidx.appcompat.widget.SearchView" + app:actionViewClass="eu.kanade.tachiyomi.widget.TachiyomiSearchView" app:iconTint="?attr/colorOnToolbar" app:showAsAction="ifRoom|collapseActionView" /> diff --git a/app/src/main/res/menu/library.xml b/app/src/main/res/menu/library.xml index efd3aa7cd..56d191ff6 100755 --- a/app/src/main/res/menu/library.xml +++ b/app/src/main/res/menu/library.xml @@ -7,7 +7,7 @@ android:id="@+id/action_search" android:icon="@drawable/ic_search_24dp" android:title="@string/action_search" - app:actionViewClass="androidx.appcompat.widget.SearchView" + app:actionViewClass="eu.kanade.tachiyomi.widget.TachiyomiSearchView" app:iconTint="?attr/colorOnToolbar" app:showAsAction="collapseActionView|ifRoom" /> diff --git a/app/src/main/res/menu/settings_main.xml b/app/src/main/res/menu/settings_main.xml index efbd32c55..743391d80 100644 --- a/app/src/main/res/menu/settings_main.xml +++ b/app/src/main/res/menu/settings_main.xml @@ -5,7 +5,7 @@ android:id="@+id/action_search" android:icon="@drawable/ic_search_24dp" android:title="@string/action_search" - app:actionViewClass="androidx.appcompat.widget.SearchView" + app:actionViewClass="eu.kanade.tachiyomi.widget.TachiyomiSearchView" app:iconTint="?attr/colorOnToolbar" app:showAsAction="collapseActionView|ifRoom" /> diff --git a/app/src/main/res/menu/settings_sources.xml b/app/src/main/res/menu/settings_sources.xml index d7818dcdf..e3b50771a 100644 --- a/app/src/main/res/menu/settings_sources.xml +++ b/app/src/main/res/menu/settings_sources.xml @@ -7,7 +7,7 @@ android:icon="@drawable/ic_search_24dp" app:showAsAction="collapseActionView|ifRoom" app:iconTint="?attr/colorOnToolbar" - app:actionViewClass="androidx.appcompat.widget.SearchView"/> + app:actionViewClass="eu.kanade.tachiyomi.widget.TachiyomiSearchView"/>