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

feat: handle android.intent.action.WEB_SEARCH and open wikipedia links #3864

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
27 changes: 25 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />

<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

Expand All @@ -58,7 +57,21 @@
<data android:pathPattern="/.*\\..*\\.zim" />
<data android:pathPattern="/.*\\..*\\..*\\.zim" />
<data android:pathPattern="/.*\\..*\\..*\\..*\\.zim" />
<data android:host="*" />
<data android:host="*"/>
</intent-filter>

<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data android:scheme="https"/>
<data android:scheme="http"/>
<data android:host="*.wikipedia.fr"/>
<data android:host="*.wikipedia.org"/>
<data android:host="*.wikipedia.com"/>
<data android:host="*.wikipedia.de"/>
<data android:pathPattern=".*"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
Expand Down Expand Up @@ -124,6 +137,16 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity-alias
android:name=".main.KiwixMainActivityWebSearch"
android:exported="true"
android:enabled="false"
android:targetActivity=".main.KiwixMainActivity" >
<intent-filter android:enabled="true">
<action android:name="android.intent.action.WEB_SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity-alias>

<receiver
android:name=".main.KiwixSearchWidget"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ class KiwixReaderFragment : CoreReaderFragment() {
}, 300)
}

else -> activity.toast(R.string.cannot_open_file)
else -> {}
}
}
return ShouldCall
Expand Down
20 changes: 20 additions & 0 deletions core/src/main/java/org/kiwix/kiwixmobile/core/dao/NewBookDao.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
package org.kiwix.kiwixmobile.core.dao

import android.net.Uri
import io.objectbox.Box
import io.objectbox.kotlin.inValues
import io.objectbox.kotlin.query
Expand Down Expand Up @@ -94,4 +95,23 @@ class NewBookDao @Inject constructor(private val box: Box<BookOnDiskEntity>) {
QueryBuilder.StringOrder.CASE_INSENSITIVE
)
}.findFirst()

