From c8d7388780e46d4b29db9d819f58017373a83708 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=98=99=E2=97=A6=20The=20Tablet=20=E2=9D=80=20GamerGirla?= =?UTF-8?q?ndCo=20=E2=97=A6=E2=9D=A7?= Date: Tue, 28 Nov 2023 15:00:02 -0500 Subject: [PATCH] add support for sideloading addons via the addon manager or file picker --- app/src/forkDebug/res/menu/addons_menu.xml | 7 +++ app/src/main/AndroidManifest.xml | 13 ++++- .../mozilla/fenix/IntentReceiverActivity.kt | 4 +- .../addons/AddonInstallIntentProcessor.kt | 58 +++++++++++++++++++ .../fenix/addons/AddonsManagementFragment.kt | 36 ++++++++++-- .../fenix/components/IntentProcessorType.kt | 2 + .../fenix/components/IntentProcessors.kt | 4 ++ app/src/main/res/values/ids.xml | 4 ++ app/src/main/res/values/strings.xml | 1 + 9 files changed, 123 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/org/mozilla/fenix/addons/AddonInstallIntentProcessor.kt create mode 100644 app/src/main/res/values/ids.xml diff --git a/app/src/forkDebug/res/menu/addons_menu.xml b/app/src/forkDebug/res/menu/addons_menu.xml index 2314d36cb..48bfcfa7d 100644 --- a/app/src/forkDebug/res/menu/addons_menu.xml +++ b/app/src/forkDebug/res/menu/addons_menu.xml @@ -9,6 +9,12 @@ android:title="@string/addons_delete_cache" android:contentDescription="@string/addons_delete_cache" app:showAsAction="ifRoom|collapseActionView" /> + + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cd5dd7617..f937e1d26 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -51,7 +51,6 @@ android:theme="@style/NormalTheme" android:usesCleartextTraffic="true" tools:ignore="UnusedAttribute"> - @@ -216,6 +215,18 @@ + + + + + + + + + + + + diff --git a/app/src/main/java/org/mozilla/fenix/IntentReceiverActivity.kt b/app/src/main/java/org/mozilla/fenix/IntentReceiverActivity.kt index 32b89d5a7..200fe843a 100644 --- a/app/src/main/java/org/mozilla/fenix/IntentReceiverActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/IntentReceiverActivity.kt @@ -99,12 +99,14 @@ class IntentReceiverActivity : Activity() { private fun getIntentProcessors(private: Boolean): List { val modeDependentProcessors = if (private) { listOf( + components.intentProcessors.addonInstallIntentProcessor, components.intentProcessors.privateCustomTabIntentProcessor, components.intentProcessors.privateIntentProcessor, ) } else { Events.openedLink.record(Events.OpenedLinkExtra("NORMAL")) listOf( + components.intentProcessors.addonInstallIntentProcessor, components.intentProcessors.customTabIntentProcessor, components.intentProcessors.intentProcessor, ) @@ -116,7 +118,7 @@ class IntentReceiverActivity : Activity() { components.intentProcessors.webNotificationsIntentProcessor + components.intentProcessors.passwordManagerIntentProcessor + modeDependentProcessors + - NewTabShortcutIntentProcessor() + NewTabShortcutIntentProcessor() + components.intentProcessors.addonInstallIntentProcessor } private fun addReferrerInformation(intent: Intent) { diff --git a/app/src/main/java/org/mozilla/fenix/addons/AddonInstallIntentProcessor.kt b/app/src/main/java/org/mozilla/fenix/addons/AddonInstallIntentProcessor.kt new file mode 100644 index 000000000..6a68c08aa --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/addons/AddonInstallIntentProcessor.kt @@ -0,0 +1,58 @@ +import android.content.Context +import android.content.Intent +import android.net.Uri +import io.github.forkmaintainers.iceraven.components.getSafeString +import mozilla.components.concept.engine.Engine +import mozilla.components.feature.intent.processing.IntentProcessor +import mozilla.components.support.ktx.android.net.getFileName +import org.json.JSONException +import org.json.JSONObject +import java.io.File +import java.io.FileOutputStream +import java.net.URLEncoder +import java.util.Base64 +import java.util.zip.ZipFile + + +class AddonInstallIntentProcessor(private val context: Context, private val engine: Engine) : IntentProcessor { + override fun process(intent: Intent): Boolean { + val iuri = fromUri(intent.data as Uri) + if(iuri == null) { + return false + } + val ext = iuri.let { parseExtension(it) } + installExtension(ext.get(0), ext.get(1)) + return true + } + fun installExtension(id: String, b64: String) { + engine.installWebExtension(id, b64) + + } + fun parseExtension(inp: File): List { + val file = ZipFile(inp) + val mis = file.getInputStream(file.getEntry("manifest.json")) + val t = org.json.JSONObject(String(mis.readBytes())) + val al = ArrayList() + val bss = try { + t.getJSONObject("browser_specific_settings") + } catch(e:JSONException) { + t.getJSONObject("applications") + } + al.add(bss.getJSONObject("gecko").getSafeString("id") ) + al.add(Uri.fromFile(inp.absoluteFile).toString()) + file.close() + mis.close() + return al + } + fun fromUri(uri: Uri): File? { + val name = uri.getFileName(context.contentResolver) + val file: File = File(context.externalCacheDir, name) + file.createNewFile() + val ostream = FileOutputStream(file.absolutePath) + val istream = context.contentResolver.openInputStream(uri)!! + istream.copyTo(ostream) + ostream.close() + istream.close() + return file + } +} diff --git a/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementFragment.kt b/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementFragment.kt index 1ffee940a..fc11f644c 100644 --- a/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/addons/AddonsManagementFragment.kt @@ -4,7 +4,9 @@ package org.mozilla.fenix.addons +import android.app.Activity import android.content.Context +import android.content.Intent import android.graphics.Typeface import android.graphics.fonts.FontStyle.FONT_WEIGHT_MEDIUM import android.os.Build @@ -13,8 +15,11 @@ import android.view.Menu import android.view.MenuInflater import android.view.MenuItem import android.view.View -import android.view.inputmethod.EditorInfo import android.view.accessibility.AccessibilityEvent +import android.view.inputmethod.EditorInfo +import androidx.activity.result.ActivityResult +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.VisibleForTesting import androidx.appcompat.app.AlertDialog import androidx.appcompat.widget.SearchView @@ -32,10 +37,10 @@ import kotlinx.coroutines.launch import mozilla.components.feature.addons.Addon import mozilla.components.feature.addons.AddonManager import mozilla.components.feature.addons.AddonManagerException -import mozilla.components.support.base.log.logger.Logger -import mozilla.components.support.ktx.android.view.hideKeyboard import mozilla.components.feature.addons.ui.AddonsManagerAdapter import mozilla.components.feature.addons.ui.AddonsManagerAdapterDelegate +import mozilla.components.support.base.log.logger.Logger +import mozilla.components.support.ktx.android.view.hideKeyboard import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.BuildConfig import org.mozilla.fenix.HomeActivity @@ -64,7 +69,7 @@ class AddonsManagementFragment : Fragment(R.layout.fragment_add_ons_management) private var addons: List = emptyList() private var adapter: AddonsManagerAdapter? = null - + private var addonImportFilePicker: ActivityResultLauncher? = null override fun onViewCreated(view: View, savedInstanceState: Bundle?) { logger.info("View created for AddonsManagementFragment") super.onViewCreated(view, savedInstanceState) @@ -76,6 +81,19 @@ class AddonsManagementFragment : Fragment(R.layout.fragment_add_ons_management) adapter?.updateAddon(it) } } + addonImportFilePicker = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + result: ActivityResult -> + if(result.resultCode == Activity.RESULT_OK) { + result.data?.data?.let{uri -> + requireComponents.intentProcessors.addonInstallIntentProcessor.fromUri(uri)?.let{tmp -> + val ext = requireComponents.intentProcessors.addonInstallIntentProcessor.parseExtension(tmp) + requireComponents.intentProcessors.addonInstallIntentProcessor.installExtension( + ext[0], ext[1] + ) + } + } + } + } } @@ -113,6 +131,10 @@ class AddonsManagementFragment : Fragment(R.layout.fragment_add_ons_management) showAlertDialog() true } + R.id.addons_sideload -> { + installFromFile() + true + } R.id.search -> { true } @@ -123,7 +145,13 @@ class AddonsManagementFragment : Fragment(R.layout.fragment_add_ons_management) viewLifecycleOwner, Lifecycle.State.RESUMED, ) } + private fun installFromFile() { + val intent = Intent() + .setType("*/*") + .setAction(Intent.ACTION_GET_CONTENT) + addonImportFilePicker!!.launch(intent) + } private fun showAlertDialog() { val builder: AlertDialog.Builder = AlertDialog.Builder(requireContext()) builder diff --git a/app/src/main/java/org/mozilla/fenix/components/IntentProcessorType.kt b/app/src/main/java/org/mozilla/fenix/components/IntentProcessorType.kt index 702132bde..5dbff8cab 100644 --- a/app/src/main/java/org/mozilla/fenix/components/IntentProcessorType.kt +++ b/app/src/main/java/org/mozilla/fenix/components/IntentProcessorType.kt @@ -35,9 +35,11 @@ enum class IntentProcessorType { * Classifies the [IntentProcessorType] based on the [IntentProcessor] that handled the [Intent]. */ fun IntentProcessors.getType(processor: IntentProcessor?) = when { + externalAppIntentProcessors.contains(processor) || customTabIntentProcessor == processor || privateCustomTabIntentProcessor == processor -> IntentProcessorType.EXTERNAL_APP + addonInstallIntentProcessor == processor -> IntentProcessorType.OTHER intentProcessor == processor || privateIntentProcessor == processor || fennecPageShortcutIntentProcessor == processor || diff --git a/app/src/main/java/org/mozilla/fenix/components/IntentProcessors.kt b/app/src/main/java/org/mozilla/fenix/components/IntentProcessors.kt index 3e2c66361..48b56ca57 100644 --- a/app/src/main/java/org/mozilla/fenix/components/IntentProcessors.kt +++ b/app/src/main/java/org/mozilla/fenix/components/IntentProcessors.kt @@ -9,6 +9,7 @@ package org.mozilla.fenix.components +import AddonInstallIntentProcessor import android.content.Context import mozilla.components.browser.state.store.BrowserStore import mozilla.components.concept.engine.Engine @@ -85,4 +86,7 @@ class IntentProcessors( val passwordManagerIntentProcessor by lazyMonitored { PasswordManagerIntentProcessor() } + val addonInstallIntentProcessor by lazyMonitored { + AddonInstallIntentProcessor(context, engine) + } } diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml new file mode 100644 index 000000000..e3b666ea3 --- /dev/null +++ b/app/src/main/res/values/ids.xml @@ -0,0 +1,4 @@ + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 25c4ca9e6..0650587cd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2361,6 +2361,7 @@ Domain Name System (DNS) over HTTPS sends your request for a domain name through an encrypted connection, creating a secure DNS and making it harder for others to see which website you’re about to access. Custom Delete addons metadata cache file + Install addon from file Are you sure to delete addons metadata cache file? OK Cancel