Compare commits

...
53 Commits
Author SHA1 Message Date
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
169 changed files with 2376 additions and 7228 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:
@@ -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
+1
View File
@@ -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 -5
View File
@@ -1,15 +1,15 @@
[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
url = https://github.com/Ehviewer-Overhauled/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
url = https://github.com/Ehviewer-Overhauled/giflib
[submodule "native/src/main/jni/image/libpng/libpng"]
path = native/src/main/jni/image/libpng/libpng
url = https://gitlab.com/NekoInverter/libpng.git
url = https://github.com/Ehviewer-Overhauled/libpng
[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
+12 -12
View File
@@ -4,20 +4,20 @@
</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>
@@ -37,7 +37,7 @@ Dynamic Color only support Android S and above!
## Download
[Github 下载](https://github.com/asuka-mio/Ehviewer/releases)
[Github 下载](https://github.com/Ehviewer-Overhauled/Ehviewer/releases)
## Screenshot
@@ -52,6 +52,7 @@ Here is the libraries
- [AOSP](http://source.android.com/)
- [android-advancedrecyclerview](https://github.com/h6ah4i/android-advancedrecyclerview)
- [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)
@@ -62,7 +63,6 @@ Here is the libraries
- [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/)
+55 -13
View File
@@ -34,8 +34,8 @@ android {
applicationId "moe.tarsin.ehviewer"
minSdkVersion 26
targetSdkVersion 33
versionCode 114528
versionName "1.7.28.1"
versionCode 114531
versionName "1.7.28.4"
resConfigs 'zh', 'zh-rCN', 'zh-rHK', 'zh-rTW', 'es', 'ja', 'ko', 'fr', 'de', 'th'
}
@@ -92,28 +92,70 @@ 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'
implementation "androidx.lifecycle:lifecycle-process:2.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-alpha01"
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.0"
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.google.android.material:material:1.7.0-alpha03'
implementation 'com.getkeepsafe.taptargetview:taptargetview:1.13.3'
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.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 'dev.rikka.rikkax.material:material-preference:2.0.0'
implementation 'org.ccil.cowan.tagsoup:tagsoup:1.2.1'
implementation 'org.greenrobot:greendao:3.3.0'
implementation 'org.jsoup:jsoup:1.15.2'
implementation projects.native
+14 -7
View File
@@ -15,15 +15,22 @@
-->
<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-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" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<!-- Required to maintain app compatibility. -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-feature
android:glEsVersion="0x00020000"
android:required="true" />
@@ -44,10 +51,10 @@
<activity
android:name="com.hippo.ehviewer.ui.MainActivity"
android:banner="@mipmap/ic_leanback_banner"
android:configChanges="screenSize"
android:exported="true"
android:launchMode="singleTask"
android:banner="@mipmap/ic_leanback_banner"
android:windowSoftInputMode="adjustResize|stateAlwaysHidden">
<intent-filter>
@@ -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, actionId: 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,7 +166,16 @@ 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<>() {
private long nextSequenceNumber = 0;
private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {
this.directory = directory;
this.appVersion = appVersion;
this.journalFile = new File(directory, JOURNAL_FILE);
this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP);
this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP);
this.valueCount = valueCount;
this.maxSize = maxSize;
} private final Callable<Void> cleanupCallable = new Callable<>() {
public Void call() throws Exception {
synchronized (DiskLruCache.this) {
if (journalWriter == null) {
@@ -182,16 +191,6 @@ public final class DiskLruCache implements Closeable {
}
};
private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {
this.directory = directory;
this.appVersion = appVersion;
this.journalFile = new File(directory, JOURNAL_FILE);
this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP);
this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP);
this.valueCount = valueCount;
this.maxSize = maxSize;
}
/**
* Opens the cache in {@code directory}, creating a cache if none exists
* there.
@@ -980,4 +979,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;
@@ -63,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;
@@ -302,13 +302,6 @@ public class EhApplication extends SceneApplication {
return application.mFavouriteStatusRouter;
}
static class EhlifecycleObserver implements DefaultLifecycleObserver {
@Override
public void onPause(@NonNull LifecycleOwner owner) {
locked = true;
}
}
@SuppressLint("StaticFieldLeak")
@Override
public void onCreate() {
@@ -338,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);
@@ -594,4 +586,11 @@ public class EhApplication extends SceneApplication {
return null;
}
}
static class EhlifecycleObserver implements DefaultLifecycleObserver {
@Override
public void onPause(@NonNull LifecycleOwner owner) {
locked = true;
}
}
}
@@ -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));
@@ -15,25 +15,24 @@
*/
package com.hippo.ehviewer.preference
import com.google.android.material.textfield.TextInputLayout
import android.widget.EditText
import com.hippo.ehviewer.R
import com.hippo.ehviewer.EhProxySelector
import android.text.TextUtils
import com.hippo.network.InetValidator
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
import java.lang.NumberFormatException
class ProxyPreference @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
@@ -95,7 +94,13 @@ class ProxyPreference @JvmOverloads constructor(
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)
(mType!!.editText as AutoCompleteTextView).setText(
array[MathUtils.clamp(
type,
0,
array.size
)], false
)
mIp!!.setText(Settings.getProxyIp())
val portString: String?
val port = Settings.getProxyPort()
@@ -296,16 +296,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"})
@@ -473,7 +464,16 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan
FrameLayout mainLayout = (FrameLayout) ViewUtils.$$(this, R.id.main);
mainLayout.addView(new GalleryGuideView(this));
}
}
} 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 onDestroy() {
@@ -361,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))
@@ -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
@@ -53,7 +53,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 +222,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 +278,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);
}
@@ -72,7 +72,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;
@@ -648,12 +647,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);
@@ -55,7 +55,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;
@@ -446,12 +445,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;
@@ -74,7 +74,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;
@@ -794,12 +793,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) {
@@ -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)
@@ -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,20 @@ 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 +79,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,195 @@ 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;
private FileDescriptorRandomAccessFile(ParcelFileDescriptor pfd, FileDescriptor fd, String mode) {
this.pfd = pfd;
this.fd = fd;
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;
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 {
}
}
@@ -28,13 +28,11 @@ public interface FilenameFilter {
/**
* Indicates if a specific filename matches this filter.
*
* @param dir
* the directory in which the {@code filename} was found.
* @param filename
* the name of the file in {@code dir} to test.
* @return {@code true} if the filename matches the filter
* and can be included in the list, {@code false}
* otherwise.
* @param dir the directory in which the {@code filename} was found.
* @param filename the name of the file in {@code dir} to test.
* @return {@code true} if the filename matches the filter
* and can be included in the list, {@code false}
* otherwise.
*/
boolean accept(UniFile dir, String filename);
}
@@ -21,7 +21,8 @@ import android.net.Uri;
import android.provider.MediaStore;
final class MediaContract {
private MediaContract() {}
private MediaContract() {
}
public static String getName(Context context, Uri self) {
return Contracts.queryForString(context, self, MediaStore.MediaColumns.DISPLAY_NAME, null);
@@ -18,7 +18,6 @@ package com.hippo.unifile;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import androidx.annotation.NonNull;
@@ -38,6 +37,10 @@ class MediaFile extends UniFile {
mUri = uri;
}
static boolean isMediaUri(Context context, Uri uri) {
return null != MediaContract.getName(context, uri);
}
@Override
public UniFile createFile(String displayName) {
return null;
@@ -54,10 +57,6 @@ class MediaFile extends UniFile {
return mUri;
}
static boolean isMediaUri(Context context, Uri uri) {
return null != MediaContract.getName(context, uri);
}
@Override
public String getName() {
return MediaContract.getName(mContext, mUri);
@@ -43,6 +43,36 @@ class RawFile extends UniFile {
mFile = file;
}
private static String getTypeForName(String name) {
final int lastDot = name.lastIndexOf('.');
if (lastDot >= 0) {
final String extension = name.substring(lastDot + 1).toLowerCase();
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
if (mime != null) {
return mime;
}
}
return "application/octet-stream";
}
private static boolean deleteContents(File dir) {
File[] files = dir.listFiles();
boolean success = true;
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
success &= deleteContents(file);
}
if (!file.delete()) {
Log.w(TAG, "Failed to delete " + file);
success = false;
}
}
}
return success;
}
@Override
public UniFile createFile(String displayName) {
final File target = new File(mFile, displayName);
@@ -239,34 +269,4 @@ class RawFile extends UniFile {
public UniRandomAccessFile createRandomAccessFile(String mode) throws FileNotFoundException {
return new RawRandomAccessFile(new RandomAccessFile(mFile, mode));
}
private static String getTypeForName(String name) {
final int lastDot = name.lastIndexOf('.');
if (lastDot >= 0) {
final String extension = name.substring(lastDot + 1).toLowerCase();
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
if (mime != null) {
return mime;
}
}
return "application/octet-stream";
}
private static boolean deleteContents(File dir) {
File[] files = dir.listFiles();
boolean success = true;
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
success &= deleteContents(file);
}
if (!file.delete()) {
Log.w(TAG, "Failed to delete " + file);
success = false;
}
}
}
return success;
}
}
@@ -18,7 +18,6 @@ package com.hippo.unifile;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import androidx.annotation.NonNull;
@@ -67,7 +67,7 @@ public abstract class UniFile {
/**
* Create a {@link UniFile} representing the given {@link File}.
*
* @param file the file to wrap
* @return the {@link UniFile} representing the given {@link File}.
*/
@@ -83,8 +83,8 @@ public abstract class UniFile {
* {@code null} when called on earlier platform versions.
*
* @param singleUri the {@link Intent#getData()} from a successful
* {@link Intent#ACTION_OPEN_DOCUMENT} or
* {@link Intent#ACTION_CREATE_DOCUMENT} request.
* {@link Intent#ACTION_OPEN_DOCUMENT} or
* {@link Intent#ACTION_CREATE_DOCUMENT} request.
* @return the {@link UniFile} representing the given {@link Uri}.
*/
public static UniFile fromSingleUri(Context context, Uri singleUri) {
@@ -103,7 +103,7 @@ public abstract class UniFile {
* {@code null} when called on earlier platform versions.
*
* @param treeUri the {@link Intent#getData()} from a successful
* {@link Intent#ACTION_OPEN_DOCUMENT_TREE} request.
* {@link Intent#ACTION_OPEN_DOCUMENT_TREE} request.
* @return the {@link UniFile} representing the given {@link Uri}.
*/
public static UniFile fromTreeUri(Context context, Uri treeUri) {
@@ -198,7 +198,7 @@ public abstract class UniFile {
* @param displayName name of new file
* @return file representing newly created document, or null if failed
* @see android.provider.DocumentsContract#createDocument(ContentResolver,
* Uri, String, String)
* Uri, String, String)
*/
public abstract UniFile createFile(String displayName);
@@ -208,7 +208,7 @@ public abstract class UniFile {
* @param displayName name of new directory
* @return file representing newly created directory, or null if failed
* @see android.provider.DocumentsContract#createDocument(ContentResolver,
* Uri, String, String)
* Uri, String, String)
*/
public abstract UniFile createDirectory(String displayName);
@@ -267,7 +267,7 @@ public abstract class UniFile {
* Indicates if this file represents a <em>directory</em>.
*
* @return {@code true} if this file is a directory, {@code false}
* otherwise.
* otherwise.
* @see android.provider.DocumentsContract.Document#MIME_TYPE_DIR
*/
public abstract boolean isDirectory();
@@ -311,7 +311,7 @@ public abstract class UniFile {
* Indicates whether the current context is allowed to write to this file.
*
* @return {@code true} if this file can be written, {@code false}
* otherwise.
* otherwise.
* @see android.provider.DocumentsContract.Document#COLUMN_FLAGS
* @see android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE
* @see android.provider.DocumentsContract.Document#FLAG_SUPPORTS_WRITE
@@ -323,7 +323,7 @@ public abstract class UniFile {
* It works like mkdirs, but it will return true if the UniFile is directory
*
* @return {@code true} if the directory was created
* or if the directory already existed.
* or if the directory already existed.
*/
public abstract boolean ensureDir();
@@ -331,7 +331,7 @@ public abstract class UniFile {
* Make sure the UniFile is file
*
* @return {@code true} if the file can be created
* or if the file already existed.
* or if the file already existed.
*/
public abstract boolean ensureFile();
@@ -351,7 +351,7 @@ public abstract class UniFile {
*
* @return {@code true} if this file was deleted, {@code false} otherwise.
* @see android.provider.DocumentsContract#deleteDocument(ContentResolver,
* Uri)
* Uri)
*/
public abstract boolean delete();
@@ -368,7 +368,7 @@ public abstract class UniFile {
*
* @return an array of files or {@code null}.
* @see android.provider.DocumentsContract#buildChildDocumentsUriUsingTree(Uri,
* String)
* String)
*/
@Nullable
public abstract UniFile[] listFiles();
@@ -408,7 +408,7 @@ public abstract class UniFile {
* @param displayName the new display name.
* @return true on success.
* @see android.provider.DocumentsContract#renameDocument(ContentResolver,
* Uri, String)
* Uri, String)
*/
public abstract boolean renameTo(String displayName);
@@ -80,8 +80,8 @@ public interface UniRandomAccessFile {
*
* @param b the buffer into which the data is read
* @return the total number of bytes read into the buffer, or
* {@code -1} if there is no more data because the end of
* the file has been reached.
* {@code -1} if there is no more data because the end of
* the file has been reached.
* @throws IOException if an I/O error occurs
*/
int read(byte[] b) throws IOException;
@@ -90,12 +90,12 @@ public interface UniRandomAccessFile {
* Reads exactly len bytes from this file into the byte array,
* starting at the current file pointer.
*
* @param b the buffer into which the data is read
* @param b the buffer into which the data is read
* @param off the start offset of the data
* @param len the number of bytes to read
* @return the total number of bytes read into the buffer, or
* {@code -1} if there is no more data because the end of
* the file has been reached.
* {@code -1} if there is no more data because the end of
* the file has been reached.
* @throws IOException
*/
int read(byte[] b, int off, int len) throws IOException;
@@ -112,7 +112,7 @@ public interface UniRandomAccessFile {
/**
* Writes len bytes from the specified byte array starting at offset off to this file.
*
* @param b the data
* @param b the data
* @param off the start offset in the data
* @param len the number of bytes to write
* @throws IOException
@@ -45,12 +45,6 @@ class UriOutputStream extends FileOutputStream {
mPfd = pfd;
}
@Override
public void close() throws IOException {
mPfd.close();
super.close();
}
@NonNull
static OutputStream create(Context context, Uri uri, String mode) throws IOException {
ParcelFileDescriptor pfd;
@@ -70,4 +64,10 @@ class UriOutputStream extends FileOutputStream {
return new UriOutputStream(pfd, fd);
}
@Override
public void close() throws IOException {
mPfd.close();
super.close();
}
}
@@ -73,6 +73,15 @@ public class BatteryView extends AppCompatTextView {
int height = (int) getTextSize();
mDrawable.setBounds(0, 0, (int) (height / 0.618f), height);
setCompoundDrawables(mDrawable, null, null, null);
}
@Override
public void setTextColor(int color) {
if (mCurrentColor == color) {
return;
}
mCurrentColor = color;
super.setTextColor(color);
} private final Runnable mCharger = new Runnable() {
private int level = 0;
@@ -88,15 +97,6 @@ public class BatteryView extends AppCompatTextView {
}
};
@Override
public void setTextColor(int color) {
if (mCurrentColor == color) {
return;
}
mCurrentColor = color;
super.setTextColor(color);
}
private void startCharger() {
if (!mIsChargerWorking) {
getHandler().post(mCharger);
@@ -143,6 +143,8 @@ public class BatteryView extends AppCompatTextView {
getContext().unregisterReceiver(mIntentReceiver);
}
private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
@Override
@@ -175,6 +177,4 @@ public class BatteryView extends AppCompatTextView {
};
}
@@ -1,515 +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.widget;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.PopupWindow;
import android.widget.RelativeLayout;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.core.graphics.drawable.DrawableCompat;
import com.hippo.ehviewer.R;
import com.hippo.yorozuya.AnimationUtils;
import com.hippo.yorozuya.LayoutUtils;
import com.hippo.yorozuya.MathUtils;
import com.hippo.yorozuya.SimpleHandler;
public class Slider extends View {
private static final char[] CHARACTERS = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
};
private static final int BUBBLE_WIDTH = 26;
private static final int BUBBLE_HEIGHT = 32;
private final RectF mLeftRectF = new RectF();
private final RectF mRightRectF = new RectF();
private final int[] mTemp = new int[2];
private Context mContext;
private Paint mPaint;
private Paint mBgPaint;
private PopupWindow mPopup;
private BubbleView mBubble;
private int mStart;
private int mEnd;
private int mProgress;
private float mPercent;
private int mDrawProgress;
private float mDrawPercent;
private int mTargetProgress;
private float mThickness;
private float mRadius;
private float mCharWidth;
private float mCharHeight;
private int mBubbleWidth;
private int mBubbleHeight;
private int mBubbleMinWidth;
private int mBubbleMinHeight;
private int mPopupX;
private int mPopupY;
private int mPopupWidth;
private boolean mReverse = false;
private boolean mShowBubble;
private float mDrawBubbleScale = 0f;
private ValueAnimator mProgressAnimation;
private ValueAnimator mBubbleScaleAnimation;
private OnSetProgressListener mListener;
private CheckForShowBubble mCheckForShowBubble;
public Slider(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public Slider(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
@SuppressWarnings("deprecation")
private void init(Context context, AttributeSet attrs) {
mContext = context;
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
Paint textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint.setTextAlign(Paint.Align.CENTER);
Resources resources = context.getResources();
mBubbleMinWidth = resources.getDimensionPixelOffset(R.dimen.slider_bubble_width);
mBubbleMinHeight = resources.getDimensionPixelOffset(R.dimen.slider_bubble_height);
mBubble = new BubbleView(context, textPaint);
mBubble.setScaleX(0.0f);
mBubble.setScaleY(0.0f);
RelativeLayout relativeLayout = new RelativeLayout(context);
relativeLayout.addView(mBubble);
relativeLayout.setBackgroundDrawable(null);
mPopup = new PopupWindow(relativeLayout);
mPopup.setOutsideTouchable(false);
mPopup.setTouchable(false);
mPopup.setFocusable(false);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Slider);
textPaint.setColor(a.getColor(R.styleable.Slider_textColor, Color.WHITE));
textPaint.setTextSize(a.getDimensionPixelSize(R.styleable.Slider_textSize, 12));
updateTextSize();
setRange(a.getInteger(R.styleable.Slider_start, 0), a.getInteger(R.styleable.Slider_end, 0));
setProgress(a.getInteger(R.styleable.Slider_slider_progress, 0));
mThickness = a.getDimension(R.styleable.Slider_thickness, 2);
mRadius = a.getDimension(R.styleable.Slider_radius, 6);
mBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
a.recycle();
mProgressAnimation = new ValueAnimator();
mProgressAnimation.setInterpolator(AnimationUtils.FAST_SLOW_INTERPOLATOR);
mProgressAnimation.addUpdateListener(animation -> {
float value = (Float) animation.getAnimatedValue();
mDrawPercent = value;
mDrawProgress = Math.round(MathUtils.lerp((float) mStart, mEnd, value));
updateBubblePosition();
mBubble.setProgress(mDrawProgress);
invalidate();
});
mBubbleScaleAnimation = new ValueAnimator();
mBubbleScaleAnimation.addUpdateListener(animation -> {
float value = (Float) animation.getAnimatedValue();
mDrawBubbleScale = value;
mBubble.setScaleX(value);
mBubble.setScaleY(value);
invalidate();
});
}
private void updateTextSize() {
int length = CHARACTERS.length;
float[] widths = new float[length];
mPaint.getTextWidths(CHARACTERS, 0, length, widths);
mCharWidth = 0.0f;
for (float f : widths) {
mCharWidth = Math.max(mCharWidth, f);
}
Paint.FontMetrics fm = mPaint.getFontMetrics();
mCharHeight = fm.bottom - fm.top;
}
private void updateBubbleSize() {
int oldWidth = mBubbleWidth;
int oldHeight = mBubbleHeight;
mBubbleWidth = (int) Math.max(mBubbleMinWidth,
Integer.toString(mEnd).length() * mCharWidth + LayoutUtils.dp2pix(mContext, 8));
mBubbleHeight = (int) Math.max(mBubbleMinHeight,
mCharHeight + LayoutUtils.dp2pix(mContext, 8));
if (oldWidth != mBubbleWidth && oldHeight != mBubbleHeight) {
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) mBubble.getLayoutParams();
lp.width = mBubbleWidth;
lp.height = mBubbleHeight;
mBubble.setLayoutParams(lp);
}
}
private void updatePopup() {
int width = getWidth();
int paddingTop = getPaddingTop();
int paddingBottom = getPaddingBottom();
getLocationInWindow(mTemp);
mPopupWidth = (int) (width - mRadius - mRadius + mBubbleWidth);
int popupHeight = mBubbleHeight;
mPopupX = (int) (mTemp[0] + mRadius - (mBubbleWidth / 2));
mPopupY = (int) (mTemp[1] - popupHeight + paddingTop +
((getHeight() - paddingTop - paddingBottom) / 2) -
mRadius - LayoutUtils.dp2pix(mContext, 2));
mPopup.update(mPopupX, mPopupY, mPopupWidth, popupHeight, false);
}
private void updateBubblePosition() {
float x = ((mPopupWidth - mBubbleWidth) * (mReverse ? (1.0f - mDrawPercent) : mDrawPercent));
mBubble.setX(x);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
updatePopup();
updateBubblePosition();
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mPopup.showAtLocation(this, Gravity.TOP | Gravity.LEFT, mPopupX, mPopupY);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mPopup.dismiss();
}
private void startProgressAnimation(float percent) {
float currentValue;
if (mProgressAnimation.isRunning()) {
mProgressAnimation.setCurrentPlayTime(mProgressAnimation.getCurrentPlayTime());
Object value = mProgressAnimation.getAnimatedValue();
if (value instanceof Float) {
currentValue = (float) value;
} else {
currentValue = mDrawPercent;
}
} else {
currentValue = mDrawPercent;
}
mProgressAnimation.cancel();
mProgressAnimation.setFloatValues(currentValue, percent);
mProgressAnimation.setDuration(Math.min(500, (long) (50 * getWidth() * Math.abs(currentValue - percent))));
mProgressAnimation.start();
}
private void startShowBubbleAnimation() {
mBubbleScaleAnimation.cancel();
mBubbleScaleAnimation.setFloatValues(mDrawBubbleScale, 1.0f);
mBubbleScaleAnimation.setInterpolator(AnimationUtils.FAST_SLOW_INTERPOLATOR);
mBubbleScaleAnimation.setDuration((long) (300 * Math.abs(mDrawBubbleScale - 1.0f)));
mBubbleScaleAnimation.start();
}
private void startHideBubbleAnimation() {
mBubbleScaleAnimation.cancel();
mBubbleScaleAnimation.setFloatValues(mDrawBubbleScale, 0.0f);
mBubbleScaleAnimation.setInterpolator(AnimationUtils.SLOW_FAST_INTERPOLATOR);
mBubbleScaleAnimation.setDuration((long) (300 * Math.abs(mDrawBubbleScale - 0.0f)));
mBubbleScaleAnimation.start();
}
public void setColor(int color) {
mPaint.setColor(color);
mBubble.setColor(color);
invalidate();
}
public void setRange(int start, int end) {
mStart = start;
mEnd = end;
setProgress(mProgress);
updateBubbleSize();
}
public int getProgress() {
return mProgress;
}
public void setProgress(int progress) {
progress = MathUtils.clamp(progress, mStart, mEnd);
int oldProgress = mProgress;
if (mProgress != progress) {
mProgress = progress;
mPercent = MathUtils.delerp(mStart, mEnd, mProgress);
mTargetProgress = progress;
if (mProgressAnimation == null) {
// For init
mDrawPercent = mPercent;
mDrawProgress = mProgress;
updateBubblePosition();
mBubble.setProgress(mDrawProgress);
} else {
startProgressAnimation(mPercent);
}
invalidate();
}
if (mListener != null) {
mListener.onSetProgress(this, progress, oldProgress, false, true);
}
}
public void setReverse(boolean reverse) {
if (mReverse != reverse) {
mReverse = reverse;
invalidate();
}
}
public void setOnSetProgressListener(OnSetProgressListener listener) {
mListener = listener;
}
@Override
protected void onDraw(@NonNull Canvas canvas) {
int width = getWidth();
int height = getHeight();
if (width < LayoutUtils.dp2pix(mContext, 24)) {
canvas.drawRect(0, 0, width, getHeight(), mPaint);
} else {
int paddingLeft = getPaddingLeft();
int paddingTop = getPaddingTop();
int paddingRight = getPaddingRight();
int paddingBottom = getPaddingBottom();
float thickness = mThickness;
float radius = mRadius;
float halfThickness = thickness / 2;
int saved = canvas.save();
canvas.translate(0, paddingTop + ((height - paddingTop - paddingBottom) / 2));
float currentX = paddingLeft + radius + (width - radius - radius - paddingLeft - paddingRight) *
(mReverse ? (1.0f - mDrawPercent) : mDrawPercent);
mLeftRectF.set(paddingLeft + radius, -halfThickness, currentX, halfThickness);
mRightRectF.set(currentX, -halfThickness, width - paddingRight - radius, halfThickness);
// Draw bar
if (mReverse) {
canvas.drawRect(mRightRectF, mPaint);
canvas.drawRect(mLeftRectF, mBgPaint);
} else {
canvas.drawRect(mLeftRectF, mPaint);
canvas.drawRect(mRightRectF, mBgPaint);
}
// Draw controller
float scale = 1.0f - mDrawBubbleScale;
if (scale != 0.0f) {
canvas.scale(scale, scale, currentX, 0);
canvas.drawCircle(currentX, 0, radius, mPaint);
}
canvas.restoreToCount(saved);
}
}
private void setShowBubble(boolean showBubble) {
if (mShowBubble != showBubble) {
mShowBubble = showBubble;
if (showBubble) {
startShowBubbleAnimation();
} else {
startHideBubbleAnimation();
}
}
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(@NonNull MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (mListener != null) {
if (action == MotionEvent.ACTION_DOWN) {
mListener.onFingerDown();
} else if (action == MotionEvent.ACTION_UP) {
mListener.onFingerUp();
}
}
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
float radius = mRadius;
float x = event.getX();
int progress = Math.round(MathUtils.lerp((float) mStart, (float) mEnd,
MathUtils.clamp((mReverse ? (getWidth() - paddingLeft - radius - x) : (x - radius - paddingLeft)) /
(getWidth() - radius - radius - paddingLeft - paddingRight), 0.0f, 1.0f)));
float percent = MathUtils.delerp(mStart, mEnd, progress);
// ACTION_CANCEL not changed
if (action == MotionEvent.ACTION_CANCEL) {
progress = mProgress;
percent = mPercent;
}
if (mTargetProgress != progress) {
mTargetProgress = progress;
startProgressAnimation(percent);
if (mListener != null) {
mListener.onSetProgress(this, mProgress, progress, true, false);
}
}
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
SimpleHandler.getInstance().removeCallbacks(mCheckForShowBubble);
setShowBubble(false);
} else if (action == MotionEvent.ACTION_DOWN) {
if (mCheckForShowBubble == null) {
mCheckForShowBubble = new CheckForShowBubble();
}
SimpleHandler.getInstance().postDelayed(mCheckForShowBubble, ViewConfiguration.getTapTimeout());
}
if (action == MotionEvent.ACTION_UP) {
int oldProgress = mProgress;
if (mProgress != progress) {
mProgress = progress;
mPercent = mDrawPercent;
}
if (mListener != null) {
mListener.onSetProgress(this, progress, oldProgress, true, true);
}
}
break;
}
return true;
}
public interface OnSetProgressListener {
void onSetProgress(Slider slider, int newProgress, int oldProgress, boolean byUser, boolean confirm);
void onFingerDown();
void onFingerUp();
}
@SuppressLint("ViewConstructor")
private static class BubbleView extends AppCompatImageView {
private static final float TEXT_CENTER = (float) BUBBLE_WIDTH / 2.0f / BUBBLE_HEIGHT;
private final Drawable mDrawable;
private final Paint mTextPaint;
private final Rect mRect = new Rect();
private String mProgressStr = "";
public BubbleView(Context context, Paint paint) {
super(context);
setImageResource(R.drawable.v_slider_bubble);
mDrawable = DrawableCompat.wrap(getDrawable());
setImageDrawable(mDrawable);
mTextPaint = paint;
}
public void setColor(int color) {
DrawableCompat.setTint(mDrawable, color);
}
public void setProgress(int progress) {
String str = Integer.toString(progress);
if (!str.equals(mProgressStr)) {
mProgressStr = str;
mTextPaint.getTextBounds(str, 0, str.length(), mRect);
invalidate();
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
setPivotX(w / 2);
setPivotY(h);
}
@Override
protected void onDraw(@NonNull Canvas canvas) {
super.onDraw(canvas);
int width = getWidth();
int height = getHeight();
int x = width / 2;
int y = (int) ((height * TEXT_CENTER) + (mRect.height() / 2));
canvas.drawText(mProgressStr, x, y, mTextPaint);
}
}
private final class CheckForShowBubble implements Runnable {
@Override
public void run() {
setShowBubble(true);
}
}
}
@@ -89,6 +89,14 @@ public class TextClock extends AppCompatTextView {
createTime(mTimeZone);
// Wait until onAttachedToWindow() to handle the ticker
chooseFormat(false);
}
private void createTime(String timeZone) {
if (timeZone != null) {
mTime = Calendar.getInstance(TimeZone.getTimeZone(timeZone));
} else {
mTime = Calendar.getInstance();
}
} private final Runnable mTicker = new Runnable() {
@Override
public void run() {
@@ -101,14 +109,6 @@ public class TextClock extends AppCompatTextView {
}
};
private void createTime(String timeZone) {
if (timeZone != null) {
mTime = Calendar.getInstance(TimeZone.getTimeZone(timeZone));
} else {
mTime = Calendar.getInstance();
}
}
public CharSequence getFormat12Hour() {
return mFormat12;
}
@@ -133,6 +133,17 @@ public class TextClock extends AppCompatTextView {
public boolean is24HourModeEnabled() {
return DateFormat.is24HourFormat(getContext());
}
public String getTimeZone() {
return mTimeZone;
}
public void setTimeZone(String timeZone) {
mTimeZone = timeZone;
createTime(timeZone);
onTimeChanged();
} private final ContentObserver mFormatChangeObserver = new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange) {
@@ -147,17 +158,6 @@ public class TextClock extends AppCompatTextView {
}
};
public String getTimeZone() {
return mTimeZone;
}
public void setTimeZone(String timeZone) {
mTimeZone = timeZone;
createTime(timeZone);
onTimeChanged();
}
/**
* Selects either one of {@link #getFormat12Hour()} or {@link #getFormat24Hour()}
* depending on whether the user has selected 24-hour format.
@@ -27,8 +27,8 @@ import java.util.Set;
*/
public abstract class AbstractMultimap<K, V, C extends Collection<V>> implements Map<K, C> {
protected Map<K, C> map;
protected final boolean threadSafeCollections;
protected Map<K, C> map;
protected AbstractMultimap(Map<K, C> map, boolean threadSafeCollections) {
this.map = map;
@@ -120,7 +120,9 @@ public abstract class AbstractMultimap<K, V, C extends Collection<V>> implements
return map.entrySet();
}
/** @return true if the collection was changed. */
/**
* @return true if the collection was changed.
*/
public synchronized boolean putElements(K key, Collection<V> values) {
C collection = map.get(key);
if (collection == null) {
@@ -130,7 +132,9 @@ public abstract class AbstractMultimap<K, V, C extends Collection<V>> implements
return collection.addAll(values);
}
/** @return true if the given element was removed. */
/**
* @return true if the given element was removed.
*/
public synchronized boolean removeElement(K key, V value) {
C collection = map.get(key);
if (collection == null) {
@@ -42,12 +42,13 @@ public class CollectionUtils {
/**
* Appends an element to the end of the array, growing the array if there is no more room.
* @param array The array to which to append the element. This must NOT be null.
*
* @param array The array to which to append the element. This must NOT be null.
* @param currentSize The number of elements in the array. Must be less than or equal to
* array.length.
* @param element The element to append.
* @param element The element to append.
* @return the array to which the element was appended. This may be different than the given
* array.
* array.
*/
public static <T> T[] append(T[] array, int currentSize, T element) {
if (currentSize + 1 > array.length) {
@@ -103,12 +104,12 @@ public class CollectionUtils {
* Inserts an element into the array at the specified index, growing the array if there is no
* more room.
*
* @param array The array to which to append the element. Must NOT be null.
* @param array The array to which to append the element. Must NOT be null.
* @param currentSize The number of elements in the array. Must be less than or equal to
* array.length.
* @param element The element to insert.
* @param element The element to insert.
* @return the array to which the element was appended. This may be different than the given
* array.
* array.
*/
public static <T> T[] insert(T[] array, int currentSize, int index, T element) {
if (currentSize + 1 <= array.length) {
@@ -60,7 +60,7 @@ public class ContainerHelpers {
}
public static <T> int binarySearch(T[] array, int size, T value,
Comparator<? super T> comparator) {
Comparator<? super T> comparator) {
int lo = 0;
int hi = size - 1;
Loaded 100 of 169 files, more files were not shown because too many files have changed in this diff. Show more