feat: Camera, Audio, Video & Network Optimizations
This commit is contained in:
@@ -170,124 +170,22 @@ class DownloadProcessor (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveToConfiguredFolder(
|
private fun streamsMatch(stream1: InputStream, stream2: InputStream): Boolean {
|
||||||
configuredFolder: String,
|
stream1.use { s1 ->
|
||||||
fileName: String,
|
stream2.use { s2 ->
|
||||||
fileType: FileType,
|
val buffer1 = ByteArray(1024 * 1024)
|
||||||
inputFile: File,
|
val buffer2 = ByteArray(1024 * 1024)
|
||||||
metadata: DownloadMetadata,
|
while (true) {
|
||||||
pendingTask: PendingTask,
|
val read1 = s1.read(buffer1)
|
||||||
): GallerySaveResult {
|
val read2 = s2.read(buffer2)
|
||||||
val outputFolder = DocumentFile.fromTreeUri(remoteSideContext.androidContext, Uri.parse(configuredFolder))
|
if (read1 != read2) return false
|
||||||
?: throw Exception("Failed to open output folder")
|
if (read1 == -1) return true
|
||||||
|
for (i in 0 until read1) {
|
||||||
val outputFileFolder = metadata.outputPath.let {
|
if (buffer1[i] != buffer2[i]) return false
|
||||||
if (it.contains("/")) {
|
|
||||||
it.substringBeforeLast("/").split("/").fold(outputFolder) { folder, name ->
|
|
||||||
folder.findFile(name)
|
|
||||||
?: folder.createDirectory(name)
|
|
||||||
?: throw Exception("Failed to create output directory $name")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
outputFolder
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
outputFileFolder.findFile(fileName)?.let { existingFile ->
|
|
||||||
pendingTask.updateProgress("Comparing existing media")
|
|
||||||
if (existingFile.length() != inputFile.length()) {
|
|
||||||
existingFile.delete()
|
|
||||||
} else {
|
|
||||||
val existingInputStream = remoteSideContext.androidContext.contentResolver.openInputStream(existingFile.uri)
|
|
||||||
?: throw Exception("Failed to open existing media for comparison")
|
|
||||||
|
|
||||||
existingInputStream.use { currentExistingInputStream ->
|
|
||||||
val buffer1 = ByteArray(1024 * 1024)
|
|
||||||
val buffer2 = ByteArray(1024 * 1024)
|
|
||||||
var read1: Int
|
|
||||||
var read2: Int
|
|
||||||
|
|
||||||
inputFile.inputStream().use { inputStream ->
|
|
||||||
while (true) {
|
|
||||||
read1 = inputStream.read(buffer1)
|
|
||||||
read2 = currentExistingInputStream.read(buffer2)
|
|
||||||
if (read1 != read2 || (read1 > 0 && !buffersMatch(buffer1, buffer2, read1))) {
|
|
||||||
existingFile.delete()
|
|
||||||
return@let
|
|
||||||
}
|
|
||||||
if (read1 == -1) break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return GallerySaveResult(existingFile.uri, alreadyDownloaded = true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val outputFile = outputFileFolder.createFile(fileType.mimeType, fileName)
|
|
||||||
?: throw Exception("Failed to create output file $fileName")
|
|
||||||
|
|
||||||
pendingTask.updateProgress("Saving media to gallery")
|
|
||||||
val outputStream = remoteSideContext.androidContext.contentResolver.openOutputStream(outputFile.uri)
|
|
||||||
?: throw Exception("Failed to open output stream for $fileName")
|
|
||||||
|
|
||||||
outputStream.use { currentOutputStream ->
|
|
||||||
inputFile.inputStream().use { inputStream ->
|
|
||||||
inputStream.copyTo(currentOutputStream)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return GallerySaveResult(outputFile.uri)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun buffersMatch(left: ByteArray, right: ByteArray, length: Int): Boolean {
|
|
||||||
for (index in 0 until length) {
|
|
||||||
if (left[index] != right[index]) return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun buildOutputFileName(outputPath: String, fileType: FileType): String {
|
|
||||||
val rawName = outputPath.substringAfterLast("/").ifBlank { "media" }
|
|
||||||
val sanitizedBase = sanitizeFileName(rawName.substringBeforeLast(".", rawName))
|
|
||||||
val currentExtension = rawName.substringAfterLast(".", "").lowercase()
|
|
||||||
val targetExtension = fileType.fileExtension?.lowercase() ?: "dat"
|
|
||||||
val extension = if (currentExtension == targetExtension) {
|
|
||||||
currentExtension
|
|
||||||
} else {
|
|
||||||
targetExtension
|
|
||||||
}
|
|
||||||
return "$sanitizedBase.$extension"
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun sanitizeFileName(name: String): String {
|
|
||||||
return name
|
|
||||||
.replace(Regex("[\\\\/:*?\"<>|]"), "_")
|
|
||||||
.replace(Regex("\\p{Cntrl}"), "")
|
|
||||||
.replace(Regex("\\s+"), " ")
|
|
||||||
.trim()
|
|
||||||
.trim('.')
|
|
||||||
.ifBlank { "media" }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun sanitizeRelativePath(path: String): String {
|
|
||||||
return path.split("/")
|
|
||||||
.mapNotNull { segment ->
|
|
||||||
segment.trim()
|
|
||||||
.takeIf { it.isNotBlank() }
|
|
||||||
?.let(::sanitizeFileName)
|
|
||||||
}
|
|
||||||
.joinToString("/")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun appendNameSuffix(fileName: String, index: Int): String {
|
|
||||||
val extension = fileName.substringAfterLast('.', "")
|
|
||||||
val baseName = fileName.substringBeforeLast(".", fileName)
|
|
||||||
return if (extension.isBlank()) {
|
|
||||||
"$baseName ($index)"
|
|
||||||
} else {
|
|
||||||
"$baseName ($index).$extension"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findExistingMediaUri(collection: Uri, fileName: String, relativePath: String): Uri? {
|
private fun findExistingMediaUri(collection: Uri, fileName: String, relativePath: String): Uri? {
|
||||||
@@ -312,29 +210,103 @@ class DownloadProcessor (
|
|||||||
return streamsMatch(existingInputStream, inputFile.inputStream())
|
return streamsMatch(existingInputStream, inputFile.inputStream())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun filesMatch(existingFile: File, inputFile: File): Boolean {
|
private fun buildOutputFileName(outputPath: String, fileType: FileType): String {
|
||||||
return streamsMatch(existingFile.inputStream(), inputFile.inputStream())
|
val rawName = outputPath.trimEnd('/').substringAfterLast("/").ifBlank { "media" }
|
||||||
|
val targetExtension = fileType.fileExtension?.lowercase() ?: "dat"
|
||||||
|
|
||||||
|
val baseName = if (rawName.lowercase().endsWith(".$targetExtension")) {
|
||||||
|
rawName.substringBeforeLast(".")
|
||||||
|
} else {
|
||||||
|
rawName
|
||||||
|
}
|
||||||
|
|
||||||
|
return "${sanitizeFileName(baseName)}.$targetExtension"
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun streamsMatch(existingInputStream: InputStream, inputInputStream: InputStream): Boolean {
|
private fun sanitizeFileName(name: String): String {
|
||||||
existingInputStream.use { currentExistingInputStream ->
|
return name
|
||||||
val buffer1 = ByteArray(1024 * 1024)
|
.replace(Regex("[\\\\/:*?\"<>|]"), "_")
|
||||||
val buffer2 = ByteArray(1024 * 1024)
|
.replace(Regex("\\p{Cntrl}"), "")
|
||||||
var read1: Int
|
.replace(Regex("\\s+"), " ")
|
||||||
var read2: Int
|
.trim()
|
||||||
|
.trim('.')
|
||||||
|
.ifBlank { "media" }
|
||||||
|
}
|
||||||
|
|
||||||
inputInputStream.use { inputStream ->
|
private fun sanitizeRelativePath(path: String): String {
|
||||||
while (true) {
|
return path.trimEnd('/').split("/")
|
||||||
read1 = inputStream.read(buffer1)
|
.mapNotNull { segment ->
|
||||||
read2 = currentExistingInputStream.read(buffer2)
|
segment.trim()
|
||||||
if (read1 != read2 || (read1 > 0 && !buffersMatch(buffer1, buffer2, read1))) {
|
.takeIf { it.isNotBlank() }
|
||||||
return false
|
?.let(::sanitizeFileName)
|
||||||
}
|
}
|
||||||
if (read1 == -1) break
|
.joinToString("/")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun appendNameSuffix(fileName: String, index: Int): String {
|
||||||
|
val extension = fileName.substringAfterLast('.', "")
|
||||||
|
val baseName = fileName.substringBeforeLast(".", fileName)
|
||||||
|
return if (extension.isBlank()) {
|
||||||
|
"$baseName ($index)"
|
||||||
|
} else {
|
||||||
|
"$baseName ($index).$extension"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveToConfiguredFolder(
|
||||||
|
configuredFolder: String,
|
||||||
|
fileName: String,
|
||||||
|
fileType: FileType,
|
||||||
|
inputFile: File,
|
||||||
|
metadata: DownloadMetadata,
|
||||||
|
pendingTask: PendingTask,
|
||||||
|
): GallerySaveResult {
|
||||||
|
val outputFolder = DocumentFile.fromTreeUri(remoteSideContext.androidContext, Uri.parse(configuredFolder))
|
||||||
|
?: throw Exception("Failed to open output folder")
|
||||||
|
|
||||||
|
val outputFileFolder = metadata.outputPath.let {
|
||||||
|
if (it.contains("/")) {
|
||||||
|
it.substringBeforeLast("/").split("/").fold(outputFolder) { folder, name ->
|
||||||
|
folder.findFile(name)
|
||||||
|
?: folder.createDirectory(name)
|
||||||
|
?: throw Exception("Failed to create output directory $name")
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
outputFolder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
|
var finalFileName = fileName
|
||||||
|
var collisionCount = 0
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
val existingFile = outputFileFolder.findFile(finalFileName) ?: break
|
||||||
|
|
||||||
|
if (existingFile.length() == inputFile.length()) {
|
||||||
|
val existingInputStream = remoteSideContext.androidContext.contentResolver.openInputStream(existingFile.uri)
|
||||||
|
if (existingInputStream != null && streamsMatch(existingInputStream, inputFile.inputStream())) {
|
||||||
|
return GallerySaveResult(existingFile.uri, alreadyDownloaded = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
collisionCount++
|
||||||
|
finalFileName = appendNameSuffix(fileName, collisionCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
val outputFile = outputFileFolder.createFile(fileType.mimeType, finalFileName)
|
||||||
|
?: throw Exception("Failed to create output file $finalFileName")
|
||||||
|
|
||||||
|
pendingTask.updateProgress("Saving media to gallery")
|
||||||
|
val outputStream = remoteSideContext.androidContext.contentResolver.openOutputStream(outputFile.uri)
|
||||||
|
?: throw Exception("Failed to open output stream for $finalFileName")
|
||||||
|
|
||||||
|
outputStream.use { currentOutputStream ->
|
||||||
|
inputFile.inputStream().use { inputStream ->
|
||||||
|
inputStream.copyTo(currentOutputStream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return GallerySaveResult(outputFile.uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveToSystemDefault(
|
private fun saveToSystemDefault(
|
||||||
@@ -346,7 +318,6 @@ class DownloadProcessor (
|
|||||||
val subPath = sanitizeRelativePath(
|
val subPath = sanitizeRelativePath(
|
||||||
metadata.outputPath.substringBeforeLast("/", missingDelimiterValue = "")
|
metadata.outputPath.substringBeforeLast("/", missingDelimiterValue = "")
|
||||||
.replace("\\", "/")
|
.replace("\\", "/")
|
||||||
.trim('/')
|
|
||||||
)
|
)
|
||||||
val baseRelative = when {
|
val baseRelative = when {
|
||||||
fileType.isImage -> Environment.DIRECTORY_PICTURES
|
fileType.isImage -> Environment.DIRECTORY_PICTURES
|
||||||
@@ -356,86 +327,65 @@ class DownloadProcessor (
|
|||||||
val relativePath = listOfNotNull(baseRelative, "PurrfectSnap", subPath.takeIf { it.isNotBlank() })
|
val relativePath = listOfNotNull(baseRelative, "PurrfectSnap", subPath.takeIf { it.isNotBlank() })
|
||||||
.joinToString("/") + "/"
|
.joinToString("/") + "/"
|
||||||
|
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
val collection = when {
|
val collection = when {
|
||||||
fileType.isImage -> MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
|
fileType.isImage -> MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
|
||||||
fileType.isVideo -> MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
|
fileType.isVideo -> MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
|
||||||
else -> MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
|
else -> MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
|
||||||
}
|
}
|
||||||
val resolver = remoteSideContext.androidContext.contentResolver
|
val resolver = remoteSideContext.androidContext.contentResolver
|
||||||
val sanitizedFileName = sanitizeFileName(fileName.substringBeforeLast(".", fileName)).let { baseName ->
|
|
||||||
val extension = fileName.substringAfterLast('.', "")
|
|
||||||
if (extension.isBlank()) baseName else "$baseName.$extension"
|
|
||||||
}
|
|
||||||
findExistingMediaUri(collection, sanitizedFileName, relativePath)?.let { existingUri ->
|
|
||||||
if (contentMatches(existingUri, inputFile)) {
|
|
||||||
remoteSideContext.log.verbose("Media already exists in gallery: $sanitizedFileName")
|
|
||||||
return GallerySaveResult(existingUri, alreadyDownloaded = true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (attempt in 0..100) {
|
for (attempt in 0..100) {
|
||||||
val candidateName = if (attempt == 0) sanitizedFileName else appendNameSuffix(sanitizedFileName, attempt)
|
val candidateName = if (attempt == 0) fileName else appendNameSuffix(fileName, attempt)
|
||||||
val values = ContentValues().apply {
|
|
||||||
put(MediaStore.MediaColumns.DISPLAY_NAME, candidateName)
|
findExistingMediaUri(collection, candidateName, relativePath)?.let { existingUri ->
|
||||||
put(MediaStore.MediaColumns.MIME_TYPE, fileType.mimeType)
|
if (contentMatches(existingUri, inputFile)) {
|
||||||
put(MediaStore.MediaColumns.RELATIVE_PATH, relativePath)
|
return GallerySaveResult(existingUri, alreadyDownloaded = true)
|
||||||
put(MediaStore.MediaColumns.IS_PENDING, 1)
|
}
|
||||||
}
|
return@let
|
||||||
|
} ?: run {
|
||||||
|
val values = ContentValues().apply {
|
||||||
|
put(MediaStore.MediaColumns.DISPLAY_NAME, candidateName)
|
||||||
|
put(MediaStore.MediaColumns.MIME_TYPE, fileType.mimeType)
|
||||||
|
put(MediaStore.MediaColumns.RELATIVE_PATH, relativePath)
|
||||||
|
put(MediaStore.MediaColumns.IS_PENDING, 1)
|
||||||
|
}
|
||||||
|
|
||||||
val uri = runCatching {
|
val uri = runCatching { resolver.insert(collection, values) }.getOrNull() ?: return@run
|
||||||
resolver.insert(collection, values)
|
|
||||||
}.onFailure {
|
|
||||||
remoteSideContext.log.verbose("MediaStore insert rejected $candidateName in $relativePath: ${it.message}")
|
|
||||||
}.getOrNull() ?: continue
|
|
||||||
|
|
||||||
runCatching {
|
runCatching {
|
||||||
resolver.openOutputStream(uri)?.use { out ->
|
resolver.openOutputStream(uri)?.use { out ->
|
||||||
inputFile.inputStream().use { it.copyTo(out) }
|
inputFile.inputStream().use { it.copyTo(out) }
|
||||||
} ?: throw IllegalStateException("Failed to open output stream for $candidateName")
|
} ?: throw IllegalStateException("Failed to open output stream")
|
||||||
|
|
||||||
ContentValues().apply {
|
ContentValues().apply { put(MediaStore.MediaColumns.IS_PENDING, 0) }.also { resolver.update(uri, it, null, null) }
|
||||||
put(MediaStore.MediaColumns.IS_PENDING, 0)
|
return GallerySaveResult(uri)
|
||||||
}.also { resolver.update(uri, it, null, null) }
|
}.onFailure {
|
||||||
|
runCatching { resolver.delete(uri, null, null) }
|
||||||
return GallerySaveResult(uri)
|
}
|
||||||
}.onFailure {
|
|
||||||
runCatching { resolver.delete(uri, null, null) }
|
|
||||||
remoteSideContext.log.error("Failed writing media to gallery for $candidateName", it)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
throw IllegalStateException("Failed to allocate unique filename")
|
||||||
throw IllegalStateException("Failed to allocate a unique gallery file for $sanitizedFileName in $relativePath")
|
|
||||||
} else {
|
} else {
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
val baseDir = Environment.getExternalStoragePublicDirectory(baseRelative)
|
val baseDir = Environment.getExternalStoragePublicDirectory(baseRelative)
|
||||||
val destDir = File(baseDir, "PurrfectSnap" + (if (subPath.isNotBlank()) "/$subPath" else ""))
|
val destDir = File(baseDir, "PurrfectSnap" + (if (subPath.isNotBlank()) "/$subPath" else ""))
|
||||||
destDir.mkdirs()
|
destDir.mkdirs()
|
||||||
val sanitizedFileName = sanitizeFileName(fileName.substringBeforeLast(".", fileName)).let { baseName ->
|
|
||||||
val extension = fileName.substringAfterLast('.', "")
|
var destFile = File(destDir, fileName)
|
||||||
if (extension.isBlank()) baseName else "$baseName.$extension"
|
var suffix = 1
|
||||||
}
|
while (destFile.exists()) {
|
||||||
var destFile = File(destDir, sanitizedFileName)
|
if (destFile.length() == inputFile.length() && streamsMatch(destFile.inputStream(), inputFile.inputStream())) {
|
||||||
if (destFile.exists()) {
|
|
||||||
if (destFile.length() == inputFile.length() && filesMatch(destFile, inputFile)) {
|
|
||||||
return GallerySaveResult(Uri.fromFile(destFile), alreadyDownloaded = true)
|
return GallerySaveResult(Uri.fromFile(destFile), alreadyDownloaded = true)
|
||||||
}
|
}
|
||||||
var suffix = 1
|
destFile = File(destDir, appendNameSuffix(fileName, suffix++))
|
||||||
while (destFile.exists()) {
|
|
||||||
destFile = File(destDir, appendNameSuffix(sanitizedFileName, suffix++))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
FileOutputStream(destFile).use { out ->
|
|
||||||
inputFile.inputStream().use { it.copyTo(out) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FileOutputStream(destFile).use { out -> inputFile.inputStream().use { it.copyTo(out) } }
|
||||||
runCatching {
|
runCatching {
|
||||||
remoteSideContext.androidContext.sendBroadcast(
|
remoteSideContext.androidContext.sendBroadcast(Intent("android.intent.action.MEDIA_SCANNER_SCAN_FILE").apply { data = Uri.fromFile(destFile) })
|
||||||
Intent("android.intent.action.MEDIA_SCANNER_SCAN_FILE").apply {
|
|
||||||
data = Uri.fromFile(destFile)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
GallerySaveResult(Uri.fromFile(destFile))
|
return GallerySaveResult(Uri.fromFile(destFile))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,5 +59,8 @@ class Camera : ConfigContainer() {
|
|||||||
val overrideBackResolution get() = _overrideBackResolution
|
val overrideBackResolution get() = _overrideBackResolution
|
||||||
val videoRecordTimer = boolean("video_record_timer")
|
val videoRecordTimer = boolean("video_record_timer")
|
||||||
|
|
||||||
|
val audioVideoOptimizations = boolean("audio_video", defaultValue = true) { requireRestart() }
|
||||||
|
val cameraOptimizations = boolean("camera_tweaks", defaultValue = false) { addNotices(FeatureNotice.UNSTABLE); requireRestart() }
|
||||||
|
|
||||||
val customResolution = string("custom_resolution") { addNotices(FeatureNotice.UNSTABLE); inputCheck = { it.matches(Regex("\\d+x\\d+")) } }
|
val customResolution = string("custom_resolution") { addNotices(FeatureNotice.UNSTABLE); inputCheck = { it.matches(Regex("\\d+x\\d+")) } }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ class Experimental : ConfigContainer() {
|
|||||||
val mediaFilePicker = boolean("media_file_picker") { requireRestart(); addNotices(FeatureNotice.UNSTABLE) }
|
val mediaFilePicker = boolean("media_file_picker") { requireRestart(); addNotices(FeatureNotice.UNSTABLE) }
|
||||||
val storyLogger = boolean("story_logger") { requireRestart(); addNotices(FeatureNotice.UNSTABLE); }
|
val storyLogger = boolean("story_logger") { requireRestart(); addNotices(FeatureNotice.UNSTABLE); }
|
||||||
val accountSwitcher = container("account_switcher", AccountSwitcherConfig()) { requireRestart(); addNotices(FeatureNotice.UNSTABLE) }
|
val accountSwitcher = container("account_switcher", AccountSwitcherConfig()) { requireRestart(); addNotices(FeatureNotice.UNSTABLE) }
|
||||||
|
val networkOptimization = boolean("network_optimization") { requireRestart() }
|
||||||
val betterTranscript = container("better_transcript", BetterTranscriptConfig()) { requireRestart() }
|
val betterTranscript = container("better_transcript", BetterTranscriptConfig()) { requireRestart() }
|
||||||
val voiceNoteAutoPlay = boolean("voice_note_auto_play") { requireRestart() }
|
val voiceNoteAutoPlay = boolean("voice_note_auto_play") { requireRestart() }
|
||||||
val friendNotes = boolean("friend_notes") { requireRestart() }
|
val friendNotes = boolean("friend_notes") { requireRestart() }
|
||||||
|
|||||||
@@ -161,6 +161,7 @@ class DeviceSpooferHook: Feature("Device Spoofer") {
|
|||||||
val overridePlayStoreInstallerPackageName by context.config.experimental.spoof.overridePlayStoreInstallerPackageName
|
val overridePlayStoreInstallerPackageName by context.config.experimental.spoof.overridePlayStoreInstallerPackageName
|
||||||
val removeVpnTransportFlag by context.config.experimental.spoof.removeVpnTransportFlag
|
val removeVpnTransportFlag by context.config.experimental.spoof.removeVpnTransportFlag
|
||||||
val forceWifiTransportFlag by context.config.experimental.spoof.forceWifiTransportFlag
|
val forceWifiTransportFlag by context.config.experimental.spoof.forceWifiTransportFlag
|
||||||
|
val networkOptimization by context.config.experimental.networkOptimization
|
||||||
val spoofAndroidId by context.config.experimental.spoof.spoofDeviceId.spoofAndroidId
|
val spoofAndroidId by context.config.experimental.spoof.spoofDeviceId.spoofAndroidId
|
||||||
|
|
||||||
if(overridePlayStoreInstallerPackageName) {
|
if(overridePlayStoreInstallerPackageName) {
|
||||||
@@ -270,6 +271,20 @@ class DeviceSpooferHook: Feature("Device Spoofer") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (networkOptimization) {
|
||||||
|
// Internal Buffer Optimization
|
||||||
|
findClass("java.net.Socket").apply {
|
||||||
|
hook("setSendBufferSize", HookStage.BEFORE) { param ->
|
||||||
|
val size = param.arg<Int>(0)
|
||||||
|
if (size < 1024 * 1024) param.setArg(0, 1024 * 1024) // Force 1MB Buffer
|
||||||
|
}
|
||||||
|
hook("setReceiveBufferSize", HookStage.BEFORE) { param ->
|
||||||
|
val size = param.arg<Int>(0)
|
||||||
|
if (size < 1024 * 1024) param.setArg(0, 1024 * 1024) // Force 1MB Buffer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (spoofAndroidId) {
|
if (spoofAndroidId) {
|
||||||
val gsfId = getRandomGsfId()
|
val gsfId = getRandomGsfId()
|
||||||
val wifiMac = generateRandomMacAddress()
|
val wifiMac = generateRandomMacAddress()
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import android.annotation.SuppressLint
|
|||||||
import android.content.ContextWrapper
|
import android.content.ContextWrapper
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
|
import android.media.MediaRecorder
|
||||||
|
import android.hardware.camera2.CaptureRequest
|
||||||
import android.hardware.camera2.CameraCharacteristics
|
import android.hardware.camera2.CameraCharacteristics
|
||||||
import android.hardware.camera2.CameraCharacteristics.Key
|
import android.hardware.camera2.CameraCharacteristics.Key
|
||||||
import android.hardware.camera2.CameraManager
|
import android.hardware.camera2.CameraManager
|
||||||
@@ -27,6 +29,34 @@ class CameraTweaks : Feature("Camera Tweaks") {
|
|||||||
override fun init() {
|
override fun init() {
|
||||||
val config = context.config.camera
|
val config = context.config.camera
|
||||||
|
|
||||||
|
// Toggle A: Audio & Video Optimizations (Bitrates)
|
||||||
|
if (config.audioVideoOptimizations.get()) {
|
||||||
|
MediaRecorder::class.java.hook("setVideoEncodingBitRate", HookStage.BEFORE) { param ->
|
||||||
|
val currentRate = param.arg<Int>(0)
|
||||||
|
if (currentRate < 30_000_000) param.setArg(0, 30_000_000)
|
||||||
|
}
|
||||||
|
MediaRecorder::class.java.hook("setAudioEncodingBitRate", HookStage.BEFORE) { param ->
|
||||||
|
param.setArg(0, 320_000)
|
||||||
|
}
|
||||||
|
MediaRecorder::class.java.hook("setAudioSamplingRate", HookStage.BEFORE) { param ->
|
||||||
|
param.setArg(0, 48_000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle B: Camera Optimizations (Hardware ISP - UNSTABLE)
|
||||||
|
if (config.cameraOptimizations.get()) {
|
||||||
|
CaptureRequest.Builder::class.java.hook("set", HookStage.BEFORE) { param ->
|
||||||
|
val key = param.arg<CaptureRequest.Key<*>>(0)
|
||||||
|
when (key) {
|
||||||
|
CaptureRequest.EDGE_MODE -> param.setArg(1, CaptureRequest.EDGE_MODE_HIGH_QUALITY)
|
||||||
|
CaptureRequest.NOISE_REDUCTION_MODE -> param.setArg(1, CaptureRequest.NOISE_REDUCTION_MODE_HIGH_QUALITY)
|
||||||
|
CaptureRequest.HOT_PIXEL_MODE -> param.setArg(1, CaptureRequest.HOT_PIXEL_MODE_HIGH_QUALITY)
|
||||||
|
CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE -> param.setArg(1, CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE_HIGH_QUALITY)
|
||||||
|
CaptureRequest.CONTROL_AF_MODE -> param.setArg(1, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val frontCameraId by lazy {
|
val frontCameraId by lazy {
|
||||||
runCatching { context.androidContext.getSystemService(CameraManager::class.java).run {
|
runCatching { context.androidContext.getSystemService(CameraManager::class.java).run {
|
||||||
cameraIdList.firstOrNull { getCameraCharacteristics(it).get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT }
|
cameraIdList.firstOrNull { getCameraCharacteristics(it).get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT }
|
||||||
|
|||||||
@@ -52,7 +52,11 @@ class MessageIndicators : Feature("Message Indicators") {
|
|||||||
contentAlignment = Alignment.TopEnd
|
contentAlignment = Alignment.TopEnd
|
||||||
) {
|
) {
|
||||||
val hasEncryption by rememberAsyncMutableState(defaultValue = false) {
|
val hasEncryption by rememberAsyncMutableState(defaultValue = false) {
|
||||||
reader.getByteArray(4, 3, 3) != null || reader.containsPath(3, 99, 3)
|
// Strictly detect Private Fidelius Wrap (1-on-1 private snaps)
|
||||||
|
reader.containsPath(4, 4, 1, 1) ||
|
||||||
|
reader.containsPath(4, 4, 1, 1, 1) ||
|
||||||
|
reader.getByteArray(4, 3, 3) != null ||
|
||||||
|
reader.containsPath(3, 99, 3)
|
||||||
}
|
}
|
||||||
val sentFromIosDevice by rememberAsyncMutableState(defaultValue = false) {
|
val sentFromIosDevice by rememberAsyncMutableState(defaultValue = false) {
|
||||||
if (reader.containsPath(4, 4, 3)) !reader.containsPath(4, 4, 3, 3, 17) else reader.getVarInt(4, 4, 11, 17, 7) != null
|
if (reader.containsPath(4, 4, 3)) !reader.containsPath(4, 4, 3, 3, 17) else reader.getVarInt(4, 4, 11, 17, 7) != null
|
||||||
|
|||||||
Reference in New Issue
Block a user