通知チェック部分のコードをコルーチン対応にした。AppOpenerはいくつかのOSアクティビティを選択肢から除去するようになった。

This commit is contained in:
tateisu 2020-12-08 22:48:23 +09:00
parent 301dde36c0
commit 8818f25b6f
8 changed files with 3226 additions and 3185 deletions

View File

@ -84,6 +84,7 @@
<w>mastodonsearch</w> <w>mastodonsearch</w>
<w>mimumedon</w> <w>mimumedon</w>
<w>misskey</w> <w>misskey</w>
<w>misskeyclientproto</w>
<w>miyon</w> <w>miyon</w>
<w>mpeg</w> <w>mpeg</w>
<w>mpga</w> <w>mpga</w>
@ -126,6 +127,7 @@
<w>styler</w> <w>styler</w>
<w>subwaytooter</w> <w>subwaytooter</w>
<w>swipy</w> <w>swipy</w>
<w>systemui</w>
<w>taisaku</w> <w>taisaku</w>
<w>tateisu</w> <w>tateisu</w>
<w>tbody</w> <w>tbody</w>

File diff suppressed because it is too large Load Diff

View File

@ -14,6 +14,7 @@ import androidx.core.content.ContextCompat
import jp.juggler.util.LogCategory import jp.juggler.util.LogCategory
import jp.juggler.subwaytooter.util.NotificationHelper import jp.juggler.subwaytooter.util.NotificationHelper
import kotlinx.coroutines.runBlocking
class PollingForegrounder : IntentService("PollingForegrounder") { class PollingForegrounder : IntentService("PollingForegrounder") {
@ -89,17 +90,17 @@ class PollingForegrounder : IntentService("PollingForegrounder") {
override fun onHandleIntent(intent : Intent?) { override fun onHandleIntent(intent : Intent?) {
if(intent == null) return if(intent == null) return
val tag = intent.getStringExtra(PollingWorker.EXTRA_TAG) runBlocking {
val context = applicationContext val tag = intent.getStringExtra(PollingWorker.EXTRA_TAG)
PollingWorker.handleFCMMessage(this, tag, object : PollingWorker.JobStatusCallback { val context = applicationContext
override fun onStatus(sv : String) { PollingWorker.handleFCMMessage(context, tag) { sv ->
if(sv.isNotEmpty() && sv != last_status) { if (sv.isEmpty() || sv==last_status) return@handleFCMMessage
log.d("onStatus %s", sv) // 状況が変化したらログと通知領域に出力する
last_status = sv last_status = sv
startForeground(NOTIFICATION_ID_FOREGROUNDER, createNotification(context, sv)) log.d("onStatus %s", sv)
} startForeground(NOTIFICATION_ID_FOREGROUNDER, createNotification(context, sv))
} }
}) }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,6 @@
package jp.juggler.subwaytooter.api package jp.juggler.subwaytooter.api
import android.content.Context import android.content.Context
import android.content.SharedPreferences
import jp.juggler.subwaytooter.* import jp.juggler.subwaytooter.*
import jp.juggler.subwaytooter.api.entity.* import jp.juggler.subwaytooter.api.entity.*
import jp.juggler.subwaytooter.table.ClientInfo import jp.juggler.subwaytooter.table.ClientInfo
@ -12,37 +11,11 @@ import okhttp3.*
import java.util.* import java.util.*
class TootApiClient( class TootApiClient(
internal val context: Context, internal val context: Context,
internal val httpClient: SimpleHttpClient = SimpleHttpClientImpl( internal val httpClient: SimpleHttpClient =
context, SimpleHttpClientImpl(context,App1.ok_http_client),
App1.ok_http_client internal val callback: TootApiCallback
),
internal val callback: TootApiCallback
) { ) {
// 認証に関する設定を保存する
internal val pref: SharedPreferences
// インスタンスのホスト名
var apiHost: Host? = null
// アカウントがある場合に使用する
var account: SavedAccount? = null
set(value) {
apiHost = value?.apiHost
field = value
}
var currentCallCallback: CurrentCallCallback?
get() = httpClient.currentCallCallback
set(value) {
httpClient.currentCallCallback = value
}
init {
pref = context.pref()
}
companion object { companion object {
private val log = LogCategory("TootApiClient") private val log = LogCategory("TootApiClient")
@ -75,10 +48,10 @@ class TootApiClient(
{ json: JsonObject -> json["error"]?.toString() } { json: JsonObject -> json["error"]?.toString() }
internal fun simplifyErrorHtml( internal fun simplifyErrorHtml(
response: Response, response: Response,
sv: String, sv: String,
jsonErrorParser: (json: JsonObject) -> String? = DEFAULT_JSON_ERROR_PARSER jsonErrorParser: (json: JsonObject) -> String? = DEFAULT_JSON_ERROR_PARSER
): String { ): String {
// JsonObjectとして解釈できるならエラーメッセージを検出する // JsonObjectとして解釈できるならエラーメッセージを検出する
try { try {
@ -104,11 +77,11 @@ class TootApiClient(
} }
fun formatResponse( fun formatResponse(
response: Response, response: Response,
caption: String, caption: String,
bodyString: String? = null, bodyString: String? = null,
jsonErrorParser: (json: JsonObject) -> String? = DEFAULT_JSON_ERROR_PARSER jsonErrorParser: (json: JsonObject) -> String? = DEFAULT_JSON_ERROR_PARSER
): String { ): String {
val sb = StringBuilder() val sb = StringBuilder()
try { try {
// body は既に読み終わっているか、そうでなければこれから読む // body は既に読み終わっているか、そうでなければこれから読む
@ -155,53 +128,53 @@ class TootApiClient(
if (ti.versionGE(TootInstance.MISSKEY_VERSION_11)) { if (ti.versionGE(TootInstance.MISSKEY_VERSION_11)) {
// https://github.com/syuilo/misskey/blob/master/src/server/api/kinds.ts // https://github.com/syuilo/misskey/blob/master/src/server/api/kinds.ts
arrayOf( arrayOf(
"read:account", "read:account",
"write:account", "write:account",
"read:blocks", "read:blocks",
"write:blocks", "write:blocks",
"read:drive", "read:drive",
"write:drive", "write:drive",
"read:favorites", "read:favorites",
"write:favorites", "write:favorites",
"read:following", "read:following",
"write:following", "write:following",
"read:messaging", "read:messaging",
"write:messaging", "write:messaging",
"read:mutes", "read:mutes",
"write:mutes", "write:mutes",
"write:notes", "write:notes",
"read:notifications", "read:notifications",
"write:notifications", "write:notifications",
"read:reactions", "read:reactions",
"write:reactions", "write:reactions",
"write:votes" "write:votes"
) )
} else { } else {
// https://github.com/syuilo/misskey/issues/2341 // https://github.com/syuilo/misskey/issues/2341
arrayOf( arrayOf(
"account-read", "account-read",
"account-write", "account-write",
"account/read", "account/read",
"account/write", "account/write",
"drive-read", "drive-read",
"drive-write", "drive-write",
"favorite-read", "favorite-read",
"favorite-write", "favorite-write",
"favorites-read", "favorites-read",
"following-read", "following-read",
"following-write", "following-write",
"messaging-read", "messaging-read",
"messaging-write", "messaging-write",
"note-read", "note-read",
"note-write", "note-write",
"notification-read", "notification-read",
"notification-write", "notification-write",
"reaction-read", "reaction-read",
"reaction-write", "reaction-write",
"vote-read", "vote-read",
"vote-write" "vote-write"
) )
} }
// APIのエラーを回避するため、重複を排除する // APIのエラーを回避するため、重複を排除する
@ -222,6 +195,25 @@ class TootApiClient(
} }
// 認証に関する設定を保存する
internal val pref = context.pref()
// インスタンスのホスト名
var apiHost: Host? = null
// アカウントがある場合に使用する
var account: SavedAccount? = null
set(value) {
apiHost = value?.apiHost
field = value
}
var currentCallCallback: (Call) -> Unit
get() = httpClient.onCallCreated
set(value) {
httpClient.onCallCreated = value
}
@Suppress("unused") @Suppress("unused")
internal val isApiCancelled: Boolean internal val isApiCancelled: Boolean
get() = callback.isApiCancelled get() = callback.isApiCancelled
@ -239,11 +231,11 @@ class TootApiClient(
// リクエストをokHttpに渡してレスポンスを取得する // リクエストをokHttpに渡してレスポンスを取得する
internal inline fun sendRequest( internal inline fun sendRequest(
result: TootApiResult, result: TootApiResult,
progressPath: String? = null, progressPath: String? = null,
tmpOkhttpClient: OkHttpClient? = null, tmpOkhttpClient: OkHttpClient? = null,
block: () -> Request block: () -> Request
): Boolean { ): Boolean {
return try { return try {
result.response = null result.response = null
result.bodyString = null result.bodyString = null
@ -254,10 +246,10 @@ class TootApiClient(
result.requestInfo = "${request.method} ${progressPath ?: request.url.encodedPath}" result.requestInfo = "${request.method} ${progressPath ?: request.url.encodedPath}"
callback.publishApiProgress( callback.publishApiProgress(
context.getString( context.getString(
R.string.request_api, request.method, progressPath ?: request.url.encodedPath R.string.request_api, request.method, progressPath ?: request.url.encodedPath
) )
) )
val response = httpClient.getResponse(request, tmpOkhttpClient = tmpOkhttpClient) val response = httpClient.getResponse(request, tmpOkhttpClient = tmpOkhttpClient)
result.response = response result.response = response
@ -266,13 +258,53 @@ class TootApiClient(
} catch (ex: Throwable) { } catch (ex: Throwable) {
result.setError( result.setError(
"${result.caption}: ${ "${result.caption}: ${
ex.withCaption( ex.withCaption(
context.resources, context.resources,
R.string.network_error R.string.network_error
) )
}" }"
) )
false
}
}
// リクエストをokHttpに渡してレスポンスを取得する
private suspend inline fun sendRequestAsync(
result: TootApiResult,
progressPath: String? = null,
tmpOkhttpClient: OkHttpClient? = null,
block: () -> Request
): Boolean {
return try {
result.response = null
result.bodyString = null
result.data = null
val request = block()
result.requestInfo = "${request.method} ${progressPath ?: request.url.encodedPath}"
callback.publishApiProgress(
context.getString(
R.string.request_api, request.method, progressPath ?: request.url.encodedPath
)
)
val response = httpClient.getResponseAsync(request, tmpOkhttpClient = tmpOkhttpClient)
result.response = response
null == result.error
} catch (ex: Throwable) {
result.setError(
"${result.caption}: ${
ex.withCaption(
context.resources,
R.string.network_error
)
}"
)
false false
} }
} }
@ -280,10 +312,10 @@ class TootApiClient(
// レスポンスがエラーかボディがカラならエラー状態を設定する // レスポンスがエラーかボディがカラならエラー状態を設定する
// 例外を出すかも // 例外を出すかも
internal fun readBodyString( internal fun readBodyString(
result: TootApiResult, result: TootApiResult,
progressPath: String? = null, progressPath: String? = null,
jsonErrorParser: (json: JsonObject) -> String? = DEFAULT_JSON_ERROR_PARSER jsonErrorParser: (json: JsonObject) -> String? = DEFAULT_JSON_ERROR_PARSER
): String? { ): String? {
if (isApiCancelled) return null if (isApiCancelled) return null
@ -291,12 +323,12 @@ class TootApiClient(
val request = response.request val request = response.request
publishApiProgress( publishApiProgress(
context.getString( context.getString(
R.string.reading_api, R.string.reading_api,
request.method, request.method,
progressPath ?: result.caption progressPath ?: result.caption
) )
) )
val bodyString = response.body?.string() val bodyString = response.body?.string()
if (isApiCancelled) return null if (isApiCancelled) return null
@ -310,11 +342,11 @@ class TootApiClient(
if (!response.isSuccessful || bodyString?.isEmpty() != false) { if (!response.isSuccessful || bodyString?.isEmpty() != false) {
result.error = formatResponse( result.error = formatResponse(
response, response,
result.caption, result.caption,
if (bodyString?.isNotEmpty() == true) bodyString else NO_INFORMATION, if (bodyString?.isNotEmpty() == true) bodyString else NO_INFORMATION,
jsonErrorParser jsonErrorParser
) )
} }
return if (result.error != null) { return if (result.error != null) {
@ -329,10 +361,10 @@ class TootApiClient(
// レスポンスがエラーかボディがカラならエラー状態を設定する // レスポンスがエラーかボディがカラならエラー状態を設定する
// 例外を出すかも // 例外を出すかも
private fun readBodyBytes( private fun readBodyBytes(
result: TootApiResult, result: TootApiResult,
progressPath: String? = null, progressPath: String? = null,
jsonErrorParser: (json: JsonObject) -> String? = DEFAULT_JSON_ERROR_PARSER jsonErrorParser: (json: JsonObject) -> String? = DEFAULT_JSON_ERROR_PARSER
): ByteArray? { ): ByteArray? {
if (isApiCancelled) return null if (isApiCancelled) return null
@ -340,12 +372,12 @@ class TootApiClient(
val request = response.request val request = response.request
publishApiProgress( publishApiProgress(
context.getString( context.getString(
R.string.reading_api, R.string.reading_api,
request.method, request.method,
progressPath ?: result.caption progressPath ?: result.caption
) )
) )
val bodyBytes = response.body?.bytes() val bodyBytes = response.body?.bytes()
if (isApiCancelled) return null if (isApiCancelled) return null
@ -353,11 +385,11 @@ class TootApiClient(
if (!response.isSuccessful || bodyBytes?.isEmpty() != false) { if (!response.isSuccessful || bodyBytes?.isEmpty() != false) {
result.error = formatResponse( result.error = formatResponse(
response, response,
result.caption, result.caption,
if (bodyBytes?.isNotEmpty() == true) bodyBytes.decodeUTF8() else NO_INFORMATION, if (bodyBytes?.isNotEmpty() == true) bodyBytes.decodeUTF8() else NO_INFORMATION,
jsonErrorParser jsonErrorParser
) )
} }
return if (result.error != null) { return if (result.error != null) {
@ -370,17 +402,16 @@ class TootApiClient(
} }
private fun parseBytes( private fun parseBytes(
result: TootApiResult, result: TootApiResult,
progressPath: String? = null, progressPath: String? = null,
jsonErrorParser: (json: JsonObject) -> String? = DEFAULT_JSON_ERROR_PARSER jsonErrorParser: (json: JsonObject) -> String? = DEFAULT_JSON_ERROR_PARSER
): TootApiResult? { ): TootApiResult? {
val response = result.response!! // nullにならないはず val response = result.response!! // nullにならないはず
try { try {
readBodyBytes(result, progressPath, jsonErrorParser) readBodyBytes(result, progressPath, jsonErrorParser)
?: return if (isApiCancelled) null else result ?: return if (isApiCancelled) null else result
} catch (ex: Throwable) { } catch (ex: Throwable) {
log.trace(ex) log.trace(ex)
result.error = result.error =
@ -390,10 +421,10 @@ class TootApiClient(
} }
internal fun parseString( internal fun parseString(
result: TootApiResult, result: TootApiResult,
progressPath: String? = null, progressPath: String? = null,
jsonErrorParser: (json: JsonObject) -> String? = DEFAULT_JSON_ERROR_PARSER jsonErrorParser: (json: JsonObject) -> String? = DEFAULT_JSON_ERROR_PARSER
): TootApiResult? { ): TootApiResult? {
val response = result.response!! // nullにならないはず val response = result.response!! // nullにならないはず
@ -413,10 +444,10 @@ class TootApiClient(
// レスポンスからJSONデータを読む // レスポンスからJSONデータを読む
internal fun parseJson( internal fun parseJson(
result: TootApiResult, result: TootApiResult,
progressPath: String? = null, progressPath: String? = null,
jsonErrorParser: (json: JsonObject) -> String? = DEFAULT_JSON_ERROR_PARSER jsonErrorParser: (json: JsonObject) -> String? = DEFAULT_JSON_ERROR_PARSER
): TootApiResult? // 引数に指定したresultそのものか、キャンセルされたらnull ): TootApiResult? // 引数に指定したresultそのものか、キャンセルされたらnull
{ {
val response = result.response!! // nullにならないはず val response = result.response!! // nullにならないはず
@ -480,9 +511,9 @@ class TootApiClient(
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
fun request( fun request(
path: String, path: String,
request_builder: Request.Builder = Request.Builder() request_builder: Request.Builder = Request.Builder()
): TootApiResult? { ): TootApiResult? {
val result = TootApiResult.makeWithCaption(apiHost?.pretty) val result = TootApiResult.makeWithCaption(apiHost?.pretty)
if (result.error != null) return result if (result.error != null) return result
@ -511,6 +542,39 @@ class TootApiClient(
} }
} }
suspend fun requestAsync(
path: String,
request_builder: Request.Builder = Request.Builder()
): TootApiResult? {
val result = TootApiResult.makeWithCaption(apiHost?.pretty)
if (result.error != null) return result
val account = this.account // may null
try {
if (!sendRequestAsync(result) {
log.d("request: $path")
request_builder.url("https://${apiHost?.ascii}$path")
val access_token = account?.getAccessToken()
if (access_token?.isNotEmpty() == true) {
request_builder.header("Authorization", "Bearer $access_token")
}
request_builder.build()
}) return result
return parseJson(result)
} finally {
val error = result.error
if (error != null) log.d("error: $error")
}
}
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// misskey authentication // misskey authentication
@ -594,9 +658,9 @@ class TootApiClient(
} }
private fun registerClientMisskey( private fun registerClientMisskey(
scope_array: JsonArray, scope_array: JsonArray,
client_name: String client_name: String
): TootApiResult? { ): TootApiResult? {
val result = TootApiResult.makeWithCaption(apiHost?.pretty) val result = TootApiResult.makeWithCaption(apiHost?.pretty)
if (result.error != null) return result if (result.error != null) return result
if (sendRequest(result) { if (sendRequest(result) {
@ -699,10 +763,10 @@ class TootApiClient(
// oAuth2認証の続きを行う // oAuth2認証の続きを行う
fun authentication2Misskey( fun authentication2Misskey(
clientNameArg: String, clientNameArg: String,
token: String, token: String,
misskeyVersion: Int misskeyVersion: Int
): TootApiResult? { ): TootApiResult? {
val result = TootApiResult.makeWithCaption(apiHost?.pretty) val result = TootApiResult.makeWithCaption(apiHost?.pretty)
if (result.error != null) return result if (result.error != null) return result
val instance = result.caption // same to instance val instance = result.caption // same to instance
@ -834,9 +898,9 @@ class TootApiClient(
// client_credentialを無効にする // client_credentialを無効にする
private fun revokeClientCredential( private fun revokeClientCredential(
client_info: JsonObject, client_info: JsonObject,
client_credential: String client_credential: String
): TootApiResult? { ): TootApiResult? {
val result = TootApiResult.makeWithCaption(apiHost?.pretty) val result = TootApiResult.makeWithCaption(apiHost?.pretty)
if (result.error != null) return result if (result.error != null) return result
@ -885,10 +949,10 @@ class TootApiClient(
} }
private fun prepareClientMastodon( private fun prepareClientMastodon(
clientNameArg: String, clientNameArg: String,
ti: TootInstance, ti: TootInstance,
forceUpdateClient: Boolean = false forceUpdateClient: Boolean = false
): TootApiResult? { ): TootApiResult? {
// 前準備 // 前準備
val result = TootApiResult.makeWithCaption(apiHost?.pretty) val result = TootApiResult.makeWithCaption(apiHost?.pretty)
if (result.error != null) return result if (result.error != null) return result
@ -977,10 +1041,10 @@ class TootApiClient(
} }
private fun authentication1Mastodon( private fun authentication1Mastodon(
clientNameArg: String, clientNameArg: String,
ti: TootInstance, ti: TootInstance,
forceUpdateClient: Boolean = false forceUpdateClient: Boolean = false
): TootApiResult? { ): TootApiResult? {
if (ti.instanceType == TootInstance.InstanceType.Pixelfed) { if (ti.instanceType == TootInstance.InstanceType.Pixelfed) {
return TootApiResult("currently Pixelfed instance is not supported.") return TootApiResult("currently Pixelfed instance is not supported.")
@ -996,11 +1060,11 @@ class TootApiClient(
// クライアントを登録してブラウザで開くURLを生成する // クライアントを登録してブラウザで開くURLを生成する
fun authentication1( fun authentication1(
clientNameArg: String, clientNameArg: String,
forceUpdateClient: Boolean = false forceUpdateClient: Boolean = false
): TootApiResult? { ): TootApiResult? {
val (ti, ri) = TootInstance.get(this) val (ti, ri) = TootInstance.get(this)
ti ?: return ri ti ?: return ri
return when { return when {
ti.misskeyVersion > 0 -> authentication1Misskey(clientNameArg, ti) ti.misskeyVersion > 0 -> authentication1Misskey(clientNameArg, ti)
@ -1054,8 +1118,8 @@ class TootApiClient(
// アクセストークン手動入力でアカウントを更新する場合、アカウントの情報を取得する // アクセストークン手動入力でアカウントを更新する場合、アカウントの情報を取得する
fun getUserCredential( fun getUserCredential(
access_token: String, tokenInfo: JsonObject = JsonObject(), misskeyVersion: Int = 0 access_token: String, tokenInfo: JsonObject = JsonObject(), misskeyVersion: Int = 0
): TootApiResult? { ): TootApiResult? {
if (misskeyVersion > 0) { if (misskeyVersion > 0) {
val result = TootApiResult.makeWithCaption(apiHost?.pretty) val result = TootApiResult.makeWithCaption(apiHost?.pretty)
if (result.error != null) return result if (result.error != null) return result
@ -1107,16 +1171,16 @@ class TootApiClient(
fun createUser1(clientNameArg: String): TootApiResult? { fun createUser1(clientNameArg: String): TootApiResult? {
val (ti, ri) = TootInstance.get(this) val (ti, ri) = TootInstance.get(this)
ti ?: return ri ti ?: return ri
return when (ti.instanceType) { return when (ti.instanceType) {
TootInstance.InstanceType.Misskey -> TootInstance.InstanceType.Misskey ->
TootApiResult("Misskey has no API to create new account") TootApiResult("Misskey has no API to create new account")
TootInstance.InstanceType.Pleroma -> TootInstance.InstanceType.Pleroma ->
TootApiResult("Pleroma has no API to create new account") TootApiResult("Pleroma has no API to create new account")
TootInstance.InstanceType.Pixelfed -> TootInstance.InstanceType.Pixelfed ->
TootApiResult("Pixelfed has no API to create new account") TootApiResult("Pixelfed has no API to create new account")
else -> else ->
prepareClientMastodon(clientNameArg, ti) prepareClientMastodon(clientNameArg, ti)
// result.JsonObject に credentialつきのclient_info を格納して返す // result.JsonObject に credentialつきのclient_info を格納して返す
@ -1125,13 +1189,13 @@ class TootApiClient(
// ユーザ名入力の後に呼ばれる // ユーザ名入力の後に呼ばれる
fun createUser2Mastodon( fun createUser2Mastodon(
client_info: JsonObject, client_info: JsonObject,
username: String, username: String,
email: String, email: String,
password: String, password: String,
agreement: Boolean, agreement: Boolean,
reason: String? reason: String?
): TootApiResult? { ): TootApiResult? {
val result = TootApiResult.makeWithCaption(apiHost?.pretty) val result = TootApiResult.makeWithCaption(apiHost?.pretty)
if (result.error != null) return result if (result.error != null) return result
@ -1166,10 +1230,9 @@ class TootApiClient(
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
// JSONデータ以外を扱うリクエスト // JSONデータ以外を扱うリクエスト
fun http(req: Request): TootApiResult? { fun http(req: Request): TootApiResult {
val result = TootApiResult.makeWithCaption(req.url.host) val result = TootApiResult.makeWithCaption(req.url.host)
if (result.error != null) return result if (result.error != null) return result
sendRequest(result, progressPath = null) { req } sendRequest(result, progressPath = null) { req }
return result return result
} }
@ -1184,12 +1247,9 @@ class TootApiClient(
// } // }
// 疑似アカウントでステータスURLからステータスIDを取得するためにHTMLを取得する // 疑似アカウントでステータスURLからステータスIDを取得するためにHTMLを取得する
fun getHttp(url: String): TootApiResult? { fun getHttp(url: String):TootApiResult?{
val result = http(Request.Builder().url(url).build()) val result = http(Request.Builder().url(url).build())
if (result != null && result.error == null) { return if (result.error != null) result else parseString(result)
parseString(result)
}
return result
} }
fun getHttpBytes(url: String): Pair<TootApiResult?, ByteArray?> { fun getHttpBytes(url: String): Pair<TootApiResult?, ByteArray?> {
@ -1206,9 +1266,9 @@ class TootApiClient(
} }
fun webSocket( fun webSocket(
path: String, path: String,
ws_listener: WebSocketListener ws_listener: WebSocketListener
): Pair<TootApiResult?, WebSocket?> { ): Pair<TootApiResult?, WebSocket?> {
var ws: WebSocket? = null var ws: WebSocket? = null
val result = TootApiResult.makeWithCaption(apiHost?.pretty) val result = TootApiResult.makeWithCaption(apiHost?.pretty)
if (result.error != null) return Pair(result, null) if (result.error != null) return Pair(result, null)
@ -1244,8 +1304,8 @@ class TootApiClient(
// query: query_string after ? ( ? itself is excluded ) // query: query_string after ? ( ? itself is excluded )
fun TootApiClient.requestMastodonSearch( fun TootApiClient.requestMastodonSearch(
parser: TootParser, parser: TootParser,
query: String query: String
): Pair<TootApiResult?, TootResults?> { ): Pair<TootApiResult?, TootResults?> {
var searchApiVersion = 2 var searchApiVersion = 2
@ -1267,8 +1327,8 @@ fun TootApiClient.requestMastodonSearch(
// result.data に TootAccountRefを格納して返す。もしくはエラーかキャンセル // result.data に TootAccountRefを格納して返す。もしくはエラーかキャンセル
fun TootApiClient.syncAccountByUrl( fun TootApiClient.syncAccountByUrl(
accessInfo: SavedAccount, accessInfo: SavedAccount,
who_url: String who_url: String
): Pair<TootApiResult?, TootAccountRef?> { ): Pair<TootApiResult?, TootAccountRef?> {
// misskey由来のアカウントURLは https://host/@user@instance などがある // misskey由来のアカウントURLは https://host/@user@instance などがある
@ -1288,18 +1348,18 @@ fun TootApiClient.syncAccountByUrl(
val acct = TootAccount.getAcctFromUrl(who_url) val acct = TootAccount.getAcctFromUrl(who_url)
?: return Pair( ?: return Pair(
TootApiResult(context.getString(R.string.user_id_conversion_failed)), TootApiResult(context.getString(R.string.user_id_conversion_failed)),
null null
) )
var ar: TootAccountRef? = null var ar: TootAccountRef? = null
val result = request( val result = request(
"/api/users/show", "/api/users/show",
accessInfo.putMisskeyApiToken().apply { accessInfo.putMisskeyApiToken().apply {
put("username", acct.username) put("username", acct.username)
acct.host?.let { put("host", it.ascii) } acct.host?.let { put("host", it.ascii) }
}.toPostRequestBuilder() }.toPostRequestBuilder()
) )
?.apply { ?.apply {
ar = TootAccountRef.mayNull(parser, parser.account(jsonObject)) ar = TootAccountRef.mayNull(parser, parser.account(jsonObject))
if (ar == null && error == null) { if (ar == null && error == null) {
@ -1308,10 +1368,10 @@ fun TootApiClient.syncAccountByUrl(
} }
Pair(result, ar) Pair(result, ar)
} else { } else {
val (apiResult, searchResult) = requestMastodonSearch( val (apiResult, searchResult) = requestMastodonSearch(
parser, parser,
"q=${who_url.encodePercent()}&resolve=true" "q=${who_url.encodePercent()}&resolve=true"
) )
val ar = searchResult?.accounts?.firstOrNull() val ar = searchResult?.accounts?.firstOrNull()
if (apiResult != null && apiResult.error == null && ar == null) { if (apiResult != null && apiResult.error == null && ar == null) {
apiResult.setError(context.getString(R.string.user_id_conversion_failed)) apiResult.setError(context.getString(R.string.user_id_conversion_failed))
@ -1321,27 +1381,27 @@ fun TootApiClient.syncAccountByUrl(
} }
fun TootApiClient.syncAccountByAcct( fun TootApiClient.syncAccountByAcct(
accessInfo: SavedAccount, accessInfo: SavedAccount,
acctArg: String acctArg: String
): Pair<TootApiResult?, TootAccountRef?> = syncAccountByAcct(accessInfo, Acct.parse(acctArg)) ): Pair<TootApiResult?, TootAccountRef?> = syncAccountByAcct(accessInfo, Acct.parse(acctArg))
fun TootApiClient.syncAccountByAcct( fun TootApiClient.syncAccountByAcct(
accessInfo: SavedAccount, accessInfo: SavedAccount,
acct: Acct acct: Acct
): Pair<TootApiResult?, TootAccountRef?> { ): Pair<TootApiResult?, TootAccountRef?> {
val parser = TootParser(context, accessInfo) val parser = TootParser(context, accessInfo)
return if (accessInfo.isMisskey) { return if (accessInfo.isMisskey) {
var ar: TootAccountRef? = null var ar: TootAccountRef? = null
val result = request( val result = request(
"/api/users/show", "/api/users/show",
accessInfo.putMisskeyApiToken() accessInfo.putMisskeyApiToken()
.apply { .apply {
if (acct.isValid) put("username", acct.username) if (acct.isValid) put("username", acct.username)
if (acct.host != null) put("host", acct.host.ascii) if (acct.host != null) put("host", acct.host.ascii)
} }
.toPostRequestBuilder() .toPostRequestBuilder()
) )
?.apply { ?.apply {
ar = TootAccountRef.mayNull(parser, parser.account(jsonObject)) ar = TootAccountRef.mayNull(parser, parser.account(jsonObject))
if (ar == null && error == null) { if (ar == null && error == null) {
@ -1350,10 +1410,10 @@ fun TootApiClient.syncAccountByAcct(
} }
Pair(result, ar) Pair(result, ar)
} else { } else {
val (apiResult, searchResult) = requestMastodonSearch( val (apiResult, searchResult) = requestMastodonSearch(
parser, parser,
"q=${acct.ascii.encodePercent()}&resolve=true" "q=${acct.ascii.encodePercent()}&resolve=true"
) )
val ar = searchResult?.accounts?.firstOrNull() val ar = searchResult?.accounts?.firstOrNull()
if (apiResult != null && apiResult.error == null && ar == null) { if (apiResult != null && apiResult.error == null && ar == null) {
apiResult.setError(context.getString(R.string.user_id_conversion_failed)) apiResult.setError(context.getString(R.string.user_id_conversion_failed))
@ -1364,8 +1424,8 @@ fun TootApiClient.syncAccountByAcct(
} }
fun TootApiClient.syncStatus( fun TootApiClient.syncStatus(
accessInfo: SavedAccount, accessInfo: SavedAccount,
urlArg: String urlArg: String
): Pair<TootApiResult?, TootStatus?> { ): Pair<TootApiResult?, TootStatus?> {
var url = urlArg var url = urlArg
@ -1380,18 +1440,18 @@ fun TootApiClient.syncStatus(
TootApiClient(context, callback = callback) TootApiClient(context, callback = callback)
.apply { apiHost = host } .apply { apiHost = host }
.request( .request(
"/api/notes/show", "/api/notes/show",
JsonObject().apply { JsonObject().apply {
put("noteId", noteId) put("noteId", noteId)
} }
.toPostRequestBuilder() .toPostRequestBuilder()
) )
?.also { result -> ?.also { result ->
TootParser( TootParser(
context, context,
linkHelper = LinkHelper.create(host, misskeyVersion = 10), linkHelper = LinkHelper.create(host, misskeyVersion = 10),
serviceType = ServiceType.MISSKEY serviceType = ServiceType.MISSKEY
) )
.status(result.jsonObject) .status(result.jsonObject)
?.apply { ?.apply {
if (accessInfo.matchHost(host)) { if (accessInfo.matchHost(host)) {
@ -1409,12 +1469,12 @@ fun TootApiClient.syncStatus(
return if (accessInfo.isMisskey) { return if (accessInfo.isMisskey) {
var targetStatus: TootStatus? = null var targetStatus: TootStatus? = null
val result = request( val result = request(
"/api/ap/show", "/api/ap/show",
accessInfo.putMisskeyApiToken().apply { accessInfo.putMisskeyApiToken().apply {
put("uri", url) put("uri", url)
} }
.toPostRequestBuilder() .toPostRequestBuilder()
) )
?.apply { ?.apply {
targetStatus = parser.parseMisskeyApShow(jsonObject) as? TootStatus targetStatus = parser.parseMisskeyApShow(jsonObject) as? TootStatus
if (targetStatus == null && error == null) { if (targetStatus == null && error == null) {
@ -1423,10 +1483,10 @@ fun TootApiClient.syncStatus(
} }
Pair(result, targetStatus) Pair(result, targetStatus)
} else { } else {
val (apiResult, searchResult) = requestMastodonSearch( val (apiResult, searchResult) = requestMastodonSearch(
parser, parser,
"q=${url.encodePercent()}&resolve=true" "q=${url.encodePercent()}&resolve=true"
) )
val targetStatus = searchResult?.statuses?.firstOrNull() val targetStatus = searchResult?.statuses?.firstOrNull()
if (apiResult != null && apiResult.error == null && targetStatus == null) { if (apiResult != null && apiResult.error == null && targetStatus == null) {
apiResult.setError(context.getString(R.string.cant_sync_toot)) apiResult.setError(context.getString(R.string.cant_sync_toot))
@ -1437,8 +1497,8 @@ fun TootApiClient.syncStatus(
} }
fun TootApiClient.syncStatus( fun TootApiClient.syncStatus(
accessInfo: SavedAccount, accessInfo: SavedAccount,
statusRemote: TootStatus statusRemote: TootStatus
): Pair<TootApiResult?, TootStatus?> { ): Pair<TootApiResult?, TootStatus?> {
// URL->URIの順に試す // URL->URIの順に試す

View File

@ -262,7 +262,7 @@ class NotificationCache(private val account_db_id : Long) {
} }
fun request( suspend fun requestAsync(
client : TootApiClient, client : TootApiClient,
account : SavedAccount, account : SavedAccount,
flags : Int, flags : Int,
@ -291,9 +291,9 @@ class NotificationCache(private val account_db_id : Long) {
} }
val result = if(account.isMisskey) { val result = if(account.isMisskey) {
client.request(path, account.putMisskeyApiToken().toPostRequestBuilder()) client.requestAsync(path, account.putMisskeyApiToken().toPostRequestBuilder())
} else { } else {
client.request(path) client.requestAsync(path)
} }
if(result == null) { if(result == null) {

View File

@ -48,9 +48,21 @@ private fun Activity.startActivityExcludeMyApp(
val myName = packageName val myName = packageName
val filter: (ResolveInfo) -> Boolean = { val filter: (ResolveInfo) -> Boolean = {
it.activityInfo.packageName != myName && when{
it.activityInfo.exported && it.activityInfo.packageName == myName -> false
-1 == it.activityInfo.packageName.indexOf("com.huawei.android.internal") !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を返すなら、それがベストなんだろう // resolveActivity がこのアプリ以外のActivityを返すなら、それがベストなんだろう

View File

@ -4,57 +4,69 @@ import android.content.Context
import okhttp3.* import okhttp3.*
import jp.juggler.subwaytooter.App1 import jp.juggler.subwaytooter.App1
import jp.juggler.util.LogCategory import jp.juggler.util.LogCategory
import ru.gildor.coroutines.okhttp.await
// okhttpそのままだとモックしづらいので // okhttpそのままだとモックしづらいので
// リクエストを投げてレスポンスを得る部分をインタフェースにまとめる // リクエストを投げてレスポンスを得る部分をインタフェースにまとめる
interface CurrentCallCallback {
fun onCallCreated(call : Call)
}
interface SimpleHttpClient { interface SimpleHttpClient {
var currentCallCallback : CurrentCallCallback?
var onCallCreated: (Call) -> Unit
fun getResponse(
request : Request, fun getResponse(
tmpOkhttpClient : OkHttpClient? = null request: Request,
) : Response tmpOkhttpClient: OkHttpClient? = null
): Response
fun getWebSocket(
request : Request, suspend fun getResponseAsync(
webSocketListener : WebSocketListener request: Request,
) : WebSocket tmpOkhttpClient: OkHttpClient? = null
): Response
fun getWebSocket(
request: Request,
webSocketListener: WebSocketListener
): WebSocket
} }
class SimpleHttpClientImpl( class SimpleHttpClientImpl(
val context : Context, val context: Context,
private val okHttpClient : OkHttpClient private val okHttpClient: OkHttpClient
) : SimpleHttpClient { ) : SimpleHttpClient {
companion object {
companion object { val log = LogCategory("SimpleHttpClientImpl")
val log = LogCategory("SimpleHttpClientImpl") }
}
override var onCallCreated: (Call) -> Unit = {}
override var currentCallCallback : CurrentCallCallback? = null
override fun getResponse(
override fun getResponse( request: Request,
request : Request, tmpOkhttpClient: OkHttpClient?
tmpOkhttpClient : OkHttpClient? ): Response {
) : Response { App1.getAppState(context).networkTracker.checkNetworkState()
val call = (tmpOkhttpClient ?: this.okHttpClient).newCall(request)
onCallCreated(call)
return call.execute()
}
override suspend fun getResponseAsync(
request: Request,
tmpOkhttpClient: OkHttpClient?
): Response {
App1.getAppState(context).networkTracker.checkNetworkState() App1.getAppState(context).networkTracker.checkNetworkState()
val call = (tmpOkhttpClient ?: this.okHttpClient).newCall(request) val call = (tmpOkhttpClient ?: this.okHttpClient).newCall(request)
currentCallCallback?.onCallCreated(call) onCallCreated(call)
return call.execute() return call.await()
} }
override fun getWebSocket( override fun getWebSocket(
request : Request, request: Request,
webSocketListener : WebSocketListener webSocketListener: WebSocketListener
) : WebSocket { ): WebSocket {
App1.getAppState(context).networkTracker.checkNetworkState() App1.getAppState(context).networkTracker.checkNetworkState()
return okHttpClient.newWebSocket(request, webSocketListener) return okHttpClient.newWebSocket(request, webSocketListener)
} }
} }