SubwayTooter-Android-App/app/src/main/java/jp/juggler/subwaytooter/util/AppOpener.kt

357 lines
13 KiB
Kotlin
Raw Normal View History

2020-09-29 19:44:56 +02:00
package jp.juggler.subwaytooter.util
import android.content.ComponentName
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.app.Activity
import android.content.SharedPreferences
import androidx.browser.customtabs.CustomTabColorSchemeParams
2020-09-29 19:44:56 +02:00
import androidx.browser.customtabs.CustomTabsIntent
import jp.juggler.subwaytooter.ActMain
2020-09-29 19:44:56 +02:00
import jp.juggler.subwaytooter.Pref
import jp.juggler.subwaytooter.R
import jp.juggler.subwaytooter.action.Action_HashTag
import jp.juggler.subwaytooter.action.Action_Toot
import jp.juggler.subwaytooter.action.Action_User
import jp.juggler.subwaytooter.api.entity.Host
import jp.juggler.subwaytooter.api.entity.TootAccount
import jp.juggler.subwaytooter.api.entity.TootAccountRef
2020-09-29 19:44:56 +02:00
import jp.juggler.subwaytooter.api.entity.TootAttachment
import jp.juggler.subwaytooter.api.entity.TootStatus.Companion.findStatusIdFromUrl
import jp.juggler.subwaytooter.api.entity.TootTag.Companion.findHashtagFromUrl
2020-09-29 19:44:56 +02:00
import jp.juggler.subwaytooter.dialog.DlgAppPicker
import jp.juggler.subwaytooter.pref
import jp.juggler.subwaytooter.span.LinkInfo
import jp.juggler.subwaytooter.table.SavedAccount
2020-09-29 19:44:56 +02:00
import jp.juggler.util.*
import java.util.ArrayList
// Subway Tooterの「アプリ設定/挙動/リンクを開く際にCustom Tabsを使わない」をONにして
// 投稿のコンテキストメニューの「トゥートへのアクション/Webページを開く」「ユーザへのアクション/Webページを開く」を使うと
// 投げたインテントをST自身が受け取って「次のアカウントから開く」ダイアログが出て
// 「Webページを開く」をまた押すと無限ループしてダイアログの影が徐々に濃くなりそのうち壊れる
// これを避けるには、投稿やトゥートを開く際に bpDontUseCustomTabs がオンならST以外のアプリを列挙したアプリ選択ダイアログを出すしかない
2020-09-29 19:44:56 +02:00
private val log = LogCategory("AppOpener")
// returns true if activity is opened.
// returns false if fallback required
2020-12-19 12:10:12 +01:00
private fun Activity.openBrowserExcludeMe(
pref:SharedPreferences,
2020-12-07 14:06:25 +01:00
intent: Intent,
startAnimationBundle: Bundle? = null
): Boolean {
try {
if( intent.component == null){
val cn = Pref.spWebBrowser(pref).cn()
if( cn?.exists(this) == true){
intent.component = cn
}
}
// このアプリのパッケージ名
val myName = packageName
val filter: (ResolveInfo) -> Boolean = {
when {
it.activityInfo.packageName == myName -> false
!it.activityInfo.exported -> false
// Huaweiの謎Activityのせいでうまく働かないことがある
-1 != it.activityInfo.packageName.indexOf("com.huawei.android.internal") -> false
// 標準アプリが設定されていない場合、アプリを選択するためのActivityが出てくる場合がある
it.activityInfo.packageName == "android" -> false
it.activityInfo.javaClass.name.startsWith("com.android.internal") -> false
it.activityInfo.javaClass.name.startsWith("com.android.systemui") -> false
// たぶんChromeとかfirefoxとか
else -> true
}
}
// resolveActivity がこのアプリ以外のActivityを返すなら、それがベストなんだろう
// ただしAndroid M以降はMATCH_DEFAULT_ONLYだと「常時」が設定されてないとnullを返す
val ri = packageManager!!.resolveActivity(
2020-12-07 14:06:25 +01:00
intent,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PackageManager.MATCH_ALL
} else {
PackageManager.MATCH_DEFAULT_ONLY
}
)?.takeIf(filter)
return when {
ri != null -> {
intent.setClassName(ri.activityInfo.packageName, ri.activityInfo.name)
log.d("startActivityExcludeMyApp(1) $intent")
startActivity(intent, startAnimationBundle)
true
}
else -> DlgAppPicker(
2020-12-07 14:06:25 +01:00
this,
intent,
autoSelect = true,
filter = filter,
addCopyAction = false
2020-12-07 14:06:25 +01:00
) {
try {
intent.component = it.cn()
log.d("startActivityExcludeMyApp(2) $intent")
startActivity(intent, startAnimationBundle)
} catch (ex: Throwable) {
log.trace(ex)
showToast(ex, "can't open. ${intent.data}")
}
}.show()
}
} catch (ex: Throwable) {
log.trace(ex)
showToast(ex, "can't open. ${intent.data}")
return true // fallback not required in this case
}
2020-09-29 19:44:56 +02:00
}
fun Activity.openBrowser(uri: Uri? , pref:SharedPreferences = pref()) {
uri ?: return
2020-12-19 12:10:12 +01:00
val rv = openBrowserExcludeMe(
pref,
Intent(Intent.ACTION_VIEW, uri)
.apply { addCategory(Intent.CATEGORY_BROWSABLE) }
)
if (!rv) showToast(true, "there is no app that can open $uri")
2020-09-29 19:44:56 +02:00
}
fun Activity.openBrowser(url: String?, pref:SharedPreferences = pref()) = openBrowser(url.mayUri(),pref)
2020-09-29 19:44:56 +02:00
// Chrome Custom Tab を開く
fun Activity.openCustomTab(url: String?, pref:SharedPreferences = pref()) {
url ?: return
if (url.isEmpty()) {
showToast(false, "URL is empty string.")
return
}
if (Pref.bpDontUseCustomTabs(pref)) {
openBrowser(url,pref)
return
}
try {
fun startCustomTabIntent(cn: ComponentName?) =
CustomTabsIntent.Builder()
.setDefaultColorSchemeParams(
CustomTabColorSchemeParams.Builder()
.setToolbarColor(attrColor(R.attr.colorPrimary))
.build()
)
.setShowTitle(true)
.build()
.let {
2020-12-07 14:06:25 +01:00
log.w("startCustomTabIntent ComponentName=$cn")
2020-12-19 12:10:12 +01:00
openBrowserExcludeMe(
pref,
2020-12-07 14:06:25 +01:00
it.intent.also { intent ->
if (cn != null) intent.component = cn
intent.data = url.toUri()
},
it.startAnimationBundle
)
}
if (url.startsWith("http") && Pref.bpPriorChrome(pref)) {
try {
// 初回はChrome指定で試す
val cn = ComponentName(
2020-12-07 14:06:25 +01:00
"com.android.chrome",
"com.google.android.apps.chrome.Main"
)
if (startCustomTabIntent(cn)) return
} catch (ex2: Throwable) {
log.e(ex2, "openCustomTab: missing chrome. retry to other application.")
}
}
// Chromeがないようなのでcomponent指定なしでリトライ
if (startCustomTabIntent(null)) return
showToast(true, "the browser app is not installed.")
} catch (ex: Throwable) {
log.trace(ex)
val scheme = url.mayUri()?.scheme ?: url
showToast(true, "can't open browser app for %s", scheme)
}
2020-09-29 19:44:56 +02:00
}
fun Activity.openCustomTab(ta: TootAttachment) =
openCustomTab(ta.getLargeUrl(pref()))
fun openCustomTab(
2020-12-07 14:06:25 +01:00
activity: ActMain,
pos: Int,
url: String,
accessInfo: SavedAccount? = null,
tagList: ArrayList<String>? = null,
allowIntercept: Boolean = true,
whoRef: TootAccountRef? = null,
linkInfo: LinkInfo? = null
) {
try {
log.d("openCustomTab: $url")
val whoAcct = if (whoRef != null) {
accessInfo?.getFullAcct(whoRef.get())
} else {
null
}
if (allowIntercept && accessInfo != null) {
// ハッシュタグはいきなり開くのではなくメニューがある
val tagInfo = url.findHashtagFromUrl()
if (tagInfo != null) {
Action_HashTag.dialog(
2020-12-07 14:06:25 +01:00
activity,
pos,
url,
Host.parse(tagInfo.second),
tagInfo.first,
tagList,
whoAcct
)
return
}
val statusInfo = url.findStatusIdFromUrl()
if (statusInfo != null) {
if (accessInfo.isNA ||
statusInfo.statusId == null ||
!accessInfo.matchHost(statusInfo.host)
) {
Action_Toot.conversationOtherInstance(
2020-12-07 14:06:25 +01:00
activity,
pos,
statusInfo.url,
statusInfo.statusId,
statusInfo.host,
statusInfo.statusId
)
} else {
Action_Toot.conversationLocal(
2020-12-07 14:06:25 +01:00
activity,
pos,
accessInfo,
statusInfo.statusId
)
}
return
}
// opener.linkInfo をチェックしてメンションを判別する
val mention = linkInfo?.mention
if (mention != null) {
val fullAcct = getFullAcctOrNull(mention.acct, mention.url, accessInfo)
if (fullAcct != null) {
if (fullAcct.host != null) {
when (fullAcct.host.ascii) {
2020-12-07 14:06:25 +01:00
"github.com",
"twitter.com" ->
activity.openCustomTab(mention.url)
"gmail.com" ->
activity.openBrowser("mailto:${fullAcct.pretty}")
else ->
Action_User.profile(
2020-12-07 14:06:25 +01:00
activity,
pos,
accessInfo, // FIXME nullが必要なケースがあったっけなかったっけ…
mention.url,
fullAcct.host,
fullAcct.username,
original_url = url
)
}
return
}
}
}
// ユーザページをアプリ内で開く
var m = TootAccount.reAccountUrl.matcher(url)
if (m.find()) {
val host = m.groupEx(1)!!
val user = m.groupEx(2)!!.decodePercent()
val instance = m.groupEx(3)?.decodePercent()?.notEmpty()
// https://misskey.xyz/@tateisu@github.com
// https://misskey.xyz/@tateisu@twitter.com
if (instance != null) {
val instanceHost = Host.parse(instance)
when (instanceHost.ascii) {
2020-12-07 14:06:25 +01:00
"github.com", "twitter.com" -> {
activity.openCustomTab("https://$instance/$user")
}
2020-12-07 14:06:25 +01:00
"gmail.com" -> {
activity.openBrowser("mailto:$user@$instance")
}
else -> {
Action_User.profile(
2020-12-07 14:06:25 +01:00
activity,
pos,
null, // Misskeyだと疑似アカが必要なんだっけ…
"https://$instance/@$user",
instanceHost,
user,
original_url = url
)
}
}
} else {
Action_User.profile(
2020-12-07 14:06:25 +01:00
activity,
pos,
accessInfo,
url,
Host.parse(host),
user
)
}
return
}
m = TootAccount.reAccountUrl2.matcher(url)
if (m.find()) {
val host = m.groupEx(1)!!
val user = m.groupEx(2)!!.decodePercent()
Action_User.profile(
2020-12-07 14:06:25 +01:00
activity,
pos,
accessInfo,
url,
Host.parse(host),
user
)
return
}
}
activity.openCustomTab(url)
} catch (ex: Throwable) {
2020-12-07 14:06:25 +01:00
log.trace(ex)
log.e(ex, "openCustomTab failed. $url")
}
2020-09-29 19:44:56 +02:00
}