Skip to content

Commit f73688c

Browse files
authored
Merge pull request #721 from esensar/feature/password-protected-zips-compress
Add support for creating password protected zips
2 parents 976d9ad + 4bf66d7 commit f73688c

File tree

4 files changed

+74
-18
lines changed

4 files changed

+74
-18
lines changed

app/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ android {
6464
}
6565

6666
dependencies {
67-
implementation 'com.github.SimpleMobileTools:Simple-Commons:2d4e07e5f4'
67+
implementation 'com.github.SimpleMobileTools:Simple-Commons:79b117a9ba'
6868
implementation 'com.github.tibbi:AndroidPdfViewer:e6a533125b'
6969
implementation 'com.github.Stericson:RootTools:df729dcb13'
7070
implementation 'com.github.Stericson:RootShell:1.6'

app/src/main/kotlin/com/simplemobiletools/filemanager/pro/adapters/ItemsAdapter.kt

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,14 @@ import kotlinx.android.synthetic.main.item_file_grid.view.*
4646
import kotlinx.android.synthetic.main.item_section.view.*
4747
import net.lingala.zip4j.exception.ZipException
4848
import net.lingala.zip4j.io.inputstream.ZipInputStream
49+
import net.lingala.zip4j.io.outputstream.ZipOutputStream
4950
import net.lingala.zip4j.model.LocalFileHeader
51+
import net.lingala.zip4j.model.ZipParameters
52+
import net.lingala.zip4j.model.enums.EncryptionMethod
5053
import java.io.BufferedInputStream
5154
import java.io.Closeable
5255
import java.io.File
5356
import java.util.*
54-
import java.util.zip.ZipEntry
55-
import java.util.zip.ZipOutputStream
5657

5758
class ItemsAdapter(
5859
activity: SimpleActivity, var listItems: MutableList<ListItem>, val listener: ItemOperationsListener?, recyclerView: MyRecyclerView,
@@ -484,8 +485,7 @@ class ItemsAdapter(
484485
return
485486
}
486487

487-
CompressAsDialog(activity, firstPath) {
488-
val destination = it
488+
CompressAsDialog(activity, firstPath) { destination, password ->
489489
activity.handleAndroidSAFDialog(firstPath) { granted ->
490490
if (!granted) {
491491
return@handleAndroidSAFDialog
@@ -498,7 +498,7 @@ class ItemsAdapter(
498498
activity.toast(R.string.compressing)
499499
val paths = getSelectedFileDirItems().map { it.path }
500500
ensureBackgroundThread {
501-
if (compressPaths(paths, destination)) {
501+
if (compressPaths(paths, destination, password)) {
502502
activity.runOnUiThread {
503503
activity.toast(R.string.compression_successful)
504504
listener?.refreshFragment()
@@ -648,13 +648,21 @@ class ItemsAdapter(
648648
}
649649

650650
@SuppressLint("NewApi")
651-
private fun compressPaths(sourcePaths: List<String>, targetPath: String): Boolean {
651+
private fun compressPaths(sourcePaths: List<String>, targetPath: String, password: String? = null): Boolean {
652652
val queue = LinkedList<String>()
653653
val fos = activity.getFileOutputStreamSync(targetPath, "application/zip") ?: return false
654654

655-
val zout = ZipOutputStream(fos)
655+
val zout = password?.let { ZipOutputStream(fos, password.toCharArray()) } ?: ZipOutputStream(fos)
656656
var res: Closeable = fos
657657

658+
fun zipEntry(name: String) = ZipParameters().also {
659+
it.fileNameInZip = name
660+
if (password != null) {
661+
it.isEncryptFiles = true
662+
it.encryptionMethod = EncryptionMethod.AES
663+
}
664+
}
665+
658666
try {
659667
sourcePaths.forEach { currentPath ->
660668
var name: String
@@ -664,7 +672,11 @@ class ItemsAdapter(
664672
queue.push(mainFilePath)
665673
if (activity.getIsPathDirectory(mainFilePath)) {
666674
name = "${mainFilePath.getFilenameFromPath()}/"
667-
zout.putNextEntry(ZipEntry(name))
675+
zout.putNextEntry(
676+
ZipParameters().also {
677+
it.fileNameInZip = name
678+
}
679+
)
668680
}
669681

670682
while (!queue.isEmpty()) {
@@ -677,9 +689,9 @@ class ItemsAdapter(
677689
if (activity.getIsPathDirectory(file.path)) {
678690
queue.push(file.path)
679691
name = "${name.trimEnd('/')}/"
680-
zout.putNextEntry(ZipEntry(name))
692+
zout.putNextEntry(zipEntry(name))
681693
} else {
682-
zout.putNextEntry(ZipEntry(name))
694+
zout.putNextEntry(zipEntry(name))
683695
activity.getFileInputStreamSync(file.path)!!.copyTo(zout)
684696
zout.closeEntry()
685697
}
@@ -692,9 +704,9 @@ class ItemsAdapter(
692704
if (activity.getIsPathDirectory(file.absolutePath)) {
693705
queue.push(file.absolutePath)
694706
name = "${name.trimEnd('/')}/"
695-
zout.putNextEntry(ZipEntry(name))
707+
zout.putNextEntry(zipEntry(name))
696708
} else {
697-
zout.putNextEntry(ZipEntry(name))
709+
zout.putNextEntry(zipEntry(name))
698710
activity.getFileInputStreamSync(file.path)!!.copyTo(zout)
699711
zout.closeEntry()
700712
}
@@ -703,7 +715,7 @@ class ItemsAdapter(
703715

704716
} else {
705717
name = if (base == currentPath) currentPath.getFilenameFromPath() else mainFilePath.relativizeWith(base)
706-
zout.putNextEntry(ZipEntry(name))
718+
zout.putNextEntry(zipEntry(name))
707719
activity.getFileInputStreamSync(mainFilePath)!!.copyTo(zout)
708720
zout.closeEntry()
709721
}

app/src/main/kotlin/com/simplemobiletools/filemanager/pro/dialogs/CompressAsDialog.kt

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,9 @@ import com.simplemobiletools.commons.dialogs.FilePickerDialog
77
import com.simplemobiletools.commons.extensions.*
88
import com.simplemobiletools.filemanager.pro.R
99
import com.simplemobiletools.filemanager.pro.extensions.config
10-
import kotlinx.android.synthetic.main.dialog_compress_as.view.filename_value
11-
import kotlinx.android.synthetic.main.dialog_compress_as.view.folder
10+
import kotlinx.android.synthetic.main.dialog_compress_as.view.*
1211

13-
class CompressAsDialog(val activity: BaseSimpleActivity, val path: String, val callback: (destination: String) -> Unit) {
12+
class CompressAsDialog(val activity: BaseSimpleActivity, val path: String, val callback: (destination: String, password: String?) -> Unit) {
1413
private val view = activity.layoutInflater.inflate(R.layout.dialog_compress_as, null)
1514

1615
init {
@@ -29,6 +28,10 @@ class CompressAsDialog(val activity: BaseSimpleActivity, val path: String, val c
2928
realPath = it
3029
}
3130
}
31+
32+
password_protect.setOnCheckedChangeListener { _, _ ->
33+
enter_password_hint.beVisibleIf(password_protect.isChecked)
34+
}
3235
}
3336

3437
activity.getAlertDialogBuilder()
@@ -39,6 +42,14 @@ class CompressAsDialog(val activity: BaseSimpleActivity, val path: String, val c
3942
alertDialog.showKeyboard(view.filename_value)
4043
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(View.OnClickListener {
4144
val name = view.filename_value.value
45+
var password: String? = null
46+
if (view.password_protect.isChecked) {
47+
password = view.password.value
48+
if (password.isEmpty()) {
49+
activity.toast(R.string.empty_password_new)
50+
return@OnClickListener
51+
}
52+
}
4253
when {
4354
name.isEmpty() -> activity.toast(R.string.empty_name)
4455
name.isAValidFilename() -> {
@@ -49,8 +60,9 @@ class CompressAsDialog(val activity: BaseSimpleActivity, val path: String, val c
4960
}
5061

5162
alertDialog.dismiss()
52-
callback(newPath)
63+
callback(newPath, password)
5364
}
65+
5466
else -> activity.toast(R.string.invalid_name)
5567
}
5668
})

app/src/main/res/layout/dialog_compress_as.xml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:app="http://schemas.android.com/apk/res-auto"
34
android:id="@+id/dialog_holder"
45
android:layout_width="match_parent"
56
android:layout_height="match_parent"
@@ -39,4 +40,35 @@
3940
android:textSize="@dimen/bigger_text_size" />
4041

4142
</com.simplemobiletools.commons.views.MyTextInputLayout>
43+
44+
<com.simplemobiletools.commons.views.MyAppCompatCheckbox
45+
android:id="@+id/password_protect"
46+
android:layout_width="match_parent"
47+
android:layout_height="wrap_content"
48+
android:layout_below="@id/filename_hint"
49+
android:layout_marginTop="@dimen/small_margin"
50+
android:paddingTop="@dimen/normal_margin"
51+
android:paddingBottom="@dimen/normal_margin"
52+
android:text="@string/add_password" />
53+
54+
<com.simplemobiletools.commons.views.MyTextInputLayout
55+
android:id="@+id/enter_password_hint"
56+
android:layout_width="match_parent"
57+
android:layout_height="wrap_content"
58+
android:layout_below="@id/password_protect"
59+
app:passwordToggleEnabled="true"
60+
android:hint="@string/password"
61+
android:visibility="gone">
62+
63+
<com.google.android.material.textfield.TextInputEditText
64+
android:id="@+id/password"
65+
android:layout_width="match_parent"
66+
android:layout_height="wrap_content"
67+
android:layout_marginBottom="@dimen/activity_margin"
68+
android:inputType="textPassword"
69+
android:singleLine="true"
70+
android:textCursorDrawable="@null"
71+
android:textSize="@dimen/normal_text_size" />
72+
73+
</com.simplemobiletools.commons.views.MyTextInputLayout>
4274
</RelativeLayout>

0 commit comments

Comments
 (0)