Skip to content

Commit

Permalink
add extraction support with "Open With" menu
Browse files Browse the repository at this point in the history
  • Loading branch information
WirelessAlien committed Dec 25, 2024
1 parent 4711585 commit bdf5d58
Show file tree
Hide file tree
Showing 9 changed files with 319 additions and 41 deletions.
38 changes: 12 additions & 26 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
android:icon="@mipmap/ic_launcher"
android:name=".App"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:targetApi="31" >
<activity
Expand All @@ -48,29 +47,6 @@
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
<intent-filter
android:label="ZipXtract"
tools:ignore="AppLinkUrlError">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*"/>

</intent-filter>

<meta-data
android:name="android.app.lib_name"
android:value="" />
</activity>

<activity
android:name=".PickedFilesActivity"
android:exported="true" >

<meta-data
android:name="android.app.lib_name"
Expand All @@ -87,6 +63,18 @@
android:resource="@xml/file_paths" />
</provider>

<activity android:name=".activity.OpenWithActivity"
android:theme="@style/AppTheme.Transparent"
android:exported="true">
<intent-filter android:label="Extract"
tools:ignore="AppLinkUrlError">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.OPENABLE" />
<data android:mimeType="*/*" />
</intent-filter>
</activity>

<service
android:name=".service.ExtractArchiveService"
android:foregroundServiceType="dataSync"
Expand Down Expand Up @@ -137,8 +125,6 @@
android:foregroundServiceType="dataSync"
android:exported="false" />