fun bookMatchingUrl(url: Uri): BookOnDiskEntity? {
url.host?.let {
it.split(".").let { splittedHost ->
val list = box.query {
contains(
BookOnDiskEntity_.name, if (splittedHost.size > 2) splittedHost[1] else splittedHost[0],
QueryBuilder.StringOrder.CASE_INSENSITIVE
)
}.find()
list.sortWith(Comparator { book1: BookOnDiskEntity, book2: BookOnDiskEntity ->
if (book1.language == splittedHost[0] || book1.language == splittedHost[splittedHost.size - 1]
) 1 else 0
})
return@bookMatchingUrl list.first()
}
}
return null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import android.content.Intent
import android.content.ServiceConnection
import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.content.res.AssetFileDescriptor
import android.content.res.Configuration
import android.graphics.Canvas
Expand Down Expand Up @@ -111,6 +112,8 @@ import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.observeNavigatio
import org.kiwix.kiwixmobile.core.extensions.ActivityExtensions.requestNotificationPermission
import org.kiwix.kiwixmobile.core.extensions.ViewGroupExtensions.findFirstTextView
import org.kiwix.kiwixmobile.core.extensions.closeFullScreenMode
import org.kiwix.kiwixmobile.core.CoreApp.Companion.coreComponent
import org.kiwix.kiwixmobile.core.extensions.coreMainActivity
import org.kiwix.kiwixmobile.core.extensions.getToolbarNavigationIcon
import org.kiwix.kiwixmobile.core.extensions.isFileExist
import org.kiwix.kiwixmobile.core.extensions.setToolTipWithContentDescription
Expand Down Expand Up @@ -487,7 +490,10 @@ abstract class CoreReaderFragment :
setupDocumentParser()
loadPrefs()
updateTitle()
handleIntentExtras(requireActivity().intent)
val intent = requireActivity().intent
if (intent != null) {
handleIntentExtras(intent)
}
tabRecyclerView?.let {
it.adapter = tabsAdapter
tabCallback?.let { callBack ->
Expand All @@ -500,7 +506,7 @@ abstract class CoreReaderFragment :
if (savedInstanceState == null) {
// call the `onNewIntent` explicitly so that the overridden method in child class will
// also call, to properly handle the zim file opening when opening the zim file from storage.
onNewIntent(requireActivity().intent, requireActivity() as AppCompatActivity)
onNewIntent(intent, requireActivity() as AppCompatActivity)
}

serviceConnection = object : ServiceConnection {
Expand Down Expand Up @@ -1538,8 +1544,29 @@ abstract class CoreReaderFragment :
getCurrentWebView()?.requestLayout()
}

private fun canOpenUrl(url: Uri): Boolean {
val intent = Intent(Intent.ACTION_VIEW, url)
val activities: List<ResolveInfo> =
requireActivity().packageManager.queryIntentActivities(
intent,
android.content.pm.PackageManager.MATCH_DEFAULT_ONLY
)
for (info in activities) {
if (info.activityInfo.packageName == requireActivity().packageName) {
return true
break
}
}
return false
}

override fun openExternalUrl(intent: Intent) {
externalLinkOpener?.openExternalUrl(intent)
val shouldOpenInApp = sharedPreferenceUtil?.prefSupportedExternalLinksOpenInApp ?: false
if (intent.data != null && shouldOpenInApp && canOpenUrl(intent.data!!)) {
handleIntentActions(intent)
} else {
externalLinkOpener?.openExternalUrl(intent)
}
}

protected fun openZimFile(
Expand Down Expand Up @@ -1857,16 +1884,46 @@ abstract class CoreReaderFragment :
requireActivity().intent.action = null
}

"android.intent.action.WEB_SEARCH" -> {
openSearch(
searchString = intent.getStringExtra("query") ?: "",
isOpenedFromTabView = false,
isVoice = false
)
intent.action = null
requireActivity().intent.action = null
}

Intent.ACTION_VIEW -> if (
(intent.type == null || intent.type != "application/octet-stream") &&
// Added condition to handle ZIM files. When opening from storage, the intent may
// return null for the type, triggering the search unintentionally. This condition
// prevents such occurrences.
intent.scheme !in listOf("file", "content")
) {
val searchString = if (intent.data == null) "" else intent.data?.lastPathSegment
intent.action = null
requireActivity().intent.action = null
var searchString: String? = null
if (intent.data != null) {
val book = coreComponent.newBookDao().bookMatchingUrl(intent.data!!)
if (book != null) {
(requireActivity() as CoreMainActivity).openPage(
pageUrl = "https://kiwix.app/A/" + intent.data!!.lastPathSegment,
zimFilePath = book.file.path
)
return
}
val params = intent.data!!.queryParameterNames
searchString = if (params.contains("search")) {
intent.data!!.getQueryParameter("search")
} else if (params.contains("q")) {
intent.data!!.getQueryParameter("q")
} else {
intent.data!!.lastPathSegment
}
}
openSearch(
searchString = searchString,
searchString = searchString ?: "",
isOpenedFromTabView = false,
isVoice = false
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package org.kiwix.kiwixmobile.core.settings
import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity.RESULT_OK
import android.content.ComponentName
import android.content.Intent
import android.content.SharedPreferences
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
Expand Down Expand Up @@ -93,6 +94,7 @@ abstract class CorePrefsFragment :
setStorage()
setUpSettings()
setupZoom()
setupWebSearchIntent()
sharedPreferenceUtil?.let {
LanguageUtils(requireActivity()).changeFont(
requireActivity(),
Expand All @@ -101,6 +103,21 @@ abstract class CorePrefsFragment :
}
}

private fun setupWebSearchIntent() {
val pref = findPreference<Preference>(SharedPreferenceUtil.PREF_ENABLE_WEB_SEARCH_INTENT)
pref?.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { _, newValue ->
val boolValue = newValue as Boolean
val pm: PackageManager = requireActivity().packageManager
pm.setComponentEnabledSetting(
ComponentName(requireActivity(), "org.kiwix.kiwixmobile.main.KiwixMainActivityWebSearch"),
if (boolValue) PackageManager.COMPONENT_ENABLED_STATE_ENABLED else PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP
)
true
}
}

private fun setupZoom() {
val textZoom = findPreference<Preference>(INTERNAL_TEXT_ZOOM)
textZoom?.onPreferenceChangeListener =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ class SharedPreferenceUtil @Inject constructor(val context: Context) {
val prefExternalLinkPopup: Boolean
get() = sharedPreferences.getBoolean(PREF_EXTERNAL_LINK_POPUP, true)

val prefSupportedExternalLinksOpenInApp: Boolean
get() = sharedPreferences.getBoolean(PREF_SUPPORTED_EXTERNAL_LINKS_OPEN_IN_APP, false)

val isPlayStoreBuild: Boolean
get() = sharedPreferences.getBoolean(IS_PLAY_STORE_BUILD, false)

Expand Down Expand Up @@ -280,6 +283,9 @@ class SharedPreferenceUtil @Inject constructor(val context: Context) {
private const val PREF_NEW_TAB_BACKGROUND = "pref_newtab_background"
private const val PREF_STORAGE_TITLE = "pref_selected_title"
const val PREF_EXTERNAL_LINK_POPUP = "pref_external_link_popup"
const val PREF_SUPPORTED_EXTERNAL_LINKS_OPEN_IN_APP =
"pref_supported_external_links_open_in_app"
const val PREF_ENABLE_WEB_SEARCH_INTENT = "pref_enable_web_search_intent"
const val PREF_SHOW_STORAGE_OPTION = "show_storgae_option"
private const val PREF_IS_FIRST_RUN = "isFirstRun"
private const val PREF_SHOW_BOOKMARKS_ALL_BOOKS = "show_bookmarks_current_book"
Expand Down
4 changes: 4 additions & 0 deletions core/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@
<string name="time_yesterday">Yesterday</string>
<string name="pref_external_link_popup_title">Warn when entering external links</string>
<string name="pref_external_link_popup_summary">Display popup to warn about additional costs or not working in offline links.</string>
<string name="pref_supported_external_links_open_in_app_title">Open supported links in app</string>
<string name="pref_supported_external_links_open_in_app_summary">Automatically open supported external links within the app</string>
<string name="pref_enable_web_search_intent_title">Use as a web search application</string>
<string name="pref_enable_web_search_intent_summary">When enabled Kiwix can be used when other applications request a web search</string>
<string name="external_link_popup_dialog_title">Entering External Link!</string>
<string name="external_link_popup_dialog_message">You are entering an external link. This could lead to additional costs for data transfer or will just not work when you are offline. Do you want to continue?</string>
<string name="do_not_ask_anymore">Do not ask anymore</string>
Expand Down
15 changes: 15 additions & 0 deletions core/src/main/res/xml/preferences.xml
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,27 @@
android:title="@string/pref_external_link_popup_title"
app:iconSpaceReserved="false" />

<SwitchPreferenceCompat
android:defaultValue="false"
android:key="pref_supported_external_links_open_in_app"
android:summary="@string/pref_supported_external_links_open_in_app_summary"
android:title="@string/pref_supported_external_links_open_in_app_title"
app:iconSpaceReserved="false" />

<SwitchPreferenceCompat
android:defaultValue="true"
android:key="pref_wifi_only"
android:summary="@string/pref_wifi_only"
android:title="@string/pref_wifi_only"
app:iconSpaceReserved="false" />


<SwitchPreferenceCompat
android:defaultValue="false"
android:key="pref_enable_web_search_intent"
android:summary="@string/pref_enable_web_search_intent_summary"
android:title="@string/pref_enable_web_search_intent_title"
app:iconSpaceReserved="false" />
</PreferenceCategory>

<PreferenceCategory
Expand Down