add support for sideloading addons via the addon manager or file picker

pull/745/head
☙◦ The Tablet ❀ GamerGirlandCo ◦❧ 6 months ago committed by akliuxingyuan
parent 65a1253780
commit c8d7388780

@ -9,6 +9,12 @@
android:title="@string/addons_delete_cache"
android:contentDescription="@string/addons_delete_cache"
app:showAsAction="ifRoom|collapseActionView" />
<item
android:id="@+id/addons_sideload"
android:icon="@drawable/ic_addons_extensions"
android:title="@string/addons_sideload"
android:contentDescription="@string/addons_sideload"
app:showAsAction="ifRoom|collapseActionView" />
<item
android:id="@+id/search"
android:icon="@drawable/ic_search"
@ -17,4 +23,5 @@
app:actionViewClass="androidx.appcompat.widget.SearchView"
android:contentDescription="@string/addons_search_hint"
app:showAsAction="ifRoom|collapseActionView" />
</menu>

@ -51,7 +51,6 @@
android:theme="@style/NormalTheme"
android:usesCleartextTraffic="true"
tools:ignore="UnusedAttribute">
<profileable
android:shell="true"
tools:targetApi="29" />
@ -216,6 +215,18 @@
<data android:mimeType="application/xhtml+xml" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
<action android:name="android.intent.action.GET_CONTENT"/>
<action android:name="android.intent.action.PICK" />
<data android:scheme="file" />
<data android:scheme="content"/>
<data android:host="*" />
<data android:mimeType="*/*"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />

@ -99,12 +99,14 @@ class IntentReceiverActivity : Activity() {
private fun getIntentProcessors(private: Boolean): List<IntentProcessor> {
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) {

@ -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<String> {
val file = ZipFile(inp)
val mis = file.getInputStream(file.getEntry("manifest.json"))
val t = org.json.JSONObject(String(mis.readBytes()))
val al = ArrayList<String>()
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
}
}

@ -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<Addon> = emptyList()
private var adapter: AddonsManagerAdapter? = null
private var addonImportFilePicker: ActivityResultLauncher<Intent>? = 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

@ -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 ||

@ -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)
}
}

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="addons_sideload" type="id" />
</resources>

@ -2361,6 +2361,7 @@
<string name="preferences_dns_over_https_summary">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 youre about to access.</string>
<string name="preferences_dns_over_https_custom_server">Custom</string>
<string name="addons_delete_cache">Delete addons metadata cache file</string>
<string name="addons_sideload">Install addon from file</string>
<string name="confirm_addons_delete_cache">Are you sure to delete addons metadata cache file?</string>
<string name="confirm_addons_delete_cache_yes">OK</string>
<string name="confirm_addons_delete_cache_no">Cancel</string>

Loading…
Cancel
Save