</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/*
* Copyright (C) 2023 WirelessAlien <https://github.com/WirelessAlien>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package com.wirelessalien.zipxtract.activity

import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.provider.OpenableColumns
import android.util.Log
import android.view.View
import android.widget.ProgressBar
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.textfield.TextInputEditText
import com.wirelessalien.zipxtract.R
import com.wirelessalien.zipxtract.service.ExtractArchiveService
import com.wirelessalien.zipxtract.service.ExtractCsArchiveService
import com.wirelessalien.zipxtract.service.ExtractRarService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import java.io.FileOutputStream
import java.io.InputStream

class OpenWithActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val uri = intent?.data
if (uri != null) {
showPasswordInputDialog(uri)
} else {
Toast.makeText(this, getString(R.string.no_file_selected), Toast.LENGTH_SHORT).show()
finish()
}
}

private fun showPasswordInputDialog(uri: Uri) {
val dialogView = layoutInflater.inflate(R.layout.password_input_open_with, null)
val passwordEditText = dialogView.findViewById<TextInputEditText>(R.id.passwordInput)
val progressBar = dialogView.findViewById<ProgressBar>(R.id.progressIndicator)

MaterialAlertDialogBuilder(this, R.style.MaterialDialog)
.setTitle(getString(R.string.enter_password))
.setView(dialogView)
.setPositiveButton(getString(R.string.ok)) { _, _ ->
val password = passwordEditText.text.toString()
progressBar.visibility = View.VISIBLE
CoroutineScope(Dispatchers.IO).launch {
val filePath = getRealPathFromURI(uri, this@OpenWithActivity)
withContext(Dispatchers.Main) {
progressBar.visibility = View.GONE
if (filePath != null) {
handleFileExtraction(filePath, password)
} else {
Log.i("OpenWithActivity", "Failed to get file path")
}
}
}
}
.setNegativeButton(getString(R.string.no_password)) { _, _ ->
progressBar.visibility = View.VISIBLE
CoroutineScope(Dispatchers.IO).launch {
val filePath = getRealPathFromURI(uri, this@OpenWithActivity)
withContext(Dispatchers.Main) {
progressBar.visibility = View.GONE
if (filePath != null) {
handleFileExtraction(filePath, null)
} else {
Log.i("OpenWithActivity", "Failed to get file path")
}
}
}
}
.setOnDismissListener {
finish()
}
.show()
}

private fun handleFileExtraction(filePath: String, password: String?) {
val fileExtension = filePath.split('.').takeLast(2).joinToString(".").lowercase()
val supportedExtensions = listOf("tar.bz2", "tar.gz", "tar.lz4", "tar.lzma", "tar.sz", "tar.xz")

when {
supportedExtensions.any { fileExtension.endsWith(it) } -> {
startExtractionCsService(filePath)
}
File(filePath).extension == "rar" -> {
startRarExtractionService(filePath, password)
}
else -> {
startExtractionService(filePath, password)
}
}
}

private fun getRealPathFromURI(uri: Uri, context: Context): String? {
val returnCursor = context.contentResolver.query(uri, null, null, null, null)
returnCursor?.use {
val nameIndex = it.getColumnIndex(OpenableColumns.DISPLAY_NAME)
val sizeIndex = it.getColumnIndex(OpenableColumns.SIZE)
it.moveToFirst()
val name = it.getString(nameIndex)
it.getLong(sizeIndex).toString()
val file = File(context.filesDir, name)
try {
val inputStream: InputStream? = context.contentResolver.openInputStream(uri)
val outputStream = FileOutputStream(file)
var read = 0
val maxBufferSize = 1 * 1024 * 1024
val bytesAvailable: Int = inputStream?.available() ?: 0
val bufferSize = bytesAvailable.coerceAtMost(maxBufferSize)
val buffers = ByteArray(bufferSize)
while (inputStream?.read(buffers).also { it1 ->
if (it1 != null) {
read = it1
}
} != -1) {
outputStream.write(buffers, 0, read)
}
inputStream?.close()
outputStream.close()
} catch (e: Exception) {
e.printStackTrace()
}
return file.path
}
return null
}

private fun startExtractionService(file: String, password: String?) {
val intent = Intent(this, ExtractArchiveService::class.java).apply {
putExtra(ExtractArchiveService.EXTRA_FILE_PATH, file)
putExtra(ExtractArchiveService.EXTRA_PASSWORD, password)
putExtra(ExtractArchiveService.EXTRA_USE_APP_NAME_DIR, true)
}
ContextCompat.startForegroundService(this, intent)
}

private fun startExtractionCsService(file: String) {
val intent = Intent(this, ExtractCsArchiveService::class.java).apply {
putExtra(ExtractCsArchiveService.EXTRA_FILE_PATH, file)
putExtra(ExtractCsArchiveService.EXTRA_USE_APP_NAME_DIR, true)
}
ContextCompat.startForegroundService(this, intent)
}

private fun startRarExtractionService(file: String, password: String?) {
val intent = Intent(this, ExtractRarService::class.java).apply {
putExtra(ExtractRarService.EXTRA_FILE_PATH, file)
putExtra(ExtractRarService.EXTRA_PASSWORD, password)
putExtra(ExtractRarService.EXTRA_USE_APP_NAME_DIR, true)
}
ContextCompat.startForegroundService(this, intent)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -172,13 +172,13 @@ class MainFragment : Fragment(), FileAdapter.OnItemClickListener, FileAdapter.On
ACTION_ARCHIVE_COMPLETE -> {
val dirPath = intent.getStringExtra(EXTRA_DIR_PATH)
if (dirPath != null) {
Snackbar.make(binding.root, getString(R.string.extraction_success), Snackbar.LENGTH_LONG)
Snackbar.make(binding.root, getString(R.string.archive_success), Snackbar.LENGTH_LONG)
.setAction(getString(R.string.open_folder)) {
navigateToParentDir(File(dirPath))
}
.show()
} else {
Toast.makeText(requireContext(), getString(R.string.extraction_success), Toast.LENGTH_SHORT).show()
Toast.makeText(requireContext(), getString(R.string.archive_success), Toast.LENGTH_SHORT).show()
}
unselectAllFiles()
aProgressDialog.dismiss()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import android.app.NotificationManager
import android.app.Service
import android.content.Intent
import android.os.Build
import android.os.Environment
import android.os.IBinder
import androidx.core.app.NotificationCompat
import androidx.localbroadcastmanager.content.LocalBroadcastManager
Expand Down Expand Up @@ -66,6 +67,7 @@ class ExtractArchiveService : Service() {
const val NOTIFICATION_ID = 18
const val EXTRA_FILE_PATH = "file_path"
const val EXTRA_PASSWORD = "password"
const val EXTRA_USE_APP_NAME_DIR = "useAppNameDir"
}

private var archiveFormat: ArchiveFormat? = null
Expand All @@ -81,6 +83,7 @@ class ExtractArchiveService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val filePath = intent?.getStringExtra(EXTRA_FILE_PATH)
val password = intent?.getStringExtra(EXTRA_PASSWORD)
val useAppNameDir = intent?.getBooleanExtra(EXTRA_USE_APP_NAME_DIR, false) ?: false

if (filePath == null) {
stopSelf()
Expand All @@ -90,7 +93,7 @@ class ExtractArchiveService : Service() {
startForeground(NOTIFICATION_ID, createNotification(0))

extractionJob = CoroutineScope(Dispatchers.IO).launch {
extractArchive(filePath, password)
extractArchive(filePath, password, useAppNameDir)
}

return START_NOT_STICKY
Expand Down Expand Up @@ -127,7 +130,7 @@ class ExtractArchiveService : Service() {
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
}

private fun extractArchive(filePath: String, password: String?) {
private fun extractArchive(filePath: String, password: String?, useAppNameDir: Boolean) {

if (filePath.isEmpty()) {
val errorMessage = getString(R.string.no_files_to_archive)
Expand All @@ -139,13 +142,23 @@ class ExtractArchiveService : Service() {

val file = File(filePath)
if (file.extension.equals("zip", ignoreCase = true)) {
extractZipArchive(file, password)
extractZipArchive(file, password, useAppNameDir)
return
}

try {
val inStream = RandomAccessFileInStream(RandomAccessFile(file, "r"))
val parentDir = file.parentFile ?: cacheDir
val parentDir: File
if (useAppNameDir) {
val rootDir = File(Environment.getExternalStorageDirectory().absolutePath)
parentDir = File(rootDir, getString(R.string.app_name))
if (!parentDir.exists()) {
parentDir.mkdirs()
}
} else {
parentDir = file.parentFile ?: cacheDir
}

val baseFileName = file.name.substring(0, file.name.lastIndexOf('.'))
var newFileName = baseFileName
var destinationDir = File(parentDir, newFileName)
Expand Down Expand Up @@ -177,6 +190,9 @@ class ExtractArchiveService : Service() {
sendLocalBroadcast(Intent(ACTION_EXTRACTION_ERROR).putExtra(EXTRA_ERROR_MESSAGE, e.message ?: getString(R.string.general_error_msg)))
} finally {
inStream.close()
if (useAppNameDir) {
filesDir.deleteRecursively()
}
}
} catch (e: Exception) {
e.printStackTrace()
Expand All @@ -185,15 +201,25 @@ class ExtractArchiveService : Service() {
}
}

private fun extractZipArchive(file: File, password: String?) {
private fun extractZipArchive(file: File, password: String?, useAppNameDir: Boolean) {
try {
val zipFile = ZipFile(file)

if (!password.isNullOrEmpty()) {
zipFile.setPassword(password.toCharArray())
}

val parentDir = file.parentFile ?: cacheDir
val parentDir: File
if (useAppNameDir) {
val rootDir = File(Environment.getExternalStorageDirectory().absolutePath)
parentDir = File(rootDir, getString(R.string.app_name))
if (!parentDir.exists()) {
parentDir.mkdirs()
}
} else {
parentDir = file.parentFile ?: cacheDir
}

val baseFileName = file.name.substring(0, file.name.lastIndexOf('.'))
var newFileName = baseFileName
var destinationDir = File(parentDir, newFileName)
Expand Down
Loading

0 comments on commit bdf5d58

Please sign in to comment.