Compare commits
140 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7e211ee9a1 | |||
| 8e8883ba37 | |||
| 02c4398e48 | |||
| ad7a8dd7dc | |||
| e3338211d6 | |||
| 7cab4b9229 | |||
| ac5f1a0d93 | |||
| 798b9d0c98 | |||
| 3ff29aa38a | |||
| f8c2b9ffb0 | |||
| 888bb8897a | |||
| 192136e66c | |||
| 5057a57f7f | |||
| d81a4e0b7f | |||
| c63a06730f | |||
| bef326d2d7 | |||
| b8e85422f0 | |||
| d050bfdc68 | |||
| 3bac176bf6 | |||
| 7c506a42ae | |||
| 2c436e2027 | |||
| df0078b725 | |||
| 09c950a890 | |||
| e7e76ed68d | |||
| 06c1eeb995 | |||
| d545d852c5 | |||
| 3486e8dcf3 | |||
| 1956b700fc | |||
| 64ad9af344 | |||
| 55fec0b82c | |||
| 8323ef5f41 | |||
| 8dbab23de7 | |||
| 825d232613 | |||
| d797a03502 | |||
| 0ef6d74514 | |||
| 6234e897a8 | |||
| fe121f59b0 | |||
| 90cf5fcdec | |||
| 8b5782a5b6 | |||
| 68a131dbeb | |||
| 1411c02e18 | |||
| 81fe3f0108 | |||
| c15cf23168 | |||
| a79dc580a5 | |||
| 8a62c6295d | |||
| 88e77e1547 | |||
| 534619bc1a | |||
| ae904753f7 | |||
| 8c4a2cb529 | |||
| 16d4893480 | |||
| a7446e2f4c | |||
| 9dcae193a9 | |||
| 36fac0f3f4 | |||
| 24a4c176c0 | |||
| 9c7f50e91e | |||
| e52bc255f3 | |||
| ae4c9887d8 | |||
| 52201e2488 | |||
| c3e2b0e002 | |||
| 709915bf59 | |||
| 9d7ec6fd60 | |||
| ee9de376a3 | |||
| b54dc6f967 | |||
| abea85d831 | |||
| 972137c035 | |||
| 1cdef5e0ee | |||
| bd7ea64b02 | |||
| 20c850c10b | |||
| 0b021e6c42 | |||
| 0d109cdd4f | |||
| 1dab9e1a7d | |||
| 593b01819d | |||
| 029aa9c01b | |||
| 149b549d8d | |||
| 786635010e | |||
| d7fe170067 | |||
| e3d4be9a5a | |||
| 4086a73727 | |||
| 483e3a760f | |||
| 2757f881dc | |||
| e2aff0ece7 | |||
| 327526330f | |||
| ea976a4d0f | |||
| 728ada5e70 | |||
| c091ac4d67 | |||
| ee4c852f1b | |||
| 1a5d334f6c | |||
| 7d72ff3514 | |||
| e224e91100 | |||
| 7c5edd1b73 | |||
| 2621415f7c | |||
| a3184c46b6 | |||
| 1575ffa6ae | |||
| ee27da3de6 | |||
| 83a7224f2d | |||
| ecea2ecdf5 | |||
| f04060b31b | |||
| d411d1966a | |||
| 507bf07104 | |||
| 09061a38bc | |||
| 611a7db2e1 | |||
| a5cf428ce5 | |||
| 31f06a2d43 | |||
| 1d7a60b630 | |||
| a2fadbe513 | |||
| dee61e191c | |||
| 32b6461c6a | |||
| 93fff42693 | |||
| 61f429896c | |||
| a9e03837a3 | |||
| 89421946af | |||
| 218af8ea54 | |||
| d0f79ca473 | |||
| ec870759cf | |||
| 0405a535c7 | |||
| 814e4ba744 | |||
| 5621c1ab58 | |||
| f1fd8bc446 | |||
| 60fdd6cda9 | |||
| 3332363a10 | |||
| 538bd3f126 | |||
| 336f985894 | |||
| ba6687355e | |||
| 983980d8da | |||
| 82d4a401fd | |||
| 76e9f42734 | |||
| 0c0035370a | |||
| a3ac136b3b | |||
| ed1509b54f | |||
| 1d0dcd097c | |||
| 785c0469ac | |||
| 7594ae5fa5 | |||
| 6b4e08fdd1 | |||
| 65435341f3 | |||
| 9bc9f963b7 | |||
| a27501371f | |||
| 9fafebc8e7 | |||
| 59d2151c92 | |||
| 1cc2a05f90 | |||
| 8aea6f5473 |
@@ -42,7 +42,7 @@ body:
|
||||
label: Suwayomi-Server version
|
||||
description: You can find your Suwayomi-Server version in **More → About**.
|
||||
placeholder: |
|
||||
Example: "v2.0.1727"
|
||||
Example: "v2.1.1867"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Validate Gradle Wrapper
|
||||
uses: gradle/wrapper-validation-action@v3
|
||||
uses: gradle/actions/wrapper-validation@v4
|
||||
|
||||
build:
|
||||
name: Build pull request
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Validate Gradle Wrapper
|
||||
uses: gradle/wrapper-validation-action@v3
|
||||
uses: gradle/actions/wrapper-validation@v4
|
||||
|
||||
build:
|
||||
name: Build Jar
|
||||
@@ -112,16 +112,25 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os:
|
||||
- debian-all
|
||||
- linux-assets
|
||||
- linux-x64
|
||||
- macOS-x64
|
||||
- macOS-arm64
|
||||
- windows-x64
|
||||
include:
|
||||
- name: debian-all
|
||||
jre: linux-x64
|
||||
- name: appimage
|
||||
jre: linux-x64
|
||||
- name: linux-assets
|
||||
jre: linux-assets
|
||||
- name: linux-x64
|
||||
jre: linux-x64
|
||||
- name: macOS-x64
|
||||
jre: macOS-x64
|
||||
- name: macOS-arm64
|
||||
jre: macOS-arm64
|
||||
- name: windows-x64
|
||||
jre: windows-x64
|
||||
name: [debian-all, appimage, linux-assets, linux-x64, macOS-x64, macOS-arm64, windows-x64]
|
||||
|
||||
name: Make ${{ matrix.os }} release
|
||||
needs: [build,jlink]
|
||||
name: Make ${{ matrix.name }} release
|
||||
needs: [build, jlink]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download Jar
|
||||
@@ -132,9 +141,9 @@ jobs:
|
||||
|
||||
- name: Download JRE
|
||||
uses: actions/download-artifact@v4
|
||||
if: matrix.os != 'linux-assets' && matrix.os != 'debian-all'
|
||||
if: matrix.name != 'linux-assets' && matrix.name != 'debian-all'
|
||||
with:
|
||||
name: ${{ matrix.os }}-jre
|
||||
name: ${{ matrix.jre }}-jre
|
||||
path: jre
|
||||
|
||||
- name: Download icons
|
||||
@@ -148,16 +157,16 @@ jobs:
|
||||
with:
|
||||
name: scripts
|
||||
|
||||
- name: Make ${{ matrix.os }} release
|
||||
- name: Make ${{ matrix.name }} release
|
||||
run: |
|
||||
mkdir upload
|
||||
tar -xvpf scripts.tar.gz
|
||||
scripts/bundler.sh -o upload/ ${{ matrix.os }}
|
||||
scripts/bundler.sh -o upload/ ${{ matrix.name }}
|
||||
|
||||
- name: Upload ${{ matrix.os }} release
|
||||
- name: Upload ${{ matrix.name }} release
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.os }}
|
||||
name: ${{ matrix.name }}
|
||||
path: upload/*
|
||||
if-no-files-found: error
|
||||
|
||||
@@ -173,6 +182,10 @@ jobs:
|
||||
with:
|
||||
name: debian-all
|
||||
path: release
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: appimage
|
||||
path: release
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: linux-assets
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Validate Gradle Wrapper
|
||||
uses: gradle/wrapper-validation-action@v3
|
||||
uses: gradle/actions/wrapper-validation@v4
|
||||
|
||||
build:
|
||||
name: Build Jar
|
||||
@@ -114,15 +114,24 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os:
|
||||
- debian-all
|
||||
- linux-assets
|
||||
- linux-x64
|
||||
- macOS-x64
|
||||
- macOS-arm64
|
||||
- windows-x64
|
||||
include:
|
||||
- name: debian-all
|
||||
jre: linux-x64
|
||||
- name: appimage
|
||||
jre: linux-x64
|
||||
- name: linux-assets
|
||||
jre: linux-assets
|
||||
- name: linux-x64
|
||||
jre: linux-x64
|
||||
- name: macOS-x64
|
||||
jre: macOS-x64
|
||||
- name: macOS-arm64
|
||||
jre: macOS-arm64
|
||||
- name: windows-x64
|
||||
jre: windows-x64
|
||||
name: [debian-all, appimage, linux-assets, linux-x64, macOS-x64, macOS-arm64, windows-x64]
|
||||
|
||||
name: Make ${{ matrix.os }} release
|
||||
name: Make ${{ matrix.name }} release
|
||||
needs: [build, jlink]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -134,9 +143,9 @@ jobs:
|
||||
|
||||
- name: Download JRE
|
||||
uses: actions/download-artifact@v4
|
||||
if: matrix.os != 'linux-assets' && matrix.os != 'debian-all'
|
||||
if: matrix.name != 'linux-assets' && matrix.name != 'debian-all'
|
||||
with:
|
||||
name: ${{ matrix.os }}-jre
|
||||
name: ${{ matrix.jre }}-jre
|
||||
path: jre
|
||||
|
||||
- name: Download icons
|
||||
@@ -150,16 +159,16 @@ jobs:
|
||||
with:
|
||||
name: scripts
|
||||
|
||||
- name: Make ${{ matrix.os }} release
|
||||
- name: Make ${{ matrix.name }} release
|
||||
run: |
|
||||
mkdir upload/
|
||||
tar -xvpf scripts.tar.gz
|
||||
scripts/bundler.sh -o upload/ ${{ matrix.os }}
|
||||
scripts/bundler.sh -o upload/ ${{ matrix.name }}
|
||||
|
||||
- name: Upload ${{ matrix.os }} files
|
||||
- name: Upload ${{ matrix.name }} files
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.os }}
|
||||
name: ${{ matrix.name }}
|
||||
path: upload/*
|
||||
if-no-files-found: error
|
||||
|
||||
@@ -176,6 +185,10 @@ jobs:
|
||||
with:
|
||||
name: debian-all
|
||||
path: release
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: appimage
|
||||
path: release
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: linux-assets
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
name: Publish to WinGet
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["CI Publish"]
|
||||
types:
|
||||
- completed
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: Version
|
||||
required: false
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: windows-latest # action can only be run on windows
|
||||
steps:
|
||||
- uses: vedantmgoyal2009/winget-releaser@v2
|
||||
with:
|
||||
identifier: Suwayomi.Tachidesk-Server
|
||||
identifier: Suwayomi.Suwayomi-Server
|
||||
installers-regex: '.*x64.msi$'
|
||||
token: ${{ secrets.WINGET_PUBLISH_PAT }}
|
||||
version: ${{ inputs.version || github.ref_name }}
|
||||
release-tag: ${{ inputs.version || github.ref_name }}
|
||||
|
||||
@@ -20,3 +20,4 @@ scripts/OpenJDK*
|
||||
scripts/zulu*
|
||||
scripts/electron-*
|
||||
scripts/rcedit-*
|
||||
scripts/resources/*.so
|
||||
|
||||
@@ -7,7 +7,7 @@ package xyz.nulldev.ts.config
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import net.harawata.appdirs.AppDirsFactory
|
||||
import ca.gosyer.appdirs.AppDirs
|
||||
|
||||
const val CONFIG_PREFIX = "suwayomi.tachidesk.config"
|
||||
|
||||
@@ -15,6 +15,6 @@ val ApplicationRootDir: String
|
||||
get(): String {
|
||||
return System.getProperty(
|
||||
"$CONFIG_PREFIX.server.rootDir",
|
||||
AppDirsFactory.getInstance().getUserDataDir("Tachidesk", null, null),
|
||||
AppDirs { appName = "Tachidesk" }.getUserDataDir(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,10 +10,11 @@ package xyz.nulldev.ts.config
|
||||
import ch.qos.logback.classic.Level
|
||||
import com.typesafe.config.Config
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import com.typesafe.config.ConfigObject
|
||||
import com.typesafe.config.ConfigValue
|
||||
import com.typesafe.config.ConfigValueFactory
|
||||
import com.typesafe.config.parser.ConfigDocument
|
||||
import com.typesafe.config.parser.ConfigDocumentFactory
|
||||
import io.github.config4k.toConfig
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
@@ -112,7 +113,8 @@ open class ConfigManager {
|
||||
value: Any,
|
||||
) {
|
||||
mutex.withLock {
|
||||
val configValue = ConfigValueFactory.fromAnyRef(value)
|
||||
val actualValue = if (value is Enum<*>) value.name else value
|
||||
val configValue = actualValue.toConfig("internal").getValue("internal")
|
||||
|
||||
updateUserConfigFile(path, configValue)
|
||||
internalConfig = internalConfig.withValue(path, configValue)
|
||||
@@ -137,12 +139,17 @@ open class ConfigManager {
|
||||
* - adds missing settings
|
||||
* - removes outdated settings
|
||||
*/
|
||||
fun updateUserConfig() {
|
||||
fun updateUserConfig(migrate: ConfigDocument.(Config) -> ConfigDocument) {
|
||||
val serverConfig = ConfigFactory.parseResources("server-reference.conf")
|
||||
val userConfig = getUserConfig()
|
||||
|
||||
val hasMissingSettings = serverConfig.entrySet().any { !userConfig.hasPath(it.key) }
|
||||
val hasOutdatedSettings = userConfig.entrySet().any { !serverConfig.hasPath(it.key) }
|
||||
// NOTE: if more than 1 dot is included, that's a nested setting, which we need to filter out here
|
||||
val refKeys =
|
||||
serverConfig.root().entries.flatMap {
|
||||
(it.value as? ConfigObject)?.entries?.map { e -> "${it.key}.${e.key}" }.orEmpty()
|
||||
}
|
||||
val hasMissingSettings = refKeys.any { !userConfig.hasPath(it) }
|
||||
val hasOutdatedSettings = userConfig.entrySet().any { !refKeys.contains(it.key) && it.key.count { c -> c == '.' } <= 1 }
|
||||
val isUserConfigOutdated = hasMissingSettings || hasOutdatedSettings
|
||||
if (!isUserConfigOutdated) {
|
||||
return
|
||||
@@ -158,10 +165,15 @@ open class ConfigManager {
|
||||
.filter {
|
||||
serverConfig.hasPath(
|
||||
it.key,
|
||||
)
|
||||
) ||
|
||||
it.key.count { c -> c == '.' } > 1
|
||||
}.forEach { newUserConfigDoc = newUserConfigDoc.withValue(it.key, it.value) }
|
||||
|
||||
newUserConfigDoc =
|
||||
migrate(newUserConfigDoc, internalConfig)
|
||||
|
||||
userConfigFile.writeText(newUserConfigDoc.render())
|
||||
getUserConfig().entrySet().forEach { internalConfig = internalConfig.withValue(it.key, it.value) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,9 +8,12 @@ package xyz.nulldev.ts.config
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import com.typesafe.config.Config
|
||||
import com.typesafe.config.ConfigException
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import com.typesafe.config.ConfigValueFactory
|
||||
import io.github.config4k.ClassContainer
|
||||
import io.github.config4k.TypeReference
|
||||
import io.github.config4k.getValue
|
||||
import io.github.config4k.readers.SelectReader
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
/**
|
||||
@@ -26,7 +29,7 @@ abstract class ConfigModule(
|
||||
*/
|
||||
abstract class SystemPropertyOverridableConfigModule(
|
||||
getConfig: () -> Config,
|
||||
moduleName: String,
|
||||
val moduleName: String,
|
||||
) : ConfigModule(getConfig) {
|
||||
val overridableConfig = SystemPropertyOverrideDelegate(getConfig, moduleName)
|
||||
}
|
||||
@@ -41,25 +44,33 @@ class SystemPropertyOverrideDelegate(
|
||||
property: KProperty<*>,
|
||||
): T {
|
||||
val config = getConfig()
|
||||
val configValue: T = config.getValue(thisRef, property)
|
||||
|
||||
val combined =
|
||||
System.getProperty(
|
||||
"$CONFIG_PREFIX.$moduleName.${property.name}",
|
||||
if (T::class.simpleName == "List") {
|
||||
ConfigValueFactory.fromAnyRef(configValue).render()
|
||||
} else {
|
||||
configValue.toString()
|
||||
},
|
||||
)
|
||||
val systemProperty =
|
||||
System.getProperty("$CONFIG_PREFIX.$moduleName.${property.name}")
|
||||
if (systemProperty == null) {
|
||||
val configValue: T = config.getValue(thisRef, property)
|
||||
return configValue
|
||||
}
|
||||
|
||||
return when (T::class.simpleName) {
|
||||
"Int" -> combined.toInt()
|
||||
"Boolean" -> combined.toBoolean()
|
||||
"Double" -> combined.toDouble()
|
||||
"List" -> ConfigFactory.parseString("internal=" + combined).getStringList("internal").orEmpty()
|
||||
// add more types as needed
|
||||
else -> combined // covers String
|
||||
} as T
|
||||
val systemPropertyConfig =
|
||||
try {
|
||||
ConfigFactory.parseString("internal=$systemProperty")
|
||||
} catch (_: ConfigException) {
|
||||
ConfigFactory.parseMap(mapOf("internal" to systemProperty))
|
||||
}
|
||||
|
||||
val genericType = object : TypeReference<T>() {}.genericType()
|
||||
val clazz = ClassContainer(T::class, genericType)
|
||||
val reader = SelectReader.getReader(clazz)
|
||||
val path = property.name
|
||||
|
||||
val result = reader(systemPropertyConfig, "internal")
|
||||
return try {
|
||||
result as T
|
||||
} catch (e: Exception) {
|
||||
throw result
|
||||
?.let { e }
|
||||
?: ConfigException.BadPath(path, "take a look at your config")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,4 +47,5 @@ dependencies {
|
||||
|
||||
// OpenJDK lacks native JPEG encoder and native WEBP decoder
|
||||
implementation(libs.bundles.twelvemonkeys)
|
||||
implementation(libs.imageio.webp)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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 android.annotation;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.ElementType.PARAMETER;
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
|
||||
/**
|
||||
* <p>Denotes that the annotated element represents a packed color
|
||||
* long. If applied to a long array, every element in the array
|
||||
* represents a color long. For more information on how colors
|
||||
* are packed in a long, please refer to the documentation of
|
||||
* the {@link android.graphics.Color} class.</p>
|
||||
*
|
||||
* <p>Example:</p>
|
||||
*
|
||||
* <pre>{@code
|
||||
* public void setFillColor(@ColorLong long color);
|
||||
* }</pre>
|
||||
*
|
||||
* @see android.graphics.Color
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@Retention(SOURCE)
|
||||
@Target({PARAMETER,METHOD,LOCAL_VARIABLE,FIELD})
|
||||
public @interface ColorLong {
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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 android.annotation;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.ElementType.PARAMETER;
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
|
||||
/**
|
||||
* <p>Denotes that the annotated element represents a half-precision floating point
|
||||
* value. Such values are stored in short data types and can be manipulated with
|
||||
* the {@link android.util.Half} class. If applied to an array of short, every
|
||||
* element in the array represents a half-precision float.</p>
|
||||
*
|
||||
* <p>Example:</p>
|
||||
*
|
||||
* <pre>{@code
|
||||
* public abstract void setPosition(@HalfFloat short x, @HalfFloat short y, @HalfFloat short z);
|
||||
* }</pre>
|
||||
*
|
||||
* @see android.util.Half
|
||||
* @see android.util.Half#toHalf(float)
|
||||
* @see android.util.Half#toFloat(short)
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@Retention(SOURCE)
|
||||
@Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD})
|
||||
public @interface HalfFloat {
|
||||
}
|
||||
@@ -2,17 +2,17 @@ package android.graphics;
|
||||
|
||||
import android.annotation.ColorInt;
|
||||
import android.annotation.NonNull;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Iterator;
|
||||
import javax.imageio.IIOImage;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageWriteParam;
|
||||
import javax.imageio.ImageWriter;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.Raster;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Iterator;
|
||||
|
||||
|
||||
public final class Bitmap {
|
||||
private final int width;
|
||||
@@ -75,6 +75,19 @@ public final class Bitmap {
|
||||
}
|
||||
}
|
||||
|
||||
private static int configToBufferedImageType(Config config) {
|
||||
switch (config) {
|
||||
case ALPHA_8:
|
||||
return BufferedImage.TYPE_BYTE_GRAY;
|
||||
case RGB_565:
|
||||
return BufferedImage.TYPE_USHORT_565_RGB;
|
||||
case ARGB_8888:
|
||||
return BufferedImage.TYPE_INT_ARGB;
|
||||
default:
|
||||
throw new UnsupportedOperationException("Bitmap.Config(" + config + ") not supported");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Common code for checking that x and y are >= 0
|
||||
*
|
||||
@@ -106,7 +119,7 @@ public final class Bitmap {
|
||||
}
|
||||
|
||||
public static Bitmap createBitmap(int width, int height, Config config) {
|
||||
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||
BufferedImage image = new BufferedImage(width, height, configToBufferedImageType(config));
|
||||
return new Bitmap(image);
|
||||
}
|
||||
|
||||
@@ -144,8 +157,10 @@ public final class Bitmap {
|
||||
formatString = "png";
|
||||
} else if (format == Bitmap.CompressFormat.JPEG) {
|
||||
formatString = "jpg";
|
||||
} else if (format == Bitmap.CompressFormat.WEBP || format == Bitmap.CompressFormat.WEBP_LOSSY) {
|
||||
formatString = "webp";
|
||||
} else {
|
||||
throw new IllegalArgumentException("unsupported compression format!");
|
||||
throw new IllegalArgumentException("unsupported compression format! " + format);
|
||||
}
|
||||
|
||||
Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(formatString);
|
||||
@@ -162,14 +177,19 @@ public final class Bitmap {
|
||||
}
|
||||
writer.setOutput(ios);
|
||||
|
||||
BufferedImage img = image;
|
||||
|
||||
ImageWriteParam param = writer.getDefaultWriteParam();
|
||||
if ("jpg".equals(formatString)) {
|
||||
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
|
||||
param.setCompressionQuality(qualityFloat);
|
||||
|
||||
img = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB);
|
||||
img.getGraphics().drawImage(image, 0, 0, null);
|
||||
}
|
||||
|
||||
try {
|
||||
writer.write(null, new IIOImage(image, null, null), param);
|
||||
writer.write(null, new IIOImage(img, null, null), param);
|
||||
ios.close();
|
||||
writer.dispose();
|
||||
} catch (IOException ex) {
|
||||
@@ -179,6 +199,12 @@ public final class Bitmap {
|
||||
return true;
|
||||
}
|
||||
|
||||
public Bitmap copy(Config config, boolean isMutable) {
|
||||
Bitmap ret = createBitmap(width, height, config);
|
||||
ret.image.getGraphics().drawImage(image, 0, 0, null);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared code to check for illegal arguments passed to getPixels()
|
||||
* or setPixels()
|
||||
@@ -224,12 +250,18 @@ public final class Bitmap {
|
||||
int x, int y, int width, int height) {
|
||||
checkPixelsAccess(x, y, width, height, offset, stride, pixels);
|
||||
|
||||
Raster raster = image.getData();
|
||||
int[] rasterPixels = raster.getPixels(x, y, width, height, (int[]) null);
|
||||
image.getRGB(x, y, width, height, pixels, offset, stride);
|
||||
}
|
||||
|
||||
for (int ht = 0; ht < height; ht++) {
|
||||
int rowOffset = offset + stride * ht;
|
||||
System.arraycopy(rasterPixels, ht * width, pixels, rowOffset, width);
|
||||
}
|
||||
public void eraseColor(int c) {
|
||||
java.awt.Color color = Color.valueOf(c).toJavaColor();
|
||||
Graphics2D graphics = image.createGraphics();
|
||||
graphics.setColor(color);
|
||||
graphics.fillRect(0, 0, width, height);
|
||||
graphics.dispose();
|
||||
}
|
||||
|
||||
public void recycle() {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,184 @@
|
||||
package android.graphics;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
import java.awt.BasicStroke;
|
||||
import java.awt.Font;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.Shape;
|
||||
import java.awt.font.GlyphVector;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
public final class Canvas {
|
||||
private BufferedImage canvasImage;
|
||||
private Graphics2D canvas;
|
||||
private List<AffineTransform> transformStack = new ArrayList<AffineTransform>();
|
||||
|
||||
private static final String TAG = "Canvas";
|
||||
|
||||
public Canvas(Bitmap bitmap) {
|
||||
canvasImage = bitmap.getImage();
|
||||
canvas = canvasImage.createGraphics();
|
||||
}
|
||||
|
||||
public void drawBitmap(Bitmap sourceBitmap, Rect src, Rect dst, Paint paint) {
|
||||
public void drawBitmap(Bitmap sourceBitmap, Rect src, Rect dst, Paint paint) {
|
||||
BufferedImage sourceImage = sourceBitmap.getImage();
|
||||
BufferedImage sourceImageCropped = sourceImage.getSubimage(src.left, src.top, src.getWidth(), src.getHeight());
|
||||
canvas.drawImage(sourceImageCropped, null, dst.left, dst.top);
|
||||
canvas.drawImage(sourceImageCropped, dst.left, dst.top, dst.getWidth(), dst.getHeight(), null);
|
||||
}
|
||||
|
||||
public void drawBitmap(Bitmap sourceBitmap, float left, float top, Paint paint) {
|
||||
BufferedImage sourceImage = sourceBitmap.getImage();
|
||||
canvas.drawImage(sourceImage, null, (int) left, (int) top);
|
||||
}
|
||||
|
||||
public void drawText(@NonNull char[] text, int index, int count, float x, float y,
|
||||
@NonNull Paint paint) {
|
||||
drawText(new String(text, index, count), x, y, paint);
|
||||
}
|
||||
|
||||
public void drawText(@NonNull String text, float x, float y, @NonNull Paint paint) {
|
||||
applyPaint(paint);
|
||||
GlyphVector glyphVector = paint.getFont().createGlyphVector(canvas.getFontRenderContext(), text);
|
||||
Shape textShape = glyphVector.getOutline();
|
||||
switch (paint.getStyle()) {
|
||||
case Paint.Style.FILL:
|
||||
canvas.drawString(text, x, y);
|
||||
break;
|
||||
case Paint.Style.STROKE:
|
||||
save();
|
||||
translate(x, y);
|
||||
canvas.draw(textShape);
|
||||
restore();
|
||||
break;
|
||||
case Paint.Style.FILL_AND_STROKE:
|
||||
save();
|
||||
translate(x, y);
|
||||
canvas.draw(textShape);
|
||||
canvas.fill(textShape);
|
||||
restore();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void drawText(@NonNull String text, int start, int end, float x, float y,
|
||||
@NonNull Paint paint) {
|
||||
drawText(text.substring(start, end), x, y, paint);
|
||||
}
|
||||
|
||||
public void drawText(@NonNull CharSequence text, int start, int end, float x, float y,
|
||||
@NonNull Paint paint) {
|
||||
String str = text.subSequence(start, end).toString();
|
||||
drawText(str, x, y, paint);
|
||||
}
|
||||
|
||||
public void drawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public void drawRoundRect(float left, float top, float right, float bottom, float rx, float ry,
|
||||
@NonNull Paint paint) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public void drawPath(@NonNull Path path, @NonNull Paint paint) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public void translate(float dx, float dy) {
|
||||
if (dx == 0.0f && dy == 0.0f) return;
|
||||
// TODO: check this, should translations stack?
|
||||
canvas.translate(dx, dy);
|
||||
}
|
||||
|
||||
public void scale(float sx, float sy) {
|
||||
if (sx == 1.0f && sy == 1.0f) return;
|
||||
canvas.scale(sx, sy);
|
||||
}
|
||||
|
||||
public final void scale(float sx, float sy, float px, float py) {
|
||||
if (sx == 1.0f && sy == 1.0f) return;
|
||||
translate(px, py);
|
||||
scale(sx, sy);
|
||||
translate(-px, -py);
|
||||
}
|
||||
|
||||
public void rotate(float degrees) {
|
||||
if (degrees == 0.0f) return;
|
||||
canvas.rotate(degrees);
|
||||
}
|
||||
|
||||
public final void rotate(float degrees, float px, float py) {
|
||||
if (degrees == 0.0f) return;
|
||||
canvas.rotate(degrees, px, py);
|
||||
}
|
||||
|
||||
public int getSaveCount() {
|
||||
return transformStack.size();
|
||||
}
|
||||
|
||||
public int save() {
|
||||
transformStack.add(canvas.getTransform());
|
||||
return getSaveCount();
|
||||
}
|
||||
|
||||
public void restoreToCount(int saveCount) {
|
||||
if (saveCount < 1) {
|
||||
throw new IllegalArgumentException(
|
||||
"Underflow in restoreToCount - more restores than saves");
|
||||
}
|
||||
if (saveCount > getSaveCount()) {
|
||||
throw new IllegalArgumentException("Overflow in restoreToCount");
|
||||
|
||||
}
|
||||
AffineTransform ts = transformStack.get(saveCount - 1);
|
||||
canvas.setTransform(ts);
|
||||
while (transformStack.size() >= saveCount) {
|
||||
transformStack.remove(transformStack.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
public void restore() {
|
||||
restoreToCount(getSaveCount());
|
||||
}
|
||||
|
||||
public boolean getClipBounds(@NonNull Rect bounds) {
|
||||
Rectangle r = canvas.getClipBounds();
|
||||
if (r == null) {
|
||||
bounds.left = 0;
|
||||
bounds.top = 0;
|
||||
bounds.right = canvasImage.getWidth();
|
||||
bounds.bottom = canvasImage.getHeight();
|
||||
return true;
|
||||
}
|
||||
bounds.left = r.x;
|
||||
bounds.top = r.y;
|
||||
bounds.right = r.x + r.width;
|
||||
bounds.bottom = r.y + r.height;
|
||||
return r.width != 0 && r.height != 0;
|
||||
}
|
||||
|
||||
private void applyPaint(Paint paint) {
|
||||
canvas.setFont(paint.getFont());
|
||||
java.awt.Color color = Color.valueOf(paint.getColorLong()).toJavaColor();
|
||||
canvas.setColor(color);
|
||||
canvas.setStroke(new BasicStroke(paint.getStrokeWidth(), BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
|
||||
if (paint.isAntiAlias()) {
|
||||
canvas.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
} else {
|
||||
canvas.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
|
||||
}
|
||||
if (paint.isDither()) {
|
||||
canvas.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
|
||||
} else {
|
||||
canvas.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
|
||||
}
|
||||
// TODO: use more from paint?
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,578 @@
|
||||
/*
|
||||
* Copyright (C) 2006 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 android.graphics;
|
||||
|
||||
import android.annotation.ColorInt;
|
||||
import android.annotation.ColorLong;
|
||||
import android.annotation.HalfFloat;
|
||||
import android.annotation.IntRange;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.Size;
|
||||
import android.util.Half;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.function.DoubleUnaryOperator;
|
||||
|
||||
public class Color {
|
||||
@ColorInt public static final int BLACK = 0xFF000000;
|
||||
@ColorInt public static final int DKGRAY = 0xFF444444;
|
||||
@ColorInt public static final int GRAY = 0xFF888888;
|
||||
@ColorInt public static final int LTGRAY = 0xFFCCCCCC;
|
||||
@ColorInt public static final int WHITE = 0xFFFFFFFF;
|
||||
@ColorInt public static final int RED = 0xFFFF0000;
|
||||
@ColorInt public static final int GREEN = 0xFF00FF00;
|
||||
@ColorInt public static final int BLUE = 0xFF0000FF;
|
||||
@ColorInt public static final int YELLOW = 0xFFFFFF00;
|
||||
@ColorInt public static final int CYAN = 0xFF00FFFF;
|
||||
@ColorInt public static final int MAGENTA = 0xFFFF00FF;
|
||||
@ColorInt public static final int TRANSPARENT = 0;
|
||||
|
||||
@NonNull
|
||||
@Size(min = 4, max = 5)
|
||||
private final float[] mComponents;
|
||||
|
||||
@NonNull
|
||||
private final ColorSpace mColorSpace;
|
||||
|
||||
public Color() {
|
||||
// This constructor is required for compatibility with previous APIs
|
||||
mComponents = new float[] { 0.0f, 0.0f, 0.0f, 1.0f };
|
||||
mColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
|
||||
}
|
||||
|
||||
private Color(float r, float g, float b, float a) {
|
||||
this(r, g, b, a, ColorSpace.get(ColorSpace.Named.SRGB));
|
||||
}
|
||||
|
||||
private Color(float r, float g, float b, float a, @NonNull ColorSpace colorSpace) {
|
||||
mComponents = new float[] { r, g, b, a };
|
||||
mColorSpace = colorSpace;
|
||||
}
|
||||
|
||||
private Color(@Size(min = 4, max = 5) float[] components, @NonNull ColorSpace colorSpace) {
|
||||
mComponents = components;
|
||||
mColorSpace = colorSpace;
|
||||
}
|
||||
|
||||
public java.awt.Color toJavaColor() {
|
||||
return new java.awt.Color(red(), green(), blue(), alpha());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public ColorSpace getColorSpace() {
|
||||
return mColorSpace;
|
||||
}
|
||||
|
||||
public ColorSpace.Model getModel() {
|
||||
return mColorSpace.getModel();
|
||||
}
|
||||
|
||||
public boolean isWideGamut() {
|
||||
return getColorSpace().isWideGamut();
|
||||
}
|
||||
|
||||
public boolean isSrgb() {
|
||||
return getColorSpace().isSrgb();
|
||||
}
|
||||
|
||||
@IntRange(from = 4, to = 5)
|
||||
public int getComponentCount() {
|
||||
return mColorSpace.getComponentCount() + 1;
|
||||
}
|
||||
|
||||
@ColorLong
|
||||
public long pack() {
|
||||
return pack(mComponents[0], mComponents[1], mComponents[2], mComponents[3], mColorSpace);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Color convert(@NonNull ColorSpace colorSpace) {
|
||||
ColorSpace.Connector connector = ColorSpace.connect(mColorSpace, colorSpace);
|
||||
float[] color = new float[] {
|
||||
mComponents[0], mComponents[1], mComponents[2], mComponents[3]
|
||||
};
|
||||
connector.transform(color);
|
||||
return new Color(color, colorSpace);
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public int toArgb() {
|
||||
if (mColorSpace.isSrgb()) {
|
||||
return ((int) (mComponents[3] * 255.0f + 0.5f) << 24) |
|
||||
((int) (mComponents[0] * 255.0f + 0.5f) << 16) |
|
||||
((int) (mComponents[1] * 255.0f + 0.5f) << 8) |
|
||||
(int) (mComponents[2] * 255.0f + 0.5f);
|
||||
}
|
||||
|
||||
float[] color = new float[] {
|
||||
mComponents[0], mComponents[1], mComponents[2], mComponents[3]
|
||||
};
|
||||
// The transformation saturates the output
|
||||
ColorSpace.connect(mColorSpace).transform(color);
|
||||
|
||||
return ((int) (color[3] * 255.0f + 0.5f) << 24) |
|
||||
((int) (color[0] * 255.0f + 0.5f) << 16) |
|
||||
((int) (color[1] * 255.0f + 0.5f) << 8) |
|
||||
(int) (color[2] * 255.0f + 0.5f);
|
||||
}
|
||||
|
||||
public float red() {
|
||||
return mComponents[0];
|
||||
}
|
||||
|
||||
public float green() {
|
||||
return mComponents[1];
|
||||
}
|
||||
|
||||
public float blue() {
|
||||
return mComponents[2];
|
||||
}
|
||||
|
||||
public float alpha() {
|
||||
return mComponents[mComponents.length - 1];
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Size(min = 4, max = 5)
|
||||
public float[] getComponents() {
|
||||
return Arrays.copyOf(mComponents, mComponents.length);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Size(min = 4)
|
||||
public float[] getComponents(@Nullable @Size(min = 4) float[] components) {
|
||||
if (components == null) {
|
||||
return Arrays.copyOf(mComponents, mComponents.length);
|
||||
}
|
||||
|
||||
if (components.length < mComponents.length) {
|
||||
throw new IllegalArgumentException("The specified array's length must be at "
|
||||
+ "least " + mComponents.length);
|
||||
}
|
||||
|
||||
System.arraycopy(mComponents, 0, components, 0, mComponents.length);
|
||||
return components;
|
||||
}
|
||||
|
||||
public float getComponent(@IntRange(from = 0, to = 4) int component) {
|
||||
return mComponents[component];
|
||||
}
|
||||
|
||||
public float luminance() {
|
||||
if (mColorSpace.getModel() != ColorSpace.Model.RGB) {
|
||||
throw new IllegalArgumentException("The specified color must be encoded in an RGB " +
|
||||
"color space. The supplied color space is " + mColorSpace.getModel());
|
||||
}
|
||||
|
||||
DoubleUnaryOperator eotf = ((ColorSpace.Rgb) mColorSpace).getEotf();
|
||||
double r = eotf.applyAsDouble(mComponents[0]);
|
||||
double g = eotf.applyAsDouble(mComponents[1]);
|
||||
double b = eotf.applyAsDouble(mComponents[2]);
|
||||
|
||||
return saturate((float) ((0.2126 * r) + (0.7152 * g) + (0.0722 * b)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Color color = (Color) o;
|
||||
|
||||
//noinspection SimplifiableIfStatement
|
||||
if (!Arrays.equals(mComponents, color.mComponents)) return false;
|
||||
return mColorSpace.equals(color.mColorSpace);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = Arrays.hashCode(mComponents);
|
||||
result = 31 * result + mColorSpace.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public String toString() {
|
||||
StringBuilder b = new StringBuilder("Color(");
|
||||
for (float c : mComponents) {
|
||||
b.append(c).append(", ");
|
||||
}
|
||||
b.append(mColorSpace.getName());
|
||||
b.append(')');
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static ColorSpace colorSpace(@ColorLong long color) {
|
||||
return ColorSpace.get((int) (color & 0x3fL));
|
||||
}
|
||||
|
||||
public static float red(@ColorLong long color) {
|
||||
if ((color & 0x3fL) == 0L) return ((color >> 48) & 0xff) / 255.0f;
|
||||
return Half.toFloat((short) ((color >> 48) & 0xffff));
|
||||
}
|
||||
|
||||
public static float green(@ColorLong long color) {
|
||||
if ((color & 0x3fL) == 0L) return ((color >> 40) & 0xff) / 255.0f;
|
||||
return Half.toFloat((short) ((color >> 32) & 0xffff));
|
||||
}
|
||||
|
||||
public static float blue(@ColorLong long color) {
|
||||
if ((color & 0x3fL) == 0L) return ((color >> 32) & 0xff) / 255.0f;
|
||||
return Half.toFloat((short) ((color >> 16) & 0xffff));
|
||||
}
|
||||
|
||||
public static float alpha(@ColorLong long color) {
|
||||
if ((color & 0x3fL) == 0L) return ((color >> 56) & 0xff) / 255.0f;
|
||||
return ((color >> 6) & 0x3ff) / 1023.0f;
|
||||
}
|
||||
|
||||
public static boolean isSrgb(@ColorLong long color) {
|
||||
return colorSpace(color).isSrgb();
|
||||
}
|
||||
|
||||
public static boolean isWideGamut(@ColorLong long color) {
|
||||
return colorSpace(color).isWideGamut();
|
||||
}
|
||||
|
||||
public static boolean isInColorSpace(@ColorLong long color, @NonNull ColorSpace colorSpace) {
|
||||
return (int) (color & 0x3fL) == colorSpace.getId();
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public static int toArgb(@ColorLong long color) {
|
||||
if ((color & 0x3fL) == 0L) return (int) (color >> 32);
|
||||
|
||||
float r = red(color);
|
||||
float g = green(color);
|
||||
float b = blue(color);
|
||||
float a = alpha(color);
|
||||
|
||||
// The transformation saturates the output
|
||||
float[] c = ColorSpace.connect(colorSpace(color)).transform(r, g, b);
|
||||
|
||||
return ((int) (a * 255.0f + 0.5f) << 24) |
|
||||
((int) (c[0] * 255.0f + 0.5f) << 16) |
|
||||
((int) (c[1] * 255.0f + 0.5f) << 8) |
|
||||
(int) (c[2] * 255.0f + 0.5f);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Color valueOf(@ColorInt int color) {
|
||||
float r = ((color >> 16) & 0xff) / 255.0f;
|
||||
float g = ((color >> 8) & 0xff) / 255.0f;
|
||||
float b = ((color ) & 0xff) / 255.0f;
|
||||
float a = ((color >> 24) & 0xff) / 255.0f;
|
||||
return new Color(r, g, b, a, ColorSpace.get(ColorSpace.Named.SRGB));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Color valueOf(@ColorLong long color) {
|
||||
return new Color(red(color), green(color), blue(color), alpha(color), colorSpace(color));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Color valueOf(float r, float g, float b) {
|
||||
return new Color(r, g, b, 1.0f);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Color valueOf(float r, float g, float b, float a) {
|
||||
return new Color(saturate(r), saturate(g), saturate(b), saturate(a));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Color valueOf(float r, float g, float b, float a, @NonNull ColorSpace colorSpace) {
|
||||
if (colorSpace.getComponentCount() > 3) {
|
||||
throw new IllegalArgumentException("The specified color space must use a color model " +
|
||||
"with at most 3 color components");
|
||||
}
|
||||
return new Color(r, g, b, a, colorSpace);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static Color valueOf(@NonNull @Size(min = 4, max = 5) float[] components,
|
||||
@NonNull ColorSpace colorSpace) {
|
||||
if (components.length < colorSpace.getComponentCount() + 1) {
|
||||
throw new IllegalArgumentException("Received a component array of length " +
|
||||
components.length + " but the color model requires " +
|
||||
(colorSpace.getComponentCount() + 1) + " (including alpha)");
|
||||
}
|
||||
return new Color(Arrays.copyOf(components, colorSpace.getComponentCount() + 1), colorSpace);
|
||||
}
|
||||
|
||||
@ColorLong
|
||||
public static long pack(@ColorInt int color) {
|
||||
return (color & 0xffffffffL) << 32;
|
||||
}
|
||||
|
||||
@ColorLong
|
||||
public static long pack(float red, float green, float blue) {
|
||||
return pack(red, green, blue, 1.0f, ColorSpace.get(ColorSpace.Named.SRGB));
|
||||
}
|
||||
|
||||
@ColorLong
|
||||
public static long pack(float red, float green, float blue, float alpha) {
|
||||
return pack(red, green, blue, alpha, ColorSpace.get(ColorSpace.Named.SRGB));
|
||||
}
|
||||
|
||||
@ColorLong
|
||||
public static long pack(float red, float green, float blue, float alpha,
|
||||
@NonNull ColorSpace colorSpace) {
|
||||
if (colorSpace.isSrgb()) {
|
||||
int argb =
|
||||
((int) (alpha * 255.0f + 0.5f) << 24) |
|
||||
((int) (red * 255.0f + 0.5f) << 16) |
|
||||
((int) (green * 255.0f + 0.5f) << 8) |
|
||||
(int) (blue * 255.0f + 0.5f);
|
||||
return (argb & 0xffffffffL) << 32;
|
||||
}
|
||||
|
||||
int id = colorSpace.getId();
|
||||
if (id == ColorSpace.MIN_ID) {
|
||||
throw new IllegalArgumentException(
|
||||
"Unknown color space, please use a color space returned by ColorSpace.get()");
|
||||
}
|
||||
if (colorSpace.getComponentCount() > 3) {
|
||||
throw new IllegalArgumentException(
|
||||
"The color space must use a color model with at most 3 components");
|
||||
}
|
||||
|
||||
@HalfFloat short r = Half.toHalf(red);
|
||||
@HalfFloat short g = Half.toHalf(green);
|
||||
@HalfFloat short b = Half.toHalf(blue);
|
||||
|
||||
int a = (int) (Math.max(0.0f, Math.min(alpha, 1.0f)) * 1023.0f + 0.5f);
|
||||
|
||||
// Suppress sign extension
|
||||
return (r & 0xffffL) << 48 |
|
||||
(g & 0xffffL) << 32 |
|
||||
(b & 0xffffL) << 16 |
|
||||
(a & 0x3ffL ) << 6 |
|
||||
id & 0x3fL;
|
||||
}
|
||||
|
||||
@ColorLong
|
||||
public static long convert(@ColorInt int color, @NonNull ColorSpace colorSpace) {
|
||||
float r = ((color >> 16) & 0xff) / 255.0f;
|
||||
float g = ((color >> 8) & 0xff) / 255.0f;
|
||||
float b = ((color ) & 0xff) / 255.0f;
|
||||
float a = ((color >> 24) & 0xff) / 255.0f;
|
||||
ColorSpace source = ColorSpace.get(ColorSpace.Named.SRGB);
|
||||
return convert(r, g, b, a, source, colorSpace);
|
||||
}
|
||||
|
||||
@ColorLong
|
||||
public static long convert(@ColorLong long color, @NonNull ColorSpace colorSpace) {
|
||||
float r = red(color);
|
||||
float g = green(color);
|
||||
float b = blue(color);
|
||||
float a = alpha(color);
|
||||
ColorSpace source = colorSpace(color);
|
||||
return convert(r, g, b, a, source, colorSpace);
|
||||
}
|
||||
|
||||
@ColorLong
|
||||
public static long convert(float r, float g, float b, float a,
|
||||
@NonNull ColorSpace source, @NonNull ColorSpace destination) {
|
||||
float[] c = ColorSpace.connect(source, destination).transform(r, g, b);
|
||||
return pack(c[0], c[1], c[2], a, destination);
|
||||
}
|
||||
|
||||
@ColorLong
|
||||
public static long convert(@ColorLong long color, @NonNull ColorSpace.Connector connector) {
|
||||
float r = red(color);
|
||||
float g = green(color);
|
||||
float b = blue(color);
|
||||
float a = alpha(color);
|
||||
return convert(r, g, b, a, connector);
|
||||
}
|
||||
|
||||
@ColorLong
|
||||
public static long convert(float r, float g, float b, float a,
|
||||
@NonNull ColorSpace.Connector connector) {
|
||||
float[] c = connector.transform(r, g, b);
|
||||
return pack(c[0], c[1], c[2], a, connector.getDestination());
|
||||
}
|
||||
|
||||
public static float luminance(@ColorLong long color) {
|
||||
ColorSpace colorSpace = colorSpace(color);
|
||||
if (colorSpace.getModel() != ColorSpace.Model.RGB) {
|
||||
throw new IllegalArgumentException("The specified color must be encoded in an RGB " +
|
||||
"color space. The supplied color space is " + colorSpace.getModel());
|
||||
}
|
||||
|
||||
DoubleUnaryOperator eotf = ((ColorSpace.Rgb) colorSpace).getEotf();
|
||||
double r = eotf.applyAsDouble(red(color));
|
||||
double g = eotf.applyAsDouble(green(color));
|
||||
double b = eotf.applyAsDouble(blue(color));
|
||||
|
||||
return saturate((float) ((0.2126 * r) + (0.7152 * g) + (0.0722 * b)));
|
||||
}
|
||||
|
||||
private static float saturate(float v) {
|
||||
return v <= 0.0f ? 0.0f : (v >= 1.0f ? 1.0f : v);
|
||||
}
|
||||
|
||||
@IntRange(from = 0, to = 255)
|
||||
public static int alpha(int color) {
|
||||
return color >>> 24;
|
||||
}
|
||||
|
||||
@IntRange(from = 0, to = 255)
|
||||
public static int red(int color) {
|
||||
return (color >> 16) & 0xFF;
|
||||
}
|
||||
|
||||
@IntRange(from = 0, to = 255)
|
||||
public static int green(int color) {
|
||||
return (color >> 8) & 0xFF;
|
||||
}
|
||||
|
||||
@IntRange(from = 0, to = 255)
|
||||
public static int blue(int color) {
|
||||
return color & 0xFF;
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public static int rgb(
|
||||
@IntRange(from = 0, to = 255) int red,
|
||||
@IntRange(from = 0, to = 255) int green,
|
||||
@IntRange(from = 0, to = 255) int blue) {
|
||||
return 0xff000000 | (red << 16) | (green << 8) | blue;
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public static int rgb(float red, float green, float blue) {
|
||||
return 0xff000000 |
|
||||
((int) (red * 255.0f + 0.5f) << 16) |
|
||||
((int) (green * 255.0f + 0.5f) << 8) |
|
||||
(int) (blue * 255.0f + 0.5f);
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public static int argb(
|
||||
@IntRange(from = 0, to = 255) int alpha,
|
||||
@IntRange(from = 0, to = 255) int red,
|
||||
@IntRange(from = 0, to = 255) int green,
|
||||
@IntRange(from = 0, to = 255) int blue) {
|
||||
return (alpha << 24) | (red << 16) | (green << 8) | blue;
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public static int argb(float alpha, float red, float green, float blue) {
|
||||
return ((int) (alpha * 255.0f + 0.5f) << 24) |
|
||||
((int) (red * 255.0f + 0.5f) << 16) |
|
||||
((int) (green * 255.0f + 0.5f) << 8) |
|
||||
(int) (blue * 255.0f + 0.5f);
|
||||
}
|
||||
|
||||
public static float luminance(@ColorInt int color) {
|
||||
ColorSpace.Rgb cs = (ColorSpace.Rgb) ColorSpace.get(ColorSpace.Named.SRGB);
|
||||
DoubleUnaryOperator eotf = cs.getEotf();
|
||||
|
||||
double r = eotf.applyAsDouble(red(color) / 255.0);
|
||||
double g = eotf.applyAsDouble(green(color) / 255.0);
|
||||
double b = eotf.applyAsDouble(blue(color) / 255.0);
|
||||
|
||||
return (float) ((0.2126 * r) + (0.7152 * g) + (0.0722 * b));
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public static int parseColor(@Size(min=1) String colorString) {
|
||||
if (colorString.charAt(0) == '#') {
|
||||
// Use a long to avoid rollovers on #ffXXXXXX
|
||||
long color = Long.parseLong(colorString.substring(1), 16);
|
||||
if (colorString.length() == 7) {
|
||||
// Set the alpha value
|
||||
color |= 0x00000000ff000000;
|
||||
} else if (colorString.length() != 9) {
|
||||
throw new IllegalArgumentException("Unknown color");
|
||||
}
|
||||
return (int)color;
|
||||
} else {
|
||||
Integer color = sColorNameMap.get(colorString.toLowerCase(Locale.ROOT));
|
||||
if (color != null) {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown color");
|
||||
}
|
||||
|
||||
public static void RGBToHSV(
|
||||
@IntRange(from = 0, to = 255) int red,
|
||||
@IntRange(from = 0, to = 255) int green,
|
||||
@IntRange(from = 0, to = 255) int blue, @Size(3) float hsv[]) {
|
||||
if (hsv.length < 3) {
|
||||
throw new RuntimeException("3 components required for hsv");
|
||||
}
|
||||
nativeRGBToHSV(red, green, blue, hsv);
|
||||
}
|
||||
|
||||
public static void colorToHSV(@ColorInt int color, @Size(3) float hsv[]) {
|
||||
RGBToHSV((color >> 16) & 0xFF, (color >> 8) & 0xFF, color & 0xFF, hsv);
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public static int HSVToColor(@Size(3) float hsv[]) {
|
||||
return HSVToColor(0xFF, hsv);
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
public static int HSVToColor(@IntRange(from = 0, to = 255) int alpha, @Size(3) float hsv[]) {
|
||||
if (hsv.length < 3) {
|
||||
throw new RuntimeException("3 components required for hsv");
|
||||
}
|
||||
return nativeHSVToColor(alpha, hsv);
|
||||
}
|
||||
|
||||
private static native void nativeRGBToHSV(int red, int greed, int blue, float hsv[]);
|
||||
private static native int nativeHSVToColor(int alpha, float hsv[]);
|
||||
|
||||
private static final HashMap<String, Integer> sColorNameMap;
|
||||
static {
|
||||
sColorNameMap = new HashMap<>();
|
||||
sColorNameMap.put("black", BLACK);
|
||||
sColorNameMap.put("darkgray", DKGRAY);
|
||||
sColorNameMap.put("gray", GRAY);
|
||||
sColorNameMap.put("lightgray", LTGRAY);
|
||||
sColorNameMap.put("white", WHITE);
|
||||
sColorNameMap.put("red", RED);
|
||||
sColorNameMap.put("green", GREEN);
|
||||
sColorNameMap.put("blue", BLUE);
|
||||
sColorNameMap.put("yellow", YELLOW);
|
||||
sColorNameMap.put("cyan", CYAN);
|
||||
sColorNameMap.put("magenta", MAGENTA);
|
||||
sColorNameMap.put("aqua", 0xFF00FFFF);
|
||||
sColorNameMap.put("fuchsia", 0xFFFF00FF);
|
||||
sColorNameMap.put("darkgrey", DKGRAY);
|
||||
sColorNameMap.put("grey", GRAY);
|
||||
sColorNameMap.put("lightgrey", LTGRAY);
|
||||
sColorNameMap.put("lime", 0xFF00FF00);
|
||||
sColorNameMap.put("maroon", 0xFF800000);
|
||||
sColorNameMap.put("navy", 0xFF000080);
|
||||
sColorNameMap.put("olive", 0xFF808000);
|
||||
sColorNameMap.put("purple", 0xFF800080);
|
||||
sColorNameMap.put("silver", 0xFFC0C0C0);
|
||||
sColorNameMap.put("teal", 0xFF008080);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,15 @@
|
||||
package android.graphics;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import android.os.Parcel;
|
||||
|
||||
public final class Rect {
|
||||
int left;
|
||||
int top;
|
||||
int right;
|
||||
int bottom;
|
||||
public int left;
|
||||
public int top;
|
||||
public int right;
|
||||
public int bottom;
|
||||
|
||||
private static final class UnflattenHelper {
|
||||
private static final Pattern FLATTENED_PATTERN = Pattern.compile(
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (C) 2007 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 android.graphics;
|
||||
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public class TemporaryBuffer {
|
||||
public static char[] obtain(int len) {
|
||||
char[] buf;
|
||||
|
||||
synchronized (TemporaryBuffer.class) {
|
||||
buf = sTemp;
|
||||
sTemp = null;
|
||||
}
|
||||
|
||||
if (buf == null || buf.length < len) {
|
||||
buf = ArrayUtils.newUnpaddedCharArray(len);
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
public static void recycle(char[] temp) {
|
||||
if (temp.length > 1000) return;
|
||||
|
||||
synchronized (TemporaryBuffer.class) {
|
||||
sTemp = temp;
|
||||
}
|
||||
}
|
||||
|
||||
private static char[] sTemp = null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,327 @@
|
||||
/*
|
||||
* Copyright (C) 2006 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 android.graphics;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.TestApi;
|
||||
import android.content.res.AssetManager;
|
||||
import android.util.Log;
|
||||
import com.android.internal.util.Preconditions;
|
||||
import java.awt.Font;
|
||||
import java.awt.FontFormatException;
|
||||
import java.awt.font.TextAttribute;
|
||||
import java.io.File;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import android.annotation.NonNull;
|
||||
|
||||
|
||||
public class Typeface {
|
||||
|
||||
private static String TAG = "Typeface";
|
||||
|
||||
/** @hide */
|
||||
public static final boolean ENABLE_LAZY_TYPEFACE_INITIALIZATION = true;
|
||||
|
||||
/** The default NORMAL typeface object */
|
||||
public static final Typeface DEFAULT;
|
||||
public static final Typeface DEFAULT_BOLD;
|
||||
/** The NORMAL style of the default sans serif typeface. */
|
||||
public static final Typeface SANS_SERIF;
|
||||
/** The NORMAL style of the default serif typeface. */
|
||||
public static final Typeface SERIF;
|
||||
/** The NORMAL style of the default monospace typeface. */
|
||||
public static final Typeface MONOSPACE;
|
||||
|
||||
public @interface Style {}
|
||||
|
||||
// Style
|
||||
public static final int NORMAL = 0;
|
||||
public static final int BOLD = 1;
|
||||
public static final int ITALIC = 2;
|
||||
public static final int BOLD_ITALIC = 3;
|
||||
/** @hide */ public static final int STYLE_MASK = 0x03;
|
||||
|
||||
public static final String DEFAULT_FAMILY = "sans-serif";
|
||||
|
||||
private final Font mFont;
|
||||
|
||||
/** Returns the typeface's weight value */
|
||||
public int getWeight() {
|
||||
Map<TextAttribute, Object> atts = (Map<TextAttribute, Object>) mFont.getAttributes();
|
||||
Object weight = atts.getOrDefault(TextAttribute.WEIGHT, TextAttribute.WEIGHT_REGULAR);
|
||||
if (weight instanceof Float) {
|
||||
float w = ((Float) weight).floatValue();
|
||||
// undo the transformation
|
||||
return (int) ((w - TextAttribute.WEIGHT_REGULAR) / (TextAttribute.WEIGHT_BOLD - TextAttribute.WEIGHT_REGULAR) * (Builder.BOLD_WEIGHT - Builder.NORMAL_WEIGHT) + Builder.NORMAL_WEIGHT);
|
||||
}
|
||||
return Builder.NORMAL_WEIGHT;
|
||||
}
|
||||
|
||||
public float getJavaWeight() {
|
||||
Map<TextAttribute, Object> atts = (Map<TextAttribute, Object>) mFont.getAttributes();
|
||||
Object weight = atts.getOrDefault(TextAttribute.WEIGHT, TextAttribute.WEIGHT_REGULAR);
|
||||
if (weight instanceof Float) {
|
||||
return ((Float) weight).floatValue();
|
||||
}
|
||||
return TextAttribute.WEIGHT_REGULAR;
|
||||
}
|
||||
|
||||
/** Returns the typeface's intrinsic style attributes */
|
||||
public @Style int getStyle() {
|
||||
if (isBold() && isItalic()) return BOLD_ITALIC;
|
||||
if (isBold()) return BOLD;
|
||||
if (isItalic()) return ITALIC;
|
||||
return NORMAL;
|
||||
}
|
||||
|
||||
/** Returns true if getStyle() has the BOLD bit set. */
|
||||
public final boolean isBold() {
|
||||
return mFont.isBold();
|
||||
}
|
||||
|
||||
/** Returns true if getStyle() has the ITALIC bit set. */
|
||||
public final boolean isItalic() {
|
||||
return mFont.isItalic();
|
||||
}
|
||||
|
||||
public final @Nullable String getSystemFontFamilyName() {
|
||||
return mFont.getFamily();
|
||||
}
|
||||
|
||||
public static Typeface findFromCache(AssetManager mgr, String path) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
/** @hide */
|
||||
public static final int NORMAL_WEIGHT = 400;
|
||||
/** @hide */
|
||||
public static final int BOLD_WEIGHT = 700;
|
||||
|
||||
private final String mPath;
|
||||
|
||||
private Font mFont;
|
||||
private int mStyle;
|
||||
private Map<TextAttribute, Object> mAttributes;
|
||||
|
||||
private String mFallbackFamilyName;
|
||||
|
||||
public Builder(@NonNull File path) {
|
||||
mFont = loadFont(path);
|
||||
Log.v(TAG, "Font loaded from " + path.toURI());
|
||||
mPath = null;
|
||||
mStyle = 0;
|
||||
mAttributes = new HashMap<TextAttribute, Object>();
|
||||
}
|
||||
|
||||
public Builder(@NonNull FileDescriptor fd) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public Builder(@NonNull String path) {
|
||||
mFont = loadFont(new File(path));
|
||||
mPath = path;
|
||||
mStyle = 0;
|
||||
mAttributes = new HashMap<TextAttribute, Object>();
|
||||
}
|
||||
|
||||
public Builder(@NonNull AssetManager assetManager, @NonNull String path) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public Builder(@NonNull AssetManager assetManager, @NonNull String path, boolean isAsset,
|
||||
int cookie) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public Builder setWeight(int weight) {
|
||||
// java font weight does not follow typical weight distribution
|
||||
// In Java, regular weight is at 1.0 and bold at 2.0, compared to 400 and 700 in TTF
|
||||
// Typical range is 0 to 1000
|
||||
|
||||
float jWeight = (weight - NORMAL_WEIGHT) / (BOLD_WEIGHT - NORMAL_WEIGHT) * (TextAttribute.WEIGHT_BOLD - TextAttribute.WEIGHT_REGULAR) + TextAttribute.WEIGHT_REGULAR;
|
||||
mAttributes.put(TextAttribute.WEIGHT, jWeight);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setItalic(boolean italic) {
|
||||
if (italic) {
|
||||
mStyle |= Font.ITALIC;
|
||||
} else {
|
||||
mStyle &= ~Font.ITALIC;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setTtcIndex(int ttcIndex) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public Builder setFontVariationSettings(@Nullable String variationSettings) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public Builder setFallback(@Nullable String familyName) {
|
||||
mFallbackFamilyName = familyName;
|
||||
return this;
|
||||
}
|
||||
|
||||
private Typeface resolveFallbackTypeface() {
|
||||
if (mFallbackFamilyName == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Typeface(new Font(mFallbackFamilyName, mStyle, 12).deriveFont(mAttributes));
|
||||
}
|
||||
|
||||
public Typeface build() {
|
||||
if (mFont == null) return resolveFallbackTypeface();
|
||||
return new Typeface(mFont.deriveFont(mStyle).deriveFont(mAttributes));
|
||||
}
|
||||
|
||||
private Font loadFont(File fontFile) {
|
||||
try {
|
||||
return Font.createFont(Font.TRUETYPE_FONT, fontFile);
|
||||
} catch (FontFormatException ex) {
|
||||
Log.v(TAG, "Failed to create font as TTF", ex);
|
||||
} catch (IOException ex) {
|
||||
Log.v(TAG, "Failed to create font as TTF", ex);
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
try {
|
||||
return Font.createFont(Font.TYPE1_FONT, fontFile);
|
||||
} catch (FontFormatException ex) {
|
||||
Log.v(TAG, "Failed to create font as T1", ex);
|
||||
} catch (IOException ex) {
|
||||
Log.v(TAG, "Failed to create font as T1", ex);
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Typeface create(String familyName, @Style int style) {
|
||||
return create(getSystemDefaultTypeface(familyName), style);
|
||||
}
|
||||
|
||||
public static Typeface create(Typeface family, @Style int style) {
|
||||
if ((style & ~STYLE_MASK) != 0) {
|
||||
style = NORMAL;
|
||||
}
|
||||
if (family == null) {
|
||||
family = getSystemDefaultTypeface(DEFAULT_FAMILY);
|
||||
}
|
||||
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public static @NonNull Typeface create(@Nullable Typeface family,
|
||||
int weight, boolean italic) {
|
||||
Preconditions.checkArgumentInRange(weight, 0, 1000, "weight");
|
||||
if (family == null) {
|
||||
family = getSystemDefaultTypeface(DEFAULT_FAMILY);
|
||||
}
|
||||
return createWeightStyle(family, weight, italic);
|
||||
}
|
||||
|
||||
private static @NonNull Typeface createWeightStyle(@NonNull Typeface base,
|
||||
int weight, boolean italic) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public static Typeface defaultFromStyle(@Style int style) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public static Typeface createFromAsset(AssetManager mgr, String path) {
|
||||
Preconditions.checkNotNull(path); // for backward compatibility
|
||||
Preconditions.checkNotNull(mgr);
|
||||
|
||||
Typeface typeface = new Builder(mgr, path).build();
|
||||
if (typeface != null) return typeface;
|
||||
// check if the file exists, and throw an exception for backward compatibility
|
||||
try (InputStream inputStream = mgr.open(path)) {
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Font asset not found " + path);
|
||||
}
|
||||
|
||||
return Typeface.DEFAULT;
|
||||
}
|
||||
|
||||
public Typeface(Font fnt) {
|
||||
mFont = fnt;
|
||||
}
|
||||
|
||||
public static Typeface createFromFile(@Nullable File file) {
|
||||
// For the compatibility reasons, leaving possible NPE here.
|
||||
// See android.graphics.cts.TypefaceTest#testCreateFromFileByFileReferenceNull
|
||||
|
||||
Typeface typeface = new Builder(file).build();
|
||||
if (typeface != null) return typeface;
|
||||
|
||||
// check if the file exists, and throw an exception for backward compatibility
|
||||
if (!file.exists()) {
|
||||
throw new RuntimeException("Font asset not found " + file.getAbsolutePath());
|
||||
}
|
||||
|
||||
return Typeface.DEFAULT;
|
||||
}
|
||||
|
||||
public static Typeface createFromFile(@Nullable String path) {
|
||||
Preconditions.checkNotNull(path); // for backward compatibility
|
||||
return createFromFile(new File(path));
|
||||
}
|
||||
|
||||
private static Typeface getSystemDefaultTypeface(@NonNull String familyName) {
|
||||
return new Typeface(new Font(familyName, 0, 12));
|
||||
}
|
||||
|
||||
public Font getFont() {
|
||||
return mFont;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Typeface typeface = (Typeface) o;
|
||||
|
||||
return typeface.mFont.equals(mFont);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mFont.hashCode();
|
||||
}
|
||||
|
||||
static {
|
||||
DEFAULT = new Typeface(new Font(null, 0, 12));
|
||||
DEFAULT_BOLD = new Typeface(new Font(null, Font.BOLD, 12));
|
||||
SANS_SERIF = new Typeface(new Font(Font.SANS_SERIF, 0, 12));
|
||||
SERIF = new Typeface(new Font(Font.SERIF, 0, 12));
|
||||
MONOSPACE = new Typeface(new Font(Font.MONOSPACED, 0, 12));
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
||||
/* //device/java/android/android/app/IActivityPendingResult.aidl
|
||||
**
|
||||
** Copyright 2007, 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 android.os;
|
||||
|
||||
import android.os.Message;
|
||||
|
||||
/** @hide */
|
||||
/* oneway */ interface IMessenger {
|
||||
void send(/* in */ Message msg);
|
||||
}
|
||||
@@ -0,0 +1,575 @@
|
||||
/*
|
||||
* Copyright (C) 2006 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 android.os;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
import android.util.Printer;
|
||||
import android.util.Slog;
|
||||
import android.util.proto.ProtoOutputStream;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Class used to run a message loop for a thread. Threads by default do
|
||||
* not have a message loop associated with them; to create one, call
|
||||
* {@link #prepare} in the thread that is to run the loop, and then
|
||||
* {@link #loop} to have it process messages until the loop is stopped.
|
||||
*
|
||||
* <p>Most interaction with a message loop is through the
|
||||
* {@link Handler} class.
|
||||
*
|
||||
* <p>This is a typical example of the implementation of a Looper thread,
|
||||
* using the separation of {@link #prepare} and {@link #loop} to create an
|
||||
* initial Handler to communicate with the Looper.
|
||||
*
|
||||
* <pre>
|
||||
* class LooperThread extends Thread {
|
||||
* public Handler mHandler;
|
||||
*
|
||||
* public void run() {
|
||||
* Looper.prepare();
|
||||
*
|
||||
* mHandler = new Handler(Looper.myLooper()) {
|
||||
* public void handleMessage(Message msg) {
|
||||
* // process incoming messages here
|
||||
* }
|
||||
* };
|
||||
*
|
||||
* Looper.loop();
|
||||
* }
|
||||
* }</pre>
|
||||
*/
|
||||
public final class Looper {
|
||||
/*
|
||||
* API Implementation Note:
|
||||
*
|
||||
* This class contains the code required to set up and manage an event loop
|
||||
* based on MessageQueue. APIs that affect the state of the queue should be
|
||||
* defined on MessageQueue or Handler rather than on Looper itself. For example,
|
||||
* idle handlers and sync barriers are defined on the queue whereas preparing the
|
||||
* thread, looping, and quitting are defined on the looper.
|
||||
*/
|
||||
|
||||
private static final String TAG = "Looper";
|
||||
|
||||
private static class NoImagePreloadHolder {
|
||||
// Enable/Disable verbose logging with a system prop. e.g.
|
||||
// adb shell 'setprop log.looper.slow.verbose false && stop && start'
|
||||
private static final boolean sVerboseLogging =
|
||||
SystemProperties.getBoolean("log.looper.slow.verbose", false);
|
||||
}
|
||||
|
||||
// sThreadLocal.get() will return null unless you've called prepare().
|
||||
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
|
||||
private static Looper sMainLooper; // guarded by Looper.class
|
||||
private static Observer sObserver;
|
||||
|
||||
final MessageQueue mQueue;
|
||||
final Thread mThread;
|
||||
private boolean mInLoop;
|
||||
|
||||
private Printer mLogging;
|
||||
private long mTraceTag;
|
||||
|
||||
/**
|
||||
* If set, the looper will show a warning log if a message dispatch takes longer than this.
|
||||
*/
|
||||
private long mSlowDispatchThresholdMs;
|
||||
|
||||
/**
|
||||
* If set, the looper will show a warning log if a message delivery (actual delivery time -
|
||||
* post time) takes longer than this.
|
||||
*/
|
||||
private long mSlowDeliveryThresholdMs;
|
||||
|
||||
/**
|
||||
* True if a message delivery takes longer than {@link #mSlowDeliveryThresholdMs}.
|
||||
*/
|
||||
private boolean mSlowDeliveryDetected;
|
||||
|
||||
/** Initialize the current thread as a looper.
|
||||
* This gives you a chance to create handlers that then reference
|
||||
* this looper, before actually starting the loop. Be sure to call
|
||||
* {@link #loop()} after calling this method, and end it by calling
|
||||
* {@link #quit()}.
|
||||
*/
|
||||
public static void prepare() {
|
||||
prepare(true);
|
||||
}
|
||||
|
||||
private static void prepare(boolean quitAllowed) {
|
||||
if (sThreadLocal.get() != null) {
|
||||
throw new RuntimeException("Only one Looper may be created per thread");
|
||||
}
|
||||
sThreadLocal.set(new Looper(quitAllowed));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the current thread as a looper, marking it as an
|
||||
* application's main looper. See also: {@link #prepare()}
|
||||
*
|
||||
* @deprecated The main looper for your application is created by the Android environment,
|
||||
* so you should never need to call this function yourself.
|
||||
*/
|
||||
@Deprecated
|
||||
public static void prepareMainLooper() {
|
||||
prepare(false);
|
||||
synchronized (Looper.class) {
|
||||
if (sMainLooper != null) {
|
||||
throw new IllegalStateException("The main Looper has already been prepared.");
|
||||
}
|
||||
sMainLooper = myLooper();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the application's main looper, which lives in the main thread of the application.
|
||||
*/
|
||||
public static Looper getMainLooper() {
|
||||
synchronized (Looper.class) {
|
||||
return sMainLooper;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Force the application's main looper to the given value. The main looper is typically
|
||||
* configured automatically by the OS, so this capability is only intended to enable testing.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static void setMainLooperForTest(@NonNull Looper looper) {
|
||||
synchronized (Looper.class) {
|
||||
sMainLooper = Objects.requireNonNull(looper);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the application's main looper to be undefined. The main looper is typically
|
||||
* configured automatically by the OS, so this capability is only intended to enable testing.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static void clearMainLooperForTest() {
|
||||
synchronized (Looper.class) {
|
||||
sMainLooper = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the transaction observer for all Loopers in this process.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static void setObserver(@Nullable Observer observer) {
|
||||
sObserver = observer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Poll and deliver single message, return true if the outer loop should continue.
|
||||
*/
|
||||
@SuppressWarnings({"UnusedTokenOfOriginalCallingIdentity",
|
||||
"ClearIdentityCallNotFollowedByTryFinally"})
|
||||
private static boolean loopOnce(final Looper me,
|
||||
final long ident, final int thresholdOverride) {
|
||||
Message msg = me.mQueue.next(); // might block
|
||||
if (msg == null) {
|
||||
// No message indicates that the message queue is quitting.
|
||||
return false;
|
||||
}
|
||||
|
||||
// This must be in a local variable, in case a UI event sets the logger
|
||||
final Printer logging = me.mLogging;
|
||||
if (logging != null) {
|
||||
logging.println(">>>>> Dispatching to " + msg.target + " "
|
||||
+ msg.callback + ": " + msg.what);
|
||||
}
|
||||
// Make sure the observer won't change while processing a transaction.
|
||||
final Observer observer = sObserver;
|
||||
|
||||
final long traceTag = me.mTraceTag;
|
||||
long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
|
||||
long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
|
||||
|
||||
final boolean hasOverride = thresholdOverride >= 0;
|
||||
if (hasOverride) {
|
||||
slowDispatchThresholdMs = thresholdOverride;
|
||||
slowDeliveryThresholdMs = thresholdOverride;
|
||||
}
|
||||
final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0 || hasOverride)
|
||||
&& (msg.when > 0);
|
||||
final boolean logSlowDispatch = (slowDispatchThresholdMs > 0 || hasOverride);
|
||||
|
||||
final boolean needStartTime = logSlowDelivery || logSlowDispatch;
|
||||
final boolean needEndTime = logSlowDispatch;
|
||||
|
||||
final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
|
||||
final long dispatchEnd;
|
||||
Object token = null;
|
||||
if (observer != null) {
|
||||
token = observer.messageDispatchStarting();
|
||||
}
|
||||
long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
|
||||
try {
|
||||
msg.target.dispatchMessage(msg);
|
||||
if (observer != null) {
|
||||
observer.messageDispatched(token, msg);
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
if (observer != null) {
|
||||
observer.dispatchingThrewException(token, msg, exception);
|
||||
}
|
||||
Log.e(TAG, "Loop handler threw", exception);
|
||||
// throw exception;
|
||||
} finally {
|
||||
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
|
||||
ThreadLocalWorkSource.restore(origWorkSource);
|
||||
}
|
||||
if (logSlowDelivery) {
|
||||
boolean slow = false;
|
||||
|
||||
if (!me.mSlowDeliveryDetected || NoImagePreloadHolder.sVerboseLogging) {
|
||||
slow = showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart,
|
||||
"delivery", msg);
|
||||
}
|
||||
if (me.mSlowDeliveryDetected) {
|
||||
if (!slow && (dispatchStart - msg.when) <= 10) {
|
||||
Slog.w(TAG, "Drained");
|
||||
me.mSlowDeliveryDetected = false;
|
||||
}
|
||||
} else if (slow) {
|
||||
// A slow delivery is detected, suppressing further logs unless verbose logging
|
||||
// is enabled.
|
||||
me.mSlowDeliveryDetected = true;
|
||||
}
|
||||
}
|
||||
if (logSlowDispatch) {
|
||||
showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
|
||||
}
|
||||
|
||||
if (logging != null) {
|
||||
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
|
||||
}
|
||||
|
||||
// Make sure that during the course of dispatching the
|
||||
// identity of the thread wasn't corrupted.
|
||||
// final long newIdent = Binder.clearCallingIdentity();
|
||||
// if (ident != newIdent) {
|
||||
// Log.wtf(TAG, "Thread identity changed from 0x"
|
||||
// + Long.toHexString(ident) + " to 0x"
|
||||
// + Long.toHexString(newIdent) + " while dispatching to "
|
||||
// + msg.target.getClass().getName() + " "
|
||||
// + msg.callback + " what=" + msg.what);
|
||||
// }
|
||||
|
||||
msg.recycleUnchecked();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the message queue in this thread. Be sure to call
|
||||
* {@link #quit()} to end the loop.
|
||||
*/
|
||||
@SuppressWarnings({"UnusedTokenOfOriginalCallingIdentity",
|
||||
"ClearIdentityCallNotFollowedByTryFinally",
|
||||
"ResultOfClearIdentityCallNotStoredInVariable"})
|
||||
public static void loop() {
|
||||
final Looper me = myLooper();
|
||||
if (me == null) {
|
||||
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
|
||||
}
|
||||
if (me.mInLoop) {
|
||||
Slog.w(TAG, "Loop again would have the queued messages be executed"
|
||||
+ " before this one completed.");
|
||||
}
|
||||
|
||||
me.mInLoop = true;
|
||||
|
||||
// Make sure the identity of this thread is that of the local process,
|
||||
// and keep track of what that identity token actually is.
|
||||
// Binder.clearCallingIdentity();
|
||||
// final long ident = Binder.clearCallingIdentity();
|
||||
final long ident = 0;
|
||||
|
||||
// Allow overriding a threshold with a system prop. e.g.
|
||||
// adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
|
||||
final int thresholdOverride = getThresholdOverride();
|
||||
|
||||
me.mSlowDeliveryDetected = false;
|
||||
|
||||
for (;;) {
|
||||
if (!loopOnce(me, ident, thresholdOverride)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int getThresholdOverride() {
|
||||
return -1;
|
||||
// // Allow overriding the threshold for all processes' main looper with a system prop.
|
||||
// // e.g. adb shell 'setprop log.looper.any.main.slow 1 && stop && start'
|
||||
// if (myLooper() == getMainLooper()) {
|
||||
// final int globalOverride = SystemProperties.getInt("log.looper.any.main.slow", -1);
|
||||
// if (globalOverride >= 0) {
|
||||
// return globalOverride;
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Allow overriding the threshold for all threads within a process with a system prop.
|
||||
// // e.g. adb shell 'setprop log.looper.1000.any.slow 1 && stop && start'
|
||||
// final int processOverride = SystemProperties.getInt("log.looper."
|
||||
// + Process.myUid() + ".any.slow", -1);
|
||||
// if (processOverride >= 0) {
|
||||
// return processOverride;
|
||||
// }
|
||||
|
||||
// return SystemProperties.getInt("log.looper."
|
||||
// + Process.myUid() + "."
|
||||
// + Thread.currentThread().getName()
|
||||
// + ".slow", -1);
|
||||
}
|
||||
|
||||
private static int getThresholdOverride$ravenwood() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static int getThreadGroup() {
|
||||
int threadGroup = Process.THREAD_GROUP_DEFAULT;
|
||||
|
||||
if (!Process.isIsolated()) {
|
||||
threadGroup = Process.getProcessGroup(Process.myTid());
|
||||
}
|
||||
return threadGroup;
|
||||
}
|
||||
|
||||
private static String threadGroupToString(int threadGroup) {
|
||||
switch (threadGroup) {
|
||||
case Process.THREAD_GROUP_SYSTEM:
|
||||
return "SYSTEM";
|
||||
case Process.THREAD_GROUP_AUDIO_APP:
|
||||
return "AUDIO_APP";
|
||||
case Process.THREAD_GROUP_AUDIO_SYS:
|
||||
return "AUDIO_SYS";
|
||||
case Process.THREAD_GROUP_TOP_APP:
|
||||
return "TOP_APP";
|
||||
case Process.THREAD_GROUP_RT_APP:
|
||||
return "RT_APP";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean showSlowLog(long threshold, long measureStart, long measureEnd,
|
||||
String what, Message msg) {
|
||||
final long actualTime = measureEnd - measureStart;
|
||||
if (actualTime < threshold) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String name = /* Process.myProcessName() */ "Stub!";
|
||||
String threadGroup = threadGroupToString(getThreadGroup());
|
||||
boolean isMain = myLooper() == getMainLooper();
|
||||
|
||||
// For slow delivery, the current message isn't really important, but log it anyway.
|
||||
Slog.w(TAG, "Slow " + what + " took " + actualTime + "ms "
|
||||
+ Thread.currentThread().getName() + " app=" + name
|
||||
+ " main=" + isMain + " group=" + threadGroup
|
||||
+ " h=" + msg.target.getClass().getName() + " c=" + msg.callback
|
||||
+ " m=" + msg.what);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Looper object associated with the current thread. Returns
|
||||
* null if the calling thread is not associated with a Looper.
|
||||
*/
|
||||
public static @Nullable Looper myLooper() {
|
||||
return sThreadLocal.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link MessageQueue} object associated with the current
|
||||
* thread. This must be called from a thread running a Looper, or a
|
||||
* NullPointerException will be thrown.
|
||||
*/
|
||||
public static @NonNull MessageQueue myQueue() {
|
||||
return myLooper().mQueue;
|
||||
}
|
||||
|
||||
private Looper(boolean quitAllowed) {
|
||||
mQueue = new MessageQueue(quitAllowed);
|
||||
mThread = Thread.currentThread();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the current thread is this looper's thread.
|
||||
*/
|
||||
public boolean isCurrentThread() {
|
||||
return Thread.currentThread() == mThread;
|
||||
}
|
||||
|
||||
/**
|
||||
* Control logging of messages as they are processed by this Looper. If
|
||||
* enabled, a log message will be written to <var>printer</var>
|
||||
* at the beginning and ending of each message dispatch, identifying the
|
||||
* target Handler and message contents.
|
||||
*
|
||||
* @param printer A Printer object that will receive log messages, or
|
||||
* null to disable message logging.
|
||||
*/
|
||||
public void setMessageLogging(@Nullable Printer printer) {
|
||||
mLogging = printer;
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
public void setTraceTag(long traceTag) {
|
||||
mTraceTag = traceTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a thresholds for slow dispatch/delivery log.
|
||||
* {@hide}
|
||||
*/
|
||||
public void setSlowLogThresholdMs(long slowDispatchThresholdMs, long slowDeliveryThresholdMs) {
|
||||
mSlowDispatchThresholdMs = slowDispatchThresholdMs;
|
||||
mSlowDeliveryThresholdMs = slowDeliveryThresholdMs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Quits the looper.
|
||||
* <p>
|
||||
* Causes the {@link #loop} method to terminate without processing any
|
||||
* more messages in the message queue.
|
||||
* </p><p>
|
||||
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
|
||||
* For example, the {@link Handler#sendMessage(Message)} method will return false.
|
||||
* </p><p class="note">
|
||||
* Using this method may be unsafe because some messages may not be delivered
|
||||
* before the looper terminates. Consider using {@link #quitSafely} instead to ensure
|
||||
* that all pending work is completed in an orderly manner.
|
||||
* </p>
|
||||
*
|
||||
* @see #quitSafely
|
||||
*/
|
||||
public void quit() {
|
||||
mQueue.quit(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Quits the looper safely.
|
||||
* <p>
|
||||
* Causes the {@link #loop} method to terminate as soon as all remaining messages
|
||||
* in the message queue that are already due to be delivered have been handled.
|
||||
* However pending delayed messages with due times in the future will not be
|
||||
* delivered before the loop terminates.
|
||||
* </p><p>
|
||||
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
|
||||
* For example, the {@link Handler#sendMessage(Message)} method will return false.
|
||||
* </p>
|
||||
*/
|
||||
public void quitSafely() {
|
||||
mQueue.quit(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Thread associated with this Looper.
|
||||
*
|
||||
* @return The looper's thread.
|
||||
*/
|
||||
public @NonNull Thread getThread() {
|
||||
return mThread;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets this looper's message queue.
|
||||
*
|
||||
* @return The looper's message queue.
|
||||
*/
|
||||
public @NonNull MessageQueue getQueue() {
|
||||
return mQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps the state of the looper for debugging purposes.
|
||||
*
|
||||
* @param pw A printer to receive the contents of the dump.
|
||||
* @param prefix A prefix to prepend to each line which is printed.
|
||||
*/
|
||||
public void dump(@NonNull Printer pw, @NonNull String prefix) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps the state of the looper for debugging purposes.
|
||||
*
|
||||
* @param pw A printer to receive the contents of the dump.
|
||||
* @param prefix A prefix to prepend to each line which is printed.
|
||||
* @param handler Only dump messages for this Handler.
|
||||
* @hide
|
||||
*/
|
||||
public void dump(@NonNull Printer pw, @NonNull String prefix, Handler handler) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Looper (" + mThread.getName() + ", tid " + mThread.getId()
|
||||
+ ") {" + Integer.toHexString(System.identityHashCode(this)) + "}";
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
public interface Observer {
|
||||
/**
|
||||
* Called right before a message is dispatched.
|
||||
*
|
||||
* <p> The token type is not specified to allow the implementation to specify its own type.
|
||||
*
|
||||
* @return a token used for collecting telemetry when dispatching a single message.
|
||||
* The token token must be passed back exactly once to either
|
||||
* {@link Observer#messageDispatched} or {@link Observer#dispatchingThrewException}
|
||||
* and must not be reused again.
|
||||
*
|
||||
*/
|
||||
Object messageDispatchStarting();
|
||||
|
||||
/**
|
||||
* Called when a message was processed by a Handler.
|
||||
*
|
||||
* @param token Token obtained by previously calling
|
||||
* {@link Observer#messageDispatchStarting} on the same Observer instance.
|
||||
* @param msg The message that was dispatched.
|
||||
*/
|
||||
void messageDispatched(Object token, Message msg);
|
||||
|
||||
/**
|
||||
* Called when an exception was thrown while processing a message.
|
||||
*
|
||||
* @param token Token obtained by previously calling
|
||||
* {@link Observer#messageDispatchStarting} on the same Observer instance.
|
||||
* @param msg The message that was dispatched and caused an exception.
|
||||
* @param exception The exception that was thrown.
|
||||
*/
|
||||
void dispatchingThrewException(Object token, Message msg, Exception exception);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,630 @@
|
||||
/*
|
||||
* Copyright (C) 2006 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 android.os;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.util.TimeUtils;
|
||||
import android.util.proto.ProtoOutputStream;
|
||||
|
||||
/**
|
||||
*
|
||||
* Defines a message containing a description and arbitrary data object that can be
|
||||
* sent to a {@link Handler}. This object contains two extra int fields and an
|
||||
* extra object field that allow you to not do allocations in many cases.
|
||||
*
|
||||
* <p class="note">While the constructor of Message is public, the best way to get
|
||||
* one of these is to call {@link #obtain Message.obtain()} or one of the
|
||||
* {@link Handler#obtainMessage Handler.obtainMessage()} methods, which will pull
|
||||
* them from a pool of recycled objects.</p>
|
||||
*/
|
||||
public final class Message implements Parcelable {
|
||||
/**
|
||||
* User-defined message code so that the recipient can identify
|
||||
* what this message is about. Each {@link Handler} has its own name-space
|
||||
* for message codes, so you do not need to worry about yours conflicting
|
||||
* with other handlers.
|
||||
*
|
||||
* If not specified, this value is 0.
|
||||
* Use values other than 0 to indicate custom message codes.
|
||||
*/
|
||||
public int what;
|
||||
|
||||
/**
|
||||
* arg1 and arg2 are lower-cost alternatives to using
|
||||
* {@link #setData(Bundle) setData()} if you only need to store a
|
||||
* few integer values.
|
||||
*/
|
||||
public int arg1;
|
||||
|
||||
/**
|
||||
* arg1 and arg2 are lower-cost alternatives to using
|
||||
* {@link #setData(Bundle) setData()} if you only need to store a
|
||||
* few integer values.
|
||||
*/
|
||||
public int arg2;
|
||||
|
||||
/**
|
||||
* An arbitrary object to send to the recipient. When using
|
||||
* {@link Messenger} to send the message across processes this can only
|
||||
* be non-null if it contains a Parcelable of a framework class (not one
|
||||
* implemented by the application). For other data transfer use
|
||||
* {@link #setData}.
|
||||
*
|
||||
* <p>Note that Parcelable objects here are not supported prior to
|
||||
* the {@link android.os.Build.VERSION_CODES#FROYO} release.
|
||||
*/
|
||||
public Object obj;
|
||||
|
||||
/**
|
||||
* Optional Messenger where replies to this message can be sent. The
|
||||
* semantics of exactly how this is used are up to the sender and
|
||||
* receiver.
|
||||
*/
|
||||
public Messenger replyTo;
|
||||
|
||||
/**
|
||||
* Indicates that the uid is not set;
|
||||
*
|
||||
* @hide Only for use within the system server.
|
||||
*/
|
||||
public static final int UID_NONE = -1;
|
||||
|
||||
/**
|
||||
* Optional field indicating the uid that sent the message. This is
|
||||
* only valid for messages posted by a {@link Messenger}; otherwise,
|
||||
* it will be -1.
|
||||
*/
|
||||
public int sendingUid = UID_NONE;
|
||||
|
||||
/**
|
||||
* Optional field indicating the uid that caused this message to be enqueued.
|
||||
*
|
||||
* @hide Only for use within the system server.
|
||||
*/
|
||||
public int workSourceUid = UID_NONE;
|
||||
|
||||
/** If set message is in use.
|
||||
* This flag is set when the message is enqueued and remains set while it
|
||||
* is delivered and afterwards when it is recycled. The flag is only cleared
|
||||
* when a new message is created or obtained since that is the only time that
|
||||
* applications are allowed to modify the contents of the message.
|
||||
*
|
||||
* It is an error to attempt to enqueue or recycle a message that is already in use.
|
||||
*/
|
||||
/*package*/ static final int FLAG_IN_USE = 1 << 0;
|
||||
|
||||
/** If set message is asynchronous */
|
||||
/*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1;
|
||||
|
||||
/** Flags to clear in the copyFrom method */
|
||||
/*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE;
|
||||
|
||||
/*package*/ int flags;
|
||||
|
||||
/**
|
||||
* The targeted delivery time of this message. The time-base is
|
||||
* {@link SystemClock#uptimeMillis}.
|
||||
* @hide Only for use within the tests.
|
||||
*/
|
||||
public long when;
|
||||
|
||||
/** @hide */
|
||||
@SuppressWarnings("unused")
|
||||
public long mInsertSeq;
|
||||
|
||||
/*package*/ Bundle data;
|
||||
|
||||
/*package*/ Handler target;
|
||||
|
||||
/*package*/ Runnable callback;
|
||||
|
||||
// sometimes we store linked lists of these things
|
||||
/*package*/ Message next;
|
||||
|
||||
|
||||
/** @hide */
|
||||
public static final Object sPoolSync = new Object();
|
||||
private static Message sPool;
|
||||
private static int sPoolSize = 0;
|
||||
|
||||
private static final int MAX_POOL_SIZE = 50;
|
||||
|
||||
private static boolean gCheckRecycle = true;
|
||||
|
||||
/**
|
||||
* Return a new Message instance from the global pool. Allows us to
|
||||
* avoid allocating new objects in many cases.
|
||||
*/
|
||||
public static Message obtain() {
|
||||
synchronized (sPoolSync) {
|
||||
if (sPool != null) {
|
||||
Message m = sPool;
|
||||
sPool = m.next;
|
||||
m.next = null;
|
||||
m.flags = 0; // clear in-use flag
|
||||
sPoolSize--;
|
||||
return m;
|
||||
}
|
||||
}
|
||||
return new Message();
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link #obtain()}, but copies the values of an existing
|
||||
* message (including its target) into the new one.
|
||||
* @param orig Original message to copy.
|
||||
* @return A Message object from the global pool.
|
||||
*/
|
||||
public static Message obtain(Message orig) {
|
||||
Message m = obtain();
|
||||
m.what = orig.what;
|
||||
m.arg1 = orig.arg1;
|
||||
m.arg2 = orig.arg2;
|
||||
m.obj = orig.obj;
|
||||
m.replyTo = orig.replyTo;
|
||||
m.sendingUid = orig.sendingUid;
|
||||
m.workSourceUid = orig.workSourceUid;
|
||||
if (orig.data != null) {
|
||||
m.data = new Bundle(orig.data);
|
||||
}
|
||||
m.target = orig.target;
|
||||
m.callback = orig.callback;
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link #obtain()}, but sets the value for the <em>target</em> member on the Message returned.
|
||||
* @param h Handler to assign to the returned Message object's <em>target</em> member.
|
||||
* @return A Message object from the global pool.
|
||||
*/
|
||||
public static Message obtain(Handler h) {
|
||||
Message m = obtain();
|
||||
m.target = h;
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link #obtain(Handler)}, but assigns a callback Runnable on
|
||||
* the Message that is returned.
|
||||
* @param h Handler to assign to the returned Message object's <em>target</em> member.
|
||||
* @param callback Runnable that will execute when the message is handled.
|
||||
* @return A Message object from the global pool.
|
||||
*/
|
||||
public static Message obtain(Handler h, Runnable callback) {
|
||||
Message m = obtain();
|
||||
m.target = h;
|
||||
m.callback = callback;
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link #obtain()}, but sets the values for both <em>target</em> and
|
||||
* <em>what</em> members on the Message.
|
||||
* @param h Value to assign to the <em>target</em> member.
|
||||
* @param what Value to assign to the <em>what</em> member.
|
||||
* @return A Message object from the global pool.
|
||||
*/
|
||||
public static Message obtain(Handler h, int what) {
|
||||
Message m = obtain();
|
||||
m.target = h;
|
||||
m.what = what;
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link #obtain()}, but sets the values of the <em>target</em>, <em>what</em>, and <em>obj</em>
|
||||
* members.
|
||||
* @param h The <em>target</em> value to set.
|
||||
* @param what The <em>what</em> value to set.
|
||||
* @param obj The <em>object</em> method to set.
|
||||
* @return A Message object from the global pool.
|
||||
*/
|
||||
public static Message obtain(Handler h, int what, Object obj) {
|
||||
Message m = obtain();
|
||||
m.target = h;
|
||||
m.what = what;
|
||||
m.obj = obj;
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link #obtain()}, but sets the values of the <em>target</em>, <em>what</em>,
|
||||
* <em>arg1</em>, and <em>arg2</em> members.
|
||||
*
|
||||
* @param h The <em>target</em> value to set.
|
||||
* @param what The <em>what</em> value to set.
|
||||
* @param arg1 The <em>arg1</em> value to set.
|
||||
* @param arg2 The <em>arg2</em> value to set.
|
||||
* @return A Message object from the global pool.
|
||||
*/
|
||||
public static Message obtain(Handler h, int what, int arg1, int arg2) {
|
||||
Message m = obtain();
|
||||
m.target = h;
|
||||
m.what = what;
|
||||
m.arg1 = arg1;
|
||||
m.arg2 = arg2;
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as {@link #obtain()}, but sets the values of the <em>target</em>, <em>what</em>,
|
||||
* <em>arg1</em>, <em>arg2</em>, and <em>obj</em> members.
|
||||
*
|
||||
* @param h The <em>target</em> value to set.
|
||||
* @param what The <em>what</em> value to set.
|
||||
* @param arg1 The <em>arg1</em> value to set.
|
||||
* @param arg2 The <em>arg2</em> value to set.
|
||||
* @param obj The <em>obj</em> value to set.
|
||||
* @return A Message object from the global pool.
|
||||
*/
|
||||
public static Message obtain(Handler h, int what,
|
||||
int arg1, int arg2, Object obj) {
|
||||
Message m = obtain();
|
||||
m.target = h;
|
||||
m.what = what;
|
||||
m.arg1 = arg1;
|
||||
m.arg2 = arg2;
|
||||
m.obj = obj;
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public static void updateCheckRecycle(int targetSdkVersion) {
|
||||
if (targetSdkVersion < Build.VERSION_CODES.LOLLIPOP) {
|
||||
gCheckRecycle = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Message instance to the global pool.
|
||||
* <p>
|
||||
* You MUST NOT touch the Message after calling this function because it has
|
||||
* effectively been freed. It is an error to recycle a message that is currently
|
||||
* enqueued or that is in the process of being delivered to a Handler.
|
||||
* </p>
|
||||
*/
|
||||
public void recycle() {
|
||||
if (isInUse()) {
|
||||
if (gCheckRecycle) {
|
||||
throw new IllegalStateException("This message cannot be recycled because it "
|
||||
+ "is still in use.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
recycleUnchecked();
|
||||
}
|
||||
|
||||
/**
|
||||
* Recycles a Message that may be in-use.
|
||||
* Used internally by the MessageQueue and Looper when disposing of queued Messages.
|
||||
*/
|
||||
void recycleUnchecked() {
|
||||
// Mark the message as in use while it remains in the recycled object pool.
|
||||
// Clear out all other details.
|
||||
flags = FLAG_IN_USE;
|
||||
what = 0;
|
||||
arg1 = 0;
|
||||
arg2 = 0;
|
||||
obj = null;
|
||||
replyTo = null;
|
||||
sendingUid = UID_NONE;
|
||||
workSourceUid = UID_NONE;
|
||||
when = 0;
|
||||
target = null;
|
||||
callback = null;
|
||||
data = null;
|
||||
|
||||
synchronized (sPoolSync) {
|
||||
if (sPoolSize < MAX_POOL_SIZE) {
|
||||
next = sPool;
|
||||
sPool = this;
|
||||
sPoolSize++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make this message like o. Performs a shallow copy of the data field.
|
||||
* Does not copy the linked list fields, nor the timestamp or
|
||||
* target/callback of the original message.
|
||||
*/
|
||||
public void copyFrom(Message o) {
|
||||
this.flags = o.flags & ~FLAGS_TO_CLEAR_ON_COPY_FROM;
|
||||
this.what = o.what;
|
||||
this.arg1 = o.arg1;
|
||||
this.arg2 = o.arg2;
|
||||
this.obj = o.obj;
|
||||
this.replyTo = o.replyTo;
|
||||
this.sendingUid = o.sendingUid;
|
||||
this.workSourceUid = o.workSourceUid;
|
||||
|
||||
if (o.data != null) {
|
||||
this.data = (Bundle) o.data.clone();
|
||||
} else {
|
||||
this.data = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the targeted delivery time of this message, in milliseconds.
|
||||
*/
|
||||
public long getWhen() {
|
||||
return when;
|
||||
}
|
||||
|
||||
public void setTarget(Handler target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the {@link android.os.Handler Handler} implementation that
|
||||
* will receive this message. The object must implement
|
||||
* {@link android.os.Handler#handleMessage(android.os.Message)
|
||||
* Handler.handleMessage()}. Each Handler has its own name-space for
|
||||
* message codes, so you do not need to
|
||||
* worry about yours conflicting with other handlers.
|
||||
*/
|
||||
public Handler getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve callback object that will execute when this message is handled.
|
||||
* This object must implement Runnable. This is called by
|
||||
* the <em>target</em> {@link Handler} that is receiving this Message to
|
||||
* dispatch it. If
|
||||
* not set, the message will be dispatched to the receiving Handler's
|
||||
* {@link Handler#handleMessage(Message)}.
|
||||
*/
|
||||
public Runnable getCallback() {
|
||||
return callback;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public Message setCallback(Runnable r) {
|
||||
callback = r;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains a Bundle of arbitrary data associated with this
|
||||
* event, lazily creating it if necessary. Set this value by calling
|
||||
* {@link #setData(Bundle)}. Note that when transferring data across
|
||||
* processes via {@link Messenger}, you will need to set your ClassLoader
|
||||
* on the Bundle via {@link Bundle#setClassLoader(ClassLoader)
|
||||
* Bundle.setClassLoader()} so that it can instantiate your objects when
|
||||
* you retrieve them.
|
||||
* @see #peekData()
|
||||
* @see #setData(Bundle)
|
||||
*/
|
||||
public Bundle getData() {
|
||||
if (data == null) {
|
||||
data = new Bundle();
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Like getData(), but does not lazily create the Bundle. A null
|
||||
* is returned if the Bundle does not already exist. See
|
||||
* {@link #getData} for further information on this.
|
||||
* @see #getData()
|
||||
* @see #setData(Bundle)
|
||||
*/
|
||||
@Nullable
|
||||
public Bundle peekData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a Bundle of arbitrary data values. Use arg1 and arg2 members
|
||||
* as a lower cost way to send a few simple integer values, if you can.
|
||||
* @see #getData()
|
||||
* @see #peekData()
|
||||
*/
|
||||
public void setData(Bundle data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Chainable setter for {@link #what}
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public Message setWhat(int what) {
|
||||
this.what = what;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends this Message to the Handler specified by {@link #getTarget}.
|
||||
* Throws a null pointer exception if this field has not been set.
|
||||
*/
|
||||
public void sendToTarget() {
|
||||
target.sendMessage(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the message is asynchronous, meaning that it is not
|
||||
* subject to {@link Looper} synchronization barriers.
|
||||
*
|
||||
* @return True if the message is asynchronous.
|
||||
*
|
||||
* @see #setAsynchronous(boolean)
|
||||
*/
|
||||
public boolean isAsynchronous() {
|
||||
return (flags & FLAG_ASYNCHRONOUS) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the message is asynchronous, meaning that it is not
|
||||
* subject to {@link Looper} synchronization barriers.
|
||||
* <p>
|
||||
* Certain operations, such as view invalidation, may introduce synchronization
|
||||
* barriers into the {@link Looper}'s message queue to prevent subsequent messages
|
||||
* from being delivered until some condition is met. In the case of view invalidation,
|
||||
* messages which are posted after a call to {@link android.view.View#invalidate}
|
||||
* are suspended by means of a synchronization barrier until the next frame is
|
||||
* ready to be drawn. The synchronization barrier ensures that the invalidation
|
||||
* request is completely handled before resuming.
|
||||
* </p><p>
|
||||
* Asynchronous messages are exempt from synchronization barriers. They typically
|
||||
* represent interrupts, input events, and other signals that must be handled independently
|
||||
* even while other work has been suspended.
|
||||
* </p><p>
|
||||
* Note that asynchronous messages may be delivered out of order with respect to
|
||||
* synchronous messages although they are always delivered in order among themselves.
|
||||
* If the relative order of these messages matters then they probably should not be
|
||||
* asynchronous in the first place. Use with caution.
|
||||
* </p>
|
||||
*
|
||||
* @param async True if the message is asynchronous.
|
||||
*
|
||||
* @see #isAsynchronous()
|
||||
*/
|
||||
public void setAsynchronous(boolean async) {
|
||||
if (async) {
|
||||
flags |= FLAG_ASYNCHRONOUS;
|
||||
} else {
|
||||
flags &= ~FLAG_ASYNCHRONOUS;
|
||||
}
|
||||
}
|
||||
|
||||
/*package*/ boolean isInUse() {
|
||||
return ((flags & FLAG_IN_USE) == FLAG_IN_USE);
|
||||
}
|
||||
|
||||
/*package*/ void markInUse() {
|
||||
flags |= FLAG_IN_USE;
|
||||
}
|
||||
|
||||
/** Constructor (but the preferred way to get a Message is to call {@link #obtain() Message.obtain()}).
|
||||
*/
|
||||
public Message() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toString(SystemClock.uptimeMillis());
|
||||
}
|
||||
|
||||
String toString(long now) {
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append("{ when=");
|
||||
TimeUtils.formatDuration(when - now, b);
|
||||
|
||||
if (target != null) {
|
||||
if (callback != null) {
|
||||
b.append(" callback=");
|
||||
b.append(callback.getClass().getName());
|
||||
} else {
|
||||
b.append(" what=");
|
||||
b.append(what);
|
||||
}
|
||||
|
||||
if (arg1 != 0) {
|
||||
b.append(" arg1=");
|
||||
b.append(arg1);
|
||||
}
|
||||
|
||||
if (arg2 != 0) {
|
||||
b.append(" arg2=");
|
||||
b.append(arg2);
|
||||
}
|
||||
|
||||
if (obj != null) {
|
||||
b.append(" obj=");
|
||||
b.append(obj);
|
||||
}
|
||||
|
||||
b.append(" target=");
|
||||
b.append(target.getClass().getName());
|
||||
} else {
|
||||
b.append(" barrier=");
|
||||
b.append(arg1);
|
||||
}
|
||||
|
||||
b.append(" }");
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
public static final @android.annotation.NonNull Parcelable.Creator<Message> CREATOR
|
||||
= new Parcelable.Creator<Message>() {
|
||||
public Message createFromParcel(Parcel source) {
|
||||
Message msg = Message.obtain();
|
||||
msg.readFromParcel(source);
|
||||
return msg;
|
||||
}
|
||||
|
||||
public Message[] newArray(int size) {
|
||||
return new Message[size];
|
||||
}
|
||||
};
|
||||
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
if (callback != null) {
|
||||
throw new RuntimeException(
|
||||
"Can't marshal callbacks across processes.");
|
||||
}
|
||||
dest.writeInt(what);
|
||||
dest.writeInt(arg1);
|
||||
dest.writeInt(arg2);
|
||||
if (obj != null) {
|
||||
try {
|
||||
Parcelable p = (Parcelable)obj;
|
||||
dest.writeInt(1);
|
||||
dest.writeParcelable(p, flags);
|
||||
} catch (ClassCastException e) {
|
||||
throw new RuntimeException(
|
||||
"Can't marshal non-Parcelable objects across processes.");
|
||||
}
|
||||
} else {
|
||||
dest.writeInt(0);
|
||||
}
|
||||
dest.writeLong(when);
|
||||
dest.writeBundle(data);
|
||||
Messenger.writeMessengerOrNullToParcel(replyTo, dest);
|
||||
dest.writeInt(sendingUid);
|
||||
dest.writeInt(workSourceUid);
|
||||
}
|
||||
|
||||
private void readFromParcel(Parcel source) {
|
||||
what = source.readInt();
|
||||
arg1 = source.readInt();
|
||||
arg2 = source.readInt();
|
||||
if (source.readInt() != 0) {
|
||||
obj = source.readParcelable(getClass().getClassLoader());
|
||||
}
|
||||
when = source.readLong();
|
||||
data = source.readBundle();
|
||||
replyTo = Messenger.readMessengerOrNullFromParcel(source);
|
||||
sendingUid = source.readInt();
|
||||
workSourceUid = source.readInt();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -42,19 +42,19 @@ public class SystemProperties {
|
||||
return native_get(key);
|
||||
}
|
||||
private static int native_get_int(String key, int def) {
|
||||
if(configModule.hasProperty(key))
|
||||
if(!configModule.hasProperty(key))
|
||||
return def;
|
||||
else
|
||||
return configModule.getIntProperty(key);
|
||||
}
|
||||
private static long native_get_long(String key, long def) {
|
||||
if(configModule.hasProperty(key))
|
||||
if(!configModule.hasProperty(key))
|
||||
return def;
|
||||
else
|
||||
return configModule.getLongProperty(key);
|
||||
}
|
||||
private static boolean native_get_boolean(String key, boolean def) {
|
||||
if(configModule.hasProperty(key))
|
||||
if(!configModule.hasProperty(key))
|
||||
return def;
|
||||
else
|
||||
return configModule.getBooleanProperty(key);
|
||||
@@ -123,4 +123,4 @@ public class SystemProperties {
|
||||
}
|
||||
native_set(key, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 android.os;
|
||||
|
||||
/**
|
||||
* Tracks who triggered the work currently executed on this thread.
|
||||
*
|
||||
* <p>ThreadLocalWorkSource is automatically updated inside system server for incoming/outgoing
|
||||
* binder calls and messages posted to handler threads.
|
||||
*
|
||||
* <p>ThreadLocalWorkSource can also be set manually if needed to refine the WorkSource.
|
||||
*
|
||||
* <p>Example:
|
||||
* <ul>
|
||||
* <li>Bluetooth process calls {@link PowerManager#isInteractive()} API on behalf of app foo.
|
||||
* <li>ThreadLocalWorkSource will be automatically set to the UID of foo.
|
||||
* <li>Any code on the thread handling {@link PowerManagerService#isInteractive()} can call
|
||||
* {@link ThreadLocalWorkSource#getUid()} to blame any resource used to handle this call.
|
||||
* <li>If a message is posted from the binder thread, the code handling the message can also call
|
||||
* {@link ThreadLocalWorkSource#getUid()} and it will return the UID of foo since the work source is
|
||||
* automatically propagated.
|
||||
* </ul>
|
||||
*
|
||||
* @hide Only for use within system server.
|
||||
*/
|
||||
public final class ThreadLocalWorkSource {
|
||||
public static final int UID_NONE = Message.UID_NONE;
|
||||
private static final ThreadLocal<int []> sWorkSourceUid =
|
||||
ThreadLocal.withInitial(() -> new int[] {UID_NONE});
|
||||
|
||||
/**
|
||||
* Returns the UID to blame for the code currently executed on this thread.
|
||||
*
|
||||
* <p>This UID is set automatically by common frameworks (e.g. Binder and Handler frameworks)
|
||||
* and automatically propagated inside system server.
|
||||
* <p>It can also be set manually using {@link #setUid(int)}.
|
||||
*/
|
||||
public static int getUid() {
|
||||
return sWorkSourceUid.get()[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the UID to blame for the code currently executed on this thread.
|
||||
*
|
||||
* <p>Inside system server, this UID will be automatically propagated.
|
||||
* <p>It will be used to attribute future resources used on this thread (e.g. binder
|
||||
* transactions or processing handler messages) and on any other threads the UID is propagated
|
||||
* to.
|
||||
*
|
||||
* @return a token that can be used to restore the state.
|
||||
*/
|
||||
public static long setUid(int uid) {
|
||||
final long token = getToken();
|
||||
sWorkSourceUid.get()[0] = uid;
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores the state using the provided token.
|
||||
*/
|
||||
public static void restore(long token) {
|
||||
sWorkSourceUid.get()[0] = parseUidFromToken(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the stored work source uid.
|
||||
*
|
||||
* <p>This method should be used when we do not know who to blame. If the UID to blame is the
|
||||
* UID of the current process, it is better to attribute the work to the current process
|
||||
* explicitly instead of clearing the work source:
|
||||
*
|
||||
* <pre>
|
||||
* ThreadLocalWorkSource.setUid(Process.myUid());
|
||||
* </pre>
|
||||
*
|
||||
* @return a token that can be used to restore the state.
|
||||
*/
|
||||
public static long clear() {
|
||||
return setUid(UID_NONE);
|
||||
}
|
||||
|
||||
private static int parseUidFromToken(long token) {
|
||||
return (int) token;
|
||||
}
|
||||
|
||||
private static long getToken() {
|
||||
return sWorkSourceUid.get()[0];
|
||||
}
|
||||
|
||||
private ThreadLocalWorkSource() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
package android.os.shadows;
|
||||
// package org.robolectric.res.android;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A unique id per object registry. Used to emulate android platform behavior of storing a long
|
||||
* which represents a pointer to an object.
|
||||
*/
|
||||
public class NativeObjRegistry<T> {
|
||||
|
||||
private static final int INITIAL_ID = 1;
|
||||
|
||||
private final String name;
|
||||
private final boolean debug;
|
||||
private final HashMap<Long, T> nativeObjToIdMap = new HashMap<Long, T>();
|
||||
private final Map<Long, DebugInfo> idToDebugInfoMap;
|
||||
|
||||
private long nextId = INITIAL_ID;
|
||||
|
||||
public NativeObjRegistry(Class<T> theClass) {
|
||||
this(theClass, false);
|
||||
}
|
||||
|
||||
public NativeObjRegistry(Class<T> theClass, boolean debug) {
|
||||
this(theClass.getSimpleName(), debug);
|
||||
}
|
||||
|
||||
public NativeObjRegistry(String name) {
|
||||
this(name, false);
|
||||
}
|
||||
|
||||
public NativeObjRegistry(String name, boolean debug) {
|
||||
this.name = name;
|
||||
this.debug = debug;
|
||||
this.idToDebugInfoMap = debug ? new HashMap<>() : null;
|
||||
}
|
||||
|
||||
private Long getNativeObjectId(T o) {
|
||||
for(Map.Entry<Long, T> entry : nativeObjToIdMap.entrySet()) {
|
||||
if (o == entry.getValue())
|
||||
return entry.getKey();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register and assign a new unique native id for given object (representing a C memory pointer).
|
||||
*
|
||||
* @throws IllegalStateException if the object was previously registered
|
||||
*/
|
||||
public synchronized long register(T o) {
|
||||
if (o == null)
|
||||
throw new IllegalStateException("Object must not be null");
|
||||
Long nativeId = getNativeObjectId(o);
|
||||
if (nativeId != null) {
|
||||
if (debug) {
|
||||
DebugInfo debugInfo = idToDebugInfoMap.get(nativeId);
|
||||
if (debugInfo != null) {
|
||||
System.out.printf(
|
||||
"NativeObjRegistry %s: register %d -> %s already registered:%n", name, nativeId, o);
|
||||
debugInfo.registrationTrace.printStackTrace(System.out);
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Object was previously registered with id " + nativeId);
|
||||
}
|
||||
|
||||
nativeId = nextId;
|
||||
if (debug) {
|
||||
System.out.printf("NativeObjRegistry %s: register %d -> %s%n", name, nativeId, o);
|
||||
idToDebugInfoMap.put(nativeId, new DebugInfo(new Trace()));
|
||||
}
|
||||
nativeObjToIdMap.put(nativeId, o);
|
||||
nextId++;
|
||||
return nativeId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister an object previously registered with {@link #register(Object)}.
|
||||
*
|
||||
* @param nativeId the unique id (representing a C memory pointer) of the object to unregister.
|
||||
* @throws IllegalStateException if the object was never registered, or was previously
|
||||
* unregistered.
|
||||
*/
|
||||
public synchronized T unregister(long nativeId) {
|
||||
T o = nativeObjToIdMap.remove(nativeId);
|
||||
if (debug) {
|
||||
System.out.printf("NativeObjRegistry %s: unregister %d -> %s%n", name, nativeId, o);
|
||||
new RuntimeException("unregister debug").printStackTrace(System.out);
|
||||
}
|
||||
if (o == null) {
|
||||
if (debug) {
|
||||
DebugInfo debugInfo = idToDebugInfoMap.get(nativeId);
|
||||
debugInfo.unregistrationTraces.add(new Trace());
|
||||
if (debugInfo.unregistrationTraces.size() > 1) {
|
||||
System.out.format("NativeObjRegistry %s: Too many unregistrations:%n", name);
|
||||
for (Trace unregistration : debugInfo.unregistrationTraces) {
|
||||
unregistration.printStackTrace(System.out);
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException(
|
||||
nativeId + " has already been removed (or was never registered)");
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
/** Retrieve the native object for given id. Throws if object with that id cannot be found */
|
||||
public synchronized T getNativeObject(long nativeId) {
|
||||
T object = nativeObjToIdMap.get(nativeId);
|
||||
if (object != null) {
|
||||
return object;
|
||||
} else {
|
||||
throw new NullPointerException(
|
||||
String.format(
|
||||
"Could not find object with nativeId: %d. Currently registered ids: %s",
|
||||
nativeId, nativeObjToIdMap.keySet()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the native object for the given id.
|
||||
*
|
||||
* @throws IllegalStateException if no object was registered with the given id before
|
||||
*/
|
||||
public synchronized void update(long nativeId, T o) {
|
||||
T previous = nativeObjToIdMap.get(nativeId);
|
||||
if (previous == null) {
|
||||
throw new IllegalStateException("Native id " + nativeId + " was never registered");
|
||||
}
|
||||
if (debug) {
|
||||
System.out.printf("NativeObjRegistry %s: update %d -> %s%n", name, nativeId, o);
|
||||
idToDebugInfoMap.put(nativeId, new DebugInfo(new Trace()));
|
||||
}
|
||||
nativeObjToIdMap.put(nativeId, o);
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to {@link #getNativeObject(long)} but returns null if object with given id cannot be
|
||||
* found.
|
||||
*/
|
||||
public synchronized T peekNativeObject(long nativeId) {
|
||||
return nativeObjToIdMap.get(nativeId);
|
||||
}
|
||||
|
||||
/** WARNING -- dangerous! Call {@link #unregister(long)} instead! */
|
||||
public synchronized void clear() {
|
||||
nextId = INITIAL_ID;
|
||||
nativeObjToIdMap.clear();
|
||||
}
|
||||
|
||||
private static class DebugInfo {
|
||||
final Trace registrationTrace;
|
||||
final List<Trace> unregistrationTraces = new ArrayList<>();
|
||||
|
||||
public DebugInfo(Trace trace) {
|
||||
registrationTrace = trace;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Trace extends Throwable {
|
||||
|
||||
private Trace() {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package android.os.shadows;
|
||||
// package org.robolectric.shadows;
|
||||
// and badly gutted
|
||||
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.MessageQueue;
|
||||
import android.os.MessageQueue.IdleHandler;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* The shadow {@link} MessageQueue} for {@link LooperMode.Mode.PAUSED}
|
||||
*
|
||||
* <p>This class should not be referenced directly. Use {@link ShadowMessageQueue} instead.
|
||||
*/
|
||||
@SuppressWarnings("SynchronizeOnNonFinalField")
|
||||
public class ShadowPausedMessageQueue {
|
||||
|
||||
// just use this class as the native object
|
||||
private static NativeObjRegistry<ShadowPausedMessageQueue> nativeQueueRegistry =
|
||||
new NativeObjRegistry<ShadowPausedMessageQueue>(ShadowPausedMessageQueue.class);
|
||||
private boolean isPolling = false;
|
||||
private Exception uncaughtException = null;
|
||||
|
||||
// shadow constructor instead of nativeInit because nativeInit signature has changed across SDK
|
||||
// versions
|
||||
public static long nativeInit() {
|
||||
return nativeQueueRegistry.register(new ShadowPausedMessageQueue());
|
||||
}
|
||||
|
||||
public static void nativeDestroy(long ptr) {
|
||||
nativeQueueRegistry.unregister(ptr);
|
||||
}
|
||||
|
||||
public static void nativePollOnce(long ptr, int timeoutMillis) {
|
||||
ShadowPausedMessageQueue obj = nativeQueueRegistry.getNativeObject(ptr);
|
||||
obj.nativePollOnce(timeoutMillis);
|
||||
}
|
||||
|
||||
public void nativePollOnce(int timeoutMillis) {
|
||||
if (timeoutMillis == 0) {
|
||||
return;
|
||||
}
|
||||
synchronized (this) {
|
||||
isPolling = true;
|
||||
try {
|
||||
if (timeoutMillis < 0) {
|
||||
this.wait();
|
||||
} else {
|
||||
this.wait(timeoutMillis);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// ignore
|
||||
}
|
||||
isPolling = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void nativeWake(long ptr) {
|
||||
ShadowPausedMessageQueue obj = nativeQueueRegistry.getNativeObject(ptr);
|
||||
synchronized (obj) {
|
||||
obj.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean nativeIsPolling(long ptr) {
|
||||
return nativeQueueRegistry.getNativeObject(ptr).isPolling;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,631 @@
|
||||
/*
|
||||
* Copyright (C) 2006 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 android.text;
|
||||
|
||||
import android.annotation.ColorInt;
|
||||
import android.annotation.IntRange;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.RectF;
|
||||
import android.util.Log;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.font.LineBreakMeasurer;
|
||||
import java.awt.font.FontRenderContext;
|
||||
import java.awt.font.TextAttribute;
|
||||
import java.awt.font.TextLayout;
|
||||
import java.text.AttributedString;
|
||||
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
import com.android.internal.util.GrowingArrayUtils;
|
||||
|
||||
|
||||
public class StaticLayout extends Layout {
|
||||
/*
|
||||
* The break iteration is done in native code. The protocol for using the native code is as
|
||||
* follows.
|
||||
*
|
||||
* First, call nInit to setup native line breaker object. Then, for each paragraph, do the
|
||||
* following:
|
||||
*
|
||||
* - Create MeasuredParagraph by MeasuredParagraph.buildForStaticLayout which measures in
|
||||
* native.
|
||||
* - Run LineBreaker.computeLineBreaks() to obtain line breaks for the paragraph.
|
||||
*
|
||||
* After all paragraphs, call finish() to release expensive buffers.
|
||||
*/
|
||||
|
||||
static final String TAG = "StaticLayout";
|
||||
|
||||
public final static class Builder {
|
||||
private Builder() {}
|
||||
|
||||
@NonNull
|
||||
public static Builder obtain(@NonNull CharSequence source, @IntRange(from = 0) int start,
|
||||
@IntRange(from = 0) int end, @NonNull TextPaint paint,
|
||||
@IntRange(from = 0) int width) {
|
||||
Builder b = new Builder();
|
||||
|
||||
// set default initial values
|
||||
b.mText = source;
|
||||
b.mStart = start;
|
||||
b.mEnd = end;
|
||||
b.mPaint = paint;
|
||||
b.mWidth = width;
|
||||
b.mAlignment = Alignment.ALIGN_NORMAL;
|
||||
b.mTextDir = TextDirectionHeuristics.FIRSTSTRONG_LTR;
|
||||
b.mSpacingMult = DEFAULT_LINESPACING_MULTIPLIER;
|
||||
b.mSpacingAdd = DEFAULT_LINESPACING_ADDITION;
|
||||
b.mIncludePad = true;
|
||||
b.mFallbackLineSpacing = false;
|
||||
b.mEllipsizedWidth = width;
|
||||
b.mEllipsize = null;
|
||||
b.mMaxLines = Integer.MAX_VALUE;
|
||||
b.mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
|
||||
b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
|
||||
b.mJustificationMode = Layout.JUSTIFICATION_MODE_NONE;
|
||||
b.mMinimumFontMetrics = null;
|
||||
return b;
|
||||
}
|
||||
|
||||
// release any expensive state
|
||||
/* package */ void finish() {
|
||||
mText = null;
|
||||
mPaint = null;
|
||||
mLeftIndents = null;
|
||||
mRightIndents = null;
|
||||
mMinimumFontMetrics = null;
|
||||
}
|
||||
|
||||
public Builder setText(CharSequence source) {
|
||||
return setText(source, 0, source.length());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Builder setText(@NonNull CharSequence source, int start, int end) {
|
||||
mText = source;
|
||||
mStart = start;
|
||||
mEnd = end;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Builder setPaint(@NonNull TextPaint paint) {
|
||||
mPaint = paint;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Builder setWidth(@IntRange(from = 0) int width) {
|
||||
mWidth = width;
|
||||
if (mEllipsize == null) {
|
||||
mEllipsizedWidth = width;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Builder setAlignment(@NonNull Alignment alignment) {
|
||||
mAlignment = alignment;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Builder setTextDirection(@NonNull TextDirectionHeuristic textDir) {
|
||||
mTextDir = textDir;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Builder setLineSpacing(float spacingAdd, float spacingMult) {
|
||||
mSpacingAdd = spacingAdd;
|
||||
mSpacingMult = spacingMult;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Builder setIncludePad(boolean includePad) {
|
||||
mIncludePad = includePad;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Builder setUseLineSpacingFromFallbacks(boolean useLineSpacingFromFallbacks) {
|
||||
mFallbackLineSpacing = useLineSpacingFromFallbacks;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Builder setEllipsizedWidth(@IntRange(from = 0) int ellipsizedWidth) {
|
||||
mEllipsizedWidth = ellipsizedWidth;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Builder setEllipsize(@Nullable TextUtils.TruncateAt ellipsize) {
|
||||
mEllipsize = ellipsize;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Builder setMaxLines(@IntRange(from = 0) int maxLines) {
|
||||
mMaxLines = maxLines;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Builder setBreakStrategy(@BreakStrategy int breakStrategy) {
|
||||
mBreakStrategy = breakStrategy;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Builder setHyphenationFrequency(@HyphenationFrequency int hyphenationFrequency) {
|
||||
mHyphenationFrequency = hyphenationFrequency;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Builder setIndents(@Nullable int[] leftIndents, @Nullable int[] rightIndents) {
|
||||
mLeftIndents = leftIndents;
|
||||
mRightIndents = rightIndents;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Builder setJustificationMode(@JustificationMode int justificationMode) {
|
||||
mJustificationMode = justificationMode;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
/* package */ Builder setAddLastLineLineSpacing(boolean value) {
|
||||
mAddLastLineLineSpacing = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressLint("MissingGetterMatchingBuilder") // The base class `Layout` has a getter.
|
||||
@NonNull
|
||||
public Builder setUseBoundsForWidth(boolean useBoundsForWidth) {
|
||||
mUseBoundsForWidth = useBoundsForWidth;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
// The corresponding getter is getShiftDrawingOffsetForStartOverhang()
|
||||
@SuppressLint("MissingGetterMatchingBuilder")
|
||||
public Builder setShiftDrawingOffsetForStartOverhang(
|
||||
boolean shiftDrawingOffsetForStartOverhang) {
|
||||
mShiftDrawingOffsetForStartOverhang = shiftDrawingOffsetForStartOverhang;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setCalculateBounds(boolean value) {
|
||||
mCalculateBounds = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public Builder setMinimumFontMetrics(@Nullable Paint.FontMetrics minimumFontMetrics) {
|
||||
mMinimumFontMetrics = minimumFontMetrics;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public StaticLayout build() {
|
||||
StaticLayout result = new StaticLayout(this, mIncludePad, mEllipsize != null
|
||||
? COLUMNS_ELLIPSIZE : COLUMNS_NORMAL);
|
||||
return result;
|
||||
}
|
||||
|
||||
private CharSequence mText;
|
||||
private int mStart;
|
||||
private int mEnd;
|
||||
private TextPaint mPaint;
|
||||
private int mWidth;
|
||||
private Alignment mAlignment;
|
||||
private TextDirectionHeuristic mTextDir;
|
||||
private float mSpacingMult;
|
||||
private float mSpacingAdd;
|
||||
private boolean mIncludePad;
|
||||
private boolean mFallbackLineSpacing;
|
||||
private int mEllipsizedWidth;
|
||||
private TextUtils.TruncateAt mEllipsize;
|
||||
private int mMaxLines;
|
||||
private int mBreakStrategy;
|
||||
private int mHyphenationFrequency;
|
||||
@Nullable private int[] mLeftIndents;
|
||||
@Nullable private int[] mRightIndents;
|
||||
private int mJustificationMode;
|
||||
private boolean mAddLastLineLineSpacing;
|
||||
private boolean mUseBoundsForWidth;
|
||||
private boolean mShiftDrawingOffsetForStartOverhang;
|
||||
private boolean mCalculateBounds;
|
||||
@Nullable private Paint.FontMetrics mMinimumFontMetrics;
|
||||
|
||||
private final Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
|
||||
}
|
||||
|
||||
private StaticLayout() {
|
||||
super(
|
||||
null, // text
|
||||
null, // paint
|
||||
0, // width
|
||||
null, // alignment
|
||||
null, // textDir
|
||||
1, // spacing multiplier
|
||||
0, // spacing amount
|
||||
false, // include font padding
|
||||
false, // fallback line spacing
|
||||
0, // ellipsized width
|
||||
null, // ellipsize
|
||||
1, // maxLines
|
||||
BREAK_STRATEGY_SIMPLE,
|
||||
HYPHENATION_FREQUENCY_NONE,
|
||||
null, // leftIndents
|
||||
null, // rightIndents
|
||||
JUSTIFICATION_MODE_NONE,
|
||||
false, // useBoundsForWidth
|
||||
false, // shiftDrawingOffsetForStartOverhang
|
||||
null // minimumFontMetrics
|
||||
);
|
||||
|
||||
mColumns = COLUMNS_ELLIPSIZE;
|
||||
mLineDirections = new Directions[2];
|
||||
mLines = new int[2 * mColumns];
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public StaticLayout(CharSequence source, TextPaint paint,
|
||||
int width,
|
||||
Alignment align, float spacingmult, float spacingadd,
|
||||
boolean includepad) {
|
||||
this(source, 0, source.length(), paint, width, align,
|
||||
spacingmult, spacingadd, includepad);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public StaticLayout(CharSequence source, int bufstart, int bufend,
|
||||
TextPaint paint, int outerwidth,
|
||||
Alignment align,
|
||||
float spacingmult, float spacingadd,
|
||||
boolean includepad) {
|
||||
this(source, bufstart, bufend, paint, outerwidth, align,
|
||||
spacingmult, spacingadd, includepad, null, 0);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public StaticLayout(CharSequence source, int bufstart, int bufend,
|
||||
TextPaint paint, int outerwidth,
|
||||
Alignment align,
|
||||
float spacingmult, float spacingadd,
|
||||
boolean includepad,
|
||||
TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
|
||||
this(source, bufstart, bufend, paint, outerwidth, align,
|
||||
TextDirectionHeuristics.FIRSTSTRONG_LTR,
|
||||
spacingmult, spacingadd, includepad, ellipsize, ellipsizedWidth, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public StaticLayout(CharSequence source, int bufstart, int bufend,
|
||||
TextPaint paint, int outerwidth,
|
||||
Alignment align, TextDirectionHeuristic textDir,
|
||||
float spacingmult, float spacingadd,
|
||||
boolean includepad,
|
||||
TextUtils.TruncateAt ellipsize, int ellipsizedWidth, int maxLines) {
|
||||
this(Builder.obtain(source, bufstart, bufend, paint, outerwidth)
|
||||
.setAlignment(align)
|
||||
.setTextDirection(textDir)
|
||||
.setLineSpacing(spacingadd, spacingmult)
|
||||
.setIncludePad(includepad)
|
||||
.setEllipsize(ellipsize)
|
||||
.setEllipsizedWidth(ellipsizedWidth)
|
||||
.setMaxLines(maxLines), includepad,
|
||||
ellipsize != null ? COLUMNS_ELLIPSIZE : COLUMNS_NORMAL);
|
||||
}
|
||||
|
||||
private StaticLayout(Builder b, boolean trackPadding, int columnSize) {
|
||||
super((b.mEllipsize == null) ? b.mText : (b.mText instanceof Spanned)
|
||||
? new SpannedEllipsizer(b.mText) : new Ellipsizer(b.mText),
|
||||
b.mPaint, b.mWidth, b.mAlignment, b.mTextDir, b.mSpacingMult, b.mSpacingAdd,
|
||||
b.mIncludePad, b.mFallbackLineSpacing, b.mEllipsizedWidth, b.mEllipsize,
|
||||
b.mMaxLines, b.mBreakStrategy, b.mHyphenationFrequency, b.mLeftIndents,
|
||||
b.mRightIndents, b.mJustificationMode, b.mUseBoundsForWidth,
|
||||
b.mShiftDrawingOffsetForStartOverhang, b.mMinimumFontMetrics);
|
||||
|
||||
mColumns = columnSize;
|
||||
if (b.mEllipsize != null) {
|
||||
Ellipsizer e = (Ellipsizer) getText();
|
||||
|
||||
e.mLayout = this;
|
||||
e.mWidth = b.mEllipsizedWidth;
|
||||
e.mMethod = b.mEllipsize;
|
||||
throw new UnsupportedOperationException("Ellipsis not supported");
|
||||
}
|
||||
|
||||
mLineDirections = new Directions[2];
|
||||
mLines = new int[2 * mColumns];
|
||||
mMaximumVisibleLineCount = b.mMaxLines;
|
||||
|
||||
mLeftIndents = b.mLeftIndents;
|
||||
mRightIndents = b.mRightIndents;
|
||||
|
||||
String str = b.mText.subSequence(b.mStart, b.mEnd).toString();
|
||||
AttributedString text = new AttributedString(str);
|
||||
text.addAttribute(TextAttribute.FONT, getPaint().getFont());
|
||||
FontRenderContext frc = new FontRenderContext(getPaint().getFont().getTransform(), RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT, RenderingHints.VALUE_FRACTIONALMETRICS_DEFAULT);
|
||||
LineBreakMeasurer measurer = new LineBreakMeasurer(text.getIterator(), frc);
|
||||
// TODO: directions
|
||||
|
||||
float y = 0;
|
||||
while (measurer.getPosition() < str.length()) {
|
||||
int off = mLineCount * mColumns;
|
||||
int want = off + mColumns + TOP;
|
||||
if (want >= mLines.length) {
|
||||
final int[] grow = ArrayUtils.newUnpaddedIntArray(GrowingArrayUtils.growSize(want));
|
||||
System.arraycopy(mLines, 0, grow, 0, mLines.length);
|
||||
mLines = grow;
|
||||
}
|
||||
|
||||
int pos = measurer.getPosition();
|
||||
TextLayout l = measurer.nextLayout(getWidth());
|
||||
mLines[off + START] = pos;
|
||||
mLines[off + TOP] = (int) y;
|
||||
mLines[off + DESCENT] = (int) (l.getDescent() + l.getLeading());
|
||||
mLines[off + EXTRA] = (int) l.getLeading();
|
||||
mLines[off + DIR] |= Layout.DIR_LEFT_TO_RIGHT << DIR_SHIFT;
|
||||
|
||||
y += l.getAscent();
|
||||
y += l.getDescent() + l.getLeading();
|
||||
|
||||
mLines[off + mColumns + START] = measurer.getPosition();
|
||||
mLines[off + mColumns + TOP] = (int) y;
|
||||
|
||||
mLineCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Override the base class so we can directly access our members,
|
||||
// rather than relying on member functions.
|
||||
// The logic mirrors that of Layout.getLineForVertical
|
||||
// FIXME: It may be faster to do a linear search for layouts without many lines.
|
||||
@Override
|
||||
public int getLineForVertical(int vertical) {
|
||||
int high = mLineCount;
|
||||
int low = -1;
|
||||
int guess;
|
||||
int[] lines = mLines;
|
||||
while (high - low > 1) {
|
||||
guess = (high + low) >> 1;
|
||||
if (lines[mColumns * guess + TOP] > vertical){
|
||||
high = guess;
|
||||
} else {
|
||||
low = guess;
|
||||
}
|
||||
}
|
||||
if (low < 0) {
|
||||
return 0;
|
||||
} else {
|
||||
return low;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLineCount() {
|
||||
return mLineCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLineTop(int line) {
|
||||
return mLines[mColumns * line + TOP];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLineExtra(int line) {
|
||||
return mLines[mColumns * line + EXTRA];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLineDescent(int line) {
|
||||
return mLines[mColumns * line + DESCENT];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLineStart(int line) {
|
||||
return mLines[mColumns * line + START] & START_MASK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getParagraphDirection(int line) {
|
||||
return mLines[mColumns * line + DIR] >> DIR_SHIFT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getLineContainsTab(int line) {
|
||||
return (mLines[mColumns * line + TAB] & TAB_MASK) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Directions getLineDirections(int line) {
|
||||
if (line > getLineCount()) {
|
||||
throw new ArrayIndexOutOfBoundsException();
|
||||
}
|
||||
return new Directions(null);
|
||||
// return mLineDirections[line];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTopPadding() {
|
||||
return mTopPadding;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBottomPadding() {
|
||||
return mBottomPadding;
|
||||
}
|
||||
|
||||
// To store into single int field, pack the pair of start and end hyphen edit.
|
||||
static int packHyphenEdit(
|
||||
@Paint.StartHyphenEdit int start, @Paint.EndHyphenEdit int end) {
|
||||
return start << START_HYPHEN_BITS_SHIFT | end;
|
||||
}
|
||||
|
||||
static int unpackStartHyphenEdit(int packedHyphenEdit) {
|
||||
return (packedHyphenEdit & START_HYPHEN_MASK) >> START_HYPHEN_BITS_SHIFT;
|
||||
}
|
||||
|
||||
static int unpackEndHyphenEdit(int packedHyphenEdit) {
|
||||
return packedHyphenEdit & END_HYPHEN_MASK;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Paint.StartHyphenEdit int getStartHyphenEdit(int lineNumber) {
|
||||
return unpackStartHyphenEdit(mLines[mColumns * lineNumber + HYPHEN] & HYPHEN_MASK);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Paint.EndHyphenEdit int getEndHyphenEdit(int lineNumber) {
|
||||
return unpackEndHyphenEdit(mLines[mColumns * lineNumber + HYPHEN] & HYPHEN_MASK);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIndentAdjust(int line, Alignment align) {
|
||||
if (align == Alignment.ALIGN_LEFT) {
|
||||
if (mLeftIndents == null) {
|
||||
return 0;
|
||||
} else {
|
||||
return mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
|
||||
}
|
||||
} else if (align == Alignment.ALIGN_RIGHT) {
|
||||
if (mRightIndents == null) {
|
||||
return 0;
|
||||
} else {
|
||||
return -mRightIndents[Math.min(line, mRightIndents.length - 1)];
|
||||
}
|
||||
} else if (align == Alignment.ALIGN_CENTER) {
|
||||
int left = 0;
|
||||
if (mLeftIndents != null) {
|
||||
left = mLeftIndents[Math.min(line, mLeftIndents.length - 1)];
|
||||
}
|
||||
int right = 0;
|
||||
if (mRightIndents != null) {
|
||||
right = mRightIndents[Math.min(line, mRightIndents.length - 1)];
|
||||
}
|
||||
return (left - right) >> 1;
|
||||
} else {
|
||||
throw new AssertionError("unhandled alignment " + align);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEllipsisCount(int line) {
|
||||
if (mColumns < COLUMNS_ELLIPSIZE) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return mLines[mColumns * line + ELLIPSIS_COUNT];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEllipsisStart(int line) {
|
||||
if (mColumns < COLUMNS_ELLIPSIZE) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return mLines[mColumns * line + ELLIPSIS_START];
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public RectF computeDrawingBoundingBox() {
|
||||
// Cache the drawing bounds result because it does not change after created.
|
||||
if (mDrawingBounds == null) {
|
||||
mDrawingBounds = super.computeDrawingBoundingBox();
|
||||
}
|
||||
return mDrawingBounds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight(boolean cap) {
|
||||
if (cap && mLineCount > mMaximumVisibleLineCount && mMaxLineHeight == -1
|
||||
&& Log.isLoggable(TAG, Log.WARN)) {
|
||||
Log.w(TAG, "maxLineHeight should not be -1. "
|
||||
+ " maxLines:" + mMaximumVisibleLineCount
|
||||
+ " lineCount:" + mLineCount);
|
||||
}
|
||||
|
||||
return cap && mLineCount > mMaximumVisibleLineCount && mMaxLineHeight != -1
|
||||
? mMaxLineHeight : super.getHeight();
|
||||
}
|
||||
|
||||
private int mLineCount;
|
||||
private int mTopPadding, mBottomPadding;
|
||||
private int mColumns;
|
||||
private RectF mDrawingBounds = null; // lazy calculation.
|
||||
|
||||
private boolean mEllipsized;
|
||||
|
||||
private int mMaxLineHeight = DEFAULT_MAX_LINE_HEIGHT;
|
||||
|
||||
private static final int COLUMNS_NORMAL = 5;
|
||||
private static final int COLUMNS_ELLIPSIZE = 7;
|
||||
private static final int START = 0;
|
||||
private static final int DIR = START;
|
||||
private static final int TAB = START;
|
||||
private static final int TOP = 1;
|
||||
private static final int DESCENT = 2;
|
||||
private static final int EXTRA = 3;
|
||||
private static final int HYPHEN = 4;
|
||||
private static final int ELLIPSIS_START = 5;
|
||||
private static final int ELLIPSIS_COUNT = 6;
|
||||
|
||||
private int[] mLines;
|
||||
private Directions[] mLineDirections;
|
||||
private int mMaximumVisibleLineCount = Integer.MAX_VALUE;
|
||||
|
||||
private static final int START_MASK = 0x1FFFFFFF;
|
||||
private static final int DIR_SHIFT = 30;
|
||||
private static final int TAB_MASK = 0x20000000;
|
||||
private static final int HYPHEN_MASK = 0xFF;
|
||||
private static final int START_HYPHEN_BITS_SHIFT = 3;
|
||||
private static final int START_HYPHEN_MASK = 0x18; // 0b11000
|
||||
private static final int END_HYPHEN_MASK = 0x7; // 0b00111
|
||||
|
||||
private static final float TAB_INCREMENT = 20; // same as Layout, but that's private
|
||||
|
||||
private static final char CHAR_NEW_LINE = '\n';
|
||||
|
||||
private static final double EXTRA_ROUNDING = 0.5;
|
||||
|
||||
private static final int DEFAULT_MAX_LINE_HEIGHT = -1;
|
||||
|
||||
// Unused, here because of gray list private API accesses.
|
||||
/*package*/ static class LineBreaks {
|
||||
private static final int INITIAL_SIZE = 16;
|
||||
public int[] breaks = new int[INITIAL_SIZE];
|
||||
public float[] widths = new float[INITIAL_SIZE];
|
||||
public float[] ascents = new float[INITIAL_SIZE];
|
||||
public float[] descents = new float[INITIAL_SIZE];
|
||||
public int[] flags = new int[INITIAL_SIZE]; // hasTab
|
||||
// breaks, widths, and flags should all have the same length
|
||||
}
|
||||
|
||||
@Nullable private int[] mLeftIndents;
|
||||
@Nullable private int[] mRightIndents;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright (C) 2010 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 android.text;
|
||||
|
||||
import android.annotation.IntRange;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint.FontMetricsInt;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.text.Layout.Directions;
|
||||
import android.text.Layout.TabStops;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.font.FontRenderContext;
|
||||
|
||||
|
||||
public class TextLine {
|
||||
private TextPaint mPaint;
|
||||
private CharSequence mText;
|
||||
private int mStart;
|
||||
private int mLen;
|
||||
private int mDir;
|
||||
private Directions mDirections;
|
||||
private boolean mHasTabs;
|
||||
private TabStops mTabs;
|
||||
private char[] mChars;
|
||||
private boolean mCharsValid;
|
||||
private Spanned mSpanned;
|
||||
private PrecomputedText mComputed;
|
||||
private RectF mTmpRectForMeasure;
|
||||
private RectF mTmpRectForPaintAPI;
|
||||
private Rect mTmpRectForPrecompute;
|
||||
|
||||
|
||||
public static final class LineInfo {
|
||||
private int mClusterCount;
|
||||
|
||||
public int getClusterCount() {
|
||||
return mClusterCount;
|
||||
}
|
||||
|
||||
public void setClusterCount(int clusterCount) {
|
||||
mClusterCount = clusterCount;
|
||||
}
|
||||
};
|
||||
|
||||
public float getAddedWordSpacingInPx() {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public float getAddedLetterSpacingInPx() {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public boolean isJustifying() {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
/** Not allowed to access. If it's for memory leak workaround, it was already fixed M. */
|
||||
private static final TextLine[] sCached = new TextLine[3];
|
||||
|
||||
public static TextLine obtain() {
|
||||
TextLine tl;
|
||||
synchronized (sCached) {
|
||||
for (int i = sCached.length; --i >= 0;) {
|
||||
if (sCached[i] != null) {
|
||||
tl = sCached[i];
|
||||
sCached[i] = null;
|
||||
return tl;
|
||||
}
|
||||
}
|
||||
}
|
||||
tl = new TextLine();
|
||||
return tl;
|
||||
}
|
||||
|
||||
public static TextLine recycle(TextLine tl) {
|
||||
synchronized(sCached) {
|
||||
for (int i = 0; i < sCached.length; ++i) {
|
||||
if (sCached[i] == null) {
|
||||
sCached[i] = tl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void set(TextPaint paint, CharSequence text, int start, int limit, int dir,
|
||||
Directions directions, boolean hasTabs, TabStops tabStops,
|
||||
int ellipsisStart, int ellipsisEnd, boolean useFallbackLineSpacing) {
|
||||
mPaint = paint;
|
||||
mText = text;
|
||||
mStart = start;
|
||||
mLen = limit - start;
|
||||
mDir = dir;
|
||||
mDirections = directions;
|
||||
if (mDirections == null) {
|
||||
throw new IllegalArgumentException("Directions cannot be null");
|
||||
}
|
||||
mHasTabs = hasTabs;
|
||||
mSpanned = null;
|
||||
|
||||
if (text instanceof Spanned) {
|
||||
mSpanned = (Spanned) text;
|
||||
}
|
||||
|
||||
mComputed = null;
|
||||
if (text instanceof PrecomputedText) {
|
||||
// Here, no need to check line break strategy or hyphenation frequency since there is no
|
||||
// line break concept here.
|
||||
mComputed = (PrecomputedText) text;
|
||||
if (!mComputed.getParams().getTextPaint().equalsForTextMeasurement(paint)) {
|
||||
mComputed = null;
|
||||
}
|
||||
}
|
||||
|
||||
mTabs = tabStops;
|
||||
}
|
||||
|
||||
public void justify(@Layout.JustificationMode int justificationMode, float justifyWidth) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public static int calculateRunFlag(int bidiRunIndex, int bidiRunCount, int lineDirection) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public static int resolveRunFlagForSubSequence(int runFlag, boolean isRtlRun, int runStart,
|
||||
int runEnd, int spanStart, int spanEnd) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public float metrics(FontMetricsInt fmi, @Nullable RectF drawBounds, boolean returnDrawWidth,
|
||||
@Nullable LineInfo lineInfo) {
|
||||
FontRenderContext frc = new FontRenderContext(null, RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT, RenderingHints.VALUE_FRACTIONALMETRICS_DEFAULT);
|
||||
return (float) mPaint.getFont().getStringBounds(mText.toString(), mStart, mStart + mLen, frc).getWidth();
|
||||
}
|
||||
|
||||
public float measure(@IntRange(from = 0) int offset, boolean trailing,
|
||||
@NonNull FontMetricsInt fmi, @Nullable RectF drawBounds, @Nullable LineInfo lineInfo) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public void measureAllBounds(@NonNull float[] bounds, @Nullable float[] advances) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public float[] measureAllOffsets(boolean[] trailing, FontMetricsInt fmi) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
// Note: keep this in sync with Minikin LineBreaker::isLineEndSpace()
|
||||
public static boolean isLineEndSpace(char ch) {
|
||||
return ch == ' ' || ch == '\t' || ch == 0x1680
|
||||
|| (0x2000 <= ch && ch <= 0x200A && ch != 0x2007)
|
||||
|| ch == 0x205F || ch == 0x3000;
|
||||
}
|
||||
|
||||
void draw(Canvas c, float x, int top, int y, int bottom) {
|
||||
c.drawText(mText, mStart, mStart + mLen, x, y, mPaint);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright (C) 2006 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 android.text;
|
||||
|
||||
import android.annotation.ColorInt;
|
||||
import android.graphics.Paint;
|
||||
|
||||
public class TextPaint extends Paint {
|
||||
|
||||
// Special value 0 means no background paint
|
||||
@ColorInt
|
||||
public int bgColor;
|
||||
public int baselineShift;
|
||||
@ColorInt
|
||||
public int linkColor;
|
||||
public int[] drawableState;
|
||||
public float density = 1.0f;
|
||||
@ColorInt
|
||||
public int underlineColor = 0;
|
||||
|
||||
public float underlineThickness;
|
||||
|
||||
public TextPaint() {
|
||||
super();
|
||||
}
|
||||
|
||||
public TextPaint(int flags) {
|
||||
super(flags);
|
||||
}
|
||||
|
||||
public TextPaint(Paint p) {
|
||||
super(p);
|
||||
}
|
||||
|
||||
public void set(TextPaint tp) {
|
||||
super.set(tp);
|
||||
|
||||
bgColor = tp.bgColor;
|
||||
baselineShift = tp.baselineShift;
|
||||
linkColor = tp.linkColor;
|
||||
drawableState = tp.drawableState;
|
||||
density = tp.density;
|
||||
underlineColor = tp.underlineColor;
|
||||
underlineThickness = tp.underlineThickness;
|
||||
}
|
||||
|
||||
public void setUnderlineText(int color, float thickness) {
|
||||
underlineColor = color;
|
||||
underlineThickness = thickness;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getUnderlineThickness() {
|
||||
if (underlineColor != 0) { // Return custom thickness only if underline color is set.
|
||||
return underlineThickness;
|
||||
} else {
|
||||
return super.getUnderlineThickness();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -136,4 +136,4 @@ public final class Log {
|
||||
first.append(msg);
|
||||
return first.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
/*
|
||||
* Copyright (C) 2006 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 android.util;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemApi;
|
||||
import android.os.Build;
|
||||
|
||||
/**
|
||||
* API for sending log output to the {@link Log#LOG_ID_SYSTEM} buffer.
|
||||
*
|
||||
* <p>Should be used by system components. Use {@code adb logcat --buffer=system} to fetch the logs.
|
||||
*
|
||||
* @see Log
|
||||
* @hide
|
||||
*/
|
||||
public final class Slog {
|
||||
|
||||
private Slog() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs {@code msg} at {@link Log#VERBOSE} level.
|
||||
*
|
||||
* @param tag identifies the source of a log message. It usually represents system service,
|
||||
* e.g. {@code PackageManager}.
|
||||
* @param msg the message to log.
|
||||
*
|
||||
* @see Log#v(String, String)
|
||||
*/
|
||||
public static int v(@Nullable String tag, @NonNull String msg) {
|
||||
return Log.println(Log.VERBOSE, tag, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs {@code msg} at {@link Log#VERBOSE} level, attaching stack trace of the {@code tr} to
|
||||
* the end of the log statement.
|
||||
*
|
||||
* @param tag identifies the source of a log message. It usually represents system service,
|
||||
* e.g. {@code PackageManager}.
|
||||
* @param msg the message to log.
|
||||
* @param tr an exception to log.
|
||||
*
|
||||
* @see Log#v(String, String, Throwable)
|
||||
*/
|
||||
public static int v(@Nullable String tag, @NonNull String msg, @Nullable Throwable tr) {
|
||||
return Log.println(Log.VERBOSE, tag,
|
||||
msg + '\n' + Log.getStackTraceString(tr));
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs {@code msg} at {@link Log#DEBUG} level.
|
||||
*
|
||||
* @param tag identifies the source of a log message. It usually represents system service,
|
||||
* e.g. {@code PackageManager}.
|
||||
* @param msg the message to log.
|
||||
*
|
||||
* @see Log#d(String, String)
|
||||
*/
|
||||
public static int d(@Nullable String tag, @NonNull String msg) {
|
||||
return Log.println(Log.DEBUG, tag, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs {@code msg} at {@link Log#DEBUG} level, attaching stack trace of the {@code tr} to
|
||||
* the end of the log statement.
|
||||
*
|
||||
* @param tag identifies the source of a log message. It usually represents system service,
|
||||
* e.g. {@code PackageManager}.
|
||||
* @param msg the message to log.
|
||||
* @param tr an exception to log.
|
||||
*
|
||||
* @see Log#d(String, String, Throwable)
|
||||
*/
|
||||
public static int d(@Nullable String tag, @NonNull String msg, @Nullable Throwable tr) {
|
||||
return Log.println(Log.DEBUG, tag,
|
||||
msg + '\n' + Log.getStackTraceString(tr));
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs {@code msg} at {@link Log#INFO} level.
|
||||
*
|
||||
* @param tag identifies the source of a log message. It usually represents system service,
|
||||
* e.g. {@code PackageManager}.
|
||||
* @param msg the message to log.
|
||||
*
|
||||
* @see Log#i(String, String)
|
||||
*/
|
||||
public static int i(@Nullable String tag, @NonNull String msg) {
|
||||
return Log.println(Log.INFO, tag, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs {@code msg} at {@link Log#INFO} level, attaching stack trace of the {@code tr} to
|
||||
* the end of the log statement.
|
||||
*
|
||||
* @param tag identifies the source of a log message. It usually represents system service,
|
||||
* e.g. {@code PackageManager}.
|
||||
* @param msg the message to log.
|
||||
* @param tr an exception to log.
|
||||
*
|
||||
* @see Log#i(String, String, Throwable)
|
||||
*/
|
||||
public static int i(@Nullable String tag, @NonNull String msg, @Nullable Throwable tr) {
|
||||
return Log.println(Log.INFO, tag,
|
||||
msg + '\n' + Log.getStackTraceString(tr));
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs {@code msg} at {@link Log#WARN} level.
|
||||
*
|
||||
* @param tag identifies the source of a log message. It usually represents system service,
|
||||
* e.g. {@code PackageManager}.
|
||||
* @param msg the message to log.
|
||||
*
|
||||
* @see Log#w(String, String)
|
||||
*/
|
||||
public static int w(@Nullable String tag, @NonNull String msg) {
|
||||
return Log.println(Log.WARN, tag, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs {@code msg} at {@link Log#WARN} level, attaching stack trace of the {@code tr} to
|
||||
* the end of the log statement.
|
||||
*
|
||||
* @param tag identifies the source of a log message. It usually represents system service,
|
||||
* e.g. {@code PackageManager}.
|
||||
* @param msg the message to log.
|
||||
* @param tr an exception to log.
|
||||
*
|
||||
* @see Log#w(String, String, Throwable)
|
||||
*/
|
||||
public static int w(@Nullable String tag, @NonNull String msg, @Nullable Throwable tr) {
|
||||
return Log.println(Log.WARN, tag,
|
||||
msg + '\n' + Log.getStackTraceString(tr));
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs stack trace of {@code tr} at {@link Log#WARN} level.
|
||||
*
|
||||
* @param tag identifies the source of a log message. It usually represents system service,
|
||||
* e.g. {@code PackageManager}.
|
||||
* @param tr an exception to log.
|
||||
*
|
||||
* @see Log#w(String, Throwable)
|
||||
*/
|
||||
public static int w(@Nullable String tag, @Nullable Throwable tr) {
|
||||
return Log.println(Log.WARN, tag, Log.getStackTraceString(tr));
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs {@code msg} at {@link Log#ERROR} level.
|
||||
*
|
||||
* @param tag identifies the source of a log message. It usually represents system service,
|
||||
* e.g. {@code PackageManager}.
|
||||
* @param msg the message to log.
|
||||
*
|
||||
* @see Log#e(String, String)
|
||||
*/
|
||||
public static int e(@Nullable String tag, @NonNull String msg) {
|
||||
return Log.println(Log.ERROR, tag, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs {@code msg} at {@link Log#ERROR} level, attaching stack trace of the {@code tr} to
|
||||
* the end of the log statement.
|
||||
*
|
||||
* @param tag identifies the source of a log message. It usually represents system service,
|
||||
* e.g. {@code PackageManager}.
|
||||
* @param msg the message to log.
|
||||
* @param tr an exception to log.
|
||||
*
|
||||
* @see Log#e(String, String, Throwable)
|
||||
*/
|
||||
public static int e(@Nullable String tag, @NonNull String msg, @Nullable Throwable tr) {
|
||||
return Log.println(Log.ERROR, tag,
|
||||
msg + '\n' + Log.getStackTraceString(tr));
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a condition that should never happen.
|
||||
*
|
||||
* <p>
|
||||
* Similar to {@link Log#wtf(String, String)}, but will never cause the caller to crash, and
|
||||
* will always be handled asynchronously. Primarily to be used by the system server.
|
||||
*
|
||||
* @param tag identifies the source of a log message. It usually represents system service,
|
||||
* e.g. {@code PackageManager}.
|
||||
* @param msg the message to log.
|
||||
*
|
||||
* @see Log#wtf(String, String)
|
||||
*/
|
||||
public static int wtf(@Nullable String tag, @NonNull String msg) {
|
||||
return Log.wtf(tag, msg, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a condition that should never happen, attaching the full call stack to the log.
|
||||
*
|
||||
* <p>
|
||||
* Similar to {@link Log#wtfStack(String, String)}, but will never cause the caller to crash,
|
||||
* and will always be handled asynchronously. Primarily to be used by the system server.
|
||||
*
|
||||
* @param tag identifies the source of a log message. It usually represents system service,
|
||||
* e.g. {@code PackageManager}.
|
||||
* @param msg the message to log.
|
||||
*
|
||||
* @see Log#wtfStack(String, String)
|
||||
*/
|
||||
public static int wtfStack(@Nullable String tag, @NonNull String msg) {
|
||||
return Log.wtf(tag, msg, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a condition that should never happen, attaching stack trace of the {@code tr} to the
|
||||
* end of the log statement.
|
||||
*
|
||||
* <p>
|
||||
* Similar to {@link Log#wtf(String, Throwable)}, but will never cause the caller to crash,
|
||||
* and will always be handled asynchronously. Primarily to be used by the system server.
|
||||
*
|
||||
* @param tag identifies the source of a log message. It usually represents system service,
|
||||
* e.g. {@code PackageManager}.
|
||||
* @param tr an exception to log.
|
||||
*
|
||||
* @see Log#wtf(String, Throwable)
|
||||
*/
|
||||
public static int wtf(@Nullable String tag, @Nullable Throwable tr) {
|
||||
return Log.wtf(tag, tr.getMessage(), tr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a condition that should never happen, attaching stack trace of the {@code tr} to the
|
||||
* end of the log statement.
|
||||
*
|
||||
* <p>
|
||||
* Similar to {@link Log#wtf(String, String, Throwable)}, but will never cause the caller to
|
||||
* crash, and will always be handled asynchronously. Primarily to be used by the system server.
|
||||
*
|
||||
* @param tag identifies the source of a log message. It usually represents system service,
|
||||
* e.g. {@code PackageManager}.
|
||||
* @param msg the message to log.
|
||||
* @param tr an exception to log.
|
||||
*
|
||||
* @see Log#wtf(String, String, Throwable)
|
||||
*/
|
||||
public static int wtf(@Nullable String tag, @NonNull String msg, @Nullable Throwable tr) {
|
||||
return Log.wtf(tag, msg, tr);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public static int println(int priority, @Nullable String tag, @NonNull String msg) {
|
||||
return Log.println(priority, tag, msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,371 @@
|
||||
/*
|
||||
* Copyright (C) 2006 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 android.util;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.TestApi;
|
||||
import android.os.Build;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A class containing utility methods related to time zones.
|
||||
*/
|
||||
public class TimeUtils {
|
||||
/** @hide */ public TimeUtils() {}
|
||||
/** {@hide} */
|
||||
private static final SimpleDateFormat sLoggingFormat =
|
||||
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
/** @hide */
|
||||
public static final SimpleDateFormat sDumpDateFormat =
|
||||
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
|
||||
|
||||
/**
|
||||
* This timestamp is used in TimeUtils methods and by the SettingsUI to filter time zones
|
||||
* to only "effective" ones in a country. It is compared against the notUsedAfter metadata that
|
||||
* Android records for some time zones.
|
||||
*
|
||||
* <p>What is notUsedAfter?</p>
|
||||
* Android chooses to avoid making users choose between functionally identical time zones at the
|
||||
* expense of not being able to represent local times in the past.
|
||||
*
|
||||
* notUsedAfter exists because some time zones can "merge" with other time zones after a given
|
||||
* point in time (i.e. they change to have identical transitions, offsets, display names, etc.).
|
||||
* From the notUsedAfter time, the zone will express the same local time as the one it merged
|
||||
* with.
|
||||
*
|
||||
* <p>Why hardcoded?</p>
|
||||
* Rather than using System.currentTimeMillis(), a timestamp known to be in the recent past is
|
||||
* used to ensure consistent behavior across devices and time, and avoid assumptions that the
|
||||
* system clock on a device is currently set correctly. The fixed value should be updated
|
||||
* occasionally, but it doesn't have to be very often as effective time zones for a country
|
||||
* don't change very often.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final Instant MIN_USE_DATE_OF_TIMEZONE =
|
||||
Instant.ofEpochMilli(1546300800000L); // 1/1/2019 00:00 UTC
|
||||
|
||||
/** @hide Field length that can hold 999 days of time */
|
||||
public static final int HUNDRED_DAY_FIELD_LEN = 19;
|
||||
|
||||
private static final int SECONDS_PER_MINUTE = 60;
|
||||
private static final int SECONDS_PER_HOUR = 60 * 60;
|
||||
private static final int SECONDS_PER_DAY = 24 * 60 * 60;
|
||||
|
||||
/** @hide */
|
||||
public static final long NANOS_PER_MS = 1000000;
|
||||
|
||||
private static final Object sFormatSync = new Object();
|
||||
private static char[] sFormatStr = new char[HUNDRED_DAY_FIELD_LEN+10];
|
||||
private static char[] sTmpFormatStr = new char[HUNDRED_DAY_FIELD_LEN+10];
|
||||
|
||||
static private int accumField(int amt, int suffix, boolean always, int zeropad) {
|
||||
if (amt > 999) {
|
||||
int num = 0;
|
||||
while (amt != 0) {
|
||||
num++;
|
||||
amt /= 10;
|
||||
}
|
||||
return num + suffix;
|
||||
} else {
|
||||
if (amt > 99 || (always && zeropad >= 3)) {
|
||||
return 3+suffix;
|
||||
}
|
||||
if (amt > 9 || (always && zeropad >= 2)) {
|
||||
return 2+suffix;
|
||||
}
|
||||
if (always || amt > 0) {
|
||||
return 1+suffix;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static private int printFieldLocked(char[] formatStr, int amt, char suffix, int pos,
|
||||
boolean always, int zeropad) {
|
||||
if (always || amt > 0) {
|
||||
final int startPos = pos;
|
||||
if (amt > 999) {
|
||||
int tmp = 0;
|
||||
while (amt != 0 && tmp < sTmpFormatStr.length) {
|
||||
int dig = amt % 10;
|
||||
sTmpFormatStr[tmp] = (char)(dig + '0');
|
||||
tmp++;
|
||||
amt /= 10;
|
||||
}
|
||||
tmp--;
|
||||
while (tmp >= 0) {
|
||||
formatStr[pos] = sTmpFormatStr[tmp];
|
||||
pos++;
|
||||
tmp--;
|
||||
}
|
||||
} else {
|
||||
if ((always && zeropad >= 3) || amt > 99) {
|
||||
int dig = amt/100;
|
||||
formatStr[pos] = (char)(dig + '0');
|
||||
pos++;
|
||||
amt -= (dig*100);
|
||||
}
|
||||
if ((always && zeropad >= 2) || amt > 9 || startPos != pos) {
|
||||
int dig = amt/10;
|
||||
formatStr[pos] = (char)(dig + '0');
|
||||
pos++;
|
||||
amt -= (dig*10);
|
||||
}
|
||||
formatStr[pos] = (char)(amt + '0');
|
||||
pos++;
|
||||
}
|
||||
formatStr[pos] = suffix;
|
||||
pos++;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
private static int formatDurationLocked(long duration, int fieldLen) {
|
||||
if (sFormatStr.length < fieldLen) {
|
||||
sFormatStr = new char[fieldLen];
|
||||
}
|
||||
|
||||
char[] formatStr = sFormatStr;
|
||||
|
||||
if (duration == 0) {
|
||||
int pos = 0;
|
||||
fieldLen -= 1;
|
||||
while (pos < fieldLen) {
|
||||
formatStr[pos++] = ' ';
|
||||
}
|
||||
formatStr[pos] = '0';
|
||||
return pos+1;
|
||||
}
|
||||
|
||||
char prefix;
|
||||
if (duration > 0) {
|
||||
prefix = '+';
|
||||
} else {
|
||||
prefix = '-';
|
||||
duration = -duration;
|
||||
}
|
||||
|
||||
int millis = (int)(duration%1000);
|
||||
int seconds = (int) Math.floor(duration / 1000);
|
||||
int days = 0, hours = 0, minutes = 0;
|
||||
|
||||
if (seconds >= SECONDS_PER_DAY) {
|
||||
days = seconds / SECONDS_PER_DAY;
|
||||
seconds -= days * SECONDS_PER_DAY;
|
||||
}
|
||||
if (seconds >= SECONDS_PER_HOUR) {
|
||||
hours = seconds / SECONDS_PER_HOUR;
|
||||
seconds -= hours * SECONDS_PER_HOUR;
|
||||
}
|
||||
if (seconds >= SECONDS_PER_MINUTE) {
|
||||
minutes = seconds / SECONDS_PER_MINUTE;
|
||||
seconds -= minutes * SECONDS_PER_MINUTE;
|
||||
}
|
||||
|
||||
int pos = 0;
|
||||
|
||||
if (fieldLen != 0) {
|
||||
int myLen = accumField(days, 1, false, 0);
|
||||
myLen += accumField(hours, 1, myLen > 0, 2);
|
||||
myLen += accumField(minutes, 1, myLen > 0, 2);
|
||||
myLen += accumField(seconds, 1, myLen > 0, 2);
|
||||
myLen += accumField(millis, 2, true, myLen > 0 ? 3 : 0) + 1;
|
||||
while (myLen < fieldLen) {
|
||||
formatStr[pos] = ' ';
|
||||
pos++;
|
||||
myLen++;
|
||||
}
|
||||
}
|
||||
|
||||
formatStr[pos] = prefix;
|
||||
pos++;
|
||||
|
||||
int start = pos;
|
||||
boolean zeropad = fieldLen != 0;
|
||||
pos = printFieldLocked(formatStr, days, 'd', pos, false, 0);
|
||||
pos = printFieldLocked(formatStr, hours, 'h', pos, pos != start, zeropad ? 2 : 0);
|
||||
pos = printFieldLocked(formatStr, minutes, 'm', pos, pos != start, zeropad ? 2 : 0);
|
||||
pos = printFieldLocked(formatStr, seconds, 's', pos, pos != start, zeropad ? 2 : 0);
|
||||
pos = printFieldLocked(formatStr, millis, 'm', pos, true, (zeropad && pos != start) ? 3 : 0);
|
||||
formatStr[pos] = 's';
|
||||
return pos + 1;
|
||||
}
|
||||
|
||||
/** @hide Just for debugging; not internationalized. */
|
||||
public static void formatDuration(long duration, StringBuilder builder) {
|
||||
synchronized (sFormatSync) {
|
||||
int len = formatDurationLocked(duration, 0);
|
||||
builder.append(sFormatStr, 0, len);
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide Just for debugging; not internationalized. */
|
||||
public static void formatDuration(long duration, StringBuilder builder, int fieldLen) {
|
||||
synchronized (sFormatSync) {
|
||||
int len = formatDurationLocked(duration, fieldLen);
|
||||
builder.append(sFormatStr, 0, len);
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide Just for debugging; not internationalized. */
|
||||
public static void formatDuration(long duration, PrintWriter pw, int fieldLen) {
|
||||
synchronized (sFormatSync) {
|
||||
int len = formatDurationLocked(duration, fieldLen);
|
||||
pw.print(new String(sFormatStr, 0, len));
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide Just for debugging; not internationalized. */
|
||||
@TestApi
|
||||
public static String formatDuration(long duration) {
|
||||
synchronized (sFormatSync) {
|
||||
int len = formatDurationLocked(duration, 0);
|
||||
return new String(sFormatStr, 0, len);
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide Just for debugging; not internationalized. */
|
||||
public static void formatDuration(long duration, PrintWriter pw) {
|
||||
formatDuration(duration, pw, 0);
|
||||
}
|
||||
|
||||
/** @hide Just for debugging; not internationalized. */
|
||||
public static void formatDuration(long time, long now, StringBuilder sb) {
|
||||
if (time == 0) {
|
||||
sb.append("--");
|
||||
return;
|
||||
}
|
||||
formatDuration(time-now, sb, 0);
|
||||
}
|
||||
|
||||
/** @hide Just for debugging; not internationalized. */
|
||||
public static void formatDuration(long time, long now, PrintWriter pw) {
|
||||
if (time == 0) {
|
||||
pw.print("--");
|
||||
return;
|
||||
}
|
||||
formatDuration(time-now, pw, 0);
|
||||
}
|
||||
|
||||
/** @hide Just for debugging; not internationalized. */
|
||||
public static String formatUptime(long time) {
|
||||
return formatTime(time, SystemClock.uptimeMillis());
|
||||
}
|
||||
|
||||
/** @hide Just for debugging; not internationalized. */
|
||||
public static String formatRealtime(long time) {
|
||||
return formatTime(time, SystemClock.elapsedRealtime());
|
||||
}
|
||||
|
||||
/** @hide Just for debugging; not internationalized. */
|
||||
public static String formatTime(long time, long referenceTime) {
|
||||
long diff = time - referenceTime;
|
||||
if (diff > 0) {
|
||||
return time + " (in " + diff + " ms)";
|
||||
}
|
||||
if (diff < 0) {
|
||||
return time + " (" + -diff + " ms ago)";
|
||||
}
|
||||
return time + " (now)";
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a System.currentTimeMillis() value to a time of day value like
|
||||
* that printed in logs. MM-DD HH:MM:SS.MMM
|
||||
*
|
||||
* @param millis since the epoch (1/1/1970)
|
||||
* @return String representation of the time.
|
||||
* @hide
|
||||
*/
|
||||
public static String logTimeOfDay(long millis) {
|
||||
Calendar c = Calendar.getInstance();
|
||||
if (millis >= 0) {
|
||||
c.setTimeInMillis(millis);
|
||||
return String.format("%tm-%td %tH:%tM:%tS.%tL", c, c, c, c, c, c);
|
||||
} else {
|
||||
return Long.toString(millis);
|
||||
}
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
public static String formatForLogging(long millis) {
|
||||
if (millis <= 0) {
|
||||
return "unknown";
|
||||
} else {
|
||||
return sLoggingFormat.format(new Date(millis));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump a currentTimeMillis style timestamp for dumpsys.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static void dumpTime(PrintWriter pw, long time) {
|
||||
pw.print(sDumpDateFormat.format(new Date(time)));
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used to find if a clock time is inclusively between two other clock times
|
||||
* @param reference The time of the day we want check if it is between start and end
|
||||
* @param start The start time reference
|
||||
* @param end The end time
|
||||
* @return true if the reference time is between the two clock times, and false otherwise.
|
||||
*/
|
||||
public static boolean isTimeBetween(@NonNull LocalTime reference,
|
||||
@NonNull LocalTime start,
|
||||
@NonNull LocalTime end) {
|
||||
// ////////E----+-----S////////
|
||||
if ((reference.isBefore(start) && reference.isAfter(end)
|
||||
// -----+----S//////////E------
|
||||
|| (reference.isBefore(end) && reference.isBefore(start) && start.isBefore(end))
|
||||
// ---------S//////////E---+---
|
||||
|| (reference.isAfter(end) && reference.isAfter(start)) && start.isBefore(end))) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump a currentTimeMillis style timestamp for dumpsys, with the delta time from now.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static void dumpTimeWithDelta(PrintWriter pw, long time, long now) {
|
||||
pw.print(sDumpDateFormat.format(new Date(time)));
|
||||
if (time == now) {
|
||||
pw.print(" (now)");
|
||||
} else {
|
||||
pw.print(" (");
|
||||
TimeUtils.formatDuration(time, now, pw);
|
||||
pw.print(")");
|
||||
}
|
||||
}}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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 android.os;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Supplier for custom trace messages.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public interface TraceNameSupplier {
|
||||
|
||||
/**
|
||||
* Gets the name used for trace messages.
|
||||
*/
|
||||
@NonNull String getTraceName();
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* 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 android.webkit;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemApi;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.os.Message;
|
||||
import android.view.View;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
public class WebChromeClient {
|
||||
|
||||
public void onProgressChanged(WebView view, int newProgress) {}
|
||||
|
||||
public void onReceivedTitle(WebView view, String title) {}
|
||||
|
||||
public void onReceivedIcon(WebView view, Bitmap icon) {}
|
||||
|
||||
public void onReceivedTouchIconUrl(WebView view, String url,
|
||||
boolean precomposed) {}
|
||||
|
||||
public interface CustomViewCallback {
|
||||
public void onCustomViewHidden();
|
||||
}
|
||||
|
||||
public void onShowCustomView(View view, CustomViewCallback callback) {};
|
||||
|
||||
@Deprecated
|
||||
public void onShowCustomView(View view, int requestedOrientation,
|
||||
CustomViewCallback callback) {};
|
||||
|
||||
public void onHideCustomView() {}
|
||||
|
||||
public boolean onCreateWindow(WebView view, boolean isDialog,
|
||||
boolean isUserGesture, Message resultMsg) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void onRequestFocus(WebView view) {}
|
||||
|
||||
public void onCloseWindow(WebView window) {}
|
||||
|
||||
public boolean onJsAlert(WebView view, String url, String message,
|
||||
JsResult result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean onJsConfirm(WebView view, String url, String message,
|
||||
JsResult result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean onJsPrompt(WebView view, String url, String message,
|
||||
String defaultValue, JsPromptResult result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean onJsBeforeUnload(WebView view, String url, String message,
|
||||
JsResult result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void onExceededDatabaseQuota(String url, String databaseIdentifier,
|
||||
long quota, long estimatedDatabaseSize, long totalQuota,
|
||||
WebStorage.QuotaUpdater quotaUpdater) {
|
||||
// This default implementation passes the current quota back to WebCore.
|
||||
// WebCore will interpret this that new quota was declined.
|
||||
quotaUpdater.updateQuota(quota);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void onReachedMaxAppCacheSize(long requiredStorage, long quota,
|
||||
WebStorage.QuotaUpdater quotaUpdater) {
|
||||
quotaUpdater.updateQuota(quota);
|
||||
}
|
||||
|
||||
public void onGeolocationPermissionsShowPrompt(String origin,
|
||||
GeolocationPermissions.Callback callback) {}
|
||||
|
||||
public void onGeolocationPermissionsHidePrompt() {}
|
||||
|
||||
public void onPermissionRequest(PermissionRequest request) {
|
||||
request.deny();
|
||||
}
|
||||
|
||||
public void onPermissionRequestCanceled(PermissionRequest request) {}
|
||||
|
||||
// This method was only called when using the JSC javascript engine. V8 became
|
||||
// the default JS engine with Froyo and support for building with JSC was
|
||||
// removed in b/5495373. V8 does not have a mechanism for making a callback such
|
||||
// as this.
|
||||
@Deprecated
|
||||
public boolean onJsTimeout() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void onConsoleMessage(String message, int lineNumber, String sourceID) { }
|
||||
|
||||
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
|
||||
// Call the old version of this function for backwards compatability.
|
||||
onConsoleMessage(consoleMessage.message(), consoleMessage.lineNumber(),
|
||||
consoleMessage.sourceId());
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Bitmap getDefaultVideoPoster() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public View getVideoLoadingProgressView() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Obtains a list of all visited history items, used for link coloring
|
||||
*/
|
||||
public void getVisitedHistory(ValueCallback<String[]> callback) {
|
||||
}
|
||||
|
||||
public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback,
|
||||
FileChooserParams fileChooserParams) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static abstract class FileChooserParams {
|
||||
@SystemApi
|
||||
public static final long ENABLE_FILE_SYSTEM_ACCESS = 364980165L;
|
||||
|
||||
/** @hide */
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Mode {}
|
||||
|
||||
/** Open single file. Requires that the file exists before allowing the user to pick it. */
|
||||
public static final int MODE_OPEN = 0;
|
||||
/** Like Open but allows multiple files to be selected. */
|
||||
public static final int MODE_OPEN_MULTIPLE = 1;
|
||||
/** Like Open but allows a folder to be selected. */
|
||||
public static final int MODE_OPEN_FOLDER = 2;
|
||||
/** Allows picking a nonexistent file and saving it. */
|
||||
public static final int MODE_SAVE = 3;
|
||||
|
||||
/** @hide */
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface PermissionMode {}
|
||||
|
||||
/** File or directory should be opened for reading only. */
|
||||
public static final int PERMISSION_MODE_READ = 0;
|
||||
/** File or directory should be opened for read and write. */
|
||||
public static final int PERMISSION_MODE_READ_WRITE = 1;
|
||||
|
||||
@Nullable
|
||||
public static Uri[] parseResult(int resultCode, Intent data) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
@Mode
|
||||
public abstract int getMode();
|
||||
|
||||
public abstract String[] getAcceptTypes();
|
||||
|
||||
public abstract boolean isCaptureEnabled();
|
||||
|
||||
@Nullable
|
||||
public abstract CharSequence getTitle();
|
||||
|
||||
@Nullable
|
||||
public abstract String getFilenameHint();
|
||||
|
||||
@PermissionMode
|
||||
public int getPermissionMode() {
|
||||
return PERMISSION_MODE_READ;
|
||||
}
|
||||
|
||||
public abstract Intent createIntent();
|
||||
}
|
||||
|
||||
@SystemApi
|
||||
@Deprecated
|
||||
public void openFileChooser(ValueCallback<Uri> uploadFile, String acceptType, String capture) {
|
||||
uploadFile.onReceiveValue(null);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,609 @@
|
||||
/*
|
||||
* 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 android.webkit;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.http.SslError;
|
||||
import android.os.Message;
|
||||
import android.view.InputEvent;
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
public class WebViewClient {
|
||||
|
||||
/**
|
||||
* Give the host application a chance to take control when a URL is about to be loaded in the
|
||||
* current WebView. If a WebViewClient is not provided, by default WebView will ask Activity
|
||||
* Manager to choose the proper handler for the URL. If a WebViewClient is provided, returning
|
||||
* {@code true} causes the current WebView to abort loading the URL, while returning
|
||||
* {@code false} causes the WebView to continue loading the URL as usual.
|
||||
*
|
||||
* <p class="note"><b>Note:</b> Do not call {@link WebView#loadUrl(String)} with the same
|
||||
* URL and then return {@code true}. This unnecessarily cancels the current load and starts a
|
||||
* new load with the same URL. The correct way to continue loading a given URL is to simply
|
||||
* return {@code false}, without calling {@link WebView#loadUrl(String)}.
|
||||
*
|
||||
* <p class="note"><b>Note:</b> This method is not called for POST requests.
|
||||
*
|
||||
* <p class="note"><b>Note:</b> This method may be called for subframes and with non-HTTP(S)
|
||||
* schemes; calling {@link WebView#loadUrl(String)} with such a URL will fail.
|
||||
*
|
||||
* @param view The WebView that is initiating the callback.
|
||||
* @param url The URL to be loaded.
|
||||
* @return {@code true} to cancel the current load, otherwise return {@code false}.
|
||||
* @deprecated Use {@link #shouldOverrideUrlLoading(WebView, WebResourceRequest)
|
||||
* shouldOverrideUrlLoading(WebView, WebResourceRequest)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Give the host application a chance to take control when a URL is about to be loaded in the
|
||||
* current WebView. If a WebViewClient is not provided, by default WebView will ask Activity
|
||||
* Manager to choose the proper handler for the URL. If a WebViewClient is provided, returning
|
||||
* {@code true} causes the current WebView to abort loading the URL, while returning
|
||||
* {@code false} causes the WebView to continue loading the URL as usual.
|
||||
*
|
||||
* <p>This callback is not called for all page navigations. In particular, this is not called
|
||||
* for navigations which the app initiated with {@code loadUrl()}: this callback would not serve
|
||||
* a purpose in this case, because the app already knows about the navigation. This callback
|
||||
* lets the app know about navigations initiated by the web page (such as navigations initiated
|
||||
* by JavaScript code), by the user (such as when the user taps on a link), or by an HTTP
|
||||
* redirect (ex. if {@code loadUrl("foo.com")} redirects to {@code "bar.com"} because of HTTP
|
||||
* 301).
|
||||
*
|
||||
* <p class="note"><b>Note:</b> Do not call {@link WebView#loadUrl(String)} with the request's
|
||||
* URL and then return {@code true}. This unnecessarily cancels the current load and starts a
|
||||
* new load with the same URL. The correct way to continue loading a given URL is to simply
|
||||
* return {@code false}, without calling {@link WebView#loadUrl(String)}.
|
||||
*
|
||||
* <p class="note"><b>Note:</b> This method is not called for POST requests.
|
||||
*
|
||||
* <p class="note"><b>Note:</b> This method may be called for subframes and with non-HTTP(S)
|
||||
* schemes; calling {@link WebView#loadUrl(String)} with such a URL will fail.
|
||||
*
|
||||
* @param view The WebView that is initiating the callback.
|
||||
* @param request Object containing the details of the request.
|
||||
* @return {@code true} to cancel the current load, otherwise return {@code false}.
|
||||
*/
|
||||
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
|
||||
return shouldOverrideUrlLoading(view, request.getUrl().toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the host application that a page has started loading. This method
|
||||
* is called once for each main frame load so a page with iframes or
|
||||
* framesets will call onPageStarted one time for the main frame. This also
|
||||
* means that onPageStarted will not be called when the contents of an
|
||||
* embedded frame changes, i.e. clicking a link whose target is an iframe,
|
||||
* it will also not be called for fragment navigations (navigations to
|
||||
* #fragment_id).
|
||||
*
|
||||
* @param view The WebView that is initiating the callback.
|
||||
* @param url The url to be loaded.
|
||||
* @param favicon The favicon for this page if it already exists in the
|
||||
* database.
|
||||
*/
|
||||
public void onPageStarted(WebView view, String url, Bitmap favicon) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the host application that a page has finished loading. This method
|
||||
* is called only for main frame. Receiving an {@code onPageFinished()} callback does not
|
||||
* guarantee that the next frame drawn by WebView will reflect the state of the DOM at this
|
||||
* point. In order to be notified that the current DOM state is ready to be rendered, request a
|
||||
* visual state callback with {@link WebView#postVisualStateCallback} and wait for the supplied
|
||||
* callback to be triggered.
|
||||
*
|
||||
* @param view The WebView that is initiating the callback.
|
||||
* @param url The url of the page.
|
||||
*/
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the host application that the WebView will load the resource
|
||||
* specified by the given url.
|
||||
*
|
||||
* @param view The WebView that is initiating the callback.
|
||||
* @param url The url of the resource the WebView will load.
|
||||
*/
|
||||
public void onLoadResource(WebView view, String url) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the host application that {@link android.webkit.WebView} content left over from
|
||||
* previous page navigations will no longer be drawn.
|
||||
*
|
||||
* <p>This callback can be used to determine the point at which it is safe to make a recycled
|
||||
* {@link android.webkit.WebView} visible, ensuring that no stale content is shown. It is called
|
||||
* at the earliest point at which it can be guaranteed that {@link WebView#onDraw} will no
|
||||
* longer draw any content from previous navigations. The next draw will display either the
|
||||
* {@link WebView#setBackgroundColor background color} of the {@link WebView}, or some of the
|
||||
* contents of the newly loaded page.
|
||||
*
|
||||
* <p>This method is called when the body of the HTTP response has started loading, is reflected
|
||||
* in the DOM, and will be visible in subsequent draws. This callback occurs early in the
|
||||
* document loading process, and as such you should expect that linked resources (for example,
|
||||
* CSS and images) may not be available.
|
||||
*
|
||||
* <p>For more fine-grained notification of visual state updates, see {@link
|
||||
* WebView#postVisualStateCallback}.
|
||||
*
|
||||
* <p>Please note that all the conditions and recommendations applicable to
|
||||
* {@link WebView#postVisualStateCallback} also apply to this API.
|
||||
*
|
||||
* <p>This callback is only called for main frame navigations.
|
||||
*
|
||||
* @param view The {@link android.webkit.WebView} for which the navigation occurred.
|
||||
* @param url The URL corresponding to the page navigation that triggered this callback.
|
||||
*/
|
||||
public void onPageCommitVisible(WebView view, String url) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the host application of a resource request and allow the
|
||||
* application to return the data. If the return value is {@code null}, the WebView
|
||||
* will continue to load the resource as usual. Otherwise, the return
|
||||
* response and data will be used.
|
||||
*
|
||||
* <p>This callback is invoked for a variety of URL schemes (e.g., {@code http(s):}, {@code
|
||||
* data:}, {@code file:}, etc.), not only those schemes which send requests over the network.
|
||||
* This is not called for {@code javascript:} URLs, {@code blob:} URLs, or for assets accessed
|
||||
* via {@code file:///android_asset/} or {@code file:///android_res/} URLs.
|
||||
*
|
||||
* <p>In the case of redirects, this is only called for the initial resource URL, not any
|
||||
* subsequent redirect URLs.
|
||||
*
|
||||
* <p class="note"><b>Note:</b> This method is called on a thread
|
||||
* other than the UI thread so clients should exercise caution
|
||||
* when accessing private data or the view system.
|
||||
*
|
||||
* <p class="note"><b>Note:</b> When Safe Browsing is enabled, these URLs still undergo Safe
|
||||
* Browsing checks. If this is undesired, you can use {@link WebView#setSafeBrowsingWhitelist}
|
||||
* to skip Safe Browsing checks for that host or dismiss the warning in {@link
|
||||
* #onSafeBrowsingHit} by calling {@link SafeBrowsingResponse#proceed}.
|
||||
*
|
||||
* @param view The {@link android.webkit.WebView} that is requesting the
|
||||
* resource.
|
||||
* @param url The raw url of the resource.
|
||||
* @return A {@link android.webkit.WebResourceResponse} containing the
|
||||
* response information or {@code null} if the WebView should load the
|
||||
* resource itself.
|
||||
* @deprecated Use {@link #shouldInterceptRequest(WebView, WebResourceRequest)
|
||||
* shouldInterceptRequest(WebView, WebResourceRequest)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@Nullable
|
||||
public WebResourceResponse shouldInterceptRequest(WebView view,
|
||||
String url) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the host application of a resource request and allow the
|
||||
* application to return the data. If the return value is {@code null}, the WebView
|
||||
* will continue to load the resource as usual. Otherwise, the return
|
||||
* response and data will be used.
|
||||
*
|
||||
* <p>This callback is invoked for a variety of URL schemes (e.g., {@code http(s):}, {@code
|
||||
* data:}, {@code file:}, etc.), not only those schemes which send requests over the network.
|
||||
* This is not called for {@code javascript:} URLs, {@code blob:} URLs, or for assets accessed
|
||||
* via {@code file:///android_asset/} or {@code file:///android_res/} URLs.
|
||||
*
|
||||
* <p>In the case of redirects, this is only called for the initial resource URL, not any
|
||||
* subsequent redirect URLs.
|
||||
*
|
||||
* <p class="note"><b>Note:</b> This method is called on a thread
|
||||
* other than the UI thread so clients should exercise caution
|
||||
* when accessing private data or the view system.
|
||||
*
|
||||
* <p class="note"><b>Note:</b> When Safe Browsing is enabled, these URLs still undergo Safe
|
||||
* Browsing checks. If this is undesired, you can use {@link WebView#setSafeBrowsingWhitelist}
|
||||
* to skip Safe Browsing checks for that host or dismiss the warning in {@link
|
||||
* #onSafeBrowsingHit} by calling {@link SafeBrowsingResponse#proceed}.
|
||||
*
|
||||
* @param view The {@link android.webkit.WebView} that is requesting the
|
||||
* resource.
|
||||
* @param request Object containing the details of the request.
|
||||
* @return A {@link android.webkit.WebResourceResponse} containing the
|
||||
* response information or {@code null} if the WebView should load the
|
||||
* resource itself.
|
||||
*/
|
||||
@Nullable
|
||||
public WebResourceResponse shouldInterceptRequest(WebView view,
|
||||
WebResourceRequest request) {
|
||||
return shouldInterceptRequest(view, request.getUrl().toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the host application that there have been an excessive number of
|
||||
* HTTP redirects. As the host application if it would like to continue
|
||||
* trying to load the resource. The default behavior is to send the cancel
|
||||
* message.
|
||||
*
|
||||
* @param view The WebView that is initiating the callback.
|
||||
* @param cancelMsg The message to send if the host wants to cancel
|
||||
* @param continueMsg The message to send if the host wants to continue
|
||||
* @deprecated This method is no longer called. When the WebView encounters
|
||||
* a redirect loop, it will cancel the load.
|
||||
*/
|
||||
@Deprecated
|
||||
public void onTooManyRedirects(WebView view, Message cancelMsg,
|
||||
Message continueMsg) {
|
||||
cancelMsg.sendToTarget();
|
||||
}
|
||||
|
||||
// These ints must match up to the hidden values in EventHandler.
|
||||
/** Generic error */
|
||||
public static final int ERROR_UNKNOWN = -1;
|
||||
/** Server or proxy hostname lookup failed */
|
||||
public static final int ERROR_HOST_LOOKUP = -2;
|
||||
/** Unsupported authentication scheme (not basic or digest) */
|
||||
public static final int ERROR_UNSUPPORTED_AUTH_SCHEME = -3;
|
||||
/** User authentication failed on server */
|
||||
public static final int ERROR_AUTHENTICATION = -4;
|
||||
/** User authentication failed on proxy */
|
||||
public static final int ERROR_PROXY_AUTHENTICATION = -5;
|
||||
/** Failed to connect to the server */
|
||||
public static final int ERROR_CONNECT = -6;
|
||||
/** Failed to read or write to the server */
|
||||
public static final int ERROR_IO = -7;
|
||||
/** Connection timed out */
|
||||
public static final int ERROR_TIMEOUT = -8;
|
||||
/** Too many redirects */
|
||||
public static final int ERROR_REDIRECT_LOOP = -9;
|
||||
/** Unsupported URI scheme */
|
||||
public static final int ERROR_UNSUPPORTED_SCHEME = -10;
|
||||
/** Failed to perform SSL handshake */
|
||||
public static final int ERROR_FAILED_SSL_HANDSHAKE = -11;
|
||||
/** Malformed URL */
|
||||
public static final int ERROR_BAD_URL = -12;
|
||||
/** Generic file error */
|
||||
public static final int ERROR_FILE = -13;
|
||||
/** File not found */
|
||||
public static final int ERROR_FILE_NOT_FOUND = -14;
|
||||
/** Too many requests during this load */
|
||||
public static final int ERROR_TOO_MANY_REQUESTS = -15;
|
||||
/** Resource load was canceled by Safe Browsing */
|
||||
public static final int ERROR_UNSAFE_RESOURCE = -16;
|
||||
|
||||
/** @hide */
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface SafeBrowsingThreat {}
|
||||
|
||||
/** The resource was blocked for an unknown reason. */
|
||||
public static final int SAFE_BROWSING_THREAT_UNKNOWN = 0;
|
||||
/** The resource was blocked because it contains malware. */
|
||||
public static final int SAFE_BROWSING_THREAT_MALWARE = 1;
|
||||
/** The resource was blocked because it contains deceptive content. */
|
||||
public static final int SAFE_BROWSING_THREAT_PHISHING = 2;
|
||||
/** The resource was blocked because it contains unwanted software. */
|
||||
public static final int SAFE_BROWSING_THREAT_UNWANTED_SOFTWARE = 3;
|
||||
/**
|
||||
* The resource was blocked because it may trick the user into a billing agreement.
|
||||
*
|
||||
* <p>This constant is only used when targetSdkVersion is at least {@link
|
||||
* android.os.Build.VERSION_CODES#Q}. Otherwise, {@link #SAFE_BROWSING_THREAT_UNKNOWN} is used
|
||||
* instead.
|
||||
*/
|
||||
public static final int SAFE_BROWSING_THREAT_BILLING = 4;
|
||||
|
||||
/**
|
||||
* Report an error to the host application. These errors are unrecoverable
|
||||
* (i.e. the main resource is unavailable). The {@code errorCode} parameter
|
||||
* corresponds to one of the {@code ERROR_*} constants.
|
||||
* @param view The WebView that is initiating the callback.
|
||||
* @param errorCode The error code corresponding to an ERROR_* value.
|
||||
* @param description A String describing the error.
|
||||
* @param failingUrl The url that failed to load.
|
||||
* @deprecated Use {@link #onReceivedError(WebView, WebResourceRequest, WebResourceError)
|
||||
* onReceivedError(WebView, WebResourceRequest, WebResourceError)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public void onReceivedError(WebView view, int errorCode,
|
||||
String description, String failingUrl) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Report web resource loading error to the host application. These errors usually indicate
|
||||
* inability to connect to the server. Note that unlike the deprecated version of the callback,
|
||||
* the new version will be called for any resource (iframe, image, etc.), not just for the main
|
||||
* page. Thus, it is recommended to perform minimum required work in this callback.
|
||||
* @param view The WebView that is initiating the callback.
|
||||
* @param request The originating request.
|
||||
* @param error Information about the error occurred.
|
||||
*/
|
||||
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
|
||||
if (request.isForMainFrame()) {
|
||||
onReceivedError(view,
|
||||
error.getErrorCode(), error.getDescription().toString(),
|
||||
request.getUrl().toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the host application that an HTTP error has been received from the server while
|
||||
* loading a resource. HTTP errors have status codes >= 400. This callback will be called
|
||||
* for any resource (iframe, image, etc.), not just for the main page. Thus, it is recommended
|
||||
* to perform minimum required work in this callback. Note that the content of the server
|
||||
* response may not be provided within the {@code errorResponse} parameter.
|
||||
* @param view The WebView that is initiating the callback.
|
||||
* @param request The originating request.
|
||||
* @param errorResponse Information about the error occurred.
|
||||
*/
|
||||
public void onReceivedHttpError(
|
||||
WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
|
||||
}
|
||||
|
||||
/**
|
||||
* As the host application if the browser should resend data as the
|
||||
* requested page was a result of a POST. The default is to not resend the
|
||||
* data.
|
||||
*
|
||||
* @param view The WebView that is initiating the callback.
|
||||
* @param dontResend The message to send if the browser should not resend
|
||||
* @param resend The message to send if the browser should resend data
|
||||
*/
|
||||
public void onFormResubmission(WebView view, Message dontResend,
|
||||
Message resend) {
|
||||
dontResend.sendToTarget();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the host application to update its visited links database.
|
||||
*
|
||||
* @param view The WebView that is initiating the callback.
|
||||
* @param url The url being visited.
|
||||
* @param isReload {@code true} if this url is being reloaded.
|
||||
*/
|
||||
public void doUpdateVisitedHistory(WebView view, String url,
|
||||
boolean isReload) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the host application that an SSL error occurred while loading a
|
||||
* resource. The host application must call either
|
||||
* {@link SslErrorHandler#cancel()} or {@link SslErrorHandler#proceed()}.
|
||||
*
|
||||
* <p class="warning"><b>Warning:</b> Application overrides of this method
|
||||
* can be used to display custom error pages or to silently log issues, but
|
||||
* the host application should always call {@code SslErrorHandler#cancel()}
|
||||
* and never proceed past errors.
|
||||
*
|
||||
* <p class="note"><b>Note:</b> Do not prompt the user about SSL errors.
|
||||
* Users are unlikely to be able to make an informed security decision, and
|
||||
* {@code WebView} does not provide a UI for showing the details of the
|
||||
* error in a meaningful way.
|
||||
*
|
||||
* <p>The decision to call {@code proceed()} or {@code cancel()} may be
|
||||
* retained to facilitate responses to future SSL errors. The default
|
||||
* behavior is to cancel the resource loading process.
|
||||
*
|
||||
* <p>This API is called only for recoverable SSL certificate errors. For
|
||||
* non-recoverable errors (such as when the server fails the client), the
|
||||
* {@code WebView} calls {@link #onReceivedError(WebView,
|
||||
* WebResourceRequest, WebResourceError) onReceivedError(WebView,
|
||||
* WebResourceRequest, WebResourceError)} with the
|
||||
* {@link #ERROR_FAILED_SSL_HANDSHAKE} argument.
|
||||
*
|
||||
* @param view {@code WebView} that initiated the callback.
|
||||
* @param handler {@link SslErrorHandler} that handles the user's response.
|
||||
* @param error SSL error object.
|
||||
*/
|
||||
public void onReceivedSslError(WebView view, SslErrorHandler handler,
|
||||
SslError error) {
|
||||
handler.cancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the host application to handle a SSL client certificate request. The host application
|
||||
* is responsible for showing the UI if desired and providing the keys. There are three ways to
|
||||
* respond: {@link ClientCertRequest#proceed}, {@link ClientCertRequest#cancel}, or {@link
|
||||
* ClientCertRequest#ignore}. Webview stores the response in memory (for the life of the
|
||||
* application) if {@link ClientCertRequest#proceed} or {@link ClientCertRequest#cancel} is
|
||||
* called and does not call {@code onReceivedClientCertRequest()} again for the same host and
|
||||
* port pair. Webview does not store the response if {@link ClientCertRequest#ignore}
|
||||
* is called. Note that, multiple layers in chromium network stack might be
|
||||
* caching the responses, so the behavior for ignore is only a best case
|
||||
* effort.
|
||||
*
|
||||
* This method is called on the UI thread. During the callback, the
|
||||
* connection is suspended.
|
||||
*
|
||||
* For most use cases, the application program should implement the
|
||||
* {@link android.security.KeyChainAliasCallback} interface and pass it to
|
||||
* {@link android.security.KeyChain#choosePrivateKeyAlias} to start an
|
||||
* activity for the user to choose the proper alias. The keychain activity will
|
||||
* provide the alias through the callback method in the implemented interface. Next
|
||||
* the application should create an async task to call
|
||||
* {@link android.security.KeyChain#getPrivateKey} to receive the key.
|
||||
*
|
||||
* An example implementation of client certificates can be seen at
|
||||
* <A href="https://android.googlesource.com/platform/packages/apps/Browser/+/android-5.1.1_r1/src/com/android/browser/Tab.java">
|
||||
* AOSP Browser</a>
|
||||
*
|
||||
* The default behavior is to cancel, returning no client certificate.
|
||||
*
|
||||
* @param view The WebView that is initiating the callback
|
||||
* @param request An instance of a {@link ClientCertRequest}
|
||||
*
|
||||
*/
|
||||
public void onReceivedClientCertRequest(WebView view, ClientCertRequest request) {
|
||||
request.cancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies the host application that the WebView received an HTTP
|
||||
* authentication request. The host application can use the supplied
|
||||
* {@link HttpAuthHandler} to set the WebView's response to the request.
|
||||
* The default behavior is to cancel the request.
|
||||
*
|
||||
* <p class="note"><b>Note:</b> The supplied HttpAuthHandler must be used on
|
||||
* the UI thread.
|
||||
*
|
||||
* @param view the WebView that is initiating the callback
|
||||
* @param handler the HttpAuthHandler used to set the WebView's response
|
||||
* @param host the host requiring authentication
|
||||
* @param realm the realm for which authentication is required
|
||||
* @see WebView#getHttpAuthUsernamePassword
|
||||
*/
|
||||
public void onReceivedHttpAuthRequest(WebView view,
|
||||
HttpAuthHandler handler, String host, String realm) {
|
||||
handler.cancel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Give the host application a chance to handle the key event synchronously.
|
||||
* e.g. menu shortcut key events need to be filtered this way. If return
|
||||
* true, WebView will not handle the key event. If return {@code false}, WebView
|
||||
* will always handle the key event, so none of the super in the view chain
|
||||
* will see the key event. The default behavior returns {@code false}.
|
||||
*
|
||||
* @param view The WebView that is initiating the callback.
|
||||
* @param event The key event.
|
||||
* @return {@code true} if the host application wants to handle the key event
|
||||
* itself, otherwise return {@code false}
|
||||
*/
|
||||
public boolean shouldOverrideKeyEvent(WebView view, KeyEvent event) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the host application that a key was not handled by the WebView.
|
||||
* Except system keys, WebView always consumes the keys in the normal flow
|
||||
* or if {@link #shouldOverrideKeyEvent} returns {@code true}. This is called asynchronously
|
||||
* from where the key is dispatched. It gives the host application a chance
|
||||
* to handle the unhandled key events.
|
||||
*
|
||||
* @param view The WebView that is initiating the callback.
|
||||
* @param event The key event.
|
||||
*/
|
||||
public void onUnhandledKeyEvent(WebView view, KeyEvent event) {
|
||||
onUnhandledInputEventInternal(view, event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the host application that a input event was not handled by the WebView.
|
||||
* Except system keys, WebView always consumes input events in the normal flow
|
||||
* or if {@link #shouldOverrideKeyEvent} returns {@code true}. This is called asynchronously
|
||||
* from where the event is dispatched. It gives the host application a chance
|
||||
* to handle the unhandled input events.
|
||||
*
|
||||
* Note that if the event is a {@link android.view.MotionEvent}, then it's lifetime is only
|
||||
* that of the function call. If the WebViewClient wishes to use the event beyond that, then it
|
||||
* <i>must</i> create a copy of the event.
|
||||
*
|
||||
* It is the responsibility of overriders of this method to call
|
||||
* {@link #onUnhandledKeyEvent(WebView, KeyEvent)}
|
||||
* when appropriate if they wish to continue receiving events through it.
|
||||
*
|
||||
* @param view The WebView that is initiating the callback.
|
||||
* @param event The input event.
|
||||
* @removed
|
||||
*/
|
||||
public void onUnhandledInputEvent(WebView view, InputEvent event) {
|
||||
if (event instanceof KeyEvent) {
|
||||
onUnhandledKeyEvent(view, (KeyEvent) event);
|
||||
return;
|
||||
}
|
||||
onUnhandledInputEventInternal(view, event);
|
||||
}
|
||||
|
||||
private void onUnhandledInputEventInternal(WebView view, InputEvent event) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the host application that the scale applied to the WebView has
|
||||
* changed.
|
||||
*
|
||||
* @param view The WebView that is initiating the callback.
|
||||
* @param oldScale The old scale factor
|
||||
* @param newScale The new scale factor
|
||||
*/
|
||||
public void onScaleChanged(WebView view, float oldScale, float newScale) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the host application that a request to automatically log in the
|
||||
* user has been processed.
|
||||
* @param view The WebView requesting the login.
|
||||
* @param realm The account realm used to look up accounts.
|
||||
* @param account An optional account. If not {@code null}, the account should be
|
||||
* checked against accounts on the device. If it is a valid
|
||||
* account, it should be used to log in the user.
|
||||
* @param args Authenticator specific arguments used to log in the user.
|
||||
*/
|
||||
public void onReceivedLoginRequest(WebView view, String realm,
|
||||
@Nullable String account, String args) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify host application that the given WebView's render process has exited.
|
||||
*
|
||||
* Multiple WebView instances may be associated with a single render process;
|
||||
* onRenderProcessGone will be called for each WebView that was affected.
|
||||
* The application's implementation of this callback should only attempt to
|
||||
* clean up the specific WebView given as a parameter, and should not assume
|
||||
* that other WebView instances are affected.
|
||||
*
|
||||
* The given WebView can't be used, and should be removed from the view hierarchy,
|
||||
* all references to it should be cleaned up, e.g any references in the Activity
|
||||
* or other classes saved using {@link android.view.View#findViewById} and similar calls, etc.
|
||||
*
|
||||
* To cause an render process crash for test purpose, the application can
|
||||
* call {@code loadUrl("chrome://crash")} on the WebView. Note that multiple WebView
|
||||
* instances may be affected if they share a render process, not just the
|
||||
* specific WebView which loaded chrome://crash.
|
||||
*
|
||||
* @param view The WebView which needs to be cleaned up.
|
||||
* @param detail the reason why it exited.
|
||||
* @return {@code true} if the host application handled the situation that process has
|
||||
* exited, otherwise, application will crash if render process crashed,
|
||||
* or be killed if render process was killed by the system.
|
||||
*/
|
||||
public boolean onRenderProcessGone(WebView view, RenderProcessGoneDetail detail) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the host application that a loading URL has been flagged by Safe Browsing.
|
||||
*
|
||||
* The application must invoke the callback to indicate the preferred response. The default
|
||||
* behavior is to show an interstitial to the user, with the reporting checkbox visible.
|
||||
*
|
||||
* If the application needs to show its own custom interstitial UI, the callback can be invoked
|
||||
* asynchronously with {@link SafeBrowsingResponse#backToSafety} or {@link
|
||||
* SafeBrowsingResponse#proceed}, depending on user response.
|
||||
*
|
||||
* @param view The WebView that hit the malicious resource.
|
||||
* @param request Object containing the details of the request.
|
||||
* @param threatType The reason the resource was caught by Safe Browsing, corresponding to a
|
||||
* {@code SAFE_BROWSING_THREAT_*} value.
|
||||
* @param callback Applications must invoke one of the callback methods.
|
||||
*/
|
||||
public void onSafeBrowsingHit(WebView view, WebResourceRequest request,
|
||||
@SafeBrowsingThreat int threatType, SafeBrowsingResponse callback) {
|
||||
callback.showInterstitial(/* allowReporting */ true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,541 @@
|
||||
/*
|
||||
* Copyright (C) 2012 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 android.webkit;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.SystemApi;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Picture;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.net.http.SslCertificate;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.print.PrintDocumentAdapter;
|
||||
import android.util.LongSparseArray;
|
||||
import android.util.SparseArray;
|
||||
import android.view.DragEvent;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.PointerIcon;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup.LayoutParams;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.view.accessibility.AccessibilityNodeProvider;
|
||||
import android.view.autofill.AutofillId;
|
||||
import android.view.autofill.AutofillValue;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
import android.view.textclassifier.TextClassifier;
|
||||
import android.webkit.WebView.HitTestResult;
|
||||
import android.webkit.WebView.PictureListener;
|
||||
import android.webkit.WebView.VisualStateCallback;
|
||||
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* WebView backend provider interface: this interface is the abstract backend to a WebView
|
||||
* instance; each WebView object is bound to exactly one WebViewProvider object which implements
|
||||
* the runtime behavior of that WebView.
|
||||
*
|
||||
* All methods must behave as per their namesake in {@link WebView}, unless otherwise noted.
|
||||
*
|
||||
* @hide Not part of the public API; only required by system implementors.
|
||||
*/
|
||||
@SystemApi
|
||||
public interface WebViewProvider {
|
||||
//-------------------------------------------------------------------------
|
||||
// Main interface for backend provider of the WebView class.
|
||||
//-------------------------------------------------------------------------
|
||||
/**
|
||||
* Initialize this WebViewProvider instance. Called after the WebView has fully constructed.
|
||||
* @param javaScriptInterfaces is a Map of interface names, as keys, and
|
||||
* object implementing those interfaces, as values.
|
||||
* @param privateBrowsing If {@code true} the web view will be initialized in private /
|
||||
* incognito mode.
|
||||
*/
|
||||
public void init(Map<String, Object> javaScriptInterfaces,
|
||||
boolean privateBrowsing);
|
||||
|
||||
// Deprecated - should never be called
|
||||
public void setHorizontalScrollbarOverlay(boolean overlay);
|
||||
|
||||
// Deprecated - should never be called
|
||||
public void setVerticalScrollbarOverlay(boolean overlay);
|
||||
|
||||
// Deprecated - should never be called
|
||||
public boolean overlayHorizontalScrollbar();
|
||||
|
||||
// Deprecated - should never be called
|
||||
public boolean overlayVerticalScrollbar();
|
||||
|
||||
public int getVisibleTitleHeight();
|
||||
|
||||
public SslCertificate getCertificate();
|
||||
|
||||
public void setCertificate(SslCertificate certificate);
|
||||
|
||||
public void savePassword(String host, String username, String password);
|
||||
|
||||
public void setHttpAuthUsernamePassword(String host, String realm,
|
||||
String username, String password);
|
||||
|
||||
public String[] getHttpAuthUsernamePassword(String host, String realm);
|
||||
|
||||
/**
|
||||
* See {@link WebView#destroy()}.
|
||||
* As well as releasing the internal state and resources held by the implementation,
|
||||
* the provider should null all references it holds on the WebView proxy class, and ensure
|
||||
* no further method calls are made to it.
|
||||
*/
|
||||
public void destroy();
|
||||
|
||||
public void setNetworkAvailable(boolean networkUp);
|
||||
|
||||
public WebBackForwardList saveState(Bundle outState);
|
||||
|
||||
public boolean savePicture(Bundle b, final File dest);
|
||||
|
||||
public boolean restorePicture(Bundle b, File src);
|
||||
|
||||
public WebBackForwardList restoreState(Bundle inState);
|
||||
|
||||
public void loadUrl(String url, Map<String, String> additionalHttpHeaders);
|
||||
|
||||
public void loadUrl(String url);
|
||||
|
||||
public void postUrl(String url, byte[] postData);
|
||||
|
||||
public void loadData(String data, String mimeType, String encoding);
|
||||
|
||||
public void loadDataWithBaseURL(String baseUrl, String data,
|
||||
String mimeType, String encoding, String historyUrl);
|
||||
|
||||
public void evaluateJavaScript(String script, ValueCallback<String> resultCallback);
|
||||
|
||||
public void saveWebArchive(String filename);
|
||||
|
||||
public void saveWebArchive(String basename, boolean autoname, ValueCallback<String> callback);
|
||||
|
||||
public void stopLoading();
|
||||
|
||||
public void reload();
|
||||
|
||||
public boolean canGoBack();
|
||||
|
||||
public void goBack();
|
||||
|
||||
public boolean canGoForward();
|
||||
|
||||
public void goForward();
|
||||
|
||||
public boolean canGoBackOrForward(int steps);
|
||||
|
||||
public void goBackOrForward(int steps);
|
||||
|
||||
public boolean isPrivateBrowsingEnabled();
|
||||
|
||||
public boolean pageUp(boolean top);
|
||||
|
||||
public boolean pageDown(boolean bottom);
|
||||
|
||||
public void insertVisualStateCallback(long requestId, VisualStateCallback callback);
|
||||
|
||||
public void clearView();
|
||||
|
||||
public Picture capturePicture();
|
||||
|
||||
public PrintDocumentAdapter createPrintDocumentAdapter(String documentName);
|
||||
|
||||
public float getScale();
|
||||
|
||||
public void setInitialScale(int scaleInPercent);
|
||||
|
||||
public void invokeZoomPicker();
|
||||
|
||||
public HitTestResult getHitTestResult();
|
||||
|
||||
public void requestFocusNodeHref(Message hrefMsg);
|
||||
|
||||
public void requestImageRef(Message msg);
|
||||
|
||||
public String getUrl();
|
||||
|
||||
public String getOriginalUrl();
|
||||
|
||||
public String getTitle();
|
||||
|
||||
public Bitmap getFavicon();
|
||||
|
||||
public String getTouchIconUrl();
|
||||
|
||||
public int getProgress();
|
||||
|
||||
public int getContentHeight();
|
||||
|
||||
public int getContentWidth();
|
||||
|
||||
public void pauseTimers();
|
||||
|
||||
public void resumeTimers();
|
||||
|
||||
public void onPause();
|
||||
|
||||
public void onResume();
|
||||
|
||||
public boolean isPaused();
|
||||
|
||||
public void freeMemory();
|
||||
|
||||
public void clearCache(boolean includeDiskFiles);
|
||||
|
||||
public void clearFormData();
|
||||
|
||||
public void clearHistory();
|
||||
|
||||
public void clearSslPreferences();
|
||||
|
||||
public WebBackForwardList copyBackForwardList();
|
||||
|
||||
public void setFindListener(WebView.FindListener listener);
|
||||
|
||||
public void findNext(boolean forward);
|
||||
|
||||
public int findAll(String find);
|
||||
|
||||
public void findAllAsync(String find);
|
||||
|
||||
public boolean showFindDialog(String text, boolean showIme);
|
||||
|
||||
public void clearMatches();
|
||||
|
||||
public void documentHasImages(Message response);
|
||||
|
||||
public void setWebViewClient(WebViewClient client);
|
||||
|
||||
public WebViewClient getWebViewClient();
|
||||
|
||||
@Nullable
|
||||
public WebViewRenderProcess getWebViewRenderProcess();
|
||||
|
||||
public void setWebViewRenderProcessClient(
|
||||
@Nullable Executor executor,
|
||||
@Nullable WebViewRenderProcessClient client);
|
||||
|
||||
@Nullable
|
||||
public WebViewRenderProcessClient getWebViewRenderProcessClient();
|
||||
|
||||
public void setDownloadListener(DownloadListener listener);
|
||||
|
||||
public void setWebChromeClient(WebChromeClient client);
|
||||
|
||||
public WebChromeClient getWebChromeClient();
|
||||
|
||||
public void setPictureListener(PictureListener listener);
|
||||
|
||||
public void addJavascriptInterface(Object obj, String interfaceName);
|
||||
|
||||
public void removeJavascriptInterface(String interfaceName);
|
||||
|
||||
public WebMessagePort[] createWebMessageChannel();
|
||||
|
||||
public void postMessageToMainFrame(WebMessage message, Uri targetOrigin);
|
||||
|
||||
public WebSettings getSettings();
|
||||
|
||||
public void setMapTrackballToArrowKeys(boolean setMap);
|
||||
|
||||
public void flingScroll(int vx, int vy);
|
||||
|
||||
public View getZoomControls();
|
||||
|
||||
public boolean canZoomIn();
|
||||
|
||||
public boolean canZoomOut();
|
||||
|
||||
public boolean zoomBy(float zoomFactor);
|
||||
|
||||
public boolean zoomIn();
|
||||
|
||||
public boolean zoomOut();
|
||||
|
||||
public void dumpViewHierarchyWithProperties(BufferedWriter out, int level);
|
||||
|
||||
public View findHierarchyView(String className, int hashCode);
|
||||
|
||||
public void setRendererPriorityPolicy(int rendererRequestedPriority, boolean waivedWhenNotVisible);
|
||||
|
||||
public int getRendererRequestedPriority();
|
||||
|
||||
public boolean getRendererPriorityWaivedWhenNotVisible();
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public default void setTextClassifier(@Nullable TextClassifier textClassifier) {}
|
||||
|
||||
@NonNull
|
||||
public default TextClassifier getTextClassifier() { return TextClassifier.NO_OP; }
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Provider internal methods
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @return the ViewDelegate implementation. This provides the functionality to back all of
|
||||
* the name-sake functions from the View and ViewGroup base classes of WebView.
|
||||
*/
|
||||
/* package */ ViewDelegate getViewDelegate();
|
||||
|
||||
/**
|
||||
* @return a ScrollDelegate implementation. Normally this would be same object as is
|
||||
* returned by getViewDelegate().
|
||||
*/
|
||||
/* package */ ScrollDelegate getScrollDelegate();
|
||||
|
||||
/**
|
||||
* Only used by FindActionModeCallback to inform providers that the find dialog has
|
||||
* been dismissed.
|
||||
*/
|
||||
public void notifyFindDialogDismissed();
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// View / ViewGroup delegation methods
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Provides mechanism for the name-sake methods declared in View and ViewGroup to be delegated
|
||||
* into the WebViewProvider instance.
|
||||
* NOTE: For many of these methods, the WebView will provide a super.Foo() call before or after
|
||||
* making the call into the provider instance. This is done for convenience in the common case
|
||||
* of maintaining backward compatibility. For remaining super class calls (e.g. where the
|
||||
* provider may need to only conditionally make the call based on some internal state) see the
|
||||
* {@link WebView.PrivateAccess} callback class.
|
||||
*/
|
||||
// TODO: See if the pattern of the super-class calls can be rationalized at all, and document
|
||||
// the remainder on the methods below.
|
||||
interface ViewDelegate {
|
||||
public boolean shouldDelayChildPressedState();
|
||||
|
||||
public void onProvideVirtualStructure(android.view.ViewStructure structure);
|
||||
|
||||
default void onProvideAutofillVirtualStructure(
|
||||
@SuppressWarnings("unused") android.view.ViewStructure structure,
|
||||
@SuppressWarnings("unused") int flags) {
|
||||
}
|
||||
|
||||
default void autofill(@SuppressWarnings("unused") SparseArray<AutofillValue> values) {
|
||||
}
|
||||
|
||||
default boolean isVisibleToUserForAutofill(@SuppressWarnings("unused") int virtualId) {
|
||||
return true; // true is the default value returned by View.isVisibleToUserForAutofill()
|
||||
}
|
||||
|
||||
default void onProvideContentCaptureStructure(
|
||||
@NonNull @SuppressWarnings("unused") android.view.ViewStructure structure,
|
||||
@SuppressWarnings("unused") int flags) {
|
||||
}
|
||||
|
||||
// @SuppressLint("NullableCollection")
|
||||
// default void onCreateVirtualViewTranslationRequests(
|
||||
// @NonNull @SuppressWarnings("unused") long[] virtualIds,
|
||||
// @NonNull @SuppressWarnings("unused") @DataFormat int[] supportedFormats,
|
||||
// @NonNull @SuppressWarnings("unused")
|
||||
// Consumer<ViewTranslationRequest> requestsCollector) {
|
||||
// }
|
||||
|
||||
// default void onVirtualViewTranslationResponses(
|
||||
// @NonNull @SuppressWarnings("unused")
|
||||
// LongSparseArray<ViewTranslationResponse> response) {
|
||||
// }
|
||||
|
||||
// default void dispatchCreateViewTranslationRequest(
|
||||
// @NonNull @SuppressWarnings("unused") Map<AutofillId, long[]> viewIds,
|
||||
// @NonNull @SuppressWarnings("unused") @DataFormat int[] supportedFormats,
|
||||
// @Nullable @SuppressWarnings("unused") TranslationCapability capability,
|
||||
// @NonNull @SuppressWarnings("unused") List<ViewTranslationRequest> requests) {
|
||||
|
||||
// }
|
||||
|
||||
public AccessibilityNodeProvider getAccessibilityNodeProvider();
|
||||
|
||||
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info);
|
||||
|
||||
public void onInitializeAccessibilityEvent(AccessibilityEvent event);
|
||||
|
||||
public boolean performAccessibilityAction(int action, Bundle arguments);
|
||||
|
||||
public void setOverScrollMode(int mode);
|
||||
|
||||
public void setScrollBarStyle(int style);
|
||||
|
||||
public void onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar, int l, int t,
|
||||
int r, int b);
|
||||
|
||||
public void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY);
|
||||
|
||||
public void onWindowVisibilityChanged(int visibility);
|
||||
|
||||
public void onDraw(Canvas canvas);
|
||||
|
||||
public void setLayoutParams(LayoutParams layoutParams);
|
||||
|
||||
public boolean performLongClick();
|
||||
|
||||
public void onConfigurationChanged(Configuration newConfig);
|
||||
|
||||
public InputConnection onCreateInputConnection(EditorInfo outAttrs);
|
||||
|
||||
public boolean onDragEvent(DragEvent event);
|
||||
|
||||
public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event);
|
||||
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event);
|
||||
|
||||
public boolean onKeyUp(int keyCode, KeyEvent event);
|
||||
|
||||
public void onAttachedToWindow();
|
||||
|
||||
public void onDetachedFromWindow();
|
||||
|
||||
public default void onMovedToDisplay(int displayId, Configuration config) {}
|
||||
|
||||
public void onVisibilityChanged(View changedView, int visibility);
|
||||
|
||||
public void onWindowFocusChanged(boolean hasWindowFocus);
|
||||
|
||||
public void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect);
|
||||
|
||||
public boolean setFrame(int left, int top, int right, int bottom);
|
||||
|
||||
public void onSizeChanged(int w, int h, int ow, int oh);
|
||||
|
||||
public void onScrollChanged(int l, int t, int oldl, int oldt);
|
||||
|
||||
public boolean dispatchKeyEvent(KeyEvent event);
|
||||
|
||||
public boolean onTouchEvent(MotionEvent ev);
|
||||
|
||||
public boolean onHoverEvent(MotionEvent event);
|
||||
|
||||
public boolean onGenericMotionEvent(MotionEvent event);
|
||||
|
||||
public boolean onTrackballEvent(MotionEvent ev);
|
||||
|
||||
public boolean requestFocus(int direction, Rect previouslyFocusedRect);
|
||||
|
||||
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec);
|
||||
|
||||
public boolean requestChildRectangleOnScreen(View child, Rect rect, boolean immediate);
|
||||
|
||||
public void setBackgroundColor(int color);
|
||||
|
||||
public void setLayerType(int layerType, Paint paint);
|
||||
|
||||
public void preDispatchDraw(Canvas canvas);
|
||||
|
||||
public void onStartTemporaryDetach();
|
||||
|
||||
public void onFinishTemporaryDetach();
|
||||
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data);
|
||||
|
||||
public Handler getHandler(Handler originalHandler);
|
||||
|
||||
public View findFocus(View originalFocusedView);
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
default boolean onCheckIsTextEditor() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see View#onApplyWindowInsets(WindowInsets).
|
||||
*
|
||||
* <p>This is the entry point for the WebView implementation to override. It returns
|
||||
* {@code null} when the WebView implementation hasn't implemented the WindowInsets support
|
||||
* on S yet. In this case, the {@link View#onApplyWindowInsets()} super method will be
|
||||
* called instead.
|
||||
*
|
||||
* @param insets Insets to apply
|
||||
* @return The supplied insets with any applied insets consumed.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
@Nullable
|
||||
default WindowInsets onApplyWindowInsets(@Nullable WindowInsets insets) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide Only used by WebView.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
@Nullable
|
||||
default PointerIcon onResolvePointerIcon(@NonNull MotionEvent event, int pointerIndex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
interface ScrollDelegate {
|
||||
// These methods are declared protected in the ViewGroup base class. This interface
|
||||
// exists to promote them to public so they may be called by the WebView proxy class.
|
||||
// TODO: Combine into ViewDelegate?
|
||||
/**
|
||||
* See {@link android.webkit.WebView#computeHorizontalScrollRange}
|
||||
*/
|
||||
public int computeHorizontalScrollRange();
|
||||
|
||||
/**
|
||||
* See {@link android.webkit.WebView#computeHorizontalScrollOffset}
|
||||
*/
|
||||
public int computeHorizontalScrollOffset();
|
||||
|
||||
/**
|
||||
* See {@link android.webkit.WebView#computeVerticalScrollRange}
|
||||
*/
|
||||
public int computeVerticalScrollRange();
|
||||
|
||||
/**
|
||||
* See {@link android.webkit.WebView#computeVerticalScrollOffset}
|
||||
*/
|
||||
public int computeVerticalScrollOffset();
|
||||
|
||||
/**
|
||||
* See {@link android.webkit.WebView#computeVerticalScrollExtent}
|
||||
*/
|
||||
public int computeVerticalScrollExtent();
|
||||
|
||||
/**
|
||||
* See {@link android.webkit.WebView#computeScroll}
|
||||
*/
|
||||
public void computeScroll();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright (C) 2006 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 android.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.RemoteViews.RemoteView;
|
||||
|
||||
|
||||
@Deprecated
|
||||
@RemoteView
|
||||
public class AbsoluteLayout extends ViewGroup {
|
||||
public AbsoluteLayout(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public AbsoluteLayout(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public AbsoluteLayout(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
this(context, attrs, defStyleAttr, 0);
|
||||
}
|
||||
|
||||
public AbsoluteLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
|
||||
return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t,
|
||||
int r, int b) {
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
|
||||
return new AbsoluteLayout.LayoutParams(getContext(), attrs);
|
||||
}
|
||||
|
||||
// Override to allow type-checking of LayoutParams.
|
||||
@Override
|
||||
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
|
||||
return p instanceof AbsoluteLayout.LayoutParams;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
|
||||
return new LayoutParams(p);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldDelayChildPressedState() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static class LayoutParams extends ViewGroup.LayoutParams {
|
||||
public int x;
|
||||
public int y;
|
||||
|
||||
public LayoutParams(int width, int height, int x, int y) {
|
||||
super(width, height);
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
public LayoutParams(Context c, AttributeSet attrs) {
|
||||
super(c, attrs);
|
||||
throw new RuntimeException("Stub!");
|
||||
}
|
||||
|
||||
public LayoutParams(ViewGroup.LayoutParams source) {
|
||||
super(source);
|
||||
}
|
||||
|
||||
public String debug(String output) {
|
||||
return output + "Absolute.LayoutParams={width="
|
||||
+ String.valueOf(width) + ", height=" + String.valueOf(height)
|
||||
+ " x=" + x + " y=" + y + "}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ public class Preference {
|
||||
protected Context context;
|
||||
|
||||
private boolean isVisible;
|
||||
private boolean isEnabled = true;
|
||||
private String key;
|
||||
private CharSequence title;
|
||||
private CharSequence summary;
|
||||
@@ -68,7 +69,11 @@ public class Preference {
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
throw new RuntimeException("Stub!");
|
||||
isEnabled = enabled;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return isEnabled;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
|
||||
@@ -11,7 +11,8 @@ import android.content.Context;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
||||
public class TwoStatePreference extends Preference {
|
||||
// Note: remove @JsonIgnore and implement methods if any extension ever uses these methods or the variables behind them
|
||||
private CharSequence mSummaryOn;
|
||||
private CharSequence mSummaryOff;
|
||||
|
||||
public TwoStatePreference(Context context) {
|
||||
super(context);
|
||||
@@ -25,16 +26,41 @@ public class TwoStatePreference extends Preference {
|
||||
public void setChecked(boolean checked) { throw new RuntimeException("Stub!"); }
|
||||
|
||||
@JsonIgnore
|
||||
public CharSequence getSummaryOn() { throw new RuntimeException("Stub!"); }
|
||||
public CharSequence getSummaryOn() {
|
||||
return mSummaryOn;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public void setSummaryOn(CharSequence summary) { throw new RuntimeException("Stub!"); }
|
||||
public void setSummaryOn(CharSequence summary) {
|
||||
this.mSummaryOn = summary;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public CharSequence getSummaryOff() { throw new RuntimeException("Stub!"); }
|
||||
public CharSequence getSummaryOff() {
|
||||
return mSummaryOff;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public void setSummaryOff(CharSequence summary) { throw new RuntimeException("Stub!"); }
|
||||
public void setSummaryOff(CharSequence summary) {
|
||||
this.mSummaryOff = summary;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getSummary() {
|
||||
final CharSequence summary = super.getSummary();
|
||||
if (summary != null) {
|
||||
return summary;
|
||||
}
|
||||
|
||||
final boolean checked = (Boolean) getCurrentValue();
|
||||
if (checked && mSummaryOn != null) {
|
||||
return mSummaryOn;
|
||||
} else if (!checked && mSummaryOff != null) {
|
||||
return mSummaryOff;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public boolean getDisableDependentsState() { throw new RuntimeException("Stub!"); }
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.internal.util;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.util.ArraySet;
|
||||
import libcore.util.EmptyArray;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.*;
|
||||
import libcore.util.EmptyArray;
|
||||
import android.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* ArrayUtils contains some methods that you can call to find out
|
||||
* the most efficient increments by which to grow arrays.
|
||||
@@ -50,6 +50,10 @@ public class ArrayUtils {
|
||||
public static Object[] newUnpaddedObjectArray(int minLen) {
|
||||
return new Object[minLen];
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T[] newUnpaddedArray(Class<T> clazz, int minLen) {
|
||||
return (T[])Array.newInstance(clazz, minLen);
|
||||
}
|
||||
/**
|
||||
* Checks if the beginnings of two byte arrays are equal.
|
||||
*
|
||||
@@ -468,4 +472,4 @@ public class ArrayUtils {
|
||||
}
|
||||
return size - leftIdx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.android.internal.util;
|
||||
|
||||
public final class GrowingArrayUtils {
|
||||
|
||||
public static <T> T[] append(T[] array, int currentSize, T element) {
|
||||
assert currentSize <= array.length;
|
||||
|
||||
if (currentSize + 1 > array.length) {
|
||||
@SuppressWarnings("unchecked")
|
||||
T[] newArray = ArrayUtils.newUnpaddedArray(
|
||||
(Class<T>) array.getClass().getComponentType(), growSize(currentSize));
|
||||
System.arraycopy(array, 0, newArray, 0, currentSize);
|
||||
array = newArray;
|
||||
}
|
||||
array[currentSize] = element;
|
||||
return array;
|
||||
}
|
||||
|
||||
public static int[] append(int[] array, int currentSize, int element) {
|
||||
assert currentSize <= array.length;
|
||||
|
||||
if (currentSize + 1 > array.length) {
|
||||
int[] newArray = ArrayUtils.newUnpaddedIntArray(growSize(currentSize));
|
||||
System.arraycopy(array, 0, newArray, 0, currentSize);
|
||||
array = newArray;
|
||||
}
|
||||
array[currentSize] = element;
|
||||
return array;
|
||||
}
|
||||
|
||||
public static long[] append(long[] array, int currentSize, long element) {
|
||||
assert currentSize <= array.length;
|
||||
|
||||
if (currentSize + 1 > array.length) {
|
||||
long[] newArray = ArrayUtils.newUnpaddedLongArray(growSize(currentSize));
|
||||
System.arraycopy(array, 0, newArray, 0, currentSize);
|
||||
array = newArray;
|
||||
}
|
||||
array[currentSize] = element;
|
||||
return array;
|
||||
}
|
||||
|
||||
public static boolean[] append(boolean[] array, int currentSize, boolean element) {
|
||||
assert currentSize <= array.length;
|
||||
|
||||
if (currentSize + 1 > array.length) {
|
||||
boolean[] newArray = ArrayUtils.newUnpaddedBooleanArray(growSize(currentSize));
|
||||
System.arraycopy(array, 0, newArray, 0, currentSize);
|
||||
array = newArray;
|
||||
}
|
||||
array[currentSize] = element;
|
||||
return array;
|
||||
}
|
||||
|
||||
public static float[] append(float[] array, int currentSize, float element) {
|
||||
assert currentSize <= array.length;
|
||||
|
||||
if (currentSize + 1 > array.length) {
|
||||
float[] newArray = ArrayUtils.newUnpaddedFloatArray(growSize(currentSize));
|
||||
System.arraycopy(array, 0, newArray, 0, currentSize);
|
||||
array = newArray;
|
||||
}
|
||||
array[currentSize] = element;
|
||||
return array;
|
||||
}
|
||||
|
||||
public static <T> T[] insert(T[] array, int currentSize, int index, T element) {
|
||||
assert currentSize <= array.length;
|
||||
|
||||
if (currentSize + 1 <= array.length) {
|
||||
System.arraycopy(array, index, array, index + 1, currentSize - index);
|
||||
array[index] = element;
|
||||
return array;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
T[] newArray = ArrayUtils.newUnpaddedArray((Class<T>)array.getClass().getComponentType(),
|
||||
growSize(currentSize));
|
||||
System.arraycopy(array, 0, newArray, 0, index);
|
||||
newArray[index] = element;
|
||||
System.arraycopy(array, index, newArray, index + 1, array.length - index);
|
||||
return newArray;
|
||||
}
|
||||
|
||||
public static int[] insert(int[] array, int currentSize, int index, int element) {
|
||||
assert currentSize <= array.length;
|
||||
|
||||
if (currentSize + 1 <= array.length) {
|
||||
System.arraycopy(array, index, array, index + 1, currentSize - index);
|
||||
array[index] = element;
|
||||
return array;
|
||||
}
|
||||
|
||||
int[] newArray = ArrayUtils.newUnpaddedIntArray(growSize(currentSize));
|
||||
System.arraycopy(array, 0, newArray, 0, index);
|
||||
newArray[index] = element;
|
||||
System.arraycopy(array, index, newArray, index + 1, array.length - index);
|
||||
return newArray;
|
||||
}
|
||||
|
||||
public static long[] insert(long[] array, int currentSize, int index, long element) {
|
||||
assert currentSize <= array.length;
|
||||
|
||||
if (currentSize + 1 <= array.length) {
|
||||
System.arraycopy(array, index, array, index + 1, currentSize - index);
|
||||
array[index] = element;
|
||||
return array;
|
||||
}
|
||||
|
||||
long[] newArray = ArrayUtils.newUnpaddedLongArray(growSize(currentSize));
|
||||
System.arraycopy(array, 0, newArray, 0, index);
|
||||
newArray[index] = element;
|
||||
System.arraycopy(array, index, newArray, index + 1, array.length - index);
|
||||
return newArray;
|
||||
}
|
||||
|
||||
public static boolean[] insert(boolean[] array, int currentSize, int index, boolean element) {
|
||||
assert currentSize <= array.length;
|
||||
|
||||
if (currentSize + 1 <= array.length) {
|
||||
System.arraycopy(array, index, array, index + 1, currentSize - index);
|
||||
array[index] = element;
|
||||
return array;
|
||||
}
|
||||
|
||||
boolean[] newArray = ArrayUtils.newUnpaddedBooleanArray(growSize(currentSize));
|
||||
System.arraycopy(array, 0, newArray, 0, index);
|
||||
newArray[index] = element;
|
||||
System.arraycopy(array, index, newArray, index + 1, array.length - index);
|
||||
return newArray;
|
||||
}
|
||||
|
||||
public static int growSize(int currentSize) {
|
||||
return currentSize <= 4 ? 8 : currentSize * 2;
|
||||
}
|
||||
|
||||
// Uninstantiable
|
||||
private GrowingArrayUtils() {}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
package xyz.nulldev.androidcompat
|
||||
|
||||
import android.webkit.WebView
|
||||
import xyz.nulldev.androidcompat.config.ApplicationInfoConfigModule
|
||||
import xyz.nulldev.androidcompat.config.FilesConfigModule
|
||||
import xyz.nulldev.androidcompat.config.SystemConfigModule
|
||||
import xyz.nulldev.androidcompat.webkit.KcefWebViewProvider
|
||||
import xyz.nulldev.ts.config.GlobalConfigManager
|
||||
|
||||
/**
|
||||
@@ -17,6 +19,8 @@ class AndroidCompatInitializer {
|
||||
SystemConfigModule.register(GlobalConfigManager.config),
|
||||
)
|
||||
|
||||
WebView.setProviderFactory({ view: WebView -> KcefWebViewProvider(view) })
|
||||
|
||||
// Set some properties extensions use
|
||||
System.setProperty(
|
||||
"http.agent",
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package xyz.nulldev.androidcompat
|
||||
|
||||
fun interface CallableArgument<A, R> {
|
||||
fun call(arg: A): R
|
||||
}
|
||||
+13
-11
@@ -96,7 +96,7 @@ class JavaSharedPreferences(
|
||||
} else {
|
||||
preferences.decodeValueOrNull(SetSerializer(String.serializer()), key)
|
||||
}
|
||||
} catch (e: SerializationException) {
|
||||
} catch (_: SerializationException) {
|
||||
throw ClassCastException("$key was not a StringSet")
|
||||
}
|
||||
}
|
||||
@@ -153,11 +153,12 @@ class JavaSharedPreferences(
|
||||
key: String,
|
||||
value: String?,
|
||||
): SharedPreferences.Editor {
|
||||
if (value != null) {
|
||||
actions += Action.Add(key, value)
|
||||
} else {
|
||||
actions += Action.Remove(key)
|
||||
}
|
||||
actions +=
|
||||
if (value != null) {
|
||||
Action.Add(key, value)
|
||||
} else {
|
||||
Action.Remove(key)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
@@ -165,11 +166,12 @@ class JavaSharedPreferences(
|
||||
key: String,
|
||||
values: MutableSet<String>?,
|
||||
): SharedPreferences.Editor {
|
||||
if (values != null) {
|
||||
actions += Action.Add(key, values)
|
||||
} else {
|
||||
actions += Action.Remove(key)
|
||||
}
|
||||
actions +=
|
||||
if (values != null) {
|
||||
Action.Add(key, values)
|
||||
} else {
|
||||
Action.Remove(key)
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,429 @@
|
||||
package xyz.nulldev.androidcompat.webkit
|
||||
|
||||
import android.webkit.WebSettings
|
||||
|
||||
class KcefWebSettings : WebSettings() {
|
||||
// Boolean settings
|
||||
private var navDumps = false
|
||||
private var mediaPlaybackRequiresUserGesture = true
|
||||
private var builtInZoomControls = false
|
||||
private var displayZoomControls = true
|
||||
private var allowFileAccess = true
|
||||
private var allowContentAccess = true
|
||||
private var pluginsEnabled = false
|
||||
private var loadWithOverviewMode = false
|
||||
private var enableSmoothTransition = false
|
||||
private var useWebViewBackgroundForOverscrollBackground = false
|
||||
private var saveFormData = true
|
||||
private var savePassword = false
|
||||
private var acceptThirdPartyCookies = true
|
||||
private var lightTouchEnabled = false
|
||||
private var useWideViewPort = true
|
||||
private var supportMultipleWindows = false
|
||||
private var loadsImagesAutomatically = true
|
||||
private var blockNetworkImage = false
|
||||
private var blockNetworkLoads = false
|
||||
private var javaScriptEnabled = true
|
||||
private var allowUniversalAccessFromFileURLs = false
|
||||
private var allowFileAccessFromFileURLs = false
|
||||
private var domStorageEnabled = true
|
||||
private var geolocationEnabled = true
|
||||
private var javaScriptCanOpenWindowsAutomatically = false
|
||||
private var needInitialFocus = false
|
||||
private var offscreenPreRaster = false
|
||||
private var videoOverlayForEmbeddedEncryptedVideoEnabled = false
|
||||
private var safeBrowsingEnabled = true
|
||||
|
||||
// Integer settings
|
||||
private var textZoom = 100
|
||||
private var minimumFontSize = 8
|
||||
private var minimumLogicalFontSize = 8
|
||||
private var defaultFontSize = 16
|
||||
private var defaultFixedFontSize = 13
|
||||
private var cacheMode = 0
|
||||
private var mixedContentMode = 0
|
||||
private var disabledActionModeMenuItems = 0
|
||||
|
||||
// String settings
|
||||
private var databasePath: String? = null
|
||||
private var geolocationDatabasePath: String? = null
|
||||
private var appCachePath: String? = null
|
||||
private var defaultTextEncodingName: String? = null
|
||||
private var userAgentString: String? = null
|
||||
private var standardFontFamily: String? = null
|
||||
private var fixedFontFamily: String? = null
|
||||
private var sansSerifFontFamily: String? = null
|
||||
private var serifFontFamily: String? = null
|
||||
private var cursiveFontFamily: String? = null
|
||||
private var fantasyFontFamily: String? = null
|
||||
|
||||
// Enum settings
|
||||
private var defaultZoom: ZoomDensity? = null
|
||||
private var layoutAlgorithm: LayoutAlgorithm? = null
|
||||
private var pluginState: PluginState? = null
|
||||
private var renderPriority: RenderPriority? = null
|
||||
|
||||
// Long settings
|
||||
private var appCacheMaxSize: Long = 0L
|
||||
|
||||
// Implementations
|
||||
|
||||
@SuppressWarnings("HiddenAbstractMethod")
|
||||
@Deprecated("inherit")
|
||||
override fun setNavDump(p0: Boolean) {
|
||||
navDumps = p0
|
||||
}
|
||||
|
||||
@Deprecated("inherit")
|
||||
@SuppressWarnings("HiddenAbstractMethod")
|
||||
override fun getNavDump(): Boolean = navDumps
|
||||
|
||||
override fun setSupportZoom(p0: Boolean) {
|
||||
mediaPlaybackRequiresUserGesture = p0
|
||||
}
|
||||
|
||||
override fun supportZoom() = mediaPlaybackRequiresUserGesture
|
||||
|
||||
override fun setMediaPlaybackRequiresUserGesture(p0: Boolean) {
|
||||
mediaPlaybackRequiresUserGesture = p0
|
||||
}
|
||||
|
||||
override fun getMediaPlaybackRequiresUserGesture() = mediaPlaybackRequiresUserGesture
|
||||
|
||||
override fun setBuiltInZoomControls(p0: Boolean) {
|
||||
builtInZoomControls = p0
|
||||
}
|
||||
|
||||
override fun getBuiltInZoomControls() = builtInZoomControls
|
||||
|
||||
override fun setDisplayZoomControls(p0: Boolean) {
|
||||
displayZoomControls = p0
|
||||
}
|
||||
|
||||
override fun getDisplayZoomControls() = displayZoomControls
|
||||
|
||||
override fun setAllowFileAccess(p0: Boolean) {
|
||||
allowFileAccess = p0
|
||||
}
|
||||
|
||||
override fun getAllowFileAccess() = allowFileAccess
|
||||
|
||||
override fun setAllowContentAccess(p0: Boolean) {
|
||||
allowContentAccess = p0
|
||||
}
|
||||
|
||||
override fun getAllowContentAccess() = allowContentAccess
|
||||
|
||||
override fun setLoadWithOverviewMode(p0: Boolean) {
|
||||
loadWithOverviewMode = p0
|
||||
}
|
||||
|
||||
override fun getLoadWithOverviewMode() = loadWithOverviewMode
|
||||
|
||||
@Deprecated("inherit")
|
||||
override fun setPluginsEnabled(p0: Boolean) {
|
||||
pluginsEnabled = p0
|
||||
}
|
||||
|
||||
@Deprecated("inherit")
|
||||
override fun getPluginsEnabled() = pluginsEnabled
|
||||
|
||||
@Deprecated("inherit")
|
||||
override fun setEnableSmoothTransition(p0: Boolean) {
|
||||
enableSmoothTransition = p0
|
||||
}
|
||||
|
||||
@Deprecated("inherit")
|
||||
override fun enableSmoothTransition() = enableSmoothTransition
|
||||
|
||||
@Deprecated("inherit")
|
||||
override fun setUseWebViewBackgroundForOverscrollBackground(p0: Boolean) {
|
||||
useWebViewBackgroundForOverscrollBackground = p0
|
||||
}
|
||||
|
||||
@Deprecated("inherit")
|
||||
override fun getUseWebViewBackgroundForOverscrollBackground() = useWebViewBackgroundForOverscrollBackground
|
||||
|
||||
@Deprecated("inherit")
|
||||
override fun setSaveFormData(p0: Boolean) {
|
||||
saveFormData = p0
|
||||
}
|
||||
|
||||
@Deprecated("inherit")
|
||||
override fun getSaveFormData() = saveFormData
|
||||
|
||||
@Deprecated("inherit")
|
||||
override fun setSavePassword(p0: Boolean) {
|
||||
savePassword = p0
|
||||
}
|
||||
|
||||
@Deprecated("inherit")
|
||||
override fun getSavePassword() = savePassword
|
||||
|
||||
override fun setTextZoom(p0: Int) {
|
||||
textZoom = p0
|
||||
}
|
||||
|
||||
override fun getTextZoom() = textZoom
|
||||
|
||||
@Deprecated("inherit")
|
||||
override fun setDefaultZoom(p0: ZoomDensity?) {
|
||||
defaultZoom = p0
|
||||
}
|
||||
|
||||
override fun setAcceptThirdPartyCookies(p0: Boolean) {
|
||||
acceptThirdPartyCookies = p0
|
||||
}
|
||||
|
||||
override fun getAcceptThirdPartyCookies() = acceptThirdPartyCookies
|
||||
|
||||
@Deprecated("inherit")
|
||||
override fun getDefaultZoom() = defaultZoom
|
||||
|
||||
@Deprecated("inherit")
|
||||
override fun setLightTouchEnabled(p0: Boolean) {
|
||||
lightTouchEnabled = p0
|
||||
}
|
||||
|
||||
@Deprecated("inherit")
|
||||
override fun getLightTouchEnabled() = lightTouchEnabled
|
||||
|
||||
@Deprecated("inherit")
|
||||
override fun setUserAgent(ua: Int) = throw RuntimeException("Stub!")
|
||||
|
||||
@Deprecated("inherit")
|
||||
override fun getUserAgent(): Int = throw RuntimeException("Stub!")
|
||||
|
||||
override fun setUseWideViewPort(p0: Boolean) {
|
||||
useWideViewPort = p0
|
||||
}
|
||||
|
||||
override fun getUseWideViewPort() = useWideViewPort
|
||||
|
||||
override fun setSupportMultipleWindows(p0: Boolean) {
|
||||
supportMultipleWindows = p0
|
||||
}
|
||||
|
||||
override fun supportMultipleWindows() = supportMultipleWindows
|
||||
|
||||
override fun setLayoutAlgorithm(p0: LayoutAlgorithm?) {
|
||||
layoutAlgorithm = p0
|
||||
}
|
||||
|
||||
override fun getLayoutAlgorithm() = layoutAlgorithm
|
||||
|
||||
override fun setStandardFontFamily(p0: String?) {
|
||||
standardFontFamily = p0
|
||||
}
|
||||
|
||||
override fun getStandardFontFamily() = standardFontFamily
|
||||
|
||||
override fun setFixedFontFamily(p0: String?) {
|
||||
fixedFontFamily = p0
|
||||
}
|
||||
|
||||
override fun getFixedFontFamily() = fixedFontFamily
|
||||
|
||||
override fun setSansSerifFontFamily(p0: String?) {
|
||||
sansSerifFontFamily = p0
|
||||
}
|
||||
|
||||
override fun getSansSerifFontFamily() = sansSerifFontFamily
|
||||
|
||||
override fun setSerifFontFamily(p0: String?) {
|
||||
serifFontFamily = p0
|
||||
}
|
||||
|
||||
override fun getSerifFontFamily() = serifFontFamily
|
||||
|
||||
override fun setCursiveFontFamily(p0: String?) {
|
||||
cursiveFontFamily = p0
|
||||
}
|
||||
|
||||
override fun getCursiveFontFamily() = cursiveFontFamily
|
||||
|
||||
override fun setFantasyFontFamily(p0: String?) {
|
||||
fantasyFontFamily = p0
|
||||
}
|
||||
|
||||
override fun getFantasyFontFamily() = fantasyFontFamily
|
||||
|
||||
override fun setMinimumFontSize(p0: Int) {
|
||||
minimumFontSize = p0
|
||||
}
|
||||
|
||||
override fun getMinimumFontSize() = minimumFontSize
|
||||
|
||||
override fun setMinimumLogicalFontSize(p0: Int) {
|
||||
minimumLogicalFontSize = p0
|
||||
}
|
||||
|
||||
override fun getMinimumLogicalFontSize() = minimumLogicalFontSize
|
||||
|
||||
override fun setDefaultFontSize(p0: Int) {
|
||||
defaultFontSize = p0
|
||||
}
|
||||
|
||||
override fun getDefaultFontSize() = defaultFontSize
|
||||
|
||||
override fun setDefaultFixedFontSize(p0: Int) {
|
||||
defaultFixedFontSize = p0
|
||||
}
|
||||
|
||||
override fun getDefaultFixedFontSize() = defaultFixedFontSize
|
||||
|
||||
override fun setLoadsImagesAutomatically(p0: Boolean) {
|
||||
loadsImagesAutomatically = p0
|
||||
}
|
||||
|
||||
override fun getLoadsImagesAutomatically() = loadsImagesAutomatically
|
||||
|
||||
override fun setBlockNetworkImage(p0: Boolean) {
|
||||
blockNetworkImage = p0
|
||||
}
|
||||
|
||||
override fun getBlockNetworkImage() = blockNetworkImage
|
||||
|
||||
override fun setBlockNetworkLoads(p0: Boolean) {
|
||||
blockNetworkLoads = p0
|
||||
}
|
||||
|
||||
override fun getBlockNetworkLoads() = blockNetworkLoads
|
||||
|
||||
override fun setJavaScriptEnabled(p0: Boolean) {
|
||||
javaScriptEnabled = p0
|
||||
}
|
||||
|
||||
override fun getJavaScriptEnabled() = javaScriptEnabled
|
||||
|
||||
@Deprecated("inherit")
|
||||
override fun setAllowUniversalAccessFromFileURLs(p0: Boolean) {
|
||||
allowUniversalAccessFromFileURLs = p0
|
||||
}
|
||||
|
||||
override fun getAllowUniversalAccessFromFileURLs() = allowUniversalAccessFromFileURLs
|
||||
|
||||
@Deprecated("inherit")
|
||||
override fun setAllowFileAccessFromFileURLs(p0: Boolean) {
|
||||
allowFileAccessFromFileURLs = p0
|
||||
}
|
||||
|
||||
override fun getAllowFileAccessFromFileURLs() = allowFileAccessFromFileURLs
|
||||
|
||||
@Deprecated("inherit")
|
||||
override fun setPluginState(p0: PluginState?) {
|
||||
pluginState = p0
|
||||
}
|
||||
|
||||
@Deprecated("inherit")
|
||||
override fun getPluginState() = pluginState
|
||||
|
||||
@Deprecated("inherit")
|
||||
override fun setDatabasePath(p0: String?) {
|
||||
databasePath = p0
|
||||
}
|
||||
|
||||
@Deprecated("inherit")
|
||||
override fun getDatabasePath() = databasePath ?: ""
|
||||
|
||||
@Deprecated("inherit")
|
||||
override fun setGeolocationDatabasePath(p0: String?) {
|
||||
geolocationDatabasePath = p0
|
||||
}
|
||||
|
||||
@Deprecated("inherit")
|
||||
override fun setAppCacheEnabled(p0: Boolean) {}
|
||||
|
||||
@Deprecated("inherit")
|
||||
override fun setAppCachePath(p0: String?) {
|
||||
appCachePath = p0
|
||||
}
|
||||
|
||||
@Deprecated("inherit")
|
||||
override fun setAppCacheMaxSize(p0: Long) {
|
||||
appCacheMaxSize = p0
|
||||
}
|
||||
|
||||
@Deprecated("inherit")
|
||||
override fun setDatabaseEnabled(p0: Boolean) {}
|
||||
|
||||
@Deprecated("inherit")
|
||||
override fun getDatabaseEnabled() = true
|
||||
|
||||
override fun setDomStorageEnabled(p0: Boolean) {
|
||||
domStorageEnabled = p0
|
||||
}
|
||||
|
||||
override fun getDomStorageEnabled() = domStorageEnabled
|
||||
|
||||
override fun setGeolocationEnabled(p0: Boolean) {
|
||||
geolocationEnabled = p0
|
||||
}
|
||||
|
||||
override fun setJavaScriptCanOpenWindowsAutomatically(p0: Boolean) {
|
||||
javaScriptCanOpenWindowsAutomatically = p0
|
||||
}
|
||||
|
||||
override fun getJavaScriptCanOpenWindowsAutomatically() = javaScriptCanOpenWindowsAutomatically
|
||||
|
||||
override fun setDefaultTextEncodingName(p0: String?) {
|
||||
defaultTextEncodingName = p0
|
||||
}
|
||||
|
||||
override fun getDefaultTextEncodingName() = defaultTextEncodingName ?: ""
|
||||
|
||||
override fun setUserAgentString(p0: String?) {
|
||||
userAgentString = p0
|
||||
}
|
||||
|
||||
override fun getUserAgentString() = userAgentString ?: defaultUserAgent()
|
||||
|
||||
override fun setNeedInitialFocus(p0: Boolean) {
|
||||
needInitialFocus = p0
|
||||
}
|
||||
|
||||
@Deprecated("inherit")
|
||||
override fun setRenderPriority(p0: RenderPriority?) {
|
||||
renderPriority = p0
|
||||
}
|
||||
|
||||
override fun setCacheMode(p0: Int) {
|
||||
cacheMode = p0
|
||||
}
|
||||
|
||||
override fun getCacheMode() = cacheMode
|
||||
|
||||
override fun setMixedContentMode(p0: Int) {
|
||||
mixedContentMode = p0
|
||||
}
|
||||
|
||||
override fun getMixedContentMode() = mixedContentMode
|
||||
|
||||
override fun setOffscreenPreRaster(p0: Boolean) {
|
||||
offscreenPreRaster = p0
|
||||
}
|
||||
|
||||
override fun getOffscreenPreRaster() = offscreenPreRaster
|
||||
|
||||
override fun setVideoOverlayForEmbeddedEncryptedVideoEnabled(p0: Boolean) {
|
||||
videoOverlayForEmbeddedEncryptedVideoEnabled = p0
|
||||
}
|
||||
|
||||
override fun getVideoOverlayForEmbeddedEncryptedVideoEnabled() = videoOverlayForEmbeddedEncryptedVideoEnabled
|
||||
|
||||
override fun setSafeBrowsingEnabled(p0: Boolean) {
|
||||
safeBrowsingEnabled = p0
|
||||
}
|
||||
|
||||
override fun getSafeBrowsingEnabled() = safeBrowsingEnabled
|
||||
|
||||
override fun setDisabledActionModeMenuItems(p0: Int) {
|
||||
disabledActionModeMenuItems = p0
|
||||
}
|
||||
|
||||
override fun getDisabledActionModeMenuItems() = disabledActionModeMenuItems
|
||||
|
||||
companion object {
|
||||
fun defaultUserAgent() = System.getProperty("http.agent")
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+185
-5
@@ -1,6 +1,186 @@
|
||||
# Server: v2.0.1727 + WevUI: v1.5.1
|
||||
# Server: v2.1.1867 + WebUI: v20250703.01 (r2643)
|
||||
|
||||
## TL;DR
|
||||
-
|
||||
- Implement WebView using KCEF
|
||||
- Improve cookie handling, share cookies between WebView and Extensions
|
||||
- Add image conversion on download
|
||||
- Add simple login menu
|
||||
- Support history in backups
|
||||
- Backup Suwayomi-specific data
|
||||
- Add OPDS Chapter Filtering/Ordering
|
||||
- Add OPDS internationalization
|
||||
- Add Java alternatives to deb package
|
||||
- Add AppImage bundle for Linux
|
||||
- Improve extension apk to jar conversion
|
||||
- Fix data insertion when authors and artists are too long
|
||||
- Fix multiple issues with extension settings
|
||||
- Fix too long page urls causing issues
|
||||
- Support extension author notes and other image manipulation
|
||||
- Sync tracking backend with Mihon
|
||||
- Add private tracking
|
||||
- Fix PWA's when auth is enabled
|
||||
- Prevent duplicated chapter pages
|
||||
- Ignore hidden folders/archives for Local Source chapter list
|
||||
- Improve Downloads Handling
|
||||
- Improve library update and auto backup scheduling
|
||||
|
||||
## Suwayomi-Server Changelog
|
||||
- ([r1866](https://github.com/Suwayomi/Suwayomi-Server/commit/8e8883ba37026bf0576390eda23dd0639c94d2b9)) Update electron ([#1556](https://github.com/Suwayomi/Suwayomi-Server/pull/1556) by @Syer10)
|
||||
- ([r1865](https://github.com/Suwayomi/Suwayomi-Server/commit/02c4398e48c95c23f61d13f9619e88a67caa88e5)) Fix handling of too long page image urls migration ([#1552](https://github.com/Suwayomi/Suwayomi-Server/pull/1552) by @schroda)
|
||||
- ([r1864](https://github.com/Suwayomi/Suwayomi-Server/commit/ad7a8dd7dc70ba86f52b5ed74af4e91f8dca3187)) Fix/page download conversion reduce logs ([#1545](https://github.com/Suwayomi/Suwayomi-Server/pull/1545) by @schroda)
|
||||
- ([r1863](https://github.com/Suwayomi/Suwayomi-Server/commit/e3338211d6b071d4512e5dbcbc480cda8666c915)) Handle too long page image urls ([#1544](https://github.com/Suwayomi/Suwayomi-Server/pull/1544) by @schroda)
|
||||
- ([r1862](https://github.com/Suwayomi/Suwayomi-Server/commit/7cab4b92296c3d8fcf1a46d34c73b54d5e443b60)) Simplify secondary config parse ([#1540](https://github.com/Suwayomi/Suwayomi-Server/pull/1540) by @Syer10)
|
||||
- ([r1861](https://github.com/Suwayomi/Suwayomi-Server/commit/ac5f1a0d93565c273dde40467af6605ab4b52c1e)) Add enabled preference setting ([#1539](https://github.com/Suwayomi/Suwayomi-Server/pull/1539) by @Syer10)
|
||||
- ([r1860](https://github.com/Suwayomi/Suwayomi-Server/commit/798b9d0c98c2261860af627452305e8fd901a76d)) Fix cookies when domain is null ([#1538](https://github.com/Suwayomi/Suwayomi-Server/pull/1538) by @Syer10)
|
||||
- ([r1859](https://github.com/Suwayomi/Suwayomi-Server/commit/3ff29aa38a447f3eba93bf72c090fab3b774509f)) snowmtl extension error fix: dynamic retrieval ([#1531](https://github.com/Suwayomi/Suwayomi-Server/pull/1531) by @Chiru-Dey)
|
||||
- ([r1858](https://github.com/Suwayomi/Suwayomi-Server/commit/f8c2b9ffb051d3ca996e25b80e696e9281a3e3e6)) Update dependency adoptium/temurin21-binaries to jdk-21.0.8+9 ([#1529](https://github.com/Suwayomi/Suwayomi-Server/pull/1529) by @renovate[bot])
|
||||
- ([r1857](https://github.com/Suwayomi/Suwayomi-Server/commit/888bb8897a58d98ed8dab76dfab8cf352a4d5c9f)) Update AppImageTool download location (by @Syer10)
|
||||
- ([r1856](https://github.com/Suwayomi/Suwayomi-Server/commit/192136e66ce2c8192f62d3aa7d8cbdeaf54c954f)) Change "download conversion compression level" type to Double ([#1535](https://github.com/Suwayomi/Suwayomi-Server/pull/1535) by @schroda)
|
||||
- ([r1855](https://github.com/Suwayomi/Suwayomi-Server/commit/5057a57f7f3a0b52766168cbafe1b22809ea1b24)) Properly bind track privately ([#1534](https://github.com/Suwayomi/Suwayomi-Server/pull/1534) by @schroda)
|
||||
- ([r1854](https://github.com/Suwayomi/Suwayomi-Server/commit/d81a4e0b7fc8ee97f45b63f05f73a922b2235f4b)) Update dependency io.mockk:mockk to v1.14.5 ([#1527](https://github.com/Suwayomi/Suwayomi-Server/pull/1527) by @renovate[bot])
|
||||
- ([r1853](https://github.com/Suwayomi/Suwayomi-Server/commit/c63a06730fdb33209d685db640398ddc54bc5523)) Fix downloads on errors when converting image ([#1526](https://github.com/Suwayomi/Suwayomi-Server/pull/1526) by @Syer10)
|
||||
- ([r1852](https://github.com/Suwayomi/Suwayomi-Server/commit/bef326d2d76d9740be5a896b65b07e3d0d807ad3)) Fix paths in system properties ([#1528](https://github.com/Suwayomi/Suwayomi-Server/pull/1528) by @Syer10)
|
||||
- ([r1851](https://github.com/Suwayomi/Suwayomi-Server/commit/b8e85422f01caf50259ed2ba1e857ccb2f7f65df)) Update polyglot to v24.2.2 ([#1523](https://github.com/Suwayomi/Suwayomi-Server/pull/1523) by @renovate[bot])
|
||||
- ([r1850](https://github.com/Suwayomi/Suwayomi-Server/commit/d050bfdc68b36f54959accba3bdf55f676cab0eb)) Localize WebView and Login pages ([#1522](https://github.com/Suwayomi/Suwayomi-Server/pull/1522) by @cpiber, @Syer10)
|
||||
- ([r1849](https://github.com/Suwayomi/Suwayomi-Server/commit/3bac176bf6420520b101730273a738af2cd5d071)) Prevent UnsupportedOperationException in DownloadManager ([#1521](https://github.com/Suwayomi/Suwayomi-Server/pull/1521) by @schroda)
|
||||
- ([r1848](https://github.com/Suwayomi/Suwayomi-Server/commit/7c506a42aecd122dd7fdb44a6559b39b0c6e878b)) Install libfuse2 when creating AppImage (by @Syer10)
|
||||
- ([r1847](https://github.com/Suwayomi/Suwayomi-Server/commit/2c436e2027b7f5bcd3a47d87491bb445245c8dd6)) Add AppImage bundle ([#1519](https://github.com/Suwayomi/Suwayomi-Server/pull/1519) by @KamaleiZestri, @Syer10)
|
||||
- ([r1846](https://github.com/Suwayomi/Suwayomi-Server/commit/df0078b7251e9e33580b16467ac9646034fbd4bc)) [#1496] Image conversion ([#1505](https://github.com/Suwayomi/Suwayomi-Server/pull/1505) by @cpiber, @Syer10)
|
||||
- ([r1845](https://github.com/Suwayomi/Suwayomi-Server/commit/09c950a890fc1c9855f2c4c18d118ab4db64977c)) Fix/gql download subscription errors spamming emits ([#1518](https://github.com/Suwayomi/Suwayomi-Server/pull/1518) by @schroda)
|
||||
- ([r1844](https://github.com/Suwayomi/Suwayomi-Server/commit/e7e76ed68d79a15b5ad58d4e6203fb2cb122715b)) Prevent duplicated meta entries in database ([#1517](https://github.com/Suwayomi/Suwayomi-Server/pull/1517) by @schroda)
|
||||
- ([r1843](https://github.com/Suwayomi/Suwayomi-Server/commit/06c1eeb995e1690dc36bde657eded644c19e1658)) Add missing transaction context to manga category update ([#1516](https://github.com/Suwayomi/Suwayomi-Server/pull/1516) by @schroda)
|
||||
- ([r1842](https://github.com/Suwayomi/Suwayomi-Server/commit/d545d852c5946735486358cb3cbccdf96b0ec11b)) Update dependency com.android.tools.build:apksig to v8.11.1 ([#1511](https://github.com/Suwayomi/Suwayomi-Server/pull/1511) by @renovate[bot])
|
||||
- ([r1841](https://github.com/Suwayomi/Suwayomi-Server/commit/3486e8dcf3576f4a93fac0bd0fdef8181c9b43aa)) Update dependency com.typesafe:config to v1.4.4 ([#1509](https://github.com/Suwayomi/Suwayomi-Server/pull/1509) by @renovate[bot])
|
||||
- ([r1840](https://github.com/Suwayomi/Suwayomi-Server/commit/1956b700fc4449aaf3e5f28fa2a51d700af70531)) Update dependency com.github.usefulness:webp-imageio to v0.10.2 ([#1507](https://github.com/Suwayomi/Suwayomi-Server/pull/1507) by @renovate[bot])
|
||||
- ([r1839](https://github.com/Suwayomi/Suwayomi-Server/commit/64ad9af344871d8177dbf19dd41bf47d5e9f0849)) Update okhttp monorepo to v5.1.0 ([#1506](https://github.com/Suwayomi/Suwayomi-Server/pull/1506) by @renovate[bot])
|
||||
- ([r1838](https://github.com/Suwayomi/Suwayomi-Server/commit/55fec0b82c2e9771c7a06f1df3366b3a0f820ff1)) Update plugin ktlint to v13 ([#1504](https://github.com/Suwayomi/Suwayomi-Server/pull/1504) by @renovate[bot])
|
||||
- ([r1837](https://github.com/Suwayomi/Suwayomi-Server/commit/8323ef5f4180f0bb9040757c216f98b4116e50c8)) [skip ci] Winget now uses Suwayomi.Suwayomi-Server (by @Syer10)
|
||||
- ([r1836](https://github.com/Suwayomi/Suwayomi-Server/commit/8dbab23de71bc0b58f4dcc8402664801573cd4af)) [skip ci] Manual release only (by @Syer10)
|
||||
- ([r1835](https://github.com/Suwayomi/Suwayomi-Server/commit/825d2326132e9604bbae936a47448501d4366542)) [skip ci] Manually input version (by @Syer10)
|
||||
- ([r1834](https://github.com/Suwayomi/Suwayomi-Server/commit/d797a0350256ea87fc6e76ecf53a1ac1490318b7)) Modify Winget (by @Syer10)
|
||||
- ([r1833](https://github.com/Suwayomi/Suwayomi-Server/commit/0ef6d745149a400747c06da7e611802c660ce185)) Add auth to log protection ([#1501](https://github.com/Suwayomi/Suwayomi-Server/pull/1501) by @Syer10)
|
||||
- ([r1832](https://github.com/Suwayomi/Suwayomi-Server/commit/6234e897a87c53fc817a72aba256a54f69cb4ece)) [#1497] WebView: Localstorage ([#1500](https://github.com/Suwayomi/Suwayomi-Server/pull/1500) by @cpiber)
|
||||
- ([r1831](https://github.com/Suwayomi/Suwayomi-Server/commit/fe121f59b0371ad09ff0ad3dae90c90ea7e313f9)) Update okhttp monorepo to v5.0.0 ([#1493](https://github.com/Suwayomi/Suwayomi-Server/pull/1493) by @renovate[bot])
|
||||
- ([r1830](https://github.com/Suwayomi/Suwayomi-Server/commit/90cf5fcdec537679cb423fe9217b58ce6b82378f)) Update dependency com.squareup.okio:okio to v3.15.0 ([#1488](https://github.com/Suwayomi/Suwayomi-Server/pull/1488) by @renovate[bot])
|
||||
- ([r1829](https://github.com/Suwayomi/Suwayomi-Server/commit/8b5782a5b6d88c7063b640c584e1a4c74520b4ae)) Update dependency gradle to v8.14.3 ([#1494](https://github.com/Suwayomi/Suwayomi-Server/pull/1494) by @renovate[bot])
|
||||
- ([r1828](https://github.com/Suwayomi/Suwayomi-Server/commit/68a131dbebac221e192b994e10ed7836781cbd1f)) [#1349] Basic Cookie Authentication ([#1498](https://github.com/Suwayomi/Suwayomi-Server/pull/1498) by @cpiber, @Syer10)
|
||||
- ([r1827](https://github.com/Suwayomi/Suwayomi-Server/commit/1411c02e181131736dbf39d2d8280a38318b2305)) [skip ci] Update CONTRIBUTING.md (by @Syer10)
|
||||
- ([r1826](https://github.com/Suwayomi/Suwayomi-Server/commit/81fe3f0108883a445a55e769bcd841c54ee70f7d)) Stop dumping cookies in the console ([#1490](https://github.com/Suwayomi/Suwayomi-Server/pull/1490) by @cpiber)
|
||||
- ([r1825](https://github.com/Suwayomi/Suwayomi-Server/commit/c15cf23168bd4aafe0969a7038a160d6c8d93e87)) Kcef: Disable SHM ([#1489](https://github.com/Suwayomi/Suwayomi-Server/pull/1489) by @cpiber)
|
||||
- ([r1824](https://github.com/Suwayomi/Suwayomi-Server/commit/a79dc580a55c636ac250b5903a327192f6b03c4b)) Browser Webview ([#1486](https://github.com/Suwayomi/Suwayomi-Server/pull/1486) by @cpiber, @Syer10)
|
||||
- ([r1823](https://github.com/Suwayomi/Suwayomi-Server/commit/8a62c6295d59272c1e90d453639f8137af773f01)) Update moko to v0.25.0 ([#1487](https://github.com/Suwayomi/Suwayomi-Server/pull/1487) by @renovate[bot])
|
||||
- ([r1822](https://github.com/Suwayomi/Suwayomi-Server/commit/88e77e1547ca27c1a0a104818ef6d6ecf8e2e162)) Update okhttp monorepo to v5.0.0-alpha.17 ([#1485](https://github.com/Suwayomi/Suwayomi-Server/pull/1485) by @renovate[bot])
|
||||
- ([r1821](https://github.com/Suwayomi/Suwayomi-Server/commit/534619bc1a37280c22f3d026d27e4e1423ba7193)) Weblate translations ([#1484](https://github.com/Suwayomi/Suwayomi-Server/pull/1484) by @weblate, @9811pc, @yutthaphon, @Syer10, @UnknownSkyrimPasserby)
|
||||
- ([r1820](https://github.com/Suwayomi/Suwayomi-Server/commit/ae904753f784f61e25dc06c013497ee1c7b43701)) systemd: use startup script, X server ([#1482](https://github.com/Suwayomi/Suwayomi-Server/pull/1482) by @cpiber)
|
||||
- ([r1819](https://github.com/Suwayomi/Suwayomi-Server/commit/8c4a2cb529640a5e6880bbf6ef4c57a8ec44a6a8)) Add chapter lastReadAt to backups as BackupHistory ([#1477](https://github.com/Suwayomi/Suwayomi-Server/pull/1477) by @Syer10)
|
||||
- ([r1818](https://github.com/Suwayomi/Suwayomi-Server/commit/16d4893480e2632e096859033dcbb9516443231d)) Update dependency com.squareup.okio:okio to v3.14.0 ([#1483](https://github.com/Suwayomi/Suwayomi-Server/pull/1483) by @renovate[bot])
|
||||
- ([r1817](https://github.com/Suwayomi/Suwayomi-Server/commit/a7446e2f4cfdc7e608080c95b02990dedb004880)) Update dependency com.github.usefulness:webp-imageio to v0.10.1 ([#1480](https://github.com/Suwayomi/Suwayomi-Server/pull/1480) by @renovate[bot])
|
||||
- ([r1816](https://github.com/Suwayomi/Suwayomi-Server/commit/9dcae193a909e3c3755a627e871bf159296f11b0)) Update serialization to v1.9.0 ([#1479](https://github.com/Suwayomi/Suwayomi-Server/pull/1479) by @renovate[bot])
|
||||
- ([r1815](https://github.com/Suwayomi/Suwayomi-Server/commit/36fac0f3f476aa8752ab286ec0a8896f04f9ab30)) Update dependency com.android.tools.build:apksig to v8.11.0 ([#1473](https://github.com/Suwayomi/Suwayomi-Server/pull/1473) by @renovate[bot])
|
||||
- ([r1814](https://github.com/Suwayomi/Suwayomi-Server/commit/24a4c176c079b39b3d946e44180f578453d52048)) Update plugin buildconfig to v5.6.7 ([#1469](https://github.com/Suwayomi/Suwayomi-Server/pull/1469) by @renovate[bot])
|
||||
- ([r1813](https://github.com/Suwayomi/Suwayomi-Server/commit/9c7f50e91e591ed6addb5b73f100d19784817c80)) Update kotlin monorepo to v2.2.0 ([#1466](https://github.com/Suwayomi/Suwayomi-Server/pull/1466) by @renovate[bot])
|
||||
- ([r1812](https://github.com/Suwayomi/Suwayomi-Server/commit/e52bc255f360ad26631c5bc1efb6518c78ecec98)) Update dependency org.jsoup:jsoup to v1.21.1 ([#1465](https://github.com/Suwayomi/Suwayomi-Server/pull/1465) by @renovate[bot])
|
||||
- ([r1811](https://github.com/Suwayomi/Suwayomi-Server/commit/ae4c9887d889da7aded204f268eebc637b7e9741)) Translations update from Hosted Weblate ([#1471](https://github.com/Suwayomi/Suwayomi-Server/pull/1471) by @weblate, @yutthaphon, @UnknownSkyrimPasserby, @Syer10, @Syer10)
|
||||
- ([r1810](https://github.com/Suwayomi/Suwayomi-Server/commit/52201e248886a7b1e3fe02c90d0cfc20de5ebeb0)) Add private to trackrecords filter ([#1468](https://github.com/Suwayomi/Suwayomi-Server/pull/1468) by @Syer10)
|
||||
- ([r1809](https://github.com/Suwayomi/Suwayomi-Server/commit/c3e2b0e002a1a86b2bec37d6749692c204465d11)) Update dependency io.mockk:mockk to v1.14.4 ([#1464](https://github.com/Suwayomi/Suwayomi-Server/pull/1464) by @renovate[bot])
|
||||
- ([r1808](https://github.com/Suwayomi/Suwayomi-Server/commit/709915bf59e6136140c5cb1be0985250230b4e97)) Update dependency io.javalin:javalin to v6.7.0 ([#1460](https://github.com/Suwayomi/Suwayomi-Server/pull/1460) by @renovate[bot])
|
||||
- ([r1807](https://github.com/Suwayomi/Suwayomi-Server/commit/9d7ec6fd60ff359ae71ef6ed35c1139958428cde)) Update dependency io.mockk:mockk to v1.14.3 ([#1462](https://github.com/Suwayomi/Suwayomi-Server/pull/1462) by @renovate[bot])
|
||||
- ([r1806](https://github.com/Suwayomi/Suwayomi-Server/commit/ee9de376a3d23e71004449af881f7831ed0ae873)) Fix new private parameter in tracking backup ([#1463](https://github.com/Suwayomi/Suwayomi-Server/pull/1463) by @Syer10)
|
||||
- ([r1805](https://github.com/Suwayomi/Suwayomi-Server/commit/b54dc6f9678feecd35032413c4ee2d45c4798fa6)) Fix Tracking DisplayScore ([#1461](https://github.com/Suwayomi/Suwayomi-Server/pull/1461) by @Syer10)
|
||||
- ([r1804](https://github.com/Suwayomi/Suwayomi-Server/commit/abea85d8318c2e4360eb7634436d54887eeb3c6e)) Update Tracking Backend ([#1457](https://github.com/Suwayomi/Suwayomi-Server/pull/1457) by @Syer10)
|
||||
- ([r1803](https://github.com/Suwayomi/Suwayomi-Server/commit/972137c035484c1112b6a6d3f146734b2fa5ffe9)) Paint: Support Typeface ([#1459](https://github.com/Suwayomi/Suwayomi-Server/pull/1459) by @cpiber)
|
||||
- ([r1802](https://github.com/Suwayomi/Suwayomi-Server/commit/1cdef5e0ee564f956827c001bd579999b100514e)) Use Kotlin AppDirs ([#1453](https://github.com/Suwayomi/Suwayomi-Server/pull/1453) by @Syer10)
|
||||
- ([r1801](https://github.com/Suwayomi/Suwayomi-Server/commit/bd7ea64b021992716f1a6e775ae79cfa51cbaf79)) Add an abort handler and preload it on Linux ([#1456](https://github.com/Suwayomi/Suwayomi-Server/pull/1456) by @cpiber)
|
||||
- ([r1800](https://github.com/Suwayomi/Suwayomi-Server/commit/20c850c10b52d24f1ab12d7ea09bbc69e184f112)) Implement Bitmap.copy, text layouting ([#1455](https://github.com/Suwayomi/Suwayomi-Server/pull/1455) by @cpiber)
|
||||
- ([r1799](https://github.com/Suwayomi/Suwayomi-Server/commit/0b021e6c42024d15a9311fd70861b79f18339cf2)) Increase WebView compatibility ([#1451](https://github.com/Suwayomi/Suwayomi-Server/pull/1451) by @cpiber)
|
||||
- ([r1798](https://github.com/Suwayomi/Suwayomi-Server/commit/0d109cdd4f3fd23769c15bac3934922c27305b18)) Update graphqlkotlin to v8.8.1 ([#1450](https://github.com/Suwayomi/Suwayomi-Server/pull/1450) by @renovate[bot], @Syer10)
|
||||
- ([r1797](https://github.com/Suwayomi/Suwayomi-Server/commit/1dab9e1a7d7c6c6a934e2daabd7dfa926c64813c)) Fix database migration (by @Syer10)
|
||||
- ([r1796](https://github.com/Suwayomi/Suwayomi-Server/commit/593b01819d2aba80c55dd6db6efff739449b8a21)) Update locales (by @Syer10)
|
||||
- ([r1795](https://github.com/Suwayomi/Suwayomi-Server/commit/029aa9c01b34334564341abd1eb7565e507c2e51)) Update plugin buildconfig to v5.6.6 ([#1449](https://github.com/Suwayomi/Suwayomi-Server/pull/1449) by @renovate[bot])
|
||||
- ([r1794](https://github.com/Suwayomi/Suwayomi-Server/commit/149b549d8d27d9731a0b82b8c5e3bb1c5c6f160c)) Handle chapter marked as downloaded without downloaded files ([#1448](https://github.com/Suwayomi/Suwayomi-Server/pull/1448) by @schroda)
|
||||
- ([r1793](https://github.com/Suwayomi/Suwayomi-Server/commit/786635010ec274493f7b7c5795d8b989dd60f6df)) Update dependency com.squareup.okio:okio to v3.13.0 ([#1447](https://github.com/Suwayomi/Suwayomi-Server/pull/1447) by @renovate[bot])
|
||||
- ([r1792](https://github.com/Suwayomi/Suwayomi-Server/commit/d7fe170067cb9ef457fa988a1384977727655b17)) Weblate translations ([#1445](https://github.com/Suwayomi/Suwayomi-Server/pull/1445) by @weblate, @cpiber, @TamilNeram)
|
||||
- ([r1791](https://github.com/Suwayomi/Suwayomi-Server/commit/e3d4be9a5afb74211fac06c989b136f513239898)) Update dependency org.bouncycastle:bcprov-jdk18on to v1.81 ([#1443](https://github.com/Suwayomi/Suwayomi-Server/pull/1443) by @renovate[bot])
|
||||
- ([r1790](https://github.com/Suwayomi/Suwayomi-Server/commit/4086a737276dd01c0b66081c34f2fd3f8a81b099)) Feature/backup suwayomi data ([#1430](https://github.com/Suwayomi/Suwayomi-Server/pull/1430) by @schroda)
|
||||
- ([r1789](https://github.com/Suwayomi/Suwayomi-Server/commit/483e3a760f5fb2b05595cfcf5cf053fa8037cfab)) Increase chapter scanlator column max length ([#1425](https://github.com/Suwayomi/Suwayomi-Server/pull/1425) by @schroda)
|
||||
- ([r1788](https://github.com/Suwayomi/Suwayomi-Server/commit/2757f881dc8a8c462032f79924b3f08e4fa1d829)) Update plugin ktlint to v12.3.0 ([#1403](https://github.com/Suwayomi/Suwayomi-Server/pull/1403) by @renovate[bot])
|
||||
- ([r1787](https://github.com/Suwayomi/Suwayomi-Server/commit/e2aff0ece723a5cd860f4c27450967632b3c7bef)) Update dependency com.pinterest.ktlint:ktlint-cli to v1.6.0 ([#1402](https://github.com/Suwayomi/Suwayomi-Server/pull/1402) by @renovate[bot])
|
||||
- ([r1786](https://github.com/Suwayomi/Suwayomi-Server/commit/327526330ff440116a5aefb6a931724681beaf0c)) [skip ci] Update README.md (by @Syer10)
|
||||
- ([r1785](https://github.com/Suwayomi/Suwayomi-Server/commit/ea976a4d0f38f788a91c139e70076505f965e3de)) Update dependency io.insert-koin:koin-core to v4.1.0 ([#1442](https://github.com/Suwayomi/Suwayomi-Server/pull/1442) by @renovate[bot])
|
||||
- ([r1784](https://github.com/Suwayomi/Suwayomi-Server/commit/728ada5e70e711885d7a7b99a489373ae701bebb)) Update okhttp monorepo to v5.0.0-alpha.16 ([#1441](https://github.com/Suwayomi/Suwayomi-Server/pull/1441) by @renovate[bot])
|
||||
- ([r1783](https://github.com/Suwayomi/Suwayomi-Server/commit/c091ac4d6771b430ca81c1fee541238d87023eb2)) Update xmlserialization to v0.91.1 ([#1400](https://github.com/Suwayomi/Suwayomi-Server/pull/1400) by @renovate[bot])
|
||||
- ([r1782](https://github.com/Suwayomi/Suwayomi-Server/commit/ee4c852f1b4b6eba9ee4bac6140f5aaf5691fcb8)) Always update manga thumbnail on fetch ([#1429](https://github.com/Suwayomi/Suwayomi-Server/pull/1429) by @schroda)
|
||||
- ([r1781](https://github.com/Suwayomi/Suwayomi-Server/commit/1a5d334f6c8e2b63a1b84d948cd63648c682692a)) Delete thumbnails during backup import ([#1428](https://github.com/Suwayomi/Suwayomi-Server/pull/1428) by @schroda)
|
||||
- ([r1780](https://github.com/Suwayomi/Suwayomi-Server/commit/7d72ff3514679ff811acae44f075e07e8745f13d)) Fix extracting "startDate" ([#1427](https://github.com/Suwayomi/Suwayomi-Server/pull/1427) by @schroda)
|
||||
- ([r1779](https://github.com/Suwayomi/Suwayomi-Server/commit/e224e91100d9fa9848362f73f3e0d248cfe36332)) Dequeue downloads of removed chapters ([#1426](https://github.com/Suwayomi/Suwayomi-Server/pull/1426) by @schroda)
|
||||
- ([r1778](https://github.com/Suwayomi/Suwayomi-Server/commit/7c5edd1b73219d2622acee9ce1ac1362daa34c16)) Realign chapter number recognition with mihon ([#1424](https://github.com/Suwayomi/Suwayomi-Server/pull/1424) by @schroda)
|
||||
- ([r1777](https://github.com/Suwayomi/Suwayomi-Server/commit/2621415f7c3f79a154337a943da66e5285d2bd9e)) Weblate translations ([#1422](https://github.com/Suwayomi/Suwayomi-Server/pull/1422) by @weblate, @dejavui, @Syer10, @Zereef, @marimo-nekomimi)
|
||||
- ([r1776](https://github.com/Suwayomi/Suwayomi-Server/commit/a3184c46b690eafe14e97bfdec02fcec928fddd6)) Update dependency com.android.tools.build:apksig to v8.10.1 ([#1419](https://github.com/Suwayomi/Suwayomi-Server/pull/1419) by @renovate[bot])
|
||||
- ([r1775](https://github.com/Suwayomi/Suwayomi-Server/commit/1575ffa6aea83c77973282b1cf17e21f860cbc71)) Migrate config renovate.json ([#1420](https://github.com/Suwayomi/Suwayomi-Server/pull/1420) by @renovate[bot])
|
||||
- ([r1774](https://github.com/Suwayomi/Suwayomi-Server/commit/ee27da3de63ebefef568390664475df160f1366e)) Update dependency com.github.Suwayomi:exposed-migrations to v3.8.0 ([#1421](https://github.com/Suwayomi/Suwayomi-Server/pull/1421) by @renovate[bot])
|
||||
- ([r1773](https://github.com/Suwayomi/Suwayomi-Server/commit/83a7224f2df6722e1517a2fbfc0b014dd460c5d5)) Update exposed to v0.61.0 ([#1291](https://github.com/Suwayomi/Suwayomi-Server/pull/1291) by @renovate[bot])
|
||||
- ([r1772](https://github.com/Suwayomi/Suwayomi-Server/commit/ecea2ecdf51dfef73f9f41764602232b201e726b)) Fix/initial scheduling of global update ([#1416](https://github.com/Suwayomi/Suwayomi-Server/pull/1416) by @schroda)
|
||||
- ([r1771](https://github.com/Suwayomi/Suwayomi-Server/commit/f04060b31b342121dfd87d59d79a46dcd5a2f899)) Update graphqlkotlin to v8.8.0 ([#1365](https://github.com/Suwayomi/Suwayomi-Server/pull/1365) by @renovate[bot])
|
||||
- ([r1770](https://github.com/Suwayomi/Suwayomi-Server/commit/d411d1966a5c57a4f1efd11f0112fda4d0b3279f)) Update dependency com.graphql-java:graphql-java to v22.4 ([#1401](https://github.com/Suwayomi/Suwayomi-Server/pull/1401) by @renovate[bot])
|
||||
- ([r1769](https://github.com/Suwayomi/Suwayomi-Server/commit/507bf071040f93dac22ee4d3508afa217811f9a5)) implement `setSummaryOn` and `setSummaryOff` in `TwoStatePreference` ([#1431](https://github.com/Suwayomi/Suwayomi-Server/pull/1431) by @AwkwardPeak7)
|
||||
- ([r1768](https://github.com/Suwayomi/Suwayomi-Server/commit/09061a38bcef76a0c64319f4ed2a2d28b5ba7de1)) Negation missing in SystemProperties ([#1433](https://github.com/Suwayomi/Suwayomi-Server/pull/1433) by @cpiber)
|
||||
- ([r1767](https://github.com/Suwayomi/Suwayomi-Server/commit/611a7db2e1403a654040431c83d03c57ef59e4ea)) Update dependency gradle to v8.14.2 ([#1435](https://github.com/Suwayomi/Suwayomi-Server/pull/1435) by @renovate[bot])
|
||||
- ([r1766](https://github.com/Suwayomi/Suwayomi-Server/commit/a5cf428ce57e3b8e869a1af64a216886f48134d5)) doc: Add Neko integration instructions ([#1440](https://github.com/Suwayomi/Suwayomi-Server/pull/1440) by @D-Brox, @Syer10)
|
||||
- ([r1765](https://github.com/Suwayomi/Suwayomi-Server/commit/31f06a2d434e7328ea9db335d733ec029e1833e4)) Update dependency com.squareup.okio:okio to v3.12.0 ([#1418](https://github.com/Suwayomi/Suwayomi-Server/pull/1418) by @renovate[bot])
|
||||
- ([r1764](https://github.com/Suwayomi/Suwayomi-Server/commit/1d7a60b630a8f4401ceaad641d5b49b2230b2a86)) Automatically truncate required varchar columns ([#1423](https://github.com/Suwayomi/Suwayomi-Server/pull/1423) by @schroda)
|
||||
- ([r1763](https://github.com/Suwayomi/Suwayomi-Server/commit/a2fadbe5131521d0254bdfb20a4f4842a4517faf)) Implement WebView via Playwright ([#1434](https://github.com/Suwayomi/Suwayomi-Server/pull/1434) by @cpiber, @Syer10)
|
||||
- ([r1762](https://github.com/Suwayomi/Suwayomi-Server/commit/dee61e191c0175c6dae6346f58293e0225df8b1a)) [ci skip] Add Translation into to README (by @Syer10)
|
||||
- ([r1761](https://github.com/Suwayomi/Suwayomi-Server/commit/32b6461c6a94f68f4c61b18d10b1587c4563f85e)) Update dependency io.javalin:javalin to v6.6.0 ([#1364](https://github.com/Suwayomi/Suwayomi-Server/pull/1364) by @renovate[bot])
|
||||
- ([r1760](https://github.com/Suwayomi/Suwayomi-Server/commit/93fff42693f69870bacfc7d892d0324a2f2bd118)) Update dependency gradle to v8.14.1 ([#1398](https://github.com/Suwayomi/Suwayomi-Server/pull/1398) by @renovate[bot])
|
||||
- ([r1759](https://github.com/Suwayomi/Suwayomi-Server/commit/61f429896cfda55d414e73e8f85b64c3fdd6c957)) feat(opds): implement full internationalization and refactor feed gen… ([#1405](https://github.com/Suwayomi/Suwayomi-Server/pull/1405) by @zeedif, @Syer10)
|
||||
- ([r1758](https://github.com/Suwayomi/Suwayomi-Server/commit/a9e03837a37c192a877541f9dd8d1a1a2104ce9e)) Exclude web manifest file from requiring authentication ([#1414](https://github.com/Suwayomi/Suwayomi-Server/pull/1414) by @schroda)
|
||||
- ([r1757](https://github.com/Suwayomi/Suwayomi-Server/commit/89421946afbe9718dd3e8c6b20f17308d5fbe873)) Properly deschedule active tasks ([#1413](https://github.com/Suwayomi/Suwayomi-Server/pull/1413) by @schroda)
|
||||
- ([r1756](https://github.com/Suwayomi/Suwayomi-Server/commit/218af8ea54865505ce1876cf4a7bd4e855c167fe)) Update Gradle Wrapper Validation ([#1412](https://github.com/Suwayomi/Suwayomi-Server/pull/1412) by @Syer10)
|
||||
- ([r1755](https://github.com/Suwayomi/Suwayomi-Server/commit/d0f79ca473ea8bd7b37535c54476d852ec6ca9fc)) Fix: Validate zipEntry directories during extension asset decompression ([#1407](https://github.com/Suwayomi/Suwayomi-Server/pull/1407) by @EdgeAtZero)
|
||||
- ([r1754](https://github.com/Suwayomi/Suwayomi-Server/commit/ec870759cfd252b08abee21d82a563457e748146)) Add highest numbered chapter function in MangaType ([#1397](https://github.com/Suwayomi/Suwayomi-Server/pull/1397) by @Syer10)
|
||||
- ([r1753](https://github.com/Suwayomi/Suwayomi-Server/commit/0405a535c76af432a124004ffcebe55f15770d2d)) Feat: Adds OPDS Chapter Filtering/Ordering ([#1392](https://github.com/Suwayomi/Suwayomi-Server/pull/1392) by @shirishsaxena)
|
||||
- ([r1752](https://github.com/Suwayomi/Suwayomi-Server/commit/814e4ba744212203d35decad9e85f97b2a0576ff)) Update kotlin monorepo to v2.1.21 ([#1383](https://github.com/Suwayomi/Suwayomi-Server/pull/1383) by @renovate[bot])
|
||||
- ([r1751](https://github.com/Suwayomi/Suwayomi-Server/commit/5621c1ab587ab6d2e8d10e050ef5c946b21675d8)) Update dependency com.android.tools.build:apksig to v8.10.0 ([#1376](https://github.com/Suwayomi/Suwayomi-Server/pull/1376) by @renovate[bot])
|
||||
- ([r1750](https://github.com/Suwayomi/Suwayomi-Server/commit/f1fd8bc446e0ae5bd4d3bfd3d7945f0199eeb6c3)) Update dependency io.mockk:mockk to v1.14.2 ([#1371](https://github.com/Suwayomi/Suwayomi-Server/pull/1371) by @renovate[bot])
|
||||
- ([r1749](https://github.com/Suwayomi/Suwayomi-Server/commit/60fdd6cda974950f901aa124344265e586fec671)) Update dependency org.jsoup:jsoup to v1.20.1 ([#1369](https://github.com/Suwayomi/Suwayomi-Server/pull/1369) by @renovate[bot])
|
||||
- ([r1748](https://github.com/Suwayomi/Suwayomi-Server/commit/3332363a106a3f716197b25aee384ae23b0bd060)) Update plugin buildconfig to v5.6.5 ([#1368](https://github.com/Suwayomi/Suwayomi-Server/pull/1368) by @renovate[bot])
|
||||
- ([r1747](https://github.com/Suwayomi/Suwayomi-Server/commit/538bd3f126a5f89aa10a0775f8c894c443dae59f)) Improve Downloads Handling ([#1387](https://github.com/Suwayomi/Suwayomi-Server/pull/1387) by @Syer10)
|
||||
- ([r1746](https://github.com/Suwayomi/Suwayomi-Server/commit/336f9858942008227e4aded1b9d50fe20bdb8e44)) Fix Downloaded pages with no cached pages from source ([#1386](https://github.com/Suwayomi/Suwayomi-Server/pull/1386) by @Syer10)
|
||||
- ([r1745](https://github.com/Suwayomi/Suwayomi-Server/commit/ba6687355eace1055c2634bf99a94f4dffa53f10)) Ignore hidden folders/archives for Local Source chapter list ([#1377](https://github.com/Suwayomi/Suwayomi-Server/pull/1377) by @BrutuZ)
|
||||
- ([r1744](https://github.com/Suwayomi/Suwayomi-Server/commit/983980d8dae26ac65d00bc15ca74c583a396ffcd)) Add Alternatives to deb package ([#1375](https://github.com/Suwayomi/Suwayomi-Server/pull/1375) by @Syer10)
|
||||
- ([r1743](https://github.com/Suwayomi/Suwayomi-Server/commit/82d4a401fd388f75dd9ffa3c54ec7adce00b314b)) Update dependency gradle to v8.14 ([#1363](https://github.com/Suwayomi/Suwayomi-Server/pull/1363) by @renovate[bot])
|
||||
- ([r1742](https://github.com/Suwayomi/Suwayomi-Server/commit/76e9f42734ed80d01a5867ad21750a81382fbef7)) Update dependency com.squareup.okio:okio to v3.11.0 ([#1362](https://github.com/Suwayomi/Suwayomi-Server/pull/1362) by @renovate[bot])
|
||||
- ([r1741](https://github.com/Suwayomi/Suwayomi-Server/commit/0c0035370adc11f7ea620c7593c5b8e23bf6117e)) Update polyglot to v24.2.1 ([#1360](https://github.com/Suwayomi/Suwayomi-Server/pull/1360) by @renovate[bot])
|
||||
- ([r1740](https://github.com/Suwayomi/Suwayomi-Server/commit/a3ac136b3b682cef3dde432f997c70e45163ec0c)) Update dependency adoptium/temurin21-binaries to jdk-21.0.7+6 ([#1359](https://github.com/Suwayomi/Suwayomi-Server/pull/1359) by @renovate[bot])
|
||||
- ([r1739](https://github.com/Suwayomi/Suwayomi-Server/commit/ed1509b54fa7764b9cde9e03497996c34b21f0d5)) Update dependency io.mockk:mockk to v1.14.0 ([#1341](https://github.com/Suwayomi/Suwayomi-Server/pull/1341) by @renovate[bot])
|
||||
- ([r1738](https://github.com/Suwayomi/Suwayomi-Server/commit/1d0dcd097cee4dd547f52ff0c5837e6ea6bfc33d)) Update kotlinx-coroutines monorepo to v1.10.2 ([#1337](https://github.com/Suwayomi/Suwayomi-Server/pull/1337) by @renovate[bot])
|
||||
- ([r1737](https://github.com/Suwayomi/Suwayomi-Server/commit/785c0469acad1377c4c5dba41fe004b7678a3d9a)) Fix/m0045 prevent duplicated chapter pages migration ([#1361](https://github.com/Suwayomi/Suwayomi-Server/pull/1361) by @schroda)
|
||||
- ([r1736](https://github.com/Suwayomi/Suwayomi-Server/commit/7594ae5fa51f0466bbe41f07ed7ce4788cf8f8f0)) Update xmlserialization to v0.91.0 ([#1331](https://github.com/Suwayomi/Suwayomi-Server/pull/1331) by @renovate[bot])
|
||||
- ([r1735](https://github.com/Suwayomi/Suwayomi-Server/commit/6b4e08fdd196206b21eb3ede7a47d0565d37875a)) Update serialization to v1.8.1 ([#1330](https://github.com/Suwayomi/Suwayomi-Server/pull/1330) by @renovate[bot])
|
||||
- ([r1734](https://github.com/Suwayomi/Suwayomi-Server/commit/65435341f33cb9078e8f2035e5aa83b814fe9f0d)) Update dependency io.github.oshai:kotlin-logging-jvm to v7.0.7 ([#1329](https://github.com/Suwayomi/Suwayomi-Server/pull/1329) by @renovate[bot])
|
||||
- ([r1733](https://github.com/Suwayomi/Suwayomi-Server/commit/9bc9f963b7df1e8168f84179bc1c9e1544fb16a3)) Update dependency io.insert-koin:koin-core to v4.0.4 ([#1326](https://github.com/Suwayomi/Suwayomi-Server/pull/1326) by @renovate[bot])
|
||||
- ([r1732](https://github.com/Suwayomi/Suwayomi-Server/commit/a27501371f0c88af430152f5523ecf2c1819b523)) Update plugin buildconfig to v5.6.3 ([#1322](https://github.com/Suwayomi/Suwayomi-Server/pull/1322) by @renovate[bot])
|
||||
- ([r1731](https://github.com/Suwayomi/Suwayomi-Server/commit/9fafebc8e78b4ed8cbd333eb803fda1a69dfccf7)) Update dependency com.android.tools.build:apksig to v8.9.2 ([#1321](https://github.com/Suwayomi/Suwayomi-Server/pull/1321) by @renovate[bot])
|
||||
- ([r1730](https://github.com/Suwayomi/Suwayomi-Server/commit/59d2151c920863175383c9f576fa4a126f5b09ad)) Prevent duplicated chapter pages ([#1353](https://github.com/Suwayomi/Suwayomi-Server/pull/1353) by @schroda)
|
||||
- ([r1729](https://github.com/Suwayomi/Suwayomi-Server/commit/1cc2a05f90ca4f5e6649009f8032eb706aa9525a)) [ci skip] Update outdated install instructions in README ([#1356](https://github.com/Suwayomi/Suwayomi-Server/pull/1356) by @schroda)
|
||||
- ([r1728](https://github.com/Suwayomi/Suwayomi-Server/commit/8aea6f54737f6ee286c60e42fad236e99f9894aa)) [ci skip] Update feature list in README ([#1355](https://github.com/Suwayomi/Suwayomi-Server/pull/1355) by @schroda)
|
||||
|
||||
Contributors:
|
||||
@Syer10, @schroda, @Chiru-Dey, @renovate[bot], @cpiber, @KamaleiZestri, @weblate, @9811pc, @yutthaphon, @UnknownSkyrimPasserby, @TamilNeram, @dejavui, @Zereef, @marimo-nekomimi, @AwkwardPeak7, @D-Brox, @zeedif, @EdgeAtZero, @shirishsaxena, @BrutuZ
|
||||
|
||||
|
||||
## [Suwayomi-WebUI Changelog](https://github.com/Suwayomi/Suwayomi-WebUI/blob/master/CHANGELOG.md#v2025070301-r2643)
|
||||
|
||||
# Server: v2.0.1727 + WebUI: v1.5.1
|
||||
## TL;DR
|
||||
- Update to Java 21
|
||||
- Add OPDS API
|
||||
- More Tracking!
|
||||
- Changing Version Scheme
|
||||
- Fix MSI Installer
|
||||
- Optimized Backup Import
|
||||
- Improve JS Support
|
||||
- Optimize included JRE
|
||||
|
||||
## Suwayomi-Server Changelog
|
||||
- ([r1726](https://github.com/Suwayomi/Suwayomi-Server/commit/1d5323a477528649f81fa4bd2e1e9e4a28da6402)) [skip ci] Add link to discord in issue templates ([#1347](https://github.com/Suwayomi/Suwayomi-Server/pull/1347) by @schroda)
|
||||
@@ -202,7 +382,7 @@ Contributors:
|
||||
## [Suwayomi-WebUI Changelog](https://github.com/Suwayomi/Suwayomi-WebUI/blob/master/CHANGELOG.md#v151-r2467)
|
||||
|
||||
|
||||
# Server: v1.1.1 + WevUI: v1.1.0
|
||||
# Server: v1.1.1 + WebUI: v1.1.0
|
||||
## TL;DR
|
||||
- WebUI update bugfixes
|
||||
|
||||
@@ -210,7 +390,7 @@ Contributors:
|
||||
- ([r1534](https://github.com/Suwayomi/Suwayomi-Server/commit/d9cb54b28593e4df87522090f03a6e5b9c7d9fa2)) Compare webUI version with bundled webUI version ([#969](https://github.com/Suwayomi/Suwayomi-Server/pull/969) by @schroda)
|
||||
- ([r1533](https://github.com/Suwayomi/Suwayomi-Server/commit/f738a162d3cd4582612d4986b3d3887e1c309bdd)) Support for "STABLEPREVIEW" webUI version ([#970](https://github.com/Suwayomi/Suwayomi-Server/pull/970) by @schroda)
|
||||
|
||||
# Server: v1.1.0 + WevUI: v1.1.0
|
||||
# Server: v1.1.0 + WebUI: v1.1.0
|
||||
## TL;DR
|
||||
- Update Manga Info in browse
|
||||
- Full Tracking support
|
||||
@@ -257,7 +437,7 @@ Contributors:
|
||||
|
||||
## [Suwayomi-WebUI Changelog](https://github.com/Suwayomi/Suwayomi-WebUI/blob/master/CHANGELOG.md#v110-r1689)
|
||||
|
||||
# Server: v1.0.0 + WevUI: r1409
|
||||
# Server: v1.0.0 + WebUI: r1409
|
||||
## TL;DR
|
||||
- GraphQL API
|
||||
- Rename to Suwayomi
|
||||
|
||||
+1
-1
@@ -61,7 +61,7 @@ This structure is chosen to
|
||||
### Prerequisites
|
||||
You need these software packages installed in order to build the project
|
||||
|
||||
- Java Development Kit and Java Runtime Environment version 8 or newer(both Oracle JDK and OpenJDK works)
|
||||
- Java Development Kit version 21 or newer(we suggest using Temurin instead of Oracle JDK)
|
||||
|
||||
### building the full-blown jar (Suwayomi-Server + Suwayomi-WebUI bundle)
|
||||
Run `./gradlew server:downloadWebUI server:shadowJar`, the resulting built jar file will be `server/build/Suwayomi-Server-vX.Y.Z-rxxx.jar`.
|
||||
|
||||
@@ -5,26 +5,34 @@
|
||||
|
||||
## Table of Content
|
||||
- [What is Suwayomi?](#what-is-suwayomi)
|
||||
- [Features](#Features)
|
||||
- [Suwayomi client projects](#Suwayomi-client-projects)
|
||||
- [Features](#features)
|
||||
- [Suwayomi client projects](#suwayomi-client-projects)
|
||||
- [Actively Developed Clients](#actively-developed-clients)
|
||||
- [Inactive Clients (functional but outdated)](#inactive-clients-functional-but-outdated)
|
||||
- [Abandoned Clients (functionality unknown)](#abandoned-clients-functionality-unknown)
|
||||
- [Downloading and Running the app](#downloading-and-running-the-app)
|
||||
* [Using Operating System Specific Bundles](#using-operating-system-specific-bundles)
|
||||
- [Launcher Scripts](#launcher-scripts)
|
||||
+ [Windows](#windows)
|
||||
+ [macOS](#macos)
|
||||
+ [GNU/Linux](#gnulinux)
|
||||
* [Other methods of getting Suwayomi](#other-methods-of-getting-suwayomi)
|
||||
+ [Arch Linux](#arch-linux)
|
||||
+ [Ubuntu-based distributions](#ubuntu-based-distributions)
|
||||
+ [Docker](#docker)
|
||||
* [Advanced Methods](#advanced-methods)
|
||||
+ [Running the jar release directly](#running-the-jar-release-directly)
|
||||
+ [Using Suwayomi Remotely](#using-suwayomi-remotely)
|
||||
- [Syncing With Mihon (Tachiyomi)](#syncing-with-mihon-tachiyomi)
|
||||
- [Troubleshooting and Support](#troubleshooting-and-support)
|
||||
- [Contributing and Technical info](#contributing-and-technical-info)
|
||||
- [Credit](#credit)
|
||||
- [License](#license)
|
||||
- [Using Operating System Specific Bundles](#using-operating-system-specific-bundles)
|
||||
- [Windows](#windows)
|
||||
- [macOS](#macos)
|
||||
- [GNU/Linux](#gnulinux)
|
||||
- [Other methods of getting Suwayomi](#other-methods-of-getting-suwayomi)
|
||||
- [Docker](#docker)
|
||||
- [Arch Linux](#arch-linux)
|
||||
- [Debian/Ubuntu](#debianubuntu)
|
||||
- [NixOS](#nixos)
|
||||
- [Advanced Methods](#advanced-methods)
|
||||
- [Running the jar release directly](#running-the-jar-release-directly)
|
||||
- [Using Suwayomi Remotely](#using-suwayomi-remotely)
|
||||
- [Syncing With Mihon (Tachiyomi) and Neko](#syncing-with-mihon-tachiyomi-and-neko)
|
||||
- [The Suwayomi extension and tracker](#the-suwayomi-extension-and-tracker)
|
||||
- [The Suwayomi merge source in Neko](#the-suwayomi-merge-source-in-neko)
|
||||
- [Other methods](#other-methods)
|
||||
- [Troubleshooting and Support](#troubleshooting-and-support)
|
||||
- [Contributing and Technical info](#contributing-and-technical-info)
|
||||
- [Translation](#translation)
|
||||
- [Credit](#credit)
|
||||
- [License](#license)
|
||||
- [Disclaimer](#disclaimer)
|
||||
<!-- Generated with https://ecotrust-canada.github.io/markdown-toc/ -->
|
||||
|
||||
# What is Suwayomi?
|
||||
@@ -52,9 +60,10 @@ You can use Mihon (Tachiyomi) to access your Suwayomi-Server. For more info look
|
||||
- Ability to download Manga for offline read
|
||||
- Backup and restore support powered by Mihon (Tachiyomi)-compatible Backups
|
||||
- Automated backup creations
|
||||
- Tracking via [MyAnimeList](https://myanimelist.net/), [AniList](https://anilist.co/), [MangaUpdates](https://www.mangaupdates.com/)
|
||||
- Tracking via [MyAnimeList](https://myanimelist.net/), [AniList](https://anilist.co/), [MangaUpdates](https://www.mangaupdates.com/), etc.
|
||||
- [FlareSolverr](https://github.com/FlareSolverr/FlareSolverr) support to bypass Cloudflare protection
|
||||
- Automated WebUI updates (supports the default WebUI and VUI)
|
||||
- OPDS and OPDS-PSE support (endpoint: `/api/opds/v1.2`)
|
||||
|
||||
# Suwayomi client projects
|
||||
**You need a client/user interface app as a front-end for Suwayomi-Server, if you [Directly Download Suwayomi-Server](https://github.com/Suwayomi/Suwayomi-Server/releases/latest) you'll get a bundled version of [Suwayomi-WebUI](https://github.com/Suwayomi/Suwayomi-WebUI) with it.**
|
||||
@@ -93,6 +102,25 @@ Download the latest `linux-x64`(x86_64) release from [the releases section](http
|
||||
|
||||
`tar xvf` the downloaded file and double-click on one of the launcher scripts or run them using the terminal.
|
||||
|
||||
#### WebView support (GNU/Linux)
|
||||
|
||||
WebView support is implemented via [KCEF](https://github.com/DATL4G/KCEF).
|
||||
This is optional, and is only necessary to support some extensions.
|
||||
|
||||
To have a functional WebView, several dependencies are required; aside from X11 libraries necessary for rendering Chromium, some JNI bindings are necessary: gluegen and jogl (found in Ubuntu as `libgluegen2-jni` and `libjogl2-jni`).
|
||||
Note that on some systems (e.g. Ubuntu), the JNI libraries are not automatically found, see below.
|
||||
|
||||
A KCEF server is launched on startup, which loads the X11 libraries.
|
||||
If those are missing, you should see "Could not load 'jcef' library".
|
||||
If so, use `ldd ~/.local/share/Tachidesk/bin/kcef/libjcef.so | grep not` to figure out which libraries are not found on your system.
|
||||
|
||||
The JNI bindings are only loaded when a browser is actually launched.
|
||||
This is done by extensions that rely on WebView, not by Suwayomi itself.
|
||||
If there is a problem loading the JNI libraries, you should see a message indicating the library and the search path.
|
||||
This search path includes the current working directory, if you do not want to modify system directories.
|
||||
|
||||
Refer to the [Dockerfile](https://github.com/Suwayomi/Suwayomi-Server-docker/blob/main/Dockerfile) for more details.
|
||||
|
||||
## Other methods of getting Suwayomi
|
||||
### Docker
|
||||
Check our Official Docker release [Suwayomi Container](https://github.com/orgs/Suwayomi/packages/container/package/tachidesk) for running Suwayomi Server in a docker container. Source code for our container is available at [docker-tachidesk](https://github.com/Suwayomi/docker-tachidesk), an example compose file can also be found there. By default, the server will be running on http://localhost:4567 open this url in your browser.
|
||||
@@ -104,19 +132,22 @@ yay -S tachidesk
|
||||
```
|
||||
|
||||
### Debian/Ubuntu
|
||||
Download the latest deb package from the release section or Install from the MPR
|
||||
```
|
||||
git clone https://mpr.makedeb.org/suwayomi-server.git
|
||||
cd suwayomi-server
|
||||
makedeb -si
|
||||
```
|
||||
Download the latest deb package from the release section.
|
||||
|
||||
### Ubuntu
|
||||
```
|
||||
sudo add-apt-repository ppa:suwayomi/suwayomi-server
|
||||
sudo apt update
|
||||
sudo apt install suwayomi-server
|
||||
```
|
||||
> [!CAUTION]
|
||||
> These options are outdated and unmaintained ([relevant issue](https://github.com/Suwayomi/Suwayomi-Server/issues/1318))
|
||||
> ### MPR
|
||||
> ```
|
||||
> git clone https://mpr.makedeb.org/tachidesk-server.git
|
||||
> cd tachidesk-server
|
||||
> makedeb -si
|
||||
> ```
|
||||
> ### Ubuntu
|
||||
> ```
|
||||
> sudo add-apt-repository ppa:suwayomi/tachidesk-server
|
||||
> sudo apt update
|
||||
> sudo apt install tachidesk-server
|
||||
> ```
|
||||
|
||||
### NixOS
|
||||
You can deploy Suwayomi on NixOS using the module `services.suwayomi-server` in your configuration:
|
||||
@@ -152,9 +183,9 @@ Check out [this wiki page](https://github.com/Suwayomi/Suwayomi-Server/wiki/Conf
|
||||
|
||||
If you face issues with your setup then we are happy to provide help, just join our discord server(a discord badge is on the top of the page, you are just a click-clack away!).
|
||||
|
||||
## Syncing With Mihon (Tachiyomi)
|
||||
## Syncing With Mihon (Tachiyomi) and Neko
|
||||
### The Suwayomi extension and tracker
|
||||
- You can install the `Suwayomi` extension inside Mihon (Tachiyomi).
|
||||
- You can install and configure the `Suwayomi` [extension](https://github.com/Suwayomi/tachiyomi-extension) inside Mihon (Tachiyomi) and forks.
|
||||
- The extension will load your Suwayomi library.
|
||||
- By manipulating extension search filters you can browse your categories.
|
||||
- You can enable the Suwayomi tracker to track reading progress with your Suwayomi server.
|
||||
@@ -162,6 +193,15 @@ If you face issues with your setup then we are happy to provide help, just join
|
||||
- Mihon (Tachiyomi) to Suwayomi: Mihon (Tachiyomi) automatically updates the chapters read status when it's updating the tracker (e.g. while reading)
|
||||
- Suwayomi to Mihon (Tachiyomi): To sync Mihon (Tachiyomi) with Suwayomi, you have to open the manga's track information, then, Mihon (Tachiyomi) will automatically update its chapter list with the state from Suwayomi
|
||||
|
||||
### The Suwayomi merge source in Neko
|
||||
- You can enable the `Suwayomi` source in the Merge Source settings
|
||||
- You can merge titles in Neko with titles from your Suwayomi library.
|
||||
- You can enable 2-way automatic sync to track reading progress with your Suwayomi server.
|
||||
- Note: only applies to merged titles
|
||||
- Neko automatically updates the chapters read status in Suwayomi
|
||||
- During updates, Neko will automatically update its chapter list with the read state from Suwayomi
|
||||
- This only pulls if the status is read, to prevent marking read chapters as unread in Neko
|
||||
|
||||
### Other methods
|
||||
Checkout [this issue](https://github.com/Suwayomi/Suwayomi-Server/issues/159) for tracking progress.
|
||||
|
||||
@@ -171,6 +211,15 @@ See [this troubleshooting wiki page](https://github.com/Suwayomi/Suwayomi-Server
|
||||
## Contributing and Technical info
|
||||
See [CONTRIBUTING.md](./CONTRIBUTING.md).
|
||||
|
||||
## Translation
|
||||
Feel free to translate the project on [Weblate](https://hosted.weblate.org/projects/suwayomi/suwayomi-server/)
|
||||
|
||||
<details><summary>Translation Progress</summary>
|
||||
<a href="https://hosted.weblate.org/engage/suwayomi-server/">
|
||||
<img src="https://hosted.weblate.org/widgets/suwayomi/-/suwayomi-server/multi-auto.svg" alt="Translation status" />
|
||||
</a>
|
||||
</details>
|
||||
|
||||
## Credit
|
||||
This project is a spiritual successor of [TachiWeb-Server](https://github.com/Tachiweb/TachiWeb-server), Many of the ideas and the groundwork adopted in this project comes from TachiWeb.
|
||||
|
||||
|
||||
+10
-4
@@ -4,11 +4,14 @@ import org.jlleitschuh.gradle.ktlint.KtlintExtension
|
||||
import org.jlleitschuh.gradle.ktlint.KtlintPlugin
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.jvm)
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
alias(libs.plugins.ktlint)
|
||||
alias(libs.plugins.kotlin.jvm) apply false
|
||||
alias(libs.plugins.kotlin.serialization) apply false
|
||||
alias(libs.plugins.ktlint) apply false
|
||||
alias(libs.plugins.buildconfig) apply false
|
||||
alias(libs.plugins.download)
|
||||
alias(libs.plugins.kotlin.multiplatform) apply false
|
||||
alias(libs.plugins.moko) apply false
|
||||
alias(libs.plugins.jte) apply false
|
||||
}
|
||||
|
||||
allprojects {
|
||||
@@ -21,6 +24,7 @@ allprojects {
|
||||
google()
|
||||
maven("https://github.com/Suwayomi/Suwayomi-Server/raw/android-jar/")
|
||||
maven("https://jitpack.io")
|
||||
maven("https://jogamp.org/deployment/maven")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +47,9 @@ subprojects {
|
||||
|
||||
tasks {
|
||||
withType<KotlinJvmCompile> {
|
||||
dependsOn("ktlintFormat")
|
||||
if (plugins.hasPlugin(KtlintPlugin::class)) {
|
||||
dependsOn("ktlintFormat")
|
||||
}
|
||||
compilerOptions {
|
||||
jvmTarget = JvmTarget.JVM_21
|
||||
freeCompilerArgs.add("-Xcontext-receivers")
|
||||
|
||||
@@ -10,9 +10,9 @@ import java.io.BufferedReader
|
||||
const val MainClass = "suwayomi.tachidesk.MainKt"
|
||||
|
||||
// should be bumped with each stable release
|
||||
val getTachideskVersion = { "v2.0.${getCommitCount()}" }
|
||||
val getTachideskVersion = { "v2.1.${getCommitCount()}" }
|
||||
|
||||
val webUIRevisionTag = "r2467"
|
||||
val webUIRevisionTag = "r2643"
|
||||
|
||||
private val getCommitCount = {
|
||||
runCatching {
|
||||
|
||||
+47
-25
@@ -1,19 +1,21 @@
|
||||
[versions]
|
||||
kotlin = "2.1.20"
|
||||
coroutines = "1.10.1"
|
||||
serialization = "1.8.0"
|
||||
okhttp = "5.0.0-alpha.14" # Major version is locked by Tachiyomi extensions
|
||||
javalin = "6.5.0"
|
||||
kotlin = "2.2.0"
|
||||
coroutines = "1.10.2"
|
||||
serialization = "1.9.0"
|
||||
okhttp = "5.1.0" # Major version is locked by Tachiyomi extensions
|
||||
javalin = "6.7.0"
|
||||
jte = "3.2.1"
|
||||
jackson = "2.18.3" # jackson version locked by javalin, ref: `io.javalin.core.util.OptionalDependency`
|
||||
exposed = "0.59.0"
|
||||
exposed = "0.61.0"
|
||||
dex2jar = "v64" # Stuck until https://github.com/ThexXTURBOXx/dex2jar/issues/27 is fixed
|
||||
polyglot = "24.2.0"
|
||||
polyglot = "24.2.2"
|
||||
settings = "1.3.0"
|
||||
twelvemonkeys = "3.12.0"
|
||||
graphqlkotlin = "8.4.0"
|
||||
xmlserialization = "0.90.3"
|
||||
ktlint = "1.5.0"
|
||||
koin = "4.0.2"
|
||||
graphqlkotlin = "8.8.1"
|
||||
xmlserialization = "0.91.1"
|
||||
ktlint = "1.6.0"
|
||||
koin = "4.1.0"
|
||||
moko = "0.25.0"
|
||||
|
||||
[libraries]
|
||||
# Kotlin
|
||||
@@ -36,26 +38,28 @@ serialization-xml = { module = "io.github.pdvrieze.xmlutil:serialization-jvm", v
|
||||
# Logging
|
||||
slf4japi = "org.slf4j:slf4j-api:2.0.17"
|
||||
logback = "ch.qos.logback:logback-classic:1.5.18"
|
||||
kotlinlogging = "io.github.oshai:kotlin-logging-jvm:7.0.5"
|
||||
kotlinlogging = "io.github.oshai:kotlin-logging-jvm:7.0.7"
|
||||
|
||||
# OkHttp
|
||||
okhttp-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
|
||||
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
|
||||
okhttp-dnsoverhttps = { module = "com.squareup.okhttp3:okhttp-dnsoverhttps", version.ref = "okhttp" }
|
||||
okhttp-brotli = { module = "com.squareup.okhttp3:okhttp-brotli", version.ref = "okhttp" }
|
||||
okio = "com.squareup.okio:okio:3.10.2"
|
||||
okio = "com.squareup.okio:okio:3.15.0"
|
||||
|
||||
# Javalin api
|
||||
javalin-core = { module = "io.javalin:javalin", version.ref = "javalin" }
|
||||
javalin-openapi = { module = "io.javalin:javalin-openapi", version.ref = "javalin" }
|
||||
javalin-rendering = { module = "io.javalin:javalin-rendering", version.ref = "javalin" }
|
||||
jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" }
|
||||
jackson-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson" }
|
||||
jackson-annotations = { module = "com.fasterxml.jackson.core:jackson-annotations", version.ref = "jackson" }
|
||||
jte = { module = "gg.jte:jte", version.ref = "jte" }
|
||||
kte = { module = "gg.jte:jte-kotlin", version.ref = "jte" }
|
||||
|
||||
# GraphQL
|
||||
graphql-kotlin-server = { module = "com.expediagroup:graphql-kotlin-server", version.ref = "graphqlkotlin" }
|
||||
graphql-kotlin-scheme = { module = "com.expediagroup:graphql-kotlin-schema-generator", version.ref = "graphqlkotlin" }
|
||||
graphql-java-core = "com.graphql-java:graphql-java:22.3" # Major version locked by graphql-kotlin
|
||||
graphql-java-scalars = "com.graphql-java:graphql-java-extended-scalars:22.0"
|
||||
|
||||
# Exposed ORM
|
||||
@@ -66,7 +70,7 @@ exposed-javatime = { module = "org.jetbrains.exposed:exposed-java-time", version
|
||||
h2 = "com.h2database:h2:1.4.200" # current database driver, can't update to h2 v2 without sql migration
|
||||
|
||||
# Exposed Migrations
|
||||
exposed-migrations = "com.github.Suwayomi:exposed-migrations:3.7.0"
|
||||
exposed-migrations = "com.github.Suwayomi:exposed-migrations:3.8.0"
|
||||
|
||||
# Dependency Injection
|
||||
koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
|
||||
@@ -79,10 +83,10 @@ systemtray-desktop = "com.dorkbox:Desktop:1.1" # version locked by SystemTray
|
||||
# dependencies of Tachiyomi extensions
|
||||
injekt = "com.github.null2264:injekt-koin:ee267b2e27"
|
||||
rxjava = "io.reactivex:rxjava:1.3.8"
|
||||
jsoup = "org.jsoup:jsoup:1.19.1"
|
||||
jsoup = "org.jsoup:jsoup:1.21.1"
|
||||
|
||||
# Config
|
||||
config = "com.typesafe:config:1.4.3"
|
||||
config = "com.typesafe:config:1.4.4"
|
||||
config4k = "io.github.config4k:config4k:0.7.0"
|
||||
|
||||
# Sort
|
||||
@@ -98,20 +102,20 @@ dex2jar-tools = { module = "com.github.ThexXTURBOXx.dex2jar:dex-tools", version.
|
||||
|
||||
# APK
|
||||
apk-parser = "net.dongliu:apk-parser:2.6.10"
|
||||
apksig = "com.android.tools.build:apksig:8.9.0"
|
||||
apksig = "com.android.tools.build:apksig:8.11.1"
|
||||
|
||||
# Xml
|
||||
xmlpull = "xmlpull:xmlpull:1.1.3.4a"
|
||||
|
||||
# Disk & File
|
||||
appdirs = "net.harawata:appdirs:1.4.0"
|
||||
appdirs = "ca.gosyer:kotlin-multiplatform-appdirs:2.0.0"
|
||||
cache4k = "io.github.reactivecircus.cache4k:cache4k:0.14.0"
|
||||
zip4j = "net.lingala.zip4j:zip4j:2.11.5"
|
||||
commonscompress = "org.apache.commons:commons-compress:1.27.1"
|
||||
junrar = "com.github.junrar:junrar:7.5.5"
|
||||
|
||||
# AES/CBC/PKCS7Padding Cypher provider
|
||||
bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.80"
|
||||
bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.81"
|
||||
|
||||
# AndroidX annotations
|
||||
android-annotations = "androidx.annotation:annotation:1.9.1"
|
||||
@@ -136,8 +140,10 @@ twelvemonkeys-imageio-metadata = { module = "com.twelvemonkeys.imageio:imageio-m
|
||||
twelvemonkeys-imageio-jpeg = { module = "com.twelvemonkeys.imageio:imageio-jpeg", version.ref = "twelvemonkeys" }
|
||||
twelvemonkeys-imageio-webp = { module = "com.twelvemonkeys.imageio:imageio-webp", version.ref = "twelvemonkeys" }
|
||||
|
||||
imageio-webp = "com.github.usefulness:webp-imageio:0.10.2"
|
||||
|
||||
# Testing
|
||||
mockk = "io.mockk:mockk:1.13.17"
|
||||
mockk = "io.mockk:mockk:1.14.5"
|
||||
|
||||
# cron scheduler
|
||||
cron4j = "it.sauronsoftware.cron4j:cron4j:2.2.5"
|
||||
@@ -145,19 +151,26 @@ cron4j = "it.sauronsoftware.cron4j:cron4j:2.2.5"
|
||||
# cron-utils
|
||||
cronUtils = "com.cronutils:cron-utils:9.2.1"
|
||||
|
||||
# Webview
|
||||
kcef = "dev.datlag:kcef:2024.04.20.4"
|
||||
|
||||
# lint - used for renovate to update ktlint version
|
||||
ktlint = { module = "com.pinterest.ktlint:ktlint-cli", version.ref = "ktlint" }
|
||||
|
||||
# moko
|
||||
moko = { module = "dev.icerock.moko:resources", version.ref = "moko" }
|
||||
|
||||
[plugins]
|
||||
# Kotlin
|
||||
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin"}
|
||||
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin"}
|
||||
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin"}
|
||||
|
||||
# Linter
|
||||
ktlint = { id = "org.jlleitschuh.gradle.ktlint", version = "12.2.0"}
|
||||
ktlint = { id = "org.jlleitschuh.gradle.ktlint", version = "13.0.0"}
|
||||
|
||||
# Build config
|
||||
buildconfig = { id = "com.github.gmazzo.buildconfig", version = "5.5.4"}
|
||||
buildconfig = { id = "com.github.gmazzo.buildconfig", version = "5.6.7"}
|
||||
|
||||
# Download
|
||||
download = { id = "de.undercouch.download", version = "5.6.0"}
|
||||
@@ -165,6 +178,12 @@ download = { id = "de.undercouch.download", version = "5.6.0"}
|
||||
# ShadowJar
|
||||
shadowjar = { id = "com.github.johnrengelman.shadow", version = "8.1.1"}
|
||||
|
||||
# Moko
|
||||
moko = { id = "dev.icerock.mobile.multiplatform-resources", version.ref = "moko" }
|
||||
|
||||
# JTE
|
||||
jte = { id = "gg.jte.gradle", version.ref = "jte" }
|
||||
|
||||
[bundles]
|
||||
shared = [
|
||||
"kotlin-stdlib-jdk8",
|
||||
@@ -186,7 +205,8 @@ shared = [
|
||||
"dex2jar-translator",
|
||||
"dex2jar-tools",
|
||||
"apk-parser",
|
||||
"jackson-annotations"
|
||||
"jackson-annotations",
|
||||
"kcef"
|
||||
]
|
||||
|
||||
sharedTest = [
|
||||
@@ -203,6 +223,8 @@ okhttp = [
|
||||
javalin = [
|
||||
"javalin-core",
|
||||
#"javalin-openapi",
|
||||
"javalin-rendering",
|
||||
"jte",
|
||||
]
|
||||
jackson = [
|
||||
"jackson-databind",
|
||||
@@ -236,4 +258,4 @@ twelvemonkeys = [
|
||||
"twelvemonkeys-imageio-metadata",
|
||||
"twelvemonkeys-imageio-jpeg",
|
||||
"twelvemonkeys-imageio-webp",
|
||||
]
|
||||
]
|
||||
|
||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -114,7 +114,7 @@ case "$( uname )" in #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
CLASSPATH="\\\"\\\""
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
@@ -213,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
|
||||
Vendored
+2
-2
@@ -70,11 +70,11 @@ goto fail
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
set CLASSPATH=
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
|
||||
+2
-2
@@ -7,8 +7,8 @@
|
||||
"customManagers": [
|
||||
{
|
||||
"customType": "regex",
|
||||
"fileMatch": [
|
||||
"scripts/bundler.sh"
|
||||
"managerFilePatterns": [
|
||||
"/scripts/bundler.sh/"
|
||||
],
|
||||
"matchStrings": [
|
||||
"JRE_RELEASE=[\"'](?<currentValue>.+?)[\"']\\s+"
|
||||
|
||||
+42
-5
@@ -30,7 +30,7 @@ main() {
|
||||
RELEASE_NAME="$(echo "${JAR%.*}" | xargs basename)-$OS"
|
||||
RELEASE_VERSION=$(echo "$JAR" | grep -oP "v\K[0-9]+\.[0-9]+\.[0-9]+")
|
||||
#RELEASE_REVISION_NUMBER="$(tmp="${JAR%.*}" && echo "${tmp##*-}" | tr -d r)"
|
||||
local electron_version="v28.1.3"
|
||||
local electron_version="v37.2.5"
|
||||
|
||||
# clean temporary directory on function return
|
||||
trap "rm -rf $RELEASE_NAME/" RETURN
|
||||
@@ -38,12 +38,28 @@ main() {
|
||||
|
||||
download_launcher
|
||||
|
||||
if [ ! -f scripts/resources/catch_abort.so ]; then
|
||||
gcc -fPIC -I$JAVA_HOME/include -I$JAVA_HOME/include/linux -shared scripts/resources/catch_abort.c -lpthread -o scripts/resources/catch_abort.so
|
||||
fi
|
||||
|
||||
case "$OS" in
|
||||
debian-all)
|
||||
RELEASE="$RELEASE_NAME.deb"
|
||||
make_deb_package
|
||||
move_release_to_output_dir
|
||||
;;
|
||||
appimage)
|
||||
# https://github.com/adoptium/temurin21-binaries/releases/
|
||||
JRE_RELEASE="jdk-21.0.8+9"
|
||||
JRE="OpenJDK21U-jre_x64_linux_hotspot_$(echo "$JRE_RELEASE" | sed 's/jdk//;s/-//g;s/+/_/g').tar.gz"
|
||||
JRE_DIR="$JRE_RELEASE-jre"
|
||||
JRE_URL="https://github.com/adoptium/temurin21-binaries/releases/download/$JRE_RELEASE/$JRE"
|
||||
setup_jre
|
||||
|
||||
RELEASE="$RELEASE_NAME.AppImage"
|
||||
make_appimage
|
||||
move_release_to_output_dir
|
||||
;;
|
||||
linux-assets)
|
||||
RELEASE="$RELEASE_NAME.tar.gz"
|
||||
copy_linux_package_assets_to "$RELEASE_NAME/"
|
||||
@@ -52,7 +68,7 @@ main() {
|
||||
;;
|
||||
linux-x64)
|
||||
# https://github.com/adoptium/temurin21-binaries/releases/
|
||||
JRE_RELEASE="jdk-21.0.6+7"
|
||||
JRE_RELEASE="jdk-21.0.8+9"
|
||||
JRE="OpenJDK21U-jre_x64_linux_hotspot_$(echo "$JRE_RELEASE" | sed 's/jdk//;s/-//g;s/+/_/g').tar.gz"
|
||||
JRE_DIR="$JRE_RELEASE-jre"
|
||||
JRE_URL="https://github.com/adoptium/temurin21-binaries/releases/download/$JRE_RELEASE/$JRE"
|
||||
@@ -68,7 +84,7 @@ main() {
|
||||
;;
|
||||
macOS-x64)
|
||||
# https://github.com/adoptium/temurin21-binaries/releases/
|
||||
JRE_RELEASE="jdk-21.0.6+7"
|
||||
JRE_RELEASE="jdk-21.0.8+9"
|
||||
JRE="OpenJDK21U-jre_x64_mac_hotspot_$(echo "$JRE_RELEASE" | sed 's/jdk//;s/-//g;s/+/_/g').tar.gz"
|
||||
JRE_DIR="$JRE_RELEASE-jre"
|
||||
JRE_URL="https://github.com/adoptium/temurin21-binaries/releases/download/$JRE_RELEASE/$JRE"
|
||||
@@ -84,7 +100,7 @@ main() {
|
||||
;;
|
||||
macOS-arm64)
|
||||
# https://github.com/adoptium/temurin21-binaries/releases/
|
||||
JRE_RELEASE="jdk-21.0.6+7"
|
||||
JRE_RELEASE="jdk-21.0.8+9"
|
||||
JRE="OpenJDK21U-jre_aarch64_mac_hotspot_$(echo "$JRE_RELEASE" | sed 's/jdk//;s/-//g;s/+/_/g').tar.gz"
|
||||
JRE_DIR="$JRE_RELEASE-jre"
|
||||
JRE_URL="https://github.com/adoptium/temurin21-binaries/releases/download/$JRE_RELEASE/$JRE"
|
||||
@@ -100,7 +116,7 @@ main() {
|
||||
;;
|
||||
windows-x64)
|
||||
# https://github.com/adoptium/temurin21-binaries/releases/
|
||||
JRE_RELEASE="jdk-21.0.6+7"
|
||||
JRE_RELEASE="jdk-21.0.8+9"
|
||||
JRE="OpenJDK21U-jre_x64_windows_hotspot_$(echo "$JRE_RELEASE" | sed 's/jdk//;s/-//g;s/+/_/g').zip"
|
||||
JRE_DIR="$JRE_RELEASE-jre"
|
||||
JRE_URL="https://github.com/adoptium/temurin21-binaries/releases/download/$JRE_RELEASE/$JRE"
|
||||
@@ -184,6 +200,7 @@ make_linux_bundle() {
|
||||
cp "$JAR" "$RELEASE_NAME/bin/Suwayomi-Server.jar"
|
||||
cp "scripts/resources/suwayomi-launcher.sh" "$RELEASE_NAME/"
|
||||
cp "scripts/resources/suwayomi-server.sh" "$RELEASE_NAME/"
|
||||
cp "scripts/resources/catch_abort.so" "$RELEASE_NAME/bin/"
|
||||
|
||||
tar -I "gzip -9" -cvf "$RELEASE" "$RELEASE_NAME/"
|
||||
}
|
||||
@@ -208,6 +225,7 @@ make_deb_package() {
|
||||
mv "$RELEASE_NAME/Suwayomi-Launcher.jar" "$RELEASE_NAME/$source_dir/Suwayomi-Launcher.jar"
|
||||
cp "$JAR" "$RELEASE_NAME/$source_dir/Suwayomi-Server.jar"
|
||||
copy_linux_package_assets_to "$RELEASE_NAME/$source_dir/"
|
||||
cp "scripts/resources/catch_abort.so" "$RELEASE_NAME/$source_dir/"
|
||||
tar -I "gzip" -C "$RELEASE_NAME/" -cvf "$upstream_source" "$source_dir"
|
||||
|
||||
cp -r "scripts/resources/deb/" "$RELEASE_NAME/$source_dir/debian/"
|
||||
@@ -224,6 +242,25 @@ make_deb_package() {
|
||||
mv "$RELEASE_NAME/$deb" "$RELEASE"
|
||||
}
|
||||
|
||||
# https://linuxconfig.org/building-a-hello-world-appimage-on-linux
|
||||
make_appimage() {
|
||||
local APPIMAGE_URL="https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage"
|
||||
local APPIMAGE_TOOLNAME="appimagetool-x86_64.AppImage"
|
||||
mkdir "$RELEASE_NAME/bin/"
|
||||
cp "$JAR" "$RELEASE_NAME/bin/Suwayomi-Server.jar"
|
||||
|
||||
cp "scripts/resources/pkg/suwayomi-server.desktop" "$RELEASE_NAME/suwayomi-server.desktop"
|
||||
cp "server/src/main/resources/icon/faviconlogo.png" "$RELEASE_NAME/suwayomi-server.png"
|
||||
cp "scripts/resources/appimage/AppRun" "$RELEASE_NAME/AppRun"
|
||||
chmod +x "$RELEASE_NAME/AppRun"
|
||||
|
||||
sudo apt update
|
||||
sudo apt install libfuse2
|
||||
curl -L $APPIMAGE_URL -o $APPIMAGE_TOOLNAME
|
||||
chmod +x $APPIMAGE_TOOLNAME
|
||||
ARCH=x86_64 ./$APPIMAGE_TOOLNAME "$RELEASE_NAME" "$RELEASE"
|
||||
}
|
||||
|
||||
make_windows_bundle() {
|
||||
## I disabled this section until someone find a solution to this error:
|
||||
##E: Unable to correct problems, you have held broken packages.
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/sh
|
||||
|
||||
exec $APPDIR/jre/bin/java -jar $APPDIR/bin/Suwayomi-Server.jar
|
||||
@@ -0,0 +1,62 @@
|
||||
// Linux only:
|
||||
// Attempts to catch SIGTRAP, inform Java, then exit the thread instead of bringing down the whole process
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <dlfcn.h>
|
||||
#include <signal.h>
|
||||
#include <pthread.h>
|
||||
#include <execinfo.h>
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
JavaVM *g_vm;
|
||||
|
||||
void load_vm() {
|
||||
if (g_vm) return;
|
||||
JavaVM *vms[1];
|
||||
jsize n = 0;
|
||||
// JNI_OnLoad won't be called when loaded via LD_PRELOAD, so attempt to find the VM now
|
||||
if (JNI_GetCreatedJavaVMs(vms, 1, &n) == JNI_OK && n > 0) {
|
||||
g_vm = vms[0];
|
||||
}
|
||||
}
|
||||
|
||||
jint throwThreadDeath(JNIEnv *env, char *message) {
|
||||
char *className = "java/lang/UnknownError";
|
||||
jclass exClass = (*env)->FindClass(env, className);
|
||||
if (exClass == NULL) return JNI_ERR;
|
||||
return (*env)->ThrowNew(env, exClass, message);
|
||||
}
|
||||
|
||||
void signalHandler(int signum, siginfo_t* si, void* uc) {
|
||||
void *retaddrs[64];
|
||||
int n = backtrace(retaddrs, sizeof(retaddrs) / sizeof(retaddrs[0]));
|
||||
printf("\n### ABORT :: Backtrace: ###\n");
|
||||
backtrace_symbols_fd(retaddrs, n, STDERR_FILENO);
|
||||
printf("### ABORT :: Exiting this thread. If this causes problems, please report the above backtrace to Suwayomi. ###\n\n");
|
||||
|
||||
load_vm();
|
||||
if (g_vm) {
|
||||
JNIEnv *env;
|
||||
jint getEnvStat = (*g_vm)->GetEnv(g_vm, (void**) &env, JNI_VERSION_1_2);
|
||||
if (getEnvStat == JNI_EDETACHED) (*g_vm)->AttachCurrentThread(g_vm, (void**) &env, NULL);
|
||||
jint exStat = throwThreadDeath(env, "SIGTRAP caught");
|
||||
if (exStat != 0) printf("Exception throwing failed: %d\n", exStat);
|
||||
(*g_vm)->DetachCurrentThread(g_vm);
|
||||
}
|
||||
pthread_exit(NULL);
|
||||
}
|
||||
|
||||
__attribute__((constructor))
|
||||
void dlmain() {
|
||||
struct sigaction sa = {0};
|
||||
sa.sa_flags = SA_SIGINFO | SA_RESTART;
|
||||
sa.sa_sigaction = &signalHandler;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
if (sigaction(SIGTRAP, &sa, NULL) != 0) {
|
||||
printf("[FATAL] sigaction failed\n");
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ Homepage: https://github.com/Suwayomi/Suwayomi-Server
|
||||
|
||||
Package: suwayomi-server
|
||||
Architecture: all
|
||||
Depends: ${misc:Depends}, openjdk-21-jre, libc++-dev
|
||||
Depends: ${misc:Depends}, openjdk-21-jre | openjdk-21-jre-headless | openjdk-21-jdk | openjdk-21-jdk-headless | temurin-21-jre | temurin-21-jdk | zulu21-jre | zulu21-jre-headless | zulu21-jdk | zulu21-jdk-headless | msopenjdk-21 | java-21-amazon-corretto-jdk
|
||||
Description: Manga Reader
|
||||
A free and open source manga reader server that runs extensions built for Tachiyomi.
|
||||
Suwayomi is an independent Tachiyomi compatible software and is not a Fork of Tachiyomi.
|
||||
|
||||
@@ -11,3 +11,4 @@ suwayomi-server.tmpfiles => usr/lib/tmpfiles.d/suwayomi-server.conf
|
||||
suwayomi-server.conf => etc/suwayomi/server.conf
|
||||
suwayomi-server.sh => usr/bin/suwayomi-server
|
||||
suwayomi-launcher.sh => usr/bin/suwayomi-launcher
|
||||
catch_abort.so usr/share/java/suwayomi-server/bin/
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
#!/bin/sh
|
||||
|
||||
exec /usr/bin/java -jar /usr/share/java/suwayomi-server/bin/Suwayomi-Server.jar
|
||||
export LD_PRELOAD="/usr/share/java/suwayomi-server/bin/catch_abort.so"
|
||||
|
||||
if [ -z "$DISPLAY" ] && command -v Xvfb >/dev/null; then
|
||||
echo "-- START: Spawning X server using xvfb-run --"
|
||||
exec xvfb-run /usr/bin/java "$@" -jar /usr/share/java/suwayomi-server/bin/Suwayomi-Server.jar
|
||||
else
|
||||
exec /usr/bin/java "$@" -jar /usr/share/java/suwayomi-server/bin/Suwayomi-Server.jar
|
||||
fi
|
||||
|
||||
@@ -10,7 +10,7 @@ Group=suwayomi-server
|
||||
SyslogIdentifier=suwayomi-server
|
||||
|
||||
EnvironmentFile=/etc/suwayomi/server.conf
|
||||
ExecStart=/usr/bin/java $JAVA_ARGS -Dsuwayomi.tachidesk.config.server.rootDir="${TACHIDESK_ROOT_DIR}" -jar /usr/share/java/suwayomi-server/bin/Suwayomi-Server.jar
|
||||
ExecStart=/usr/bin/suwayomi-server $JAVA_ARGS -Dsuwayomi.tachidesk.config.server.rootDir="${TACHIDESK_ROOT_DIR}"
|
||||
Restart=on-failure
|
||||
|
||||
ProtectSystem=full
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/bin/sh
|
||||
|
||||
export LD_PRELOAD="`realpath ./bin/catch_abort.so`"
|
||||
exec ./jre/bin/java -jar ./bin/Suwayomi-Server.jar
|
||||
|
||||
+18
-1
@@ -25,6 +25,11 @@ plugins {
|
||||
.get()
|
||||
.pluginId,
|
||||
)
|
||||
id(
|
||||
libs.plugins.jte
|
||||
.get()
|
||||
.pluginId,
|
||||
)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -43,7 +48,6 @@ dependencies {
|
||||
// GraphQL
|
||||
implementation(libs.graphql.kotlin.server)
|
||||
implementation(libs.graphql.kotlin.scheme)
|
||||
implementation(libs.graphql.java.core)
|
||||
implementation(libs.graphql.java.scalars)
|
||||
|
||||
// Exposed ORM
|
||||
@@ -85,6 +89,9 @@ dependencies {
|
||||
implementation(projects.androidCompat)
|
||||
implementation(projects.androidCompat.config)
|
||||
|
||||
// i18n
|
||||
implementation(projects.server.i18n)
|
||||
|
||||
// uncomment to test extensions directly
|
||||
// implementation(fileTree("lib/"))
|
||||
implementation(kotlin("script-runtime"))
|
||||
@@ -94,6 +101,12 @@ dependencies {
|
||||
implementation(libs.cron4j)
|
||||
|
||||
implementation(libs.cronUtils)
|
||||
|
||||
compileOnly(libs.kte)
|
||||
}
|
||||
|
||||
jte {
|
||||
generate()
|
||||
}
|
||||
|
||||
application {
|
||||
@@ -210,4 +223,8 @@ tasks {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
runKtlintCheckOverMainSourceSet {
|
||||
mustRunAfter(generateJte)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonArray
|
||||
import com.google.gson.JsonObject
|
||||
|
||||
plugins {
|
||||
id(
|
||||
libs.plugins.kotlin.multiplatform
|
||||
.get()
|
||||
.pluginId,
|
||||
)
|
||||
id(
|
||||
libs.plugins.moko
|
||||
.get()
|
||||
.pluginId,
|
||||
)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvm()
|
||||
|
||||
sourceSets {
|
||||
getByName("jvmMain") {
|
||||
dependencies {
|
||||
api(libs.moko)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
multiplatformResources {
|
||||
resourcesPackage = "suwayomi.tachidesk.i18n"
|
||||
}
|
||||
|
||||
tasks {
|
||||
register("generateLocales") {
|
||||
group = "moko-resources"
|
||||
doFirst {
|
||||
val langs =
|
||||
listOf("en") +
|
||||
file("src/commonMain/moko-resources/values")
|
||||
.listFiles()
|
||||
?.map { it.name }
|
||||
?.minus("base")
|
||||
?.map { it.replace("-r", "-") }
|
||||
?.sorted()
|
||||
.orEmpty()
|
||||
|
||||
val langFile = file("src/commonMain/moko-resources/files/languages.json", PathValidation.NONE)
|
||||
if (langFile.exists()) {
|
||||
val currentLangs =
|
||||
langFile.reader().use {
|
||||
Gson()
|
||||
.fromJson(it, JsonObject::class.java)
|
||||
.getAsJsonArray("langs")
|
||||
.mapNotNull { it.asString }
|
||||
.toSet()
|
||||
}
|
||||
|
||||
if (currentLangs == langs.toSet()) return@doFirst
|
||||
}
|
||||
langFile.parentFile.mkdirs()
|
||||
|
||||
val json =
|
||||
JsonObject().apply {
|
||||
val array =
|
||||
JsonArray().apply {
|
||||
langs.forEach(::add)
|
||||
}
|
||||
add("langs", array)
|
||||
}
|
||||
|
||||
langFile.writer().use {
|
||||
Gson().toJson(json, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"langs":["en","de","es","ja","pl","pt","ta","vi","zh-CN"]}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<resources>
|
||||
<string name="opds_search_shortname">Suwayomi OPDS Search</string>
|
||||
<string name="opds_search_description">Search manga in the catalog</string>
|
||||
|
||||
<string name="opds_feeds_root">Suwayomi OPDS Catalog</string>
|
||||
<string name="opds_feeds_manga_chapters">%1$s Chapters</string>
|
||||
<string name="opds_feeds_chapter_details">%1$s | %2$s | Details</string>
|
||||
|
||||
<string name="opds_feeds_all_manga_title">All Manga</string>
|
||||
<string name="opds_feeds_all_manga_entry_content">Browse all manga in your library</string>
|
||||
|
||||
<string name="opds_feeds_search_results">Search Results</string>
|
||||
|
||||
<string name="opds_feeds_sources_title">Sources</string>
|
||||
<string name="opds_feeds_sources_entry_content">Browse manga by source</string>
|
||||
|
||||
<string name="opds_feeds_categories_title">Categories</string>
|
||||
<string name="opds_feeds_categories_entry_content">Browse manga organized by categories</string>
|
||||
|
||||
<string name="opds_feeds_genres_title">Genres</string>
|
||||
<string name="opds_feeds_genres_entry_content">Browse manga by genre tags</string>
|
||||
|
||||
<string name="opds_feeds_status_title">Status</string>
|
||||
<string name="opds_feeds_status_entry_content">Browse manga by publication status</string>
|
||||
|
||||
<string name="opds_feeds_languages_title">Languages</string>
|
||||
<string name="opds_feeds_languages_entry_content">Browse manga by content language</string>
|
||||
|
||||
<string name="opds_feeds_library_updates_title">Library Update History</string>
|
||||
<string name="opds_feeds_library_updates_entry_content">Recently updated chapters from your library</string>
|
||||
|
||||
<string name="opds_feeds_category_specific_title">Category: %1$s</string>
|
||||
<string name="opds_feeds_genre_specific_title">Genre: %1$s</string>
|
||||
<string name="opds_feeds_status_specific_title">Status: %1$s</string>
|
||||
<string name="opds_feeds_language_specific_title">Language: %1$s</string>
|
||||
<string name="opds_feeds_source_specific_title">Source: %1$s</string>
|
||||
|
||||
<string name="opds_error_manga_not_found">Manga with ID %1$d not found</string>
|
||||
<string name="opds_error_chapter_not_found">Chapter with index %1$d not found</string>
|
||||
|
||||
<string name="opds_facetgroup_sort_order">Sort Order</string>
|
||||
<string name="opds_facetgroup_read_status">Read Status</string>
|
||||
|
||||
<string name="opds_facet_sort_oldest_first">Oldest First</string>
|
||||
<string name="opds_facet_sort_newest_first">Newest First</string>
|
||||
<string name="opds_facet_sort_date_asc">Date ascending</string>
|
||||
<string name="opds_facet_sort_date_desc">Date descending</string>
|
||||
|
||||
<string name="opds_facet_filter_all_chapters">All Chapters</string>
|
||||
<string name="opds_facet_filter_unread_only">Unread Only</string>
|
||||
<string name="opds_facet_filter_read_only">Read Only</string>
|
||||
|
||||
<string name="opds_linktitle_view_chapter_details">View Chapter Details & Get Pages</string>
|
||||
<string name="opds_linktitle_download_cbz">Download CBZ</string>
|
||||
<string name="opds_linktitle_stream_pages">View Pages (Streaming)</string>
|
||||
<string name="opds_linktitle_chapter_cover">Chapter Cover</string>
|
||||
<string name="opds_linktitle_current_page">Current Page</string>
|
||||
<string name="opds_linktitle_catalog_root">Catalog Root</string>
|
||||
<string name="opds_linktitle_search_catalog">Search Catalog</string>
|
||||
<string name="opds_linktitle_previous_page">Previous Page</string>
|
||||
<string name="opds_linktitle_next_page">Next Page</string>
|
||||
<string name="opds_linktitle_self_feed">Current Feed</string>
|
||||
|
||||
<string name="opds_chapter_status_downloaded">⬇️ </string>
|
||||
<string name="opds_chapter_status_read">✅ </string>
|
||||
<string name="opds_chapter_status_in_progress">⌛ </string>
|
||||
<string name="opds_chapter_status_error">⚠️ </string>
|
||||
<string name="opds_chapter_status_unknown">❔ </string>
|
||||
<string name="opds_chapter_status_unread">⭕ </string>
|
||||
|
||||
<string name="opds_chapter_details_base">%1$s | %2$s</string>
|
||||
<string name="opds_chapter_details_scanlator"> | By %1$s</string>
|
||||
<string name="opds_chapter_details_progress"> | Progress: %1$d of %2$d</string>
|
||||
|
||||
<string name="manga_status_unknown">Unknown</string>
|
||||
<string name="manga_status_ongoing">Ongoing</string>
|
||||
<string name="manga_status_completed">Completed</string>
|
||||
<string name="manga_status_licensed">Licensed</string>
|
||||
<string name="manga_status_publishing_finished">Publishing Finished</string>
|
||||
<string name="manga_status_cancelled">Cancelled</string>
|
||||
<string name="manga_status_on_hiatus">On Hiatus</string>
|
||||
|
||||
<string name="label_error">Error</string>
|
||||
<string name="label_version">Version <xliff:g id="version" example="v2.0.1833">%1$s</xliff:g></string>
|
||||
|
||||
<string name="webview_label_title">Suwayomi WebView</string>
|
||||
<string name="webview_label_disconnected">Disconnected, please refresh</string>
|
||||
<string name="webview_label_reversescroll">Reverse Scrolling</string>
|
||||
<string name="webview_label_bindingshint">Note: While focus is on the WebView part, no keybinds, including refresh, will be handled by the browser.</string>
|
||||
<string name="webview_label_init">Initializing... Please wait</string>
|
||||
<string name="webview_label_getstarted">Enter a URL to get started</string>
|
||||
<string name="webview_label_loading">Loading page...</string>
|
||||
<string name="webview_placeholder_url">Enter URL...</string>
|
||||
|
||||
<string name="login_label_title">Suwayomi Login</string>
|
||||
<string name="login_label_username">Username</string>
|
||||
<string name="login_label_password">Password</string>
|
||||
<string name="login_label_login">Log In</string>
|
||||
<string name="login_placeholder_username">Type username...</string>
|
||||
<string name="login_placeholder_password">Secret...</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="opds_feeds_status_title">Status</string>
|
||||
<string name="opds_linktitle_current_page">Aktuelle Seite</string>
|
||||
<string name="opds_feeds_categories_title">Kategorien</string>
|
||||
<string name="opds_facet_filter_read_only">Nur gelesen</string>
|
||||
<string name="opds_feeds_genre_specific_title">Genre: %1$s</string>
|
||||
<string name="opds_feeds_all_manga_entry_content">Alle Manga in Bibliothek durchsuchen</string>
|
||||
<string name="opds_feeds_source_specific_title">Quelle: %1$s</string>
|
||||
<string name="opds_feeds_root">Suwayomi OPDS Katalog</string>
|
||||
<string name="opds_feeds_chapter_details">%1$s | %2$s | Details</string>
|
||||
<string name="opds_feeds_all_manga_title">Alle Manga</string>
|
||||
<string name="opds_feeds_search_results">Suchergebnisse</string>
|
||||
<string name="opds_feeds_sources_title">Quellen</string>
|
||||
<string name="opds_feeds_sources_entry_content">Manga nach Quelle durchsuchen</string>
|
||||
<string name="opds_feeds_genres_title">Genres</string>
|
||||
<string name="opds_feeds_genres_entry_content">Manga nach Genre durchsuchen</string>
|
||||
<string name="opds_feeds_status_entry_content">Manga nach Publikationsstatus durchsuchen</string>
|
||||
<string name="opds_feeds_languages_title">Sprachen</string>
|
||||
<string name="opds_feeds_languages_entry_content">Manga nach Inhaltssprache durchsuchen</string>
|
||||
<string name="opds_feeds_library_updates_title">Aktualisierungshistorie der Bibliothek</string>
|
||||
<string name="opds_feeds_library_updates_entry_content">Neulich aktualisierte Kapitel aus der Bibliothek</string>
|
||||
<string name="opds_feeds_category_specific_title">Kategorie: %1$s</string>
|
||||
<string name="opds_feeds_status_specific_title">Status: %1$s</string>
|
||||
<string name="opds_feeds_language_specific_title">Sprache: %1$s</string>
|
||||
<string name="opds_error_manga_not_found">Manga mit ID %1$d nicht gefunden</string>
|
||||
<string name="opds_facetgroup_sort_order">Sortieren nach</string>
|
||||
<string name="opds_facetgroup_read_status">Lese-Status</string>
|
||||
<string name="opds_facet_sort_oldest_first">Älteste zuerst</string>
|
||||
<string name="opds_facet_sort_newest_first">Neuste zuerst</string>
|
||||
<string name="opds_facet_sort_date_asc">Datum aufsteigend</string>
|
||||
<string name="opds_facet_sort_date_desc">Datum absteigend</string>
|
||||
<string name="opds_facet_filter_all_chapters">Alle Kapitel</string>
|
||||
<string name="opds_facet_filter_unread_only">Nur ungelesen</string>
|
||||
<string name="opds_linktitle_view_chapter_details">Kapitel Details ansehen & Seiten holen</string>
|
||||
<string name="opds_linktitle_download_cbz">Als CBZ herunterladen</string>
|
||||
<string name="opds_linktitle_stream_pages">Seiten anzeigen (Streaming)</string>
|
||||
<string name="opds_linktitle_search_catalog">Katalog durchsuchen</string>
|
||||
<string name="opds_linktitle_previous_page">Vorherige Seite</string>
|
||||
<string name="opds_linktitle_next_page">Nächste Seite</string>
|
||||
<string name="opds_linktitle_self_feed">Aktueller Feed</string>
|
||||
<string name="opds_chapter_status_downloaded">⬇️</string>
|
||||
<string name="opds_chapter_status_in_progress">⌛</string>
|
||||
<string name="opds_chapter_status_error">⚠️</string>
|
||||
<string name="opds_chapter_status_unread">⭕</string>
|
||||
<string name="opds_chapter_details_scanlator">| Von %1$s</string>
|
||||
<string name="manga_status_unknown">Unbekannt</string>
|
||||
<string name="manga_status_ongoing">Laufend</string>
|
||||
<string name="manga_status_completed">Abgeschlossen</string>
|
||||
<string name="manga_status_licensed">Lizenziert</string>
|
||||
<string name="manga_status_publishing_finished">Herausgabe abgeschlossen</string>
|
||||
<string name="manga_status_cancelled">Abgebrochen</string>
|
||||
<string name="manga_status_on_hiatus">Hiatus</string>
|
||||
<string name="opds_error_chapter_not_found">Kapitel mit Index %1$d nicht gefunden</string>
|
||||
<string name="opds_search_shortname">Suwayomi OPDS Suche</string>
|
||||
<string name="opds_search_description">Manga im Katalog suchen</string>
|
||||
<string name="opds_feeds_manga_chapters">%1$s Kapitel</string>
|
||||
<string name="opds_chapter_details_progress">| Fortschritt: %1$d von %2$d</string>
|
||||
<string name="opds_feeds_categories_entry_content">Manga nach Kategorien organisiert durchsuchen</string>
|
||||
<string name="opds_chapter_details_base">%1$s | %2$s</string>
|
||||
<string name="opds_linktitle_chapter_cover">Kapitel Cover</string>
|
||||
<string name="opds_linktitle_catalog_root">Katalog Root</string>
|
||||
<string name="opds_chapter_status_read">✅</string>
|
||||
<string name="opds_chapter_status_unknown">❔</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="opds_search_shortname">Búsqueda OPDS de Suwayomi</string>
|
||||
<string name="opds_search_description">Buscar mangas en el catálogo</string>
|
||||
<string name="opds_feeds_root">Catálogo OPDS de Suwayomi</string>
|
||||
<string name="opds_feeds_manga_chapters">Capítulos de %1$s</string>
|
||||
<string name="opds_feeds_chapter_details">%1$s | Detalles de %2$s</string>
|
||||
<string name="opds_feeds_all_manga_title">Todos los mangas</string>
|
||||
<string name="opds_feeds_all_manga_entry_content">Explorar todos los mangas en tu biblioteca</string>
|
||||
<string name="opds_feeds_search_results">Resultados de búsqueda</string>
|
||||
<string name="opds_feeds_sources_title">Fuentes</string>
|
||||
<string name="opds_feeds_sources_entry_content">Explorar mangas por fuente</string>
|
||||
<string name="opds_feeds_categories_title">Categorías</string>
|
||||
<string name="opds_feeds_categories_entry_content">Explorar mangas organizados por categorías</string>
|
||||
<string name="opds_feeds_genres_title">Géneros</string>
|
||||
<string name="opds_feeds_genres_entry_content">Explorar mangas por etiquetas de género</string>
|
||||
<string name="opds_feeds_status_title">Estado</string>
|
||||
<string name="opds_feeds_status_entry_content">Explorar mangas por estado de publicación</string>
|
||||
<string name="opds_feeds_languages_title">Idiomas</string>
|
||||
<string name="opds_feeds_languages_entry_content">Explorar mangas por idioma del contenido</string>
|
||||
<string name="opds_feeds_library_updates_title">Historial de actualizaciones</string>
|
||||
<string name="opds_feeds_library_updates_entry_content">Capítulos recientemente actualizados de tu biblioteca</string>
|
||||
<string name="opds_feeds_category_specific_title">Categoría: %1$s</string>
|
||||
<string name="opds_feeds_genre_specific_title">Género: %1$s</string>
|
||||
<string name="opds_feeds_status_specific_title">Estado: %1$s</string>
|
||||
<string name="opds_feeds_language_specific_title">Idioma: %1$s</string>
|
||||
<string name="opds_feeds_source_specific_title">Fuente: %1$s</string>
|
||||
<string name="opds_facetgroup_sort_order">Ordenar por</string>
|
||||
<string name="opds_facetgroup_read_status">Estado de lectura</string>
|
||||
<string name="opds_error_manga_not_found">Manga con ID %1$d no encontrado</string>
|
||||
<string name="opds_error_chapter_not_found">Capítulo con índice %1$d no encontrado</string>
|
||||
<string name="opds_facet_sort_oldest_first">Más antiguos primero</string>
|
||||
<string name="opds_facet_sort_newest_first">Más recientes primero</string>
|
||||
<string name="opds_facet_sort_date_asc">Fecha ascendente</string>
|
||||
<string name="opds_facet_sort_date_desc">Fecha descendente</string>
|
||||
<string name="opds_facet_filter_all_chapters">Todos los capítulos</string>
|
||||
<string name="opds_facet_filter_unread_only">Solo sin leer</string>
|
||||
<string name="opds_facet_filter_read_only">Solo leídos</string>
|
||||
<string name="opds_linktitle_view_chapter_details">Ver detalles del capítulo y obtener páginas</string>
|
||||
<string name="opds_linktitle_download_cbz">Descargar CBZ</string>
|
||||
<string name="opds_linktitle_stream_pages">Ver páginas (streaming)</string>
|
||||
<string name="opds_linktitle_chapter_cover">Portada del capítulo</string>
|
||||
<string name="opds_linktitle_current_page">Página actual</string>
|
||||
<string name="opds_linktitle_catalog_root">Raíz del catálogo</string>
|
||||
<string name="opds_linktitle_search_catalog">Buscar en catálogo</string>
|
||||
<string name="opds_linktitle_previous_page">Página anterior</string>
|
||||
<string name="opds_linktitle_next_page">Página siguiente</string>
|
||||
<string name="opds_linktitle_self_feed">Feed actual</string>
|
||||
<string name="opds_chapter_status_downloaded">⬇️ </string>
|
||||
<string name="opds_chapter_status_read">✅ </string>
|
||||
<string name="opds_chapter_status_in_progress">⌛ </string>
|
||||
<string name="opds_chapter_status_error">⚠️ </string>
|
||||
<string name="opds_chapter_status_unknown">❔ </string>
|
||||
<string name="opds_chapter_status_unread">⭕ </string>
|
||||
<string name="opds_chapter_details_base">Manga: %1$s | %2$s</string>
|
||||
<string name="opds_chapter_details_scanlator"> | Publicado por: %1$s</string>
|
||||
<string name="opds_chapter_details_progress"> | Progreso: %1$d de %2$d</string>
|
||||
<string name="manga_status_unknown">Desconocido</string>
|
||||
<string name="manga_status_ongoing">En emisión</string>
|
||||
<string name="manga_status_completed">Completado</string>
|
||||
<string name="manga_status_licensed">Licenciado</string>
|
||||
<string name="manga_status_publishing_finished">Publicación finalizada</string>
|
||||
<string name="manga_status_cancelled">Cancelado</string>
|
||||
<string name="manga_status_on_hiatus">En pausa</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="opds_linktitle_search_catalog">カタログ検索</string>
|
||||
<string name="manga_status_unknown">不明</string>
|
||||
<string name="opds_linktitle_stream_pages">ページを表示 (ストリーミング)</string>
|
||||
<string name="opds_linktitle_next_page">次のページ</string>
|
||||
<string name="opds_search_shortname">Suwayomi OPDS検索</string>
|
||||
<string name="opds_feeds_genres_entry_content">ジャンルタグでマンガをブラウズする</string>
|
||||
<string name="opds_feeds_category_specific_title">カテゴリー: %1$s</string>
|
||||
<string name="opds_feeds_library_updates_entry_content">ライブラリから最近更新された章</string>
|
||||
<string name="opds_feeds_status_entry_content">出版状況でマンガを閲覧</string>
|
||||
<string name="opds_feeds_source_specific_title">ソース: %1$s</string>
|
||||
<string name="opds_feeds_manga_chapters">%1$s 章</string>
|
||||
<string name="opds_search_description">カタログ内でマンガを検索する</string>
|
||||
<string name="opds_feeds_chapter_details">%1$s | %2$s | 詳細</string>
|
||||
<string name="opds_feeds_all_manga_entry_content">ライブラリ内のすべてのマンガを閲覧</string>
|
||||
<string name="opds_feeds_sources_entry_content">ソースでマンガをブラウズ</string>
|
||||
<string name="opds_feeds_categories_title">カテゴリー</string>
|
||||
<string name="opds_feeds_categories_entry_content">カテゴリー別にマンガを閲覧</string>
|
||||
<string name="opds_feeds_genres_title">ジャンル</string>
|
||||
<string name="opds_feeds_status_title">ステータス</string>
|
||||
<string name="opds_feeds_languages_title">言語</string>
|
||||
<string name="opds_feeds_library_updates_title">ライブラリ更新履歴</string>
|
||||
<string name="opds_feeds_genre_specific_title">ジャンル: %1$s</string>
|
||||
<string name="opds_feeds_status_specific_title">ステータス: %1$s</string>
|
||||
<string name="opds_error_chapter_not_found">インデックス %1$d の章が見つかりません</string>
|
||||
<string name="opds_facetgroup_sort_order">並び替え</string>
|
||||
<string name="opds_facet_filter_all_chapters">すべての章</string>
|
||||
<string name="opds_linktitle_download_cbz">CBZをダウンロード</string>
|
||||
<string name="opds_linktitle_chapter_cover">チャプターカバー</string>
|
||||
<string name="opds_linktitle_previous_page">前のページ</string>
|
||||
<string name="opds_chapter_status_downloaded">⬇️</string>
|
||||
<string name="opds_chapter_status_read">✅</string>
|
||||
<string name="opds_chapter_status_in_progress">⌛</string>
|
||||
<string name="opds_chapter_status_unknown">❔</string>
|
||||
<string name="opds_chapter_details_base">%1$s | %2$s</string>
|
||||
<string name="manga_status_on_hiatus">休止中</string>
|
||||
<string name="opds_error_manga_not_found">ID %1$d のマンガが見つかりません</string>
|
||||
<string name="opds_feeds_root">Suwayomi OPDSカタログ</string>
|
||||
<string name="opds_facetgroup_read_status">読書状況</string>
|
||||
<string name="opds_feeds_all_manga_title">すべてのマンガ</string>
|
||||
<string name="opds_feeds_search_results">検索結果</string>
|
||||
<string name="opds_feeds_sources_title">ソース</string>
|
||||
<string name="opds_linktitle_catalog_root">カタログトップ</string>
|
||||
<string name="opds_facet_filter_unread_only">未読のみ</string>
|
||||
<string name="opds_chapter_status_error">⚠️</string>
|
||||
<string name="opds_linktitle_current_page">現在のページ</string>
|
||||
<string name="opds_chapter_status_unread">⭕</string>
|
||||
<string name="opds_feeds_language_specific_title">言語: %1$s</string>
|
||||
<string name="opds_feeds_languages_entry_content">コンテンツの言語でマンガを閲覧</string>
|
||||
<string name="opds_facet_sort_oldest_first">古い順</string>
|
||||
<string name="opds_facet_sort_newest_first">新しい順</string>
|
||||
<string name="opds_facet_sort_date_asc">日付昇順</string>
|
||||
<string name="opds_facet_sort_date_desc">日付降順</string>
|
||||
<string name="opds_facet_filter_read_only">既読のみ</string>
|
||||
<string name="opds_linktitle_view_chapter_details">詳細を表示・ページ取得</string>
|
||||
<string name="opds_linktitle_self_feed">現在のページ</string>
|
||||
<string name="opds_chapter_details_scanlator">| 翻訳 %1$s</string>
|
||||
<string name="opds_chapter_details_progress">| 読書進捗: %1$d / %2$d</string>
|
||||
<string name="manga_status_ongoing">連載中</string>
|
||||
<string name="manga_status_completed">完結済み</string>
|
||||
<string name="manga_status_licensed">正式版</string>
|
||||
<string name="manga_status_publishing_finished">連載終了</string>
|
||||
<string name="manga_status_cancelled">打ち切り</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="opds_search_description">Wyszukiwanie mangi w katalogu</string>
|
||||
<string name="manga_status_on_hiatus">Zawieszone</string>
|
||||
<string name="opds_feeds_genre_specific_title">Gatunek: %1$s</string>
|
||||
<string name="opds_feeds_chapter_details">%1$s | %2$s | Szczegóły</string>
|
||||
<string name="opds_chapter_details_base">%1$s | %2$s</string>
|
||||
<string name="opds_chapter_status_error">⚠️</string>
|
||||
<string name="opds_feeds_library_updates_title">Historia Aktualizacji Biblioteki</string>
|
||||
<string name="opds_feeds_categories_entry_content">Przeglądaj mangi uporządkowane według kategorii</string>
|
||||
<string name="opds_chapter_status_downloaded">⬇️</string>
|
||||
<string name="opds_chapter_status_unknown">❔</string>
|
||||
<string name="opds_linktitle_self_feed">Aktualny Kanał</string>
|
||||
<string name="opds_chapter_status_unread">⭕</string>
|
||||
<string name="opds_feeds_all_manga_entry_content">Przeglądaj wszystkie mangi w swojej bibliotece</string>
|
||||
<string name="opds_linktitle_next_page">Następna Strona</string>
|
||||
<string name="opds_feeds_manga_chapters">%1$s Rozdziały</string>
|
||||
<string name="opds_feeds_all_manga_title">Wszystkie mangi</string>
|
||||
<string name="opds_search_shortname">Suwayomi Wyszukiwanie OPDS</string>
|
||||
<string name="opds_feeds_root">Suwayomi Katalog OPDS</string>
|
||||
<string name="opds_feeds_search_results">Wyniki wyszukiwania</string>
|
||||
<string name="opds_feeds_sources_title">Źródła</string>
|
||||
<string name="opds_feeds_sources_entry_content">Przeglądaj mangi według źródła</string>
|
||||
<string name="opds_feeds_genres_title">Gatunki</string>
|
||||
<string name="opds_feeds_status_title">Status</string>
|
||||
<string name="opds_feeds_languages_title">Języki</string>
|
||||
<string name="opds_feeds_languages_entry_content">Przeglądaj mangi według języka treści</string>
|
||||
<string name="opds_feeds_library_updates_entry_content">Ostatnio zaktualizowane rozdziały z biblioteki</string>
|
||||
<string name="opds_feeds_category_specific_title">Kategoria: %1$s</string>
|
||||
<string name="opds_feeds_status_specific_title">Status: %1$s</string>
|
||||
<string name="opds_feeds_language_specific_title">Język: %1$s</string>
|
||||
<string name="opds_feeds_source_specific_title">Źródło: %1$s</string>
|
||||
<string name="opds_error_manga_not_found">Nie znaleziono mangi o identyfikatorze %1$d</string>
|
||||
<string name="opds_error_chapter_not_found">Nie znaleziono rozdziału z indeksem %1$d</string>
|
||||
<string name="opds_facetgroup_sort_order">Kolejność Sortowania</string>
|
||||
<string name="opds_facetgroup_read_status">Status Czytania</string>
|
||||
<string name="opds_facet_sort_oldest_first">Najpierw Najstarszy</string>
|
||||
<string name="opds_facet_sort_newest_first">Najpierw Najnowszy</string>
|
||||
<string name="opds_facet_sort_date_asc">Data rosnąco</string>
|
||||
<string name="opds_facet_sort_date_desc">Data malejąco</string>
|
||||
<string name="opds_facet_filter_all_chapters">Wszystkie Rozdziały</string>
|
||||
<string name="opds_facet_filter_unread_only">Tylko Nieprzeczytane</string>
|
||||
<string name="opds_facet_filter_read_only">Tylko Przeczytane</string>
|
||||
<string name="opds_linktitle_view_chapter_details">Wyświetl Szczegóły Rozdziału i Pobierz Strony</string>
|
||||
<string name="opds_linktitle_download_cbz">Pobierz CBZ</string>
|
||||
<string name="opds_linktitle_stream_pages">Wyświetlanie Stron (Strumieniowanie)</string>
|
||||
<string name="opds_linktitle_chapter_cover">Okładka Rozdziału</string>
|
||||
<string name="opds_linktitle_current_page">Bieżąca Strona</string>
|
||||
<string name="opds_linktitle_catalog_root">Katalog Główny</string>
|
||||
<string name="opds_linktitle_search_catalog">Katalog Wyszukiwania</string>
|
||||
<string name="opds_linktitle_previous_page">Poprzednia Strona</string>
|
||||
<string name="opds_chapter_status_read">✅</string>
|
||||
<string name="opds_chapter_status_in_progress">⌛</string>
|
||||
<string name="opds_chapter_details_scanlator">| Przez %1$s</string>
|
||||
<string name="opds_chapter_details_progress">| Postęp: %1$d z %2$d</string>
|
||||
<string name="manga_status_unknown">Nieznany</string>
|
||||
<string name="manga_status_ongoing">W trakcie</string>
|
||||
<string name="manga_status_completed">Zakończono</string>
|
||||
<string name="manga_status_licensed">Licencjonowano</string>
|
||||
<string name="manga_status_publishing_finished">Publikacja Zakończona</string>
|
||||
<string name="manga_status_cancelled">Anulowano</string>
|
||||
<string name="opds_feeds_categories_title">Kategorie</string>
|
||||
<string name="opds_feeds_genres_entry_content">Przeglądaj mangi według tagów gatunku</string>
|
||||
<string name="opds_feeds_status_entry_content">Przeglądaj mangi według statusu publikacji</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="opds_search_description">Pesquisar mangá no catálogo</string>
|
||||
<string name="opds_feeds_manga_chapters">%1$s Capítulos</string>
|
||||
<string name="opds_feeds_chapter_details">%1$s | %2$s | Detalhes</string>
|
||||
<string name="opds_feeds_all_manga_title">Todos os Mangás</string>
|
||||
<string name="opds_feeds_search_results">Resultados da pesquisa</string>
|
||||
<string name="opds_feeds_categories_title">Categorias</string>
|
||||
<string name="opds_feeds_genres_title">Gêneros</string>
|
||||
<string name="opds_feeds_languages_title">Idiomas</string>
|
||||
<string name="opds_feeds_category_specific_title">Categoria: %1$s</string>
|
||||
<string name="opds_feeds_language_specific_title">Idioma: %1$s</string>
|
||||
<string name="opds_feeds_source_specific_title">Fonte: %1$s</string>
|
||||
<string name="opds_feeds_sources_title">Fontes</string>
|
||||
<string name="opds_feeds_genre_specific_title">Gênero: %1$s</string>
|
||||
<string name="opds_feeds_all_manga_entry_content">Explorar todos mangás na sua biblioteca</string>
|
||||
<string name="opds_feeds_sources_entry_content">Explorar mangás por fonte</string>
|
||||
<string name="opds_feeds_categories_entry_content">Explorar mangás organizados por categorias</string>
|
||||
<string name="opds_feeds_genres_entry_content">Explorar mangás por marcadores de gênero</string>
|
||||
<string name="opds_feeds_status_title">Status</string>
|
||||
<string name="opds_feeds_status_entry_content">Explorar mangás por status de publicação</string>
|
||||
<string name="opds_feeds_languages_entry_content">Explorar mangás por idioma do conteúdo</string>
|
||||
<string name="opds_feeds_library_updates_title">Histórico de atualização da biblioteca</string>
|
||||
<string name="opds_feeds_library_updates_entry_content">Capítulos de sua biblioteca recentemente atualizados</string>
|
||||
<string name="opds_feeds_status_specific_title">Status: %1$s</string>
|
||||
<string name="opds_feeds_root">Catálogo OPDS do Suwayomi</string>
|
||||
<string name="opds_search_shortname">Pesquisa no Suwayomi OPDS</string>
|
||||
<string name="opds_error_manga_not_found">Manga com ID %1$d não encontrado</string>
|
||||
<string name="opds_error_chapter_not_found">Capítulo com índice %1$d não encontrado</string>
|
||||
<string name="opds_facetgroup_sort_order">Ordenar por</string>
|
||||
<string name="opds_facetgroup_read_status">Status de leitura</string>
|
||||
<string name="opds_facet_sort_oldest_first">O mais velho primeiro</string>
|
||||
<string name="opds_facet_sort_newest_first">O mais novo primeiro</string>
|
||||
<string name="opds_facet_sort_date_asc">Data ascendente</string>
|
||||
<string name="opds_facet_sort_date_desc">Data descendente</string>
|
||||
<string name="opds_facet_filter_all_chapters">Todos os capítulos</string>
|
||||
<string name="opds_facet_filter_unread_only">Somente não lidos</string>
|
||||
<string name="opds_facet_filter_read_only">Somente lidos</string>
|
||||
<string name="opds_linktitle_download_cbz">Baixar como CBZ</string>
|
||||
<string name="opds_linktitle_view_chapter_details">Ver detalhes do capítulo & Obter Páginas</string>
|
||||
<string name="opds_linktitle_stream_pages">Ver Páginas (Streaming)</string>
|
||||
<string name="opds_linktitle_chapter_cover">Capa do capítulo</string>
|
||||
<string name="opds_linktitle_current_page">Página atual</string>
|
||||
<string name="opds_linktitle_catalog_root">Raiz do catálogo</string>
|
||||
<string name="opds_linktitle_search_catalog">Pesquisar no catálogo</string>
|
||||
<string name="opds_linktitle_previous_page">Página anterior</string>
|
||||
<string name="opds_linktitle_next_page">Página seguinte</string>
|
||||
<string name="opds_chapter_status_downloaded">⬇️</string>
|
||||
<string name="opds_linktitle_self_feed">Feed atual</string>
|
||||
<string name="opds_chapter_status_read">✅</string>
|
||||
<string name="opds_chapter_status_in_progress">⌛</string>
|
||||
<string name="opds_chapter_status_error">⚠️</string>
|
||||
<string name="opds_chapter_status_unknown">❔</string>
|
||||
<string name="opds_chapter_status_unread">⭕</string>
|
||||
<string name="opds_chapter_details_base">%1$s | %2$s</string>
|
||||
<string name="opds_chapter_details_progress">| Progresso: %1$d de %2$d</string>
|
||||
<string name="manga_status_unknown">Desconhecido</string>
|
||||
<string name="manga_status_ongoing">Em andamento</string>
|
||||
<string name="manga_status_completed">Completo</string>
|
||||
<string name="manga_status_licensed">Licenciado</string>
|
||||
<string name="manga_status_publishing_finished">Publicação concluída</string>
|
||||
<string name="manga_status_cancelled">Cancelado</string>
|
||||
<string name="manga_status_on_hiatus">Em Hiatus</string>
|
||||
<string name="opds_chapter_details_scanlator">| Por %1$s</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="opds_feeds_search_results">தேடல் முடிவுகள்</string>
|
||||
<string name="opds_feeds_status_title">நிலை</string>
|
||||
<string name="opds_feeds_genres_title">வகைகள்</string>
|
||||
<string name="opds_feeds_status_entry_content">வெளியீட்டு நிலை மூலம் மங்காவை உலாவுக</string>
|
||||
<string name="opds_linktitle_catalog_root">அட்டவணை வேர்</string>
|
||||
<string name="opds_linktitle_search_catalog">தேடல் பட்டியல்</string>
|
||||
<string name="opds_linktitle_previous_page">முந்தைய பக்கம்</string>
|
||||
<string name="opds_linktitle_next_page">அடுத்த பக்கம்</string>
|
||||
<string name="manga_status_completed">முடிந்தது</string>
|
||||
<string name="opds_chapter_details_scanlator">| %1$s மூலம்</string>
|
||||
<string name="opds_chapter_details_progress">| முன்னேற்றம்: %1$d %2$d</string>
|
||||
<string name="manga_status_on_hiatus">இடைவெளியில்</string>
|
||||
<string name="opds_feeds_categories_title">வகைகள்</string>
|
||||
<string name="opds_feeds_languages_title">மொழிகள்</string>
|
||||
<string name="manga_status_cancelled">ரத்து செய்யப்பட்டது</string>
|
||||
<string name="manga_status_licensed">உரிமம்</string>
|
||||
<string name="manga_status_ongoing">நடந்து கொண்டிருக்கிறது</string>
|
||||
<string name="manga_status_publishing_finished">வெளியீடு முடிந்தது</string>
|
||||
<string name="manga_status_unknown">தெரியவில்லை</string>
|
||||
<string name="opds_feeds_sources_title">மூலங்கள்</string>
|
||||
<string name="opds_feeds_root">சுவாவோமி OPDS பட்டியல்</string>
|
||||
<string name="opds_search_shortname">சுவோவோமி OPDS தேடல்</string>
|
||||
<string name="opds_search_description">பட்டியலில் மங்காவைத் தேடுங்கள்</string>
|
||||
<string name="opds_feeds_manga_chapters">%1$s அத்தியாயங்கள்</string>
|
||||
<string name="opds_feeds_chapter_details">%1$s | %2$s | விவரங்கள்</string>
|
||||
<string name="opds_feeds_all_manga_title">அனைத்து மங்கா</string>
|
||||
<string name="opds_feeds_all_manga_entry_content">உங்கள் நூலகத்தில் அனைத்து மங்காவையும் உலாவுக</string>
|
||||
<string name="opds_feeds_sources_entry_content">மங்காவை மூலத்தால் உலாவுக</string>
|
||||
<string name="opds_feeds_categories_entry_content">வகைகளால் ஏற்பாடு செய்யப்பட்ட மங்காவை உலாவுக</string>
|
||||
<string name="opds_feeds_genres_entry_content">வகை குறிச்சொற்களால் மங்காவை உலாவுக</string>
|
||||
<string name="opds_feeds_languages_entry_content">உள்ளடக்க மொழியால் மங்காவை உலாவுக</string>
|
||||
<string name="opds_feeds_library_updates_title">நூலக புதுப்பிப்பு வரலாறு</string>
|
||||
<string name="opds_feeds_library_updates_entry_content">உங்கள் நூலகத்திலிருந்து அண்மைக் காலத்தில் புதுப்பிக்கப்பட்ட அத்தியாயங்கள்</string>
|
||||
<string name="opds_feeds_category_specific_title">வகை: %1$s</string>
|
||||
<string name="opds_feeds_language_specific_title">மொழி: %1$s</string>
|
||||
<string name="opds_feeds_status_specific_title">நிலை: %1$s</string>
|
||||
<string name="opds_feeds_source_specific_title">ஆதாரம்: %1$s</string>
|
||||
<string name="opds_error_manga_not_found">அடையாளம் %1$d உடன் மங்கா காணப்படவில்லை</string>
|
||||
<string name="opds_error_chapter_not_found">குறியீட்டு %1$d உடன் அத்தியாயம் காணப்படவில்லை</string>
|
||||
<string name="opds_facetgroup_sort_order">வரிசைப்படுத்தும் முறை</string>
|
||||
<string name="opds_facetgroup_read_status">நிலையைப் படியுங்கள்</string>
|
||||
<string name="opds_facet_sort_oldest_first">முதலில் பழமையானது</string>
|
||||
<string name="opds_facet_sort_newest_first">புதிய முதல்</string>
|
||||
<string name="opds_facet_sort_date_asc">தேதி ஏறுதல்</string>
|
||||
<string name="opds_facet_sort_date_desc">தேதி இறங்கு</string>
|
||||
<string name="opds_facet_filter_all_chapters">அனைத்து அத்தியாயங்களும்</string>
|
||||
<string name="opds_facet_filter_unread_only">படிக்க மட்டும்</string>
|
||||
<string name="opds_facet_filter_read_only">படிக்கவும்</string>
|
||||
<string name="opds_linktitle_view_chapter_details">அத்தியாயம் விவரங்களைக் காண்க & பக்கங்களைப் பெறுங்கள்</string>
|
||||
<string name="opds_linktitle_download_cbz">CBZ ஐ பதிவிறக்கவும்</string>
|
||||
<string name="opds_linktitle_stream_pages">பக்கங்களைக் காண்க (ச்ட்ரீமிங்)</string>
|
||||
<string name="opds_linktitle_chapter_cover">அத்தியாயம் கவர்</string>
|
||||
<string name="opds_linktitle_current_page">தற்போதைய பக்கம்</string>
|
||||
<string name="opds_linktitle_self_feed">தற்போதைய ஊட்டம்</string>
|
||||
<string name="opds_chapter_status_downloaded">⬇️</string>
|
||||
<string name="opds_chapter_status_read">✅</string>
|
||||
<string name="opds_chapter_status_in_progress">⌛</string>
|
||||
<string name="opds_chapter_status_error">⚠️</string>
|
||||
<string name="opds_chapter_status_unknown">❔</string>
|
||||
<string name="opds_chapter_status_unread">⭕</string>
|
||||
<string name="opds_chapter_details_base">%1$s | %2$s</string>
|
||||
<string name="opds_feeds_genre_specific_title">இசைவகை: %1$s</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="opds_feeds_search_results">Kết quả tìm kiếm</string>
|
||||
<string name="opds_feeds_all_manga_entry_content">Duyệt tất cả manga trong thư viện của bạn</string>
|
||||
<string name="opds_feeds_all_manga_title">Tất cả Manga</string>
|
||||
<string name="opds_search_description">Tìm kiếm manga trong danh mục</string>
|
||||
<string name="opds_feeds_chapter_details">%1$s | %2$s | Chi tiết</string>
|
||||
<string name="opds_feeds_categories_title">Danh mục</string>
|
||||
<string name="opds_feeds_sources_entry_content">Duyệt manga theo nguồn</string>
|
||||
<string name="opds_feeds_categories_entry_content">Duyệt manga được sắp xếp theo danh mục</string>
|
||||
<string name="opds_feeds_genres_title">Thể loại</string>
|
||||
<string name="opds_feeds_root">Suwayomi OPDS Danh mục</string>
|
||||
<string name="opds_feeds_sources_title">Nguồn</string>
|
||||
<string name="opds_search_shortname">Suwayomi OPDS Tìm kiếm</string>
|
||||
<string name="opds_feeds_manga_chapters">%1$s Chương</string>
|
||||
<string name="opds_feeds_genres_entry_content">Duyệt manga theo thể loại</string>
|
||||
<string name="opds_feeds_status_title">Trạng thái</string>
|
||||
<string name="opds_feeds_languages_title">Ngôn ngữ</string>
|
||||
<string name="opds_feeds_languages_entry_content">Duyệt manga theo ngôn ngữ</string>
|
||||
<string name="opds_feeds_library_updates_entry_content">Các chương mới cập nhật gần đây từ thư viện của bạn</string>
|
||||
<string name="manga_status_ongoing">Đang tiến hành</string>
|
||||
<string name="manga_status_unknown">Không rõ</string>
|
||||
<string name="manga_status_completed">Đã hoàn thành</string>
|
||||
<string name="manga_status_on_hiatus">Đang tạm ngưng</string>
|
||||
<string name="opds_feeds_status_entry_content">Duyệt manga theo trạng thái xuất bản</string>
|
||||
<string name="opds_feeds_library_updates_title">Lịch sử cập nhật thư viện</string>
|
||||
<string name="opds_feeds_category_specific_title">Danh mục: %1$s</string>
|
||||
<string name="opds_feeds_genre_specific_title">Thể loại: %1$s</string>
|
||||
<string name="opds_feeds_status_specific_title">Trạng thái: %1$s</string>
|
||||
<string name="opds_feeds_language_specific_title">Ngôn ngữ: %1$s</string>
|
||||
<string name="opds_feeds_source_specific_title">Nguồn: %1$s</string>
|
||||
<string name="opds_error_manga_not_found">Manga có ID %1$d không tìm thấy</string>
|
||||
<string name="opds_error_chapter_not_found">Chương có chỉ mục %1$d không tìm thấy</string>
|
||||
<string name="opds_facetgroup_sort_order">Thứ tự sắp xếp</string>
|
||||
<string name="opds_facetgroup_read_status">Đọc trạng thái</string>
|
||||
<string name="opds_facet_sort_oldest_first">Cũ nhất trước</string>
|
||||
<string name="opds_facet_sort_newest_first">Mới nhất trước</string>
|
||||
<string name="opds_facet_sort_date_asc">Ngày tăng dần</string>
|
||||
<string name="opds_facet_filter_unread_only">Chỉ chưa đọc</string>
|
||||
<string name="opds_facet_filter_read_only">Chỉ đọc</string>
|
||||
<string name="opds_linktitle_stream_pages">Xem trang (Trực tuyến)</string>
|
||||
<string name="opds_linktitle_chapter_cover">Bìa chương</string>
|
||||
<string name="opds_linktitle_current_page">Trang hiện tại</string>
|
||||
<string name="opds_linktitle_catalog_root">Danh mục gốc</string>
|
||||
<string name="opds_linktitle_search_catalog">Tìm kiếm danh mục</string>
|
||||
<string name="opds_linktitle_previous_page">Trang trước</string>
|
||||
<string name="opds_linktitle_next_page">Trang tiếp theo</string>
|
||||
<string name="opds_chapter_status_downloaded">⬇️</string>
|
||||
<string name="opds_chapter_status_read">✅</string>
|
||||
<string name="opds_chapter_status_unknown">❔</string>
|
||||
<string name="opds_chapter_status_unread">⭕</string>
|
||||
<string name="opds_chapter_details_scanlator">| Bởi %1$s</string>
|
||||
<string name="opds_chapter_details_progress">| Tiến triển: %1$d of %2$d</string>
|
||||
<string name="manga_status_licensed">Đã được mua bản quyền</string>
|
||||
<string name="manga_status_publishing_finished">Đã hoàn thành xuất bản</string>
|
||||
<string name="manga_status_cancelled">Đã ngưng</string>
|
||||
<string name="opds_linktitle_download_cbz">Tải xuống CBZ</string>
|
||||
<string name="opds_linktitle_self_feed">Nguồn cấp dữ liệu hiện tại</string>
|
||||
<string name="opds_facet_sort_date_desc">Ngày giảm dần</string>
|
||||
<string name="opds_linktitle_view_chapter_details">Xem Chi Tiết Chương & Nhận Trang</string>
|
||||
<string name="opds_facet_filter_all_chapters">Tất cả các chương</string>
|
||||
<string name="opds_chapter_status_in_progress">⌛</string>
|
||||
<string name="opds_chapter_status_error">⚠️</string>
|
||||
<string name="opds_chapter_details_base">%1$s | %2$s</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="opds_search_shortname">Suwayomi OPDS 搜索</string>
|
||||
<string name="opds_search_description">在目录中搜索漫画</string>
|
||||
<string name="opds_facet_sort_newest_first">最新优先</string>
|
||||
<string name="opds_facet_sort_date_asc">日期升序</string>
|
||||
<string name="opds_linktitle_chapter_cover">章节封面</string>
|
||||
<string name="opds_linktitle_current_page">当前页面</string>
|
||||
<string name="opds_linktitle_catalog_root">目录根目录</string>
|
||||
<string name="opds_linktitle_search_catalog">搜索目录</string>
|
||||
<string name="opds_linktitle_previous_page">上一页</string>
|
||||
<string name="opds_linktitle_next_page">下一页</string>
|
||||
<string name="opds_linktitle_self_feed">当前订阅</string>
|
||||
<string name="opds_chapter_details_progress">| 进度:%1$d/%2$d</string>
|
||||
<string name="manga_status_unknown">未知</string>
|
||||
<string name="manga_status_ongoing">连载中</string>
|
||||
<string name="manga_status_completed">已完结</string>
|
||||
<string name="opds_feeds_search_results">搜索结果</string>
|
||||
<string name="opds_feeds_sources_title">来源</string>
|
||||
<string name="opds_feeds_root">Suwayomi OPDS 目录</string>
|
||||
<string name="opds_feeds_manga_chapters">%1$s 章节</string>
|
||||
<string name="opds_feeds_chapter_details">%1$s | %2$s | 详情</string>
|
||||
<string name="opds_feeds_all_manga_title">所有漫画</string>
|
||||
<string name="opds_feeds_all_manga_entry_content">浏览您的图书馆中的所有漫画</string>
|
||||
<string name="opds_feeds_sources_entry_content">按来源浏览漫画</string>
|
||||
<string name="opds_feeds_categories_title">类别</string>
|
||||
<string name="opds_feeds_categories_entry_content">按类别组织的漫画浏览</string>
|
||||
<string name="opds_feeds_genres_title">类型</string>
|
||||
<string name="opds_feeds_genres_entry_content">按类型标签浏览漫画</string>
|
||||
<string name="opds_feeds_status_title">状态</string>
|
||||
<string name="opds_feeds_status_entry_content">按出版状态浏览漫画</string>
|
||||
<string name="opds_feeds_languages_title">语言</string>
|
||||
<string name="opds_feeds_genre_specific_title">类型:%1$s</string>
|
||||
<string name="opds_feeds_languages_entry_content">按内容语言浏览漫画</string>
|
||||
<string name="opds_feeds_library_updates_title">图书馆更新历史</string>
|
||||
<string name="opds_feeds_library_updates_entry_content">您图书馆中最近更新的章节</string>
|
||||
<string name="opds_feeds_category_specific_title">类别:%1$s</string>
|
||||
<string name="opds_feeds_status_specific_title">状态:%1$s</string>
|
||||
<string name="opds_feeds_language_specific_title">语言:%1$s</string>
|
||||
<string name="opds_feeds_source_specific_title">来源:%1$s</string>
|
||||
<string name="opds_error_manga_not_found">未找到 ID 为 %1$d 的漫画</string>
|
||||
<string name="opds_error_chapter_not_found">未找到索引为 %1$d 的章节</string>
|
||||
<string name="opds_facetgroup_sort_order">排序方式</string>
|
||||
<string name="opds_facetgroup_read_status">阅读状态</string>
|
||||
<string name="opds_facet_sort_oldest_first">最旧优先</string>
|
||||
<string name="opds_facet_sort_date_desc">日期降序</string>
|
||||
<string name="opds_facet_filter_all_chapters">所有章节</string>
|
||||
<string name="opds_facet_filter_unread_only">仅未读</string>
|
||||
<string name="opds_facet_filter_read_only">仅已读</string>
|
||||
<string name="opds_linktitle_view_chapter_details">查看章节详情 & 获取页面</string>
|
||||
<string name="opds_linktitle_download_cbz">下载 CBZ</string>
|
||||
<string name="opds_linktitle_stream_pages">查看页面(流式)</string>
|
||||
<string name="opds_chapter_status_downloaded">⬇️</string>
|
||||
<string name="opds_chapter_status_read">✅</string>
|
||||
<string name="opds_chapter_status_in_progress">⌛</string>
|
||||
<string name="opds_chapter_status_error">⚠️</string>
|
||||
<string name="opds_chapter_status_unknown">❔</string>
|
||||
<string name="opds_chapter_status_unread">⭕</string>
|
||||
<string name="opds_chapter_details_base">%1$s | %2$s</string>
|
||||
<string name="opds_chapter_details_scanlator">| 由 %1$s</string>
|
||||
<string name="manga_status_licensed">授权</string>
|
||||
<string name="manga_status_publishing_finished">出版完成</string>
|
||||
<string name="manga_status_cancelled">已取消</string>
|
||||
<string name="manga_status_on_hiatus">暂停中</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,170 @@
|
||||
@import suwayomi.tachidesk.i18n.MR
|
||||
@import suwayomi.tachidesk.server.generated.BuildConfig
|
||||
|
||||
@param locale: java.util.Locale
|
||||
@param error: String
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>${MR.strings.login_label_title.localized(locale)}</title>
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
}
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: rgb(12, 16, 33);
|
||||
font-family: "Roboto","Helvetica","Arial",sans-serif;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0em;
|
||||
}
|
||||
button[disabled], input[disabled] {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
header {
|
||||
background-color: rgb(34, 38, 53);
|
||||
box-shadow: rgba(0, 0, 0, 0.2) 0px 2px 4px -1px, rgba(0, 0, 0, 0.14) 0px 4px 5px 0px, rgba(0, 0, 0, 0.12) 0px 1px 10px 0px;
|
||||
color: #fff;
|
||||
padding: 8px 32px;
|
||||
}
|
||||
header h1, header p {
|
||||
margin: 0;
|
||||
}
|
||||
footer {
|
||||
color: #fff;
|
||||
padding: 8px;
|
||||
}
|
||||
footer p {
|
||||
margin: 0;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
main {
|
||||
height: 100%;
|
||||
}
|
||||
main {
|
||||
position: relative;
|
||||
padding-top: 24px;
|
||||
}
|
||||
form {
|
||||
margin: 8px;
|
||||
padding: 8px 24px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgb(12, 16, 33);
|
||||
background-color: rgb(6, 8, 16);
|
||||
color: white;
|
||||
}
|
||||
.error {
|
||||
margin: 8px;
|
||||
padding: 8px 16px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #b71c1c;
|
||||
background-color: #c62828;
|
||||
color: white;
|
||||
}
|
||||
.error:empty {
|
||||
display: none;
|
||||
}
|
||||
form label {
|
||||
cursor: pointer;
|
||||
}
|
||||
form button {
|
||||
all: unset;
|
||||
padding: 8px;
|
||||
line-height: 1.75;
|
||||
text-align: center;
|
||||
min-width: 64px;
|
||||
border-radius: 4px;
|
||||
padding: 6px 8px;
|
||||
color: rgb(91, 116, 239);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.02857em;
|
||||
}
|
||||
form button:not([disabled]) {
|
||||
cursor: pointer;
|
||||
}
|
||||
form button:not([disabled]):hover {
|
||||
background-color: rgba(91, 116, 239, 0.08);
|
||||
}
|
||||
form input {
|
||||
all: unset;
|
||||
border-radius: 4px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.23);
|
||||
padding: 6px 12px;
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
}
|
||||
form input:hover {
|
||||
border-color: white;
|
||||
}
|
||||
form input:focus {
|
||||
border-color: rgb(91, 116, 239);
|
||||
}
|
||||
form .controls {
|
||||
display: grid;
|
||||
align-items: center;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
form .controls > :nth-child(even):not(:last-child) {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
form .submit {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
@media (min-width: 500px) {
|
||||
form {
|
||||
width: 100%;
|
||||
max-width: 450px;
|
||||
margin: 8px auto;
|
||||
}
|
||||
.error {
|
||||
width: 100%;
|
||||
max-width: 450px;
|
||||
margin: 8px auto;
|
||||
}
|
||||
form .controls {
|
||||
grid-template-columns: auto 1fr;
|
||||
column-gap: 16px;
|
||||
row-gap: 6px;
|
||||
}
|
||||
form .controls > :nth-child(even):not(:last-child) {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Suwayomi</h1>
|
||||
</header>
|
||||
<main>
|
||||
<div class="error">${error}</div>
|
||||
<form method="POST">
|
||||
<h2>Login</h2>
|
||||
<div class="controls">
|
||||
<label for="user">${MR.strings.login_label_username.localized(locale)}:</label>
|
||||
<input type="text" name="user" id="user" required placeholder="${MR.strings.login_placeholder_username.localized(locale)}"/>
|
||||
<label for="pass">${MR.strings.login_label_password.localized(locale)}:</label>
|
||||
<input type="password" name="pass" id="pass" required placeholder="${MR.strings.login_placeholder_password.localized(locale)}"/>
|
||||
</div>
|
||||
<div class="submit">
|
||||
<button type="submit">${MR.strings.login_label_login.localized(locale)}</button>
|
||||
</div>
|
||||
</form>
|
||||
</main>
|
||||
<footer>
|
||||
<p>Suwayomi: ${MR.strings.label_version.localized(locale, BuildConfig.VERSION)}</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,431 @@
|
||||
@import suwayomi.tachidesk.i18n.MR
|
||||
|
||||
@param locale: java.util.Locale
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content" />
|
||||
<title>${MR.strings.webview_label_title.localized(locale)}</title>
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
}
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: "Roboto","Helvetica","Arial",sans-serif;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0em;
|
||||
}
|
||||
body.disconnected::after {
|
||||
content: "${MR.strings.webview_label_disconnected.localized(locale)}";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(150, 0, 0, 0.5);
|
||||
color: white;
|
||||
text-align: center;
|
||||
align-content: center;
|
||||
font-size: 2rem;
|
||||
}
|
||||
button[disabled], input[disabled] {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
header {
|
||||
background-color: rgb(34, 38, 53);
|
||||
box-shadow: rgba(0, 0, 0, 0.2) 0px 2px 4px -1px, rgba(0, 0, 0, 0.14) 0px 4px 5px 0px, rgba(0, 0, 0, 0.12) 0px 1px 10px 0px;
|
||||
color: #fff;
|
||||
padding: 8px 32px;
|
||||
}
|
||||
header h1, header p {
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
header nav {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
column-gap: 20px;
|
||||
align-items: center;
|
||||
}
|
||||
header form {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
flex: auto 1 1;
|
||||
min-width: 400px;
|
||||
}
|
||||
header label {
|
||||
flex: auto 0 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
header button {
|
||||
all: unset;
|
||||
padding: 8px;
|
||||
border-radius: 50%;
|
||||
min-width: 1em;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
}
|
||||
header button:not([disabled]) {
|
||||
cursor: pointer;
|
||||
}
|
||||
header button:not([disabled]):hover {
|
||||
background-color: rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
header input {
|
||||
flex: 100% 1 1;
|
||||
}
|
||||
main, iframe {
|
||||
height: 100%;
|
||||
}
|
||||
main {
|
||||
position: relative;
|
||||
}
|
||||
canvas, input#inputtrap {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
input#inputtrap {
|
||||
opacity: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: none;
|
||||
}
|
||||
main .message, main .status {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
main .message {
|
||||
padding: 8px;
|
||||
max-width: 1100px;
|
||||
margin: auto;
|
||||
font-style: italic;
|
||||
}
|
||||
main .message.error {
|
||||
color: red;
|
||||
font-style: regular;
|
||||
font-weight: bold;
|
||||
}
|
||||
main .message:empty {
|
||||
display: none;
|
||||
}
|
||||
main .status {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
max-width: 50%;
|
||||
background: #555;
|
||||
color: white;
|
||||
padding: 2px 4px;
|
||||
font-size: 0.8rem;
|
||||
border-top-right-radius: 3px;
|
||||
}
|
||||
main .status:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* https://css-tricks.com/snippets/css/css-triangle/ */
|
||||
.arrow-right {
|
||||
display: inline-block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 9px solid transparent;
|
||||
border-bottom: 9px solid transparent;
|
||||
border-left: 9px solid currentcolor;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1 id="title">${MR.strings.webview_label_title.localized(locale)}</h1>
|
||||
<nav>
|
||||
<form id="browseForm">
|
||||
<input type="text" id="url" name="url" placeholder="${MR.strings.webview_placeholder_url.localized(locale)}" disabled/>
|
||||
<button type="submit" id="goButton" disabled><span class="arrow-right"></span></button>
|
||||
</form>
|
||||
<label><input type="checkbox" id="reverseScroll" disabled/> ${MR.strings.webview_label_reversescroll.localized(locale)}</label>
|
||||
</nav>
|
||||
<p><i>${MR.strings.webview_label_bindingshint.localized(locale)}</i></p>
|
||||
</header>
|
||||
<main>
|
||||
<div class="message" id="message">${MR.strings.webview_label_init.localized(locale)}</div>
|
||||
<div class="status" id="status"></div>
|
||||
<canvas id="frame"></canvas>
|
||||
<input type="text" id="inputtrap" autocomplete="off"/>
|
||||
</main>
|
||||
<script>
|
||||
const messageDiv = document.getElementById('message');
|
||||
const statusDiv = document.getElementById('status');
|
||||
const frame = document.getElementById('frame');
|
||||
const frameInput = document.getElementById('inputtrap');
|
||||
const ctx = frame.getContext("2d");
|
||||
const browseForm = document.getElementById('browseForm');
|
||||
const goButton = document.getElementById('goButton');
|
||||
const urlInput = document.getElementById('url');
|
||||
const titleDiv = document.getElementById('title');
|
||||
const reverseToggle = document.getElementById('reverseScroll');
|
||||
const origTitle = document.title;
|
||||
|
||||
try {
|
||||
const socketUrl = (window.location.origin + window.location.pathname).replace(/^http/,'ws');
|
||||
const socket = new WebSocket(socketUrl);
|
||||
|
||||
urlInput.disabled = false;
|
||||
goButton.disabled = false;
|
||||
reverseToggle.disabled = false;
|
||||
reverseToggle.checked = window.localStorage.getItem('suwayomi_mouse_reverse_scroll') === "true";
|
||||
|
||||
let url = '';
|
||||
try {
|
||||
url = window.decodeURIComponent(window.location.hash.replace(/^#/, ''));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
/// Helpers
|
||||
|
||||
const setHash = (u) => {
|
||||
let current = '';
|
||||
try {
|
||||
current = window.decodeURIComponent(window.location.hash.replace(/^#/, ''));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
if (current != u)
|
||||
history.pushState(null, null, window.location.origin + window.location.pathname + '#' + window.encodeURIComponent(u));
|
||||
};
|
||||
|
||||
const setTitle = (title) => {
|
||||
if (!title) {
|
||||
document.title = origTitle;
|
||||
titleDiv.textContent = origTitle;
|
||||
} else {
|
||||
document.title = "Suwayomi: " + title;
|
||||
titleDiv.textContent = "Suwayomi: " + title;
|
||||
}
|
||||
}
|
||||
|
||||
const loadUrl = (u) => {
|
||||
if (!u) {
|
||||
urlInput.value = u;
|
||||
setHash(u);
|
||||
setTitle();
|
||||
messageDiv.textContent = "${MR.strings.webview_label_getstarted.localized(locale)}";
|
||||
ctx.clearRect(0, 0, frame.width, frame.height);
|
||||
return;
|
||||
}
|
||||
messageDiv.textContent = "${MR.strings.webview_label_loading.localized(locale)}";
|
||||
messageDiv.classList.remove('error');
|
||||
urlInput.value = u;
|
||||
socket.send(JSON.stringify({ type: 'loadUrl', url: u, width: frame.clientWidth, height: frame.clientHeight }));
|
||||
ctx.clearRect(0, 0, frame.width, frame.height);
|
||||
};
|
||||
|
||||
/// Form
|
||||
|
||||
window.addEventListener('hashchange', e => {
|
||||
const url = window.decodeURIComponent(window.location.hash.replace(/^#/, ''));
|
||||
loadUrl(url);
|
||||
console.log('Navigate to', url);
|
||||
});
|
||||
|
||||
browseForm.addEventListener('submit', e => {
|
||||
e.preventDefault();
|
||||
const url = urlInput.value;
|
||||
loadUrl(url);
|
||||
console.log('Navigate to', url);
|
||||
});
|
||||
|
||||
reverseToggle.addEventListener('change', e => {
|
||||
window.localStorage.setItem('suwayomi_mouse_reverse_scroll', e.target.checked ? "true" : "false");
|
||||
});
|
||||
|
||||
/// Server events
|
||||
|
||||
socket.addEventListener('open', () => {
|
||||
loadUrl(url);
|
||||
console.log('WebSocket connection opened');
|
||||
});
|
||||
|
||||
socket.addEventListener('message', e => {
|
||||
const obj = JSON.parse(e.data);
|
||||
switch (obj.type) {
|
||||
case "addressChange":
|
||||
console.log('Loaded');
|
||||
messageDiv.textContent = '';
|
||||
urlInput.value = obj.url;
|
||||
setHash(obj.url);
|
||||
setTitle(obj.title);
|
||||
break;
|
||||
case "statusChange":
|
||||
statusDiv.textContent = obj.message;
|
||||
break;
|
||||
case "load": {
|
||||
if (obj.error) {
|
||||
messageDiv.textContent = "${MR.strings.label_error.localized(locale)}: " + obj.error;
|
||||
messageDiv.classList.add('error');
|
||||
} else {
|
||||
messageDiv.textContent = "";
|
||||
}
|
||||
urlInput.value = obj.url;
|
||||
setTitle(obj.title);
|
||||
} break;
|
||||
case "render": {
|
||||
const img = new Image();
|
||||
const imgData = new Blob([new Uint8Array(obj.image)], { type: "image/png" });
|
||||
const url = URL.createObjectURL(imgData);
|
||||
img.addEventListener('load', e => {
|
||||
frame.width = img.width;
|
||||
frame.height = img.height;
|
||||
ctx.drawImage(img, 0, 0);
|
||||
});
|
||||
img.src = url;
|
||||
} break;
|
||||
case "consoleMessage": {
|
||||
const lg = obj.severity == 4 ? console.error : obj.severity == 3 ? console.warn : console.log;
|
||||
lg(obj.source + ':' + obj.line + ':', obj.message);
|
||||
} break;
|
||||
default:
|
||||
console.warn("Unknown event", obj.type)
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
socket.addEventListener('close', e => {
|
||||
if (e.wasClean) {
|
||||
console.log(`WebSocket connection closed cleanly, code=` + e.code + `, reason=` + e.reason);
|
||||
} else {
|
||||
console.error('WebSocket connection died');
|
||||
}
|
||||
document.body.classList.add('disconnected');
|
||||
});
|
||||
|
||||
socket.addEventListener('error', e => {
|
||||
messageDiv.textContent = "${MR.strings.label_error.localized(locale)}: " + (e.message || e.reason || e);
|
||||
messageDiv.classList.add('error');
|
||||
console.error('WebSocket error:', e);
|
||||
});
|
||||
|
||||
/// Page events
|
||||
|
||||
const observer = new ResizeObserver(() => {
|
||||
socket.send(JSON.stringify({ type: 'resize', width: frame.clientWidth, height: frame.clientHeight }));
|
||||
});
|
||||
observer.observe(frame);
|
||||
|
||||
const frameEvent = (e) => {
|
||||
// Chrome Android bug, see below
|
||||
if (e.key === "Unidentified") return;
|
||||
e.preventDefault();
|
||||
const rect = frame.getBoundingClientRect();
|
||||
const clickX = e.clientX !== undefined ? e.clientX - rect.left : 0;
|
||||
const clickY = e.clientY !== undefined ? e.clientY - rect.top : 0;
|
||||
socket.send(JSON.stringify({
|
||||
type: 'event',
|
||||
eventType: e.type,
|
||||
clickX,
|
||||
clickY,
|
||||
button: e.button,
|
||||
ctrlKey: e.ctrlKey,
|
||||
shiftKey: e.shiftKey,
|
||||
altKey: e.altKey,
|
||||
metaKey: e.metaKey,
|
||||
key: e.key,
|
||||
clientX: e.clientX,
|
||||
clientY: e.clientY,
|
||||
deltaY: reverseToggle.checked && typeof e.deltaY === 'number' ? -e.deltaY : e.deltaY,
|
||||
}));
|
||||
frameInput.focus();
|
||||
};
|
||||
|
||||
const attachEvents = () => {
|
||||
console.log('Attaching event handlers to new document');
|
||||
const events = ["click", "mousedown", "mouseup", "mousemove", "wheel", "keydown", "keyup"];
|
||||
for (const ev of events) {
|
||||
frameInput.addEventListener(ev, frameEvent, false);
|
||||
}
|
||||
|
||||
let touch = undefined;
|
||||
frameInput.addEventListener('touchstart', e => {
|
||||
if (e.touches.length === 1) {
|
||||
touch = e.touches[0];
|
||||
}
|
||||
}, false);
|
||||
frameInput.addEventListener('touchend', e => {
|
||||
touch = undefined;
|
||||
}, false);
|
||||
frameInput.addEventListener('touchmove', e => {
|
||||
if (e.touches.length === 1 && touch !== undefined) {
|
||||
e.preventDefault();
|
||||
let deltaX = touch.pageX - e.touches[0].pageX;
|
||||
let deltaY = touch.pageY - e.touches[0].pageY;
|
||||
console.log(deltaX, deltaY)
|
||||
if (Math.abs(deltaX) > Math.abs(deltaY)) {
|
||||
// assume horizontal scroll
|
||||
socket.send(JSON.stringify({
|
||||
type: 'event',
|
||||
eventType: 'wheel',
|
||||
clickX: e.touches[0].pageX,
|
||||
clickY: e.touches[0].pageY,
|
||||
shiftKey: true,
|
||||
clientX: e.touches[0].clientX,
|
||||
clientY: e.touches[0].clientY,
|
||||
deltaY: deltaX,
|
||||
}));
|
||||
} else {
|
||||
socket.send(JSON.stringify({
|
||||
type: 'event',
|
||||
eventType: 'wheel',
|
||||
clickX: e.touches[0].pageX,
|
||||
clickY: e.touches[0].pageY,
|
||||
clientX: e.touches[0].clientX,
|
||||
clientY: e.touches[0].clientY,
|
||||
deltaY: deltaY,
|
||||
}));
|
||||
}
|
||||
touch = e.touches[0];
|
||||
}
|
||||
}, false);
|
||||
// known bug on Chrome Android:
|
||||
// https://stackoverflow.com/questions/36753548/keycode-on-android-is-always-229
|
||||
// on other browsers, the preventDefault above works so we don't get this event
|
||||
frameInput.addEventListener('input', e => {
|
||||
e.preventDefault();
|
||||
socket.send(JSON.stringify({
|
||||
type: 'event',
|
||||
eventType: 'keydown',
|
||||
clickX: 0,
|
||||
clickY: 0,
|
||||
key: e.data,
|
||||
}));
|
||||
socket.send(JSON.stringify({
|
||||
type: 'event',
|
||||
eventType: 'keyup',
|
||||
clickX: 0,
|
||||
clickY: 0,
|
||||
key: e.data,
|
||||
}));
|
||||
e.target.value = '';
|
||||
});
|
||||
frameInput.addEventListener('contextmenu', e => {
|
||||
e.preventDefault();
|
||||
}, false);
|
||||
};
|
||||
attachEvents();
|
||||
frameInput.focus();
|
||||
} catch (e) {
|
||||
messageDiv.textContent = "${MR.strings.label_error.localized(locale)}: " + (e.message || e);
|
||||
messageDiv.classList.add('error');
|
||||
console.error(e);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -16,6 +16,7 @@ import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
@@ -24,10 +25,10 @@ import okhttp3.OkHttpClient
|
||||
import okhttp3.brotli.BrotliInterceptor
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource
|
||||
import java.io.File
|
||||
import java.net.CookieHandler
|
||||
import java.net.CookieManager
|
||||
import java.net.CookiePolicy
|
||||
import java.nio.file.Files
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class NetworkHelper(
|
||||
@@ -54,6 +55,7 @@ class NetworkHelper(
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
|
||||
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||||
)
|
||||
val userAgentFlow = userAgent.asStateFlow()
|
||||
|
||||
fun defaultUserAgentProvider(): String = userAgent.value
|
||||
|
||||
@@ -77,7 +79,7 @@ class NetworkHelper(
|
||||
.callTimeout(2, TimeUnit.MINUTES)
|
||||
.cache(
|
||||
Cache(
|
||||
directory = File.createTempFile("tachidesk_network_cache", null),
|
||||
directory = Files.createTempDirectory("tachidesk_network_cache").toFile(),
|
||||
maxSize = 5L * 1024 * 1024, // 5 MiB
|
||||
),
|
||||
).addInterceptor(UncaughtExceptionInterceptor())
|
||||
|
||||
@@ -8,8 +8,6 @@ import okio.withLock
|
||||
import java.net.CookieStore
|
||||
import java.net.HttpCookie
|
||||
import java.net.URI
|
||||
import java.net.URL
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
@@ -18,30 +16,40 @@ import kotlin.time.Duration.Companion.seconds
|
||||
class PersistentCookieStore(
|
||||
context: Context,
|
||||
) : CookieStore {
|
||||
private val cookieMap = ConcurrentHashMap<String, List<Cookie>>()
|
||||
private val cookieMap = mutableMapOf<String, List<Cookie>>()
|
||||
private val prefs = context.getSharedPreferences("cookie_store", Context.MODE_PRIVATE)
|
||||
|
||||
private val lock = ReentrantLock()
|
||||
|
||||
init {
|
||||
val domains =
|
||||
prefs.all.keys
|
||||
.map { it.substringBeforeLast(".") }
|
||||
.toSet()
|
||||
domains.forEach { domain ->
|
||||
val cookies = prefs.getStringSet(domain, emptySet())
|
||||
if (!cookies.isNullOrEmpty()) {
|
||||
try {
|
||||
val url = "http://$domain".toHttpUrlOrNull() ?: return@forEach
|
||||
val nonExpiredCookies =
|
||||
cookies
|
||||
.mapNotNull { Cookie.parse(url, it) }
|
||||
.filter { !it.hasExpired() }
|
||||
cookieMap[domain] = nonExpiredCookies
|
||||
} catch (e: Exception) {
|
||||
// Ignore
|
||||
lock.withLock {
|
||||
val domains =
|
||||
prefs.all.keys
|
||||
.map { it.substringBeforeLast(".") }
|
||||
.toSet()
|
||||
val domainsToSave = mutableSetOf<String>()
|
||||
domains.forEach { domain ->
|
||||
val cookies = prefs.getStringSet(domain, emptySet())
|
||||
if (!cookies.isNullOrEmpty()) {
|
||||
try {
|
||||
val url = "http://$domain".toHttpUrlOrNull() ?: return@forEach
|
||||
val nonExpiredCookies =
|
||||
cookies
|
||||
.mapNotNull { Cookie.parse(url, it) }
|
||||
.filter { !it.hasExpired() }
|
||||
.groupBy { it.domain }
|
||||
.mapValues { it.value.distinctBy { it.name } }
|
||||
nonExpiredCookies.forEach { (domain, cookies) ->
|
||||
cookieMap[domain] = cookies
|
||||
domainsToSave.add(domain)
|
||||
}
|
||||
domainsToSave.add(domain)
|
||||
} catch (_: Exception) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
saveToDisk(domainsToSave)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,9 +58,10 @@ class PersistentCookieStore(
|
||||
cookies: List<Cookie>,
|
||||
) {
|
||||
lock.withLock {
|
||||
val domainsToSave = mutableSetOf<String>()
|
||||
// Append or replace the cookies for this domain.
|
||||
val cookiesForDomain = cookieMap[url.host].orEmpty().toMutableList()
|
||||
for (cookie in cookies) {
|
||||
val cookiesForDomain = cookieMap[cookie.domain].orEmpty().toMutableList()
|
||||
// Find a cookie with the same name. Replace it if found, otherwise add a new one.
|
||||
val pos = cookiesForDomain.indexOfFirst { it.name == cookie.name }
|
||||
if (pos == -1) {
|
||||
@@ -60,10 +69,11 @@ class PersistentCookieStore(
|
||||
} else {
|
||||
cookiesForDomain[pos] = cookie
|
||||
}
|
||||
cookieMap[cookie.domain] = cookiesForDomain
|
||||
domainsToSave.add(cookie.domain)
|
||||
}
|
||||
cookieMap[url.host] = cookiesForDomain
|
||||
|
||||
saveToDisk(url.toUrl())
|
||||
saveToDisk(domainsToSave.toSet())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,48 +95,66 @@ class PersistentCookieStore(
|
||||
|
||||
override fun get(uri: URI): List<HttpCookie> {
|
||||
val url = uri.toURL()
|
||||
return get(url.host).map {
|
||||
return get(url.toHttpUrlOrNull()!!).map {
|
||||
it.toHttpCookie()
|
||||
}
|
||||
}
|
||||
|
||||
fun get(url: HttpUrl): List<Cookie> = get(url.host)
|
||||
fun get(url: HttpUrl): List<Cookie> =
|
||||
lock.withLock {
|
||||
cookieMap.entries
|
||||
.filter {
|
||||
url.host.endsWith(it.key)
|
||||
}.flatMap { it.value }
|
||||
}
|
||||
|
||||
override fun add(
|
||||
uri: URI?,
|
||||
cookie: HttpCookie,
|
||||
) {
|
||||
@Suppress("NAME_SHADOWING")
|
||||
val uri = uri ?: URI("http://" + cookie.domain.removePrefix("."))
|
||||
val url = uri.toURL()
|
||||
lock.withLock {
|
||||
val cookies = cookieMap[url.host]
|
||||
cookieMap[url.host] = cookies.orEmpty() + cookie.toCookie(uri)
|
||||
saveToDisk(url)
|
||||
val cookie = cookie.toCookie(uri?.host) ?: return@withLock
|
||||
val cookiesForDomain = cookieMap[cookie.domain].orEmpty().toMutableList()
|
||||
// Find a cookie with the same name. Replace it if found, otherwise add a new one.
|
||||
val pos = cookiesForDomain.indexOfFirst { it.name == cookie.name }
|
||||
if (pos == -1) {
|
||||
cookiesForDomain.add(cookie)
|
||||
} else {
|
||||
cookiesForDomain[pos] = cookie
|
||||
}
|
||||
cookieMap[cookie.domain] = cookiesForDomain
|
||||
saveToDisk(setOf(cookie.domain))
|
||||
}
|
||||
}
|
||||
|
||||
override fun getCookies(): List<HttpCookie> =
|
||||
cookieMap.values.flatMap {
|
||||
it.map {
|
||||
it.toHttpCookie()
|
||||
lock.withLock {
|
||||
cookieMap.values.flatMap {
|
||||
it.map {
|
||||
it.toHttpCookie()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getStoredCookies(): List<Cookie> =
|
||||
lock.withLock {
|
||||
cookieMap.values.flatMap { it }
|
||||
}
|
||||
|
||||
override fun getURIs(): List<URI> =
|
||||
cookieMap.keys().toList().map {
|
||||
URI("http://$it")
|
||||
lock.withLock {
|
||||
cookieMap.keys.toList().map {
|
||||
URI("http://$it")
|
||||
}
|
||||
}
|
||||
|
||||
override fun remove(
|
||||
uri: URI?,
|
||||
cookie: HttpCookie,
|
||||
): Boolean {
|
||||
@Suppress("NAME_SHADOWING")
|
||||
val uri = uri ?: URI("http://" + cookie.domain.removePrefix("."))
|
||||
val url = uri.toURL()
|
||||
return lock.withLock {
|
||||
val cookies = cookieMap[url.host].orEmpty()
|
||||
): Boolean =
|
||||
lock.withLock {
|
||||
val cookie = cookie.toCookie(uri?.host) ?: return@withLock false
|
||||
val cookies = cookieMap[cookie.domain].orEmpty()
|
||||
val index =
|
||||
cookies.indexOfFirst {
|
||||
it.name == cookie.name &&
|
||||
@@ -135,63 +163,73 @@ class PersistentCookieStore(
|
||||
if (index >= 0) {
|
||||
val newList = cookies.toMutableList()
|
||||
newList.removeAt(index)
|
||||
cookieMap[url.host] = newList.toList()
|
||||
saveToDisk(url)
|
||||
cookieMap[cookie.domain] = newList.toList()
|
||||
saveToDisk(setOf(cookie.domain))
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun get(url: String): List<Cookie> = cookieMap[url].orEmpty().filter { !it.hasExpired() }
|
||||
|
||||
private fun saveToDisk(url: URL) {
|
||||
private fun saveToDisk(domains: Set<String>) {
|
||||
// Get cookies to be stored in disk
|
||||
val newValues =
|
||||
cookieMap[url.host]
|
||||
.orEmpty()
|
||||
.asSequence()
|
||||
.filter { it.persistent && !it.hasExpired() }
|
||||
.map(Cookie::toString)
|
||||
.toSet()
|
||||
|
||||
prefs.edit().putStringSet(url.host, newValues).apply()
|
||||
prefs
|
||||
.edit()
|
||||
.apply {
|
||||
domains.forEach { domain ->
|
||||
val newValues =
|
||||
cookieMap[domain]
|
||||
.orEmpty()
|
||||
.asSequence()
|
||||
.filter { it.persistent && !it.hasExpired() }
|
||||
.map(Cookie::toString)
|
||||
.toSet()
|
||||
if (newValues.isNotEmpty()) {
|
||||
remove(domain)
|
||||
putStringSet(domain, newValues)
|
||||
} else {
|
||||
remove(domain)
|
||||
}
|
||||
}
|
||||
}.apply()
|
||||
}
|
||||
|
||||
private fun Cookie.hasExpired() = System.currentTimeMillis() >= expiresAt
|
||||
|
||||
private fun HttpCookie.toCookie(uri: URI) =
|
||||
Cookie
|
||||
private fun HttpCookie.toCookie(urlDomain: String?): Cookie? {
|
||||
return Cookie
|
||||
.Builder()
|
||||
.name(name)
|
||||
.value(value)
|
||||
.domain(uri.toURL().host)
|
||||
.domain((domain ?: urlDomain ?: return null).removePrefix("."))
|
||||
.path(path ?: "/")
|
||||
.let {
|
||||
.also {
|
||||
if (maxAge != -1L) {
|
||||
it.expiresAt(System.currentTimeMillis() + maxAge.seconds.inWholeMilliseconds)
|
||||
} else {
|
||||
it.expiresAt(Long.MAX_VALUE)
|
||||
}
|
||||
}.let {
|
||||
if (secure) {
|
||||
it.secure()
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}.let {
|
||||
if (isHttpOnly) {
|
||||
it.httpOnly()
|
||||
} else {
|
||||
it
|
||||
}
|
||||
if (domain != null && !domain.startsWith('.')) {
|
||||
it.hostOnlyDomain(domain.removePrefix("."))
|
||||
}
|
||||
}.build()
|
||||
}
|
||||
|
||||
private fun Cookie.toHttpCookie(): HttpCookie {
|
||||
val it = this
|
||||
return HttpCookie(it.name, it.value).apply {
|
||||
domain = it.domain
|
||||
domain =
|
||||
if (hostOnly) {
|
||||
it.domain
|
||||
} else {
|
||||
"." + it.domain
|
||||
}
|
||||
path = it.path
|
||||
secure = it.secure
|
||||
maxAge =
|
||||
|
||||
+1
@@ -238,6 +238,7 @@ object CFClearance {
|
||||
if (!cookie.path.isNullOrEmpty()) it.path(cookie.path)
|
||||
// We need to convert the expires time to milliseconds for the persistent cookie store
|
||||
if (cookie.expires != null && cookie.expires > 0) it.expiresAt((cookie.expires * 1000).toLong())
|
||||
if (!cookie.domain.startsWith('.')) it.hostOnlyDomain(cookie.domain.removePrefix("."))
|
||||
}.build()
|
||||
}.groupBy { it.domain }
|
||||
.flatMap { (domain, cookies) ->
|
||||
|
||||
@@ -289,6 +289,7 @@ class LocalSource(
|
||||
fileSystem
|
||||
.getFilesInMangaDirectory(manga.url)
|
||||
// Only keep supported formats
|
||||
.filterNot { it.name.orEmpty().startsWith('.') }
|
||||
.filter { it.isDirectory || Archive.isSupported(it) }
|
||||
.map { chapterFile ->
|
||||
SChapter.create().apply {
|
||||
|
||||
@@ -40,27 +40,38 @@ object ChapterRecognition {
|
||||
}
|
||||
|
||||
// Get chapter title with lower case
|
||||
var name = chapterName.lowercase()
|
||||
val cleanChapterName =
|
||||
chapterName
|
||||
.lowercase()
|
||||
// Remove manga title from chapter title.
|
||||
.replace(mangaTitle.lowercase(), "")
|
||||
.trim()
|
||||
// Remove comma's or hyphens.
|
||||
.replace(',', '.')
|
||||
.replace('-', '.')
|
||||
// Remove unwanted white spaces.
|
||||
.replace(unwantedWhiteSpace, "")
|
||||
|
||||
// Remove manga title from chapter title.
|
||||
name = name.replace(mangaTitle.lowercase(), "").trim()
|
||||
val numberMatch = number.findAll(cleanChapterName)
|
||||
|
||||
// Remove comma's or hyphens.
|
||||
name = name.replace(',', '.').replace('-', '.')
|
||||
when {
|
||||
numberMatch.none() -> {
|
||||
return chapterNumber ?: -1.0
|
||||
}
|
||||
numberMatch.count() > 1 -> {
|
||||
// Remove unwanted tags.
|
||||
unwanted.replace(cleanChapterName, "").let { name ->
|
||||
// Check base case ch.xx
|
||||
basic.find(name)?.let { return getChapterNumberFromMatch(it) }
|
||||
|
||||
// Remove unwanted white spaces.
|
||||
name = unwantedWhiteSpace.replace(name, "")
|
||||
// need to find again first number might already removed
|
||||
number.find(name)?.let { return getChapterNumberFromMatch(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove unwanted tags.
|
||||
name = unwanted.replace(name, "")
|
||||
|
||||
// Check base case ch.xx
|
||||
basic.find(name)?.let { return getChapterNumberFromMatch(it) }
|
||||
|
||||
// Take the first number encountered.
|
||||
number.find(name)?.let { return getChapterNumberFromMatch(it) }
|
||||
|
||||
return chapterNumber ?: -1.0
|
||||
// return the first number encountered
|
||||
return getChapterNumberFromMatch(numberMatch.first())
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,8 +10,10 @@ package suwayomi.tachidesk.global
|
||||
import io.javalin.apibuilder.ApiBuilder.get
|
||||
import io.javalin.apibuilder.ApiBuilder.patch
|
||||
import io.javalin.apibuilder.ApiBuilder.path
|
||||
import io.javalin.apibuilder.ApiBuilder.ws
|
||||
import suwayomi.tachidesk.global.controller.GlobalMetaController
|
||||
import suwayomi.tachidesk.global.controller.SettingsController
|
||||
import suwayomi.tachidesk.global.controller.WebViewController
|
||||
|
||||
object GlobalAPI {
|
||||
fun defineEndpoints() {
|
||||
@@ -23,5 +25,9 @@ object GlobalAPI {
|
||||
get("about", SettingsController.about)
|
||||
get("check-update", SettingsController.checkUpdate)
|
||||
}
|
||||
path("webview") {
|
||||
get("", WebViewController.webview)
|
||||
ws("", WebViewController::webviewWS)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package suwayomi.tachidesk.global.controller
|
||||
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import io.javalin.http.ContentType
|
||||
import io.javalin.http.HttpStatus
|
||||
import io.javalin.websocket.WsConfig
|
||||
import suwayomi.tachidesk.global.impl.WebView
|
||||
import suwayomi.tachidesk.i18n.LocalizationHelper
|
||||
import suwayomi.tachidesk.server.util.handler
|
||||
import suwayomi.tachidesk.server.util.queryParam
|
||||
import suwayomi.tachidesk.server.util.withOperation
|
||||
import java.util.Locale
|
||||
|
||||
object WebViewController {
|
||||
val webview =
|
||||
handler(
|
||||
queryParam<String?>("lang"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("WebView")
|
||||
description("Opens and browses WebView")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, lang ->
|
||||
val locale: Locale = LocalizationHelper.ctxToLocale(ctx, lang)
|
||||
ctx.contentType(ContentType.TEXT_HTML)
|
||||
ctx.render(
|
||||
"Webview.jte",
|
||||
mapOf(
|
||||
"locale" to locale,
|
||||
),
|
||||
)
|
||||
},
|
||||
withResults = { mime<String>(HttpStatus.OK, "text/html") },
|
||||
)
|
||||
|
||||
fun webviewWS(ws: WsConfig) {
|
||||
ws.onConnect { ctx -> WebView.addClient(ctx) }
|
||||
ws.onMessage { ctx -> WebView.handleRequest(ctx) }
|
||||
ws.onClose { ctx -> WebView.removeClient(ctx) }
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
package suwayomi.tachidesk.global.impl
|
||||
|
||||
import org.jetbrains.exposed.sql.insert
|
||||
import org.jetbrains.exposed.dao.id.EntityID
|
||||
import org.jetbrains.exposed.sql.batchInsert
|
||||
import org.jetbrains.exposed.sql.selectAll
|
||||
import org.jetbrains.exposed.sql.statements.BatchUpdateStatement
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import org.jetbrains.exposed.sql.update
|
||||
import suwayomi.tachidesk.global.model.table.GlobalMetaTable
|
||||
|
||||
/*
|
||||
@@ -18,20 +19,32 @@ object GlobalMeta {
|
||||
key: String,
|
||||
value: String,
|
||||
) {
|
||||
transaction {
|
||||
val meta =
|
||||
transaction {
|
||||
GlobalMetaTable.selectAll().where { GlobalMetaTable.key eq key }
|
||||
}.firstOrNull()
|
||||
modifyMetas(mapOf(key to value))
|
||||
}
|
||||
|
||||
if (meta == null) {
|
||||
GlobalMetaTable.insert {
|
||||
it[GlobalMetaTable.key] = key
|
||||
it[GlobalMetaTable.value] = value
|
||||
fun modifyMetas(meta: Map<String, String>) {
|
||||
transaction {
|
||||
val dbMetaMap =
|
||||
GlobalMetaTable
|
||||
.selectAll()
|
||||
.where { GlobalMetaTable.key inList meta.keys }
|
||||
.associateBy { it[GlobalMetaTable.key] }
|
||||
val (existingMeta, newMeta) = meta.toList().partition { (key) -> key in dbMetaMap.keys }
|
||||
|
||||
if (existingMeta.isNotEmpty()) {
|
||||
BatchUpdateStatement(GlobalMetaTable).apply {
|
||||
existingMeta.forEach { (key, value) ->
|
||||
addBatch(EntityID(dbMetaMap[key]!![GlobalMetaTable.id].value, GlobalMetaTable))
|
||||
this[GlobalMetaTable.value] = value
|
||||
}
|
||||
execute(this@transaction)
|
||||
}
|
||||
} else {
|
||||
GlobalMetaTable.update({ GlobalMetaTable.key eq key }) {
|
||||
it[GlobalMetaTable.value] = value
|
||||
}
|
||||
|
||||
if (newMeta.isNotEmpty()) {
|
||||
GlobalMetaTable.batchInsert(newMeta) { (key, value) ->
|
||||
this[GlobalMetaTable.key] = key
|
||||
this[GlobalMetaTable.value] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,589 @@
|
||||
package suwayomi.tachidesk.global.impl
|
||||
|
||||
import dev.datlag.kcef.KCEF
|
||||
import dev.datlag.kcef.KCEFBrowser
|
||||
import dev.datlag.kcef.KCEFClient
|
||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Cookie
|
||||
import okhttp3.HttpUrl
|
||||
import org.cef.CefSettings
|
||||
import org.cef.browser.CefBrowser
|
||||
import org.cef.browser.CefFrame
|
||||
import org.cef.browser.CefRendering
|
||||
import org.cef.handler.CefDisplayHandlerAdapter
|
||||
import org.cef.handler.CefLoadHandler
|
||||
import org.cef.handler.CefLoadHandlerAdapter
|
||||
import org.cef.handler.CefRenderHandlerAdapter
|
||||
import org.cef.handler.CefRequestHandlerAdapter
|
||||
import org.cef.handler.CefResourceRequestHandler
|
||||
import org.cef.input.CefTouchEvent
|
||||
import org.cef.misc.BoolRef
|
||||
import org.cef.network.CefCookie
|
||||
import org.cef.network.CefCookieManager
|
||||
import org.cef.network.CefRequest
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.awt.Component
|
||||
import java.awt.Rectangle
|
||||
import java.awt.event.InputEvent
|
||||
import java.awt.event.KeyEvent
|
||||
import java.awt.event.MouseEvent
|
||||
import java.awt.event.MouseWheelEvent
|
||||
import java.awt.image.BufferedImage
|
||||
import java.awt.image.DataBufferInt
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import java.util.Date
|
||||
import javax.imageio.ImageIO
|
||||
import javax.swing.JPanel
|
||||
|
||||
class KcefWebView {
|
||||
private val logger = KotlinLogging.logger {}
|
||||
private val renderHandler = RenderHandler()
|
||||
private var kcefClient: KCEFClient? = null
|
||||
private var browser: KCEFBrowser? = null
|
||||
private var width = 1000
|
||||
private var height = 1000
|
||||
|
||||
companion object {
|
||||
private val networkHelper: NetworkHelper by injectLazy()
|
||||
|
||||
fun Cookie.toCefCookie(): CefCookie {
|
||||
val cookie = this
|
||||
return CefCookie(
|
||||
cookie.name,
|
||||
cookie.value,
|
||||
if (cookie.hostOnly) {
|
||||
cookie.domain
|
||||
} else {
|
||||
"." + cookie.domain
|
||||
},
|
||||
cookie.path,
|
||||
cookie.secure,
|
||||
cookie.httpOnly,
|
||||
Date(),
|
||||
null,
|
||||
cookie.expiresAt < 253402300799999L, // okhttp3.internal.http.MAX_DATE
|
||||
Date(cookie.expiresAt),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable sealed class Event
|
||||
|
||||
@Serializable
|
||||
@SerialName("consoleMessage")
|
||||
private data class ConsoleEvent(
|
||||
val severity: Int,
|
||||
val message: String,
|
||||
val source: String,
|
||||
val line: Int,
|
||||
) : Event()
|
||||
|
||||
@Serializable
|
||||
@SerialName("addressChange")
|
||||
private data class AddressEvent(
|
||||
val url: String,
|
||||
val title: String,
|
||||
) : Event()
|
||||
|
||||
@Serializable
|
||||
@SerialName("statusChange")
|
||||
private data class StatusEvent(
|
||||
val message: String,
|
||||
) : Event()
|
||||
|
||||
@Suppress("ArrayInDataClass")
|
||||
@Serializable
|
||||
@SerialName("render")
|
||||
private data class RenderEvent(
|
||||
val image: ByteArray,
|
||||
) : Event()
|
||||
|
||||
@Serializable
|
||||
@SerialName("load")
|
||||
private data class LoadEvent(
|
||||
val url: String,
|
||||
val title: String,
|
||||
val status: Int = 0,
|
||||
val error: String? = null,
|
||||
) : Event()
|
||||
|
||||
private inner class DisplayHandler : CefDisplayHandlerAdapter() {
|
||||
override fun onConsoleMessage(
|
||||
browser: CefBrowser,
|
||||
level: CefSettings.LogSeverity,
|
||||
message: String,
|
||||
source: String,
|
||||
line: Int,
|
||||
): Boolean {
|
||||
WebView.notifyAllClients(
|
||||
Json.encodeToString<Event>(
|
||||
ConsoleEvent(level.ordinal, message, source, line),
|
||||
),
|
||||
)
|
||||
logger.debug { "$source:$line: $message" }
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onAddressChange(
|
||||
browser: CefBrowser,
|
||||
frame: CefFrame,
|
||||
url: String,
|
||||
) {
|
||||
if (!frame.isMain) return
|
||||
this@KcefWebView.browser!!.evaluateJavaScript("return document.title") {
|
||||
WebView.notifyAllClients(
|
||||
Json.encodeToString<Event>(
|
||||
AddressEvent(url, it ?: ""),
|
||||
),
|
||||
)
|
||||
}
|
||||
flush()
|
||||
}
|
||||
|
||||
override fun onStatusMessage(
|
||||
browser: CefBrowser,
|
||||
value: String,
|
||||
) {
|
||||
WebView.notifyAllClients(
|
||||
Json.encodeToString<Event>(
|
||||
StatusEvent(value),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class LoadHandler : CefLoadHandlerAdapter() {
|
||||
override fun onLoadEnd(
|
||||
browser: CefBrowser,
|
||||
frame: CefFrame,
|
||||
httpStatusCode: Int,
|
||||
) {
|
||||
logger.info { "Load event: ${frame.name} - ${frame.url}" }
|
||||
if (httpStatusCode > 0 && frame.isMain) handleLoad(frame.url, httpStatusCode)
|
||||
flush()
|
||||
}
|
||||
|
||||
override fun onLoadError(
|
||||
browser: CefBrowser,
|
||||
frame: CefFrame,
|
||||
errorCode: CefLoadHandler.ErrorCode,
|
||||
errorText: String,
|
||||
failedUrl: String,
|
||||
) {
|
||||
if (frame.isMain) handleLoad(failedUrl, 0, errorText)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class RequestHandler : CefRequestHandlerAdapter() {
|
||||
override fun getResourceRequestHandler(
|
||||
browser: CefBrowser,
|
||||
frame: CefFrame,
|
||||
request: CefRequest,
|
||||
isNavigation: Boolean,
|
||||
isDownload: Boolean,
|
||||
requestInitiator: String,
|
||||
disableDefaultHandling: BoolRef,
|
||||
): CefResourceRequestHandler? {
|
||||
logger.info { "Load resource: ${frame.name} - ${request.url}" }
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// Loosely based on
|
||||
// https://github.com/JetBrains/jcef/blob/main/java/org/cef/browser/CefBrowserOsr.java
|
||||
private inner class RenderHandler : CefRenderHandlerAdapter() {
|
||||
var myImage: BufferedImage? = null
|
||||
|
||||
override fun getViewRect(browser: CefBrowser): Rectangle = Rectangle(0, 0, width, height)
|
||||
|
||||
override fun onPaint(
|
||||
browser: CefBrowser,
|
||||
popup: Boolean,
|
||||
dirtyRects: Array<Rectangle>,
|
||||
buffer: ByteBuffer,
|
||||
width: Int,
|
||||
height: Int,
|
||||
) {
|
||||
var image = myImage ?: BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE)
|
||||
|
||||
if (image.width != width || image.height != height) {
|
||||
image = BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE)
|
||||
}
|
||||
|
||||
val dst = (image.raster.getDataBuffer() as DataBufferInt).getData()
|
||||
val src = buffer.order(ByteOrder.LITTLE_ENDIAN).asIntBuffer()
|
||||
src.get(dst)
|
||||
|
||||
myImage = image
|
||||
val stream = ByteArrayOutputStream()
|
||||
val success = ImageIO.write(myImage, "png", stream)
|
||||
if (!success) {
|
||||
throw IllegalStateException("Failed to convert image to PNG")
|
||||
}
|
||||
|
||||
WebView.notifyAllClients(
|
||||
Json.encodeToString<Event>(
|
||||
RenderEvent(stream.toByteArray()),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
destroy()
|
||||
kcefClient =
|
||||
KCEF.newClientBlocking().apply {
|
||||
addDisplayHandler(DisplayHandler())
|
||||
addLoadHandler(LoadHandler())
|
||||
addRequestHandler(RequestHandler())
|
||||
}
|
||||
|
||||
logger.info { "Start loading cookies" }
|
||||
CefCookieManager.getGlobalManager().apply {
|
||||
val cookies = networkHelper.cookieStore.getStoredCookies()
|
||||
for (cookie in cookies) {
|
||||
try {
|
||||
if (!setCookie(
|
||||
"https://" + cookie.domain,
|
||||
cookie.toCefCookie(),
|
||||
)
|
||||
) {
|
||||
throw Exception()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logger.warn(e) { "Loading cookie ${cookie.name} failed" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun destroy() {
|
||||
flush()
|
||||
browser?.close(true)
|
||||
browser?.dispose()
|
||||
browser = null
|
||||
kcefClient?.dispose()
|
||||
kcefClient = null
|
||||
}
|
||||
|
||||
fun loadUrl(url: String) {
|
||||
browser?.close(true)
|
||||
browser?.dispose()
|
||||
browser =
|
||||
kcefClient!!
|
||||
.createBrowser(
|
||||
url,
|
||||
CefRendering.CefRenderingWithHandler(renderHandler, JPanel()),
|
||||
// NOTE: with a context, we don't seem to be getting any cookies
|
||||
).apply {
|
||||
// NOTE: Without this, we don't seem to be receiving any events
|
||||
createImmediately()
|
||||
}
|
||||
}
|
||||
|
||||
fun resize(
|
||||
width: Int,
|
||||
height: Int,
|
||||
) {
|
||||
this.width = width
|
||||
this.height = height
|
||||
browser?.wasResized(width, height)
|
||||
}
|
||||
|
||||
private fun flush() {
|
||||
if (browser == null) return
|
||||
logger.info { "Start cookie flush" }
|
||||
CefCookieManager.getGlobalManager().visitAllCookies { it, _, _, _ ->
|
||||
try {
|
||||
networkHelper.cookieStore.addAll(
|
||||
HttpUrl
|
||||
.Builder()
|
||||
.scheme("http")
|
||||
.host(it.domain.removePrefix("."))
|
||||
.build(),
|
||||
listOf(
|
||||
Cookie
|
||||
.Builder()
|
||||
.name(it.name)
|
||||
.value(it.value)
|
||||
.path(if (it.path.startsWith('/')) it.path else "/" + it.path)
|
||||
.domain(it.domain.removePrefix("."))
|
||||
.apply {
|
||||
if (it.hasExpires) {
|
||||
expiresAt(it.expires.time)
|
||||
} else {
|
||||
expiresAt(Long.MAX_VALUE)
|
||||
}
|
||||
if (it.httponly) {
|
||||
httpOnly()
|
||||
}
|
||||
if (it.secure) {
|
||||
secure()
|
||||
}
|
||||
if (!it.domain.startsWith('.')) {
|
||||
hostOnlyDomain(it.domain.removePrefix("."))
|
||||
}
|
||||
}.build(),
|
||||
),
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
logger.warn(e) { "Writing cookie ${it.name} failed" }
|
||||
}
|
||||
return@visitAllCookies true
|
||||
}
|
||||
}
|
||||
|
||||
private fun keyEvent(
|
||||
msg: WebView.JsEventMessage,
|
||||
component: Component,
|
||||
id: Int,
|
||||
modifier: Int,
|
||||
): KeyEvent? {
|
||||
val char = if (msg.key?.length == 1) msg.key[0] else KeyEvent.CHAR_UNDEFINED
|
||||
val code =
|
||||
when (char.uppercaseChar()) {
|
||||
in 'A'..'Z', in '0'..'9' -> char.uppercaseChar().code
|
||||
'&' -> KeyEvent.VK_AMPERSAND
|
||||
'*' -> KeyEvent.VK_ASTERISK
|
||||
'@' -> KeyEvent.VK_AT
|
||||
'\\' -> KeyEvent.VK_BACK_SLASH
|
||||
'{' -> KeyEvent.VK_BRACELEFT
|
||||
'}' -> KeyEvent.VK_BRACERIGHT
|
||||
'^' -> KeyEvent.VK_CIRCUMFLEX
|
||||
']' -> KeyEvent.VK_CLOSE_BRACKET
|
||||
':' -> KeyEvent.VK_COLON
|
||||
',' -> KeyEvent.VK_COMMA
|
||||
'$' -> KeyEvent.VK_DOLLAR
|
||||
'=' -> KeyEvent.VK_EQUALS
|
||||
'€' -> KeyEvent.VK_EURO_SIGN
|
||||
'!' -> KeyEvent.VK_EXCLAMATION_MARK
|
||||
'>' -> KeyEvent.VK_GREATER
|
||||
'(' -> KeyEvent.VK_LEFT_PARENTHESIS
|
||||
'<' -> KeyEvent.VK_LESS
|
||||
'-' -> KeyEvent.VK_MINUS
|
||||
'#' -> KeyEvent.VK_NUMBER_SIGN
|
||||
'[' -> KeyEvent.VK_OPEN_BRACKET
|
||||
'.' -> KeyEvent.VK_PERIOD
|
||||
'+' -> KeyEvent.VK_PLUS
|
||||
'\'' -> KeyEvent.VK_QUOTE
|
||||
'"' -> KeyEvent.VK_QUOTEDBL
|
||||
')' -> KeyEvent.VK_RIGHT_PARENTHESIS
|
||||
';' -> KeyEvent.VK_SEMICOLON
|
||||
'/' -> KeyEvent.VK_SLASH
|
||||
' ' -> KeyEvent.VK_SPACE
|
||||
'_' -> KeyEvent.VK_UNDERSCORE
|
||||
else ->
|
||||
when (msg.key) {
|
||||
"Alt" -> KeyEvent.VK_ALT
|
||||
"Backspace" -> KeyEvent.VK_BACK_SPACE
|
||||
"Delete" -> KeyEvent.VK_DELETE
|
||||
"CapsLock" -> KeyEvent.VK_CAPS_LOCK
|
||||
"Control" -> KeyEvent.VK_CONTROL
|
||||
"ArrowDown" -> KeyEvent.VK_DOWN
|
||||
"End" -> KeyEvent.VK_END
|
||||
"Enter" -> KeyEvent.VK_ENTER
|
||||
"Escape" -> KeyEvent.VK_ESCAPE
|
||||
"F1" -> KeyEvent.VK_F1
|
||||
"F2" -> KeyEvent.VK_F2
|
||||
"F3" -> KeyEvent.VK_F3
|
||||
"F4" -> KeyEvent.VK_F4
|
||||
"F5" -> KeyEvent.VK_F5
|
||||
"F6" -> KeyEvent.VK_F6
|
||||
"F7" -> KeyEvent.VK_F7
|
||||
"F8" -> KeyEvent.VK_F8
|
||||
"F9" -> KeyEvent.VK_F9
|
||||
"F10" -> KeyEvent.VK_F10
|
||||
"F11" -> KeyEvent.VK_F11
|
||||
"F12" -> KeyEvent.VK_F12
|
||||
"Home" -> KeyEvent.VK_HOME
|
||||
"Insert" -> KeyEvent.VK_INSERT
|
||||
"ArrowLeft" -> KeyEvent.VK_LEFT
|
||||
"Meta" -> KeyEvent.VK_META
|
||||
"NumLock" -> KeyEvent.VK_NUM_LOCK
|
||||
"PageDown" -> KeyEvent.VK_PAGE_DOWN
|
||||
"PageUp" -> KeyEvent.VK_PAGE_UP
|
||||
"Pause" -> KeyEvent.VK_PAUSE
|
||||
"ArrowRight" -> KeyEvent.VK_RIGHT
|
||||
"ScrollLock" -> KeyEvent.VK_SCROLL_LOCK
|
||||
"Shift" -> KeyEvent.VK_SHIFT
|
||||
"Tab" -> KeyEvent.VK_TAB
|
||||
"ArrowUp" -> KeyEvent.VK_UP
|
||||
else -> KeyEvent.VK_UNDEFINED
|
||||
}
|
||||
}
|
||||
if (id == KeyEvent.KEY_TYPED) {
|
||||
if (char == KeyEvent.CHAR_UNDEFINED && code != KeyEvent.VK_ENTER) return null
|
||||
return KeyEvent(
|
||||
component,
|
||||
id,
|
||||
0L,
|
||||
modifier,
|
||||
KeyEvent.VK_UNDEFINED,
|
||||
if (code == KeyEvent.VK_ENTER) code.toChar() else char,
|
||||
KeyEvent.KEY_LOCATION_UNKNOWN,
|
||||
)
|
||||
}
|
||||
return KeyEvent(
|
||||
component,
|
||||
id,
|
||||
0L,
|
||||
modifier,
|
||||
code,
|
||||
if (code == KeyEvent.VK_ENTER) code.toChar() else char,
|
||||
KeyEvent.KEY_LOCATION_STANDARD,
|
||||
)
|
||||
}
|
||||
|
||||
fun event(msg: WebView.JsEventMessage) {
|
||||
val component = browser?.uiComponent ?: return
|
||||
val type = msg.eventType
|
||||
val clickX = msg.clickX
|
||||
val clickY = msg.clickY
|
||||
val modifier =
|
||||
(
|
||||
(if (msg.altKey == true) InputEvent.ALT_DOWN_MASK else 0) or
|
||||
(if (msg.ctrlKey == true) InputEvent.CTRL_DOWN_MASK else 0) or
|
||||
(if (msg.shiftKey == true) InputEvent.SHIFT_DOWN_MASK else 0) or
|
||||
(if (msg.metaKey == true) InputEvent.META_DOWN_MASK else 0)
|
||||
)
|
||||
|
||||
if (type == "wheel") {
|
||||
val d = msg.deltaY?.toInt() ?: 1
|
||||
val ev =
|
||||
MouseWheelEvent(
|
||||
component,
|
||||
0,
|
||||
0L,
|
||||
modifier,
|
||||
clickX.toInt(),
|
||||
clickY.toInt(),
|
||||
0,
|
||||
false,
|
||||
MouseWheelEvent.WHEEL_UNIT_SCROLL,
|
||||
-d,
|
||||
1,
|
||||
)
|
||||
browser!!.sendMouseWheelEvent(ev)
|
||||
return
|
||||
}
|
||||
if (type == "keydown") {
|
||||
browser!!.sendKeyEvent(keyEvent(msg, component, KeyEvent.KEY_PRESSED, modifier)!!)
|
||||
keyEvent(msg, component, KeyEvent.KEY_TYPED, modifier)?.let { browser!!.sendKeyEvent(it) }
|
||||
return
|
||||
}
|
||||
if (type == "keyup") {
|
||||
browser!!.sendKeyEvent(keyEvent(msg, component, KeyEvent.KEY_RELEASED, modifier)!!)
|
||||
return
|
||||
}
|
||||
if (type == "mousedown" || type == "mouseup" || type == "click") {
|
||||
val id =
|
||||
when (type) {
|
||||
"mousedown" -> MouseEvent.MOUSE_PRESSED
|
||||
"mouseup" -> MouseEvent.MOUSE_PRESSED
|
||||
"click" -> MouseEvent.MOUSE_CLICKED
|
||||
else -> 0
|
||||
}
|
||||
val mouseModifier =
|
||||
when (msg.button ?: 0) {
|
||||
0 -> MouseEvent.BUTTON1_DOWN_MASK
|
||||
1 -> MouseEvent.BUTTON2_DOWN_MASK
|
||||
2 -> MouseEvent.BUTTON3_DOWN_MASK
|
||||
else -> 0
|
||||
}
|
||||
val button =
|
||||
when (msg.button ?: 0) {
|
||||
0 -> MouseEvent.BUTTON1
|
||||
1 -> MouseEvent.BUTTON2
|
||||
2 -> MouseEvent.BUTTON3
|
||||
else -> 0
|
||||
}
|
||||
val ev =
|
||||
MouseEvent(
|
||||
component,
|
||||
id,
|
||||
0L,
|
||||
modifier or mouseModifier,
|
||||
clickX.toInt(),
|
||||
clickY.toInt(),
|
||||
msg.clientX?.toInt() ?: 0,
|
||||
msg.clientY?.toInt() ?: 0,
|
||||
1,
|
||||
true,
|
||||
button,
|
||||
)
|
||||
browser!!.sendMouseEvent(ev)
|
||||
val evType =
|
||||
when (type) {
|
||||
"mousedown" -> CefTouchEvent.EventType.PRESSED
|
||||
"mouseup" -> CefTouchEvent.EventType.RELEASED
|
||||
else -> CefTouchEvent.EventType.MOVED
|
||||
}
|
||||
val ev2 =
|
||||
CefTouchEvent(
|
||||
0,
|
||||
clickX,
|
||||
clickY,
|
||||
10.0f,
|
||||
10.0f,
|
||||
0.0f,
|
||||
1.0f,
|
||||
evType,
|
||||
modifier,
|
||||
CefTouchEvent.PointerType.MOUSE,
|
||||
)
|
||||
browser!!.sendTouchEvent(ev2)
|
||||
return
|
||||
}
|
||||
if (type == "mousemove") {
|
||||
val ev =
|
||||
MouseEvent(
|
||||
component,
|
||||
MouseEvent.MOUSE_MOVED,
|
||||
0L,
|
||||
modifier,
|
||||
clickX.toInt(),
|
||||
clickY.toInt(),
|
||||
msg.clientX?.toInt() ?: 0,
|
||||
msg.clientY?.toInt() ?: 0,
|
||||
0,
|
||||
true,
|
||||
0,
|
||||
)
|
||||
browser!!.sendMouseEvent(ev)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fun canGoBack(): Boolean = browser!!.canGoBack()
|
||||
|
||||
fun goBack() {
|
||||
browser!!.goBack()
|
||||
}
|
||||
|
||||
fun canGoForward(): Boolean = browser!!.canGoForward()
|
||||
|
||||
fun goForward() {
|
||||
browser!!.goForward()
|
||||
}
|
||||
|
||||
private fun handleLoad(
|
||||
url: String,
|
||||
status: Int = 0,
|
||||
error: String? = null,
|
||||
) {
|
||||
browser!!.evaluateJavaScript("return document.title") {
|
||||
logger.info { "Load finished with title $it" }
|
||||
WebView.notifyAllClients(
|
||||
Json.encodeToString<Event>(
|
||||
LoadEvent(url, it ?: "", status, error),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package suwayomi.tachidesk.global.impl
|
||||
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import io.javalin.websocket.WsContext
|
||||
import io.javalin.websocket.WsMessageContext
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.eclipse.jetty.websocket.api.CloseStatus
|
||||
import suwayomi.tachidesk.manga.impl.update.Websocket
|
||||
|
||||
object WebView : Websocket<String>() {
|
||||
private val logger = KotlinLogging.logger {}
|
||||
private var driver: KcefWebView? = null
|
||||
|
||||
override fun addClient(ctx: WsContext) {
|
||||
if (clients.isNotEmpty()) {
|
||||
// TODO: allow multiple concurrent accesses?
|
||||
clients.forEach { it.value.closeSession(CloseStatus(1001, "Other client connected")) }
|
||||
clients.clear()
|
||||
}
|
||||
if (driver == null) {
|
||||
driver = KcefWebView()
|
||||
}
|
||||
super.addClient(ctx)
|
||||
ctx.enableAutomaticPings()
|
||||
}
|
||||
|
||||
override fun removeClient(ctx: WsContext) {
|
||||
super.removeClient(ctx)
|
||||
if (clients.isEmpty()) {
|
||||
driver?.destroy()
|
||||
driver = null
|
||||
}
|
||||
}
|
||||
|
||||
override fun notifyClient(
|
||||
ctx: WsContext,
|
||||
value: String?,
|
||||
) {
|
||||
if (value != null) {
|
||||
ctx.send(value)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
sealed class TypeObject
|
||||
|
||||
@Serializable
|
||||
@SerialName("loadUrl")
|
||||
private data class LoadUrlMessage(
|
||||
val url: String,
|
||||
val width: Int,
|
||||
val height: Int,
|
||||
) : TypeObject()
|
||||
|
||||
@Serializable
|
||||
@SerialName("resize")
|
||||
private data class ResizeMessage(
|
||||
val width: Int,
|
||||
val height: Int,
|
||||
) : TypeObject()
|
||||
|
||||
@Serializable
|
||||
@SerialName("event")
|
||||
data class JsEventMessage(
|
||||
val eventType: String,
|
||||
val clickX: Float,
|
||||
val clickY: Float,
|
||||
val button: Int? = null,
|
||||
val ctrlKey: Boolean? = null,
|
||||
val shiftKey: Boolean? = null,
|
||||
val altKey: Boolean? = null,
|
||||
val metaKey: Boolean? = null,
|
||||
val key: String? = null,
|
||||
val code: String? = null,
|
||||
val clientX: Float? = null,
|
||||
val clientY: Float? = null,
|
||||
val deltaY: Float? = null,
|
||||
) : TypeObject()
|
||||
|
||||
override fun handleRequest(ctx: WsMessageContext) {
|
||||
val dr = driver ?: return
|
||||
try {
|
||||
val event = Json.decodeFromString<TypeObject>(ctx.message())
|
||||
when (event) {
|
||||
is LoadUrlMessage -> {
|
||||
val url = event.url
|
||||
dr.loadUrl(url)
|
||||
dr.resize(event.width, event.height)
|
||||
logger.info { "Loading URL $url" }
|
||||
}
|
||||
is ResizeMessage -> {
|
||||
dr.resize(event.width, event.height)
|
||||
logger.info { "Resize browser" }
|
||||
}
|
||||
is JsEventMessage -> {
|
||||
dr.event(event)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logger.warn(e) { "Failed to deserialize client request: ${ctx.message()}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -256,3 +256,27 @@ class FirstUnreadChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType?>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class HighestNumberedChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType?> {
|
||||
override val dataLoaderName = "HighestNumberedChapterForMangaDataLoader"
|
||||
|
||||
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType?> =
|
||||
DataLoaderFactory.newDataLoader<Int, ChapterType?> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val highestNumberedChaptersByMangaId =
|
||||
ChapterTable
|
||||
.selectAll()
|
||||
.where { (ChapterTable.manga inList ids) and (ChapterTable.chapter_number greater 0f) }
|
||||
.orderBy(ChapterTable.chapter_number to SortOrder.DESC_NULLS_LAST)
|
||||
.groupBy { it[ChapterTable.manga].value }
|
||||
ids.map { id ->
|
||||
highestNumberedChaptersByMangaId[id]
|
||||
?.firstOrNull()
|
||||
?.let { chapter -> ChapterType(chapter) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,9 @@ import suwayomi.tachidesk.graphql.types.TrackStatusType
|
||||
import suwayomi.tachidesk.graphql.types.TrackerType
|
||||
import suwayomi.tachidesk.manga.impl.track.tracker.TrackerManager
|
||||
import suwayomi.tachidesk.manga.impl.track.tracker.model.toTrack
|
||||
import suwayomi.tachidesk.manga.impl.track.tracker.model.toTrackSearch
|
||||
import suwayomi.tachidesk.manga.model.table.TrackRecordTable
|
||||
import suwayomi.tachidesk.manga.model.table.TrackSearchTable
|
||||
import suwayomi.tachidesk.server.JavalinSetup.future
|
||||
|
||||
class TrackerDataLoader : KotlinDataLoader<Int, TrackerType> {
|
||||
@@ -116,7 +118,30 @@ class DisplayScoreForTrackRecordDataLoader : KotlinDataLoader<Int, String> {
|
||||
.toList()
|
||||
.map { it.toTrack() }
|
||||
.associateBy { it.id!! }
|
||||
.mapValues { TrackerManager.getTracker(it.value.sync_id)?.displayScore(it.value) }
|
||||
.mapValues { TrackerManager.getTracker(it.value.tracker_id)?.displayScore(it.value) }
|
||||
|
||||
ids.map { trackRecords[it] }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DisplayScoreForTrackSearchDataLoader : KotlinDataLoader<Int, String> {
|
||||
override val dataLoaderName = "DisplayScoreForTrackSearchDataLoader"
|
||||
|
||||
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, String> =
|
||||
DataLoaderFactory.newDataLoader<Int, String> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val trackRecords =
|
||||
TrackSearchTable
|
||||
.selectAll()
|
||||
.where { TrackSearchTable.id inList ids }
|
||||
.toList()
|
||||
.map { it.toTrackSearch() }
|
||||
.associateBy { it.id!! }
|
||||
.mapValues { TrackerManager.getTracker(it.value.tracker_id)?.displayScore(it.value) }
|
||||
|
||||
ids.map { trackRecords[it] }
|
||||
}
|
||||
|
||||
@@ -64,6 +64,8 @@ class BackupMutation {
|
||||
includeChapters = input?.includeChapters ?: true,
|
||||
includeTracking = true,
|
||||
includeHistory = true,
|
||||
includeClientData = true,
|
||||
includeServerSettings = true,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user