Compare commits

..
132 Commits
Author SHA1 Message Date
tarsin d13cd1e07f 1.8.0.2
CI / Build (push) Has been cancelled
Releases / Build (push) Has been cancelled
2022-07-28 10:37:07 +08:00
tarsin 7365fd0448 Fix a memleak 2022-07-28 10:33:47 +08:00
txyyh f1febca312 Let renovate bot detect two branch 2022-07-28 04:12:33 +08:00
renovate[bot] dbf67404c0 Update fragment_version to v1.5.1 (#138)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-28 03:39:42 +08:00
renovate[bot] 8cfe8c4b80 Update collection_version to v1.3.0-alpha02 (#137)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-28 03:39:25 +08:00
tarsin 674f53315f Code clean 2022-07-27 19:46:39 +08:00
tarsin e51cd6b90d Drop taptargetview
* Not MD3
2022-07-25 20:44:59 +08:00
tarsin a44f12b2e0 Reformat Code 2022-07-25 20:07:16 +08:00
tarsin a2552ed5b9 Reformat 2022-07-25 20:05:38 +08:00
tarsin 763c007950 Log tweaks 2022-07-25 20:02:47 +08:00
tarsin b221bf8e52 Release 1.8.0.1
CI / Build (push) Has been cancelled
Releases / Build (push) Has been cancelled
2022-07-25 19:16:07 +08:00
tarsin ab9a89685c Code clean 2022-07-25 19:02:10 +08:00
tarsin 41ccb16227 Code clean 2022-07-25 18:42:18 +08:00
tarsin f6bf7b1ba6 No need to thanks image decs anymore 2022-07-25 13:06:24 +08:00
tarsin 09e7113df1 Fix a dirty dealloc 2022-07-25 12:23:52 +08:00
tarsin 62a7e283a4 Should be able to support all format now 2022-07-25 10:51:57 +08:00
tarsin a7f7bf1316 Release 1.8
CI / Build (push) Has been cancelled
Releases / Build (push) Has been cancelled
2022-07-25 10:20:16 +08:00
tarsin a72fca3dfb Fix a lag 2022-07-25 10:16:54 +08:00
tarsin c5e7a6e8f9 Code clean 2022-07-25 10:02:34 +08:00
tarsin 80a20f25b9 Fix animated 2022-07-25 09:28:40 +08:00
tarsin 500163fe1a Merge pull request #132 from Ehviewer-Overhauled/master
Pull master
2022-07-25 07:54:04 +08:00
txyyh 513886eff4 pick gallery_detail_header.xml changes to television (#131)
* Rewrite info TableLayout to ConstraintLayout for television

* Rewrite layout and minor design improve for television
2022-07-25 07:50:58 +08:00
txyyh 0093db9986 Make buttongroup horizontal (#125)
* Use another impl to create space

* Horizontal button
2022-07-25 07:50:41 +08:00
tarsin 50c6111773 Imagedecoder do not support anon pipe, drop it and use memarea 2022-07-24 23:32:03 +08:00
tarsin 3a05649c71 Code clean 2022-07-24 22:34:19 +08:00
tarsin 9d25376e1f Refactor 2022-07-24 21:41:53 +08:00
tarsin 89935b5639 Hard cast 2022-07-24 20:32:39 +08:00
tarsin e095627aee Refactor 2022-07-24 20:20:04 +08:00
tarsin b61e46e956 Clean up 2022-07-24 19:49:34 +08:00
tarsin 76459c5dd2 Initial stupid migrate to ImageDecoder 2022-07-24 19:18:54 +08:00
tarsin 7ad67a3dbd Minsdk 31 2022-07-24 18:24:24 +08:00
tarsin 577b49ac19 Drop image decode native libs 2022-07-24 18:17:37 +08:00
txyyh 182e3a0d40 Rewrite README description (#124) 2022-07-24 17:40:19 +08:00
txyyh 6d140aaa9d Delete FOSSA (#123) 2022-07-24 14:24:31 +08:00
tarsin 17248d3a82 Revert "Use SMAPSHOT version of mdc"
This reverts commit 7a0fab29bc.
2022-07-24 13:07:28 +08:00
txyyh ec80e7a189 Back to corner (#121) 2022-07-24 05:36:07 +08:00
txyyh 493eeadf1b Add note for CI download (#122) 2022-07-24 03:35:28 +08:00
tarsin 7a0fab29bc Use SMAPSHOT version of mdc 2022-07-23 20:41:02 +08:00
hathlife 5f7e40f258 Gallery detail head tv layout (#96)
* Make height of App logo ImageView match parent

* Add TV layout for gallery detail scene.

* Add credits

* Use GPLv3 for all TV layout code
2022-07-23 20:27:42 +08:00
txyyh 0bf6b8d827 Release 1.7.29.1
CI / Build (push) Has been cancelled
Releases / Build (push) Has been cancelled
2022-07-23 17:14:25 +08:00
tarsin 4c6ccab051 Native lib info refactor 2022-07-23 16:25:53 +08:00
tarsin 00377f15b9 Set our own libs 2022-07-23 16:07:23 +08:00
tarsin 96b50eb5aa Add nettle for encrypt 2022-07-23 15:51:43 +08:00
tarsin acdf681d40 Add gnutls libnettle 2022-07-23 14:26:17 +08:00
tarsin b92cf9746b Add .gz .xz .tar .cbz .cbr to supported mimeTypes 2022-07-23 13:28:41 +08:00
txyyh d0dfbaaae0 Rewrite layout and minor design improve (#116)
* Use ConstraintLayout instead

* Specify info Card Text to BodySmall

* Specify thumb shape to none (no corner)
2022-07-23 07:44:33 +08:00
txyyh 0c656e617b Add CI download link (#117)
* Add CI Download link

* Use head in right way
2022-07-23 00:49:03 +08:00
tarsin 148b1a6344 Release 1.7.29.0
CI / Build (push) Has been cancelled
Releases / Build (push) Has been cancelled
2022-07-22 23:12:27 +08:00
tarsin 1fe0b079cd Support auto store archive's passwd 2022-07-22 22:53:44 +08:00
tarsin 87505aa989 Code clean 2022-07-22 22:18:41 +08:00
tarsin 3d7a71647e Initial support for encrypted archive read 2022-07-22 21:28:19 +08:00
txyyh c9d2f698b6 Add MDC-Android to Thanks (#113) 2022-07-22 16:51:52 +08:00
tarsin 233adbddae Drop another spinner 2022-07-22 14:14:29 +08:00
tarsin 6900d8a8c9 Rename .java to .kt 2022-07-22 14:14:29 +08:00
tarsin 640f75d8d9 Finally our data is no need to pass any jByteArray 2022-07-22 10:08:04 +08:00
tarsin 4381eb8c3d Fuck off the buffer 2022-07-22 09:52:35 +08:00
tarsin 3026b16d70 Don't emulate posix read, do mmap instead 2022-07-22 00:13:53 +08:00
txyyh ac3662a469 Release 1.7.28.4
CI / Build (push) Has been cancelled
Releases / Build (push) Has been cancelled
2022-07-22 00:07:31 +08:00
tarsin 912e6c5a5b Fix a miscompare 2022-07-21 22:25:50 +08:00
txyyh 6a73a51f63 Rewrite info TableLayout to ConstraintLayout (#111) 2022-07-21 22:13:07 +08:00
tarsin 768b6bdda7 Don't rewind fd if we can continue read 2022-07-21 22:11:50 +08:00
tarsin 9ea7cd5f7c JNIEnv is shared in the same thread 2022-07-21 21:56:32 +08:00
tarsin a91547ace7 Use in-place decompress 2022-07-21 20:47:55 +08:00
tarsin ed58085b8f Update AndroidManifest.xml
https://developer.android.google.cn/about/versions/13/behavior-changes-13?hl=zh-cn#migration
2022-07-20 21:10:07 +08:00
txyyh 944e267795 Separate different action (#107)
* Rename ci action

* Separate push and pull request action

* Change README CI shield

* Rename artifact to right name
2022-07-20 19:22:59 +08:00
txyyh 325d6df2d3 Add thanks (#106) 2022-07-20 07:57:02 +08:00
txyyh 3ba08bf003 Auto-gen releases discussion (#105) 2022-07-20 07:56:49 +08:00
tarsin 86d00cc0d1 Use mdc's slider 2022-07-19 22:17:40 +08:00
tarsin 4ee518451c Release 1.7.28.3 2022-07-19 19:42:10 +08:00
tarsin 7b167b1612 Check to our xz fork 2022-07-19 19:01:03 +08:00
txyyh 7f3965bb08 Regen repo link in app (#103) 2022-07-19 17:27:00 +08:00
txyyh 49ea156ea8 Regen repo link (#102) 2022-07-19 17:20:02 +08:00
tarsin 90c7f9fd98 Re-checkout upstream 2022-07-19 17:02:22 +08:00
tarsin 1604885596 Optimize size 2022-07-19 16:37:48 +08:00
NekoInverter f0fbf2f46e Open app info when cannot open open by default 2022-07-19 16:37:48 +08:00
txyyh 3ab563e25d Require SoC and device model in bug report template (#101)
* Require device model on bug report

* Require SoC model on bug report
2022-07-19 12:27:54 +08:00
tarsin 1bdb52a096 kotlinize 2022-07-19 12:16:58 +08:00
tarsin 029401288e Rename .java to .kt 2022-07-19 12:16:57 +08:00
tarsin b84daa45c5 kotlinize 2022-07-19 12:02:07 +08:00
tarsin ba3fe869d8 Rename .java to .kt 2022-07-19 12:02:07 +08:00
tarsin 74494848d3 kotlinize 2022-07-19 11:49:51 +08:00
tarsin fa0a72e159 Rename .java to .kt 2022-07-19 11:49:51 +08:00
tarsin 30c408a27f kotlinize 2022-07-19 11:41:49 +08:00
tarsin 4e9a8be076 Rename .java to .kt 2022-07-19 11:41:49 +08:00
tarsin e3a87d44f4 Gallery info card tweaks 2022-07-18 22:15:28 +08:00
tarsin 1387b7acf9 Revert "Update proguard-rules.pro"
This reverts commit 77e5c3a347.
2022-07-18 22:11:22 +08:00
tarsin 3df6fed3ac Only use kotlin 1.7.10 2022-07-18 21:17:36 +08:00
tarsin 30d11e92c6 Regen gradle wrappers 2022-07-18 21:05:13 +08:00
tarsin 77e5c3a347 Update proguard-rules.pro 2022-07-18 21:03:08 +08:00
tarsin 2ddb26b1aa app: Remove unused res 2022-07-18 20:52:52 +08:00
tarsin 191db14398 app: Do code cleanup 2022-07-18 20:47:04 +08:00
tarsin 9dcc24f4d9 Make renovate bot happy 2022-07-18 20:44:00 +08:00
tarsin 05d29f4cfd Update dependencies 2022-07-18 20:19:09 +08:00
tarsin f257c936ba Use fwb's glSurfaceView 2022-07-18 16:57:43 +08:00
tarsin c2d8cf8b47 Use fwb's html parser
* Drop tagsoup as well

* Our minsdk is 26, no need to concern about api compatibility below 24 then
2022-07-18 16:34:10 +08:00
txyyh 7cfbfb5db5 Update screenshots (#97) 2022-07-17 16:53:16 +08:00
tarsin 9d00e47f74 Refactor gallery comment to adapt non-divider recyclerview 2022-07-17 15:55:58 +08:00
tarsin 452b373f62 Don't use dividers
* That's so fucking ugly
2022-07-17 15:33:57 +08:00
tarsin 8fa04644c4 Convert filter add dialog's spinner to dropdownmenu 2022-07-17 15:06:44 +08:00
tarsin 67024486e7 Rename .java to .kt 2022-07-17 15:06:44 +08:00
txyyh e1be24053b Version Bump (#94)
EhViewer / Build (push) Has been cancelled
Releases / Build (push) Has been cancelled
2022-07-17 00:25:33 +08:00
txyyh 5ddf2b0d8f CI for pull request (#95) 2022-07-17 00:25:20 +08:00
tarsin 0802b0b4c6 Drop NekoInverter's email 2022-07-16 07:20:12 +08:00
tarsin 2a709e6190 Make all fragmentPref use fade anim 2022-07-15 13:34:31 +08:00
tarsin 156e4f990d Workaround for preference anim 2022-07-15 13:12:30 +08:00
tarsin 07c1737597 Drop takisoft's preferenceFix 2022-07-15 11:24:53 +08:00
tarsin a3f61ab4ef Remove some workground 2022-07-15 07:43:14 +08:00
tarsin 7aa3eff23f Fix fab size 2022-07-15 07:39:58 +08:00
renovate[bot] a79375843b Update dependency com.google.android.material:material to v1.7.0-alpha03 (#93)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-15 06:53:50 +08:00
renovate[bot] 1b50ce31f5 Update dependency gradle to v7.5 (#91)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-15 06:53:38 +08:00
tarsin 167fe89908 Version Bump
EhViewer / Build (push) Has been cancelled
Releases / Build (push) Has been cancelled
2022-07-14 11:20:29 +08:00
tarsin 892b3ece41 Fix name shadow 2022-07-14 11:19:10 +08:00
tarsin 6dfa056e41 Use polly optimization
* Polly is always exist in ndk
2022-07-14 11:08:39 +08:00
tarsin 1a8afd86be Use llvm's Link Time Optimization 2022-07-14 09:51:47 +08:00
tarsin e0ef18659b We have no cpp things 2022-07-14 09:26:37 +08:00
tarsin 52be469b2d Optimize size 2022-07-14 09:24:47 +08:00
tarsin 6895289ec8 Upgrade to ndk r25 2022-07-14 08:37:57 +08:00
tarsin 3e86cbcf60 Convert a spinner to m3AutoCompleteTextInputLayout 2022-07-13 23:45:12 +08:00
tarsin 536d294f7b Rename .java to .kt 2022-07-13 23:45:12 +08:00
tarsin 8e7888df35 Use rikkax's correct m3Switch temporarily 2022-07-13 21:04:58 +08:00
tarsin 79703ce0dd Make simplemenuPreference md3 2022-07-13 20:56:25 +08:00
tarsin 25aa08d8e4 Use AppcompatSpinner 2022-07-13 20:06:25 +08:00
hathlife 35a77cd03d Add TV layout for initial wizard scenes. (#90)
* fix typo

* Add TV layout for initial setting scenes.

* Add credits
2022-07-13 07:36:55 +08:00
renovate[bot] 1198927d85 Update dependency org.jboss.forge.roaster:roaster-api to v2.26.0.Final (#87)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-11 22:49:38 +08:00
renovate[bot] f1c5e7660e Update dependency org.jboss.forge.roaster:roaster-jdt to v2.26.0.Final (#88)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-11 22:47:47 +08:00
txyyh 0f99403702 Use webp instead 2022-07-11 00:19:38 +08:00
tarsin 10170a274b Only need unlock when process is paused 2022-07-10 22:08:11 +08:00
tarsin 47630e6620 Implement #81 2022-07-10 20:05:13 +08:00
tarsin bb5799b414 Fix #86 2022-07-10 19:23:36 +08:00
renovate[bot] 2110e1b102 Update dependency dev.rikka.rikkax.core:core-ktx to v1.4.1 (#83)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-10 13:10:51 +08:00
hathlife 3dfbd102b4 Add Entry for Android TV launchers (#82) 2022-07-10 10:14:04 +08:00
renovate[bot] 0ba6500625 Update plugin org.jetbrains.kotlin.android to v1.7.10 (#80)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2022-07-08 00:06:29 +08:00
286 changed files with 5397 additions and 13606 deletions

No files matched your search

+17 -6
View File
@@ -58,7 +58,7 @@ body:
attributes:
label: EhViewer CI 版本 / EhViewer CI Version
description: 直接将对应 CI 下载页面网址填入即可 / Simply drop CI download page url here
placeholder: https://github.com/asuka-mio/Ehviewer/actions/runs/XXXXXXXXXX
placeholder: https://github.com/Ehviewer-Overhauled/Ehviewer/actions/runs/XXXXXXXXXX
validations:
required: true
@@ -74,9 +74,20 @@ body:
- type: input
id: device
attributes:
label: 设备信息 / Device info
description: 特殊设备在此填入设备信息 / Put message here if you device is special
placeholder: WSA, arm-v7a
label: 设备型号 / Device model
description: 在此填入设备型号 / Put device model here
placeholder: OnePlus 7 Pro, Xiaomi 12 Ultra
validations:
required: true
- type: input
id: SoC
attributes:
label: SoC 型号 / Soc model
description: 在此填入 SoC 型号 / Put Soc model here
placeholder: 骁龙 8+ Gen 1, Snapdragon 8+ Gen 1
validations:
required: true
- type: textarea
id: log
@@ -98,10 +109,10 @@ body:
- label: 如果您有足够的时间和能力,并愿意为此提交 PR ,请勾上此复选框 / Pull request is welcome. Check this if you want to start a pull request
required: false
- label: 您已仔细查看并知情 [Q&A](https://github.com/asuka-mio/Ehviewer/issues/61) 中的内容 / You have checked [Q&A](https://github.com/asuka-mio/Ehviewer/issues/61) carefully
- label: 您已仔细查看并知情 [Q&A](https://github.com/Ehviewer-Overhauled/Ehviewer/issues/61) 中的内容 / You have checked [Q&A](https://github.com/Ehviewer-Overhauled/Ehviewer/issues/61) carefully
required: true
- label: 您已搜索过 [Issue Tracker](https://github.com/asuka-mio/Ehviewer/issues),没有找到类似内容 / I have searched on [Issue Tracker](https://github.com/asuka-mio/Ehviewer/issues), No duplicate or related open issue has been found
- label: 您已搜索过 [Issue Tracker](https://github.com/Ehviewer-Overhauled/Ehviewer/issues),没有找到类似内容 / I have searched on [Issue Tracker](https://github.com/Ehviewer-Overhauled/Ehviewer/issues), No duplicate or related open issue has been found
required: true
- label: 您确保这个 Issue 只提及一个问题。如果您有多个问题报告,烦请发起多个 Issue / Ensure there is only one bug report in this issue. Please make mutiply issue for mutiply bugs
+1 -1
View File
@@ -1,4 +1,4 @@
contact_links:
- name: 所有其他问题 / All other questions
url: https://github.com/asuka-mio/Ehviewer/discussions
url: https://github.com/Ehviewer-Overhauled/Ehviewer/discussions
about: 转到 Discussions / Turn to discussions
+3 -3
View File
@@ -57,7 +57,7 @@ body:
attributes:
label: EhViewer CI 版本 / EhViewer CI Version
description: 直接将对应 CI 下载页面网址填入即可 / Simply drop CI download page url here
placeholder: https://github.com/asuka-mio/Ehviewer/actions/runs/XXXXXXXXXX
placeholder: https://github.com/Ehviewer-Overhauled/Ehviewer/actions/runs/XXXXXXXXXX
validations:
required: true
@@ -72,10 +72,10 @@ body:
- label: 如果您有足够的时间和能力,并愿意为此提交 PR ,请勾上此复选框 / Pull request is welcome. Check this if you want to start a pull request
required: false
- label: 您已仔细查看并知情 [Q&A](https://github.com/asuka-mio/Ehviewer/issues/61) 中的内容 / You have checked [Q&A](https://github.com/asuka-mio/Ehviewer/issues/61) carefully
- label: 您已仔细查看并知情 [Q&A](https://github.com/Ehviewer-Overhauled/Ehviewer/issues/61) 中的内容 / You have checked [Q&A](https://github.com/Ehviewer-Overhauled/Ehviewer/issues/61) carefully
required: true
- label: 您已搜索过 [Issue Tracker](https://github.com/asuka-mio/Ehviewer/issues),没有找到类似内容 / I have searched on [Issue Tracker](https://github.com/asuka-mio/Ehviewer/issues), No duplicate or related open issue has been found
- label: 您已搜索过 [Issue Tracker](https://github.com/Ehviewer-Overhauled/Ehviewer/issues),没有找到类似内容 / I have searched on [Issue Tracker](https://github.com/Ehviewer-Overhauled/Ehviewer/issues), No duplicate or related open issue has been found
required: true
- label: 您确保这个 Issue 只提及一个功能。如果您有多个功能请求,烦请发起多个 Issue / Ensure there is only one feature request in this issue. Please make mutiply issue for mutiply feature request
@@ -1,6 +1,6 @@
name: EhViewer
name: CI
on: push
on: [push]
jobs:
linux:
@@ -21,7 +21,7 @@ jobs:
- name: Install NDK
run: |
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;24.0.8215888" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.0.8775105" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
- name: Gradle Build
uses: gradle/gradle-build-action@v2
@@ -32,7 +32,7 @@ jobs:
- name: Update ARM64
uses: actions/upload-artifact@v3
with:
name: arm64-${{ github.sha }}
name: arm64-v8a-${{ github.sha }}
path: app/build/outputs/apk/release/app-arm64-v8a-release.apk
- name: Update x86_64
+54
View File
@@ -0,0 +1,54 @@
name: Pull Request Check
on: [pull_request]
jobs:
linux:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Java 11
uses: actions/setup-java@v3
with:
distribution: temurin
java-version: 11
- name: Setup Android SDK Tools
uses: android-actions/setup-android@v2
- name: Install NDK
run: |
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.0.8775105" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
- name: Gradle Build
uses: gradle/gradle-build-action@v2
- run: |
git submodule update --init --recursive
gradle assembleRelease
- name: Update ARM64
uses: actions/upload-artifact@v3
with:
name: arm64-v8a-${{ github.sha }}
path: app/build/outputs/apk/release/app-arm64-v8a-release.apk
- name: Update x86_64
uses: actions/upload-artifact@v3
with:
name: x86_64-${{ github.sha }}
path: app/build/outputs/apk/release/app-x86_64-release.apk
- name: Update ARM32
uses: actions/upload-artifact@v3
with:
name: armeabi-v7a-${{ github.sha }}
path: app/build/outputs/apk/release/app-armeabi-v7a-release.apk
- name: Update x86
uses: actions/upload-artifact@v3
with:
name: x86-${{ github.sha }}
path: app/build/outputs/apk/release/app-x86-release.apk
+2 -1
View File
@@ -24,7 +24,7 @@ jobs:
- name: Install NDK
run: |
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;24.0.8215888" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
echo "y" | sudo ${ANDROID_HOME}/tools/bin/sdkmanager --install "ndk;25.0.8775105" --sdk_root=${ANDROID_SDK_ROOT} &> /dev/null
- name: Gradle Build
uses: gradle/gradle-build-action@v2
@@ -36,6 +36,7 @@ jobs:
uses: softprops/action-gh-release@v0.1.14
with:
body: Bump Version
discussion_category_name: Announcements
files: |
app/build/outputs/apk/release/app-arm64-v8a-release.apk
app/build/outputs/apk/release/app-x86_64-release.apk
+5 -11
View File
@@ -1,15 +1,9 @@
[submodule "native/src/main/jni/image/libjpeg-turbo/libjpeg-turbo"]
path = native/src/main/jni/image/libjpeg-turbo/libjpeg-turbo
url = https://github.com/asuka-mio/libjpeg-turbo
[submodule "native/src/main/jni/image/giflib/giflib"]
path = native/src/main/jni/image/giflib/giflib
url = https://gitlab.com/NekoInverter/giflib.git
[submodule "native/src/main/jni/image/libpng/libpng"]
path = native/src/main/jni/image/libpng/libpng
url = https://gitlab.com/NekoInverter/libpng.git
[submodule "native/src/main/jni/libarchive"]
path = native/src/main/jni/libarchive
url = https://github.com/asuka-mio/libarchive
url = https://github.com/Ehviewer-Overhauled/libarchive
[submodule "native/src/main/jni/liblzma"]
path = native/src/main/jni/liblzma
url = https://git.tukaani.org/xz.git
url = https://github.com/Ehviewer-Overhauled/xz
[submodule "native/src/main/jni/nettle/nettle"]
path = native/src/main/jni/nettle/nettle
url = https://github.com/Ehviewer-Overhauled/nettle
+23 -29
View File
@@ -4,47 +4,46 @@
</h1>
<p align="center">
<a href="https://github.com/asuka-mio/Ehviewer/actions">
<img src="https://img.shields.io/github/workflow/status/asuka-mio/Ehviewer/EhViewer" alt="Github Actions">
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/actions/workflows/ci.yml">
<img src="https://github.com/Ehviewer-Overhauled/Ehviewer/actions/workflows/ci.yml/badge.svg" alt="Github Actions">
</a>
<a href="https://github.com/asuka-mio/Ehviewer/blob/master/LICENSE">
<img src="https://img.shields.io/github/license/asuka-mio/Ehviewer" alt="LICENSE">
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/blob/master/LICENSE">
<img src="https://img.shields.io/github/license/Ehviewer-Overhauled/Ehviewer" alt="LICENSE">
</a>
<a href="https://www.codefactor.io/repository/github/asuka-mio/ehviewer">
<img src="https://www.codefactor.io/repository/github/asuka-mio/ehviewer/badge" alt="CodeFactor">
<a href="https://www.codefactor.io/repository/github/Ehviewer-Overhauled/ehviewer">
<img src="https://www.codefactor.io/repository/github/Ehviewer-Overhauled/ehviewer/badge" alt="CodeFactor">
</a>
<a href="https://github.com/asuka-mio/Ehviewer/releases">
<img src="https://img.shields.io/github/v/release/asuka-mio/ehviewer?include_prereleases" alt="Release">
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/releases">
<img src="https://img.shields.io/github/v/release/Ehviewer-Overhauled/ehviewer?include_prereleases" alt="Release">
</a>
<a href="https://github.com/asuka-mio/Ehviewer/issues">
<img src="https://img.shields.io/github/issues/asuka-mio/ehviewer" alt="Issues">
<a href="https://github.com/Ehviewer-Overhauled/Ehviewer/issues">
<img src="https://img.shields.io/github/issues/Ehviewer-Overhauled/ehviewer" alt="Issues">
</a>
</p>
## Description
这是一个 E-Hentai Android 平台的浏览器
# Description
致力于轻量化与良好性能表现的 EhViewer 分支
已针对性适配 Material Design 3 及其动态取色系统
动态配色仅供 Android 12 及以上平台使用!
EhViewer fork dedicated to lightweight and high-performance
An E-Hentai Application for Android.
with Material Design 3 and Dynamic Color Support
with Material Design 3 and Dynamic Color Support.
# Download
Dynamic Color only support Android S and above!
[Github 下载](https://github.com/Ehviewer-Overhauled/Ehviewer/releases)
## Download
[CI 版本下载(需登录)](https://github.com/Ehviewer-Overhauled/Ehviewer/actions/workflows/ci.yml)
[Github 下载](https://github.com/asuka-mio/Ehviewer/releases)
## Screenshot
# Screenshot
![screenshot-01](art/screenshot-01.png)
![screenshot-02](art/screenshot-02.png)
## Thanks
# Thanks
本项目受到了诸多开源项目的帮助
@@ -52,22 +51,19 @@ Here is the libraries
- [AOSP](http://source.android.com/)
- [android-advancedrecyclerview](https://github.com/h6ah4i/android-advancedrecyclerview)
- [material-components-android](https://github.com/material-components/material-components-android)
- [material-design-icons](https://github.com/google/material-design-icons)
- [Apache Commons Lang](https://commons.apache.org/proper/commons-lang/)
- [apng](http://apng.sourceforge.net/)
- [giflib](http://giflib.sourceforge.net)
- [greenDAO](https://github.com/greenrobot/greenDAO)
- [jsoup](https://github.com/jhy/jsoup)
- [libjpeg-turbo](https://libjpeg-turbo.org/)
- [libpng](http://www.libpng.org/pub/png/libpng.html)
- [okhttp](https://github.com/square/okhttp)
- [roaster](https://github.com/forge/roaster)
- [TapTargetView](https://github.com/KeepSafe/TapTargetView)
- [TagSoup](http://home.ccil.org/~cowan/tagsoup/)
- [libarchive](http://www.libarchive.org/)
- [XZ Utils](https://tukaani.org/xz/)
## License
# License
Copyright 2014-2019 Hippo Seven
Copyright 2020-2022 NekoInverter
@@ -78,5 +74,3 @@ Here is the libraries
EhViewer is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with EhViewer. If not, see <https://www.gnu.org/licenses/>.
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fasuka-mio%2FEhviewer.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fasuka-mio%2FEhviewer?ref=badge_large)
+63 -15
View File
@@ -32,10 +32,10 @@ android {
defaultConfig {
applicationId "moe.tarsin.ehviewer"
minSdkVersion 26
minSdkVersion 31
targetSdkVersion 33
versionCode 114527
versionName "1.7.28.0"
versionCode 180002
versionName "1.8.0.2"
resConfigs 'zh', 'zh-rCN', 'zh-rHK', 'zh-rTW', 'es', 'ja', 'ko', 'fr', 'de', 'th'
}
@@ -92,27 +92,75 @@ tasks.withType(JavaCompile) {
}
dependencies {
implementation 'androidx.activity:activity:1.6.0-alpha05'
implementation 'androidx.appcompat:appcompat:1.6.0-alpha05'
implementation 'androidx.biometric:biometric-ktx:1.2.0-alpha04'
implementation 'androidx.browser:browser:1.4.0'
implementation 'androidx.fragment:fragment:1.5.0'
def activity_version = "1.6.0-alpha05"
implementation "androidx.activity:activity:$activity_version"
implementation "androidx.activity:activity-ktx:$activity_version"
implementation "androidx.annotation:annotation:1.4.0"
implementation "androidx.annotation:annotation-experimental:1.2.0"
implementation "androidx.appcompat:appcompat:1.6.0-alpha05"
def arch_core_version = "2.1.0"
implementation "androidx.arch.core:core-common:$arch_core_version"
implementation "androidx.arch.core:core-runtime:$arch_core_version"
implementation "androidx.biometric:biometric-ktx:1.2.0-alpha04"
implementation "androidx.browser:browser:1.4.0"
def collection_version = "1.3.0-alpha02"
implementation "androidx.collection:collection:$collection_version"
implementation "androidx.collection:collection-ktx:$collection_version"
implementation "androidx.constraintlayout:constraintlayout:2.2.0-alpha03"
implementation "androidx.coordinatorlayout:coordinatorlayout:1.2.0"
def core_version = "1.9.0-alpha05"
implementation "androidx.core:core:$core_version"
implementation "androidx.core:core-ktx:$core_version"
implementation "androidx.customview:customview:1.2.0-alpha01"
implementation "androidx.documentfile:documentfile:1.1.0-alpha01"
implementation "androidx.dynamicanimation:dynamicanimation:1.1.0-alpha03"
def fragment_version = "1.5.1"
implementation "androidx.fragment:fragment:$fragment_version"
implementation "androidx.fragment:fragment-ktx:$fragment_version"
def lifecycle_version = "2.6.0-alpha01"
implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-livedata-core-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"
implementation "androidx.loader:loader:1.1.0"
implementation 'androidx.preference:preference:1.2.0'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'androidx.recyclerview:recyclerview:1.3.0-beta01'
implementation 'androidx.startup:startup-runtime:1.2.0-alpha01'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01'
implementation 'androidx.webkit:webkit:1.4.0'
implementation "androidx.transition:transition-ktx:1.4.1"
implementation "androidx.vectordrawable:vectordrawable:1.2.0-beta01"
implementation "androidx.vectordrawable:vectordrawable-animated:1.1.0"
implementation 'androidx.viewpager:viewpager:1.1.0-alpha01'
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
implementation 'androidx.webkit:webkit:1.5.0-beta01'
implementation 'com.drakeet.drawer:drawer:1.0.3'
implementation 'com.google.android.material:material:1.7.0-alpha02'
implementation 'com.getkeepsafe.taptargetview:taptargetview:1.13.3'
implementation 'com.google.android.material:material:1.7.0-alpha03'
implementation 'com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:1.0.0'
implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.10'
implementation 'com.takisoft.preferencex:preferencex:1.1.0'
implementation 'dev.rikka.rikkax.core:core-ktx:1.4.0'
implementation 'dev.rikka.rikkax.core:core-ktx:1.4.1'
implementation 'dev.rikka.rikkax.insets:insets:1.3.0'
implementation 'dev.rikka.rikkax.layoutinflater:layoutinflater:1.2.0'
implementation 'dev.rikka.rikkax.preference:simplemenu-preference:1.0.3'
implementation 'org.ccil.cowan.tagsoup:tagsoup:1.2.1'
implementation 'dev.rikka.rikkax.material:material-preference:2.0.0'
implementation 'org.greenrobot:greendao:3.3.0'
implementation 'org.jsoup:jsoup:1.15.2'
implementation projects.native
}
configurations.all {
exclude group: 'dev.rikka.rikkax.appcompat', module: 'appcompat'
exclude group: 'dev.rikka.rikkax.material', module: 'material'
}
+31 -30
View File
@@ -16,12 +16,21 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-feature
android:name="android.software.leanback"
android:required="false" />
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- Required to maintain app compatibility. -->
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-feature
android:glEsVersion="0x00020000"
@@ -43,6 +52,7 @@
<activity
android:name="com.hippo.ehviewer.ui.MainActivity"
android:banner="@mipmap/ic_leanback_banner"
android:configChanges="screenSize"
android:exported="true"
android:launchMode="singleTask"
@@ -53,6 +63,11 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
@@ -106,35 +121,21 @@
<data android:mimeType="application/x-7z-compressed" />
<data android:mimeType="application/x-rar-compressed" />
<data android:mimeType="application/x-zip-compressed" />
</intent-filter>
<data android:mimeType="application/x-tar" />
<data android:mimeType="application/x-xz" />
<data android:mimeType="application/gzip" />
<data android:mimeType="application/x-cbz" />
<data android:mimeType="application/x-cbr" />
<data android:mimeType="application/x-cbt" />
<data android:mimeType="application/x-cb7" />
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="file" />
<data android:scheme="content" />
<data android:host="*" />
<data android:pathPattern=".*\\.7z" />
<data android:pathPattern=".*\\.rar" />
<data android:pathPattern=".*\\.zip" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="file" />
<data android:scheme="content" />
<data android:host="*" />
<data android:mimeType="*/*" />
<data android:pathPattern=".*\\.7z" />
<data android:pathPattern=".*\\.rar" />
<data android:pathPattern=".*\\.zip" />
<!-- untested -->
<data android:mimeType="application/vnd.comicbook+zip" />
<data android:mimeType="application/vnd.comicbook-rar" />
<data android:mimeType="application/x-compressed-tar" />
<data android:mimeType="application/x-bzip-compressed-tar" />
<data android:mimeType="application/x-lzma-compressed-tar" />
<data android:mimeType="application/x-xz-compressed-tar" />
</intent-filter>
</activity>
@@ -1,47 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.app;
import android.annotation.SuppressLint;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.CheckBox;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.hippo.ehviewer.R;
public class CheckBoxDialogBuilder extends MaterialAlertDialogBuilder {
private final CheckBox mCheckBox;
@SuppressLint("InflateParams")
public CheckBoxDialogBuilder(Context context, String message, String checkText, boolean checked) {
super(context);
View view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_checkbox_builder, null);
setView(view);
setMessage(message);
mCheckBox = view.findViewById(R.id.checkbox);
mCheckBox.setText(checkText);
mCheckBox.setChecked(checked);
view.setOnClickListener(v -> mCheckBox.toggle());
}
public boolean isChecked() {
return mCheckBox.isChecked();
}
}
@@ -0,0 +1,45 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.app
import android.content.Context
import android.view.LayoutInflater
import android.widget.CheckBox
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.hippo.ehviewer.R
class CheckBoxDialogBuilder constructor(
context: Context,
message: String?,
checkText: String?,
checked: Boolean
) : MaterialAlertDialogBuilder(
context
) {
private val mCheckBox: CheckBox
val isChecked: Boolean
get() = mCheckBox.isChecked
init {
val view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_checkbox_builder, null)
setView(view)
setMessage(message)
mCheckBox = view.findViewById(R.id.checkbox)
mCheckBox.text = checkText
mCheckBox.isChecked = checked
view.setOnClickListener { mCheckBox.toggle() }
}
}
@@ -1,86 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.app;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.DialogInterface;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.textfield.TextInputLayout;
import com.hippo.ehviewer.R;
public class EditTextDialogBuilder extends MaterialAlertDialogBuilder implements EditText.OnEditorActionListener {
private final TextInputLayout mTextInputLayout;
private final EditText mEditText;
private AlertDialog mDialog;
@SuppressLint("InflateParams")
public EditTextDialogBuilder(Context context, String text, String hint) {
super(context);
View view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_edittext_builder, null);
setView(view);
mTextInputLayout = (TextInputLayout) view;
mEditText = view.findViewById(R.id.edit_text);
mEditText.setText(text);
mEditText.setSelection(mEditText.getText().length());
mEditText.setOnEditorActionListener(this);
mTextInputLayout.setHint(hint);
}
public EditText getEditText() {
return mEditText;
}
public String getText() {
return mEditText.getText().toString();
}
public void setError(CharSequence error) {
mTextInputLayout.setError(error);
}
@NonNull
@Override
public AlertDialog create() {
mDialog = super.create();
return mDialog;
}
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (mDialog != null) {
Button button = mDialog.getButton(DialogInterface.BUTTON_POSITIVE);
if (button != null) {
button.performClick();
}
return true;
} else {
return false;
}
}
}
@@ -0,0 +1,72 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.app
import android.content.Context
import android.content.DialogInterface
import android.view.KeyEvent
import android.view.LayoutInflater
import android.widget.EditText
import android.widget.TextView
import android.widget.TextView.OnEditorActionListener
import androidx.appcompat.app.AlertDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.textfield.TextInputLayout
import com.hippo.ehviewer.R
class EditTextDialogBuilder constructor(
context: Context,
text: String?,
hint: String?
) : MaterialAlertDialogBuilder(
context
), OnEditorActionListener {
private val mTextInputLayout: TextInputLayout
val editText: EditText
private var mDialog: AlertDialog? = null
val text: String
get() = editText.text.toString()
fun setError(error: CharSequence?) {
mTextInputLayout.error = error
}
override fun create(): AlertDialog {
mDialog = super.create()
return mDialog as AlertDialog
}
override fun onEditorAction(v: TextView?, p1: Int, event: KeyEvent?): Boolean {
return if (mDialog != null) {
val button = mDialog!!.getButton(DialogInterface.BUTTON_POSITIVE)
button?.performClick()
true
} else {
false
}
}
init {
val view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_edittext_builder, null)
setView(view)
mTextInputLayout = view as TextInputLayout
editText = view.findViewById(R.id.edit_text)
editText.setText(text)
editText.setSelection(editText.text.length)
editText.setOnEditorActionListener(this)
mTextInputLayout.hint = hint
}
}
@@ -1,73 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.app;
import android.annotation.SuppressLint;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.ListView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.hippo.ehviewer.R;
import com.hippo.yorozuya.ViewUtils;
public class ListCheckBoxDialogBuilder extends MaterialAlertDialogBuilder {
private final CheckBox mCheckBox;
private AlertDialog mDialog;
@SuppressLint("InflateParams")
public ListCheckBoxDialogBuilder(Context context, CharSequence[] items,
final OnItemClickListener listener, String checkText, boolean checked) {
super(context);
View view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_list_checkbox_builder, null);
setView(view);
ListView listView = (ListView) ViewUtils.$$(view, R.id.list_view);
mCheckBox = (CheckBox) ViewUtils.$$(view, R.id.checkbox);
listView.setAdapter(new ArrayAdapter<>(getContext(), R.layout.item_select_dialog, items));
listView.setOnItemClickListener((parent, view1, position, id) -> {
if (listener != null) {
listener.onItemClick(ListCheckBoxDialogBuilder.this, mDialog, position);
}
mDialog.dismiss();
});
mCheckBox.setText(checkText);
mCheckBox.setChecked(checked);
}
public boolean isChecked() {
return mCheckBox.isChecked();
}
@NonNull
@Override
public AlertDialog create() {
mDialog = super.create();
return mDialog;
}
public interface OnItemClickListener {
void onItemClick(ListCheckBoxDialogBuilder builder, AlertDialog dialog, int position);
}
}
@@ -0,0 +1,65 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.app
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.CheckBox
import android.widget.ListView
import androidx.appcompat.app.AlertDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.hippo.ehviewer.R
import com.hippo.yorozuya.ViewUtils
class ListCheckBoxDialogBuilder constructor(
context: Context, items: Array<CharSequence>,
listener: OnItemClickListener?, checkText: String?, checked: Boolean
) : MaterialAlertDialogBuilder(
context
) {
private val mCheckBox: CheckBox
private var mDialog: AlertDialog? = null
val isChecked: Boolean
get() = mCheckBox.isChecked
override fun create(): AlertDialog {
mDialog = super.create()
return mDialog as AlertDialog
}
interface OnItemClickListener {
fun onItemClick(builder: ListCheckBoxDialogBuilder?, dialog: AlertDialog?, position: Int)
}
init {
val view =
LayoutInflater.from(getContext()).inflate(R.layout.dialog_list_checkbox_builder, null)
setView(view)
val listView = ViewUtils.`$$`(view, R.id.list_view) as ListView
mCheckBox = ViewUtils.`$$`(view, R.id.checkbox) as CheckBox
listView.adapter = ArrayAdapter(getContext(), R.layout.item_select_dialog, items)
listView.onItemClickListener =
AdapterView.OnItemClickListener { _: AdapterView<*>?, _: View?, position: Int, _: Long ->
listener?.onItemClick(this@ListCheckBoxDialogBuilder, mDialog, position)
mDialog?.dismiss()
}
mCheckBox.text = checkText
mCheckBox.isChecked = checked
}
}
@@ -166,21 +166,7 @@ public final class DiskLruCache implements Closeable {
* a sequence number each time an edit is committed. A snapshot is stale if
* its sequence number is not equal to its entry's sequence number.
*/
private long nextSequenceNumber = 0; private final Callable<Void> cleanupCallable = new Callable<>() {
public Void call() throws Exception {
synchronized (DiskLruCache.this) {
if (journalWriter == null) {
return null; // Closed.
}
trimToSize();
if (journalRebuildRequired()) {
rebuildJournal();
redundantOpCount = 0;
}
}
return null;
}
};
private long nextSequenceNumber = 0;
private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {
this.directory = directory;
@@ -245,7 +231,21 @@ public final class DiskLruCache implements Closeable {
cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
cache.rebuildJournal();
return cache;
}
} private final Callable<Void> cleanupCallable = new Callable<>() {
public Void call() throws Exception {
synchronized (DiskLruCache.this) {
if (journalWriter == null) {
return null; // Closed.
}
trimToSize();
if (journalRebuildRequired()) {
rebuildJournal();
redundantOpCount = 0;
}
}
return null;
}
};
private static void deleteIfExists(File file) throws IOException {
if (file.exists() && !file.delete()) {
@@ -980,4 +980,6 @@ public final class DiskLruCache implements Closeable {
}
}
@@ -1,131 +0,0 @@
/*
* Copyright 2017 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.database;
/*
* Created by Hippo on 2017/9/3.
*/
import android.content.Context;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.SparseArray;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MSQLiteBuilder {
public static final String COLUMN_ID = "_id";
private static final Map<Class, String> JAVA_TYPE_TO_SQLITE_TYPE = new HashMap<>();
static {
JAVA_TYPE_TO_SQLITE_TYPE.put(boolean.class, "INTEGER NOT NULL DEFAULT 0");
JAVA_TYPE_TO_SQLITE_TYPE.put(byte.class, "INTEGER NOT NULL DEFAULT 0");
JAVA_TYPE_TO_SQLITE_TYPE.put(short.class, "INTEGER NOT NULL DEFAULT 0");
JAVA_TYPE_TO_SQLITE_TYPE.put(int.class, "INTEGER NOT NULL DEFAULT 0");
JAVA_TYPE_TO_SQLITE_TYPE.put(long.class, "INTEGER NOT NULL DEFAULT 0");
JAVA_TYPE_TO_SQLITE_TYPE.put(float.class, "REAL NOT NULL DEFAULT 0");
JAVA_TYPE_TO_SQLITE_TYPE.put(double.class, "REAL NOT NULL DEFAULT 0");
JAVA_TYPE_TO_SQLITE_TYPE.put(String.class, "TEXT");
}
private final SparseArray<List<String>> statementsMap = new SparseArray<>();
private int version = 0;
private List<String> statements;
private static String javaTypeToSQLiteType(Class clazz) {
String type = JAVA_TYPE_TO_SQLITE_TYPE.get(clazz);
if (type == null) {
throw new IllegalStateException("Unknown type: " + clazz);
}
return type;
}
/**
* Bump database version.
*/
public MSQLiteBuilder version(int version) {
if (version <= this.version) {
throw new IllegalStateException("New version must be bigger than current version. "
+ "current version: " + this.version + ", new version: " + version + ".");
}
this.version = version;
this.statements = new ArrayList<>();
this.statementsMap.put(version, this.statements);
return this;
}
/**
* Creates a table with int {@link #COLUMN_ID} primary key.
*/
public MSQLiteBuilder createTable(String table) {
return createTable(table, COLUMN_ID, int.class);
}
/**
* Creates a table.
*/
public MSQLiteBuilder createTable(String table, String column, Class clazz) {
return statement("CREATE TABLE " + table + " (" + column + " " + javaTypeToSQLiteType(clazz) + " PRIMARY KEY);");
}
/**
* Drops a table.
*/
public MSQLiteBuilder dropTable(String table) {
return statement("DROP TABLE " + table + ";");
}
/**
* Inserts a column to the table.
*/
public MSQLiteBuilder insertColumn(String table, String column, Class clazz) {
return statement("ALTER TABLE " + table + " ADD COLUMN " + column + " " + javaTypeToSQLiteType(clazz) + ";");
}
/**
* Add a statement.
*/
public MSQLiteBuilder statement(String statement) {
if (version == 0 || statements == null) {
throw new IllegalStateException("Call version() first!");
}
statements.add(statement);
return this;
}
/**
* Build a SQLiteOpenHelper from it.
*/
public SQLiteOpenHelper build(Context context, String name, int version) {
return new MSQLiteOpenHelper(context, name, version, this);
}
List<String> getStatements(int oldVersion, int newVersion) {
List<String> result = new ArrayList<>();
for (int i = oldVersion + 1; i <= newVersion; ++i) {
List<String> list = statementsMap.get(i);
if (list != null) {
result.addAll(list);
}
}
return result;
}
}
@@ -0,0 +1,124 @@
/*
* Copyright 2017 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.database
import android.content.Context
import android.database.sqlite.SQLiteOpenHelper
import android.util.SparseArray
class MSQLiteBuilder {
companion object {
const val COLUMN_ID = "_id"
private val JAVA_TYPE_TO_SQLITE_TYPE: MutableMap<Class<*>?, String> = HashMap()
private fun javaTypeToSQLiteType(clazz: Class<*>?): String {
return JAVA_TYPE_TO_SQLITE_TYPE[clazz]
?: throw IllegalStateException("Unknown type: $clazz")
}
init {
JAVA_TYPE_TO_SQLITE_TYPE[Boolean::class.javaPrimitiveType] =
"INTEGER NOT NULL DEFAULT 0"
JAVA_TYPE_TO_SQLITE_TYPE[Byte::class.javaPrimitiveType] =
"INTEGER NOT NULL DEFAULT 0"
JAVA_TYPE_TO_SQLITE_TYPE[Short::class.javaPrimitiveType] =
"INTEGER NOT NULL DEFAULT 0"
JAVA_TYPE_TO_SQLITE_TYPE[Int::class.javaPrimitiveType] = "INTEGER NOT NULL DEFAULT 0"
JAVA_TYPE_TO_SQLITE_TYPE[Long::class.javaPrimitiveType] =
"INTEGER NOT NULL DEFAULT 0"
JAVA_TYPE_TO_SQLITE_TYPE[Float::class.javaPrimitiveType] =
"REAL NOT NULL DEFAULT 0"
JAVA_TYPE_TO_SQLITE_TYPE[Double::class.javaPrimitiveType] = "REAL NOT NULL DEFAULT 0"
JAVA_TYPE_TO_SQLITE_TYPE[String::class.java] = "TEXT"
}
}
private val statementsMap = SparseArray<List<String>?>()
private var version = 0
private var statements: MutableList<String>? = null
/**
* Bump database version.
*/
fun version(version: Int): MSQLiteBuilder {
check(version > this.version) {
("New version must be bigger than current version. "
+ "current version: " + this.version + ", new version: " + version + ".")
}
this.version = version
statements = ArrayList()
statementsMap.put(version, statements)
return this
}
/**
* Creates a table.
*/
/**
* Creates a table with int [.COLUMN_ID] primary key.
*/
@JvmOverloads
fun createTable(
table: String,
column: String = COLUMN_ID,
clazz: Class<*>? = Int::class.javaPrimitiveType
): MSQLiteBuilder {
return statement("CREATE TABLE " + table + " (" + column + " " + javaTypeToSQLiteType(clazz) + " PRIMARY KEY);")
}
/**
* Drops a table.
*/
fun dropTable(table: String): MSQLiteBuilder {
return statement("DROP TABLE $table;")
}
/**
* Inserts a column to the table.
*/
fun insertColumn(table: String, column: String, clazz: Class<*>?): MSQLiteBuilder {
return statement(
"ALTER TABLE $table ADD COLUMN $column " + javaTypeToSQLiteType(
clazz
) + ";"
)
}
/**
* Add a statement.
*/
fun statement(statement: String): MSQLiteBuilder {
check(!(version == 0 || statements == null)) { "Call version() first!" }
statements!!.add(statement)
return this
}
/**
* Build a SQLiteOpenHelper from it.
*/
fun build(context: Context?, name: String?, version: Int): SQLiteOpenHelper {
return MSQLiteOpenHelper(context, name, version, this)
}
fun getStatements(oldVersion: Int, newVersion: Int): List<String> {
val result: MutableList<String> = ArrayList()
for (i in oldVersion + 1..newVersion) {
val list = statementsMap[i]
if (list != null) {
result.addAll(list)
}
}
return result
}
}
@@ -1,57 +0,0 @@
/*
* Copyright 2017 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.database;
/*
* Created by Hippo on 2017/9/4.
*/
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import com.hippo.util.SqlUtils;
class MSQLiteOpenHelper extends SQLiteOpenHelper {
private final int version;
private final MSQLiteBuilder builder;
public MSQLiteOpenHelper(Context context, String name, int version, MSQLiteBuilder builder) {
super(context, name, null, version);
this.version = version;
this.builder = builder;
}
@Override
public void onCreate(SQLiteDatabase db) {
onUpgrade(db, 0, version);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
for (String command : builder.getStatements(oldVersion, newVersion)) {
db.execSQL(command);
}
}
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
SqlUtils.dropAllTable(db);
onCreate(db);
}
}
@@ -0,0 +1,43 @@
/*
* Copyright 2017 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.database
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import com.hippo.util.SqlUtils
internal class MSQLiteOpenHelper(
context: Context?,
name: String?,
private val version: Int,
private val builder: MSQLiteBuilder
) : SQLiteOpenHelper(context, name, null, version) {
override fun onCreate(db: SQLiteDatabase) {
onUpgrade(db, 0, version)
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
for (command in builder.getStatements(oldVersion, newVersion)) {
db.execSQL(command)
}
}
override fun onDowngrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
SqlUtils.dropAllTable(db)
onCreate(db)
}
}
@@ -1,271 +0,0 @@
/*
* Copyright (C) 2015 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.easyrecyclerview;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.view.View;
import androidx.core.view.ViewCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
// Add overlap
/**
* Only work for {@link androidx.recyclerview.widget.LinearLayoutManager}.<br>
* Show divider between item, just like
* {@link android.widget.ListView#setDivider(android.graphics.drawable.Drawable)}
*/
public class LinearDividerItemDecoration extends RecyclerView.ItemDecoration {
public static final int HORIZONTAL = LinearLayoutManager.HORIZONTAL;
public static final int VERTICAL = LinearLayoutManager.VERTICAL;
private final Rect mRect;
private final Paint mPaint;
private boolean mShowFirstDivider = false;
private boolean mShowLastDivider = false;
private int mOrientation;
private int mThickness;
private int mPaddingStart = 0;
private int mPaddingEnd = 0;
private boolean mOverlap = false;
private ShowDividerHelper mShowDividerHelper;
public LinearDividerItemDecoration(int orientation, int color, int thickness) {
mRect = new Rect();
mPaint = new Paint();
mPaint.setStyle(Paint.Style.FILL);
setOrientation(orientation);
setColor(color);
setThickness(thickness);
}
public void setShowDividerHelper(ShowDividerHelper showDividerHelper) {
mShowDividerHelper = showDividerHelper;
}
public void setOrientation(int orientation) {
if (orientation != HORIZONTAL && orientation != VERTICAL) {
throw new IllegalArgumentException("invalid orientation");
}
mOrientation = orientation;
}
public void setColor(int color) {
mPaint.setColor(color);
}
public void setThickness(int thickness) {
mThickness = thickness;
}
public void setShowFirstDivider(boolean showFirstDivider) {
mShowFirstDivider = showFirstDivider;
}
public void setShowLastDivider(boolean showLastDivider) {
mShowLastDivider = showLastDivider;
}
public void setPadding(int padding) {
setPaddingStart(padding);
setPaddingEnd(padding);
}
public void setPaddingStart(int paddingStart) {
mPaddingStart = paddingStart;
}
public void setPaddingEnd(int paddingEnd) {
mPaddingEnd = paddingEnd;
}
public void setOverlap(boolean overlap) {
mOverlap = overlap;
}
@Override
public void getItemOffsets(Rect outRect, View view,
RecyclerView parent, RecyclerView.State state) {
if (parent.getAdapter() == null) {
outRect.set(0, 0, 0, 0);
return;
}
if (mOverlap) {
outRect.set(0, 0, 0, 0);
return;
}
final int position = parent.getChildLayoutPosition(view);
final int itemCount = parent.getAdapter().getItemCount();
if (mShowDividerHelper != null) {
if (mOrientation == VERTICAL) {
if (position == 0 && mShowDividerHelper.showDivider(0)) {
outRect.top = mThickness;
}
if (mShowDividerHelper.showDivider(position + 1)) {
outRect.bottom = mThickness;
}
} else {
if (position == 0 && mShowDividerHelper.showDivider(0)) {
outRect.left = mThickness;
}
if (mShowDividerHelper.showDivider(position + 1)) {
outRect.right = mThickness;
}
}
} else {
if (mOrientation == VERTICAL) {
if (position == 0 && mShowFirstDivider) {
outRect.top = mThickness;
}
outRect.bottom = mThickness;
if (position == itemCount - 1 && !mShowLastDivider) {
outRect.bottom = 0;
}
} else {
if (position == 0 && mShowFirstDivider) {
outRect.left = mThickness;
}
outRect.right = mThickness;
if (position == itemCount - 1 && !mShowLastDivider) {
outRect.right = 0;
}
}
}
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent,
RecyclerView.State state) {
RecyclerView.Adapter adapter = parent.getAdapter();
if (adapter == null) {
return;
}
int itemCount = adapter.getItemCount();
boolean overlap = mOverlap;
if (mOrientation == VERTICAL) {
final boolean isRtl = ViewCompat.getLayoutDirection(parent) == ViewCompat.LAYOUT_DIRECTION_RTL;
int mPaddingLeft;
int mPaddingRight;
if (isRtl) {
mPaddingLeft = mPaddingEnd;
mPaddingRight = mPaddingStart;
} else {
mPaddingLeft = mPaddingStart;
mPaddingRight = mPaddingEnd;
}
final int left = parent.getPaddingLeft() + mPaddingLeft;
final int right = parent.getWidth() - parent.getPaddingRight() - mPaddingRight;
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
final int position = parent.getChildLayoutPosition(child);
boolean show;
if (mShowDividerHelper != null) {
show = mShowDividerHelper.showDivider(position + 1);
} else {
show = position != itemCount - 1 || mShowLastDivider;
}
if (show) {
int top = child.getBottom() + lp.bottomMargin;
if (overlap) {
top -= mThickness;
}
final int bottom = top + mThickness;
mRect.set(left, top, right, bottom);
c.drawRect(mRect, mPaint);
}
if (position == 0) {
if (mShowDividerHelper != null) {
show = mShowDividerHelper.showDivider(0);
} else {
show = mShowFirstDivider;
}
if (show) {
int bottom = child.getTop() + lp.topMargin;
if (overlap) {
bottom += mThickness;
}
final int top = bottom - mThickness;
mRect.set(left, top, right, bottom);
c.drawRect(mRect, mPaint);
}
}
}
} else {
final int top = parent.getPaddingTop() + mPaddingStart;
final int bottom = parent.getHeight() - parent.getPaddingBottom() - mPaddingEnd;
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
final int position = parent.getChildLayoutPosition(child);
boolean show;
if (mShowDividerHelper != null) {
show = mShowDividerHelper.showDivider(position + 1);
} else {
show = position != itemCount - 1 || mShowLastDivider;
}
if (show) {
int left = child.getRight() + lp.rightMargin;
if (overlap) {
left -= mThickness;
}
final int right = left + mThickness;
mRect.set(left, top, right, bottom);
c.drawRect(mRect, mPaint);
}
if (position == 0) {
if (mShowDividerHelper != null) {
show = mShowDividerHelper.showDivider(0);
} else {
show = mShowFirstDivider;
}
if (show) {
int right = child.getLeft() + lp.leftMargin;
if (overlap) {
right += mThickness;
}
final int left = right - mThickness;
mRect.set(left, top, right, bottom);
c.drawRect(mRect, mPaint);
}
}
}
}
}
public interface ShowDividerHelper {
boolean showDivider(int index);
}
}
@@ -27,6 +27,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.os.Debug;
import android.text.Html;
import android.text.method.LinkMovementMethod;
import android.util.Log;
import android.view.View;
@@ -38,6 +39,9 @@ import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.collection.LruCache;
import androidx.core.os.LocaleListCompat;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ProcessLifecycleOwner;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.hippo.Native;
@@ -60,7 +64,6 @@ import com.hippo.image.Image;
import com.hippo.image.ImageBitmap;
import com.hippo.network.StatusCodeException;
import com.hippo.scene.SceneApplication;
import com.hippo.text.Html;
import com.hippo.unifile.UniFile;
import com.hippo.util.BitmapUtils;
import com.hippo.util.ClipboardUtil;
@@ -104,6 +107,8 @@ public class EhApplication extends SceneApplication {
private static final boolean DEBUG_PRINT_IMAGE_COUNT = false;
private static final long DEBUG_PRINT_INTERVAL = 3000L;
public static boolean locked = false;
private static EhApplication instance;
private final IntIdGenerator mIdGenerator = new IntIdGenerator();
@@ -300,6 +305,7 @@ public class EhApplication extends SceneApplication {
@SuppressLint("StaticFieldLeak")
@Override
public void onCreate() {
ProcessLifecycleOwner.get().getLifecycle().addObserver(new EhlifecycleObserver());
instance = this;
Thread.UncaughtExceptionHandler handler = Thread.getDefaultUncaughtExceptionHandler();
@@ -325,7 +331,6 @@ public class EhApplication extends SceneApplication {
StatusCodeException.initialize(this);
Settings.initialize(this);
ReadableTime.initialize(this);
Html.initialize(this);
AppConfig.initialize(this);
SpiderDen.initialize(this);
EhDB.initialize(this);
@@ -581,4 +586,11 @@ public class EhApplication extends SceneApplication {
return null;
}
}
static class EhlifecycleObserver implements DefaultLifecycleObserver {
@Override
public void onPause(@NonNull LifecycleOwner owner) {
locked = true;
}
}
}
@@ -23,6 +23,7 @@ import com.hippo.conaco.ValueHelper;
import com.hippo.image.ImageBitmap;
import com.hippo.streampipe.InputStreamPipe;
import java.io.FileInputStream;
import java.io.IOException;
public class ImageBitmapHelper implements ValueHelper<ImageBitmap> {
@@ -34,7 +35,8 @@ public class ImageBitmapHelper implements ValueHelper<ImageBitmap> {
public ImageBitmap decode(@NonNull InputStreamPipe isPipe) {
try {
isPipe.obtain();
return ImageBitmap.decode(isPipe.open());
FileInputStream is = (FileInputStream) isPipe.open();
return ImageBitmap.decode(is.getFD());
} catch (OutOfMemoryError | IOException e) {
return null;
} finally {
@@ -41,6 +41,7 @@ import com.hippo.yorozuya.NumberUtils;
import java.io.File;
import java.util.Locale;
import java.util.Set;
public class Settings {
@@ -271,6 +272,11 @@ public class Settings {
private static final int DEFAULT_DOWNLOAD_DELAY = 0;
private static final String KEY_REQUEST_NEWS = "request_news";
private static final boolean DEFAULT_REQUEST_NEWS = true;
private static final String KEY_ARCHIVE_PASSWDS = "archive_passwds";
private static Context sContext;
private static SharedPreferences sSettingsPre;
private static EhConfig sEhConfig;
@@ -384,6 +390,21 @@ public class Settings {
sSettingsPre.edit().putString(key, value).apply();
}
public static Set<String> getStringSet(String key) {
return sSettingsPre.getStringSet(key, null);
}
public static void putStringToStringSet(String key, String value) {
Set<String> set = getStringSet(key);
if (set == null)
set = Set.of(value);
else if (set.contains(value))
return;
else
set.add(value);
sSettingsPre.edit().putStringSet(key, set).apply();
}
public static int getIntFromStr(String key, int defValue) {
try {
return NumberUtils.parseIntSafely(sSettingsPre.getString(key, Integer.toString(defValue)), defValue);
@@ -1139,4 +1160,12 @@ public class Settings {
public static void putClipboardTextHashCode(int value) {
putInt(KEY_CLIPBOARD_TEXT_HASH_CODE, value);
}
public static Set<String> getArchivePasswds() {
return getStringSet(KEY_ARCHIVE_PASSWDS);
}
public static void putPasswdToArchivePasswds(String value) {
putStringToStringSet(KEY_ARCHIVE_PASSWDS, value);
}
}
@@ -18,6 +18,7 @@ package com.hippo.ehviewer.gallery;
import android.content.Context;
import android.net.Uri;
import android.os.Handler;
import android.os.Process;
import androidx.annotation.NonNull;
@@ -26,26 +27,29 @@ import androidx.annotation.Nullable;
import com.hippo.UriArchiveAccessor;
import com.hippo.ehviewer.GetText;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
import com.hippo.glgallery.GalleryPageView;
import com.hippo.image.Image;
import com.hippo.unifile.UniFile;
import com.hippo.yorozuya.thread.PVLock;
import com.hippo.yorozuya.thread.PriorityThread;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.concurrent.atomic.AtomicInteger;
public class ArchiveGalleryProvider extends GalleryProvider2 {
private static final AtomicInteger sIdGenerator = new AtomicInteger();
public static Handler showPasswd;
public static String passwd;
public static PVLock pv = new PVLock(0);
private final UriArchiveAccessor archiveAccessor;
private final Stack<Integer> requests = new Stack<>();
private final AtomicInteger extractingIndex = new AtomicInteger(GalleryPageView.INVALID_INDEX);
private final LinkedHashMap<Integer, InputStream> streams = new LinkedHashMap<>();
private final LinkedHashMap<Integer, Long> streams = new LinkedHashMap<>();
private final AtomicInteger decodingIndex = new AtomicInteger(GalleryPageView.INVALID_INDEX);
private Thread archiveThread;
private Thread decodeThread;
@@ -56,7 +60,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
UriArchiveAccessor archiveAccessor1;
try {
archiveAccessor1 = new UriArchiveAccessor(context, uri);
} catch (FileNotFoundException e) {
} catch (Exception e) {
archiveAccessor1 = null;
e.printStackTrace();
}
@@ -90,6 +94,11 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
decodeThread.interrupt();
decodeThread = null;
}
try {
archiveAccessor.close();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
@@ -159,9 +168,27 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
}
private class ArchiveTask implements Runnable {
private void waitPasswd() throws InterruptedException {
showPasswd.sendEmptyMessage(0);
pv.p();
}
private void notifyError() {
showPasswd.sendEmptyMessage(1);
}
private void notifyDismiss() {
showPasswd.sendEmptyMessage(2);
}
@Override
public void run() {
size = archiveAccessor.open();
try {
size = archiveAccessor.open();
} catch (Exception e) {
e.printStackTrace();
size = 0;
}
if (size <= 0) {
size = STATE_ERROR;
error = GetText.getString(R.string.error_reading_failed);
@@ -171,6 +198,34 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
// Update size and notify changed
notifyDataChanged();
if (archiveAccessor.needPassword()) {
boolean need_request = true;
Set<String> set = Settings.getArchivePasswds();
if (set != null) {
for (String passwd : set) {
if (archiveAccessor.providePassword(passwd)) {
need_request = false;
break;
}
}
}
while (!Thread.currentThread().isInterrupted() && need_request) {
try {
waitPasswd();
} catch (InterruptedException e) {
e.printStackTrace();
return;
}
if (!archiveAccessor.providePassword(passwd))
notifyError();
else {
Settings.putPasswdToArchivePasswds(passwd);
notifyDismiss();
break;
}
}
}
while (!Thread.currentThread().isInterrupted()) {
int index;
synchronized (requests) {
@@ -194,17 +249,18 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
continue;
}
Pipe pipe = new Pipe(4096 * 16);
synchronized (streams) {
if (streams.get(index) != null) {
continue;
}
streams.put(index, pipe.getInputStream());
streams.notify();
}
archiveAccessor.extractTargetIndexToOutputStream(index, pipe.getOutputStream());
long addr = archiveAccessor.extracttoOutputStream(index);
synchronized (streams) {
streams.put(index, addr);
streams.notify();
}
}
}
}
@@ -214,7 +270,7 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
public void run() {
while (!Thread.currentThread().isInterrupted()) {
int index;
InputStream stream;
Long addr;
synchronized (streams) {
if (streams.isEmpty()) {
try {
@@ -226,16 +282,16 @@ public class ArchiveGalleryProvider extends GalleryProvider2 {
continue;
}
Iterator<Map.Entry<Integer, InputStream>> iterator = streams.entrySet().iterator();
Map.Entry<Integer, InputStream> entry = iterator.next();
Iterator<Map.Entry<Integer, Long>> iterator = streams.entrySet().iterator();
Map.Entry<Integer, Long> entry = iterator.next();
iterator.remove();
index = entry.getKey();
stream = entry.getValue();
addr = entry.getValue();
decodingIndex.lazySet(index);
}
try {
Image image = Image.decode(stream, true);
Image image = Image.decodeAddr(addr, true);
if (image != null) {
notifyPageSucceed(index, image);
} else {
@@ -34,6 +34,7 @@ import com.hippo.yorozuya.IOUtils;
import com.hippo.yorozuya.StringUtils;
import com.hippo.yorozuya.thread.PriorityThread;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -258,7 +259,7 @@ public class DirGalleryProvider extends GalleryProvider2 implements Runnable {
InputStream is = null;
try {
is = files[index].openInputStream();
Image image = Image.decode(is, true);
Image image = Image.decode(((FileInputStream) is).getFD(), true);
mDecodingIndex.lazySet(GalleryPageView.INVALID_INDEX);
if (image != null) {
notifyPageSucceed(index, image);
@@ -1,156 +0,0 @@
/*
* Copyright 2019 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.gallery;
import androidx.annotation.NonNull;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
class Pipe {
private final int capacity;
private final byte[] buffer;
private int head = 0;
private int tail = 0;
private boolean full = false;
private boolean inClosed = false;
private boolean outClosed = false;
private final InputStream inputStream = new InputStream() {
@Override
public int read() throws IOException {
synchronized (Pipe.this) {
byte[] bytes = new byte[1];
if (read(bytes, 0, 1) != -1) {
return bytes[0];
} else {
return -1;
}
}
}
@Override
public int read(@NonNull byte[] b, int off, int len) throws IOException {
synchronized (Pipe.this) {
for (; ; ) {
if (inClosed) {
throw new IOException("The InputStream is closed");
}
if (len == 0) {
return 0;
}
if (head == tail && !full) {
if (outClosed) {
// No bytes available and the OutputStream is closed. So it's the end.
return -1;
} else {
// Wait for OutputStream write bytes
try {
Pipe.this.wait();
} catch (InterruptedException e) {
throw new IOException("The thread interrupted", e);
}
}
} else {
int read = Math.min(len, (head < tail ? tail : capacity) - head);
System.arraycopy(buffer, head, b, off, read);
head += read;
if (head == capacity) {
head = 0;
}
full = false;
Pipe.this.notifyAll();
return read;
}
}
}
}
@Override
public void close() {
synchronized (Pipe.this) {
inClosed = true;
Pipe.this.notifyAll();
}
}
};
private final OutputStream outputStream = new OutputStream() {
@Override
public void write(int b) throws IOException {
synchronized (Pipe.this) {
byte[] bytes = new byte[]{(byte) b};
write(bytes, 0, 1);
}
}
@Override
public void write(@NonNull byte[] b, int off, int len) throws IOException {
synchronized (Pipe.this) {
while (len != 0) {
if (head == tail && full) {
// The buffer is full, wait for InputStream read bytes
try {
Pipe.this.wait();
} catch (InterruptedException e) {
throw new IOException("The thread interrupted", e);
}
} else {
int write = Math.min(len, (head <= tail ? capacity : head) - tail);
System.arraycopy(b, off, buffer, tail, write);
off += write;
len -= write;
tail += write;
if (tail == capacity) {
tail = 0;
}
if (head == tail) {
full = true;
}
Pipe.this.notifyAll();
}
}
}
}
@Override
public void close() {
synchronized (Pipe.this) {
outClosed = true;
Pipe.this.notifyAll();
}
}
};
Pipe(int capacity) {
this.capacity = capacity;
this.buffer = new byte[capacity];
}
InputStream getInputStream() {
return inputStream;
}
OutputStream getOutputStream() {
return outputStream;
}
}
@@ -18,6 +18,7 @@ package com.hippo.ehviewer.preference;
import android.annotation.SuppressLint;
import android.content.Context;
import android.text.Html;
import android.util.AttributeSet;
import androidx.appcompat.app.AlertDialog;
@@ -29,7 +30,6 @@ import com.hippo.ehviewer.client.EhUrl;
import com.hippo.ehviewer.ui.SettingsActivity;
import com.hippo.ehviewer.ui.scene.BaseScene;
import com.hippo.preference.MessagePreference;
import com.hippo.text.Html;
import com.hippo.util.ClipboardUtil;
import java.util.LinkedList;
@@ -92,7 +92,7 @@ public class IdentityCookiePreference extends MessagePreference {
message = EhCookieStore.KEY_IPD_MEMBER_ID + ": " + ipbMemberId + "<br>"
+ EhCookieStore.KEY_IPD_PASS_HASH + ": " + ipbPassHash + "<br>"
+ EhCookieStore.KEY_IGNEOUS + ": " + igneous;
setDialogMessage(Html.fromHtml(getContext().getString(R.string.settings_eh_identity_cookies_signed, message)));
setDialogMessage(Html.fromHtml(getContext().getString(R.string.settings_eh_identity_cookies_signed, message), Html.FROM_HTML_MODE_LEGACY));
message = message.replace("<br>", "\n");
} else {
setDialogMessage(getContext().getString(R.string.settings_eh_identity_cookies_tourist));
@@ -1,191 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.preference;
import android.annotation.SuppressLint;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.EditText;
import android.widget.Spinner;
import androidx.appcompat.app.AlertDialog;
import com.google.android.material.textfield.TextInputLayout;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.EhProxySelector;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
import com.hippo.network.InetValidator;
import com.hippo.preference.DialogPreference;
import com.hippo.yorozuya.MathUtils;
import com.hippo.yorozuya.ViewUtils;
public class ProxyPreference extends DialogPreference implements View.OnClickListener {
private Spinner mType;
private TextInputLayout mIpInputLayout;
private EditText mIp;
private TextInputLayout mPortInputLayout;
private EditText mPort;
public ProxyPreference(Context context) {
super(context);
init();
}
public ProxyPreference(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ProxyPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
setDialogLayoutResource(R.layout.preference_dialog_proxy);
updateSummary(Settings.getProxyType(), Settings.getProxyIp(), Settings.getProxyPort());
}
private String getProxyTypeText(Context context, int type) {
String[] array = context.getResources().getStringArray(R.array.proxy_types);
return array[MathUtils.clamp(type, 0, array.length - 1)];
}
private void updateSummary(int type, String ip, int port) {
if ((type == EhProxySelector.TYPE_HTTP || type == EhProxySelector.TYPE_SOCKS)
&& (TextUtils.isEmpty(ip) || !InetValidator.isValidInetPort(port))) {
type = EhProxySelector.TYPE_SYSTEM;
}
if (type == EhProxySelector.TYPE_HTTP || type == EhProxySelector.TYPE_SOCKS) {
Context context = getContext();
setSummary(context.getString(R.string.settings_advanced_proxy_summary_1,
getProxyTypeText(context, type),
ip,
port));
} else {
Context context = getContext();
setSummary(context.getString(R.string.settings_advanced_proxy_summary_2,
getProxyTypeText(context, type)));
}
}
@Override
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
super.onPrepareDialogBuilder(builder);
builder.setPositiveButton(android.R.string.ok, null);
}
@Override
@SuppressLint("SetTextI18n")
protected void onDialogCreated(AlertDialog dialog) {
super.onDialogCreated(dialog);
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(this);
mType = (Spinner) ViewUtils.$$(dialog, R.id.type);
mIpInputLayout = (TextInputLayout) ViewUtils.$$(dialog, R.id.ip_input_layout);
mIp = (EditText) ViewUtils.$$(dialog, R.id.ip);
mPortInputLayout = (TextInputLayout) ViewUtils.$$(dialog, R.id.port_input_layout);
mPort = (EditText) ViewUtils.$$(dialog, R.id.port);
int type = Settings.getProxyType();
String[] array = getContext().getResources().getStringArray(R.array.proxy_types);
mType.setSelection(MathUtils.clamp(type, 0, array.length));
mIp.setText(Settings.getProxyIp());
String portString;
int port = Settings.getProxyPort();
if (!InetValidator.isValidInetPort(port)) {
portString = null;
} else {
portString = Integer.toString(port);
}
mPort.setText(portString);
}
@Override
protected void onDialogClosed(boolean positiveResult) {
super.onDialogClosed(positiveResult);
mType = null;
mIpInputLayout = null;
mIp = null;
mPortInputLayout = null;
mPort = null;
}
@Override
public void onClick(View v) {
Dialog dialog = getDialog();
Context context = getContext();
if (null == dialog || null == context || null == mType ||
null == mIpInputLayout || null == mIp ||
null == mPortInputLayout || null == mPort) {
return;
}
int type = mType.getSelectedItemPosition();
String ip = mIp.getText().toString().trim();
if (ip.isEmpty()) {
if (type == EhProxySelector.TYPE_HTTP || type == EhProxySelector.TYPE_SOCKS) {
mIpInputLayout.setError(context.getString(R.string.text_is_empty));
return;
}
}
mIpInputLayout.setError(null);
int port;
String portString = mPort.getText().toString().trim();
if (portString.isEmpty()) {
if (type == EhProxySelector.TYPE_HTTP || type == EhProxySelector.TYPE_SOCKS) {
mPortInputLayout.setError(context.getString(R.string.text_is_empty));
return;
} else {
port = -1;
}
} else {
try {
port = Integer.parseInt(portString);
} catch (NumberFormatException e) {
port = -1;
}
if (!InetValidator.isValidInetPort(port)) {
mPortInputLayout.setError(context.getString(R.string.proxy_invalid_port));
return;
}
}
mPortInputLayout.setError(null);
Settings.putProxyType(type);
Settings.putProxyIp(ip);
Settings.putProxyPort(port);
updateSummary(type, ip, port);
EhApplication.getEhProxySelector(getContext()).updateProxy();
dialog.dismiss();
}
}
@@ -0,0 +1,167 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.preference
import android.annotation.SuppressLint
import android.content.Context
import android.content.DialogInterface
import android.text.TextUtils
import android.util.AttributeSet
import android.view.View
import android.widget.AutoCompleteTextView
import android.widget.EditText
import androidx.appcompat.app.AlertDialog
import com.google.android.material.textfield.TextInputLayout
import com.hippo.ehviewer.EhApplication
import com.hippo.ehviewer.EhProxySelector
import com.hippo.ehviewer.R
import com.hippo.ehviewer.Settings
import com.hippo.network.InetValidator
import com.hippo.preference.DialogPreference
import com.hippo.yorozuya.MathUtils
import com.hippo.yorozuya.ViewUtils
class ProxyPreference @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : DialogPreference(context, attrs), View.OnClickListener {
private var mType: TextInputLayout? = null
private var mIpInputLayout: TextInputLayout? = null
private var mIp: EditText? = null
private var mPortInputLayout: TextInputLayout? = null
private var mPort: EditText? = null
private val mArray: Array<String>
init {
mArray = context.resources.getStringArray(R.array.proxy_types)
dialogLayoutResource = R.layout.preference_dialog_proxy
updateSummary(Settings.getProxyType(), Settings.getProxyIp(), Settings.getProxyPort())
}
private fun getProxyTypeText(type: Int): String {
return mArray[MathUtils.clamp(type, 0, mArray.size - 1)]
}
private fun updateSummary(type: Int, ip: String?, port: Int) {
var type1 = type
if ((type1 == EhProxySelector.TYPE_HTTP || type1 == EhProxySelector.TYPE_SOCKS)
&& (TextUtils.isEmpty(ip) || !InetValidator.isValidInetPort(port))
) {
type1 = EhProxySelector.TYPE_SYSTEM
}
summary = if (type1 == EhProxySelector.TYPE_HTTP || type1 == EhProxySelector.TYPE_SOCKS) {
val context = context
context.getString(
R.string.settings_advanced_proxy_summary_1,
getProxyTypeText(type1),
ip,
port
)
} else {
val context = context
context.getString(
R.string.settings_advanced_proxy_summary_2,
getProxyTypeText(type1)
)
}
}
override fun onPrepareDialogBuilder(builder: AlertDialog.Builder) {
super.onPrepareDialogBuilder(builder)
builder.setPositiveButton(android.R.string.ok, null)
}
@SuppressLint("SetTextI18n")
override fun onDialogCreated(dialog: AlertDialog) {
super.onDialogCreated(dialog)
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(this)
mType = ViewUtils.`$$`(dialog, R.id.type) as TextInputLayout
val array = context.resources.getStringArray(R.array.proxy_types)
mIpInputLayout = ViewUtils.`$$`(dialog, R.id.ip_input_layout) as TextInputLayout
mIp = ViewUtils.`$$`(dialog, R.id.ip) as EditText
mPortInputLayout = ViewUtils.`$$`(dialog, R.id.port_input_layout) as TextInputLayout
mPort = ViewUtils.`$$`(dialog, R.id.port) as EditText
val type = Settings.getProxyType()
(mType!!.editText as AutoCompleteTextView).setText(
array[MathUtils.clamp(
type,
0,
array.size
)], false
)
mIp!!.setText(Settings.getProxyIp())
val portString: String?
val port = Settings.getProxyPort()
portString = if (!InetValidator.isValidInetPort(port)) {
null
} else {
port.toString()
}
mPort!!.setText(portString)
}
override fun onDialogClosed(positiveResult: Boolean) {
super.onDialogClosed(positiveResult)
mType = null
mIpInputLayout = null
mIp = null
mPortInputLayout = null
mPort = null
}
override fun onClick(v: View) {
val dialog = dialog
val context: Context = context
if (null == dialog || null == mType || null == mIpInputLayout || null == mIp || null == mPortInputLayout || null == mPort) {
return
}
val type = mArray.indexOf(mType!!.editText!!.text.toString())
val ip = mIp!!.text.toString().trim { it <= ' ' }
if (ip.isEmpty()) {
if (type == EhProxySelector.TYPE_HTTP || type == EhProxySelector.TYPE_SOCKS) {
mIpInputLayout!!.error = context.getString(R.string.text_is_empty)
return
}
}
mIpInputLayout!!.error = null
val port: Int
val portString = mPort!!.text.toString().trim { it <= ' ' }
if (portString.isEmpty()) {
if (type == EhProxySelector.TYPE_HTTP || type == EhProxySelector.TYPE_SOCKS) {
mPortInputLayout!!.error = context.getString(R.string.text_is_empty)
return
} else {
port = -1
}
} else {
port = try {
portString.toInt()
} catch (e: NumberFormatException) {
-1
}
if (!InetValidator.isValidInetPort(port)) {
mPortInputLayout!!.error = context.getString(R.string.proxy_invalid_port)
return
}
}
mPortInputLayout!!.error = null
Settings.putProxyType(type)
Settings.putProxyIp(ip)
Settings.putProxyPort(port)
updateSummary(type, ip, port)
EhApplication.getEhProxySelector(getContext()).updateProxy()
dialog.dismiss()
}
}
@@ -41,6 +41,27 @@ class VersionPreference @JvmOverloads constructor(
override fun onPrepareDialogBuilder(builder: AlertDialog.Builder) {
super.onPrepareDialogBuilder(builder)
builder.setTitle(R.string.show_library_version)
builder.setMessage(Native.getlibarchiveVersion() + "\nlzma(xz-utils) " + Native.getliblzmaVersion() + "\nzlib " + Native.getzlibVersion() + "\nlibjpeg-turbo " + Native.getlibjpeg_turboVersion())
val info = NativeLibInfo().addLib(Native.getlibarchiveVersion())
.addLib("lzma(xz-utils)", Native.getliblzmaVersion())
.addLib("zlib", Native.getzlibVersion())
.addLib(Native.getnettleVersion())
builder.setMessage(info.getMessage())
}
inner class NativeLibInfo {
private var message: String = ""
fun addLib(libname: String, libversion: String): NativeLibInfo {
message = "$message$libname $libversion\n"
return this
}
fun getMessage(): String {
return message;
}
fun addLib(msg: String): NativeLibInfo {
message = "$message$msg\n"
return this
}
}
}
@@ -66,6 +66,7 @@ import com.hippo.yorozuya.thread.PriorityThread;
import com.hippo.yorozuya.thread.PriorityThreadFactory;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -1096,33 +1097,6 @@ public final class SpiderQueen implements Runnable {
void onGetImageFailure(int index, String error);
}
private static class AutoCloseInputStream extends InputStream {
private final InputStreamPipe mPipe;
private final InputStream mIs;
public AutoCloseInputStream(InputStreamPipe pipe, InputStream is) {
mPipe = pipe;
mIs = is;
}
@Override
public int read() throws IOException {
return mIs.read();
}
@Override
public int read(@NonNull byte[] buffer, int byteOffset, int byteCount) throws IOException {
return mIs.read(buffer, byteOffset, byteCount);
}
@Override
public void close() throws IOException {
mPipe.close();
mPipe.release();
}
}
private class SpiderWorker implements Runnable {
private final long mGid;
@@ -1693,7 +1667,7 @@ public final class SpiderQueen implements Runnable {
pipe.obtain();
try {
is = new AutoCloseInputStream(pipe, pipe.open());
is = pipe.open();
} catch (IOException e) {
// Can't open pipe
error = GetText.getString(R.string.error_reading_failed);
@@ -1703,7 +1677,17 @@ public final class SpiderQueen implements Runnable {
}
if (is != null) {
image = Image.decode(is, true);
try {
image = Image.decode(((FileInputStream) is).getFD(), true);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (image == null) {
error = GetText.getString(R.string.error_decoding_failed);
}
@@ -37,6 +37,8 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.view.KeyEvent;
@@ -67,6 +69,7 @@ import androidx.core.view.WindowInsetsControllerCompat;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.materialswitch.MaterialSwitch;
import com.hippo.app.EditTextDialogBuilder;
import com.hippo.ehviewer.AppConfig;
import com.hippo.ehviewer.BuildConfig;
import com.hippo.ehviewer.R;
@@ -103,6 +106,7 @@ import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
import rikka.core.res.ConfigurationKt;
import rikka.core.res.ResourcesKt;
@@ -222,6 +226,9 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
private int mSize;
private int mCurrentIndex;
private int mSavingPage = -1;
private EditTextDialogBuilder builder;
private boolean dialogShown = false;
private AlertDialog dialog;
private void buildProvider() {
if (mGalleryProvider != null) {
@@ -283,7 +290,16 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
}
outState.putInt(KEY_PAGE, mPage);
outState.putInt(KEY_CURRENT_INDEX, mCurrentIndex);
}
} ActivityResultLauncher<String> requestStoragePermissionLauncher = registerForActivityResult(
new ActivityResultContracts.RequestPermission(),
result -> {
if (result && mSavingPage != -1) {
saveImage(mSavingPage);
} else {
Toast.makeText(this, R.string.error_cant_save_image, Toast.LENGTH_SHORT).show();
}
mSavingPage = -1;
});
@Override
protected void attachBaseContext(@NonNull Context newBase) {
@@ -296,16 +312,7 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
break;
}
super.attachBaseContext(newBase);
} ActivityResultLauncher<String> requestStoragePermissionLauncher = registerForActivityResult(
new ActivityResultContracts.RequestPermission(),
result -> {
if (result && mSavingPage != -1) {
saveImage(mSavingPage);
} else {
Toast.makeText(this, R.string.error_cant_save_image, Toast.LENGTH_SHORT).show();
}
mSavingPage = -1;
});
}
@Override
@SuppressWarnings({"WrongConstant"})
@@ -323,10 +330,19 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
onRestore(savedInstanceState);
}
builder = new EditTextDialogBuilder(this, null, getString(R.string.archive_passwd));
builder.setTitle(getString(R.string.archive_need_passwd));
builder.setPositiveButton(getString(android.R.string.ok), null);
dialog = builder.create();
dialog.setCanceledOnTouchOutside(false);
if (mGalleryProvider == null) {
finish();
return;
}
ArchiveGalleryProvider.showPasswd = new ShowPasswdDialogHandler(this);
mGalleryProvider.start();
// Get start page
@@ -1033,6 +1049,66 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
}
}
private void showPasswdDialog() {
if (!dialogShown) {
dialogShown = true;
dialog.show();
if (dialog.getButton(AlertDialog.BUTTON_POSITIVE) != null) {
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
GalleryActivity.this.onProvidePasswd();
});
}
dialog.setOnCancelListener(v -> finish());
}
}
private void onProvidePasswd() {
String passwd = builder.getText();
if (passwd.isEmpty())
builder.setError(getString(R.string.passwd_cannot_be_empty));
else {
ArchiveGalleryProvider.passwd = passwd;
ArchiveGalleryProvider.pv.v();
}
}
private void onPasswdWrong() {
builder.setError(getString(R.string.passwd_wrong));
}
private void onPasswdCorrect() {
dialog.dismiss();
}
private static class ShowPasswdDialogHandler extends Handler {
//弱引用持有HandlerActivity , GC 回收时会被回收掉
private final WeakReference<GalleryActivity> weakReference;
public ShowPasswdDialogHandler(GalleryActivity activity) {
this.weakReference = new WeakReference(activity);
}
@Override
public void handleMessage(Message msg) {
GalleryActivity activity = weakReference.get();
super.handleMessage(msg);
if (null != activity) {
switch (msg.what) {
case 0:
activity.showPasswdDialog();
break;
case 1:
activity.onPasswdWrong();
break;
case 2:
activity.onPasswdCorrect();
break;
}
}
}
}
private class GalleryMenuHelper implements DialogInterface.OnClickListener {
private final View mView;
@@ -1294,6 +1370,4 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
}
}
@@ -47,6 +47,7 @@ import com.google.android.material.navigation.NavigationView;
import com.google.android.material.snackbar.Snackbar;
import com.hippo.app.EditTextDialogBuilder;
import com.hippo.ehviewer.AppConfig;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
import com.hippo.ehviewer.client.EhTagDatabase;
@@ -360,9 +361,15 @@ public final class MainActivity extends StageActivity
.setTitle(R.string.app_link_not_verified_title)
.setMessage(R.string.app_link_not_verified_message)
.setPositiveButton(R.string.open_settings, (dialogInterface, i) -> {
Intent intent = new Intent(android.provider.Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS,
Uri.parse("package:" + getPackageName()));
startActivity(intent);
try {
Intent intent = new Intent(android.provider.Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS,
Uri.parse("package:" + getPackageName()));
startActivity(intent);
} catch (Throwable t) {
Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.parse("package:" + getPackageName()));
startActivity(intent);
}
})
.setNegativeButton(android.R.string.cancel, null)
.setNeutralButton(R.string.dont_show_again, (dialogInterface, i) -> Settings.putAppLinkVerifyTip(true))
@@ -420,6 +427,9 @@ public final class MainActivity extends StageActivity
@Override
protected void onResume() {
if (Settings.getSecurity() && SecurityScene.Companion.isAuthenticationSupported(this) && EhApplication.locked) {
startScene(new Announcer(SecurityScene.class), true);
}
super.onResume();
setNavCheckedItem(mNavCheckedItem);
checkClipboardUrl();
@@ -21,6 +21,7 @@ import android.view.MenuItem;
import androidx.annotation.StringRes;
import androidx.appcompat.app.ActionBar;
import androidx.fragment.app.FragmentTransaction;
import com.google.android.material.snackbar.Snackbar;
import com.hippo.ehviewer.R;
@@ -41,6 +42,7 @@ public final class SettingsActivity extends EhActivity {
if (savedInstanceState == null) {
getSupportFragmentManager()
.beginTransaction()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_MATCH_ACTIVITY_OPEN)
.replace(R.id.fragment, new SettingsFragment())
.commitAllowingStateLoss();
}
@@ -17,12 +17,12 @@
package com.hippo.ehviewer.ui.fragment;
import android.os.Bundle;
import android.text.Html;
import androidx.annotation.Nullable;
import androidx.preference.Preference;
import com.hippo.ehviewer.R;
import com.hippo.text.Html;
public class AboutFragment extends BasePreferenceFragment {
@@ -30,10 +30,10 @@ public class AboutFragment extends BasePreferenceFragment {
private static final String KEY_CHECK_FOR_UPDATES = "check_for_updates";
@Override
public void onCreatePreferencesFix(@Nullable Bundle savedInstanceState, String rootKey) {
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.about_settings);
Preference author = findPreference(KEY_AUTHOR);
author.setSummary(Html.fromHtml(getString(R.string.settings_about_author_summary).replace('$', '@')));
author.setSummary(Html.fromHtml(getString(R.string.settings_about_author_summary).replace('$', '@'), Html.FROM_HTML_MODE_LEGACY));
}
@Override
@@ -22,7 +22,6 @@ import android.content.res.Resources;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
@@ -53,7 +52,6 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Locale;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@@ -223,7 +221,7 @@ public class AdvancedFragment extends BasePreferenceFragment {
});
@Override
public void onCreatePreferencesFix(@Nullable Bundle savedInstanceState, String rootKey) {
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.advanced_settings);
Preference dumpLogcat = findPreference(KEY_DUMP_LOGCAT);
@@ -279,9 +277,15 @@ public class AdvancedFragment extends BasePreferenceFragment {
}
return true;
} else if (KEY_OPEN_BY_DEFAULT.equals(key)) {
Intent intent = new Intent(Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS,
Uri.parse("package:" + requireContext().getPackageName()));
startActivity(intent);
try {
Intent intent = new Intent(android.provider.Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS,
Uri.parse("package:" + requireContext().getPackageName()));
startActivity(intent);
} catch (Throwable t) {
Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.parse("package:" + requireContext().getPackageName()));
startActivity(intent);
}
return true;
}
return false;
@@ -22,10 +22,13 @@ import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.ui.SettingsActivity;
import com.takisoft.preferencex.PreferenceFragmentCompat;
public class BasePreferenceFragment extends PreferenceFragmentCompat
@@ -52,10 +55,26 @@ public class BasePreferenceFragment extends PreferenceFragmentCompat
}
@Override
public void onCreatePreferencesFix(@Nullable Bundle savedInstanceState, String rootKey) {
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
}
@Override
public boolean onPreferenceTreeClick(Preference preference) {
String key = preference.getKey();
try {
Fragment classObj = (Fragment) Class.forName(key).newInstance();
requireActivity().getSupportFragmentManager()
.beginTransaction()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
.replace(R.id.fragment, classObj)
.addToBackStack(null)
.commitAllowingStateLoss();
} catch (ClassNotFoundException | IllegalAccessException | java.lang.InstantiationException ignored) {
}
return true;
}
private void setTitle(@StringRes int string) {
requireActivity().setTitle(string);
}
@@ -59,7 +59,7 @@ public class DownloadFragment extends BasePreferenceFragment {
});
@Override
public void onCreatePreferencesFix(@Nullable Bundle savedInstanceState, String rootKey) {
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.download_settings);
Preference mediaScan = findPreference(Settings.KEY_MEDIA_SCAN);
@@ -31,7 +31,7 @@ import com.hippo.ehviewer.client.EhTagDatabase;
public class EhFragment extends BasePreferenceFragment {
@Override
public void onCreatePreferencesFix(@Nullable Bundle savedInstanceState, String rootKey) {
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.eh_settings);
Preference theme = findPreference(Settings.KEY_THEME);
@@ -1,483 +0,0 @@
/*
* Copyright 2022 Tarsin Norbin
*
* This file is part of EhViewer
*
* EhViewer is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* EhViewer is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with EhViewer.
* If not, see <https://www.gnu.org/licenses/>.
*/
package com.hippo.ehviewer.ui.fragment;
import android.content.DialogInterface;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Spinner;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.checkbox.MaterialCheckBox;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.textfield.TextInputLayout;
import com.hippo.easyrecyclerview.EasyRecyclerView;
import com.hippo.easyrecyclerview.LinearDividerItemDecoration;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.client.EhFilter;
import com.hippo.ehviewer.dao.Filter;
import com.hippo.view.ViewTransition;
import com.hippo.yorozuya.LayoutUtils;
import com.hippo.yorozuya.ViewUtils;
import java.util.List;
import rikka.core.res.ResourcesKt;
public class FilterFragment extends BaseFragment {
@Nullable
private ViewTransition mViewTransition;
@Nullable
private FilterAdapter mAdapter;
@Nullable
private FilterList mFilterList;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.activity_filter, container, false);
mFilterList = new FilterList();
RecyclerView recyclerView = (EasyRecyclerView) ViewUtils.$$(view, R.id.recycler_view);
TextView tip = (TextView) ViewUtils.$$(view, R.id.tip);
mViewTransition = new ViewTransition(recyclerView, tip);
FloatingActionButton fab = view.findViewById(R.id.fab);
Drawable drawable = ContextCompat.getDrawable(requireActivity(), R.drawable.big_filter);
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
tip.setCompoundDrawables(null, drawable, null, null);
mAdapter = new FilterAdapter();
mAdapter.setHasStableIds(true);
recyclerView.setAdapter(mAdapter);
recyclerView.setClipToPadding(false);
recyclerView.setClipChildren(false);
LinearDividerItemDecoration decoration = new LinearDividerItemDecoration(
LinearDividerItemDecoration.VERTICAL,
ResourcesKt.resolveColor(requireActivity().getTheme(), R.attr.dividerColor),
LayoutUtils.dp2pix(requireActivity(), 1));
decoration.setShowLastDivider(true);
recyclerView.addItemDecoration(decoration);
recyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
recyclerView.setHasFixedSize(true);
DefaultItemAnimator defaultItemAnimator = (DefaultItemAnimator) recyclerView.getItemAnimator();
if (defaultItemAnimator != null) {
defaultItemAnimator.setSupportsChangeAnimations(false);
}
fab.setOnClickListener(v -> showAddFilterDialog());
return view;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
updateView(false);
}
private void updateView(boolean animation) {
if (null == mViewTransition) {
return;
}
if (null == mFilterList || 0 == mFilterList.size()) {
mViewTransition.showView(1, animation);
} else {
mViewTransition.showView(0, animation);
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
mViewTransition = null;
mAdapter = null;
mFilterList = null;
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.activity_filter, menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int itemId = item.getItemId();
if (itemId == R.id.action_tip) {
showTipDialog();
return true;
}
return super.onOptionsItemSelected(item);
}
private void showTipDialog() {
new MaterialAlertDialogBuilder(requireActivity())
.setTitle(R.string.filter)
.setMessage(R.string.filter_tip)
.setPositiveButton(android.R.string.ok, null)
.show();
}
private void showAddFilterDialog() {
AlertDialog dialog = new MaterialAlertDialogBuilder(requireActivity())
.setTitle(R.string.add_filter)
.setView(R.layout.dialog_add_filter)
.setPositiveButton(R.string.add, null)
.setNegativeButton(android.R.string.cancel, null)
.show();
AddFilterDialogHelper helper = new AddFilterDialogHelper();
helper.setDialog(dialog);
}
private void showDeleteFilterDialog(final Filter filter) {
String message = getString(R.string.delete_filter, filter.text);
new MaterialAlertDialogBuilder(requireActivity())
.setMessage(message)
.setPositiveButton(R.string.delete, (dialog, which) -> {
if (DialogInterface.BUTTON_POSITIVE != which || null == mFilterList) {
return;
}
mFilterList.delete(filter);
if (null != mAdapter) {
mAdapter.notifyDataSetChanged();
}
updateView(true);
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
@Override
public int getFragmentTitle() {
return R.string.filter;
}
private static class FilterHolder extends RecyclerView.ViewHolder {
private final MaterialCheckBox checkbox;
private final TextView text;
private final ImageView delete;
public FilterHolder(View itemView) {
super(itemView);
checkbox = itemView.findViewById(R.id.checkbox);
text = itemView.findViewById(R.id.text);
delete = itemView.findViewById(R.id.delete);
}
}
private class AddFilterDialogHelper implements View.OnClickListener {
@Nullable
private AlertDialog mDialog;
@Nullable
private Spinner mSpinner;
@Nullable
private TextInputLayout mInputLayout;
@Nullable
private EditText mEditText;
public void setDialog(AlertDialog dialog) {
mDialog = dialog;
mSpinner = (Spinner) ViewUtils.$$(dialog, R.id.spinner);
mInputLayout = (TextInputLayout) ViewUtils.$$(dialog, R.id.text_input_layout);
mEditText = mInputLayout.getEditText();
View button = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
if (null != button) {
button.setOnClickListener(this);
}
}
@Override
public void onClick(View v) {
if (null == mFilterList || null == mDialog || null == mSpinner ||
null == mInputLayout || null == mEditText) {
return;
}
String text = mEditText.getText().toString().trim();
if (TextUtils.isEmpty(text)) {
mInputLayout.setError(getString(R.string.text_is_empty));
return;
} else {
mInputLayout.setError(null);
}
int mode = mSpinner.getSelectedItemPosition();
Filter filter = new Filter();
filter.mode = mode;
filter.text = text;
if (!mFilterList.add(filter)) {
mInputLayout.setError(getString(R.string.label_text_exist));
return;
} else {
mInputLayout.setError(null);
}
if (null != mAdapter) {
mAdapter.notifyDataSetChanged();
}
updateView(true);
mDialog.dismiss();
mDialog = null;
mSpinner = null;
mInputLayout = null;
mEditText = null;
}
}
private class FilterAdapter extends RecyclerView.Adapter<FilterHolder> {
private static final int TYPE_ITEM = 0;
private static final int TYPE_HEADER = 1;
@Override
public int getItemViewType(int position) {
if (null == mFilterList) {
return TYPE_ITEM;
}
if (mFilterList.get(position).mode == FilterList.MODE_HEADER) {
return TYPE_HEADER;
} else {
return TYPE_ITEM;
}
}
@NonNull
@Override
public FilterHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
int layoutId;
switch (viewType) {
default:
case TYPE_ITEM:
layoutId = R.layout.item_filter;
break;
case TYPE_HEADER:
layoutId = R.layout.item_filter_header;
break;
}
return new FilterHolder(getLayoutInflater().inflate(layoutId, parent, false));
}
@Override
public void onBindViewHolder(@NonNull FilterHolder holder, int position) {
if (null == mFilterList) {
return;
}
Filter filter = mFilterList.get(position);
if (FilterList.MODE_HEADER == filter.mode) {
holder.text.setText(filter.text);
} else {
holder.checkbox.setText(filter.text);
holder.checkbox.setChecked(filter.enable);
holder.itemView.setOnClickListener(v -> {
mFilterList.trigger(filter);
//for updating delete line on filter text
if (mAdapter != null) {
mAdapter.notifyItemChanged(position);
}
});
holder.delete.setOnClickListener(v -> showDeleteFilterDialog(filter));
}
}
@Override
public int getItemCount() {
return null != mFilterList ? mFilterList.size() : 0;
}
@Override
public long getItemId(int position) {
if (mFilterList == null) {
return 0;
} else {
Filter filter = mFilterList.get(position);
if (filter.getId() != null) {
return (filter.text.hashCode() >> filter.mode) + filter.getId();
}
return filter.text.hashCode() >> filter.mode;
}
}
}
private class FilterList {
public static final int MODE_HEADER = -1;
private final EhFilter mEhFilter;
private final List<Filter> mTitleFilterList;
private final List<Filter> mUploaderFilterList;
private final List<Filter> mTagFilterList;
private final List<Filter> mTagNamespaceFilterList;
private Filter mTitleHeader;
private Filter mUploaderHeader;
private Filter mTagHeader;
private Filter mTagNamespaceHeader;
public FilterList() {
mEhFilter = EhFilter.getInstance();
mTitleFilterList = mEhFilter.getTitleFilterList();
mUploaderFilterList = mEhFilter.getUploaderFilterList();
mTagFilterList = mEhFilter.getTagFilterList();
mTagNamespaceFilterList = mEhFilter.getTagNamespaceFilterList();
}
public int size() {
int count = 0;
int size = mTitleFilterList.size();
count += 0 == size ? 0 : size + 1;
size = mUploaderFilterList.size();
count += 0 == size ? 0 : size + 1;
size = mTagFilterList.size();
count += 0 == size ? 0 : size + 1;
size = mTagNamespaceFilterList.size();
count += 0 == size ? 0 : size + 1;
return count;
}
private Filter getTitleHeader() {
if (null == mTitleHeader) {
mTitleHeader = new Filter();
mTitleHeader.mode = MODE_HEADER;
mTitleHeader.text = getString(R.string.filter_title);
}
return mTitleHeader;
}
private Filter getUploaderHeader() {
if (null == mUploaderHeader) {
mUploaderHeader = new Filter();
mUploaderHeader.mode = MODE_HEADER;
mUploaderHeader.text = getString(R.string.filter_uploader);
}
return mUploaderHeader;
}
private Filter getTagHeader() {
if (null == mTagHeader) {
mTagHeader = new Filter();
mTagHeader.mode = MODE_HEADER;
mTagHeader.text = getString(R.string.filter_tag);
}
return mTagHeader;
}
private Filter getTagNamespaceHeader() {
if (null == mTagNamespaceHeader) {
mTagNamespaceHeader = new Filter();
mTagNamespaceHeader.mode = MODE_HEADER;
mTagNamespaceHeader.text = getString(R.string.filter_tag_namespace);
}
return mTagNamespaceHeader;
}
public Filter get(int index) {
int size = mTitleFilterList.size();
if (0 != size) {
if (index == 0) {
return getTitleHeader();
} else if (index <= size) {
return mTitleFilterList.get(index - 1);
} else {
index -= size + 1;
}
}
size = mUploaderFilterList.size();
if (0 != size) {
if (index == 0) {
return getUploaderHeader();
} else if (index <= size) {
return mUploaderFilterList.get(index - 1);
} else {
index -= size + 1;
}
}
size = mTagFilterList.size();
if (0 != size) {
if (index == 0) {
return getTagHeader();
} else if (index <= size) {
return mTagFilterList.get(index - 1);
} else {
index -= size + 1;
}
}
size = mTagNamespaceFilterList.size();
if (0 != size) {
if (index == 0) {
return getTagNamespaceHeader();
} else if (index <= size) {
return mTagNamespaceFilterList.get(index - 1);
}
}
throw new IndexOutOfBoundsException();
}
public boolean add(Filter filter) {
return mEhFilter.addFilter(filter);
}
public void delete(Filter filter) {
mEhFilter.deleteFilter(filter);
}
public void trigger(Filter filter) {
mEhFilter.triggerFilter(filter);
}
}
}
@@ -0,0 +1,382 @@
/*
* Copyright 2022 Tarsin Norbin
*
* This file is part of EhViewer
*
* EhViewer is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* EhViewer is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with EhViewer.
* If not, see <https://www.gnu.org/licenses/>.
*/
package com.hippo.ehviewer.ui.fragment
import android.content.DialogInterface
import android.os.Bundle
import android.text.TextUtils
import android.view.*
import android.widget.EditText
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.core.view.MenuProvider
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.checkbox.MaterialCheckBox
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.textfield.TextInputLayout
import com.hippo.easyrecyclerview.EasyRecyclerView
import com.hippo.ehviewer.R
import com.hippo.ehviewer.client.EhFilter
import com.hippo.ehviewer.dao.Filter
import com.hippo.view.ViewTransition
import com.hippo.yorozuya.ViewUtils
class FilterFragment : BaseFragment() {
private var mViewTransition: ViewTransition? = null
private var mAdapter: FilterAdapter = FilterAdapter()
private var mFilterList: FilterList = FilterList()
private val mMenuProvider: MenuProvider = FilterMenuProvider()
inner class FilterMenuProvider : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.activity_filter, menu)
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
val itemId = menuItem.itemId
if (itemId == R.id.action_tip) {
showTipDialog()
return true
}
return false
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.activity_filter, container, false)
val recyclerView: RecyclerView =
ViewUtils.`$$`(view, R.id.recycler_view) as EasyRecyclerView
val tip = ViewUtils.`$$`(view, R.id.tip) as TextView
mViewTransition = ViewTransition(recyclerView, tip)
val fab = view.findViewById<FloatingActionButton>(R.id.fab)
val drawable = ContextCompat.getDrawable(requireActivity(), R.drawable.big_filter)
drawable!!.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
tip.setCompoundDrawables(null, drawable, null, null)
mAdapter.setHasStableIds(true)
recyclerView.adapter = mAdapter
recyclerView.clipToPadding = false
recyclerView.clipChildren = false
recyclerView.layoutManager = LinearLayoutManager(requireContext())
recyclerView.setHasFixedSize(true)
val defaultItemAnimator = recyclerView.itemAnimator as DefaultItemAnimator?
defaultItemAnimator?.supportsChangeAnimations = false
fab.setOnClickListener { showAddFilterDialog() }
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
updateView(false)
requireActivity().addMenuProvider(mMenuProvider)
}
private fun updateView(animation: Boolean) {
if (null == mViewTransition) {
return
}
if (0 == mFilterList.size()) {
mViewTransition!!.showView(1, animation)
} else {
mViewTransition!!.showView(0, animation)
}
}
override fun onDestroyView() {
super.onDestroyView()
mViewTransition = null
requireActivity().removeMenuProvider(mMenuProvider)
}
private fun showTipDialog() {
MaterialAlertDialogBuilder(requireActivity())
.setTitle(R.string.filter)
.setMessage(R.string.filter_tip)
.setPositiveButton(android.R.string.ok, null)
.show()
}
private fun showAddFilterDialog() {
val dialog = MaterialAlertDialogBuilder(requireActivity())
.setTitle(R.string.add_filter)
.setView(R.layout.dialog_add_filter)
.setPositiveButton(R.string.add, null)
.setNegativeButton(android.R.string.cancel, null)
.show()
AddFilterDialogHelper(dialog)
}
private fun showDeleteFilterDialog(filter: Filter) {
val message = getString(R.string.delete_filter, filter.text)
MaterialAlertDialogBuilder(requireActivity())
.setMessage(message)
.setPositiveButton(R.string.delete) { _: DialogInterface?, which: Int ->
if (DialogInterface.BUTTON_POSITIVE != which) {
return@setPositiveButton
}
mFilterList.delete(filter)
mAdapter.notifyDataSetChanged()
updateView(true)
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
override fun getFragmentTitle(): Int {
return R.string.filter
}
private class FilterHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val checkbox: MaterialCheckBox?
val text: TextView?
val delete: ImageView?
init {
checkbox = itemView.findViewById(R.id.checkbox)
text = itemView.findViewById(R.id.text)
delete = itemView.findViewById(R.id.delete)
}
}
private inner class AddFilterDialogHelper(dialog: AlertDialog) : View.OnClickListener {
private var mDialog: AlertDialog
private var mSpinner: TextInputLayout
private var mInputLayout: TextInputLayout
private var mEditText: EditText
private val mArray: Array<String> =
context!!.resources.getStringArray(R.array.filter_entries)
init {
mDialog = dialog
mSpinner = ViewUtils.`$$`(dialog, R.id.spinner) as TextInputLayout
mInputLayout = ViewUtils.`$$`(dialog, R.id.text_input_layout) as TextInputLayout
mEditText = mInputLayout.editText!!
val button: View? = dialog.getButton(DialogInterface.BUTTON_POSITIVE)
button?.setOnClickListener(this)
}
override fun onClick(v: View) {
val text1 = mSpinner.editText!!.text.toString()
if (TextUtils.isEmpty(text1)) {
mSpinner.error = getString(R.string.text_is_empty)
return
} else {
mSpinner.error = null
}
val text = mEditText.text.toString().trim { it <= ' ' }
if (TextUtils.isEmpty(text)) {
mInputLayout.error = getString(R.string.text_is_empty)
return
} else {
mInputLayout.error = null
}
val mode = mArray.indexOf(text1)
val filter = Filter()
filter.mode = mode
filter.text = text
if (!mFilterList.add(filter)) {
mInputLayout.error = getString(R.string.label_text_exist)
return
} else {
mInputLayout.error = null
}
mAdapter.notifyDataSetChanged()
updateView(true)
mDialog.dismiss()
}
}
private inner class FilterAdapter : RecyclerView.Adapter<FilterHolder>() {
override fun getItemViewType(position: Int): Int {
return if (mFilterList[position].mode == MODE_HEADER) {
TYPE_HEADER
} else {
TYPE_ITEM
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FilterHolder {
val layoutId: Int = when (viewType) {
TYPE_ITEM -> R.layout.item_filter
TYPE_HEADER -> R.layout.item_filter_header
else -> R.layout.item_filter
}
return FilterHolder(layoutInflater.inflate(layoutId, parent, false))
}
override fun onBindViewHolder(holder: FilterHolder, position: Int) {
val filter = mFilterList[position]
if (MODE_HEADER == filter.mode) {
holder.text?.text = filter.text
} else {
holder.checkbox?.text = filter.text
holder.checkbox?.isChecked = filter.enable
holder.itemView.setOnClickListener {
mFilterList.trigger(filter)
//for updating delete line on filter text
mAdapter.notifyItemChanged(position)
}
holder.delete?.setOnClickListener { showDeleteFilterDialog(filter) }
}
}
override fun getItemCount(): Int {
return mFilterList.size()
}
override fun getItemId(position: Int): Long {
return run {
val filter = mFilterList[position]
if (filter.id != null) {
(filter.text.hashCode() shr filter.mode) + filter.id
} else (filter.text.hashCode() shr filter.mode).toLong()
}
}
}
private inner class FilterList {
private val mEhFilter: EhFilter = EhFilter.getInstance()
private val mTitleFilterList: List<Filter> = mEhFilter.titleFilterList
private val mUploaderFilterList: List<Filter> = mEhFilter.uploaderFilterList
private val mTagFilterList: List<Filter> = mEhFilter.tagFilterList
private val mTagNamespaceFilterList: List<Filter> = mEhFilter.tagNamespaceFilterList
private var mTitleHeader: Filter? = null
private var mUploaderHeader: Filter? = null
private var mTagHeader: Filter? = null
private var mTagNamespaceHeader: Filter? = null
fun size(): Int {
var count = 0
var size = mTitleFilterList.size
count += if (0 == size) 0 else size + 1
size = mUploaderFilterList.size
count += if (0 == size) 0 else size + 1
size = mTagFilterList.size
count += if (0 == size) 0 else size + 1
size = mTagNamespaceFilterList.size
count += if (0 == size) 0 else size + 1
return count
}
private val titleHeader: Filter
get() {
if (null == mTitleHeader) {
mTitleHeader = Filter()
mTitleHeader!!.mode = MODE_HEADER
mTitleHeader!!.text = getString(R.string.filter_title)
}
return mTitleHeader!!
}
private val uploaderHeader: Filter
get() {
if (null == mUploaderHeader) {
mUploaderHeader = Filter()
mUploaderHeader!!.mode = MODE_HEADER
mUploaderHeader!!.text = getString(R.string.filter_uploader)
}
return mUploaderHeader!!
}
private val tagHeader: Filter
get() {
if (null == mTagHeader) {
mTagHeader = Filter()
mTagHeader!!.mode = MODE_HEADER
mTagHeader!!.text = getString(R.string.filter_tag)
}
return mTagHeader!!
}
private val tagNamespaceHeader: Filter
get() {
if (null == mTagNamespaceHeader) {
mTagNamespaceHeader = Filter()
mTagNamespaceHeader!!.mode = MODE_HEADER
mTagNamespaceHeader!!.text = getString(R.string.filter_tag_namespace)
}
return mTagNamespaceHeader!!
}
operator fun get(index: Int): Filter {
var index1 = index
var size = mTitleFilterList.size
if (0 != size) {
index1 -= if (index1 == 0) {
return titleHeader
} else if (index1 <= size) {
return mTitleFilterList[index1 - 1]
} else {
size + 1
}
}
size = mUploaderFilterList.size
if (0 != size) {
index1 -= if (index1 == 0) {
return uploaderHeader
} else if (index1 <= size) {
return mUploaderFilterList[index1 - 1]
} else {
size + 1
}
}
size = mTagFilterList.size
if (0 != size) {
index1 -= if (index1 == 0) {
return tagHeader
} else if (index1 <= size) {
return mTagFilterList[index1 - 1]
} else {
size + 1
}
}
size = mTagNamespaceFilterList.size
if (0 != size) {
if (index1 == 0) {
return tagNamespaceHeader
} else if (index1 <= size) {
return mTagNamespaceFilterList[index1 - 1]
}
}
throw IndexOutOfBoundsException()
}
fun add(filter: Filter?): Boolean {
return mEhFilter.addFilter(filter)
}
fun delete(filter: Filter?) {
mEhFilter.deleteFilter(filter)
}
fun trigger(filter: Filter?) {
mEhFilter.triggerFilter(filter)
}
}
companion object {
private const val MODE_HEADER = -1
private const val TYPE_ITEM = 0
private const val TYPE_HEADER = 1
}
}
@@ -37,19 +37,15 @@ import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.textfield.TextInputLayout;
import com.hippo.easyrecyclerview.LinearDividerItemDecoration;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.Hosts;
import com.hippo.ehviewer.R;
import com.hippo.view.ViewTransition;
import com.hippo.yorozuya.LayoutUtils;
import com.hippo.yorozuya.ViewUtils;
import java.util.List;
import java.util.Locale;
import rikka.core.res.ResourcesKt;
public class HostsFragment extends BaseFragment
implements View.OnClickListener {
@@ -85,12 +81,6 @@ public class HostsFragment extends BaseFragment
adapter = new HostsAdapter();
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(new LinearLayoutManager(requireActivity(), RecyclerView.VERTICAL, false));
LinearDividerItemDecoration decoration = new LinearDividerItemDecoration(
LinearDividerItemDecoration.VERTICAL,
ResourcesKt.resolveColor(requireActivity().getTheme(), R.attr.dividerColor),
LayoutUtils.dp2pix(requireActivity(), 1));
decoration.setShowLastDivider(true);
recyclerView.addItemDecoration(decoration);
recyclerView.setHasFixedSize(true);
fab.setOnClickListener(this);
@@ -28,7 +28,7 @@ import com.hippo.ehviewer.ui.scene.SecurityScene.Companion.isAuthenticationSuppo
* Created by Mo10 on 2018/2/10.
*/
class PrivacyFragment : BasePreferenceFragment() {
override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.privacy_settings)
}
@@ -25,7 +25,7 @@ import com.hippo.ehviewer.R;
public class ReadFragment extends BasePreferenceFragment {
@Override
public void onCreatePreferencesFix(@Nullable Bundle savedInstanceState, String rootKey) {
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.read_settings);
}
@@ -26,7 +26,7 @@ import com.hippo.ehviewer.R;
public class SettingsFragment extends BasePreferenceFragment {
@Override
public void onCreatePreferencesFix(@Nullable Bundle savedInstanceState, String rootKey) {
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.settings_headers);
}
@@ -55,8 +55,6 @@ import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.SimpleItemAnimator;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import com.getkeepsafe.taptargetview.TapTarget;
import com.getkeepsafe.taptargetview.TapTargetView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.h6ah4i.android.widget.advrecyclerview.animator.DraggableItemAnimator;
@@ -72,7 +70,6 @@ import com.hippo.conaco.ProgressNotifier;
import com.hippo.easyrecyclerview.EasyRecyclerView;
import com.hippo.easyrecyclerview.FastScroller;
import com.hippo.easyrecyclerview.HandlerDrawable;
import com.hippo.easyrecyclerview.LinearDividerItemDecoration;
import com.hippo.easyrecyclerview.MarginItemDecoration;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.EhDB;
@@ -391,90 +388,9 @@ public class DownloadsScene extends ToolbarScene
updateView();
guide();
return view;
}
private void guide() {
if (Settings.getGuideDownloadThumb() && null != mRecyclerView) {
mRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (Settings.getGuideDownloadThumb()) {
guideDownloadThumb();
}
if (null != mRecyclerView) {
ViewUtils.removeOnGlobalLayoutListener(mRecyclerView.getViewTreeObserver(), this);
}
}
});
} else {
guideDownloadLabels();
}
}
private void guideDownloadThumb() {
MainActivity activity = getMainActivity();
if (null == activity || !Settings.getGuideDownloadThumb() || null == mLayoutManager || null == mRecyclerView) {
guideDownloadLabels();
return;
}
int position = mLayoutManager.findFirstCompletelyVisibleItemPositions(null)[0];
if (position < 0) {
guideDownloadLabels();
return;
}
RecyclerView.ViewHolder holder = mRecyclerView.findViewHolderForAdapterPosition(position);
if (null == holder) {
guideDownloadLabels();
return;
}
TapTargetView.showFor(requireActivity(),
TapTarget.forView(((DownloadHolder) holder).thumb,
getString(R.string.guide_download_thumb_title),
getString(R.string.guide_download_thumb_text))
.transparentTarget(true),
new TapTargetView.Listener() {
@Override
public void onTargetDismissed(TapTargetView view, boolean userInitiated) {
super.onTargetDismissed(view, userInitiated);
Settings.putGuideDownloadThumb(false);
guideDownloadLabels();
}
});
}
private void guideDownloadLabels() {
MainActivity activity = getMainActivity();
if (null == activity || !Settings.getGuideDownloadLabels()) {
return;
}
Display display = activity.getWindowManager().getDefaultDisplay();
Point point = new Point();
display.getSize(point);
Rect bounds = new Rect(point.x + LayoutUtils.dp2pix(requireContext(), 20),
point.y / 3 + LayoutUtils.dp2pix(requireContext(), 20),
point.x - LayoutUtils.dp2pix(requireContext(), 20),
point.y / 3 - LayoutUtils.dp2pix(requireContext(), 20));
TapTargetView.showFor(requireActivity(),
TapTarget.forBounds(bounds,
getString(R.string.guide_download_labels_title),
getString(R.string.guide_download_labels_text))
.transparentTarget(true),
new TapTargetView.Listener() {
@Override
public void onTargetDismissed(TapTargetView view, boolean userInitiated) {
super.onTargetClick(view);
Settings.puttGuideDownloadLabels(false);
openDrawer(Gravity.RIGHT);
}
});
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
@@ -648,12 +564,6 @@ public class DownloadsScene extends ToolbarScene
mLabelAdapter = new DownloadLabelAdapter(inflater);
final EasyRecyclerView recyclerView = view.findViewById(R.id.recycler_view_drawer);
recyclerView.setLayoutManager(new LinearLayoutManager(context));
LinearDividerItemDecoration decoration = new LinearDividerItemDecoration(
LinearDividerItemDecoration.VERTICAL,
ResourcesKt.resolveColor(getTheme(), R.attr.dividerColor),
LayoutUtils.dp2pix(context, 1));
decoration.setShowLastDivider(true);
recyclerView.addItemDecoration(decoration);
mLabelAdapter.setHasStableIds(true);
final GeneralItemAnimator animator = new DraggableItemAnimator();
recyclerView.setItemAnimator(animator);
@@ -45,8 +45,6 @@ import androidx.drawerlayout.widget.DrawerLayout;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.getkeepsafe.taptargetview.TapTarget;
import com.getkeepsafe.taptargetview.TapTargetView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.hippo.annotation.Implemented;
@@ -55,7 +53,6 @@ import com.hippo.drawable.AddDeleteDrawable;
import com.hippo.drawable.DrawerArrowDrawable;
import com.hippo.easyrecyclerview.EasyRecyclerView;
import com.hippo.easyrecyclerview.FastScroller;
import com.hippo.easyrecyclerview.LinearDividerItemDecoration;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.EhDB;
import com.hippo.ehviewer.R;
@@ -312,40 +309,9 @@ public class FavoritesScene extends BaseScene implements
mHelper.firstRefresh();
}
guideCollections();
return view;
}
private void guideCollections() {
Activity activity = getMainActivity();
if (null == activity || !Settings.getGuideCollections()) {
return;
}
Display display = activity.getWindowManager().getDefaultDisplay();
Point point = new Point();
display.getSize(point);
Rect bounds = new Rect(point.x + LayoutUtils.dp2pix(requireContext(), 20),
point.y / 3 + LayoutUtils.dp2pix(requireContext(), 20),
point.x - LayoutUtils.dp2pix(requireContext(), 20),
point.y / 3 - LayoutUtils.dp2pix(requireContext(), 20));
TapTargetView.showFor(requireActivity(),
TapTarget.forBounds(bounds,
getString(R.string.guide_collections_title),
getString(R.string.guide_collections_text))
.transparentTarget(true),
new TapTargetView.Listener() {
@Override
public void onTargetDismissed(TapTargetView view, boolean userInitiated) {
super.onTargetClick(view);
Settings.putGuideCollections(false);
openDrawer(Gravity.RIGHT);
}
});
}
// keyword of mUrlBuilder, fav cat of mUrlBuilder, mFavCatArray.
// They changed, call it
private void updateSearchBar() {
@@ -446,12 +412,6 @@ public class FavoritesScene extends BaseScene implements
EasyRecyclerView recyclerView = view.findViewById(R.id.recycler_view_drawer);
recyclerView.setLayoutManager(new LinearLayoutManager(context));
LinearDividerItemDecoration decoration = new LinearDividerItemDecoration(
LinearDividerItemDecoration.VERTICAL,
ResourcesKt.resolveColor(getTheme(), R.attr.dividerColor),
LayoutUtils.dp2pix(context, 1));
decoration.setShowLastDivider(true);
recyclerView.addItemDecoration(decoration);
mDrawerAdapter = new FavDrawerAdapter(inflater);
recyclerView.setAdapter(mDrawerAdapter);
@@ -24,9 +24,11 @@ import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Looper;
import android.text.Html;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.ClickableSpan;
import android.text.style.ForegroundColorSpan;
@@ -55,7 +57,6 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.hippo.easyrecyclerview.EasyRecyclerView;
import com.hippo.easyrecyclerview.LinearDividerItemDecoration;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.UrlOpener;
@@ -70,7 +71,6 @@ import com.hippo.ehviewer.client.data.ListUrlBuilder;
import com.hippo.ehviewer.client.parser.VoteCommentParser;
import com.hippo.ehviewer.ui.MainActivity;
import com.hippo.scene.SceneFragment;
import com.hippo.text.Html;
import com.hippo.text.URLImageGetter;
import com.hippo.util.ClipboardUtil;
import com.hippo.util.ExceptionUtils;
@@ -82,8 +82,6 @@ import com.hippo.widget.LinkifyTextView;
import com.hippo.widget.ObservedTextView;
import com.hippo.yorozuya.AnimationUtils;
import com.hippo.yorozuya.AssertUtils;
import com.hippo.yorozuya.LayoutUtils;
import com.hippo.yorozuya.ResourcesUtils;
import com.hippo.yorozuya.SimpleAnimatorListener;
import com.hippo.yorozuya.StringUtils;
import com.hippo.yorozuya.ViewUtils;
@@ -209,11 +207,6 @@ public final class GalleryCommentsScene extends ToolbarScene
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setLayoutManager(new LinearLayoutManager(context,
RecyclerView.VERTICAL, false));
LinearDividerItemDecoration decoration = new LinearDividerItemDecoration(
LinearDividerItemDecoration.VERTICAL, ResourcesKt.resolveColor(getTheme(), R.attr.dividerColor),
LayoutUtils.dp2pix(context, 1));
decoration.setShowLastDivider(true);
mRecyclerView.addItemDecoration(decoration);
mRecyclerView.setHasFixedSize(true);
// Cancel change animator
RecyclerView.ItemAnimator itemAnimator = mRecyclerView.getItemAnimator();
@@ -327,11 +320,6 @@ public final class GalleryCommentsScene extends ToolbarScene
}
});
rv.setLayoutManager(new LinearLayoutManager(context));
LinearDividerItemDecoration decoration = new LinearDividerItemDecoration(
LinearDividerItemDecoration.VERTICAL, ResourcesKt.resolveColor(getTheme(), R.attr.dividerColor),
LayoutUtils.dp2pix(context, 1));
decoration.setPadding(ResourcesUtils.getAttrDimensionPixelOffset(context, androidx.appcompat.R.attr.dialogPreferredPadding));
rv.addItemDecoration(decoration);
rv.setClipToPadding(false);
builder.setView(rv).show();
}
@@ -869,9 +857,11 @@ public final class GalleryCommentsScene extends ToolbarScene
}
private CharSequence generateComment(Context context, ObservedTextView textView, GalleryComment comment) {
SpannableStringBuilder ssb = Html.fromHtml(comment.comment, new URLImageGetter(textView,
Spanned sp = Html.fromHtml(comment.comment, Html.FROM_HTML_MODE_LEGACY, new URLImageGetter(textView,
EhApplication.getConaco(context)), null);
SpannableStringBuilder ssb = new SpannableStringBuilder(sp);
if (0 != comment.id && 0 != comment.score) {
int score = comment.score;
String scoreString = score > 0 ? "+" + score : Integer.toString(score);
@@ -34,6 +34,7 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.text.Html;
import android.text.TextUtils;
import android.util.Pair;
import android.view.LayoutInflater;
@@ -100,7 +101,6 @@ import com.hippo.ehviewer.ui.annotation.WholeLifeCircle;
import com.hippo.ehviewer.widget.GalleryRatingBar;
import com.hippo.scene.Announcer;
import com.hippo.scene.SceneFragment;
import com.hippo.text.Html;
import com.hippo.text.URLImageGetter;
import com.hippo.util.AppHelper;
import com.hippo.util.ClipboardUtil;
@@ -1063,7 +1063,7 @@ public class GalleryDetailScene extends ToolbarScene implements View.OnClickList
time.setText(ReadableTime.getTimeAgo(comment.time));
ObservedTextView c = v.findViewById(R.id.comment);
c.setMaxLines(5);
c.setText(Html.fromHtml(comment.comment,
c.setText(Html.fromHtml(comment.comment, Html.FROM_HTML_MODE_LEGACY,
new URLImageGetter(c, EhApplication.getConaco(context)), null));
v.setBackgroundColor(Color.TRANSPARENT);
}
@@ -29,7 +29,6 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.hippo.easyrecyclerview.EasyRecyclerView;
import com.hippo.easyrecyclerview.LinearDividerItemDecoration;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
import com.hippo.ehviewer.UrlOpener;
@@ -38,13 +37,10 @@ import com.hippo.ehviewer.client.EhUtils;
import com.hippo.ehviewer.client.data.GalleryDetail;
import com.hippo.util.ClipboardUtil;
import com.hippo.yorozuya.AssertUtils;
import com.hippo.yorozuya.LayoutUtils;
import com.hippo.yorozuya.ViewUtils;
import java.util.ArrayList;
import rikka.core.res.ResourcesKt;
public final class GalleryInfoScene extends ToolbarScene {
public static final String KEY_GALLERY_DETAIL = "gallery_detail";
@@ -164,13 +160,6 @@ public final class GalleryInfoScene extends ToolbarScene {
InfoAdapter adapter = new InfoAdapter();
mRecyclerView.setAdapter(adapter);
mRecyclerView.setLayoutManager(new LinearLayoutManager(context, RecyclerView.VERTICAL, false));
LinearDividerItemDecoration decoration = new LinearDividerItemDecoration(
LinearDividerItemDecoration.VERTICAL,
ResourcesKt.resolveColor(getTheme(), R.attr.dividerColor),
LayoutUtils.dp2pix(context, 1));
int keylineMargin = context.getResources().getDimensionPixelOffset(R.dimen.keyline_margin);
decoration.setPadding(keylineMargin);
mRecyclerView.addItemDecoration(decoration);
mRecyclerView.setClipToPadding(false);
mRecyclerView.setHasFixedSize(true);
return view;
@@ -59,8 +59,6 @@ import androidx.drawerlayout.widget.DrawerLayout;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.getkeepsafe.taptargetview.TapTarget;
import com.getkeepsafe.taptargetview.TapTargetView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.h6ah4i.android.widget.advrecyclerview.animator.DraggableItemAnimator;
@@ -74,7 +72,6 @@ import com.hippo.drawable.AddDeleteDrawable;
import com.hippo.drawable.DrawerArrowDrawable;
import com.hippo.easyrecyclerview.EasyRecyclerView;
import com.hippo.easyrecyclerview.FastScroller;
import com.hippo.easyrecyclerview.LinearDividerItemDecoration;
import com.hippo.ehviewer.EhApplication;
import com.hippo.ehviewer.EhDB;
import com.hippo.ehviewer.FavouriteStatusRouter;
@@ -635,40 +632,9 @@ public final class GalleryListScene extends BaseScene
mHelper.firstRefresh();
}
guideQuickSearch();
return view;
}
private void guideQuickSearch() {
Activity activity = getMainActivity();
if (null == activity || !Settings.getGuideQuickSearch()) {
return;
}
Display display = activity.getWindowManager().getDefaultDisplay();
Point point = new Point();
display.getSize(point);
Rect bounds = new Rect(point.x + LayoutUtils.dp2pix(requireContext(), 20),
point.y / 3 + LayoutUtils.dp2pix(requireContext(), 20),
point.x - LayoutUtils.dp2pix(requireContext(), 20),
point.y / 3 - LayoutUtils.dp2pix(requireContext(), 20));
TapTargetView.showFor(requireActivity(),
TapTarget.forBounds(bounds,
getString(R.string.guide_quick_search_title),
getString(R.string.guide_quick_search_text))
.transparentTarget(true),
new TapTargetView.Listener() {
@Override
public void onTargetDismissed(TapTargetView view, boolean userInitiated) {
super.onTargetClick(view);
Settings.putGuideQuickSearch(false);
openDrawer(Gravity.RIGHT);
}
});
}
@Override
public void onDestroyView() {
super.onDestroyView();
@@ -794,12 +760,6 @@ public final class GalleryListScene extends BaseScene
final EasyRecyclerView recyclerView = view.findViewById(R.id.recycler_view_drawer);
recyclerView.setLayoutManager(new LinearLayoutManager(context));
LinearDividerItemDecoration decoration = new LinearDividerItemDecoration(
LinearDividerItemDecoration.VERTICAL,
ResourcesKt.resolveColor(getTheme(), R.attr.dividerColor),
LayoutUtils.dp2pix(context, 1));
decoration.setShowLastDivider(true);
recyclerView.addItemDecoration(decoration);
final QsDrawerAdapter qsDrawerAdapter = new QsDrawerAdapter(inflater);
qsDrawerAdapter.setHasStableIds(true);
final GeneralItemAnimator animator = new DraggableItemAnimator();
@@ -36,6 +36,7 @@ import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.slider.Slider;
import com.hippo.easyrecyclerview.EasyRecyclerView;
import com.hippo.easyrecyclerview.MarginItemDecoration;
import com.hippo.ehviewer.EhApplication;
@@ -53,7 +54,6 @@ import com.hippo.ehviewer.ui.MainActivity;
import com.hippo.scene.SceneFragment;
import com.hippo.widget.ContentLayout;
import com.hippo.widget.LoadImageView;
import com.hippo.widget.Slider;
import com.hippo.widget.recyclerview.AutoGridLayoutManager;
import com.hippo.yorozuya.AssertUtils;
import com.hippo.yorozuya.LayoutUtils;
@@ -209,7 +209,7 @@ public class GalleryPreviewsScene extends ToolbarScene {
return true;
}
int pages = mHelper.getPages();
if (pages > 0 && mHelper.canGoTo()) {
if (pages > 1 && mHelper.canGoTo()) {
GoToDialogHelper helper = new GoToDialogHelper(pages, mHelper.getPageForTop());
AlertDialog dialog = new MaterialAlertDialogBuilder(context).setTitle(R.string.go_to)
.setView(R.layout.dialog_go_to)
@@ -418,8 +418,8 @@ public class GalleryPreviewsScene extends ToolbarScene {
((TextView) ViewUtils.$$(dialog, R.id.start)).setText(String.format(Locale.US, "%d", 1));
((TextView) ViewUtils.$$(dialog, R.id.end)).setText(String.format(Locale.US, "%d", mPages));
mSlider = (Slider) ViewUtils.$$(dialog, R.id.slider);
mSlider.setRange(1, mPages);
mSlider.setProgress(mCurrentPage + 1);
mSlider.setValueTo(mPages);
mSlider.setValue(mCurrentPage + 1);
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(this);
dialog.setOnDismissListener(this);
@@ -431,7 +431,7 @@ public class GalleryPreviewsScene extends ToolbarScene {
return;
}
int page = mSlider.getProgress() - 1;
int page = (int) (mSlider.getValue() - 1);
if (page >= 0 && page < mPages && mHelper != null) {
mHelper.goTo(page);
if (mDialog != null) {
@@ -27,6 +27,7 @@ import androidx.biometric.auth.AuthPromptCallback
import androidx.biometric.auth.startClass2BiometricOrCredentialAuthentication
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
import com.hippo.ehviewer.EhApplication
import com.hippo.ehviewer.R
class SecurityScene : SolidScene() {
@@ -39,7 +40,7 @@ class SecurityScene : SolidScene() {
if (isAuthenticationSupported(requireContext()))
startAuthentication(getString(R.string.settings_privacy_require_unlock))
else
startSceneForCheckStep(CHECK_STEP_SECURITY, arguments)
onSuccess()
}
private fun startAuthentication(
@@ -61,8 +62,15 @@ class SecurityScene : SolidScene() {
activity: FragmentActivity?,
result: BiometricPrompt.AuthenticationResult
) {
super.onAuthenticationSucceeded(activity, result)
startSceneForCheckStep(CHECK_STEP_SECURITY, arguments)
onSuccess()
}
override fun onAuthenticationError(
activity: FragmentActivity?,
errorCode: Int,
errString: CharSequence
) {
requireActivity().finish()
}
}
@@ -73,4 +81,11 @@ class SecurityScene : SolidScene() {
.canAuthenticate(authenticators) == BiometricManager.BIOMETRIC_SUCCESS
}
}
private fun onSuccess() {
EhApplication.locked = false
if (stackIndex == 0)
startSceneForCheckStep(CHECK_STEP_SECURITY, arguments)
finish()
}
}
@@ -1,229 +0,0 @@
/*
* Copyright (C) 2015 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.widget;
import android.content.Context;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.Spinner;
import com.hippo.ehviewer.R;
import com.hippo.yorozuya.NumberUtils;
public class AdvanceSearchTable extends LinearLayout {
public static final int SNAME = 0x1;
public static final int STAGS = 0x2;
public static final int SDESC = 0x4;
public static final int STORR = 0x8;
public static final int STO = 0x10;
public static final int SDT1 = 0x20;
public static final int SDT2 = 0x40;
public static final int SH = 0x80;
public static final int SFL = 0x100;
public static final int SFU = 0x200;
public static final int SFT = 0x400;
private static final String STATE_KEY_SUPER = "super";
private static final String STATE_KEY_ADVANCE_SEARCH = "advance_search";
private static final String STATE_KEY_MIN_RATING = "min_rating";
private static final String STATE_KEY_PAGE_FROM = "page_from";
private static final String STATE_KEY_PAGE_TO = "page_to";
private CheckBox mSname;
private CheckBox mStags;
private CheckBox mSdesc;
private CheckBox mStorr;
private CheckBox mSto;
private CheckBox mSdt1;
private CheckBox mSdt2;
private CheckBox mSh;
private CheckBox mSr;
private Spinner mMinRating;
private CheckBox mSp;
private EditText mSpf;
private EditText mSpt;
private CheckBox mSfl;
private CheckBox mSfu;
private CheckBox mSft;
public AdvanceSearchTable(Context context) {
super(context);
init(context);
}
public AdvanceSearchTable(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public void init(Context context) {
setOrientation(LinearLayout.VERTICAL);
LayoutInflater inflater = LayoutInflater.from(getContext());
inflater.inflate(R.layout.widget_advance_search_table, this);
ViewGroup row0 = (ViewGroup) getChildAt(0);
mSname = (CheckBox) row0.getChildAt(0);
mStags = (CheckBox) row0.getChildAt(1);
ViewGroup row1 = (ViewGroup) getChildAt(1);
mSdesc = (CheckBox) row1.getChildAt(0);
mStorr = (CheckBox) row1.getChildAt(1);
ViewGroup row2 = (ViewGroup) getChildAt(2);
mSto = (CheckBox) row2.getChildAt(0);
mSdt1 = (CheckBox) row2.getChildAt(1);
ViewGroup row3 = (ViewGroup) getChildAt(3);
mSdt2 = (CheckBox) row3.getChildAt(0);
mSh = (CheckBox) row3.getChildAt(1);
ViewGroup row4 = (ViewGroup) getChildAt(4);
mSr = (CheckBox) row4.getChildAt(0);
mMinRating = (Spinner) row4.getChildAt(1);
ViewGroup row5 = (ViewGroup) getChildAt(5);
mSp = (CheckBox) row5.getChildAt(0);
mSpf = (EditText) row5.getChildAt(1);
mSpt = (EditText) row5.getChildAt(3);
ViewGroup row7 = (ViewGroup) getChildAt(7);
mSfl = (CheckBox) row7.getChildAt(0);
mSfu = (CheckBox) row7.getChildAt(1);
mSft = (CheckBox) row7.getChildAt(2);
// Avoid java.lang.IllegalStateException: focus search returned a view that wasn't able to take focus!
mSpt.setOnEditorActionListener((v, actionId, event) -> {
View nextView = v.focusSearch(View.FOCUS_DOWN);
if (nextView != null) {
nextView.requestFocus(View.FOCUS_DOWN);
}
return true;
});
}
public int getAdvanceSearch() {
int advanceSearch = 0;
if (mSname.isChecked()) advanceSearch |= SNAME;
if (mStags.isChecked()) advanceSearch |= STAGS;
if (mSdesc.isChecked()) advanceSearch |= SDESC;
if (mStorr.isChecked()) advanceSearch |= STORR;
if (mSto.isChecked()) advanceSearch |= STO;
if (mSdt1.isChecked()) advanceSearch |= SDT1;
if (mSdt2.isChecked()) advanceSearch |= SDT2;
if (mSh.isChecked()) advanceSearch |= SH;
if (mSfl.isChecked()) advanceSearch |= SFL;
if (mSfu.isChecked()) advanceSearch |= SFU;
if (mSft.isChecked()) advanceSearch |= SFT;
return advanceSearch;
}
public void setAdvanceSearch(int advanceSearch) {
mSname.setChecked(NumberUtils.int2boolean(advanceSearch & SNAME));
mStags.setChecked(NumberUtils.int2boolean(advanceSearch & STAGS));
mSdesc.setChecked(NumberUtils.int2boolean(advanceSearch & SDESC));
mStorr.setChecked(NumberUtils.int2boolean(advanceSearch & STORR));
mSto.setChecked(NumberUtils.int2boolean(advanceSearch & STO));
mSdt1.setChecked(NumberUtils.int2boolean(advanceSearch & SDT1));
mSdt2.setChecked(NumberUtils.int2boolean(advanceSearch & SDT2));
mSh.setChecked(NumberUtils.int2boolean(advanceSearch & SH));
mSfl.setChecked(NumberUtils.int2boolean(advanceSearch & SFL));
mSfu.setChecked(NumberUtils.int2boolean(advanceSearch & SFU));
mSft.setChecked(NumberUtils.int2boolean(advanceSearch & SFT));
}
public int getMinRating() {
int position = mMinRating.getSelectedItemPosition();
if (mSr.isChecked() && position >= 0) {
return position + 2;
} else {
return -1;
}
}
public void setMinRating(int minRating) {
if (minRating >= 2 && minRating <= 5) {
mSr.setChecked(true);
mMinRating.setSelection(minRating - 2);
} else {
mSr.setChecked(false);
}
}
public int getPageFrom() {
if (mSp.isChecked()) {
return NumberUtils.parseIntSafely(mSpf.getText().toString(), -1);
}
return -1;
}
public void setPageFrom(int pageFrom) {
if (pageFrom > 0) {
mSpf.setText(Integer.toString(pageFrom));
mSp.setChecked(true);
} else {
mSp.setChecked(false);
mSpf.setText(null);
}
}
public int getPageTo() {
if (mSp.isChecked()) {
return NumberUtils.parseIntSafely(mSpt.getText().toString(), -1);
}
return -1;
}
public void setPageTo(int pageTo) {
if (pageTo > 0) {
mSpt.setText(Integer.toString(pageTo));
mSp.setChecked(true);
} else {
mSp.setChecked(false);
mSpt.setText(null);
}
}
@Override
public Parcelable onSaveInstanceState() {
final Bundle state = new Bundle();
state.putParcelable(STATE_KEY_SUPER, super.onSaveInstanceState());
state.putInt(STATE_KEY_ADVANCE_SEARCH, getAdvanceSearch());
state.putInt(STATE_KEY_MIN_RATING, getMinRating());
state.putInt(STATE_KEY_PAGE_FROM, getPageFrom());
state.putInt(STATE_KEY_PAGE_TO, getPageTo());
return state;
}
@Override
public void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
final Bundle savedState = (Bundle) state;
super.onRestoreInstanceState(savedState.getParcelable(STATE_KEY_SUPER));
setAdvanceSearch(savedState.getInt(STATE_KEY_ADVANCE_SEARCH));
setMinRating(savedState.getInt(STATE_KEY_MIN_RATING));
setPageFrom(savedState.getInt(STATE_KEY_PAGE_FROM));
setPageTo(savedState.getInt(STATE_KEY_PAGE_TO));
}
}
}
@@ -0,0 +1,189 @@
/*
* Copyright (C) 2015 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.ehviewer.widget
import android.content.Context
import android.util.AttributeSet
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.*
import com.google.android.material.textfield.TextInputLayout
import com.hippo.ehviewer.R
import com.hippo.yorozuya.NumberUtils
class AdvanceSearchTable @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : LinearLayout(context, attrs) {
private var mSname: CheckBox
private var mStags: CheckBox
private var mSdesc: CheckBox
private var mStorr: CheckBox
private var mSto: CheckBox
private var mSdt1: CheckBox
private var mSdt2: CheckBox
private var mSh: CheckBox
private var mSr: CheckBox
private var mMinRating: TextInputLayout
private var mSp: CheckBox
private var mSpf: EditText
private var mSpt: EditText
private var mSfl: CheckBox
private var mSfu: CheckBox
private var mSft: CheckBox
private val mArray: Array<String> = context.resources.getStringArray(R.array.search_min_rating)
init {
orientation = VERTICAL
val inflater = LayoutInflater.from(context)
inflater.inflate(R.layout.widget_advance_search_table, this)
val row0 = getChildAt(0) as ViewGroup
mSname = row0.getChildAt(0) as CheckBox
mStags = row0.getChildAt(1) as CheckBox
val row1 = getChildAt(1) as ViewGroup
mSdesc = row1.getChildAt(0) as CheckBox
mStorr = row1.getChildAt(1) as CheckBox
val row2 = getChildAt(2) as ViewGroup
mSto = row2.getChildAt(0) as CheckBox
mSdt1 = row2.getChildAt(1) as CheckBox
val row3 = getChildAt(3) as ViewGroup
mSdt2 = row3.getChildAt(0) as CheckBox
mSh = row3.getChildAt(1) as CheckBox
val row4 = getChildAt(4) as ViewGroup
mSr = row4.getChildAt(0) as CheckBox
mMinRating = row4.getChildAt(1) as TextInputLayout
val row5 = getChildAt(5) as ViewGroup
mSp = row5.getChildAt(0) as CheckBox
mSpf = row5.getChildAt(1) as EditText
mSpt = row5.getChildAt(3) as EditText
val row7 = getChildAt(7) as ViewGroup
mSfl = row7.getChildAt(0) as CheckBox
mSfu = row7.getChildAt(1) as CheckBox
mSft = row7.getChildAt(2) as CheckBox
mSpt.setOnEditorActionListener { v: TextView, _: Int, _: KeyEvent? ->
val nextView = v.focusSearch(
FOCUS_DOWN
)
nextView?.requestFocus(FOCUS_DOWN)
true
}
mMinRating.isEnabled = mSr.isChecked
mSr.setOnCheckedChangeListener { _: CompoundButton, isChecked: Boolean ->
mMinRating.isEnabled = isChecked
}
mSpt.isEnabled = mSp.isChecked
mSpf.isEnabled = mSp.isChecked
mSp.setOnCheckedChangeListener { _: CompoundButton, isChecked: Boolean ->
mSpt.isEnabled = isChecked
mSpf.isEnabled = isChecked
}
}
var advanceSearch: Int
get() {
var advanceSearch = 0
if (mSname.isChecked) advanceSearch = advanceSearch or SNAME
if (mStags.isChecked) advanceSearch = advanceSearch or STAGS
if (mSdesc.isChecked) advanceSearch = advanceSearch or SDESC
if (mStorr.isChecked) advanceSearch = advanceSearch or STORR
if (mSto.isChecked) advanceSearch = advanceSearch or STO
if (mSdt1.isChecked) advanceSearch = advanceSearch or SDT1
if (mSdt2.isChecked) advanceSearch = advanceSearch or SDT2
if (mSh.isChecked) advanceSearch = advanceSearch or SH
if (mSfl.isChecked) advanceSearch = advanceSearch or SFL
if (mSfu.isChecked) advanceSearch = advanceSearch or SFU
if (mSft.isChecked) advanceSearch = advanceSearch or SFT
return advanceSearch
}
set(advanceSearch) {
mSname.isChecked =
NumberUtils.int2boolean(advanceSearch and SNAME)
mStags.isChecked =
NumberUtils.int2boolean(advanceSearch and STAGS)
mSdesc.isChecked =
NumberUtils.int2boolean(advanceSearch and SDESC)
mStorr.isChecked = NumberUtils.int2boolean(advanceSearch and STORR)
mSto.isChecked = NumberUtils.int2boolean(advanceSearch and STO)
mSdt1.isChecked = NumberUtils.int2boolean(advanceSearch and SDT1)
mSdt2.isChecked = NumberUtils.int2boolean(advanceSearch and SDT2)
mSh.isChecked = NumberUtils.int2boolean(advanceSearch and SH)
mSfl.isChecked = NumberUtils.int2boolean(advanceSearch and SFL)
mSfu.isChecked = NumberUtils.int2boolean(advanceSearch and SFU)
mSft.isChecked = NumberUtils.int2boolean(advanceSearch and SFT)
}
var minRating: Int
get() {
val position = mArray.indexOf(mMinRating.editText!!.text.toString())
return if (mSr.isChecked && position >= 0) {
position + 2
} else {
-1
}
}
set(minRating) {
if (minRating in 2..5) {
mSr.isChecked = true
(mMinRating.editText!! as AutoCompleteTextView).setText(
mArray[minRating - 2],
false
)
} else {
mSr.isChecked = false
}
}
var pageFrom: Int
get() = if (mSp.isChecked) {
NumberUtils.parseIntSafely(mSpf.text.toString(), -1)
} else -1
set(pageFrom) {
if (pageFrom > 0) {
mSpf.setText(pageFrom.toString())
mSp.isChecked = true
} else {
mSp.isChecked = false
mSpf.text = null
}
}
var pageTo: Int
get() = if (mSp.isChecked) {
NumberUtils.parseIntSafely(mSpt.text.toString(), -1)
} else -1
set(pageTo) {
if (pageTo > 0) {
mSpt.setText(pageTo.toString())
mSp.isChecked = true
} else {
mSp.isChecked = false
}
}
companion object {
const val SNAME = 0x1
const val STAGS = 0x2
const val SDESC = 0x4
const val STORR = 0x8
const val STO = 0x10
const val SDT1 = 0x20
const val SDT2 = 0x40
const val SH = 0x80
const val SFL = 0x100
const val SFU = 0x200
const val SFT = 0x400
}
}
@@ -30,8 +30,6 @@ import com.hippo.ehviewer.R;
import com.hippo.ehviewer.Settings;
import com.hippo.yorozuya.ViewUtils;
import rikka.core.res.ResourcesKt;
public class GalleryGuideView extends ViewGroup implements View.OnClickListener {
private final float[] mPoints = new float[3 * 4];
@@ -48,12 +48,10 @@ import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.card.MaterialCardView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.hippo.easyrecyclerview.EasyRecyclerView;
import com.hippo.easyrecyclerview.LinearDividerItemDecoration;
import com.hippo.ehviewer.R;
import com.hippo.ehviewer.client.EhTagDatabase;
import com.hippo.view.ViewTransition;
import com.hippo.yorozuya.AnimationUtils;
import com.hippo.yorozuya.LayoutUtils;
import com.hippo.yorozuya.MathUtils;
import com.hippo.yorozuya.SimpleAnimatorListener;
import com.hippo.yorozuya.ViewUtils;
@@ -61,8 +59,6 @@ import com.hippo.yorozuya.ViewUtils;
import java.util.ArrayList;
import java.util.List;
import rikka.core.res.ResourcesKt;
public class SearchBar extends MaterialCardView implements View.OnClickListener,
TextView.OnEditorActionListener, TextWatcher,
SearchEditText.SearchEditTextListener {
@@ -147,12 +143,6 @@ public class SearchBar extends MaterialCardView implements View.OnClickListener,
mSuggestionAdapter = new SuggestionAdapter(LayoutInflater.from(getContext()));
mListView.setAdapter(mSuggestionAdapter);
LinearLayoutManager layoutManager = new LinearLayoutManager(context);
LinearDividerItemDecoration decoration = new LinearDividerItemDecoration(
LinearDividerItemDecoration.VERTICAL,
ResourcesKt.resolveColor(context.getTheme(), R.attr.dividerColor),
LayoutUtils.dp2pix(context, 1));
decoration.setShowLastDivider(false);
mListView.addItemDecoration(decoration);
mListView.setLayoutManager(layoutManager);
}
@@ -249,7 +249,7 @@ class SearchLayout @JvmOverloads constructor(
fun onSelectImage()
}
internal class SearchLayoutManager(context: Context?) : LinearLayoutManager(context) {
internal class SearchLayoutManager(context: Context?) : LinearLayoutManager(context!!) {
override fun onLayoutChildren(recycler: Recycler, state: State) {
try {
super.onLayoutChildren(recycler, state)
@@ -57,6 +57,7 @@ public abstract class GalleryProvider {
public void stop() {
OSUtils.checkMainLoop();
mImageCache.evictAll();
Image.lazyEvictAll();
}
public void setGLRoot(GLRoot glRoot) {
@@ -245,18 +246,14 @@ public abstract class GalleryProvider {
}
public void add(Integer key, ImageWrapper value) {
if (value.getFormat() != Image.FORMAT_GIF && value.obtain()) {
put(key, value);
}
if (value.getFormat() != Image.FORMAT_ANIMATED)
if (value.obtain())
put(key, value);
}
@Override
protected int sizeOf(Integer key, ImageWrapper value) {
int size = value.getWidth() * value.getHeight() * 4;
if (value.getFormat() == Image.FORMAT_GIF) {
size *= 5;
}
return size;
return value.getWidth() * value.getHeight() * 4;
}
@Override
@@ -153,12 +153,6 @@ public class ImageTexture implements Texture, Animatable {
}
mTiles = list.toArray(new Tile[list.size()]);
if (!mImage.isCompleted()) {
Runnable runnable = new AnimateRunnable();
mAnimateRunnable = runnable;
sThreadExecutor.execute(runnable);
}
}
private static Tile obtainSmallTile() {
@@ -239,7 +233,7 @@ public class ImageTexture implements Texture, Animatable {
}
boolean end = mReleased.get() || mImage.isImageRecycled() || mNeedRelease.get() ||
(mImage.isCompleted() && mImage.getFrameCount() <= 1) || mRunning.get();
(mImage.getFormat() == 0) || mRunning.get();
synchronized (mImage) {
mImageBusy = false;
@@ -597,25 +591,11 @@ public class ImageTexture implements Texture, Animatable {
mImageBusy = true;
}
if (!mImage.isCompleted()) {
try {
sPVLock.p();
} catch (InterruptedException e) {
// Ignore
}
if (!mNeedRelease.get()) {
mImage.complete();
}
sPVLock.v();
}
int frameCount = mImage.getFrameCount();
synchronized (mImage) {
// Release image
mImageBusy = false;
// Check need release, frameCount <= 1
if (mNeedRelease.get() || frameCount <= 1) {
if (mNeedRelease.get() || mImage.getFormat() == 0) {
mAnimateRunnable = null;
return;
}
@@ -122,22 +122,6 @@ public class ImageWrapper {
return mCut.height();
}
/**
* @see Image#complete()
*/
public boolean complete() {
synchronized (mImage) {
return mImage.complete();
}
}
/**
* @see Image#isCompleted()
*/
public boolean isCompleted() {
return mImage.isCompleted();
}
/**
* @see Image#render(int, int, Bitmap, int, int, int, int, boolean, int)
*/
@@ -168,13 +152,6 @@ public class ImageWrapper {
return mImage.getDelay();
}
/**
* @see Image#getFrameCount()
*/
public int getFrameCount() {
return mImage.getFrameCount();
}
/**
* @see Image#isOpaque()
*/
@@ -16,12 +16,11 @@
package com.hippo.glview.view;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.PixelFormat;
import android.opengl.GLSurfaceView;
import android.opengl.GLUtils;
import android.os.Build;
import android.os.Parcelable;
import android.os.Process;
import android.os.SystemClock;
@@ -42,10 +41,6 @@ import com.hippo.glview.glrenderer.UploadedTexture;
import com.hippo.glview.util.ApiHelper;
import com.hippo.glview.util.GalleryUtils;
import com.hippo.glview.util.MotionEventHelper;
import com.hippo.tuxiang.EGLConfigChooser;
import com.hippo.tuxiang.EGLContextFactory;
import com.hippo.tuxiang.GLSurfaceView;
import com.hippo.tuxiang.Renderer;
import com.hippo.yorozuya.AssertUtils;
import java.util.ArrayDeque;
@@ -89,12 +84,7 @@ public class GLRootView extends GLSurfaceView
private final ReentrantLock mRenderLock = new ReentrantLock();
private final Condition mFreezeCondition =
mRenderLock.newCondition();
private final Runnable mRequestRenderOnAnimationFrame = new Runnable() {
@Override
public void run() {
superRequestRender();
}
};
private final Runnable mRequestRenderOnAnimationFrame = this::superRequestRender;
private int mFrameCount = 0;
private long mFrameCountingStart = 0;
private int mInvalidateColor = 0;
@@ -187,11 +177,7 @@ public class GLRootView extends GLSurfaceView
}
if (mRenderRequested) return;
mRenderRequested = true;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
postOnAnimation(mRequestRenderOnAnimationFrame);
} else {
super.requestRender();
}
postOnAnimation(mRequestRenderOnAnimationFrame);
}
private void superRequestRender() {
@@ -281,7 +267,7 @@ public class GLRootView extends GLSurfaceView
++mFrameCount;
}
private void onDrawFrameLocked(GL10 gl) {
private void onDrawFrameLocked() {
if (DEBUG_FPS) outputFps();
// release the unbound textures and deleted buffers.
mCanvas.deleteRecycledResources();
@@ -391,9 +377,20 @@ public class GLRootView extends GLSurfaceView
@Override
public void onPause() {
unfreeze();
if (mContentView != null) {
mContentView.pause();
}
super.onPause();
}
@Override
public void onResume() {
if (mContentView != null) {
mContentView.resume();
}
super.onResume();
}
@Override
public void setOrientationSource(OrientationSource source) {
mOrientationSource = source;
@@ -430,7 +427,6 @@ public class GLRootView extends GLSurfaceView
}
@Override
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
public void setLightsOutMode(boolean enabled) {
if (!ApiHelper.HAS_SET_SYSTEM_UI_VISIBILITY) return;
@@ -503,7 +499,7 @@ public class GLRootView extends GLSurfaceView
}
}
private class GLRootRenderer implements Renderer {
private class GLRootRenderer implements GLSurfaceView.Renderer {
/**
* Called when the context is created, possibly after automatic destruction.
@@ -547,7 +543,7 @@ public class GLRootView extends GLSurfaceView
}
@Override
public boolean onDrawFrame(GL10 gl) {
public void onDrawFrame(GL10 gl) {
AnimationTime.update();
long t0 = System.nanoTime();
@@ -559,7 +555,7 @@ public class GLRootView extends GLSurfaceView
}
try {
onDrawFrameLocked(gl);
onDrawFrameLocked();
} finally {
mRenderLock.unlock();
}
@@ -570,33 +566,6 @@ public class GLRootView extends GLSurfaceView
if (duration > 8) {
Log.v(TAG, "--- " + duration + " ---");
}
return true;
}
@Override
public void onGLThreadStart() {
}
@Override
public void onGLThreadExit() {
if (mContentView != null && mContentView.isAttachedToRoot()) {
mContentView.detachFromRoot();
}
}
@Override
public void onGLThreadPause() {
if (mContentView != null) {
mContentView.pause();
}
}
@Override
public void onGLThreadResume() {
if (mContentView != null) {
mContentView.resume();
}
}
}
@@ -662,7 +631,7 @@ public class GLRootView extends GLSurfaceView
throw new IllegalArgumentException("No config chosen");
}
int renderableType = findConfigAttrib(egl, display, config, EGL10.EGL_RENDERABLE_TYPE, 0);
int renderableType = findConfigAttrib(egl, display, config, EGL10.EGL_RENDERABLE_TYPE);
if ((renderableType & 0x0004) == 0x0004 /* EGL_OPENGL_ES2_BIT */) {
mEGLContextClientVersion = 2;
} else {
@@ -680,21 +649,21 @@ public class GLRootView extends GLSurfaceView
for (int i = 0, n = configs.length; i < n; i++) {
final EGLConfig config = configs[i];
final int redSize = findConfigAttrib(egl, display, config,
EGL10.EGL_RED_SIZE, 0);
EGL10.EGL_RED_SIZE);
final int greenSize = findConfigAttrib(egl, display, config,
EGL10.EGL_GREEN_SIZE, 0);
EGL10.EGL_GREEN_SIZE);
final int blueSize = findConfigAttrib(egl, display, config,
EGL10.EGL_BLUE_SIZE, 0);
EGL10.EGL_BLUE_SIZE);
final int alphaSize = findConfigAttrib(egl, display, config,
EGL10.EGL_ALPHA_SIZE, 0);
EGL10.EGL_ALPHA_SIZE);
final int sampleBuffers = findConfigAttrib(egl, display, config,
EGL10.EGL_SAMPLE_BUFFERS, 0);
EGL10.EGL_SAMPLE_BUFFERS);
final int samples = findConfigAttrib(egl, display, config,
EGL10.EGL_SAMPLES, 0);
EGL10.EGL_SAMPLES);
final int depth = findConfigAttrib(egl, display, config,
EGL10.EGL_DEPTH_SIZE, 0);
EGL10.EGL_DEPTH_SIZE);
final int stencil = findConfigAttrib(egl, display, config,
EGL10.EGL_STENCIL_SIZE, 0);
EGL10.EGL_STENCIL_SIZE);
final int score = redSize + greenSize + blueSize + alphaSize +
sampleBuffers + samples - depth - stencil;
@@ -709,11 +678,11 @@ public class GLRootView extends GLSurfaceView
}
private int findConfigAttrib(EGL10 egl, EGLDisplay display,
EGLConfig config, int attribute, int defaultValue) {
EGLConfig config, int attribute) {
if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) {
return mValue[0];
}
return defaultValue;
return 0;
}
}
@@ -1,237 +0,0 @@
/*
* Copyright 2017 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.network;
/*
* Created by Hippo on 2017/9/4.
*/
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteStatement;
import android.util.Log;
import androidx.annotation.Nullable;
import com.hippo.database.MSQLiteBuilder;
import com.hippo.util.SqlUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import okhttp3.Cookie;
class CookieDatabase {
private static final String LOG_TAG = CookieDatabase.class.getSimpleName();
private static final int VERSION_1 = 1;
private static final String TABLE_COOKIE = "OK_HTTP_3_COOKIE";
private static final String COLUMN_ID = "_id";
private static final String COLUMN_NAME = "NAME";
private static final String COLUMN_VALUE = "VALUE";
private static final String COLUMN_EXPIRES_AT = "EXPIRES_AT";
private static final String COLUMN_DOMAIN = "DOMAIN";
private static final String COLUMN_PATH = "PATH";
private static final String COLUMN_SECURE = "SECURE";
private static final String COLUMN_HTTP_ONLY = "HTTP_ONLY";
private static final String COLUMN_PERSISTENT = "PERSISTENT";
private static final String COLUMN_HOST_ONLY = "HOST_ONLY";
private static final int DB_VERSION = VERSION_1;
private final Map<Cookie, Long> cookieIdMap = new HashMap<>();
private final SQLiteOpenHelper helper;
private final SQLiteDatabase db;
public CookieDatabase(Context context, String name) {
helper = new MSQLiteBuilder()
.version(VERSION_1)
.createTable(TABLE_COOKIE)
.insertColumn(TABLE_COOKIE, COLUMN_NAME, String.class)
.insertColumn(TABLE_COOKIE, COLUMN_VALUE, String.class)
.insertColumn(TABLE_COOKIE, COLUMN_EXPIRES_AT, long.class)
.insertColumn(TABLE_COOKIE, COLUMN_DOMAIN, String.class)
.insertColumn(TABLE_COOKIE, COLUMN_PATH, String.class)
.insertColumn(TABLE_COOKIE, COLUMN_SECURE, boolean.class)
.insertColumn(TABLE_COOKIE, COLUMN_HTTP_ONLY, boolean.class)
.insertColumn(TABLE_COOKIE, COLUMN_PERSISTENT, boolean.class)
.insertColumn(TABLE_COOKIE, COLUMN_HOST_ONLY, boolean.class)
.build(context, name, DB_VERSION);
db = helper.getWritableDatabase();
}
@Nullable
private static Cookie getCookie(Cursor cursor, long now) {
String name = SqlUtils.getString(cursor, COLUMN_NAME, null);
String value = SqlUtils.getString(cursor, COLUMN_VALUE, null);
long expiresAt = SqlUtils.getLong(cursor, COLUMN_EXPIRES_AT, 0);
String domain = SqlUtils.getString(cursor, COLUMN_DOMAIN, null);
String path = SqlUtils.getString(cursor, COLUMN_PATH, null);
boolean secure = SqlUtils.getBoolean(cursor, COLUMN_SECURE, false);
boolean httpOnly = SqlUtils.getBoolean(cursor, COLUMN_HTTP_ONLY, false);
boolean persistent = SqlUtils.getBoolean(cursor, COLUMN_PERSISTENT, false);
boolean hostOnly = SqlUtils.getBoolean(cursor, COLUMN_HOST_ONLY, false);
if (name == null || domain == null || path == null) {
return null;
}
// Check non-persistent or expired
if (!persistent || expiresAt <= now) {
return null;
}
Cookie.Builder builder = new Cookie.Builder();
builder.name(name);
builder.value(value);
if (hostOnly) {
builder.hostOnlyDomain(domain);
} else {
builder.domain(domain);
}
builder.path(path);
builder.expiresAt(expiresAt);
if (secure) builder.secure();
if (httpOnly) builder.httpOnly();
return builder.build();
}
public Map<String, CookieSet> getAllCookies() {
long now = System.currentTimeMillis();
Map<String, CookieSet> map = new HashMap<>();
List<Long> toRemove = new ArrayList<>();
try (Cursor cursor = db.rawQuery("SELECT * FROM " + TABLE_COOKIE + ";", null)) {
while (cursor.moveToNext()) {
long id = SqlUtils.getLong(cursor, COLUMN_ID, 0);
Cookie cookie = getCookie(cursor, now);
if (cookie != null) {
// Save id of the cookie in db
cookieIdMap.put(cookie, id);
// Put cookie to set
CookieSet set = map.get(cookie.domain());
if (set == null) {
set = new CookieSet();
map.put(cookie.domain(), set);
}
set.add(cookie);
} else {
// Mark to remove the cookie
toRemove.add(id);
}
}
}
// Remove invalid or expired cookie
if (!toRemove.isEmpty()) {
SQLiteStatement statement = db.compileStatement(
"DELETE FROM " + TABLE_COOKIE + " WHERE " + COLUMN_ID + " = ?;");
db.beginTransaction();
try {
for (long id : toRemove) {
statement.bindLong(1, id);
statement.executeUpdateDelete();
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
return map;
}
public ContentValues toContentValues(Cookie cookie) {
ContentValues contentValues = new ContentValues(9);
contentValues.put(COLUMN_NAME, cookie.name());
contentValues.put(COLUMN_VALUE, cookie.value());
contentValues.put(COLUMN_EXPIRES_AT, cookie.expiresAt());
contentValues.put(COLUMN_DOMAIN, cookie.domain());
contentValues.put(COLUMN_PATH, cookie.path());
contentValues.put(COLUMN_SECURE, cookie.secure());
contentValues.put(COLUMN_HTTP_ONLY, cookie.httpOnly());
contentValues.put(COLUMN_PERSISTENT, cookie.persistent());
contentValues.put(COLUMN_HOST_ONLY, cookie.hostOnly());
return contentValues;
}
public void add(Cookie cookie) {
long id = db.insert(TABLE_COOKIE, null, toContentValues(cookie));
if (id != -1L) {
Long oldId = cookieIdMap.put(cookie, id);
if (oldId != null) Log.e(LOG_TAG, "Add a duplicate cookie");
} else {
Log.e(LOG_TAG, "An error occurred when insert a cookie");
}
}
public void update(Cookie from, Cookie to) {
Long id = cookieIdMap.get(from);
if (id == null) {
Log.e(LOG_TAG, "Can't get id when update the cookie");
return;
}
ContentValues values = toContentValues(to);
String whereClause = COLUMN_ID + " = ?";
String[] whereArgs = {id.toString()};
int count = db.update(TABLE_COOKIE, values, whereClause, whereArgs);
if (count != 1) {
Log.e(LOG_TAG, "Bad result when update cookie: " + count);
}
// Update it in cookie-id map
cookieIdMap.remove(from);
cookieIdMap.put(to, id);
}
public void remove(Cookie cookie) {
Long id = cookieIdMap.get(cookie);
if (id == null) {
Log.e(LOG_TAG, "Can't get id when remove the cookie");
return;
}
String whereClause = COLUMN_ID + " = ?";
String[] whereArgs = {id.toString()};
int count = db.delete(TABLE_COOKIE, whereClause, whereArgs);
if (count != 1) {
Log.e(LOG_TAG, "Bad result when remove cookie: " + count);
}
// Remove it from cookie-id map
cookieIdMap.remove(cookie);
}
public void clear() {
db.delete(TABLE_COOKIE, null, null);
cookieIdMap.clear();
}
public void close() {
db.close();
helper.close();
}
}
@@ -0,0 +1,214 @@
/*
* Copyright 2017 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.network
import android.content.ContentValues
import android.content.Context
import android.database.Cursor
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import android.util.Log
import com.hippo.database.MSQLiteBuilder
import com.hippo.util.SqlUtils
import okhttp3.Cookie
internal class CookieDatabase(context: Context, name: String) {
private val cookieIdMap: MutableMap<Cookie, Long> = HashMap()
private val helper: SQLiteOpenHelper
private val db: SQLiteDatabase
val allCookies: MutableMap<String, CookieSet>
get() {
val now = System.currentTimeMillis()
val map: MutableMap<String, CookieSet> = HashMap()
val toRemove: MutableList<Long> = ArrayList()
db.rawQuery("SELECT * FROM $TABLE_COOKIE;", null).use { cursor ->
while (cursor.moveToNext()) {
val id = SqlUtils.getLong(cursor, COLUMN_ID, 0)
val cookie = getCookie(cursor, now)
if (cookie != null) {
// Save id of the cookie in db
cookieIdMap[cookie] = id
// Put cookie to set
var set = map[cookie.domain]
if (set == null) {
set = CookieSet()
map[cookie.domain] = set
}
set.add(cookie)
} else {
// Mark to remove the cookie
toRemove.add(id)
}
}
}
// Remove invalid or expired cookie
if (toRemove.isNotEmpty()) {
val statement = db.compileStatement(
"DELETE FROM $TABLE_COOKIE WHERE $COLUMN_ID = ?;"
)
db.beginTransaction()
try {
for (id in toRemove) {
statement.bindLong(1, id)
statement.executeUpdateDelete()
}
db.setTransactionSuccessful()
} finally {
db.endTransaction()
}
}
return map
}
private fun toContentValues(cookie: Cookie): ContentValues {
val contentValues = ContentValues(9)
contentValues.put(COLUMN_NAME, cookie.name)
contentValues.put(COLUMN_VALUE, cookie.value)
contentValues.put(COLUMN_EXPIRES_AT, cookie.expiresAt)
contentValues.put(COLUMN_DOMAIN, cookie.domain)
contentValues.put(COLUMN_PATH, cookie.path)
contentValues.put(COLUMN_SECURE, cookie.secure)
contentValues.put(COLUMN_HTTP_ONLY, cookie.httpOnly)
contentValues.put(COLUMN_PERSISTENT, cookie.persistent)
contentValues.put(COLUMN_HOST_ONLY, cookie.hostOnly)
return contentValues
}
fun add(cookie: Cookie) {
val id = db.insert(TABLE_COOKIE, null, toContentValues(cookie))
if (id != -1L) {
val oldId = cookieIdMap.put(cookie, id)
if (oldId != null) Log.e(LOG_TAG, "Add a duplicate cookie")
} else {
Log.e(LOG_TAG, "An error occurred when insert a cookie")
}
}
fun update(from: Cookie, to: Cookie) {
val id = cookieIdMap[from]
if (id == null) {
Log.e(LOG_TAG, "Can't get id when update the cookie")
return
}
val values = toContentValues(to)
val whereClause = "$COLUMN_ID = ?"
val whereArgs = arrayOf(id.toString())
val count = db.update(TABLE_COOKIE, values, whereClause, whereArgs)
if (count != 1) {
Log.e(LOG_TAG, "Bad result when update cookie: $count")
}
// Update it in cookie-id map
cookieIdMap.remove(from)
cookieIdMap[to] = id
}
fun remove(cookie: Cookie) {
val id = cookieIdMap[cookie]
if (id == null) {
Log.e(LOG_TAG, "Can't get id when remove the cookie")
return
}
val whereClause = "$COLUMN_ID = ?"
val whereArgs = arrayOf(id.toString())
val count = db.delete(TABLE_COOKIE, whereClause, whereArgs)
if (count != 1) {
Log.e(LOG_TAG, "Bad result when remove cookie: $count")
}
// Remove it from cookie-id map
cookieIdMap.remove(cookie)
}
fun clear() {
db.delete(TABLE_COOKIE, null, null)
cookieIdMap.clear()
}
fun close() {
db.close()
helper.close()
}
companion object {
private val LOG_TAG = CookieDatabase::class.java.simpleName
private const val VERSION_1 = 1
private const val TABLE_COOKIE = "OK_HTTP_3_COOKIE"
private const val COLUMN_ID = "_id"
private const val COLUMN_NAME = "NAME"
private const val COLUMN_VALUE = "VALUE"
private const val COLUMN_EXPIRES_AT = "EXPIRES_AT"
private const val COLUMN_DOMAIN = "DOMAIN"
private const val COLUMN_PATH = "PATH"
private const val COLUMN_SECURE = "SECURE"
private const val COLUMN_HTTP_ONLY = "HTTP_ONLY"
private const val COLUMN_PERSISTENT = "PERSISTENT"
private const val COLUMN_HOST_ONLY = "HOST_ONLY"
private const val DB_VERSION = VERSION_1
private fun getCookie(cursor: Cursor, now: Long): Cookie? {
val name = SqlUtils.getString(cursor, COLUMN_NAME, null)
val value = SqlUtils.getString(cursor, COLUMN_VALUE, null)
val expiresAt = SqlUtils.getLong(cursor, COLUMN_EXPIRES_AT, 0)
val domain = SqlUtils.getString(cursor, COLUMN_DOMAIN, null)
val path = SqlUtils.getString(cursor, COLUMN_PATH, null)
val secure = SqlUtils.getBoolean(cursor, COLUMN_SECURE, false)
val httpOnly = SqlUtils.getBoolean(cursor, COLUMN_HTTP_ONLY, false)
val persistent = SqlUtils.getBoolean(cursor, COLUMN_PERSISTENT, false)
val hostOnly = SqlUtils.getBoolean(cursor, COLUMN_HOST_ONLY, false)
if (name == null || domain == null || path == null) {
return null
}
// Check non-persistent or expired
if (!persistent || expiresAt <= now) {
return null
}
val builder = Cookie.Builder()
builder.name(name)
builder.value(value)
if (hostOnly) {
builder.hostOnlyDomain(domain)
} else {
builder.domain(domain)
}
builder.path(path)
builder.expiresAt(expiresAt)
if (secure) builder.secure()
if (httpOnly) builder.httpOnly()
return builder.build()
}
}
init {
helper = MSQLiteBuilder()
.version(VERSION_1)
.createTable(TABLE_COOKIE)
.insertColumn(TABLE_COOKIE, COLUMN_NAME, String::class.java)
.insertColumn(TABLE_COOKIE, COLUMN_VALUE, String::class.java)
.insertColumn(TABLE_COOKIE, COLUMN_EXPIRES_AT, Long::class.javaPrimitiveType)
.insertColumn(TABLE_COOKIE, COLUMN_DOMAIN, String::class.java)
.insertColumn(TABLE_COOKIE, COLUMN_PATH, String::class.java)
.insertColumn(TABLE_COOKIE, COLUMN_SECURE, Boolean::class.javaPrimitiveType)
.insertColumn(TABLE_COOKIE, COLUMN_HTTP_ONLY, Boolean::class.javaPrimitiveType)
.insertColumn(TABLE_COOKIE, COLUMN_PERSISTENT, Boolean::class.javaPrimitiveType)
.insertColumn(TABLE_COOKIE, COLUMN_HOST_ONLY, Boolean::class.javaPrimitiveType)
.build(context, name, DB_VERSION)
db = helper.writableDatabase
}
}
@@ -1,195 +0,0 @@
/*
* Copyright 2017 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.network;
/*
* Created by Hippo on 2017/9/4.
*/
import android.content.Context;
import androidx.annotation.NonNull;
import com.hippo.yorozuya.ObjectUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import okhttp3.Cookie;
import okhttp3.CookieJar;
import okhttp3.HttpUrl;
public class CookieRepository implements CookieJar {
/**
* Quick and dirty pattern to differentiate IP addresses from hostnames. This is an approximation
* of Android's private InetAddress#isNumeric API.
* <p>
* This matches IPv6 addresses as a hex string containing at least one colon, and possibly
* including dots after the first colon. It matches IPv4 addresses as strings containing only
* decimal digits and dots. This pattern matches strings like "a:.23" and "54" that are neither IP
* addresses nor hostnames; they will be verified as IP addresses (which is a more strict
* verification).
*/
private static final Pattern VERIFY_AS_IP_ADDRESS = Pattern.compile("([0-9a-fA-F]*:[0-9a-fA-F:.]*)|([\\d.]+)");
private final CookieDatabase db;
private final Map<String, CookieSet> map;
public CookieRepository(Context context, String name) {
db = new CookieDatabase(context, name);
map = db.getAllCookies();
}
/**
* Returns true if `host` is not a host name and might be an IP address.
*/
private static boolean verifyAsIpAddress(String host) {
return VERIFY_AS_IP_ADDRESS.matcher(host).matches();
}
// okhttp3.Cookie.domainMatch(HttpUrl, String)
protected static boolean domainMatch(HttpUrl url, String domain) {
String urlHost = url.host();
if (urlHost.equals(domain)) {
return true; // As in 'example.com' matching 'example.com'.
}
return urlHost.endsWith(domain)
&& urlHost.charAt(urlHost.length() - domain.length() - 1) == '.'
&& !verifyAsIpAddress(urlHost); // As in 'example.com' matching 'www.example.com'.
}
public synchronized void addCookie(Cookie cookie) {
// For cookie database
Cookie toAdd = null;
Cookie toUpdate = null;
Cookie toRemove = null;
CookieSet set = map.get(cookie.domain());
if (set == null) {
set = new CookieSet();
map.put(cookie.domain(), set);
}
if (cookie.expiresAt() <= System.currentTimeMillis()) {
toRemove = set.remove(cookie);
// If the cookie is not persistent, it's not in database
if (toRemove != null && !toRemove.persistent()) {
toRemove = null;
}
} else {
toAdd = cookie;
toUpdate = set.add(cookie);
// If the cookie is not persistent, it's not in database
if (!toAdd.persistent()) toAdd = null;
if (toUpdate != null && !toUpdate.persistent()) toUpdate = null;
// Remove the cookie if it updates to null
if (toAdd == null && toUpdate != null) {
toRemove = toUpdate;
toUpdate = null;
}
}
if (toRemove != null) {
db.remove(toRemove);
}
if (toAdd != null) {
if (toUpdate != null) {
db.update(toUpdate, toAdd);
} else {
db.add(toAdd);
}
}
}
public String getCookieHeader(HttpUrl url) {
List<Cookie> cookies = getCookies(url);
StringBuilder cookieHeader = new StringBuilder();
for (int i = 0, size = cookies.size(); i < size; i++) {
if (i > 0) {
cookieHeader.append("; ");
}
Cookie cookie = cookies.get(i);
cookieHeader.append(cookie.name()).append('=').append(cookie.value());
}
return cookieHeader.toString();
}
public synchronized List<Cookie> getCookies(HttpUrl url) {
List<Cookie> accepted = new ArrayList<>();
List<Cookie> expired = new ArrayList<>();
for (Map.Entry<String, CookieSet> entry : map.entrySet()) {
String domain = entry.getKey();
CookieSet cookieSet = entry.getValue();
if (domainMatch(url, domain)) {
cookieSet.get(url, accepted, expired);
}
}
for (Cookie cookie : expired) {
if (cookie.persistent()) {
db.remove(cookie);
}
}
// RFC 6265 Section-5.4 step 2, sort the cookie-list
// Cookies with longer paths are listed before cookies with shorter paths.
// Ignore creation-time, we don't store them.
Collections.sort(accepted, (o1, o2) -> o2.path().length() - o1.path().length());
return accepted;
}
public boolean contains(HttpUrl url, String name) {
for (Cookie cookie : getCookies(url)) {
if (ObjectUtils.equal(cookie.name(), name)) {
return true;
}
}
return false;
}
/**
* Remove all cookies in this {@code CookieRepository}.
*/
public synchronized void clear() {
map.clear();
db.clear();
}
public synchronized void close() {
db.close();
}
@Override
public void saveFromResponse(@NonNull HttpUrl httpUrl, List<Cookie> list) {
for (Cookie cookie : list) {
addCookie(cookie);
}
}
@NonNull
@Override
public List<Cookie> loadForRequest(@NonNull HttpUrl httpUrl) {
return getCookies(httpUrl);
}
}
@@ -0,0 +1,181 @@
/*
* Copyright 2017 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.network
import android.content.Context
import com.hippo.yorozuya.ObjectUtils
import okhttp3.Cookie
import okhttp3.CookieJar
import okhttp3.HttpUrl
import java.util.regex.Pattern
open class CookieRepository(context: Context, name: String) : CookieJar {
private val db: CookieDatabase
private val map: MutableMap<String, CookieSet>
@Synchronized
fun addCookie(cookie: Cookie) {
// For cookie database
var toAdd: Cookie? = null
var toUpdate: Cookie? = null
var toRemove: Cookie? = null
var set = map[cookie.domain]
if (set == null) {
set = CookieSet()
map[cookie.domain] = set
}
if (cookie.expiresAt <= System.currentTimeMillis()) {
toRemove = set.remove(cookie)
// If the cookie is not persistent, it's not in database
if (toRemove != null && !toRemove.persistent) {
toRemove = null
}
} else {
toAdd = cookie
toUpdate = set.add(cookie)
// If the cookie is not persistent, it's not in database
if (!toAdd.persistent) toAdd = null
if (toUpdate != null && !toUpdate.persistent) toUpdate = null
// Remove the cookie if it updates to null
if (toAdd == null && toUpdate != null) {
toRemove = toUpdate
toUpdate = null
}
}
if (toRemove != null) {
db.remove(toRemove)
}
if (toAdd != null) {
if (toUpdate != null) {
db.update(toUpdate, toAdd)
} else {
db.add(toAdd)
}
}
}
fun getCookieHeader(url: HttpUrl): String {
val cookies = getCookies(url)
val cookieHeader = StringBuilder()
var i = 0
val size = cookies.size
while (i < size) {
if (i > 0) {
cookieHeader.append("; ")
}
val cookie = cookies[i]
cookieHeader.append(cookie.name).append('=').append(cookie.value)
i++
}
return cookieHeader.toString()
}
@Synchronized
fun getCookies(url: HttpUrl): List<Cookie> {
val accepted: MutableList<Cookie> = ArrayList()
val expired: MutableList<Cookie> = ArrayList()
for ((domain, cookieSet) in map) {
if (domainMatch(url, domain)) {
cookieSet[url, accepted, expired]
}
}
for (cookie in expired) {
if (cookie.persistent) {
db.remove(cookie)
}
}
// RFC 6265 Section-5.4 step 2, sort the cookie-list
// Cookies with longer paths are listed before cookies with shorter paths.
// Ignore creation-time, we don't store them.
accepted.sortWith { o1: Cookie, o2: Cookie -> o2.path.length - o1.path.length }
return accepted
}
fun contains(url: HttpUrl, name: String?): Boolean {
for (cookie in getCookies(url)) {
if (ObjectUtils.equal(cookie.name, name)) {
return true
}
}
return false
}
/**
* Remove all cookies in this `CookieRepository`.
*/
@Synchronized
fun clear() {
map.clear()
db.clear()
}
@Synchronized
fun close() {
db.close()
}
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
for (cookie in cookies) {
addCookie(cookie)
}
}
override fun loadForRequest(url: HttpUrl): List<Cookie> {
return getCookies(url)
}
companion object {
/**
* Quick and dirty pattern to differentiate IP addresses from hostnames. This is an approximation
* of Android's private InetAddress#isNumeric API.
*
*
* This matches IPv6 addresses as a hex string containing at least one colon, and possibly
* including dots after the first colon. It matches IPv4 addresses as strings containing only
* decimal digits and dots. This pattern matches strings like "a:.23" and "54" that are neither IP
* addresses nor hostnames; they will be verified as IP addresses (which is a more strict
* verification).
*/
private val VERIFY_AS_IP_ADDRESS =
Pattern.compile("([0-9a-fA-F]*:[0-9a-fA-F:.]*)|([\\d.]+)")
/**
* Returns true if `host` is not a host name and might be an IP address.
*/
private fun verifyAsIpAddress(host: String): Boolean {
return VERIFY_AS_IP_ADDRESS.matcher(host).matches()
}
// okhttp3.Cookie.domainMatch(HttpUrl, String)
@JvmStatic
protected fun domainMatch(url: HttpUrl, domain: String?): Boolean {
val urlHost = url.host
return if (urlHost == domain) {
true // As in 'example.com' matching 'example.com'.
} else urlHost.endsWith(domain!!)
&& urlHost[urlHost.length - domain.length - 1] == '.' && !verifyAsIpAddress(
urlHost
)
// As in 'example.com' matching 'www.example.com'.
}
}
init {
db = CookieDatabase(context, name)
map = db.allCookies
}
}
@@ -1,106 +0,0 @@
/*
* Copyright 2017 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.network;
/*
* Created by Hippo on 2017/9/4.
*/
import com.hippo.util.HashCodeUtils;
import com.hippo.yorozuya.ObjectUtils;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import okhttp3.Cookie;
import okhttp3.HttpUrl;
class CookieSet {
private final Map<Key, Cookie> map = new HashMap<>();
/**
* Adds a cookie to this {@code CookieSet}.
* Returns a previous cookie with
* the same name, domain and path or {@code null}.
*/
public Cookie add(Cookie cookie) {
return map.put(new Key(cookie), cookie);
}
/**
* Removes a cookie with the same name,
* domain and path as the cookie.
* Returns the removed cookie or {@code null}.
*/
public Cookie remove(Cookie cookie) {
return map.remove(new Key(cookie));
}
/**
* Get cookies for the url. Fill {@code accepted} and {@code expired}.
*/
public void get(HttpUrl url, List<Cookie> accepted, List<Cookie> expired) {
long now = System.currentTimeMillis();
Iterator<Map.Entry<Key, Cookie>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Cookie cookie = iterator.next().getValue();
if (cookie.expiresAt() <= now) {
iterator.remove();
expired.add(cookie);
} else if (cookie.matches(url)) {
accepted.add(cookie);
}
}
}
static class Key {
private final String name;
private final String domain;
private final String path;
public Key(Cookie cookie) {
this.name = cookie.name();
this.domain = cookie.domain();
this.path = cookie.path();
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj instanceof Key) {
Key key = (Key) obj;
return ObjectUtils.equal(key.name, this.name) &&
ObjectUtils.equal(key.domain, this.domain) &&
ObjectUtils.equal(key.path, this.path);
}
return false;
}
@Override
public int hashCode() {
return HashCodeUtils.hashCode(name, domain, path);
}
}
}
@@ -0,0 +1,87 @@
/*
* Copyright 2017 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.network
import com.hippo.util.HashCodeUtils
import com.hippo.yorozuya.ObjectUtils
import okhttp3.Cookie
import okhttp3.HttpUrl
internal class CookieSet {
private val map: MutableMap<Key, Cookie> = HashMap()
/**
* Adds a cookie to this `CookieSet`.
* Returns a previous cookie with
* the same name, domain and path or `null`.
*/
fun add(cookie: Cookie): Cookie? {
return map.put(Key(cookie), cookie)
}
/**
* Removes a cookie with the same name,
* domain and path as the cookie.
* Returns the removed cookie or `null`.
*/
fun remove(cookie: Cookie): Cookie? {
return map.remove(Key(cookie))
}
/**
* Get cookies for the url. Fill `accepted` and `expired`.
*/
operator fun get(url: HttpUrl?, accepted: MutableList<Cookie>, expired: MutableList<Cookie>) {
val now = System.currentTimeMillis()
val iterator: MutableIterator<Map.Entry<Key, Cookie>> = map.entries.iterator()
while (iterator.hasNext()) {
val cookie = iterator.next().value
if (cookie.expiresAt <= now) {
iterator.remove()
expired.add(cookie)
} else if (cookie.matches(url!!)) {
accepted.add(cookie)
}
}
}
internal class Key(cookie: Cookie) {
private val name: String
private val domain: String
private val path: String
override fun equals(other: Any?): Boolean {
if (other === this) {
return true
}
if (other is Key) {
return ObjectUtils.equal(other.name, name) &&
ObjectUtils.equal(other.domain, domain) &&
ObjectUtils.equal(other.path, path)
}
return false
}
override fun hashCode(): Int {
return HashCodeUtils.hashCode(name, domain, path)
}
init {
name = cookie.name
domain = cookie.domain
path = cookie.path
}
}
}
@@ -1,67 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.network;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public final class InetValidator {
private static final String IPV4_REGEX =
"^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$";
private static final Pattern IPV4_PATTERN = Pattern.compile(IPV4_REGEX);
private InetValidator() {
}
public static boolean isValidInet4Address(String inet4Address) {
if (null == inet4Address) {
return false;
}
Matcher matcher = IPV4_PATTERN.matcher(inet4Address);
if (!matcher.find()) {
return false;
}
// verify that address subgroups are legal
for (int i = 1; i <= 4; i++) {
String ipSegment = matcher.group(i);
if (ipSegment == null || ipSegment.length() == 0) {
return false;
}
int iIpSegment;
try {
iIpSegment = Integer.parseInt(ipSegment);
} catch (NumberFormatException e) {
return false;
}
if (iIpSegment > 255) {
return false;
}
if (ipSegment.length() > 1 && ipSegment.startsWith("0")) {
return false;
}
}
return true;
}
public static boolean isValidInetPort(int inetPort) {
return inetPort >= 0 && inetPort <= 65535;
}
}
@@ -0,0 +1,59 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.network
import java.util.regex.Pattern
object InetValidator {
private const val IPV4_REGEX = "^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$"
private val IPV4_PATTERN = Pattern.compile(IPV4_REGEX)
@JvmStatic
fun isValidInet4Address(inet4Address: String?): Boolean {
if (null == inet4Address) {
return false
}
val matcher = IPV4_PATTERN.matcher(inet4Address)
if (!matcher.find()) {
return false
}
// verify that address subgroups are legal
for (i in 1..4) {
val ipSegment = matcher.group(i)
if (ipSegment == null || ipSegment.isEmpty()) {
return false
}
val iIpSegment: Int = try {
ipSegment.toInt()
} catch (e: NumberFormatException) {
return false
}
if (iIpSegment > 255) {
return false
}
if (ipSegment.length > 1 && ipSegment.startsWith("0")) {
return false
}
}
return true
}
@JvmStatic
fun isValidInetPort(inetPort: Int): Boolean {
return inetPort in 0..65535
}
}
@@ -1,90 +0,0 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.network;
import android.content.Context;
import android.content.res.Resources;
import android.util.SparseArray;
import com.hippo.ehviewer.R;
public class StatusCodeException extends Exception {
private static final SparseArray<String> ERROR_MESSAGE_ARRAY = new SparseArray<>(24);
private static final String DEFAULT_ERROR_MESSAGE = "Error response code";
private final int mResponseCode;
private final String mMessage;
public StatusCodeException(int responseCode) {
mResponseCode = responseCode;
mMessage = ERROR_MESSAGE_ARRAY.get(responseCode, DEFAULT_ERROR_MESSAGE);
}
public StatusCodeException(int responseCode, String message) {
mResponseCode = responseCode;
mMessage = message;
}
public static void initialize(Context context) {
Resources resources = context.getApplicationContext().getResources();
ERROR_MESSAGE_ARRAY.append(400, resources.getString(R.string.error_status_code_400));
ERROR_MESSAGE_ARRAY.append(401, resources.getString(R.string.error_status_code_401));
ERROR_MESSAGE_ARRAY.append(402, resources.getString(R.string.error_status_code_402));
ERROR_MESSAGE_ARRAY.append(403, resources.getString(R.string.error_status_code_403));
ERROR_MESSAGE_ARRAY.append(404, resources.getString(R.string.error_status_code_404));
ERROR_MESSAGE_ARRAY.append(405, resources.getString(R.string.error_status_code_405));
ERROR_MESSAGE_ARRAY.append(406, resources.getString(R.string.error_status_code_406));
ERROR_MESSAGE_ARRAY.append(407, resources.getString(R.string.error_status_code_407));
ERROR_MESSAGE_ARRAY.append(408, resources.getString(R.string.error_status_code_408));
ERROR_MESSAGE_ARRAY.append(409, resources.getString(R.string.error_status_code_409));
ERROR_MESSAGE_ARRAY.append(410, resources.getString(R.string.error_status_code_410));
ERROR_MESSAGE_ARRAY.append(411, resources.getString(R.string.error_status_code_411));
ERROR_MESSAGE_ARRAY.append(412, resources.getString(R.string.error_status_code_412));
ERROR_MESSAGE_ARRAY.append(413, resources.getString(R.string.error_status_code_413));
ERROR_MESSAGE_ARRAY.append(414, resources.getString(R.string.error_status_code_414));
ERROR_MESSAGE_ARRAY.append(415, resources.getString(R.string.error_status_code_415));
ERROR_MESSAGE_ARRAY.append(416, resources.getString(R.string.error_status_code_416));
ERROR_MESSAGE_ARRAY.append(417, resources.getString(R.string.error_status_code_417));
ERROR_MESSAGE_ARRAY.append(500, resources.getString(R.string.error_status_code_500));
ERROR_MESSAGE_ARRAY.append(501, resources.getString(R.string.error_status_code_501));
ERROR_MESSAGE_ARRAY.append(502, resources.getString(R.string.error_status_code_502));
ERROR_MESSAGE_ARRAY.append(503, resources.getString(R.string.error_status_code_503));
ERROR_MESSAGE_ARRAY.append(504, resources.getString(R.string.error_status_code_504));
ERROR_MESSAGE_ARRAY.append(505, resources.getString(R.string.error_status_code_505));
}
public int getResponseCode() {
return mResponseCode;
}
public boolean isIdentifiedResponseCode() {
return !DEFAULT_ERROR_MESSAGE.equals(mMessage);
}
@Override
public String getMessage() {
return mMessage;
}
@Override
public String getLocalizedMessage() {
return getMessage();
}
}
@@ -0,0 +1,66 @@
/*
* Copyright 2016 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.network
import android.content.Context
import android.util.SparseArray
import com.hippo.ehviewer.R
class StatusCodeException(val responseCode: Int) : Exception() {
override val message: String = ERROR_MESSAGE_ARRAY[responseCode, DEFAULT_ERROR_MESSAGE]
val isIdentifiedResponseCode: Boolean
get() = DEFAULT_ERROR_MESSAGE != message
override fun getLocalizedMessage(): String {
return message
}
companion object {
private val ERROR_MESSAGE_ARRAY = SparseArray<String>(24)
private const val DEFAULT_ERROR_MESSAGE = "Error response code"
@JvmStatic
fun initialize(context: Context) {
val resources = context.applicationContext.resources
ERROR_MESSAGE_ARRAY.append(400, resources.getString(R.string.error_status_code_400))
ERROR_MESSAGE_ARRAY.append(401, resources.getString(R.string.error_status_code_401))
ERROR_MESSAGE_ARRAY.append(402, resources.getString(R.string.error_status_code_402))
ERROR_MESSAGE_ARRAY.append(403, resources.getString(R.string.error_status_code_403))
ERROR_MESSAGE_ARRAY.append(404, resources.getString(R.string.error_status_code_404))
ERROR_MESSAGE_ARRAY.append(405, resources.getString(R.string.error_status_code_405))
ERROR_MESSAGE_ARRAY.append(406, resources.getString(R.string.error_status_code_406))
ERROR_MESSAGE_ARRAY.append(407, resources.getString(R.string.error_status_code_407))
ERROR_MESSAGE_ARRAY.append(408, resources.getString(R.string.error_status_code_408))
ERROR_MESSAGE_ARRAY.append(409, resources.getString(R.string.error_status_code_409))
ERROR_MESSAGE_ARRAY.append(410, resources.getString(R.string.error_status_code_410))
ERROR_MESSAGE_ARRAY.append(411, resources.getString(R.string.error_status_code_411))
ERROR_MESSAGE_ARRAY.append(412, resources.getString(R.string.error_status_code_412))
ERROR_MESSAGE_ARRAY.append(413, resources.getString(R.string.error_status_code_413))
ERROR_MESSAGE_ARRAY.append(414, resources.getString(R.string.error_status_code_414))
ERROR_MESSAGE_ARRAY.append(415, resources.getString(R.string.error_status_code_415))
ERROR_MESSAGE_ARRAY.append(416, resources.getString(R.string.error_status_code_416))
ERROR_MESSAGE_ARRAY.append(417, resources.getString(R.string.error_status_code_417))
ERROR_MESSAGE_ARRAY.append(500, resources.getString(R.string.error_status_code_500))
ERROR_MESSAGE_ARRAY.append(501, resources.getString(R.string.error_status_code_501))
ERROR_MESSAGE_ARRAY.append(502, resources.getString(R.string.error_status_code_502))
ERROR_MESSAGE_ARRAY.append(503, resources.getString(R.string.error_status_code_503))
ERROR_MESSAGE_ARRAY.append(504, resources.getString(R.string.error_status_code_504))
ERROR_MESSAGE_ARRAY.append(505, resources.getString(R.string.error_status_code_505))
}
}
}
@@ -1,57 +0,0 @@
/*
* Copyright (C) 2014-2015 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.network;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class UrlBuilder {
public String mRootUrl;
public Map<String, Object> mQueryMap = new HashMap<>();
public UrlBuilder(String rootUrl) {
mRootUrl = rootUrl;
}
public void addQuery(String key, Object value) {
mQueryMap.put(key, value);
}
public String build() {
if (mQueryMap.size() == 0) {
return mRootUrl;
} else {
StringBuilder sb = new StringBuilder(mRootUrl);
sb.append("?");
Iterator<String> iter = mQueryMap.keySet().iterator();
if (iter.hasNext()) {
String key = iter.next();
Object value = mQueryMap.get(key);
sb.append(key).append("=").append(value);
}
while (iter.hasNext()) {
String key = iter.next();
Object value = mQueryMap.get(key);
sb.append("&").append(key).append("=").append(value);
}
return sb.toString();
}
}
}
@@ -0,0 +1,44 @@
/*
* Copyright (C) 2014-2015 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.network
class UrlBuilder(private var mRootUrl: String) {
private var mQueryMap: MutableMap<String, Any> = HashMap()
fun addQuery(key: String, value: Any) {
mQueryMap[key] = value
}
fun build(): String {
return if (mQueryMap.isEmpty()) {
mRootUrl
} else {
val sb = StringBuilder(mRootUrl)
sb.append("?")
val iter: Iterator<String> = mQueryMap.keys.iterator()
if (iter.hasNext()) {
val key = iter.next()
val value = mQueryMap[key]
sb.append(key).append("=").append(value)
}
while (iter.hasNext()) {
val key = iter.next()
val value = mQueryMap[key]
sb.append("&").append(key).append("=").append(value)
}
sb.toString()
}
}
}
@@ -13,26 +13,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.okhttp
package com.hippo.okhttp;
import okhttp3.Request
import okhttp3.Request;
public class ChromeRequestBuilder extends Request.Builder {
private static final String CHROME_USER_AGENT =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36";
private static final String CHROME_ACCEPT =
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9";
private static final String CHROME_ACCEPT_LANGUAGE =
"en-US,en;q=0.5";
public ChromeRequestBuilder(String url) {
url(url);
addHeader("User-Agent", CHROME_USER_AGENT);
addHeader("Accept", CHROME_ACCEPT);
addHeader("Accept-Language", CHROME_ACCEPT_LANGUAGE);
open class ChromeRequestBuilder(url: String) : Request.Builder() {
companion object {
private const val CHROME_USER_AGENT =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36"
private const val CHROME_ACCEPT =
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
private const val CHROME_ACCEPT_LANGUAGE = "en-US,en;q=0.5"
}
}
init {
this.url(url)
this.addHeader("User-Agent", CHROME_USER_AGENT)
this.addHeader("Accept", CHROME_ACCEPT)
this.addHeader("Accept-Language", CHROME_ACCEPT_LANGUAGE)
}
}
@@ -18,6 +18,7 @@ package com.hippo.preference;
import android.content.Context;
import android.content.res.TypedArray;
import android.text.Html;
import android.text.method.LinkMovementMethod;
import android.util.AttributeSet;
import android.view.View;
@@ -26,7 +27,6 @@ import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import com.hippo.ehviewer.R;
import com.hippo.text.Html;
public class MessagePreference extends DialogPreference {
@@ -52,7 +52,7 @@ public class MessagePreference extends DialogPreference {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MessagePreference, defStyleAttr, defStyleRes);
String message = a.getString(R.styleable.MessagePreference_dialogMessage);
if (a.getBoolean(R.styleable.MessagePreference_dialogMessageHtml, false)) {
mDialogMessage = Html.fromHtml(message);
mDialogMessage = Html.fromHtml(message, Html.FROM_HTML_MODE_LEGACY);
} else {
mDialogMessage = message;
}
File diff suppressed because it is too large. Load diff
@@ -17,6 +17,7 @@
package com.hippo.text;
import android.graphics.drawable.Drawable;
import android.text.Html;
import com.hippo.conaco.Conaco;
import com.hippo.drawable.UnikeryDrawable;
@@ -1,85 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.tuxiang;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLDisplay;
// android-9.0.0_r30
public abstract class BaseConfigChooser implements EGLConfigChooser {
private final int mEGLContextClientVersion;
public BaseConfigChooser(int eglContextClientVersion, int[] configSpec) {
mEGLContextClientVersion = eglContextClientVersion;
mConfigSpec = filterConfigSpec(configSpec);
}
@Override
public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
final int[] num_config = new int[1];
if (!egl.eglChooseConfig(display, mConfigSpec, null, 0,
num_config)) {
throw new IllegalArgumentException("eglChooseConfig failed");
}
final int numConfigs = num_config[0];
if (numConfigs <= 0) {
throw new IllegalArgumentException(
"No configs match configSpec");
}
final EGLConfig[] configs = new EGLConfig[numConfigs];
if (!egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs,
num_config)) {
throw new IllegalArgumentException("eglChooseConfig#2 failed");
}
final EGLConfig config = chooseConfig(egl, display, configs);
if (config == null) {
throw new IllegalArgumentException("No config chosen");
}
return config;
}
public abstract EGLConfig chooseConfig(EGL10 egl, EGLDisplay display,
EGLConfig[] configs);
protected int[] mConfigSpec;
private int[] filterConfigSpec(int[] configSpec) {
if (mEGLContextClientVersion != 2 && mEGLContextClientVersion != 3) {
return configSpec;
}
/* We know none of the subclasses define EGL_RENDERABLE_TYPE.
* And we know the configSpec is well formed.
*/
final int len = configSpec.length;
final int[] newConfigSpec = new int[len + 2];
System.arraycopy(configSpec, 0, newConfigSpec, 0, len-1);
newConfigSpec[len-1] = EGL10.EGL_RENDERABLE_TYPE;
if (mEGLContextClientVersion == 2) {
newConfigSpec[len] = 0x0004; /* EGL_OPENGL_ES2_BIT */
} else {
newConfigSpec[len] = 0x0040; /* EGL_OPENGL_ES3_BIT_KHR */
}
newConfigSpec[len+1] = EGL10.EGL_NONE;
return newConfigSpec;
}
}
@@ -1,94 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.tuxiang;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLDisplay;
// android-9.0.0_r30
/**
* Choose a configuration with exactly the specified r,g,b,a sizes,
* and at least the specified depth and stencil sizes.
*/
public class ComponentSizeChooser extends BaseConfigChooser {
public ComponentSizeChooser(int eglContextClientVersion,
int redSize, int greenSize, int blueSize,
int alphaSize, int depthSize, int stencilSize) {
super(eglContextClientVersion, new int[] {
EGL10.EGL_RED_SIZE, redSize,
EGL10.EGL_GREEN_SIZE, greenSize,
EGL10.EGL_BLUE_SIZE, blueSize,
EGL10.EGL_ALPHA_SIZE, alphaSize,
EGL10.EGL_DEPTH_SIZE, depthSize,
EGL10.EGL_STENCIL_SIZE, stencilSize,
EGL10.EGL_NONE});
mValue = new int[1];
mRedSize = redSize;
mGreenSize = greenSize;
mBlueSize = blueSize;
mAlphaSize = alphaSize;
mDepthSize = depthSize;
mStencilSize = stencilSize;
}
@Override
public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display,
EGLConfig[] configs) {
for (final EGLConfig config : configs) {
final int d = findConfigAttrib(egl, display, config,
EGL10.EGL_DEPTH_SIZE, 0);
final int s = findConfigAttrib(egl, display, config,
EGL10.EGL_STENCIL_SIZE, 0);
if ((d >= mDepthSize) && (s >= mStencilSize)) {
final int r = findConfigAttrib(egl, display, config,
EGL10.EGL_RED_SIZE, 0);
final int g = findConfigAttrib(egl, display, config,
EGL10.EGL_GREEN_SIZE, 0);
final int b = findConfigAttrib(egl, display, config,
EGL10.EGL_BLUE_SIZE, 0);
final int a = findConfigAttrib(egl, display, config,
EGL10.EGL_ALPHA_SIZE, 0);
if ((r == mRedSize) && (g == mGreenSize)
&& (b == mBlueSize) && (a == mAlphaSize)) {
return config;
}
}
}
return null;
}
private int findConfigAttrib(EGL10 egl, EGLDisplay display,
EGLConfig config, int attribute, int defaultValue) {
if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) {
return mValue[0];
}
return defaultValue;
}
private final int[] mValue;
// Subclasses can adjust these values:
protected int mRedSize;
protected int mGreenSize;
protected int mBlueSize;
protected int mAlphaSize;
protected int mDepthSize;
protected int mStencilSize;
}
@@ -1,58 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.tuxiang;
import android.util.Log;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLContext;
import javax.microedition.khronos.egl.EGLDisplay;
// android-9.0.0_r30
public class DefaultContextFactory implements EGLContextFactory {
private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
private final int mEGLContextClientVersion;
public DefaultContextFactory(int eglContextClientVersion) {
mEGLContextClientVersion = eglContextClientVersion;
}
@Override
public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig config) {
final int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, mEGLContextClientVersion,
EGL10.EGL_NONE };
return egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT,
mEGLContextClientVersion != 0 ? attrib_list : null);
}
@Override
public void destroyContext(EGL10 egl, EGLDisplay display,
EGLContext context) {
if (!egl.eglDestroyContext(display, context)) {
Log.e("DefaultContextFactory", "display:" + display + " context: " + context);
if (GLStuff.LOG_THREADS) {
Log.i("DefaultContextFactory", "tid=" + Thread.currentThread().getId());
}
EglHelper.throwEglException("eglDestroyContex", egl.eglGetError());
}
}
}
@@ -1,53 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.tuxiang;
import android.util.Log;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLDisplay;
import javax.microedition.khronos.egl.EGLSurface;
// android-9.0.0_r30
public class DefaultWindowSurfaceFactory implements EGLWindowSurfaceFactory {
@Override
public EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display,
EGLConfig config, Object nativeWindow) {
EGLSurface result = null;
try {
result = egl.eglCreateWindowSurface(display, config, nativeWindow, null);
} catch (IllegalArgumentException e) {
// This exception indicates that the surface flinger surface
// is not valid. This can happen if the surface flinger surface has
// been torn down, but the application has not yet been
// notified via SurfaceHolder.Callback.surfaceDestroyed.
// In theory the application should be notified first,
// but in practice sometimes it is not. See b/4588890
Log.e("DefWinSurfaceFactory", "eglCreateWindowSurface", e);
}
return result;
}
@Override
public void destroySurface(EGL10 egl, EGLDisplay display,
EGLSurface surface) {
egl.eglDestroySurface(display, surface);
}
}
@@ -1,43 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.tuxiang;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLDisplay;
// android-9.0.0_r30
/**
* An interface for choosing an EGLConfig configuration from a list of
* potential configurations.
* <p>
* This interface must be implemented by clients wishing to call
* {@link GLStuff#setEGLConfigChooser(EGLConfigChooser)}
*/
public interface EGLConfigChooser {
/**
* Choose a configuration from the list. Implementors typically
* implement this method by calling
* {@link EGL10#eglChooseConfig} and iterating through the results. Please consult the
* EGL specification available from The Khronos Group to learn how to call eglChooseConfig.
* @param egl the EGL10 for the current display.
* @param display the current display.
* @return the chosen configuration.
*/
EGLConfig chooseConfig(EGL10 egl, EGLDisplay display);
}
@@ -1,38 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* An interface for customizing the eglCreateContext and eglDestroyContext calls.
*/
package com.hippo.tuxiang;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLContext;
import javax.microedition.khronos.egl.EGLDisplay;
// android-9.0.0_r30
/**
* An interface for customizing the eglCreateContext and eglDestroyContext calls.
* <p>
* This interface must be implemented by clients wishing to call
* {@link GLStuff#setEGLContextFactory(EGLContextFactory)}
*/
public interface EGLContextFactory {
EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig);
void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context);
}
@@ -1,39 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.tuxiang;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLDisplay;
import javax.microedition.khronos.egl.EGLSurface;
// android-9.0.0_r30
/**
* An interface for customizing the eglCreateWindowSurface and eglDestroySurface calls.
* <p>
* This interface must be implemented by clients wishing to call
* {@link GLStuff#setEGLWindowSurfaceFactory(EGLWindowSurfaceFactory)}
*/
public interface EGLWindowSurfaceFactory {
/**
* @return null if the surface cannot be constructed.
*/
EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display, EGLConfig config,
Object nativeWindow);
void destroySurface(EGL10 egl, EGLDisplay display, EGLSurface surface);
}
@@ -1,298 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.tuxiang;
import android.opengl.GLDebugHelper;
import android.opengl.GLUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import java.io.Writer;
import java.lang.ref.WeakReference;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLContext;
import javax.microedition.khronos.egl.EGLDisplay;
import javax.microedition.khronos.egl.EGLSurface;
import javax.microedition.khronos.opengles.GL;
// android-9.0.0_r30
/**
* An EGL helper class.
*/
class EglHelper {
public EglHelper(WeakReference<GLStuff> glStuffWeakRef) {
mGLStuffViewWeakRef = glStuffWeakRef;
}
/**
* Initialize EGL for a given configuration spec.
*/
public void start() {
if (GLStuff.LOG_EGL) {
Log.w("EglHelper", "start() tid=" + Thread.currentThread().getId());
}
/*
* Get an EGL instance
*/
mEgl = (EGL10) EGLContext.getEGL();
/*
* Get to the default display.
*/
mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
if (mEglDisplay == EGL10.EGL_NO_DISPLAY) {
throw new RuntimeException("eglGetDisplay failed");
}
/*
* We can now initialize EGL for that display
*/
final int[] version = new int[2];
if(!mEgl.eglInitialize(mEglDisplay, version)) {
throw new RuntimeException("eglInitialize failed");
}
final GLStuff stuff = mGLStuffViewWeakRef.get();
if (stuff == null) {
mEglConfig = null;
mEglContext = null;
} else {
mEglConfig = stuff.getEGLConfigChooser().chooseConfig(mEgl, mEglDisplay);
/*
* Create an EGL context. We want to do this as rarely as we can, because an
* EGL context is a somewhat heavy object.
*/
mEglContext = stuff.getEGLContextFactory().createContext(mEgl, mEglDisplay, mEglConfig);
}
if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) {
mEglContext = null;
throwEglException("createContext");
}
if (GLStuff.LOG_EGL) {
Log.w("EglHelper", "createContext " + mEglContext + " tid=" + Thread.currentThread().getId());
}
mEglSurface = null;
}
/**
* Create an egl surface for the current SurfaceHolder surface. If a surface
* already exists, destroy it before creating the new surface.
*
* @return true if the surface was created successfully.
*/
public boolean createSurface() {
if (GLStuff.LOG_EGL) {
Log.w("EglHelper", "createSurface() tid=" + Thread.currentThread().getId());
}
/*
* Check preconditions.
*/
if (mEgl == null) {
throw new RuntimeException("egl not initialized");
}
if (mEglDisplay == null) {
throw new RuntimeException("eglDisplay not initialized");
}
if (mEglConfig == null) {
throw new RuntimeException("mEglConfig not initialized");
}
/*
* The window size has changed, so we need to create a new
* surface.
*/
destroySurfaceImp();
/*
* Create an EGL surface we can render into.
*/
final GLStuff stuff = mGLStuffViewWeakRef.get();
if (stuff != null) {
mEglSurface = stuff.getEGLWindowSurfaceFactory().createWindowSurface(mEgl,
mEglDisplay, mEglConfig, stuff.getNativeWindow());
} else {
mEglSurface = null;
}
if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) {
final int error = mEgl.eglGetError();
if (error == EGL10.EGL_BAD_NATIVE_WINDOW) {
Log.e("EglHelper", "createWindowSurface returned EGL_BAD_NATIVE_WINDOW.");
}
return false;
}
/*
* Before we can issue GL commands, we need to make sure
* the context is current and bound to a surface.
*/
if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
/*
* Could not make the context current, probably because the underlying
* SurfaceView surface has been destroyed.
*/
logEglErrorAsWarning("EGLHelper", "eglMakeCurrent", mEgl.eglGetError());
return false;
}
return true;
}
/**
* Create a GL object for the current EGL context.
*/
GL createGL() {
GL gl = mEglContext.getGL();
final GLStuff stuff = mGLStuffViewWeakRef.get();
if (stuff != null) {
final GLWrapper glWrapper = stuff.getGLWrapper();
if (glWrapper != null) {
gl = glWrapper.wrap(gl);
}
final int debugFlags = stuff.getDebugFlags();
if ((debugFlags & (GLStuff.DEBUG_CHECK_GL_ERROR | GLStuff.DEBUG_LOG_GL_CALLS)) != 0) {
int configFlags = 0;
Writer log = null;
if ((debugFlags & GLStuff.DEBUG_CHECK_GL_ERROR) != 0) {
configFlags |= GLDebugHelper.CONFIG_CHECK_GL_ERROR;
}
if ((debugFlags & GLStuff.DEBUG_LOG_GL_CALLS) != 0) {
log = new LogWriter();
}
gl = GLDebugHelper.wrap(gl, configFlags, log);
}
}
return gl;
}
/**
* Display the current render surface.
* @return the EGL error code from eglSwapBuffers.
*/
public int swap() {
if (! mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
return mEgl.eglGetError();
}
return EGL10.EGL_SUCCESS;
}
public void destroySurface() {
if (GLStuff.LOG_EGL) {
Log.w("EglHelper", "destroySurface() tid=" + Thread.currentThread().getId());
}
destroySurfaceImp();
}
private void destroySurfaceImp() {
if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) {
mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE,
EGL10.EGL_NO_SURFACE,
EGL10.EGL_NO_CONTEXT);
final GLStuff stuff = mGLStuffViewWeakRef.get();
if (stuff != null) {
stuff.getEGLWindowSurfaceFactory().destroySurface(mEgl, mEglDisplay, mEglSurface);
}
mEglSurface = null;
}
}
public void finish() {
if (GLStuff.LOG_EGL) {
Log.w("EglHelper", "finish() tid=" + Thread.currentThread().getId());
}
if (mEglContext != null) {
final GLStuff stuff = mGLStuffViewWeakRef.get();
if (stuff != null) {
stuff.getEGLContextFactory().destroyContext(mEgl, mEglDisplay, mEglContext);
}
mEglContext = null;
}
if (mEglDisplay != null) {
mEgl.eglTerminate(mEglDisplay);
mEglDisplay = null;
}
}
private void throwEglException(String function) {
throwEglException(function, mEgl.eglGetError());
}
public static void throwEglException(String function, int error) {
final String message = formatEglError(function, error);
if (GLStuff.LOG_THREADS) {
Log.e("EglHelper", "throwEglException tid=" + Thread.currentThread().getId() + " "
+ message);
}
throw new RuntimeException(message);
}
public static void logEglErrorAsWarning(String tag, String function, int error) {
Log.w(tag, formatEglError(function, error));
}
public static String formatEglError(String function, int error) {
return function + " failed: " + GLUtils.getEGLErrorString(error);
}
private final WeakReference<GLStuff> mGLStuffViewWeakRef;
EGL10 mEgl;
EGLDisplay mEglDisplay;
EGLSurface mEglSurface;
EGLConfig mEglConfig;
EGLContext mEglContext;
static class LogWriter extends Writer {
@Override public void close() {
flushBuilder();
}
@Override public void flush() {
flushBuilder();
}
@Override public void write(@NonNull char[] buf, int offset, int count) {
for(int i = 0; i < count; i++) {
final char c = buf[offset + i];
if ( c == '\n') {
flushBuilder();
}
else {
mBuilder.append(c);
}
}
}
private void flushBuilder() {
if (mBuilder.length() > 0) {
Log.v("GLSurfaceView", mBuilder.toString());
mBuilder.delete(0, mBuilder.length());
}
}
private final StringBuilder mBuilder = new StringBuilder();
}
}
@@ -1,320 +0,0 @@
/*
* Copyright 2015 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.tuxiang;
import javax.microedition.khronos.opengles.GL;
// android-9.0.0_r30
public interface GLStuff {
boolean LOG_ATTACH_DETACH = false;
boolean LOG_THREADS = false;
boolean LOG_PAUSE_RESUME = false;
boolean LOG_SURFACE = false;
boolean LOG_RENDERER = false;
boolean LOG_RENDERER_DRAW_FRAME = false;
boolean LOG_EGL = false;
/**
* The renderer only renders
* when the surface is created, or when {@link #requestRender} is called.
*
* @see #getRenderMode()
* @see #setRenderMode(int)
* @see #requestRender()
*/
int RENDERMODE_WHEN_DIRTY = 0;
/**
* The renderer is called
* continuously to re-render the scene.
*
* @see #getRenderMode()
* @see #setRenderMode(int)
*/
int RENDERMODE_CONTINUOUSLY = 1;
/**
* Check glError() after every GL call and throw an exception if glError indicates
* that an error has occurred. This can be used to help track down which OpenGL ES call
* is causing an error.
*
* @see #getDebugFlags
* @see #setDebugFlags
*/
int DEBUG_CHECK_GL_ERROR = 1;
/**
* Log GL calls to the system log at "verbose" level with tag "GLSurfaceView".
*
* @see #getDebugFlags
* @see #setDebugFlags
*/
int DEBUG_LOG_GL_CALLS = 2;
/**
* Get the native window object
*/
Object getNativeWindow();
/**
* Set the glWrapper. If the glWrapper is not null, its
* {@link GLWrapper#wrap(GL)} method is called
* whenever a surface is created. A GLWrapper can be used to wrap
* the GL object that's passed to the renderer. Wrapping a GL
* object enables examining and modifying the behavior of the
* GL calls made by the renderer.
* <p>
* Wrapping is typically used for debugging purposes.
* <p>
* The default value is null.
* @param glWrapper the new GLWrapper
*/
void setGLWrapper(GLWrapper glWrapper);
/**
* @return Returns the {@code GLWrapper}
*/
GLWrapper getGLWrapper();
/**
* Set the debug flags to a new value. The value is
* constructed by OR-together zero or more
* of the DEBUG_CHECK_* constants. The debug flags take effect
* whenever a surface is created. The default value is zero.
* @param debugFlags the new debug flags
* @see #DEBUG_CHECK_GL_ERROR
* @see #DEBUG_LOG_GL_CALLS
*/
void setDebugFlags(int debugFlags);
/**
* Get the current value of the debug flags.
* @return the current value of the debug flags.
*/
int getDebugFlags();
/**
* Control whether the EGL context is preserved when the GLSurfaceView is paused and
* resumed.
* <p>
* If set to true, then the EGL context may be preserved when the GLSurfaceView is paused.
* <p>
* Prior to API level 11, whether the EGL context is actually preserved or not
* depends upon whether the Android device can support an arbitrary number of
* EGL contexts or not. Devices that can only support a limited number of EGL
* contexts must release the EGL context in order to allow multiple applications
* to share the GPU.
* <p>
* If set to false, the EGL context will be released when the GLSurfaceView is paused,
* and recreated when the GLSurfaceView is resumed.
* <p>
*
* The default is false.
*
* @param preserveOnPause preserve the EGL context when paused
*/
void setPreserveEGLContextOnPause(boolean preserveOnPause);
/**
* @return true if the EGL context will be preserved when paused
*/
boolean getPreserveEGLContextOnPause();
/**
* Set the renderer associated with this view. Also starts the thread that
* will call the renderer, which in turn causes the rendering to start.
* <p>This method should be called once and only once in the life-cycle of
* a GLSurfaceView.
* <p>The following GLSurfaceView methods can only be called <em>before</em>
* setRenderer is called:
* <ul>
* <li>{@link #setEGLConfigChooser(EGLConfigChooser)}
* </ul>
* <p>
* The following GLSurfaceView methods can only be called <em>after</em>
* setRenderer is called:
* <ul>
* <li>{@link #getRenderMode()}
* <li>{@link #onPause()}
* <li>{@link #onResume()}
* <li>{@link #queueEvent(Runnable)}
* <li>{@link #requestRender()}
* <li>{@link #setRenderMode(int)}
* </ul>
*
* @param renderer the renderer to use to perform OpenGL drawing.
*/
void setRenderer(Renderer renderer);
/**
* @return Returns the {@code Renderer}
*/
Renderer getRenderer();
/**
* Install a custom EGLContextFactory.
* <p>If this method is
* called, it must be called before {@link #setRenderer(Renderer)}
* is called.
* <p>
* If this method is not called, then by default
* a context will be created with no shared context and
* with a null attribute list.
*/
void setEGLContextFactory(EGLContextFactory factory);
/**
* @return Returns the {@code EGLWindowSurfaceFactory}
*/
EGLWindowSurfaceFactory getEGLWindowSurfaceFactory();
/**
* Install a custom EGLWindowSurfaceFactory.
* <p>If this method is
* called, it must be called before {@link #setRenderer(Renderer)}
* is called.
* <p>
* If this method is not called, then by default
* a window surface will be created with a null attribute list.
*/
void setEGLWindowSurfaceFactory(EGLWindowSurfaceFactory factory);
/**
* @return Returns the {@code EGLContextFactory}
*/
EGLContextFactory getEGLContextFactory();
/**
* Install a custom EGLConfigChooser.
* <p>If this method is
* called, it must be called before {@link #setRenderer(Renderer)}
* is called.
* <p>
* If no setEGLConfigChooser method is called, then by default the
* view will choose an EGLConfig that is compatible with the current
* android.view.Surface, with a depth buffer depth of
* at least 16 bits.
*/
void setEGLConfigChooser(EGLConfigChooser configChooser);
/**
* @return Returns the {@code EGLConfigChooser}
*/
EGLConfigChooser getEGLConfigChooser();
/**
* Inform the default EGLContextFactory and default EGLConfigChooser
* which EGLContext client version to pick.
* <p>Use this method to create an OpenGL ES 2.0-compatible context.
* Example:
* <pre class="prettyprint">
* public MyView(Context context) {
* super(context);
* setEGLContextClientVersion(2); // Pick an OpenGL ES 2.0 context.
* setRenderer(new MyRenderer());
* }
* </pre>
* <p>Note: Activities which require OpenGL ES 2.0 should indicate this by
* setting @lt;uses-feature android:glEsVersion="0x00020000" /> in the activity's
* AndroidManifest.xml file.
* <p>If this method is called, it must be called before {@link #setRenderer(Renderer)}
* is called.
* <p>This method only affects the behavior of the default EGLContexFactory and the
* default EGLConfigChooser. If
* {@link #setEGLContextFactory(EGLContextFactory)} has been called, then the supplied
* EGLContextFactory is responsible for creating an OpenGL ES 2.0-compatible context.
* If
* {@link #setEGLConfigChooser(EGLConfigChooser)} has been called, then the supplied
* EGLConfigChooser is responsible for choosing an OpenGL ES 2.0-compatible config.
* @param version The EGLContext client version to choose. Use 2 for OpenGL ES 2.0
*/
void setEGLContextClientVersion(int version);
/**
* Get EGLContextClientVersion
*/
int getEGLContextClientVersion();
/**
* Set the rendering mode. When renderMode is
* RENDERMODE_CONTINUOUSLY, the renderer is called
* repeatedly to re-render the scene. When renderMode
* is RENDERMODE_WHEN_DIRTY, the renderer only rendered when the surface
* is created, or when {@link #requestRender} is called. Defaults to RENDERMODE_CONTINUOUSLY.
* <p>
* Using RENDERMODE_WHEN_DIRTY can improve battery life and overall system performance
* by allowing the GPU and CPU to idle when the view does not need to be updated.
* <p>
* This method can only be called after {@link #setRenderer(Renderer)}
*
* @param renderMode one of the RENDERMODE_X constants
* @see #RENDERMODE_CONTINUOUSLY
* @see #RENDERMODE_WHEN_DIRTY
*/
void setRenderMode(int renderMode);
/**
* Get the current rendering mode. May be called
* from any thread. Must not be called before a renderer has been set.
* @return the current rendering mode.
* @see #RENDERMODE_CONTINUOUSLY
* @see #RENDERMODE_WHEN_DIRTY
*/
int getRenderMode();
/**
* Request that the renderer render a frame.
* This method is typically used when the render mode has been set to
* {@link #RENDERMODE_WHEN_DIRTY}, so that frames are only rendered on demand.
* May be called
* from any thread. Must not be called before a renderer has been set.
*/
void requestRender();
/**
* Pause the rendering thread, optionally tearing down the EGL context
* depending upon the value of {@link #setPreserveEGLContextOnPause(boolean)}.
*
* This method should be called when it is no longer desirable for the
* GLSurfaceView to continue rendering, such as in response to
* {@link android.app.Activity#onStop Activity.onStop}.
*
* Must not be called before a renderer has been set.
*/
void onPause();
/**
* Resumes the rendering thread, re-creating the OpenGL context if necessary. It
* is the counterpart to {@link #onPause()}.
*
* This method should typically be called in
* {@link android.app.Activity#onStart Activity.onStart}.
*
* Must not be called before a renderer has been set.
*/
void onResume();
/**
* Queue a runnable to be run on the GL rendering thread. This can be used
* to communicate with the Renderer on the rendering thread.
* Must not be called before a renderer has been set.
* @param r the runnable to be run on the GL rendering thread.
*/
void queueEvent(Runnable r);
}
@@ -1,434 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.tuxiang;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.lang.ref.WeakReference;
// android-9.0.0_r30
/**
* An implementation of SurfaceView that uses the dedicated surface for
* displaying OpenGL rendering.
* <p>
* A GLSurfaceView provides the following features:
* <p>
* <ul>
* <li>Manages a surface, which is a special piece of memory that can be
* composited into the Android view system.
* <li>Manages an EGL display, which enables OpenGL to render into a surface.
* <li>Accepts a user-provided Renderer object that does the actual rendering.
* <li>Renders on a dedicated thread to decouple rendering performance from the
* UI thread.
* <li>Supports both on-demand and continuous rendering.
* <li>Optionally wraps, traces, and/or error-checks the renderer's OpenGL calls.
* </ul>
*
* <div class="special reference">
* <h3>Developer Guides</h3>
* <p>For more information about how to use OpenGL, read the
* <a href="{@docRoot}guide/topics/graphics/opengl.html">OpenGL</a> developer guide.</p>
* </div>
*
* <h3>Using GLSurfaceView</h3>
* <p>
* Typically you use GLSurfaceView by subclassing it and overriding one or more of the
* View system input event methods. If your application does not need to override event
* methods then GLSurfaceView can be used as-is. For the most part
* GLSurfaceView behavior is customized by calling "set" methods rather than by subclassing.
* For example, unlike a regular View, drawing is delegated to a separate Renderer object which
* is registered with the GLSurfaceView
* using the {@link #setRenderer(Renderer)} call.
* <p>
* <h3>Initializing GLSurfaceView</h3>
* All you have to do to initialize a GLSurfaceView is call {@link #setRenderer(Renderer)}.
* However, if desired, you can modify the default behavior of GLSurfaceView by calling one or
* more of these methods before calling setRenderer:
* <ul>
* <li>{@link #setDebugFlags(int)}
* <li>{@link #setEGLConfigChooser(EGLConfigChooser)}
* <li>{@link #setGLWrapper(GLWrapper)}
* </ul>
* <p>
* <h4>Specifying the android.view.Surface</h4>
* By default GLSurfaceView will create a PixelFormat.RGB_888 format surface. If a translucent
* surface is required, call getHolder().setFormat(PixelFormat.TRANSLUCENT).
* The exact format of a TRANSLUCENT surface is device dependent, but it will be
* a 32-bit-per-pixel surface with 8 bits per component.
* <p>
* <h4>Choosing an EGL Configuration</h4>
* A given Android device may support multiple EGLConfig rendering configurations.
* The available configurations may differ in how many channels of data are present, as
* well as how many bits are allocated to each channel. Therefore, the first thing
* GLSurfaceView has to do when starting to render is choose what EGLConfig to use.
* <p>
* By default GLSurfaceView chooses a EGLConfig that has an RGB_888 pixel format,
* with at least a 16-bit depth buffer and no stencil.
* <p>
* If you would prefer a different EGLConfig
* you can override the default behavior by calling one of the
* setEGLConfigChooser methods.
* <p>
* <h4>Debug Behavior</h4>
* You can optionally modify the behavior of GLSurfaceView by calling
* one or more of the debugging methods {@link #setDebugFlags(int)},
* and {@link #setGLWrapper}. These methods may be called before and/or after setRenderer, but
* typically they are called before setRenderer so that they take effect immediately.
* <p>
* <h4>Setting a Renderer</h4>
* Finally, you must call {@link #setRenderer} to register a {@link Renderer}.
* The renderer is
* responsible for doing the actual OpenGL rendering.
* <p>
* <h3>Rendering Mode</h3>
* Once the renderer is set, you can control whether the renderer draws
* continuously or on-demand by calling
* {@link #setRenderMode}. The default is continuous rendering.
* <p>
* <h3>Activity Life-cycle</h3>
* A GLSurfaceView must be notified when to pause and resume rendering. GLSurfaceView clients
* are required to call {@link #onPause()} when the activity stops and
* {@link #onResume()} when the activity starts. These calls allow GLSurfaceView to
* pause and resume the rendering thread, and also allow GLSurfaceView to release and recreate
* the OpenGL display.
* <p>
* <h3>Handling events</h3>
* <p>
* To handle an event you will typically subclass GLSurfaceView and override the
* appropriate method, just as you would with any other View. However, when handling
* the event, you may need to communicate with the Renderer object
* that's running in the rendering thread. You can do this using any
* standard Java cross-thread communication mechanism. In addition,
* one relatively easy way to communicate with your renderer is
* to call
* {@link #queueEvent(Runnable)}. For example:
* <pre class="prettyprint">
* class MyGLSurfaceView extends GLSurfaceView {
*
* private MyRenderer mMyRenderer;
*
* public void start() {
* mMyRenderer = ...;
* setRenderer(mMyRenderer);
* }
*
* public boolean onKeyDown(int keyCode, KeyEvent event) {
* if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
* queueEvent(new Runnable() {
* // This method will be called on the rendering
* // thread:
* public void run() {
* mMyRenderer.handleDpadCenter();
* }});
* return true;
* }
* return super.onKeyDown(keyCode, event);
* }
* }
* </pre>
*
*/
public class GLSurfaceView extends SurfaceView implements GLStuff, SurfaceHolder.Callback2 {
private final static String TAG = "GLSurfaceView";
/**
* Standard View constructor. In order to render something, you
* must call {@link #setRenderer} to register a renderer.
*/
public GLSurfaceView(Context context) {
super(context);
init();
}
/**
* Standard View constructor. In order to render something, you
* must call {@link #setRenderer} to register a renderer.
*/
public GLSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
@Override
protected void finalize() throws Throwable {
try {
if (mGLThread != null) {
// GLThread may still be running if this view was never
// attached to a window.
mGLThread.requestExitAndWait();
}
} finally {
super.finalize();
}
}
private void init() {
// Install a SurfaceHolder.Callback so we get notified when the
// underlying surface is created and destroyed
SurfaceHolder holder = getHolder();
holder.addCallback(this);
// setFormat is done by SurfaceView in SDK 2.3 and newer. Uncomment
// this statement if back-porting to 2.2 or older:
// holder.setFormat(PixelFormat.RGB_565);
//
// setType is not needed for SDK 2.0 or newer. Uncomment this
// statement if back-porting this code to older SDKs.
// holder.setType(SurfaceHolder.SURFACE_TYPE_GPU);
}
@Override
public Object getNativeWindow() {
return getHolder();
}
@Override
public void setGLWrapper(GLWrapper glWrapper) {
mGLWrapper = glWrapper;
}
@Override
public GLWrapper getGLWrapper() {
return mGLWrapper;
}
@Override
public void setDebugFlags(int debugFlags) {
mDebugFlags = debugFlags;
}
@Override
public int getDebugFlags() {
return mDebugFlags;
}
@Override
public void setPreserveEGLContextOnPause(boolean preserveOnPause) {
mPreserveEGLContextOnPause = preserveOnPause;
}
@Override
public boolean getPreserveEGLContextOnPause() {
return mPreserveEGLContextOnPause;
}
private void checkRenderThreadState() {
if (mGLThread != null) {
throw new IllegalStateException(
"setRenderer has already been called for this instance.");
}
}
@Override
public void setRenderer(Renderer renderer) {
checkRenderThreadState();
if (mEGLConfigChooser == null) {
mEGLConfigChooser = new SimpleEGLConfigChooser(mEGLContextClientVersion, true);
}
if (mEGLContextFactory == null) {
mEGLContextFactory = new DefaultContextFactory(mEGLContextClientVersion);
}
if (mEGLWindowSurfaceFactory == null) {
mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory();
}
mRenderer = renderer;
mGLThread = new GLThread(mThisWeakRef);
mGLThread.start();
}
@Override
public Renderer getRenderer() {
return mRenderer;
}
@Override
public void setEGLContextFactory(EGLContextFactory factory) {
checkRenderThreadState();
mEGLContextFactory = factory;
}
@Override
public EGLContextFactory getEGLContextFactory() {
return mEGLContextFactory;
}
@Override
public void setEGLWindowSurfaceFactory(EGLWindowSurfaceFactory factory) {
checkRenderThreadState();
mEGLWindowSurfaceFactory = factory;
}
@Override
public EGLWindowSurfaceFactory getEGLWindowSurfaceFactory() {
return mEGLWindowSurfaceFactory;
}
@Override
public void setEGLConfigChooser(EGLConfigChooser configChooser) {
checkRenderThreadState();
mEGLConfigChooser = configChooser;
}
@Override
public EGLConfigChooser getEGLConfigChooser() {
return mEGLConfigChooser;
}
@Override
public void setEGLContextClientVersion(int version) {
checkRenderThreadState();
mEGLContextClientVersion = version;
}
@Override
public int getEGLContextClientVersion() {
return mEGLContextClientVersion;
}
@Override
public void setRenderMode(int renderMode) {
mGLThread.setRenderMode(renderMode);
}
@Override
public int getRenderMode() {
return mGLThread.getRenderMode();
}
@Override
public void requestRender() {
mGLThread.requestRender();
}
/**
* This method is part of the SurfaceHolder.Callback2 interface, and is
* not normally called or subclassed by clients of GLSurfaceView.
*/
@Override
public void surfaceCreated(SurfaceHolder holder) {
mGLThread.surfaceCreated();
}
/**
* This method is part of the SurfaceHolder.Callback2 interface, and is
* not normally called or subclassed by clients of GLSurfaceView.
*/
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// Surface will be destroyed when we return
mGLThread.surfaceDestroyed();
}
/**
* This method is part of the SurfaceHolder.Callback2 interface, and is
* not normally called or subclassed by clients of GLSurfaceView.
*/
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
mGLThread.onWindowResize(w, h);
}
/**
* This method is part of the SurfaceHolder.Callback2 interface, and is
* not normally called or subclassed by clients of GLSurfaceView.
*/
@Override
public void surfaceRedrawNeededAsync(SurfaceHolder holder, Runnable finishDrawing) {
if (mGLThread != null) {
mGLThread.requestRenderAndNotify(finishDrawing);
}
}
/**
* This method is part of the SurfaceHolder.Callback2 interface, and is
* not normally called or subclassed by clients of GLSurfaceView.
*/
@Override
public void surfaceRedrawNeeded(SurfaceHolder holder) {
if (mGLThread != null) {
mGLThread.requestRenderAndWait();
}
}
@Override
public void onPause() {
mGLThread.onPause();
}
@Override
public void onResume() {
mGLThread.onResume();
}
@Override
public void queueEvent(Runnable r) {
mGLThread.queueEvent(r);
}
/**
* This method is used as part of the View class and is not normally
* called or subclassed by clients of GLSurfaceView.
*/
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (LOG_ATTACH_DETACH) {
Log.d(TAG, "onAttachedToWindow reattach =" + mDetached);
}
if (mDetached && (mRenderer != null)) {
int renderMode = RENDERMODE_CONTINUOUSLY;
if (mGLThread != null) {
renderMode = mGLThread.getRenderMode();
}
mGLThread = new GLThread(mThisWeakRef);
if (renderMode != RENDERMODE_CONTINUOUSLY) {
mGLThread.setRenderMode(renderMode);
}
mGLThread.start();
}
mDetached = false;
}
@Override
protected void onDetachedFromWindow() {
if (LOG_ATTACH_DETACH) {
Log.d(TAG, "onDetachedFromWindow");
}
if (mGLThread != null) {
mGLThread.requestExitAndWait();
}
mDetached = true;
super.onDetachedFromWindow();
}
private final WeakReference<GLStuff> mThisWeakRef =
new WeakReference<>((GLStuff) this);
private GLThread mGLThread;
private Renderer mRenderer;
private boolean mDetached;
private EGLConfigChooser mEGLConfigChooser;
private EGLContextFactory mEGLContextFactory;
private EGLWindowSurfaceFactory mEGLWindowSurfaceFactory;
private GLWrapper mGLWrapper;
private int mDebugFlags;
private int mEGLContextClientVersion;
private boolean mPreserveEGLContextOnPause;
}
@@ -1,292 +0,0 @@
/*
* Copyright 2022 Tarsin Norbin
*
* This file is part of EhViewer
*
* EhViewer is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* EhViewer is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with EhViewer.
* If not, see <https://www.gnu.org/licenses/>.
*/
package com.hippo.tuxiang;
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.util.AttributeSet;
import android.util.Log;
import android.view.TextureView;
import java.lang.ref.WeakReference;
/**
* TextureView version {@link GLSurfaceView}
*/
public class GLTextureView extends TextureView implements GLStuff, TextureView.SurfaceTextureListener {
private final static String TAG = "GLSurfaceView";
public GLTextureView(Context context) {
super(context);
init();
}
public GLTextureView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public GLTextureView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@Override
protected void finalize() throws Throwable {
try {
if (mGLThread != null) {
// GLThread may still be running if this view was never
// attached to a window.
mGLThread.requestExitAndWait();
}
} finally {
super.finalize();
}
}
private void init() {
setSurfaceTextureListener(this);
}
@Override
public Object getNativeWindow() {
return getSurfaceTexture();
}
@Override
public void setGLWrapper(GLWrapper glWrapper) {
mGLWrapper = glWrapper;
}
@Override
public GLWrapper getGLWrapper() {
return mGLWrapper;
}
@Override
public void setDebugFlags(int debugFlags) {
mDebugFlags = debugFlags;
}
@Override
public int getDebugFlags() {
return mDebugFlags;
}
@Override
public void setPreserveEGLContextOnPause(boolean preserveOnPause) {
mPreserveEGLContextOnPause = preserveOnPause;
}
@Override
public boolean getPreserveEGLContextOnPause() {
return mPreserveEGLContextOnPause;
}
private void checkRenderThreadState() {
if (mGLThread != null) {
throw new IllegalStateException(
"setRenderer has already been called for this instance.");
}
}
@Override
public void setRenderer(Renderer renderer) {
checkRenderThreadState();
if (mEGLConfigChooser == null) {
mEGLConfigChooser = new SimpleEGLConfigChooser(mEGLContextClientVersion, true);
}
if (mEGLContextFactory == null) {
mEGLContextFactory = new DefaultContextFactory(mEGLContextClientVersion);
}
if (mEGLWindowSurfaceFactory == null) {
mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory();
}
mRenderer = renderer;
mGLThread = new GLThread(mThisWeakRef);
mGLThread.start();
}
@Override
public Renderer getRenderer() {
return mRenderer;
}
@Override
public void setEGLContextFactory(EGLContextFactory factory) {
checkRenderThreadState();
mEGLContextFactory = factory;
}
@Override
public EGLContextFactory getEGLContextFactory() {
return mEGLContextFactory;
}
@Override
public void setEGLWindowSurfaceFactory(EGLWindowSurfaceFactory factory) {
checkRenderThreadState();
mEGLWindowSurfaceFactory = factory;
}
@Override
public EGLWindowSurfaceFactory getEGLWindowSurfaceFactory() {
return mEGLWindowSurfaceFactory;
}
@Override
public void setEGLConfigChooser(EGLConfigChooser configChooser) {
checkRenderThreadState();
mEGLConfigChooser = configChooser;
}
@Override
public EGLConfigChooser getEGLConfigChooser() {
return mEGLConfigChooser;
}
@Override
public void setEGLContextClientVersion(int version) {
checkRenderThreadState();
mEGLContextClientVersion = version;
}
@Override
public int getEGLContextClientVersion() {
return mEGLContextClientVersion;
}
@Override
public void setRenderMode(int renderMode) {
mGLThread.setRenderMode(renderMode);
}
@Override
public int getRenderMode() {
return mGLThread.getRenderMode();
}
@Override
public void requestRender() {
mGLThread.requestRender();
}
/**
* This method is part of the {@link android.view.TextureView.SurfaceTextureListener}
* interface, and is not normally called or subclassed by clients of GLTextureView.
*/
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
mGLThread.surfaceCreated();
mGLThread.onWindowResize(width, height);
}
/**
* This method is part of the {@link android.view.TextureView.SurfaceTextureListener}
* interface, and is not normally called or subclassed by clients of GLTextureView.
*/
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
mGLThread.onWindowResize(width, height);
}
/**
* This method is part of the {@link android.view.TextureView.SurfaceTextureListener}
* interface, and is not normally called or subclassed by clients of GLTextureView.
*/
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
// Surface will be destroyed when we return
mGLThread.surfaceDestroyed();
return true;
}
/**
* This method is part of the {@link android.view.TextureView.SurfaceTextureListener}
* interface, and is not normally called or subclassed by clients of GLTextureView.
*/
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
requestRender();
}
@Override
public void onPause() {
mGLThread.onPause();
}
@Override
public void onResume() {
mGLThread.onResume();
}
@Override
public void queueEvent(Runnable r) {
mGLThread.queueEvent(r);
}
/**
* This method is used as part of the View class and is not normally
* called or subclassed by clients of GLTextureView.
*/
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (LOG_ATTACH_DETACH) {
Log.d(TAG, "onAttachedToWindow reattach =" + mDetached);
}
if (mDetached && (mRenderer != null)) {
int renderMode = RENDERMODE_CONTINUOUSLY;
if (mGLThread != null) {
renderMode = mGLThread.getRenderMode();
}
mGLThread = new GLThread(mThisWeakRef);
if (renderMode != RENDERMODE_CONTINUOUSLY) {
mGLThread.setRenderMode(renderMode);
}
mGLThread.start();
}
mDetached = false;
}
@Override
protected void onDetachedFromWindow() {
if (LOG_ATTACH_DETACH) {
Log.d(TAG, "onDetachedFromWindow");
}
if (mGLThread != null) {
mGLThread.requestExitAndWait();
}
mDetached = true;
super.onDetachedFromWindow();
}
private final WeakReference<GLStuff> mThisWeakRef =
new WeakReference<>((GLStuff) this);
private GLThread mGLThread;
private Renderer mRenderer;
private boolean mDetached;
private EGLConfigChooser mEGLConfigChooser;
private EGLContextFactory mEGLContextFactory;
private EGLWindowSurfaceFactory mEGLWindowSurfaceFactory;
private GLWrapper mGLWrapper;
private int mDebugFlags;
private int mEGLContextClientVersion;
private boolean mPreserveEGLContextOnPause;
}
@@ -1,686 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.tuxiang;
import android.util.Log;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGL11;
import javax.microedition.khronos.opengles.GL10;
// android-9.0.0_r30
/**
* A generic GL Thread. Takes care of initializing EGL and GL. Delegates
* to a Renderer instance to do the actual drawing. Can be configured to
* render continuously or on request.
*
* All potentially blocking synchronization is done through the
* sGLThreadManager object. This avoids multiple-lock ordering issues.
*
*/
class GLThread extends Thread {
GLThread(WeakReference<GLStuff> glStuffWeakRef) {
super();
mWidth = 0;
mHeight = 0;
mRequestRender = true;
mRenderMode = GLStuff.RENDERMODE_CONTINUOUSLY;
mWantRenderNotification = false;
mGLStuffWeakRef = glStuffWeakRef;
}
@Override
public void run() {
setName("GLThread " + getId());
if (GLStuff.LOG_THREADS) {
Log.i("GLThread", "starting tid=" + getId());
}
GLStuff stuff;
stuff = mGLStuffWeakRef.get();
if (stuff != null) {
stuff.getRenderer().onGLThreadStart();
stuff = null;
}
try {
guardedRun();
} catch (InterruptedException e) {
// fall thru and exit normally
} finally {
stuff = mGLStuffWeakRef.get();
if (stuff != null) {
stuff.getRenderer().onGLThreadExit();
stuff = null;
}
sGLThreadManager.threadExiting(this);
}
}
/*
* This private method should only be called inside a
* synchronized(sGLThreadManager) block.
*/
private void stopEglSurfaceLocked() {
if (mHaveEglSurface) {
mHaveEglSurface = false;
mEglHelper.destroySurface();
}
}
/*
* This private method should only be called inside a
* synchronized(sGLThreadManager) block.
*/
private void stopEglContextLocked() {
if (mHaveEglContext) {
mEglHelper.finish();
mHaveEglContext = false;
sGLThreadManager.releaseEglContextLocked(this);
}
}
private void guardedRun() throws InterruptedException {
mEglHelper = new EglHelper(mGLStuffWeakRef);
mHaveEglContext = false;
mHaveEglSurface = false;
mWantRenderNotification = false;
try {
GL10 gl = null;
boolean createEglContext = false;
boolean createEglSurface = false;
boolean createGlInterface = false;
boolean lostEglContext = false;
boolean sizeChanged = false;
boolean wantRenderNotification = false;
boolean doRenderNotification = false;
boolean askedToReleaseEglContext = false;
int w = 0;
int h = 0;
Runnable event = null;
Runnable finishDrawingRunnable = null;
while (true) {
synchronized (sGLThreadManager) {
while (true) {
if (mShouldExit) {
return;
}
if (! mEventQueue.isEmpty()) {
event = mEventQueue.remove(0);
break;
}
// Update the pause state.
boolean pausing = false;
if (mPaused != mRequestPaused) {
pausing = mRequestPaused;
mPaused = mRequestPaused;
sGLThreadManager.notifyAll();
if (GLStuff.LOG_PAUSE_RESUME) {
Log.i("GLThread", "mPaused is now " + mPaused + " tid=" + getId());
}
GLStuff stuff = mGLStuffWeakRef.get();
if (stuff != null) {
if (pausing) {
stuff.getRenderer().onGLThreadPause();
} else {
stuff.getRenderer().onGLThreadResume();
}
stuff = null;
}
}
// Do we need to give up the EGL context?
if (mShouldReleaseEglContext) {
if (GLStuff.LOG_SURFACE) {
Log.i("GLThread", "releasing EGL context because asked to tid=" + getId());
}
stopEglSurfaceLocked();
stopEglContextLocked();
mShouldReleaseEglContext = false;
askedToReleaseEglContext = true;
}
// Have we lost the EGL context?
if (lostEglContext) {
stopEglSurfaceLocked();
stopEglContextLocked();
lostEglContext = false;
}
// When pausing, release the EGL surface:
if (pausing && mHaveEglSurface) {
if (GLStuff.LOG_SURFACE) {
Log.i("GLThread", "releasing EGL surface because paused tid=" + getId());
}
stopEglSurfaceLocked();
}
// When pausing, optionally release the EGL Context:
if (pausing && mHaveEglContext) {
GLStuff stuff = mGLStuffWeakRef.get();
boolean preserveEglContextOnPause = stuff == null ?
false : stuff.getPreserveEGLContextOnPause();
if (!preserveEglContextOnPause) {
stopEglContextLocked();
if (GLStuff.LOG_SURFACE) {
Log.i("GLThread", "releasing EGL context because paused tid=" + getId());
}
}
}
// Have we lost the SurfaceView surface?
if ((! mHasSurface) && (! mWaitingForSurface)) {
if (GLStuff.LOG_SURFACE) {
Log.i("GLThread", "noticed surfaceView surface lost tid=" + getId());
}
if (mHaveEglSurface) {
stopEglSurfaceLocked();
}
mWaitingForSurface = true;
mSurfaceIsBad = false;
sGLThreadManager.notifyAll();
}
// Have we acquired the surface view surface?
if (mHasSurface && mWaitingForSurface) {
if (GLStuff.LOG_SURFACE) {
Log.i("GLThread", "noticed surfaceView surface acquired tid=" + getId());
}
mWaitingForSurface = false;
sGLThreadManager.notifyAll();
}
if (doRenderNotification) {
if (GLStuff.LOG_SURFACE) {
Log.i("GLThread", "sending render notification tid=" + getId());
}
mWantRenderNotification = false;
doRenderNotification = false;
mRenderComplete = true;
sGLThreadManager.notifyAll();
}
if (mFinishDrawingRunnable != null) {
finishDrawingRunnable = mFinishDrawingRunnable;
mFinishDrawingRunnable = null;
}
// Ready to draw?
if (readyToDraw()) {
// If we don't have an EGL context, try to acquire one.
if (! mHaveEglContext) {
if (askedToReleaseEglContext) {
askedToReleaseEglContext = false;
} else {
try {
mEglHelper.start();
} catch (RuntimeException t) {
sGLThreadManager.releaseEglContextLocked(this);
throw t;
}
mHaveEglContext = true;
createEglContext = true;
sGLThreadManager.notifyAll();
}
}
if (mHaveEglContext && !mHaveEglSurface) {
mHaveEglSurface = true;
createEglSurface = true;
createGlInterface = true;
sizeChanged = true;
}
if (mHaveEglSurface) {
if (mSizeChanged) {
sizeChanged = true;
w = mWidth;
h = mHeight;
mWantRenderNotification = true;
if (GLStuff.LOG_SURFACE) {
Log.i("GLThread",
"noticing that we want render notification tid="
+ getId());
}
// Destroy and recreate the EGL surface.
createEglSurface = true;
mSizeChanged = false;
}
mRequestRender = false;
sGLThreadManager.notifyAll();
if (mWantRenderNotification) {
wantRenderNotification = true;
}
break;
}
} else {
if (finishDrawingRunnable != null) {
Log.w("GLThread", "Warning, !readyToDraw() but waiting for " +
"draw finished! Early reporting draw finished.");
finishDrawingRunnable.run();
finishDrawingRunnable = null;
}
}
// By design, this is the only place in a GLThread thread where we wait().
if (GLStuff.LOG_THREADS) {
Log.i("GLThread", "waiting tid=" + getId()
+ " mHaveEglContext: " + mHaveEglContext
+ " mHaveEglSurface: " + mHaveEglSurface
+ " mFinishedCreatingEglSurface: " + mFinishedCreatingEglSurface
+ " mPaused: " + mPaused
+ " mHasSurface: " + mHasSurface
+ " mSurfaceIsBad: " + mSurfaceIsBad
+ " mWaitingForSurface: " + mWaitingForSurface
+ " mWidth: " + mWidth
+ " mHeight: " + mHeight
+ " mRequestRender: " + mRequestRender
+ " mRenderMode: " + mRenderMode);
}
sGLThreadManager.wait();
}
} // end of synchronized(sGLThreadManager)
if (event != null) {
event.run();
event = null;
continue;
}
if (createEglSurface) {
if (GLStuff.LOG_SURFACE) {
Log.w("GLThread", "egl createSurface");
}
if (mEglHelper.createSurface()) {
synchronized(sGLThreadManager) {
mFinishedCreatingEglSurface = true;
sGLThreadManager.notifyAll();
}
} else {
synchronized(sGLThreadManager) {
mFinishedCreatingEglSurface = true;
mSurfaceIsBad = true;
sGLThreadManager.notifyAll();
}
continue;
}
createEglSurface = false;
}
if (createGlInterface) {
gl = (GL10) mEglHelper.createGL();
createGlInterface = false;
}
if (createEglContext) {
if (GLStuff.LOG_RENDERER) {
Log.w("GLThread", "onSurfaceCreated");
}
GLStuff stuff = mGLStuffWeakRef.get();
if (stuff != null) {
stuff.getRenderer().onSurfaceCreated(gl, mEglHelper.mEglConfig);
stuff = null;
}
createEglContext = false;
}
if (sizeChanged) {
if (GLStuff.LOG_RENDERER) {
Log.w("GLThread", "onSurfaceChanged(" + w + ", " + h + ")");
}
GLStuff stuff = mGLStuffWeakRef.get();
if (stuff != null) {
stuff.getRenderer().onSurfaceChanged(gl, w, h);
stuff = null;
}
sizeChanged = false;
}
if (GLStuff.LOG_RENDERER_DRAW_FRAME) {
Log.w("GLThread", "onDrawFrame tid=" + getId());
}
boolean drew = false;
{
GLStuff stuff = mGLStuffWeakRef.get();
if (stuff != null) {
drew = stuff.getRenderer().onDrawFrame(gl);
stuff = null;
if (finishDrawingRunnable != null) {
finishDrawingRunnable.run();
finishDrawingRunnable = null;
}
}
}
if (drew) {
final int swapError = mEglHelper.swap();
switch (swapError) {
case EGL10.EGL_SUCCESS:
break;
case EGL11.EGL_CONTEXT_LOST:
if (GLStuff.LOG_SURFACE) {
Log.i("GLThread", "egl context lost tid=" + getId());
}
lostEglContext = true;
break;
default:
// Other errors typically mean that the current surface is bad,
// probably because the SurfaceView surface has been destroyed,
// but we haven't been notified yet.
// Log the error to help developers understand why rendering stopped.
EglHelper.logEglErrorAsWarning("GLThread", "eglSwapBuffers", swapError);
synchronized (sGLThreadManager) {
mSurfaceIsBad = true;
sGLThreadManager.notifyAll();
}
break;
}
}
if (wantRenderNotification) {
doRenderNotification = true;
wantRenderNotification = false;
}
}
} finally {
/*
* clean-up everything...
*/
synchronized (sGLThreadManager) {
stopEglSurfaceLocked();
stopEglContextLocked();
}
}
}
public boolean ableToDraw() {
return mHaveEglContext && mHaveEglSurface && readyToDraw();
}
private boolean readyToDraw() {
return (!mPaused) && mHasSurface && (!mSurfaceIsBad)
&& (mWidth > 0) && (mHeight > 0)
&& (mRequestRender || (mRenderMode == GLStuff.RENDERMODE_CONTINUOUSLY));
}
public void setRenderMode(int renderMode) {
if ( !((GLStuff.RENDERMODE_WHEN_DIRTY <= renderMode) && (renderMode <= GLStuff.RENDERMODE_CONTINUOUSLY)) ) {
throw new IllegalArgumentException("renderMode");
}
synchronized(sGLThreadManager) {
mRenderMode = renderMode;
sGLThreadManager.notifyAll();
}
}
public int getRenderMode() {
synchronized(sGLThreadManager) {
return mRenderMode;
}
}
public void requestRender() {
synchronized(sGLThreadManager) {
mRequestRender = true;
sGLThreadManager.notifyAll();
}
}
public void requestRenderAndNotify(Runnable finishDrawing) {
synchronized(sGLThreadManager) {
// If we are already on the GL thread, this means a client callback
// has caused reentrancy, for example via updating the SurfaceView parameters.
// We will return to the client rendering code, so here we don't need to
// do anything.
if (Thread.currentThread() == this) {
return;
}
mWantRenderNotification = true;
mRequestRender = true;
mRenderComplete = false;
mFinishDrawingRunnable = finishDrawing;
sGLThreadManager.notifyAll();
}
}
public void requestRenderAndWait() {
synchronized(sGLThreadManager) {
// If we are already on the GL thread, this means a client callback
// has caused reentrancy, for example via updating the SurfaceView parameters.
// We will return to the client rendering code, so here we don't need to
// do anything.
if (Thread.currentThread() == this) {
return;
}
mWantRenderNotification = true;
mRequestRender = true;
mRenderComplete = false;
sGLThreadManager.notifyAll();
while (!mExited && !mPaused && !mRenderComplete && ableToDraw()) {
try {
sGLThreadManager.wait();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
public void surfaceCreated() {
synchronized(sGLThreadManager) {
if (GLStuff.LOG_THREADS) {
Log.i("GLThread", "surfaceCreated tid=" + getId());
}
mHasSurface = true;
mFinishedCreatingEglSurface = false;
sGLThreadManager.notifyAll();
while (mWaitingForSurface
&& !mFinishedCreatingEglSurface
&& !mExited) {
try {
sGLThreadManager.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
public void surfaceDestroyed() {
synchronized(sGLThreadManager) {
if (GLStuff.LOG_THREADS) {
Log.i("GLThread", "surfaceDestroyed tid=" + getId());
}
mHasSurface = false;
sGLThreadManager.notifyAll();
while((!mWaitingForSurface) && (!mExited)) {
try {
sGLThreadManager.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
public void onPause() {
synchronized (sGLThreadManager) {
if (GLStuff.LOG_PAUSE_RESUME) {
Log.i("GLThread", "onPause tid=" + getId());
}
mRequestPaused = true;
sGLThreadManager.notifyAll();
while ((! mExited) && (! mPaused)) {
if (GLStuff.LOG_PAUSE_RESUME) {
Log.i("Main thread", "onPause waiting for mPaused.");
}
try {
sGLThreadManager.wait();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
public void onResume() {
synchronized (sGLThreadManager) {
if (GLStuff.LOG_PAUSE_RESUME) {
Log.i("GLThread", "onResume tid=" + getId());
}
mRequestPaused = false;
mRequestRender = true;
mRenderComplete = false;
sGLThreadManager.notifyAll();
while ((! mExited) && mPaused && (!mRenderComplete)) {
if (GLStuff.LOG_PAUSE_RESUME) {
Log.i("Main thread", "onResume waiting for !mPaused.");
}
try {
sGLThreadManager.wait();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
public void onWindowResize(int w, int h) {
synchronized (sGLThreadManager) {
mWidth = w;
mHeight = h;
mSizeChanged = true;
mRequestRender = true;
mRenderComplete = false;
// If we are already on the GL thread, this means a client callback
// has caused reentrancy, for example via updating the SurfaceView parameters.
// We need to process the size change eventually though and update our EGLSurface.
// So we set the parameters and return so they can be processed on our
// next iteration.
if (Thread.currentThread() == this) {
return;
}
sGLThreadManager.notifyAll();
// Wait for thread to react to resize and render a frame
while (! mExited && !mPaused && !mRenderComplete
&& ableToDraw()) {
if (GLStuff.LOG_SURFACE) {
Log.i("Main thread", "onWindowResize waiting for render complete from tid=" + getId());
}
try {
sGLThreadManager.wait();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
public void requestExitAndWait() {
// don't call this from GLThread thread or it is a guaranteed
// deadlock!
synchronized(sGLThreadManager) {
mShouldExit = true;
sGLThreadManager.notifyAll();
while (! mExited) {
try {
sGLThreadManager.wait();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
}
public void requestReleaseEglContextLocked() {
mShouldReleaseEglContext = true;
sGLThreadManager.notifyAll();
}
/**
* Queue an "event" to be run on the GL rendering thread.
* @param r the runnable to be run on the GL rendering thread.
*/
public void queueEvent(Runnable r) {
if (r == null) {
throw new IllegalArgumentException("r must not be null");
}
synchronized(sGLThreadManager) {
mEventQueue.add(r);
sGLThreadManager.notifyAll();
}
}
private static final GLThreadManager sGLThreadManager = new GLThreadManager();
// Once the thread is started, all accesses to the following member
// variables are protected by the sGLThreadManager monitor
private boolean mShouldExit;
boolean mExited;
private boolean mRequestPaused;
private boolean mPaused;
private boolean mHasSurface;
private boolean mSurfaceIsBad;
private boolean mWaitingForSurface;
private boolean mHaveEglContext;
private boolean mHaveEglSurface;
private boolean mFinishedCreatingEglSurface;
private boolean mShouldReleaseEglContext;
private int mWidth;
private int mHeight;
private int mRenderMode;
private boolean mRequestRender;
private boolean mWantRenderNotification;
private boolean mRenderComplete;
private final ArrayList<Runnable> mEventQueue = new ArrayList<>();
private boolean mSizeChanged = true;
private Runnable mFinishDrawingRunnable = null;
// End of member variables protected by the sGLThreadManager monitor.
private EglHelper mEglHelper;
/**
* Set once at thread construction time, nulled out when the parent view is garbage
* called. This weak reference allows the GLSurfaceView to be garbage collected while
* the GLThread is still alive.
*/
private final WeakReference<GLStuff> mGLStuffWeakRef;
}
@@ -1,41 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.tuxiang;
import android.util.Log;
// android-9.0.0_r30
final class GLThreadManager {
private static final String TAG = "GLThreadManager";
public synchronized void threadExiting(GLThread thread) {
if (GLStuff.LOG_THREADS) {
Log.i("GLThread", "exiting tid=" + thread.getId());
}
thread.mExited = true;
notifyAll();
}
/*
* Releases the EGL context. Requires that we are already in the
* sGLThreadManager monitor when this is called.
*/
public void releaseEglContextLocked(GLThread thread) {
notifyAll();
}
}
@@ -1,50 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.tuxiang;
import javax.microedition.khronos.opengles.GL;
// android-9.0.0_r30
/**
* An interface used to wrap a GL interface.
* <p>Typically
* used for implementing debugging and tracing on top of the default
* GL interface. You would typically use this by creating your own class
* that implemented all the GL methods by delegating to another GL instance.
* Then you could add your own behavior before or after calling the
* delegate. All the GLWrapper would do was instantiate and return the
* wrapper GL instance:
* <pre class="prettyprint">
* class MyGLWrapper implements GLWrapper {
* GL wrap(GL gl) {
* return new MyGLImplementation(gl);
* }
* static class MyGLImplementation implements GL,GL10,GL11,... {
* ...
* }
* }
* </pre>
*/
public interface GLWrapper {
/**
* Wraps a gl interface in another gl interface.
* @param gl a GL interface that is to be wrapped.
* @return either the input argument or another GL object that wraps the input argument.
*/
GL wrap(GL gl);
}
@@ -1,160 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.tuxiang;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
// android-9.0.0_r30
/**
* A generic renderer interface.
* <p>
* The renderer is responsible for making OpenGL calls to render a frame.
* <p>
* GLSurfaceView clients typically create their own classes that implement
* this interface, and then call {@link GLStuff#setRenderer} to
* register the renderer with the GLSurfaceView.
* <p>
*
* <div class="special reference">
* <h3>Developer Guides</h3>
* <p>For more information about how to use OpenGL, read the
* <a href="{@docRoot}guide/topics/graphics/opengl.html">OpenGL</a> developer guide.</p>
* </div>
*
* <h3>Threading</h3>
* The renderer will be called on a separate thread, so that rendering
* performance is decoupled from the UI thread. Clients typically need to
* communicate with the renderer from the UI thread, because that's where
* input events are received. Clients can communicate using any of the
* standard Java techniques for cross-thread communication, or they can
* use the {@link GLStuff#queueEvent(Runnable)} convenience method.
* <p>
* <h3>EGL Context Lost</h3>
* There are situations where the EGL rendering context will be lost. This
* typically happens when device wakes up after going to sleep. When
* the EGL context is lost, all OpenGL resources (such as textures) that are
* associated with that context will be automatically deleted. In order to
* keep rendering correctly, a renderer must recreate any lost resources
* that it still needs. The {@link #onSurfaceCreated(GL10, EGLConfig)} method
* is a convenient place to do this.
*
* @see GLStuff#setRenderer(Renderer)
*/
public interface Renderer {
/**
* Called when the surface is created or recreated.
* <p>
* Called when the rendering thread
* starts and whenever the EGL context is lost. The EGL context will typically
* be lost when the Android device awakes after going to sleep.
* <p>
* Since this method is called at the beginning of rendering, as well as
* every time the EGL context is lost, this method is a convenient place to put
* code to create resources that need to be created when the rendering
* starts, and that need to be recreated when the EGL context is lost.
* Textures are an example of a resource that you might want to create
* here.
* <p>
* Note that when the EGL context is lost, all OpenGL resources associated
* with that context will be automatically deleted. You do not need to call
* the corresponding "glDelete" methods such as glDeleteTextures to
* manually delete these lost resources.
* <p>
* @param gl the GL interface. Use <code>instanceof</code> to
* test if the interface supports GL11 or higher interfaces.
* @param config the EGLConfig of the created surface. Can be used
* to create matching pbuffers.
*/
void onSurfaceCreated(GL10 gl, EGLConfig config);
/**
* Called when the surface changed size.
* <p>
* Called after the surface is created and whenever
* the OpenGL ES surface size changes.
* <p>
* Typically you will set your viewport here. If your camera
* is fixed then you could also set your projection matrix here:
* <pre class="prettyprint">
* void onSurfaceChanged(GL10 gl, int width, int height) {
* gl.glViewport(0, 0, width, height);
* // for a fixed camera, set the projection too
* float ratio = (float) width / height;
* gl.glMatrixMode(GL10.GL_PROJECTION);
* gl.glLoadIdentity();
* gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
* }
* </pre>
* @param gl the GL interface. Use <code>instanceof</code> to
* test if the interface supports GL11 or higher interfaces.
*/
void onSurfaceChanged(GL10 gl, int width, int height);
/**
* Called to draw the current frame.
* <p>
* This method is responsible for drawing the current frame.
* <p>
* The implementation of this method typically looks like this:
* <pre class="prettyprint">
* void onDrawFrame(GL10 gl) {
* gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
* //... other gl calls to render the scene ...
* }
* </pre>
* @param gl the GL interface. Use <code>instanceof</code> to
* test if the interface supports GL11 or higher interfaces.
* @return {@code false} for not drew, will not swap buffers.
*/
boolean onDrawFrame(GL10 gl);
/**
* Called when the GL thread exits.
* <p>
* GL thread starts after {@link GLStuff#setRenderer(Renderer)} called, and
* after {@link android.view.View#onDetachedFromWindow} called except
* the first time.
* <p>
* It is a good time to prepare resources.
*/
void onGLThreadStart();
/**
* Called when the GL thread exits.
* <p>
* GL thread ends after {@link android.view.View#onDetachedFromWindow} called.
* <p>
* It is a good time to release resources.
*/
void onGLThreadExit();
/**
* Called after {@link GLStuff#onPause()} called.
* <p>
* It is a good time to stop animations.
*/
void onGLThreadPause();
/**
* Called after {@link GLStuff#onResume()} called.
* <p>
* It is a good time to restart animations.
*/
void onGLThreadResume();
}
@@ -1,29 +0,0 @@
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.tuxiang;
// android-9.0.0_r30
/**
* This class will choose a RGB_888 surface with
* or without a depth buffer.
*/
public class SimpleEGLConfigChooser extends ComponentSizeChooser {
public SimpleEGLConfigChooser(int eglContextClientVersion, boolean withDepthBuffer) {
super(eglContextClientVersion, 8, 8, 8, 0, withDepthBuffer ? 16 : 0, 0);
}
}
@@ -28,15 +28,16 @@ import java.io.InputStream;
import java.io.OutputStream;
final class Contracts {
private Contracts() {}
private Contracts() {
}
static String queryForString(Context context, Uri self, String column,
String defaultValue) {
String defaultValue) {
final ContentResolver resolver = context.getContentResolver();
Cursor c = null;
try {
c = resolver.query(self, new String[] { column }, null, null, null);
c = resolver.query(self, new String[]{column}, null, null, null);
if (c != null && c.moveToFirst() && !c.isNull(0)) {
return c.getString(0);
} else {
@@ -51,17 +52,17 @@ final class Contracts {
}
static int queryForInt(Context context, Uri self, String column,
int defaultValue) {
int defaultValue) {
return (int) queryForLong(context, self, column, defaultValue);
}
static long queryForLong(Context context, Uri self, String column,
long defaultValue) {
long defaultValue) {
final ContentResolver resolver = context.getContentResolver();
Cursor c = null;
try {
c = resolver.query(self, new String[] { column }, null, null, null);
c = resolver.query(self, new String[]{column}, null, null, null);
if (c != null && c.moveToFirst() && !c.isNull(0)) {
return c.getLong(0);
} else {
@@ -30,10 +30,11 @@ import android.util.Log;
@TargetApi(Build.VERSION_CODES.KITKAT)
final class DocumentsContractApi19 {
private DocumentsContractApi19() {}
private static final String TAG = DocumentsContractApi19.class.getSimpleName();
private DocumentsContractApi19() {
}
public static boolean isDocumentUri(Context context, Uri self) {
return DocumentsContract.isDocumentUri(context, self);
}
@@ -132,8 +133,8 @@ final class DocumentsContractApi19 {
Cursor c = null;
try {
c = resolver.query(self, new String[] {
DocumentsContract.Document.COLUMN_DOCUMENT_ID }, null, null, null);
c = resolver.query(self, new String[]{
DocumentsContract.Document.COLUMN_DOCUMENT_ID}, null, null, null);
return null != c && c.getCount() > 0;
} catch (Throwable e) {
Utils.throwIfFatal(e);
@@ -24,20 +24,21 @@ import android.net.Uri;
import android.os.Build;
import android.provider.DocumentsContract;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
final class DocumentsContractApi21 {
private DocumentsContractApi21() {}
private static final String TAG = DocumentsContractApi21.class.getSimpleName();
private static final String PATH_DOCUMENT = "document";
private static final String PATH_TREE = "tree";
private DocumentsContractApi21() {
}
public static Uri createFile(Context context, Uri self, String mimeType,
String displayName) {
String displayName) {
try {
return DocumentsContract.createDocument(context.getContentResolver(), self, mimeType,
displayName);
@@ -79,8 +80,8 @@ final class DocumentsContractApi21 {
Cursor c = null;
try {
c = resolver.query(childrenUri, new String[] {
DocumentsContract.Document.COLUMN_DOCUMENT_ID }, null, null, null);
c = resolver.query(childrenUri, new String[]{
DocumentsContract.Document.COLUMN_DOCUMENT_ID}, null, null, null);
if (null != c) {
while (c.moveToNext()) {
final String documentId = c.getString(0);
@@ -18,7 +18,6 @@ package com.hippo.unifile;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.system.ErrnoException;
import android.system.Os;
@@ -34,196 +33,196 @@ import java.lang.annotation.RetentionPolicy;
class FileDescriptorRandomAccessFile implements UniRandomAccessFile {
@IntDef({FLUSH_NONE, FLUSH_FSYNC, FLUSH_FDATASYNC})
@Retention(RetentionPolicy.SOURCE)
public @interface FlushMode {}
public static final int FLUSH_NONE = 0;
public static final int FLUSH_FSYNC = 1;
public static final int FLUSH_FDATASYNC = 2;
private final ParcelFileDescriptor pfd;
private final FileDescriptor fd;
private final int flushAfterWrite;
private final Object closeLock = new Object();
private volatile boolean closed = false;
public static final int FLUSH_NONE = 0;
public static final int FLUSH_FSYNC = 1;
public static final int FLUSH_FDATASYNC = 2;
private FileDescriptorRandomAccessFile(ParcelFileDescriptor pfd, FileDescriptor fd, String mode) {
this.pfd = pfd;
this.fd = fd;
private final ParcelFileDescriptor pfd;
private final FileDescriptor fd;
private final int flushAfterWrite;
private final Object closeLock = new Object();
private volatile boolean closed = false;
private FileDescriptorRandomAccessFile(ParcelFileDescriptor pfd, FileDescriptor fd, String mode) {
this.pfd = pfd;
this.fd = fd;
switch (mode) {
case "rws":
flushAfterWrite = FLUSH_FSYNC;
break;
case "rwd":
flushAfterWrite = FLUSH_FDATASYNC;
break;
default:
flushAfterWrite = FLUSH_NONE;
break;
}
}
private void maybeSync() {
if (flushAfterWrite == FLUSH_FSYNC) {
try {
fd.sync();
} catch (IOException e) {
// Ignored
}
} else if (flushAfterWrite == FLUSH_FDATASYNC) {
try {
Os.fdatasync(fd);
} catch (ErrnoException e) {
// Ignored
}
}
}
@Override
public void close() throws IOException {
synchronized (closeLock) {
if (closed) {
return;
}
closed = true;
switch (mode) {
case "rws":
flushAfterWrite = FLUSH_FSYNC;
break;
case "rwd":
flushAfterWrite = FLUSH_FDATASYNC;
break;
default:
flushAfterWrite = FLUSH_NONE;
break;
}
}
pfd.close();
@NonNull
static FileDescriptorRandomAccessFile create(Context context, Uri uri, String mode) throws IOException {
// Get FileDescriptor
ParcelFileDescriptor pfd;
try {
pfd = context.getContentResolver().openFileDescriptor(uri, mode);
} catch (Throwable e) {
Utils.throwIfFatal(e);
throw new IOException("Can't get ParcelFileDescriptor", e);
}
if (pfd == null) {
throw new IOException("Can't get ParcelFileDescriptor");
}
FileDescriptor fd;
try {
fd = pfd.getFileDescriptor();
} catch (SecurityException e) {
pfd.close();
throw new IOException("Can't get FileDescriptor", e);
}
if (fd == null) {
pfd.close();
throw new IOException("Can't get FileDescriptor");
}
try {
Os.close(fd);
} catch (ErrnoException e) {
throw new IOException("An ErrnoException occurs", e);
}
}
@Override
public long getFilePointer() throws IOException {
try {
return Os.lseek(fd, 0L, OsConstants.SEEK_CUR);
} catch (ErrnoException e) {
throw new IOException("An ErrnoException occurs", e);
}
}
@Override
public void seek(long pos) throws IOException {
if (pos < 0) {
throw new IOException("offset < 0: " + pos);
} else {
try {
Os.lseek(fd, pos, OsConstants.SEEK_SET);
} catch (ErrnoException e) {
throw new IOException("An ErrnoException occurs", e);
}
}
}
@Override
public int skipBytes(int n) throws IOException {
long pos;
long len;
long newpos;
if (n <= 0) {
return 0;
}
pos = getFilePointer();
len = length();
newpos = pos + n;
if (newpos > len) {
newpos = len;
}
seek(newpos);
/* return the actual number of bytes skipped */
return (int) (newpos - pos);
}
@Override
public long length() throws IOException {
try {
return Os.fstat(fd).st_size;
} catch (ErrnoException e) {
throw new IOException("An ErrnoException occurs", e);
}
}
@Override
public void setLength(long newLength) throws IOException {
if (newLength < 0) {
throw new IllegalArgumentException("newLength < 0");
}
try {
Os.ftruncate(fd, newLength);
} catch (ErrnoException e) {
throw new IOException("An ErrnoException occurs", e);
return new FileDescriptorRandomAccessFile(pfd, fd, mode);
}
long filePointer = getFilePointer();
if (filePointer > newLength) {
seek(newLength);
private void maybeSync() {
if (flushAfterWrite == FLUSH_FSYNC) {
try {
fd.sync();
} catch (IOException e) {
// Ignored
}
} else if (flushAfterWrite == FLUSH_FDATASYNC) {
try {
Os.fdatasync(fd);
} catch (ErrnoException e) {
// Ignored
}
}
}
maybeSync();
}
@Override
public void close() throws IOException {
synchronized (closeLock) {
if (closed) {
return;
}
closed = true;
}
@Override
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
pfd.close();
@Override
public int read(byte[] b, int off, int len) throws IOException {
try {
return Os.read(fd, b, off, len);
} catch (ErrnoException e) {
throw new IOException("An ErrnoException occurs", e);
}
}
@Override
public void write(byte[] b) throws IOException {
write(b, 0, b.length);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
try {
Os.write(fd, b, off, len);
} catch (ErrnoException e) {
throw new IOException("An ErrnoException occurs", e);
}
maybeSync();
}
@NonNull
static FileDescriptorRandomAccessFile create(Context context, Uri uri, String mode) throws IOException {
// Get FileDescriptor
ParcelFileDescriptor pfd;
try {
pfd = context.getContentResolver().openFileDescriptor(uri, mode);
} catch (Throwable e) {
Utils.throwIfFatal(e);
throw new IOException("Can't get ParcelFileDescriptor", e);
}
if (pfd == null) {
throw new IOException("Can't get ParcelFileDescriptor");
}
FileDescriptor fd;
try {
fd = pfd.getFileDescriptor();
} catch (SecurityException e) {
pfd.close();
throw new IOException("Can't get FileDescriptor", e);
}
if (fd == null) {
pfd.close();
throw new IOException("Can't get FileDescriptor");
try {
Os.close(fd);
} catch (ErrnoException e) {
throw new IOException("An ErrnoException occurs", e);
}
}
return new FileDescriptorRandomAccessFile(pfd, fd, mode);
}
@Override
public long getFilePointer() throws IOException {
try {
return Os.lseek(fd, 0L, OsConstants.SEEK_CUR);
} catch (ErrnoException e) {
throw new IOException("An ErrnoException occurs", e);
}
}
@Override
public void seek(long pos) throws IOException {
if (pos < 0) {
throw new IOException("offset < 0: " + pos);
} else {
try {
Os.lseek(fd, pos, OsConstants.SEEK_SET);
} catch (ErrnoException e) {
throw new IOException("An ErrnoException occurs", e);
}
}
}
@Override
public int skipBytes(int n) throws IOException {
long pos;
long len;
long newpos;
if (n <= 0) {
return 0;
}
pos = getFilePointer();
len = length();
newpos = pos + n;
if (newpos > len) {
newpos = len;
}
seek(newpos);
/* return the actual number of bytes skipped */
return (int) (newpos - pos);
}
@Override
public long length() throws IOException {
try {
return Os.fstat(fd).st_size;
} catch (ErrnoException e) {
throw new IOException("An ErrnoException occurs", e);
}
}
@Override
public void setLength(long newLength) throws IOException {
if (newLength < 0) {
throw new IllegalArgumentException("newLength < 0");
}
try {
Os.ftruncate(fd, newLength);
} catch (ErrnoException e) {
throw new IOException("An ErrnoException occurs", e);
}
long filePointer = getFilePointer();
if (filePointer > newLength) {
seek(newLength);
}
maybeSync();
}
@Override
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
try {
return Os.read(fd, b, off, len);
} catch (ErrnoException e) {
throw new IOException("An ErrnoException occurs", e);
}
}
@Override
public void write(byte[] b) throws IOException {
write(b, 0, b.length);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
try {
Os.write(fd, b, off, len);
} catch (ErrnoException e) {
throw new IOException("An ErrnoException occurs", e);
}
maybeSync();
}
@IntDef({FLUSH_NONE, FLUSH_FSYNC, FLUSH_FDATASYNC})
@Retention(RetentionPolicy.SOURCE)
public @interface FlushMode {
}
}
Loaded 100 of 286 files, more files were not shown because too many files have changed in this diff. Show more