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
|
2020-12-19 07:46:42 +01:00
|
|
|
|
import android.content.SharedPreferences
|
2020-12-07 09:33:01 +01:00
|
|
|
|
import androidx.browser.customtabs.CustomTabColorSchemeParams
|
2020-09-29 19:44:56 +02:00
|
|
|
|
import androidx.browser.customtabs.CustomTabsIntent
|
2020-12-07 09:33:01 +01:00
|
|
|
|
import jp.juggler.subwaytooter.ActMain
|
2020-09-29 19:44:56 +02:00
|
|
|
|
import jp.juggler.subwaytooter.Pref
|
|
|
|
|
import jp.juggler.subwaytooter.R
|
2020-12-07 09:33:01 +01:00
|
|
|
|
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
|
2020-12-07 09:33:01 +01:00
|
|
|
|
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
|
2020-12-07 09:33:01 +01:00
|
|
|
|
import jp.juggler.subwaytooter.span.LinkInfo
|
|
|
|
|
import jp.juggler.subwaytooter.table.SavedAccount
|
2020-09-29 19:44:56 +02:00
|
|
|
|
import jp.juggler.util.*
|
2020-12-07 09:33:01 +01:00
|
|
|
|
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(
|
2020-12-19 07:46:42 +01:00
|
|
|
|
pref:SharedPreferences,
|
2020-12-07 14:06:25 +01:00
|
|
|
|
intent: Intent,
|
|
|
|
|
startAnimationBundle: Bundle? = null
|
2020-12-07 09:33:01 +01:00
|
|
|
|
): Boolean {
|
|
|
|
|
try {
|
2020-12-19 07:46:42 +01:00
|
|
|
|
if( intent.component == null){
|
|
|
|
|
val cn = Pref.spWebBrowser(pref).cn()
|
|
|
|
|
if( cn?.exists(this) == true){
|
|
|
|
|
intent.component = cn
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-07 09:33:01 +01:00
|
|
|
|
// このアプリのパッケージ名
|
|
|
|
|
val myName = packageName
|
|
|
|
|
|
|
|
|
|
val filter: (ResolveInfo) -> Boolean = {
|
2020-12-19 07:46:42 +01:00
|
|
|
|
when {
|
2020-12-08 14:48:23 +01:00
|
|
|
|
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
|
2020-12-19 07:46:42 +01:00
|
|
|
|
it.activityInfo.javaClass.name.startsWith("com.android.internal") -> false
|
2020-12-08 14:48:23 +01:00
|
|
|
|
it.activityInfo.javaClass.name.startsWith("com.android.systemui") -> false
|
|
|
|
|
|
|
|
|
|
// たぶんChromeとかfirefoxとか
|
|
|
|
|
else -> true
|
|
|
|
|
}
|
2020-12-07 09:33:01 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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)
|
2020-12-07 09:33:01 +01:00
|
|
|
|
|
|
|
|
|
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,
|
2020-12-19 07:46:42 +01:00
|
|
|
|
filter = filter,
|
|
|
|
|
addCopyAction = false
|
2020-12-07 14:06:25 +01:00
|
|
|
|
) {
|
2020-12-07 09:33:01 +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
|
|
|
|
}
|
|
|
|
|
|
2020-12-19 07:46:42 +01:00
|
|
|
|
fun Activity.openBrowser(uri: Uri? , pref:SharedPreferences = pref()) {
|
2020-12-07 09:33:01 +01:00
|
|
|
|
uri ?: return
|
2020-12-19 12:10:12 +01:00
|
|
|
|
val rv = openBrowserExcludeMe(
|
2020-12-19 07:46:42 +01:00
|
|
|
|
pref,
|
|
|
|
|
Intent(Intent.ACTION_VIEW, uri)
|
|
|
|
|
.apply { addCategory(Intent.CATEGORY_BROWSABLE) }
|
|
|
|
|
)
|
2020-12-07 09:33:01 +01:00
|
|
|
|
if (!rv) showToast(true, "there is no app that can open $uri")
|
2020-09-29 19:44:56 +02:00
|
|
|
|
}
|
|
|
|
|
|
2020-12-19 07:46:42 +01:00
|
|
|
|
fun Activity.openBrowser(url: String?, pref:SharedPreferences = pref()) = openBrowser(url.mayUri(),pref)
|
2020-09-29 19:44:56 +02:00
|
|
|
|
|
2020-12-07 09:33:01 +01:00
|
|
|
|
// Chrome Custom Tab を開く
|
2020-12-19 07:46:42 +01:00
|
|
|
|
fun Activity.openCustomTab(url: String?, pref:SharedPreferences = pref()) {
|
2020-12-07 09:33:01 +01:00
|
|
|
|
url ?: return
|
|
|
|
|
|
|
|
|
|
if (url.isEmpty()) {
|
|
|
|
|
showToast(false, "URL is empty string.")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (Pref.bpDontUseCustomTabs(pref)) {
|
2020-12-19 07:46:42 +01:00
|
|
|
|
openBrowser(url,pref)
|
2020-12-07 09:33:01 +01:00
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
fun startCustomTabIntent(cn: ComponentName?) =
|
|
|
|
|
CustomTabsIntent.Builder()
|
|
|
|
|
.setDefaultColorSchemeParams(
|
|
|
|
|
CustomTabColorSchemeParams.Builder()
|
2021-01-04 02:11:45 +01:00
|
|
|
|
.setToolbarColor(attrColor(R.attr.colorPrimary))
|
2020-12-07 09:33:01 +01:00
|
|
|
|
.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(
|
2020-12-19 07:46:42 +01:00
|
|
|
|
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
|
|
|
|
|
)
|
2020-12-07 09:33:01 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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"
|
|
|
|
|
)
|
2020-12-07 09:33:01 +01:00
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
2020-12-07 09:33:01 +01: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
|
2020-12-07 09:33:01 +01:00
|
|
|
|
) {
|
|
|
|
|
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
|
|
|
|
|
)
|
2020-12-07 09:33:01 +01:00
|
|
|
|
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
|
|
|
|
|
)
|
2020-12-07 09:33:01 +01:00
|
|
|
|
} else {
|
|
|
|
|
Action_Toot.conversationLocal(
|
2020-12-07 14:06:25 +01:00
|
|
|
|
activity,
|
|
|
|
|
pos,
|
|
|
|
|
accessInfo,
|
|
|
|
|
statusInfo.statusId
|
|
|
|
|
)
|
2020-12-07 09:33:01 +01:00
|
|
|
|
}
|
|
|
|
|
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}")
|
2020-12-07 09:33:01 +01:00
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
)
|
2020-12-07 09:33:01 +01:00
|
|
|
|
}
|
|
|
|
|
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 09:33:01 +01:00
|
|
|
|
|
2020-12-07 14:06:25 +01:00
|
|
|
|
"gmail.com" -> {
|
|
|
|
|
activity.openBrowser("mailto:$user@$instance")
|
|
|
|
|
}
|
2020-12-07 09:33:01 +01:00
|
|
|
|
|
|
|
|
|
else -> {
|
|
|
|
|
Action_User.profile(
|
2020-12-07 14:06:25 +01:00
|
|
|
|
activity,
|
|
|
|
|
pos,
|
|
|
|
|
null, // Misskeyだと疑似アカが必要なんだっけ…?
|
|
|
|
|
"https://$instance/@$user",
|
|
|
|
|
instanceHost,
|
|
|
|
|
user,
|
|
|
|
|
original_url = url
|
|
|
|
|
)
|
2020-12-07 09:33:01 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
Action_User.profile(
|
2020-12-07 14:06:25 +01:00
|
|
|
|
activity,
|
|
|
|
|
pos,
|
|
|
|
|
accessInfo,
|
|
|
|
|
url,
|
|
|
|
|
Host.parse(host),
|
|
|
|
|
user
|
|
|
|
|
)
|
2020-12-07 09:33:01 +01:00
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
|
)
|
2020-12-07 09:33:01 +01:00
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
activity.openCustomTab(url)
|
|
|
|
|
|
|
|
|
|
} catch (ex: Throwable) {
|
2020-12-07 14:06:25 +01:00
|
|
|
|
log.trace(ex)
|
2020-12-07 09:33:01 +01:00
|
|
|
|
log.e(ex, "openCustomTab failed. $url")
|
|
|
|
|
}
|
2020-09-29 19:44:56 +02:00
|
|
|
|
}
|
|
|
|
|
|