通知チェック部分のコードをコルーチン対応にした。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>mimumedon</w>
<w>misskey</w>
<w>misskeyclientproto</w>
<w>miyon</w>
<w>mpeg</w>
<w>mpga</w>
@ -126,6 +127,7 @@
<w>styler</w>
<w>subwaytooter</w>
<w>swipy</w>
<w>systemui</w>
<w>taisaku</w>
<w>tateisu</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.subwaytooter.util.NotificationHelper
import kotlinx.coroutines.runBlocking
class PollingForegrounder : IntentService("PollingForegrounder") {
@ -89,17 +90,17 @@ class PollingForegrounder : IntentService("PollingForegrounder") {
override fun onHandleIntent(intent : Intent?) {
if(intent == null) return
runBlocking {
val tag = intent.getStringExtra(PollingWorker.EXTRA_TAG)
val context = applicationContext
PollingWorker.handleFCMMessage(this, tag, object : PollingWorker.JobStatusCallback {
override fun onStatus(sv : String) {
if(sv.isNotEmpty() && sv != last_status) {
log.d("onStatus %s", sv)
PollingWorker.handleFCMMessage(context, tag) { sv ->
if (sv.isEmpty() || sv==last_status) return@handleFCMMessage
// 状況が変化したらログと通知領域に出力する
last_status = sv
log.d("onStatus %s", sv)
startForeground(NOTIFICATION_ID_FOREGROUNDER, createNotification(context, sv))
}
}
})
}
}

View File

@ -15,7 +15,6 @@ import android.net.ConnectivityManager
import android.net.Uri
import android.net.wifi.WifiManager
import android.os.Build
import android.os.Handler
import android.os.PowerManager
import android.os.SystemClock
import android.service.notification.StatusBarNotification
@ -24,6 +23,7 @@ import androidx.core.content.ContextCompat
import com.google.firebase.messaging.FirebaseMessaging
import jp.juggler.subwaytooter.api.TootApiCallback
import jp.juggler.subwaytooter.api.TootApiClient
import jp.juggler.subwaytooter.api.TootApiResult
import jp.juggler.subwaytooter.api.TootParser
import jp.juggler.subwaytooter.api.entity.*
import jp.juggler.subwaytooter.table.*
@ -31,7 +31,9 @@ import jp.juggler.subwaytooter.table.NotificationCache.Companion.getEntityOrderI
import jp.juggler.subwaytooter.table.NotificationCache.Companion.parseNotificationType
import jp.juggler.subwaytooter.util.*
import jp.juggler.util.*
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.ClosedReceiveChannelException
import kotlinx.coroutines.tasks.await
import okhttp3.Call
import okhttp3.Request
@ -41,16 +43,12 @@ import java.util.*
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicReference
import kotlin.coroutines.coroutineContext
import kotlin.math.max
import kotlin.math.min
class PollingWorker private constructor(contextArg: Context) {
interface JobStatusCallback {
fun onStatus(sv: String)
}
enum class TrackingType(val str: String) {
All("all"),
Reply("reply"),
@ -68,6 +66,14 @@ class PollingWorker private constructor(contextArg: Context) {
}
internal class Data(val access_info: SavedAccount, val notification: TootNotification)
internal class InjectData {
var account_db_id: Long = 0
val list = ArrayList<TootNotification>()
}
companion object {
internal val log = LogCategory("PollingWorker")
@ -122,19 +128,19 @@ class PollingWorker private constructor(contextArg: Context) {
return s
}
suspend fun getFirebaseMessagingToken(context:Context):String?{
suspend fun getFirebaseMessagingToken(context: Context): String? {
val prefDevice = PrefDevice.prefDevice(context)
// 設定ファイルに保持されていたらそれを使う
prefDevice
.getString(PrefDevice.KEY_DEVICE_TOKEN, null)
?.notEmpty()?.let{ return it}
?.notEmpty()?.let { return it }
// 古い形式
// return FirebaseInstanceId.getInstance().getToken(FCM_SENDER_ID, FCM_SCOPE)
// com.google.firebase:firebase-messaging.20.3.0 以降
// implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:$kotlinx_coroutines_version"
try{
try {
val sv = FirebaseMessaging.getInstance().token.await()
return if (sv.isNullOrBlank()) {
log.e("getFirebaseMessagingToken: missing device token.")
@ -146,14 +152,13 @@ class PollingWorker private constructor(contextArg: Context) {
.apply()
sv
}
}catch(ex:Throwable){
} catch (ex: Throwable) {
log.trace(ex, "getFirebaseMessagingToken: could not get device token.")
return null
}
}
// インストールIDを生成する前に、各データの通知登録キャッシュをクリアする
// トークンがまだ生成されていない場合、このメソッドは null を返します。
@Suppress("BlockingMethodInNonBlockingContext")
@ -186,7 +191,7 @@ class PollingWorker private constructor(contextArg: Context) {
log.e(
TootApiClient.formatResponse(
response,
"getInstallId: get /counter failed."
"getInstallId: get/counter failed."
)
)
return null
@ -371,29 +376,43 @@ class PollingWorker private constructor(contextArg: Context) {
addTask(context, true, TASK_PACKAGE_REPLACED, null)
}
internal val job_status = AtomicReference<String>(null)
private val job_status = AtomicReference<String>(null)
fun handleFCMMessage(context: Context, tag: String?, callback: JobStatusCallback) {
private var workerStatus: String
get() = job_status.get()
set(x) {
log.d("workerStatus:$x")
job_status.set(x)
}
// IntentServiceが作ったスレッドから呼ばれる
suspend fun handleFCMMessage(
context: Context,
tag: String?,
progress: (String) -> Unit
) {
log.d("handleFCMMessage: start. tag=$tag")
val time_start = SystemClock.elapsedRealtime()
callback.onStatus("=>")
// この呼出でIntentServiceがstartForegroundする
progress("=>")
// タスクを追加
val data = JsonObject().apply {
try {
putNotNull(EXTRA_TAG, tag)
task_list.addLast(
context,
true,
JsonObject().apply {
this[EXTRA_TASK_ID] = TASK_FCM_MESSAGE
} catch (_: JsonException) {
}
if (tag != null) this[EXTRA_TAG] = tag
}
)
task_list.addLast(context, true, data)
callback.onStatus("==>")
progress("==>")
// 疑似ジョブを開始
val pw = getInstance(context)
pw.addJobFCM()
// 疑似ジョブが終了するまで待機する
@ -407,49 +426,35 @@ class PollingWorker private constructor(contextArg: Context) {
)
break
}
// ジョブの状況を通知する
var sv: String? = job_status.get()
if (sv == null) sv = "(null)"
callback.onStatus(sv)
progress(job_status.get() ?: "(null)")
// 少し待機
try {
Thread.sleep(50L)
} catch (ex: InterruptedException) {
log.e(ex, "handleFCMMessage: blocking is interrupted.")
break
delay(50L)
}
}
}
}
}
}
internal val context: Context
private val appState: AppState
internal val handler: Handler
internal val pref: SharedPreferences
private val connectivityManager: ConnectivityManager
internal val notification_manager: NotificationManager
internal val scheduler: JobScheduler
private val power_manager: PowerManager?
internal val power_lock: PowerManager.WakeLock
private val power_lock: PowerManager.WakeLock
private val wifi_manager: WifiManager?
internal val wifi_lock: WifiManager.WifiLock
private var worker: Worker
private val wifi_lock: WifiManager.WifiLock
internal val job_list = LinkedList<JobItem>()
internal class Data(val access_info: SavedAccount, val notification: TootNotification)
internal class InjectData {
var account_db_id: Long = 0
val list = ArrayList<TootNotification>()
}
private val workerJob: Job
private val workerNotifier = Channel<Unit>(capacity = Channel.CONFLATED)
init {
log.d("ctor")
log.d("init")
val context = contextArg.applicationContext
@ -457,9 +462,8 @@ class PollingWorker private constructor(contextArg: Context) {
// クラッシュレポートによると App1.onCreate より前にここを通る場合がある
// データベースへアクセスできるようにする
this.appState = App1.prepare(context, "PollingWorker.ctor()")
this.appState = App1.prepare(context, "PollingWorker.init")
this.pref = App1.pref
this.handler = appState.handler
this.connectivityManager = systemService(context)
?: error("missing ConnectivityManager system service")
@ -496,18 +500,7 @@ class PollingWorker private constructor(contextArg: Context) {
wifi_lock.setReferenceCounted(false)
//
worker = Worker()
worker.start()
}
inner class Worker : WorkerBase() {
val bThreadCancelled = AtomicBoolean(false)
override fun cancel() {
bThreadCancelled.set(true)
notifyEx()
workerJob = GlobalScope.launch(Dispatchers.Default) { worker() }
}
@SuppressLint("WakelockTimeout")
@ -547,48 +540,47 @@ class PollingWorker private constructor(contextArg: Context) {
} catch (ex: Throwable) {
log.trace(ex)
}
}
override fun run() {
log.d("worker thread start.")
job_status.set("worker thread start.")
while (!bThreadCancelled.get()) {
private suspend fun worker() {
workerStatus = "worker start."
try {
val item: JobItem? = synchronized(job_list) {
suspend fun isActive() = coroutineContext[Job]?.isActive == true
while (isActive()) {
while (true) {
handleJobItem(synchronized(job_list) {
for (ji in job_list) {
if (bThreadCancelled.get()) break
if (ji.mJobCancelled_.get()) continue
if (ji.mWorkerAttached.compareAndSet(false, true)) {
return@synchronized ji
}
}
null
} ?: break)
}
try {
workerNotifier.receive()
} catch (ex: ClosedReceiveChannelException) {
}
}
} finally {
workerStatus = "worker end."
}
}
if (item == null) {
job_status.set("no job to run.")
waitEx(86400000L)
continue
}
job_status.set("start job " + item.jobId)
private suspend fun handleJobItem(item: JobItem) {
try {
workerStatus = "start job ${item.jobId}"
acquirePowerLock()
try {
item.refWorker.set(this@Worker)
item.run()
} finally {
job_status.set("end job " + item.jobId)
item.refWorker.set(null)
releasePowerLock()
}
} catch (ex: Throwable) {
log.trace(ex)
}
}
job_status.set("worker thread end.")
log.d("worker thread end.")
} finally {
workerStatus = "end job ${item.jobId}"
}
}
@ -612,7 +604,7 @@ class PollingWorker private constructor(contextArg: Context) {
}
// JobService#onStartJob から呼ばれる
fun onStartJob(jobService: JobService, params: JobParameters): Boolean {
suspend fun onStartJob(jobService: JobService, params: JobParameters): Boolean {
val item = JobItem(jobService, params)
addJob(item, true)
return true
@ -631,11 +623,11 @@ class PollingWorker private constructor(contextArg: Context) {
}
// FCMメッセージイベントから呼ばれる
private fun addJobFCM() {
private suspend fun addJobFCM() {
addJob(JobItem(JOB_FCM), false)
}
private fun addJob(item: JobItem, bRemoveOld: Boolean) {
private suspend fun addJob(item: JobItem, bRemoveOld: Boolean) {
val jobId = item.jobId
// 同じジョブ番号がジョブリストにあるか?
@ -656,7 +648,8 @@ class PollingWorker private constructor(contextArg: Context) {
job_list.add(item)
}
worker.notifyEx()
workerNotifier.send(Unit)
}
// JobService#onStopJob から呼ばれる
@ -705,14 +698,9 @@ class PollingWorker private constructor(contextArg: Context) {
var current_call: Call? = null
val refWorker = AtomicReference<Worker>(null)
val isJobCancelled: Boolean
get() {
if (mJobCancelled_.get()) return true
val worker = refWorker.get()
return worker != null && worker.bThreadCancelled.get()
}
get() = mJobCancelled_.get() || workerJob.isCancelled
constructor(jobService: JobService, params: JobParameters) {
this.jobParams = params
@ -726,45 +714,34 @@ class PollingWorker private constructor(contextArg: Context) {
this.refJobService = null
}
fun notifyWorkerThread() {
val worker = refWorker.get()
worker?.notifyEx()
}
fun waitWorkerThread(ms: Long) {
val worker = refWorker.get()
worker?.waitEx(ms)
}
fun cancel(bReschedule: Boolean) {
mJobCancelled_.set(true)
mReschedule.set(bReschedule)
current_call?.cancel()
notifyWorkerThread()
runBlocking { workerNotifier.send(Unit) }
}
fun run() {
job_status.set("job start.")
suspend fun run() = coroutineScope {
workerStatus = "job start."
try {
log.d("(JobItem.run jobId=${jobId}")
if (isJobCancelled) throw JobCancelledException()
job_status.set("check network status..")
workerStatus = "check network status.."
val net_wait_start = SystemClock.elapsedRealtime()
var connectionState: String? = null
try {
withTimeout(10000L) {
while (true) {
val connectionState = App1.getAppState(context, "PollingWorker.JobItem.run()")
.networkTracker.connectionState
?: break
if (isJobCancelled) throw JobCancelledException()
val now = SystemClock.elapsedRealtime()
val delta = now - net_wait_start
if (delta >= 10000L) {
log.d("network state timeout. $connectionState")
break
connectionState = App1.getAppState(context, "PollingWorker.JobItem.run()")
.networkTracker.connectionState
?: break // null if connected
delay(333L)
}
waitWorkerThread(333L)
}
} catch (ex: TimeoutCancellationException) {
log.d("network state timeout. $connectionState")
}
muted_app = MutedApp.nameSet
@ -783,7 +760,8 @@ class PollingWorker private constructor(contextArg: Context) {
// タスクがなかった場合でも定期実行ジョブからの実行ならポーリングを行う
TaskRunner().runTask(this@JobItem, TASK_POLLING, JsonObject())
}
job_status.set("make next schedule.")
workerStatus = "make next schedule."
log.d("pollingComplete=${bPollingComplete},isJobCancelled=${isJobCancelled},bPollingRequired=${bPollingRequired.get()}")
@ -809,33 +787,32 @@ class PollingWorker private constructor(contextArg: Context) {
log.trace(ex)
log.e(ex, "job execution failed.")
} finally {
job_status.set("job finished.")
workerStatus = "job finished."
}
// ジョブ終了報告
if (!isJobCancelled) {
handler.post(Runnable {
if (isJobCancelled) return@Runnable
log.d(")JobItem.run jobId=${jobId}, cancel=${isJobCancelled}")
launch(Dispatchers.Main) {
if (isJobCancelled) return@launch
synchronized(job_list) {
job_list.remove(this@JobItem)
}
refJobService?.get()?.let { jobService ->
try {
val jobService = refJobService?.get()
if (jobService != null) {
// ジョブ終了報告
val willReschedule = mReschedule.get()
log.d("sending jobFinished. willReschedule=$willReschedule")
jobService.jobFinished(jobParams, willReschedule)
}
} catch (ex: Throwable) {
log.trace(ex, "jobFinished failed(1).")
}
})
}
log.d(")JobItem.run jobId=${jobId}, cancel=${isJobCancelled}")
}
}
}
}
private fun TrackingType.trackingTypeName() = when (this) {
TrackingType.NotReply -> NotificationHelper.TRACKING_NAME_DEFAULT
@ -850,16 +827,15 @@ class PollingWorker private constructor(contextArg: Context) {
val error_instance = ArrayList<String>()
fun runTask(job: JobItem, taskId: Int, taskData: JsonObject) {
try {
log.d("(runTask: taskId=${taskId}")
job_status.set("start task $taskId")
suspend fun runTask(job: JobItem, taskId: Int, taskData: JsonObject) {
workerStatus = "start task $taskId"
this.job = job
this.taskId = taskId
var process_db_id = -1L //
coroutineScope {
try {
when (taskId) {
TASK_APP_DATA_IMPORT_BEFORE -> {
scheduler.cancelAll()
@ -873,7 +849,7 @@ class PollingWorker private constructor(contextArg: Context) {
}
mBusyAppDataImportBefore.set(false)
return
return@coroutineScope
}
TASK_APP_DATA_IMPORT_AFTER -> {
@ -886,7 +862,8 @@ class PollingWorker private constructor(contextArg: Context) {
}
// アプリデータのインポート処理がビジーな間、他のジョブは実行されない
if (mBusyAppDataImportBefore.get() || mBusyAppDataImportAfter.get()) return
if (mBusyAppDataImportBefore.get() || mBusyAppDataImportAfter.get())
return@coroutineScope
// タスクによってはポーリング前にすることがある
when (taskId) {
@ -945,7 +922,7 @@ class PollingWorker private constructor(contextArg: Context) {
if (db_id != null) {
NotificationTracking.updateRead(db_id, typeName)
}
return
return@coroutineScope
}
TASK_NOTIFICATION_CLICK -> {
@ -969,42 +946,45 @@ class PollingWorker private constructor(contextArg: Context) {
// DB更新処理
NotificationTracking.updateRead(db_id, typeName)
}
return
return@coroutineScope
}
}
}
job_status.set("make install id")
workerStatus = "make install id"
// インストールIDを生成する
// インストールID生成時にSavedAccountテーブルを操作することがあるので
// アカウントリストの取得より先に行う
if (job.install_id == null) {
job.install_id = runBlocking { prepareInstallId(context, job) }
job.install_id = prepareInstallId(context, job)
}
// アカウント別に処理スレッドを作る
job_status.set("create account thread")
val thread_list = LinkedList<AccountThread>()
for (_a in SavedAccount.loadAccountList(context)) {
if (_a.isPseudo) continue
if (process_db_id != -1L && _a.db_id != process_db_id) continue
val t = AccountThread(_a)
thread_list.add(t)
t.start()
workerStatus = "create account thread"
val thread_list = LinkedList<AccountRunner>()
suspend fun startForAccount(_a: SavedAccount) {
if (_a.isPseudo) return
thread_list.add(AccountRunner(_a).apply { start() })
}
if (process_db_id != -1L) {
// process_db_id が指定されているなら、そのdb_idだけ処理する
SavedAccount.loadAccount(context, process_db_id)?.let { startForAccount(it) }
} else {
// 全てのアカウントを処理する
SavedAccount.loadAccountList(context).forEach { startForAccount(it) }
}
while (true) {
// 同じホスト名が重複しないようにSetに集める
val liveSet = TreeSet<Host>()
for (t in thread_list) {
if (!t.isAlive) continue
if (!t.isActive) continue
if (job.isJobCancelled) t.cancel()
liveSet.add(t.account.apiHost)
}
if (liveSet.isEmpty()) break
job_status.set("waiting " + liveSet.joinToString(", ") { it.pretty })
job.waitWorkerThread(if (job.isJobCancelled) 100L else 1000L)
workerStatus = "waiting ${liveSet.joinToString(", ") { it.pretty }}"
delay(if (job.isJobCancelled) 100L else 1000L)
}
synchronized(error_instance) {
@ -1017,45 +997,65 @@ class PollingWorker private constructor(contextArg: Context) {
log.trace(ex, "task execution failed.")
} finally {
log.d(")runTask: taskId=$taskId")
job_status.set("end task $taskId")
workerStatus = "end task $taskId"
}
}
}
internal inner class AccountThread(
val account: SavedAccount
) : Thread(), CurrentCallCallback {
internal inner class AccountRunner(val account: SavedAccount) {
private var current_call: Call? = null
private var suspendJob: Job? = null
private lateinit var parser: TootParser
private lateinit var cache: NotificationCache
private var currentCall: WeakReference<Call>? = null
///////////////////
val isActive: Boolean
get() = suspendJob?.isActive ?: true
private val onCallCreated: (Call) -> Unit =
{ currentCall = WeakReference(it) }
private val client = TootApiClient(context, callback = object : TootApiCallback {
override val isApiCancelled: Boolean
get() = job.isJobCancelled
})
get() = job.isJobCancelled || (suspendJob?.isCancelled == true)
}).apply {
currentCallCallback = onCallCreated
}
private val favMuteSet: HashSet<Acct> get() = job.favMuteSet
private lateinit var parser: TootParser
private lateinit var cache: NotificationCache
init {
client.currentCallCallback = this
}
override fun onCallCreated(call: Call) {
current_call = call
}
fun cancel() {
try {
current_call?.cancel()
currentCall?.get()?.cancel()
} catch (ex: Throwable) {
log.trace(ex)
}
}
override fun run() = runBlocking { runSuspend() }
private suspend fun runSuspend(){
suspend fun start() {
coroutineScope {
this@AccountRunner.suspendJob = launch(Dispatchers.IO) {
runSuspend()
}
}
}
private val onError: (TootApiResult) -> Unit = { result ->
val sv = result.error
if (sv?.contains("Timeout") == true && !account.dont_show_timeout) {
synchronized(error_instance) {
if (!error_instance.any { it == sv }) error_instance.add(sv)
}
}
}
private suspend fun runSuspend() {
try {
// 疑似アカウントはチェック対象外
if (account.isPseudo) return
@ -1099,32 +1099,15 @@ class PollingWorker private constructor(contextArg: Context) {
this.cache = NotificationCache(account.db_id).apply {
load()
request(
requestAsync(
client,
account,
wps.flags,
onError = { result ->
val sv = result.error
if (sv?.contains("Timeout") == true && !account.dont_show_timeout) {
synchronized(error_instance) {
var bFound = false
for (x in error_instance) {
if (x == sv) {
bFound = true
break
}
}
if (!bFound) {
error_instance.add(sv)
}
}
}
},
isCancelled = {
job.isJobCancelled
}
onError = onError,
isCancelled = { job.isJobCancelled }
)
}
if (job.isJobCancelled) return
this.parser = TootParser(context, account)
@ -1159,7 +1142,7 @@ class PollingWorker private constructor(contextArg: Context) {
} catch (ex: Throwable) {
log.trace(ex)
} finally {
job.notifyWorkerThread()
workerNotifier.send(Unit)
}
}
@ -1244,6 +1227,7 @@ class PollingWorker private constructor(contextArg: Context) {
internal fun updateNotification() {
val notification_tag = when (trackingName) {
"" -> "${account.db_id}/_"
else -> "${account.db_id}/$trackingName"
@ -1548,7 +1532,9 @@ class PollingWorker private constructor(contextArg: Context) {
}
}
private fun getNotificationLine(item: Data): String {
val name = when (Pref.bpShowAcctInSystemNotification(pref)) {
false -> item.notification.accountRef?.decoded_display_name
@ -1561,48 +1547,49 @@ class PollingWorker private constructor(contextArg: Context) {
}
}
} ?: "?"
return when (item.notification.type) {
return "- " + when (item.notification.type) {
TootNotification.TYPE_MENTION,
TootNotification.TYPE_REPLY ->
"- " + context.getString(R.string.display_name_replied_by, name)
context.getString(R.string.display_name_replied_by, name)
TootNotification.TYPE_RENOTE,
TootNotification.TYPE_REBLOG ->
"- " + context.getString(R.string.display_name_boosted_by, name)
context.getString(R.string.display_name_boosted_by, name)
TootNotification.TYPE_QUOTE ->
"- " + context.getString(R.string.display_name_quoted_by, name)
context.getString(R.string.display_name_quoted_by, name)
TootNotification.TYPE_STATUS ->
"- " + context.getString(R.string.display_name_posted_by, name)
context.getString(R.string.display_name_posted_by, name)
TootNotification.TYPE_FOLLOW ->
"- " + context.getString(R.string.display_name_followed_by, name)
context.getString(R.string.display_name_followed_by, name)
TootNotification.TYPE_UNFOLLOW ->
"- " + context.getString(R.string.display_name_unfollowed_by, name)
context.getString(R.string.display_name_unfollowed_by, name)
TootNotification.TYPE_FAVOURITE ->
"- " + context.getString(R.string.display_name_favourited_by, name)
context.getString(R.string.display_name_favourited_by, name)
TootNotification.TYPE_REACTION ->
"- " + context.getString(R.string.display_name_reaction_by, name)
context.getString(R.string.display_name_reaction_by, name)
TootNotification.TYPE_VOTE,
TootNotification.TYPE_POLL_VOTE_MISSKEY ->
"- " + context.getString(R.string.display_name_voted_by, name)
context.getString(R.string.display_name_voted_by, name)
TootNotification.TYPE_FOLLOW_REQUEST,
TootNotification.TYPE_FOLLOW_REQUEST_MISSKEY ->
"- " + context.getString(R.string.display_name_follow_request_by, name)
context.getString(R.string.display_name_follow_request_by, name)
TootNotification.TYPE_FOLLOW_REQUEST_ACCEPTED_MISSKEY ->
"- " + context.getString(R.string.display_name_follow_request_accepted_by, name)
context.getString(R.string.display_name_follow_request_accepted_by, name)
TootNotification.TYPE_POLL ->
"- " + context.getString(R.string.end_of_polling_from, name)
context.getString(R.string.end_of_polling_from, name)
else -> "- " + "?"
else -> "?"
}
}

View File

@ -1,7 +1,6 @@
package jp.juggler.subwaytooter.api
import android.content.Context
import android.content.SharedPreferences
import jp.juggler.subwaytooter.*
import jp.juggler.subwaytooter.api.entity.*
import jp.juggler.subwaytooter.table.ClientInfo
@ -13,36 +12,10 @@ import java.util.*
class TootApiClient(
internal val context: Context,
internal val httpClient: SimpleHttpClient = SimpleHttpClientImpl(
context,
App1.ok_http_client
),
internal val httpClient: SimpleHttpClient =
SimpleHttpClientImpl(context,App1.ok_http_client),
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 {
private val log = LogCategory("TootApiClient")
@ -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")
internal val isApiCancelled: Boolean
get() = callback.isApiCancelled
@ -277,6 +269,46 @@ class TootApiClient(
}
}
// リクエストを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
}
}
// レスポンスがエラーかボディがカラならエラー状態を設定する
// 例外を出すかも
internal fun readBodyString(
@ -380,7 +412,6 @@ class TootApiClient(
try {
readBodyBytes(result, progressPath, jsonErrorParser)
?: return if (isApiCancelled) null else result
} catch (ex: Throwable) {
log.trace(ex)
result.error =
@ -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
@ -1166,10 +1230,9 @@ class TootApiClient(
////////////////////////////////////////////////////////////////////////
// JSONデータ以外を扱うリクエスト
fun http(req: Request): TootApiResult? {
fun http(req: Request): TootApiResult {
val result = TootApiResult.makeWithCaption(req.url.host)
if (result.error != null) return result
sendRequest(result, progressPath = null) { req }
return result
}
@ -1184,12 +1247,9 @@ class TootApiClient(
// }
// 疑似アカウントでステータスURLからステータスIDを取得するためにHTMLを取得する
fun getHttp(url: String): TootApiResult? {
fun getHttp(url: String):TootApiResult?{
val result = http(Request.Builder().url(url).build())
if (result != null && result.error == null) {
parseString(result)
}
return result
return if (result.error != null) result else parseString(result)
}
fun getHttpBytes(url: String): Pair<TootApiResult?, ByteArray?> {

View File

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

View File

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

View File

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