Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move compression and decompression into a service #742

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ android {
}

dependencies {
implementation 'com.github.SimpleMobileTools:Simple-Commons:d1629c7f1a'
implementation 'com.github.esensar:Simple-Commons:ef5602aee2'
implementation 'com.github.tibbi:AndroidPdfViewer:e6a533125b'
implementation 'com.github.Stericson:RootTools:df729dcb13'
implementation 'com.github.Stericson:RootShell:1.6'
Expand Down
8 changes: 8 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />

<uses-feature
android:name="android.hardware.faketouch"
Expand Down Expand Up @@ -127,6 +129,12 @@
</intent-filter>
</activity>

<service android:name=".services.CompressionService"
android:exported="false"
android:foregroundServiceType="specialUse">
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE" android:value="File decompression" />
</service>

<activity
android:name=".activities.SaveAsActivity"
android:configChanges="orientation"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
package com.simplemobiletools.filemanager.pro.activities

import android.annotation.SuppressLint
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import com.simplemobiletools.commons.dialogs.EnterPasswordDialog
import com.simplemobiletools.commons.dialogs.FilePickerDialog
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.NavigationIcon
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.commons.helpers.isOreoPlus
import com.simplemobiletools.commons.helpers.*
import com.simplemobiletools.filemanager.pro.R
import com.simplemobiletools.filemanager.pro.adapters.DecompressItemsAdapter
import com.simplemobiletools.filemanager.pro.databinding.ActivityDecompressBinding
import com.simplemobiletools.filemanager.pro.extensions.config
import com.simplemobiletools.filemanager.pro.models.ListItem
import com.simplemobiletools.filemanager.pro.services.CompressionService
import net.lingala.zip4j.exception.ZipException
import net.lingala.zip4j.exception.ZipException.Type
import net.lingala.zip4j.io.inputstream.ZipInputStream
import net.lingala.zip4j.model.LocalFileHeader
import java.io.BufferedInputStream
import java.io.File

class DecompressActivity : SimpleActivity() {
companion object {
Expand Down Expand Up @@ -94,12 +93,17 @@ class DecompressActivity : SimpleActivity() {
currentPath = path
try {
val listItems = getFolderItems(currentPath)
DecompressItemsAdapter(this, listItems, binding.decompressList) {
if ((it as ListItem).isDirectory) {
updateCurrentPath(it.path)
val existingAdapter = (binding.decompressList.adapter as? DecompressItemsAdapter)
if (existingAdapter != null) {
existingAdapter.updateItems(listItems)
} else {
DecompressItemsAdapter(this, listItems, binding.decompressList) {
if ((it as ListItem).isDirectory) {
updateCurrentPath(it.path)
}
}.apply {
binding.decompressList.adapter = this
}
}.apply {
binding.decompressList.adapter = this
}
} catch (e: Exception) {
showErrorToast(e)
Expand All @@ -111,66 +115,18 @@ class DecompressActivity : SimpleActivity() {
FilePickerDialog(this, defaultFolder, false, config.showHidden, true, true, showFavoritesButton = true) { destination ->
handleSAFDialog(destination) {
if (it) {
ensureBackgroundThread {
decompressTo(destination)
Intent(this, CompressionService::class.java).apply {
action = CompressionService.ACTION_DECOMPRESS
putExtra(CompressionService.EXTRA_URI, uri!!)
putExtra(CompressionService.EXTRA_PASSWORD, password)
putExtra(CompressionService.EXTRA_DESTINATION, destination)
startService(this)
}
}
}
}
}

private fun decompressTo(destination: String) {
try {
val inputStream = contentResolver.openInputStream(uri!!)
val zipInputStream = ZipInputStream(BufferedInputStream(inputStream!!))
if (password != null) {
zipInputStream.setPassword(password?.toCharArray())
}
val buffer = ByteArray(1024)

zipInputStream.use {
while (true) {
val entry = zipInputStream.nextEntry ?: break
val filename = title.toString().substringBeforeLast(".")
val parent = "$destination/$filename"
val newPath = "$parent/${entry.fileName.trimEnd('/')}"

if (!getDoesFilePathExist(parent)) {
if (!createDirectorySync(parent)) {
continue
}
}

if (entry.isDirectory) {
continue
}

val isVulnerableForZipPathTraversal = !File(newPath).canonicalPath.startsWith(parent)
if (isVulnerableForZipPathTraversal) {
continue
}

val fos = getFileOutputStreamSync(newPath, newPath.getMimeType())
var count: Int
while (true) {
count = zipInputStream.read(buffer)
if (count == -1) {
break
}

fos!!.write(buffer, 0, count)
}
fos!!.close()
}

toast(R.string.decompression_successful)
finish()
}
} catch (e: Exception) {
showErrorToast(e)
}
}

private fun getFolderItems(parent: String): ArrayList<ListItem> {
return allFiles.filter {
val fileParent = if (it.path.contains("/")) {
Expand All @@ -185,47 +141,72 @@ class DecompressActivity : SimpleActivity() {

@SuppressLint("NewApi")
private fun fillAllListItems(uri: Uri) {
val inputStream = try {
contentResolver.openInputStream(uri)
} catch (e: Exception) {
showErrorToast(e)
return
}
binding.progressBar.beVisible()
ensureBackgroundThread {
val inputStream = try {
contentResolver.openInputStream(uri)
} catch (e: Exception) {
runOnUiThread {
showErrorToast(e)
binding.progressBar.beGone()
}
return@ensureBackgroundThread
}

val zipInputStream = ZipInputStream(BufferedInputStream(inputStream))
if (password != null) {
zipInputStream.setPassword(password?.toCharArray())
}
var zipEntry: LocalFileHeader?
while (true) {
try {
zipEntry = zipInputStream.nextEntry
} catch (passwordException: ZipException) {
if (passwordException.type == Type.WRONG_PASSWORD) {
if (password != null) {
toast(getString(R.string.invalid_password))
passwordDialog?.clearPassword()
val zipInputStream = ZipInputStream(BufferedInputStream(inputStream))
if (password != null) {
zipInputStream.setPassword(password?.toCharArray())
}
var zipEntry: LocalFileHeader?
while (true) {
try {
zipEntry = zipInputStream.nextEntry
} catch (passwordException: ZipException) {
if (passwordException.type == Type.WRONG_PASSWORD) {
if (password != null) {
runOnUiThread {
toast(getString(R.string.invalid_password))
binding.progressBar.beGone()
if (passwordDialog == null) {
askForPassword()
} else {
passwordDialog?.clearPassword()
}
}
} else {
runOnUiThread {
binding.progressBar.beGone()
askForPassword()
}
}
return@ensureBackgroundThread
} else {
askForPassword()
break
}
return
} else {
} catch (ignored: Exception) {
break
}
} catch (ignored: Exception) {
break
}

if (zipEntry == null) {
break
}
if (zipEntry == null) {
break
}

val lastModified = if (isOreoPlus()) zipEntry.lastModifiedTime else 0
val filename = zipEntry.fileName.removeSuffix("/")
val listItem = ListItem(filename, filename.getFilenameFromPath(), zipEntry.isDirectory, 0, 0L, lastModified, false, false)
allFiles.add(listItem)
val lastModified = if (isOreoPlus()) zipEntry.lastModifiedTime else 0
val filename = zipEntry.fileName.removeSuffix("/")
val listItem = ListItem(filename, filename.getFilenameFromPath(), zipEntry.isDirectory, 0, 0L, lastModified, false, false)
runOnUiThread {
allFiles.add(listItem)
passwordDialog?.dismiss(notify = false)
passwordDialog = null
updateCurrentPath(currentPath)
}
}
runOnUiThread {
binding.progressBar.beGone()
passwordDialog?.dismiss(notify = false)
passwordDialog = null
}
}
passwordDialog?.dismiss(notify = false)
}

private fun askForPassword() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import android.util.TypedValue
import android.view.Menu
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
Expand All @@ -21,6 +22,7 @@ import com.simplemobiletools.filemanager.pro.activities.SimpleActivity
import com.simplemobiletools.filemanager.pro.databinding.ItemDecompressionListFileDirBinding
import com.simplemobiletools.filemanager.pro.extensions.config
import com.simplemobiletools.filemanager.pro.models.ListItem
import java.util.ArrayList

class DecompressItemsAdapter(activity: SimpleActivity, var listItems: MutableList<ListItem>, recyclerView: MyRecyclerView, itemClick: (Any) -> Unit) :
MyRecyclerViewAdapter(activity, recyclerView, itemClick) {
Expand Down Expand Up @@ -135,4 +137,23 @@ class DecompressItemsAdapter(activity: SimpleActivity, var listItems: MutableLis
fileDrawable = resources.getDrawable(R.drawable.ic_file_generic)
fileDrawables = getFilePlaceholderDrawables(activity)
}

fun updateItems(listItems: ArrayList<ListItem>) {
val diffResult = DiffUtil.calculateDiff(DiffCallback(this.listItems, listItems))
this.listItems.clear()
this.listItems.addAll(listItems)
diffResult.dispatchUpdatesTo(this)
}

class DiffCallback(private val oldList: List<ListItem>, private val newList: List<ListItem>) : DiffUtil.Callback() {
override fun getOldListSize(): Int = oldList.size
override fun getNewListSize(): Int = newList.size

override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
oldList[oldItemPosition].path == newList[newItemPosition].path

override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean =
oldList[oldItemPosition] == newList[newItemPosition]

}
}
Loading