通知チェック部分のコードをコルーチン対応にした。AppOpenerはいくつかのOSアクティビティを選択肢から除去するようになった。
This commit is contained in:
parent
301dde36c0
commit
8818f25b6f
|
@ -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
|
@ -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))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 -> "?"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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?> {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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を返すなら、それがベストなんだろう
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue