2018-01-04 19:52:25 +01:00
|
|
|
package jp.juggler.subwaytooter
|
|
|
|
|
|
|
|
import android.annotation.SuppressLint
|
|
|
|
import android.app.NotificationManager
|
|
|
|
import android.app.PendingIntent
|
|
|
|
import android.app.job.JobInfo
|
|
|
|
import android.app.job.JobParameters
|
|
|
|
import android.app.job.JobScheduler
|
|
|
|
import android.app.job.JobService
|
|
|
|
import android.content.ComponentName
|
|
|
|
import android.content.Context
|
|
|
|
import android.content.Intent
|
|
|
|
import android.content.SharedPreferences
|
|
|
|
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
|
2019-02-15 02:51:22 +01:00
|
|
|
import androidx.core.app.NotificationCompat
|
|
|
|
import androidx.core.content.ContextCompat
|
2018-01-04 19:52:25 +01:00
|
|
|
import com.google.firebase.iid.FirebaseInstanceId
|
2018-12-06 23:43:13 +01:00
|
|
|
import jp.juggler.subwaytooter.api.TootApiCallback
|
|
|
|
import jp.juggler.subwaytooter.api.TootApiClient
|
|
|
|
import jp.juggler.subwaytooter.api.TootParser
|
2018-08-18 12:58:14 +02:00
|
|
|
import jp.juggler.subwaytooter.api.entity.EntityId
|
2018-12-06 23:43:13 +01:00
|
|
|
import jp.juggler.subwaytooter.api.entity.TootNotification
|
2018-08-30 08:51:32 +02:00
|
|
|
import jp.juggler.subwaytooter.api.entity.TootStatus
|
2018-03-15 17:23:43 +01:00
|
|
|
import jp.juggler.subwaytooter.table.*
|
2018-01-12 10:01:25 +01:00
|
|
|
import jp.juggler.subwaytooter.util.*
|
2018-12-01 00:02:18 +01:00
|
|
|
import jp.juggler.util.*
|
2018-01-04 19:52:25 +01:00
|
|
|
import okhttp3.Call
|
|
|
|
import okhttp3.Request
|
2018-12-06 23:43:13 +01:00
|
|
|
import org.json.JSONArray
|
|
|
|
import org.json.JSONException
|
|
|
|
import org.json.JSONObject
|
|
|
|
import java.lang.ref.WeakReference
|
|
|
|
import java.util.*
|
|
|
|
import java.util.concurrent.ConcurrentLinkedQueue
|
|
|
|
import java.util.concurrent.atomic.AtomicBoolean
|
|
|
|
import java.util.concurrent.atomic.AtomicReference
|
2018-01-04 19:52:25 +01:00
|
|
|
|
2018-09-17 20:06:15 +02:00
|
|
|
class PollingWorker private constructor(contextArg : Context) {
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
interface JobStatusCallback {
|
|
|
|
fun onStatus(sv : String)
|
|
|
|
}
|
|
|
|
|
|
|
|
companion object {
|
|
|
|
internal val log = LogCategory("PollingWorker")
|
|
|
|
|
2018-09-17 20:06:15 +02:00
|
|
|
private const val FCM_SENDER_ID = "433682361381"
|
2018-08-08 22:08:15 +02:00
|
|
|
private const val FCM_SCOPE = "FCM"
|
2018-09-17 20:06:15 +02:00
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
const val NOTIFICATION_ID = 1
|
|
|
|
const val NOTIFICATION_ID_ERROR = 3
|
|
|
|
|
|
|
|
// Notification のJSONObject を日時でソートするためにデータを追加する
|
|
|
|
const val KEY_TIME = "<>time"
|
|
|
|
|
|
|
|
val mBusyAppDataImportBefore = AtomicBoolean(false)
|
|
|
|
val mBusyAppDataImportAfter = AtomicBoolean(false)
|
|
|
|
|
|
|
|
const val EXTRA_DB_ID = "db_id"
|
|
|
|
const val EXTRA_TAG = "tag"
|
|
|
|
const val EXTRA_TASK_ID = "task_id"
|
|
|
|
|
|
|
|
const val APP_SERVER = "https://mastodon-msg.juggler.jp"
|
|
|
|
|
|
|
|
const val PATH_NOTIFICATIONS = "/api/v1/notifications"
|
|
|
|
|
|
|
|
internal val inject_queue = ConcurrentLinkedQueue<InjectData>()
|
|
|
|
|
|
|
|
// ジョブID
|
|
|
|
const val JOB_POLLING = 1
|
|
|
|
private const val JOB_TASK = 2
|
|
|
|
const val JOB_FCM = 3
|
|
|
|
|
|
|
|
// タスクID
|
|
|
|
const val TASK_POLLING = 1
|
|
|
|
const val TASK_DATA_INJECTED = 2
|
|
|
|
const val TASK_NOTIFICATION_CLEAR = 3
|
|
|
|
const val TASK_APP_DATA_IMPORT_BEFORE = 4
|
|
|
|
const val TASK_APP_DATA_IMPORT_AFTER = 5
|
|
|
|
const val TASK_FCM_DEVICE_TOKEN = 6
|
|
|
|
const val TASK_FCM_MESSAGE = 7
|
|
|
|
const val TASK_BOOT_COMPLETED = 8
|
|
|
|
const val TASK_PACKAGE_REPLACED = 9
|
|
|
|
const val TASK_NOTIFICATION_DELETE = 10
|
|
|
|
const val TASK_NOTIFICATION_CLICK = 11
|
|
|
|
private const val TASK_UPDATE_NOTIFICATION = 12
|
|
|
|
|
|
|
|
@SuppressLint("StaticFieldLeak")
|
2018-01-10 16:47:35 +01:00
|
|
|
private var sInstance : PollingWorker? = null
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
fun getInstance(applicationContext : Context) : PollingWorker {
|
2018-01-10 16:47:35 +01:00
|
|
|
var s = sInstance
|
2018-01-13 16:24:51 +01:00
|
|
|
if(s == null) {
|
2018-01-10 16:47:35 +01:00
|
|
|
s = PollingWorker(applicationContext)
|
|
|
|
sInstance = s
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
2018-01-10 16:47:35 +01:00
|
|
|
return s
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
|
2018-05-12 10:17:12 +02:00
|
|
|
fun getDeviceId(context : Context) : String? {
|
2018-08-08 22:08:15 +02:00
|
|
|
// 設定ファイルに保持されていたらそれを使う
|
2018-05-12 10:17:12 +02:00
|
|
|
val prefDevice = PrefDevice.prefDevice(context)
|
|
|
|
var device_token = prefDevice.getString(PrefDevice.KEY_DEVICE_TOKEN, null)
|
|
|
|
if(device_token?.isNotEmpty() == true) return device_token
|
|
|
|
|
|
|
|
try {
|
2018-08-08 22:08:15 +02:00
|
|
|
// FirebaseのAPIから取得する
|
2018-09-17 20:06:15 +02:00
|
|
|
device_token = FirebaseInstanceId.getInstance().getToken(FCM_SENDER_ID, FCM_SCOPE)
|
2018-05-12 10:17:12 +02:00
|
|
|
if(device_token?.isNotEmpty() == true) {
|
2018-08-08 22:08:15 +02:00
|
|
|
prefDevice
|
|
|
|
.edit()
|
|
|
|
.putString(PrefDevice.KEY_DEVICE_TOKEN, device_token)
|
2018-05-12 10:17:12 +02:00
|
|
|
.apply()
|
|
|
|
return device_token
|
|
|
|
}
|
|
|
|
log.e("getDeviceId: missing device token.")
|
|
|
|
return null
|
|
|
|
} catch(ex : Throwable) {
|
2018-09-17 20:06:15 +02:00
|
|
|
log.trace(ex, "getDeviceId: could not get device token.")
|
2018-05-12 10:17:12 +02:00
|
|
|
return null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// インストールIDを生成する前に、各データの通知登録キャッシュをクリアする
|
|
|
|
// トークンがまだ生成されていない場合、このメソッドは null を返します。
|
|
|
|
fun prepareInstallId(
|
|
|
|
context : Context,
|
|
|
|
job : JobItem? = null
|
|
|
|
) : String? {
|
|
|
|
val prefDevice = PrefDevice.prefDevice(context)
|
|
|
|
|
|
|
|
var sv = prefDevice.getString(PrefDevice.KEY_INSTALL_ID, null)
|
|
|
|
if(sv?.isNotEmpty() == true) return sv
|
2018-09-17 20:06:15 +02:00
|
|
|
|
2018-05-12 10:17:12 +02:00
|
|
|
SavedAccount.clearRegistrationCache()
|
|
|
|
|
|
|
|
try {
|
|
|
|
var device_token = prefDevice.getString(PrefDevice.KEY_DEVICE_TOKEN, null)
|
2018-09-17 20:06:15 +02:00
|
|
|
if(device_token?.isEmpty() != false) {
|
2018-05-12 10:17:12 +02:00
|
|
|
try {
|
2018-09-17 20:06:15 +02:00
|
|
|
device_token =
|
|
|
|
FirebaseInstanceId.getInstance().getToken(FCM_SENDER_ID, FCM_SCOPE)
|
2018-05-12 10:17:12 +02:00
|
|
|
if(device_token == null || device_token.isEmpty()) {
|
|
|
|
log.e("getInstallId: missing device token.")
|
|
|
|
return null
|
|
|
|
} else {
|
2018-09-17 20:06:15 +02:00
|
|
|
prefDevice.edit().putString(PrefDevice.KEY_DEVICE_TOKEN, device_token)
|
|
|
|
.apply()
|
2018-05-12 10:17:12 +02:00
|
|
|
}
|
|
|
|
} catch(ex : Throwable) {
|
2018-09-17 20:06:15 +02:00
|
|
|
log.trace(ex, "getInstallId: could not get device token.")
|
2018-05-12 10:17:12 +02:00
|
|
|
return null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
val request = Request.Builder()
|
|
|
|
.url("$APP_SERVER/counter")
|
|
|
|
.build()
|
|
|
|
|
|
|
|
val call = App1.ok_http_client.newCall(request)
|
|
|
|
if(job != null) {
|
|
|
|
job.current_call = call
|
|
|
|
}
|
|
|
|
|
|
|
|
val response = call.execute()
|
|
|
|
val body = response.body()?.string()
|
|
|
|
|
|
|
|
if(! response.isSuccessful || body?.isEmpty() != false) {
|
|
|
|
log.e(
|
|
|
|
TootApiClient.formatResponse(
|
|
|
|
response,
|
|
|
|
"getInstallId: get /counter failed."
|
|
|
|
)
|
|
|
|
)
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
2018-05-16 21:09:07 +02:00
|
|
|
sv = (device_token + UUID.randomUUID() + body).digestSHA256Base64Url()
|
2018-05-12 10:17:12 +02:00
|
|
|
prefDevice.edit().putString(PrefDevice.KEY_INSTALL_ID, sv).apply()
|
|
|
|
|
|
|
|
return sv
|
|
|
|
|
|
|
|
} catch(ex : Throwable) {
|
2018-09-20 18:41:04 +02:00
|
|
|
log.trace(ex, "prepareInstallId failed.")
|
2018-05-12 10:17:12 +02:00
|
|
|
}
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
// タスクの管理
|
|
|
|
|
|
|
|
val task_list = TaskList()
|
|
|
|
|
|
|
|
fun scheduleJob(context : Context, job_id : Int) {
|
|
|
|
|
2018-04-10 09:08:30 +02:00
|
|
|
val scheduler = context.getSystemService(Context.JOB_SCHEDULER_SERVICE)
|
|
|
|
as? JobScheduler
|
2018-01-04 19:52:25 +01:00
|
|
|
?: throw NotImplementedError("missing JobScheduler system service")
|
|
|
|
|
|
|
|
val component = ComponentName(context, PollingService::class.java)
|
|
|
|
|
|
|
|
val builder = JobInfo.Builder(job_id, component)
|
|
|
|
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
|
|
|
|
|
|
|
|
if(job_id == JOB_POLLING) {
|
2018-04-12 02:29:07 +02:00
|
|
|
val intervalMillis =
|
2018-04-10 09:08:30 +02:00
|
|
|
60000L * Pref.spPullNotificationCheckInterval.toInt(Pref.pref(context))
|
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
if(Build.VERSION.SDK_INT >= 24) {
|
2018-04-12 02:29:07 +02:00
|
|
|
/// val minInterval = JobInfo.getMinPeriodMillis() // 15 min
|
|
|
|
/// val minFlex = JobInfo.getMinFlexMillis() // 5 min
|
|
|
|
val minInterval = 300000L // 5 min
|
|
|
|
val minFlex = 60000L // 1 min
|
|
|
|
builder.setPeriodic(
|
|
|
|
Math.max(minInterval, intervalMillis),
|
|
|
|
Math.max(minFlex, intervalMillis shr 1)
|
|
|
|
)
|
2018-01-04 19:52:25 +01:00
|
|
|
} else {
|
2018-04-12 02:29:07 +02:00
|
|
|
val minInterval = 300000L // 5 min
|
|
|
|
builder.setPeriodic(Math.max(minInterval, intervalMillis))
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
builder.setPersisted(true)
|
|
|
|
} else {
|
|
|
|
builder
|
|
|
|
.setMinimumLatency(0)
|
|
|
|
.setOverrideDeadline(60000L)
|
|
|
|
}
|
2018-04-10 09:08:30 +02:00
|
|
|
val jobInfo = builder.build()
|
|
|
|
scheduler.schedule(jobInfo)
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// タスクの追加
|
2018-01-21 13:46:36 +01:00
|
|
|
private fun addTask(
|
|
|
|
context : Context,
|
|
|
|
removeOld : Boolean,
|
|
|
|
task_id : Int,
|
|
|
|
taskDataArg : JSONObject?
|
|
|
|
) {
|
2018-01-04 19:52:25 +01:00
|
|
|
try {
|
|
|
|
val taskData = taskDataArg ?: JSONObject()
|
|
|
|
taskData.put(EXTRA_TASK_ID, task_id)
|
|
|
|
task_list.addLast(context, removeOld, taskData)
|
|
|
|
scheduleJob(context, JOB_TASK)
|
|
|
|
} catch(ex : Throwable) {
|
|
|
|
log.trace(ex)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
fun queueUpdateNotification(context : Context) {
|
|
|
|
addTask(context, true, TASK_UPDATE_NOTIFICATION, null)
|
|
|
|
}
|
|
|
|
|
2018-09-17 20:06:15 +02:00
|
|
|
fun injectData(
|
|
|
|
context : Context,
|
|
|
|
account : SavedAccount,
|
|
|
|
src : ArrayList<TootNotification>
|
|
|
|
) {
|
2018-08-25 02:54:17 +02:00
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
if(src.isEmpty()) return
|
|
|
|
|
|
|
|
val id = InjectData()
|
2018-08-25 02:54:17 +02:00
|
|
|
id.account_db_id = account.db_id
|
2018-01-04 19:52:25 +01:00
|
|
|
id.list.addAll(src)
|
|
|
|
inject_queue.add(id)
|
|
|
|
|
|
|
|
addTask(context, true, TASK_DATA_INJECTED, null)
|
|
|
|
}
|
|
|
|
|
|
|
|
fun queueNotificationCleared(context : Context, db_id : Long) {
|
|
|
|
try {
|
|
|
|
val data = JSONObject()
|
|
|
|
data.putOpt(EXTRA_DB_ID, db_id)
|
|
|
|
addTask(context, true, TASK_NOTIFICATION_CLEAR, data)
|
|
|
|
} catch(ex : JSONException) {
|
|
|
|
log.trace(ex)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
fun queueNotificationDeleted(context : Context, db_id : Long) {
|
|
|
|
try {
|
|
|
|
val data = JSONObject()
|
|
|
|
data.putOpt(EXTRA_DB_ID, db_id)
|
|
|
|
addTask(context, true, TASK_NOTIFICATION_DELETE, data)
|
|
|
|
} catch(ex : JSONException) {
|
|
|
|
log.trace(ex)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
fun queueNotificationClicked(context : Context, db_id : Long) {
|
|
|
|
try {
|
|
|
|
val data = JSONObject()
|
|
|
|
data.putOpt(EXTRA_DB_ID, db_id)
|
|
|
|
addTask(context, true, TASK_NOTIFICATION_CLICK, data)
|
|
|
|
} catch(ex : JSONException) {
|
|
|
|
log.trace(ex)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
fun queueAppDataImportBefore(context : Context) {
|
|
|
|
mBusyAppDataImportBefore.set(true)
|
|
|
|
mBusyAppDataImportAfter.set(true)
|
|
|
|
addTask(context, false, TASK_APP_DATA_IMPORT_BEFORE, null)
|
|
|
|
}
|
|
|
|
|
|
|
|
fun queueAppDataImportAfter(context : Context) {
|
|
|
|
addTask(context, false, TASK_APP_DATA_IMPORT_AFTER, null)
|
|
|
|
}
|
|
|
|
|
|
|
|
fun queueFCMTokenUpdated(context : Context) {
|
|
|
|
addTask(context, true, TASK_FCM_DEVICE_TOKEN, null)
|
|
|
|
}
|
|
|
|
|
|
|
|
fun queueBootCompleted(context : Context) {
|
|
|
|
addTask(context, true, TASK_BOOT_COMPLETED, null)
|
|
|
|
}
|
|
|
|
|
|
|
|
fun queuePackageReplaced(context : Context) {
|
|
|
|
addTask(context, true, TASK_PACKAGE_REPLACED, null)
|
|
|
|
}
|
|
|
|
|
|
|
|
internal val job_status = AtomicReference<String>(null)
|
|
|
|
|
|
|
|
fun handleFCMMessage(context : Context, tag : String?, callback : JobStatusCallback) {
|
2018-04-10 09:08:30 +02:00
|
|
|
log.d("handleFCMMessage: start. tag=$tag")
|
2018-01-04 19:52:25 +01:00
|
|
|
val time_start = SystemClock.elapsedRealtime()
|
|
|
|
|
|
|
|
callback.onStatus("=>")
|
|
|
|
|
|
|
|
// タスクを追加
|
|
|
|
val data = JSONObject()
|
|
|
|
try {
|
|
|
|
if(tag != null) data.putOpt(EXTRA_TAG, tag)
|
|
|
|
data.put(EXTRA_TASK_ID, TASK_FCM_MESSAGE)
|
|
|
|
} catch(ignored : JSONException) {
|
|
|
|
}
|
|
|
|
|
|
|
|
task_list.addLast(context, true, data)
|
|
|
|
|
|
|
|
callback.onStatus("==>")
|
|
|
|
|
|
|
|
// 疑似ジョブを開始
|
|
|
|
val pw = getInstance(context)
|
|
|
|
pw.addJob(JOB_FCM, false)
|
|
|
|
|
|
|
|
// 疑似ジョブが終了するまで待機する
|
|
|
|
while(true) {
|
|
|
|
// ジョブが完了した?
|
|
|
|
val now = SystemClock.elapsedRealtime()
|
|
|
|
if(! pw.hasJob(JOB_FCM)) {
|
2018-01-21 13:46:36 +01:00
|
|
|
log.d(
|
|
|
|
"handleFCMMessage: JOB_FCM completed. time=%.2f",
|
|
|
|
(now - time_start) / 1000f
|
|
|
|
)
|
2018-01-04 19:52:25 +01:00
|
|
|
break
|
|
|
|
}
|
|
|
|
// ジョブの状況を通知する
|
|
|
|
var sv : String? = job_status.get()
|
|
|
|
if(sv == null) sv = "(null)"
|
|
|
|
callback.onStatus(sv)
|
|
|
|
|
|
|
|
// 少し待機
|
|
|
|
try {
|
|
|
|
Thread.sleep(50L)
|
|
|
|
} catch(ex : InterruptedException) {
|
|
|
|
log.e(ex, "handleFCMMessage: blocking is interrupted.")
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
2018-08-30 08:51:32 +02:00
|
|
|
|
2018-09-17 20:06:15 +02:00
|
|
|
private fun EntityId.isLatestThan(previous : EntityId?) = when(previous) {
|
2018-08-30 08:51:32 +02:00
|
|
|
null -> true
|
|
|
|
else -> this > previous
|
|
|
|
}
|
|
|
|
|
2019-01-28 19:02:09 +01:00
|
|
|
private fun getEntityOrderId(account : SavedAccount, src : JSONObject) : EntityId =
|
|
|
|
if( account.isMisskey){
|
|
|
|
when(val created_at = src.parseString("createdAt")) {
|
2019-01-29 02:56:24 +01:00
|
|
|
null -> EntityId.DEFAULT
|
|
|
|
else -> EntityId(TootStatus.parseTime(created_at).toString())
|
2018-08-30 08:51:32 +02:00
|
|
|
}
|
2019-01-28 19:02:09 +01:00
|
|
|
}else{
|
|
|
|
EntityId.mayDefault(src.parseString("id"))
|
2018-08-30 08:51:32 +02:00
|
|
|
}
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
internal val context : Context
|
2018-09-17 20:06:15 +02:00
|
|
|
private val appState : AppState
|
2018-01-04 19:52:25 +01:00
|
|
|
internal val handler : Handler
|
|
|
|
internal val pref : SharedPreferences
|
2018-12-06 23:43:13 +01:00
|
|
|
private val connectivityManager : ConnectivityManager
|
2018-01-04 19:52:25 +01:00
|
|
|
internal val notification_manager : NotificationManager
|
|
|
|
internal val scheduler : JobScheduler
|
|
|
|
private val power_manager : PowerManager?
|
|
|
|
internal val power_lock : PowerManager.WakeLock
|
|
|
|
private val wifi_manager : WifiManager?
|
|
|
|
internal val wifi_lock : WifiManager.WifiLock
|
|
|
|
|
|
|
|
private var worker : Worker
|
|
|
|
|
|
|
|
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
|
2018-01-10 16:47:35 +01:00
|
|
|
val list = ArrayList<TootNotification>()
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
init {
|
|
|
|
log.d("ctor")
|
|
|
|
|
2018-09-17 20:06:15 +02:00
|
|
|
val context = contextArg.applicationContext
|
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
this.context = context
|
|
|
|
|
2018-01-10 16:47:35 +01:00
|
|
|
this.connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
|
2018-09-17 20:06:15 +02:00
|
|
|
?: error("missing ConnectivityManager system service")
|
2018-01-04 19:52:25 +01:00
|
|
|
|
2018-01-10 16:47:35 +01:00
|
|
|
this.notification_manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager
|
2018-09-17 20:06:15 +02:00
|
|
|
?: error("missing NotificationManager system service")
|
2018-01-04 19:52:25 +01:00
|
|
|
|
2018-01-10 16:47:35 +01:00
|
|
|
this.scheduler = context.getSystemService(Context.JOB_SCHEDULER_SERVICE) as? JobScheduler
|
2018-09-17 20:06:15 +02:00
|
|
|
?: error("missing JobScheduler system service")
|
2018-01-04 19:52:25 +01:00
|
|
|
|
2018-01-10 16:47:35 +01:00
|
|
|
this.power_manager = context.getSystemService(Context.POWER_SERVICE) as? PowerManager
|
2018-09-17 20:06:15 +02:00
|
|
|
?: error("missing PowerManager system service")
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
// WifiManagerの取得時はgetApplicationContext を使わないとlintに怒られる
|
2018-01-10 16:47:35 +01:00
|
|
|
this.wifi_manager = context.applicationContext.getSystemService(Context.WIFI_SERVICE) as? WifiManager
|
2018-09-17 20:06:15 +02:00
|
|
|
?: error("missing WifiManager system service")
|
2018-01-04 19:52:25 +01:00
|
|
|
|
2018-01-21 13:46:36 +01:00
|
|
|
power_lock = power_manager.newWakeLock(
|
|
|
|
PowerManager.PARTIAL_WAKE_LOCK,
|
|
|
|
PollingWorker::class.java.name
|
|
|
|
)
|
2018-01-04 19:52:25 +01:00
|
|
|
power_lock.setReferenceCounted(false)
|
|
|
|
|
|
|
|
wifi_lock = wifi_manager.createWifiLock(PollingWorker::class.java.name)
|
|
|
|
wifi_lock.setReferenceCounted(false)
|
|
|
|
|
|
|
|
// クラッシュレポートによると App1.onCreate より前にここを通る場合がある
|
|
|
|
// データベースへアクセスできるようにする
|
2018-09-17 20:06:15 +02:00
|
|
|
this.appState = App1.prepare(context)
|
2018-01-04 19:52:25 +01:00
|
|
|
this.pref = App1.pref
|
|
|
|
this.handler = Handler(context.mainLooper)
|
|
|
|
|
|
|
|
//
|
|
|
|
worker = Worker()
|
|
|
|
worker.start()
|
|
|
|
}
|
|
|
|
|
2018-05-12 10:17:12 +02:00
|
|
|
inner class Worker : WorkerBase() {
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
val bThreadCancelled = AtomicBoolean(false)
|
|
|
|
|
|
|
|
override fun cancel() {
|
|
|
|
bThreadCancelled.set(true)
|
|
|
|
notifyEx()
|
|
|
|
}
|
|
|
|
|
|
|
|
@SuppressLint("WakelockTimeout")
|
|
|
|
private fun acquirePowerLock() {
|
|
|
|
log.d("acquire power lock...")
|
|
|
|
try {
|
|
|
|
if(! power_lock.isHeld) {
|
|
|
|
power_lock.acquire()
|
|
|
|
}
|
|
|
|
} catch(ex : Throwable) {
|
|
|
|
log.trace(ex)
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
if(! wifi_lock.isHeld) {
|
|
|
|
wifi_lock.acquire()
|
|
|
|
}
|
|
|
|
} catch(ex : Throwable) {
|
|
|
|
log.trace(ex)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun releasePowerLock() {
|
|
|
|
log.d("release power lock...")
|
|
|
|
try {
|
|
|
|
if(power_lock.isHeld) {
|
|
|
|
power_lock.release()
|
|
|
|
}
|
|
|
|
} catch(ex : Throwable) {
|
|
|
|
log.trace(ex)
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
if(wifi_lock.isHeld) {
|
|
|
|
wifi_lock.release()
|
|
|
|
}
|
|
|
|
} catch(ex : Throwable) {
|
|
|
|
log.trace(ex)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun run() {
|
2018-08-28 14:56:09 +02:00
|
|
|
log.d("worker thread start.")
|
2018-01-04 19:52:25 +01:00
|
|
|
job_status.set("worker thread start.")
|
|
|
|
while(! bThreadCancelled.get()) {
|
|
|
|
try {
|
|
|
|
val item : JobItem? = 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
|
|
|
|
}
|
|
|
|
|
|
|
|
if(item == null) {
|
|
|
|
job_status.set("no job to run.")
|
|
|
|
waitEx(86400000L)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
job_status.set("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.")
|
2018-08-28 14:56:09 +02:00
|
|
|
log.d("worker thread end.")
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
// ジョブの管理
|
|
|
|
|
|
|
|
// JobService#onDestroy から呼ばれる
|
|
|
|
fun onJobServiceDestroy() {
|
|
|
|
log.d("onJobServiceDestroy")
|
|
|
|
|
|
|
|
synchronized(job_list) {
|
|
|
|
val it = job_list.iterator()
|
|
|
|
while(it.hasNext()) {
|
|
|
|
val item = it.next()
|
|
|
|
if(item.jobId != JOB_FCM) {
|
|
|
|
it.remove()
|
|
|
|
item.cancel(false)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// JobService#onStartJob から呼ばれる
|
|
|
|
fun onStartJob(jobService : JobService, params : JobParameters) : Boolean {
|
|
|
|
val item = JobItem(jobService, params)
|
|
|
|
addJob(item, true)
|
|
|
|
return true
|
|
|
|
// return True if your context needs to process the work (on a separate thread).
|
|
|
|
// return False if there's no more work to be done for this job.
|
|
|
|
}
|
|
|
|
|
|
|
|
// FCMメッセージイベントから呼ばれる
|
|
|
|
private fun hasJob(jobId : Int) : Boolean {
|
|
|
|
synchronized(job_list) {
|
|
|
|
for(item in job_list) {
|
|
|
|
if(item.jobId == jobId) return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// FCMメッセージイベントから呼ばれる
|
|
|
|
private fun addJob(jobId : Int, bRemoveOld : Boolean) {
|
|
|
|
addJob(JobItem(jobId), bRemoveOld)
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun addJob(item : JobItem, bRemoveOld : Boolean) {
|
|
|
|
val jobId = item.jobId
|
|
|
|
|
|
|
|
// 同じジョブ番号がジョブリストにあるか?
|
|
|
|
synchronized(job_list) {
|
|
|
|
if(bRemoveOld) {
|
|
|
|
val it = job_list.iterator()
|
|
|
|
while(it.hasNext()) {
|
|
|
|
val itemOld = it.next()
|
|
|
|
if(itemOld.jobId == jobId) {
|
2018-04-10 09:08:30 +02:00
|
|
|
log.w("addJob: jobId=$jobId, old job cancelled.")
|
2018-01-04 19:52:25 +01:00
|
|
|
// 同じジョブをすぐに始めるのだからrescheduleはfalse
|
|
|
|
itemOld.cancel(false)
|
|
|
|
it.remove()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-04-10 09:08:30 +02:00
|
|
|
log.d("addJob: jobId=$jobId, add to list.")
|
2018-01-04 19:52:25 +01:00
|
|
|
job_list.add(item)
|
|
|
|
}
|
|
|
|
|
|
|
|
worker.notifyEx()
|
|
|
|
}
|
|
|
|
|
|
|
|
// JobService#onStopJob から呼ばれる
|
|
|
|
fun onStopJob(params : JobParameters) : Boolean {
|
|
|
|
val jobId = params.jobId
|
|
|
|
|
|
|
|
// 同じジョブ番号がジョブリストにあるか?
|
|
|
|
synchronized(job_list) {
|
|
|
|
val it = job_list.iterator()
|
|
|
|
while(it.hasNext()) {
|
|
|
|
val item = it.next()
|
|
|
|
if(item.jobId == jobId) {
|
2018-04-10 09:08:30 +02:00
|
|
|
log.w("onStopJob: jobId=${jobId}, set cancel flag.")
|
2018-01-04 19:52:25 +01:00
|
|
|
// リソースがなくてStopされるのだからrescheduleはtrue
|
|
|
|
item.cancel(true)
|
|
|
|
it.remove()
|
|
|
|
return item.mReschedule.get()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 該当するジョブを依頼されていない
|
2018-04-10 09:08:30 +02:00
|
|
|
log.w("onStopJob: jobId=${jobId}, not started..")
|
2018-01-04 19:52:25 +01:00
|
|
|
return false
|
|
|
|
// return True to indicate to the JobManager whether you'd like to reschedule this job based on the retry criteria provided at job creation-time.
|
|
|
|
// return False to drop the job. Regardless of the value returned, your job must stop executing.
|
|
|
|
}
|
|
|
|
|
|
|
|
internal class JobCancelledException : RuntimeException("job is cancelled.")
|
|
|
|
|
2018-05-12 10:17:12 +02:00
|
|
|
inner class JobItem {
|
2018-01-04 19:52:25 +01:00
|
|
|
val jobId : Int
|
|
|
|
private val refJobService : WeakReference<JobService>?
|
|
|
|
private val jobParams : JobParameters?
|
|
|
|
val mJobCancelled_ = AtomicBoolean()
|
|
|
|
val mReschedule = AtomicBoolean()
|
|
|
|
val mWorkerAttached = AtomicBoolean()
|
|
|
|
|
|
|
|
val bPollingRequired = AtomicBoolean(false)
|
|
|
|
lateinit var muted_app : HashSet<String>
|
|
|
|
lateinit var muted_word : WordTrieTree
|
2018-03-15 17:23:43 +01:00
|
|
|
lateinit var favMuteSet : HashSet<String>
|
2018-01-04 19:52:25 +01:00
|
|
|
var bPollingComplete = false
|
|
|
|
var install_id : String? = null
|
|
|
|
|
|
|
|
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()
|
|
|
|
}
|
|
|
|
|
|
|
|
constructor(jobService : JobService, params : JobParameters) {
|
|
|
|
this.jobParams = params
|
|
|
|
this.jobId = params.jobId
|
|
|
|
this.refJobService = WeakReference(jobService)
|
|
|
|
}
|
|
|
|
|
|
|
|
constructor(jobId : Int) {
|
|
|
|
this.jobId = jobId
|
|
|
|
this.jobParams = null
|
|
|
|
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()
|
|
|
|
}
|
|
|
|
|
|
|
|
fun run() {
|
|
|
|
|
|
|
|
job_status.set("job start.")
|
|
|
|
try {
|
2018-04-10 09:08:30 +02:00
|
|
|
log.d("(JobItem.run jobId=${jobId}")
|
2018-01-04 19:52:25 +01:00
|
|
|
if(isJobCancelled) throw JobCancelledException()
|
|
|
|
|
|
|
|
job_status.set("check network status..")
|
|
|
|
|
|
|
|
val net_wait_start = SystemClock.elapsedRealtime()
|
|
|
|
while(! checkNetwork()) {
|
|
|
|
if(isJobCancelled) throw JobCancelledException()
|
|
|
|
val now = SystemClock.elapsedRealtime()
|
|
|
|
val delta = now - net_wait_start
|
|
|
|
if(delta >= 10000L) {
|
|
|
|
log.d("network state timeout.")
|
|
|
|
break
|
|
|
|
}
|
|
|
|
waitWorkerThread(333L)
|
|
|
|
}
|
|
|
|
|
|
|
|
muted_app = MutedApp.nameSet
|
|
|
|
muted_word = MutedWord.nameSet
|
2018-03-15 17:23:43 +01:00
|
|
|
favMuteSet = FavMute.acctSet
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
// タスクがあれば処理する
|
|
|
|
while(true) {
|
|
|
|
if(isJobCancelled) throw JobCancelledException()
|
|
|
|
val data = task_list.next(context) ?: break
|
|
|
|
val task_id = data.optInt(EXTRA_TASK_ID, 0)
|
|
|
|
TaskRunner().runTask(this@JobItem, task_id, data)
|
|
|
|
}
|
|
|
|
|
|
|
|
if(! isJobCancelled && ! bPollingComplete && jobId == JOB_POLLING) {
|
|
|
|
// タスクがなかった場合でも定期実行ジョブからの実行ならポーリングを行う
|
2018-01-13 07:15:52 +01:00
|
|
|
TaskRunner().runTask(this@JobItem, TASK_POLLING, JSONObject())
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
job_status.set("make next schedule.")
|
|
|
|
|
2018-04-10 09:08:30 +02:00
|
|
|
log.d("pollingComplete=${bPollingComplete},isJobCancelled=${isJobCancelled},bPollingRequired=${bPollingRequired.get()}")
|
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
if(! isJobCancelled && bPollingComplete) {
|
2018-04-10 09:08:30 +02:00
|
|
|
// ポーリングが完了した
|
2018-01-04 19:52:25 +01:00
|
|
|
if(! bPollingRequired.get()) {
|
2018-04-10 09:08:30 +02:00
|
|
|
// Pull通知を必要とするアカウントが存在しないなら、スケジュール登録を解除する
|
2018-01-04 19:52:25 +01:00
|
|
|
log.d("polling job is no longer required.")
|
|
|
|
try {
|
|
|
|
scheduler.cancel(JOB_POLLING)
|
|
|
|
} catch(ex : Throwable) {
|
|
|
|
log.trace(ex)
|
|
|
|
}
|
2018-04-10 09:08:30 +02:00
|
|
|
} else if(! scheduler.allPendingJobs.any { it.id == JOB_POLLING }) {
|
|
|
|
// まだスケジュールされてないなら登録する
|
|
|
|
log.d("registering polling job…")
|
|
|
|
scheduleJob(context, JOB_POLLING)
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch(ex : JobCancelledException) {
|
|
|
|
log.e("job execution cancelled.")
|
|
|
|
} catch(ex : Throwable) {
|
|
|
|
log.trace(ex)
|
|
|
|
log.e(ex, "job execution failed.")
|
|
|
|
} finally {
|
|
|
|
job_status.set("job finished.")
|
|
|
|
}
|
|
|
|
// ジョブ終了報告
|
|
|
|
if(! isJobCancelled) {
|
|
|
|
handler.post(Runnable {
|
|
|
|
if(isJobCancelled) return@Runnable
|
|
|
|
|
|
|
|
synchronized(job_list) {
|
|
|
|
job_list.remove(this@JobItem)
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
val jobService = refJobService?.get()
|
|
|
|
if(jobService != null) {
|
2018-04-10 09:08:30 +02:00
|
|
|
val willReschedule = mReschedule.get()
|
|
|
|
log.d("sending jobFinished. willReschedule=$willReschedule")
|
|
|
|
jobService.jobFinished(jobParams, willReschedule)
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
} catch(ex : Throwable) {
|
2018-09-17 20:06:15 +02:00
|
|
|
log.trace(ex, "jobFinished failed(1).")
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2018-04-10 09:08:30 +02:00
|
|
|
log.d(")JobItem.run jobId=${jobId}, cancel=${isJobCancelled}")
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private fun checkNetwork() : Boolean {
|
2018-09-30 17:00:28 +02:00
|
|
|
return App1.getAppState(context).networkTracker.isConnected
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
internal inner class TaskRunner {
|
|
|
|
|
|
|
|
lateinit var job : JobItem
|
|
|
|
private var taskId : Int = 0
|
|
|
|
|
|
|
|
val error_instance = ArrayList<String>()
|
|
|
|
|
2018-01-13 07:15:52 +01:00
|
|
|
fun runTask(job : JobItem, taskId : Int, taskData : JSONObject) {
|
2018-01-04 19:52:25 +01:00
|
|
|
try {
|
2018-08-28 14:56:09 +02:00
|
|
|
log.d("(runTask: taskId=${taskId}")
|
2018-04-10 09:08:30 +02:00
|
|
|
job_status.set("start task $taskId")
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
this.job = job
|
|
|
|
this.taskId = taskId
|
|
|
|
|
2018-01-13 16:24:51 +01:00
|
|
|
var process_db_id = - 1L //
|
2018-01-21 13:46:36 +01:00
|
|
|
|
2018-01-13 16:24:51 +01:00
|
|
|
when(taskId) {
|
|
|
|
TASK_APP_DATA_IMPORT_BEFORE -> {
|
|
|
|
scheduler.cancelAll()
|
|
|
|
for(a in SavedAccount.loadAccountList(context)) {
|
|
|
|
try {
|
|
|
|
val notification_tag = a.db_id.toString()
|
|
|
|
notification_manager.cancel(notification_tag, NOTIFICATION_ID)
|
|
|
|
} catch(ex : Throwable) {
|
|
|
|
log.trace(ex)
|
|
|
|
}
|
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
2018-01-13 16:24:51 +01:00
|
|
|
mBusyAppDataImportBefore.set(false)
|
|
|
|
return
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
2018-01-21 13:46:36 +01:00
|
|
|
|
2018-01-13 16:24:51 +01:00
|
|
|
TASK_APP_DATA_IMPORT_AFTER -> {
|
|
|
|
mBusyAppDataImportAfter.set(false)
|
|
|
|
mBusyAppDataImportBefore.set(false)
|
|
|
|
NotificationTracking.resetPostAll()
|
|
|
|
// fall
|
|
|
|
}
|
2018-01-21 13:46:36 +01:00
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// アプリデータのインポート処理がビジーな間、他のジョブは実行されない
|
2018-01-13 16:24:51 +01:00
|
|
|
if(mBusyAppDataImportBefore.get() || mBusyAppDataImportAfter.get()) return
|
2018-01-04 19:52:25 +01:00
|
|
|
|
2018-01-13 16:24:51 +01:00
|
|
|
// タスクによってはポーリング前にすることがある
|
|
|
|
when(taskId) {
|
|
|
|
TASK_DATA_INJECTED -> processInjectedData()
|
|
|
|
|
|
|
|
TASK_BOOT_COMPLETED -> NotificationTracking.resetPostAll()
|
2018-01-04 19:52:25 +01:00
|
|
|
|
2018-01-13 16:24:51 +01:00
|
|
|
TASK_PACKAGE_REPLACED -> NotificationTracking.resetPostAll()
|
2018-08-08 22:08:15 +02:00
|
|
|
|
|
|
|
// デバイストークンが更新された
|
2018-01-13 16:24:51 +01:00
|
|
|
TASK_FCM_DEVICE_TOKEN -> {
|
|
|
|
}
|
2018-08-08 22:08:15 +02:00
|
|
|
|
|
|
|
// プッシュ通知が届いた
|
2018-01-13 16:24:51 +01:00
|
|
|
TASK_FCM_MESSAGE -> {
|
|
|
|
var bDone = false
|
2018-01-21 13:46:36 +01:00
|
|
|
val tag = taskData.parseString(EXTRA_TAG)
|
2018-01-13 16:24:51 +01:00
|
|
|
if(tag != null) {
|
2018-05-12 10:17:12 +02:00
|
|
|
if(tag.startsWith("acct<>")) {
|
|
|
|
val acct = tag.substring(6)
|
|
|
|
val sa = SavedAccount.loadAccountByAcct(context, acct)
|
|
|
|
if(sa != null) {
|
|
|
|
NotificationTracking.resetLastLoad(sa.db_id)
|
|
|
|
process_db_id = sa.db_id
|
|
|
|
bDone = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(! bDone) {
|
|
|
|
for(sa in SavedAccount.loadByTag(context, tag)) {
|
|
|
|
NotificationTracking.resetLastLoad(sa.db_id)
|
|
|
|
process_db_id = sa.db_id
|
|
|
|
bDone = true
|
|
|
|
}
|
2018-01-13 16:24:51 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if(! bDone) {
|
|
|
|
// タグにマッチする情報がなかった場合、全部読み直す
|
|
|
|
NotificationTracking.resetLastLoad()
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
}
|
2018-01-21 13:46:36 +01:00
|
|
|
|
2018-01-13 16:24:51 +01:00
|
|
|
TASK_NOTIFICATION_CLEAR -> {
|
2018-01-21 13:46:36 +01:00
|
|
|
val db_id = taskData.parseLong(EXTRA_DB_ID)
|
2018-04-10 09:08:30 +02:00
|
|
|
log.d("Notification clear! db_id=$db_id")
|
2018-01-21 13:46:36 +01:00
|
|
|
if(db_id != null) {
|
|
|
|
deleteCacheData(db_id)
|
|
|
|
}
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
2018-01-21 13:46:36 +01:00
|
|
|
|
2018-01-13 16:24:51 +01:00
|
|
|
TASK_NOTIFICATION_DELETE -> {
|
2018-01-21 13:46:36 +01:00
|
|
|
val db_id = taskData.parseLong(EXTRA_DB_ID)
|
2018-04-10 09:08:30 +02:00
|
|
|
log.d("Notification deleted! db_id=$db_id")
|
2018-01-21 13:46:36 +01:00
|
|
|
if(db_id != null) {
|
|
|
|
NotificationTracking.updateRead(db_id)
|
|
|
|
}
|
2018-01-13 16:24:51 +01:00
|
|
|
return
|
|
|
|
}
|
2018-01-21 13:46:36 +01:00
|
|
|
|
2018-01-13 16:24:51 +01:00
|
|
|
TASK_NOTIFICATION_CLICK -> {
|
2018-01-21 13:46:36 +01:00
|
|
|
val db_id = taskData.parseLong(EXTRA_DB_ID)
|
2018-04-10 09:08:30 +02:00
|
|
|
log.d("Notification clicked! db_id=$db_id")
|
2018-01-21 13:46:36 +01:00
|
|
|
if(db_id != null) {
|
|
|
|
// 通知をキャンセル
|
|
|
|
notification_manager.cancel(db_id.toString(), NOTIFICATION_ID)
|
|
|
|
// DB更新処理
|
|
|
|
NotificationTracking.updateRead(db_id)
|
|
|
|
|
|
|
|
}
|
2018-01-13 16:24:51 +01:00
|
|
|
return
|
|
|
|
}
|
2018-01-21 13:46:36 +01:00
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
job_status.set("make install id")
|
|
|
|
|
|
|
|
// インストールIDを生成する
|
|
|
|
// インストールID生成時にSavedAccountテーブルを操作することがあるので
|
|
|
|
// アカウントリストの取得より先に行う
|
|
|
|
if(job.install_id == null) {
|
2018-05-12 10:17:12 +02:00
|
|
|
job.install_id = prepareInstallId(context, job)
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
|
2018-01-13 16:24:51 +01:00
|
|
|
// アカウント別に処理スレッドを作る
|
2018-01-04 19:52:25 +01:00
|
|
|
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()
|
|
|
|
}
|
|
|
|
while(true) {
|
2018-04-10 09:08:30 +02:00
|
|
|
// 同じホスト名が重複しないようにSetに集める
|
2018-01-13 16:24:51 +01:00
|
|
|
val liveSet = TreeSet<String>()
|
2018-04-10 09:08:30 +02:00
|
|
|
for(t in thread_list) {
|
|
|
|
if(! t.isAlive) continue
|
|
|
|
if(job.isJobCancelled) t.cancel()
|
2018-01-13 16:24:51 +01:00
|
|
|
liveSet.add(t.account.host)
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
2018-04-10 09:08:30 +02:00
|
|
|
if(liveSet.isEmpty()) break
|
|
|
|
|
|
|
|
job_status.set("waiting " + liveSet.joinToString(", "))
|
|
|
|
job.waitWorkerThread(if(job.isJobCancelled) 100L else 1000L)
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
synchronized(error_instance) {
|
|
|
|
createErrorNotification(error_instance)
|
|
|
|
}
|
|
|
|
|
|
|
|
if(! job.isJobCancelled) job.bPollingComplete = true
|
|
|
|
|
|
|
|
} catch(ex : Throwable) {
|
2018-09-17 20:06:15 +02:00
|
|
|
log.trace(ex, "task execution failed.")
|
2018-01-04 19:52:25 +01:00
|
|
|
} finally {
|
2018-08-28 14:56:09 +02:00
|
|
|
log.d(")runTask: taskId=$taskId")
|
2018-04-10 09:08:30 +02:00
|
|
|
job_status.set("end task $taskId")
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun createErrorNotification(error_instance : ArrayList<String>) {
|
|
|
|
if(error_instance.isEmpty()) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// 通知タップ時のPendingIntent
|
|
|
|
val intent_click = Intent(context, ActCallback::class.java)
|
2018-11-02 01:09:31 +01:00
|
|
|
// FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY を付与してはいけない
|
2018-01-04 19:52:25 +01:00
|
|
|
intent_click.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
2018-01-21 13:46:36 +01:00
|
|
|
val pi_click = PendingIntent.getActivity(
|
|
|
|
context,
|
|
|
|
3,
|
|
|
|
intent_click,
|
|
|
|
PendingIntent.FLAG_UPDATE_CURRENT
|
|
|
|
)
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
val builder = if(Build.VERSION.SDK_INT >= 26) {
|
|
|
|
// Android 8 から、通知のスタイルはユーザが管理することになった
|
|
|
|
// NotificationChannel を端末に登録しておけば、チャネルごとに管理画面が作られる
|
|
|
|
val channel = NotificationHelper.createNotificationChannel(
|
2018-01-21 13:46:36 +01:00
|
|
|
context,
|
|
|
|
"ErrorNotification",
|
|
|
|
"Error",
|
|
|
|
null,
|
|
|
|
2 /* NotificationManager.IMPORTANCE_LOW */
|
2018-01-04 19:52:25 +01:00
|
|
|
)
|
|
|
|
NotificationCompat.Builder(context, channel.id)
|
|
|
|
} else {
|
|
|
|
NotificationCompat.Builder(context, "not_used")
|
|
|
|
}
|
|
|
|
|
|
|
|
builder
|
|
|
|
.setContentIntent(pi_click)
|
|
|
|
.setAutoCancel(true)
|
|
|
|
.setSmallIcon(R.drawable.ic_notification) // ここは常に白テーマのアイコンを使う
|
2018-01-21 13:46:36 +01:00
|
|
|
.setColor(
|
|
|
|
ContextCompat.getColor(
|
|
|
|
context,
|
|
|
|
R.color.Light_colorAccent
|
|
|
|
)
|
|
|
|
) // ここは常に白テーマの色を使う
|
2018-01-04 19:52:25 +01:00
|
|
|
.setWhen(System.currentTimeMillis())
|
|
|
|
.setGroup(context.packageName + ":" + "Error")
|
|
|
|
|
|
|
|
run {
|
|
|
|
val header = context.getString(R.string.error_notification_title)
|
|
|
|
val summary = context.getString(R.string.error_notification_summary)
|
|
|
|
|
|
|
|
builder
|
|
|
|
.setContentTitle(header)
|
|
|
|
.setContentText(summary + ": " + error_instance[0])
|
|
|
|
|
|
|
|
val style = NotificationCompat.InboxStyle()
|
|
|
|
.setBigContentTitle(header)
|
|
|
|
.setSummaryText(summary)
|
|
|
|
for(i in 0 .. 4) {
|
|
|
|
if(i >= error_instance.size) break
|
|
|
|
style.addLine(error_instance[i])
|
|
|
|
}
|
|
|
|
builder.setStyle(style)
|
|
|
|
}
|
|
|
|
notification_manager.notify(NOTIFICATION_ID_ERROR, builder.build())
|
|
|
|
}
|
|
|
|
|
2018-05-12 10:17:12 +02:00
|
|
|
internal inner class AccountThread(
|
|
|
|
val account : SavedAccount
|
|
|
|
) : Thread(), CurrentCallCallback {
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
private var current_call : Call? = null
|
|
|
|
|
2018-01-13 16:24:51 +01:00
|
|
|
private val client = TootApiClient(context, callback = object : TootApiCallback {
|
2018-01-04 19:52:25 +01:00
|
|
|
override val isApiCancelled : Boolean
|
|
|
|
get() = job.isJobCancelled
|
|
|
|
})
|
|
|
|
|
2018-08-30 08:51:32 +02:00
|
|
|
private val duplicate_check = HashSet<EntityId>()
|
2018-01-13 16:24:51 +01:00
|
|
|
private val dstListJson = ArrayList<JSONObject>()
|
|
|
|
private val dstListData = ArrayList<Data>()
|
2018-03-15 17:23:43 +01:00
|
|
|
private val favMuteSet : HashSet<String> get() = job.favMuteSet
|
2018-01-13 16:24:51 +01:00
|
|
|
private lateinit var nr : NotificationTracking
|
|
|
|
private lateinit var parser : TootParser
|
2018-01-04 19:52:25 +01:00
|
|
|
|
2018-09-17 20:06:15 +02:00
|
|
|
private var nid_last_show : EntityId? = null
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
init {
|
2018-01-12 10:01:25 +01:00
|
|
|
client.currentCallCallback = this
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
override fun onCallCreated(call : Call) {
|
|
|
|
current_call = call
|
|
|
|
}
|
|
|
|
|
|
|
|
fun cancel() {
|
|
|
|
try {
|
2018-01-13 16:24:51 +01:00
|
|
|
current_call?.cancel()
|
2018-01-04 19:52:25 +01:00
|
|
|
} catch(ex : Throwable) {
|
|
|
|
log.trace(ex)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun run() {
|
|
|
|
try {
|
2018-01-13 16:24:51 +01:00
|
|
|
// 疑似アカウントはチェック対象外
|
2018-01-04 19:52:25 +01:00
|
|
|
if(account.isPseudo) return
|
|
|
|
|
2018-12-25 12:54:41 +01:00
|
|
|
// 未確認アカウントはチェック対象外
|
|
|
|
if(! account.isConfirmed) return
|
|
|
|
|
2018-05-12 10:17:12 +02:00
|
|
|
client.account = account
|
2018-09-17 20:06:15 +02:00
|
|
|
|
2018-08-08 22:08:15 +02:00
|
|
|
val wps = PushSubscriptionHelper(context, account)
|
|
|
|
if(wps.flags != 0) {
|
2018-05-13 13:35:17 +02:00
|
|
|
job.bPollingRequired.set(true)
|
|
|
|
}
|
2018-08-08 22:08:15 +02:00
|
|
|
|
2018-05-13 13:35:17 +02:00
|
|
|
wps.updateSubscription(client)
|
2018-05-12 10:17:12 +02:00
|
|
|
val wps_log = wps.log
|
|
|
|
if(wps_log.isNotEmpty()) {
|
2018-05-16 21:09:07 +02:00
|
|
|
log.d("PushSubscriptionHelper: ${account.acct} $wps_log")
|
2018-05-12 10:17:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if(job.isJobCancelled) return
|
2018-01-13 16:24:51 +01:00
|
|
|
|
2018-12-06 23:43:13 +01:00
|
|
|
if(wps.flags == 0) return
|
2018-01-04 19:52:25 +01:00
|
|
|
|
2018-01-13 16:24:51 +01:00
|
|
|
checkAccount()
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
if(job.isJobCancelled) return
|
|
|
|
|
2018-01-13 16:24:51 +01:00
|
|
|
showNotification(dstListData)
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
} catch(ex : Throwable) {
|
|
|
|
log.trace(ex)
|
|
|
|
} finally {
|
|
|
|
job.notifyWorkerThread()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-13 16:24:51 +01:00
|
|
|
private fun checkAccount() {
|
|
|
|
this.nr = NotificationTracking.load(account.db_id)
|
|
|
|
this.parser = TootParser(context, account)
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
// まずキャッシュされたデータを処理する
|
2018-01-21 13:46:36 +01:00
|
|
|
try {
|
|
|
|
val last_data = nr.last_data
|
|
|
|
if(last_data != null) {
|
|
|
|
val array = last_data.toJsonArray()
|
2018-01-04 19:52:25 +01:00
|
|
|
for(i in array.length() - 1 downTo 0) {
|
|
|
|
if(job.isJobCancelled) return
|
|
|
|
val src = array.optJSONObject(i)
|
2018-01-13 16:24:51 +01:00
|
|
|
update_sub(src)
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
}
|
2018-01-21 13:46:36 +01:00
|
|
|
} catch(ex : JSONException) {
|
|
|
|
log.trace(ex)
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
|
2018-01-21 13:46:36 +01:00
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
if(job.isJobCancelled) return
|
|
|
|
|
|
|
|
// 前回の更新から一定時刻が経過したら新しいデータを読んでリストに追加する
|
|
|
|
val now = System.currentTimeMillis()
|
|
|
|
if(now - nr.last_load >= 60000L * 2) {
|
|
|
|
nr.last_load = now
|
|
|
|
|
|
|
|
for(nTry in 0 .. 3) {
|
|
|
|
if(job.isJobCancelled) return
|
|
|
|
|
2018-09-17 20:06:15 +02:00
|
|
|
val result = if(account.isMisskey) {
|
2018-08-30 08:51:32 +02:00
|
|
|
val params = account.putMisskeyApiToken(JSONObject())
|
2018-09-17 20:06:15 +02:00
|
|
|
client.request("/api/i/notifications", params.toPostRequestBuilder())
|
|
|
|
} else {
|
2018-08-30 08:51:32 +02:00
|
|
|
val path = when {
|
|
|
|
nid_last_show != null -> "$PATH_NOTIFICATIONS?since_id=$nid_last_show"
|
|
|
|
else -> PATH_NOTIFICATIONS
|
|
|
|
}
|
|
|
|
|
|
|
|
client.request(path)
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
if(result == null) {
|
|
|
|
log.d("cancelled.")
|
|
|
|
break
|
|
|
|
}
|
|
|
|
val array = result.jsonArray
|
|
|
|
if(array != null) {
|
|
|
|
try {
|
|
|
|
for(i in array.length() - 1 downTo 0) {
|
|
|
|
val src = array.optJSONObject(i)
|
2018-01-13 16:24:51 +01:00
|
|
|
update_sub(src)
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
} catch(ex : JSONException) {
|
|
|
|
log.trace(ex)
|
|
|
|
}
|
|
|
|
|
|
|
|
break
|
|
|
|
} else {
|
2018-04-10 09:08:30 +02:00
|
|
|
log.d("error. ${result.error}")
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
val sv = result.error
|
2018-01-13 16:24:51 +01:00
|
|
|
if(sv?.contains("Timeout") == true && ! account.dont_show_timeout) {
|
2018-01-04 19:52:25 +01:00
|
|
|
synchronized(error_instance) {
|
|
|
|
var bFound = false
|
|
|
|
for(x in error_instance) {
|
|
|
|
if(x == sv) {
|
|
|
|
bFound = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(! bFound) {
|
|
|
|
error_instance.add(sv)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(job.isJobCancelled) return
|
|
|
|
|
2018-01-13 16:24:51 +01:00
|
|
|
dstListJson.sortWith(Comparator { a, b ->
|
2018-01-21 13:46:36 +01:00
|
|
|
val la = a.parseLong(KEY_TIME) ?: 0
|
|
|
|
val lb = b.parseLong(KEY_TIME) ?: 0
|
2018-01-04 19:52:25 +01:00
|
|
|
// 新しい順
|
2018-01-13 16:24:51 +01:00
|
|
|
return@Comparator if(la < lb) 1 else if(la > lb) - 1 else 0
|
2018-01-04 19:52:25 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
if(job.isJobCancelled) return
|
|
|
|
|
|
|
|
val d = JSONArray()
|
|
|
|
for(i in 0 .. 9) {
|
2018-01-13 16:24:51 +01:00
|
|
|
if(i >= dstListJson.size) break
|
|
|
|
d.put(dstListJson[i])
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
nr.last_data = d.toString()
|
|
|
|
nr.save()
|
|
|
|
}
|
|
|
|
|
|
|
|
@Throws(JSONException::class)
|
2018-01-13 16:24:51 +01:00
|
|
|
private fun update_sub(src : JSONObject) {
|
2018-01-04 19:52:25 +01:00
|
|
|
|
2018-09-17 20:06:15 +02:00
|
|
|
if(nr.nid_read == null || nr.nid_show == null) {
|
2018-08-30 08:51:32 +02:00
|
|
|
log.d("update_sub[${account.db_id}], nid_read=${nr.nid_read}, nid_show=${nr.nid_show}")
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
|
2018-09-17 20:06:15 +02:00
|
|
|
val id = getEntityOrderId(account, src)
|
2018-01-04 19:52:25 +01:00
|
|
|
|
2018-09-17 20:06:15 +02:00
|
|
|
if(id.isDefault || duplicate_check.contains(id)) return
|
2018-01-04 19:52:25 +01:00
|
|
|
duplicate_check.add(id)
|
|
|
|
|
2018-09-17 20:06:15 +02:00
|
|
|
if(id.isLatestThan(nid_last_show)) {
|
2018-01-04 19:52:25 +01:00
|
|
|
nid_last_show = id
|
|
|
|
}
|
|
|
|
|
2018-09-17 20:06:15 +02:00
|
|
|
if(! id.isLatestThan(nr.nid_read)) {
|
2018-08-30 08:51:32 +02:00
|
|
|
// 既読のID以下なら何もしない
|
2018-01-04 19:52:25 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-04-10 09:08:30 +02:00
|
|
|
log.d("update_sub: found data that id=${id}, > read id ${nr.nid_read}")
|
2018-03-15 17:23:43 +01:00
|
|
|
|
2018-09-17 20:06:15 +02:00
|
|
|
if(id.isLatestThan(nr.nid_show)) {
|
2018-04-10 09:08:30 +02:00
|
|
|
log.d("update_sub: found new data that id=${id}, greater than shown id ${nr.nid_show}")
|
2018-01-04 19:52:25 +01:00
|
|
|
// 種別チェックより先に「表示済み」idの更新を行う
|
|
|
|
nr.nid_show = id
|
|
|
|
}
|
2018-04-10 09:08:30 +02:00
|
|
|
|
2018-03-15 17:23:43 +01:00
|
|
|
val type = src.parseString("type")
|
2018-01-04 19:52:25 +01:00
|
|
|
|
2018-09-17 20:06:15 +02:00
|
|
|
if(! account.canNotificationShowing(type)) return
|
2018-08-30 08:51:32 +02:00
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
val notification = parser.notification(src) ?: return
|
|
|
|
|
2018-03-15 17:23:43 +01:00
|
|
|
// アプリミュートと単語ミュート
|
2018-09-17 20:06:15 +02:00
|
|
|
if(notification.status?.checkMuted() == true) {
|
2018-03-15 17:23:43 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// ふぁぼ魔ミュート
|
2018-04-10 09:08:30 +02:00
|
|
|
when(type) {
|
|
|
|
TootNotification.TYPE_REBLOG, TootNotification.TYPE_FAVOURITE, TootNotification.TYPE_FOLLOW -> {
|
2018-03-15 17:23:43 +01:00
|
|
|
val who = notification.account
|
2018-04-10 09:08:30 +02:00
|
|
|
if(who != null && favMuteSet.contains(account.getFullAcct(who))) {
|
|
|
|
log.d("${account.getFullAcct(who)} is in favMuteSet.")
|
2018-01-04 19:52:25 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
val data = Data(account, notification)
|
2018-01-13 16:24:51 +01:00
|
|
|
dstListData.add(data)
|
2018-01-04 19:52:25 +01:00
|
|
|
//
|
|
|
|
src.put(KEY_TIME, data.notification.time_created_at)
|
2018-01-13 16:24:51 +01:00
|
|
|
dstListJson.add(src)
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
|
2019-01-16 14:27:26 +01:00
|
|
|
private fun getNotificationLine(item:Data):String{
|
|
|
|
val name = when(Pref.bpShowAcctInSystemNotification(pref)) {
|
|
|
|
false -> item.notification.accountRef?.decoded_display_name
|
|
|
|
|
|
|
|
true -> {
|
|
|
|
val acct = item.notification.accountRef?.get()?.acct
|
|
|
|
if(acct?.isNotEmpty() == true) {
|
|
|
|
"@$acct"
|
|
|
|
} else {
|
|
|
|
null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} ?: "?"
|
|
|
|
return when(item.notification.type) {
|
2018-09-17 20:06:15 +02:00
|
|
|
TootNotification.TYPE_MENTION,
|
|
|
|
TootNotification.TYPE_REPLY ->
|
2019-01-16 14:27:26 +01:00
|
|
|
"- " + context.getString(R.string.display_name_replied_by, name)
|
2018-09-17 20:06:15 +02:00
|
|
|
|
|
|
|
TootNotification.TYPE_RENOTE,
|
|
|
|
TootNotification.TYPE_REBLOG ->
|
2019-01-16 14:27:26 +01:00
|
|
|
"- " + context.getString(R.string.display_name_boosted_by, name)
|
2018-09-17 20:06:15 +02:00
|
|
|
|
|
|
|
TootNotification.TYPE_QUOTE ->
|
2019-01-16 14:27:26 +01:00
|
|
|
"- " + context.getString(R.string.display_name_quoted_by, name)
|
2018-09-17 20:06:15 +02:00
|
|
|
|
|
|
|
TootNotification.TYPE_FOLLOW ->
|
2019-01-16 14:27:26 +01:00
|
|
|
"- " + context.getString(R.string.display_name_followed_by, name)
|
2018-09-17 20:06:15 +02:00
|
|
|
|
|
|
|
TootNotification.TYPE_UNFOLLOW ->
|
2019-01-16 14:27:26 +01:00
|
|
|
"- " + context.getString(R.string.display_name_unfollowed_by, name)
|
2018-09-17 20:06:15 +02:00
|
|
|
|
|
|
|
TootNotification.TYPE_FAVOURITE ->
|
2019-01-16 14:27:26 +01:00
|
|
|
"- " + context.getString(R.string.display_name_favourited_by, name)
|
2018-09-17 20:06:15 +02:00
|
|
|
|
|
|
|
TootNotification.TYPE_REACTION ->
|
2019-01-16 14:27:26 +01:00
|
|
|
"- " + context.getString(R.string.display_name_reaction_by, name)
|
2018-09-17 20:06:15 +02:00
|
|
|
|
|
|
|
TootNotification.TYPE_VOTE ->
|
2019-01-16 14:27:26 +01:00
|
|
|
"- " + context.getString(R.string.display_name_voted_by, name)
|
2018-09-17 20:06:15 +02:00
|
|
|
|
|
|
|
TootNotification.TYPE_FOLLOW_REQUEST ->
|
|
|
|
"- " + context.getString(
|
|
|
|
R.string.display_name_follow_request_by,
|
2019-01-16 14:27:26 +01:00
|
|
|
name
|
2018-09-17 20:06:15 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
else -> "- " + "?"
|
|
|
|
}
|
2019-01-16 14:27:26 +01:00
|
|
|
}
|
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
private fun showNotification(data_list : ArrayList<Data>) {
|
|
|
|
|
|
|
|
val notification_tag = account.db_id.toString()
|
|
|
|
if(data_list.isEmpty()) {
|
2018-04-10 09:08:30 +02:00
|
|
|
log.d("showNotification[${account.acct}] cancel notification.")
|
2018-01-04 19:52:25 +01:00
|
|
|
notification_manager.cancel(notification_tag, NOTIFICATION_ID)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
Collections.sort(data_list, Comparator { a, b ->
|
|
|
|
val la = a.notification.time_created_at
|
|
|
|
val lb = b.notification.time_created_at
|
|
|
|
// 新しい順
|
|
|
|
if(la < lb) return@Comparator + 1
|
|
|
|
if(la > lb) - 1 else 0
|
|
|
|
})
|
|
|
|
var item = data_list[0]
|
|
|
|
|
|
|
|
val nt = NotificationTracking.load(account.db_id)
|
|
|
|
|
2018-08-18 12:58:14 +02:00
|
|
|
if(item.notification.time_created_at == nt.post_time
|
2018-08-30 08:51:32 +02:00
|
|
|
&& item.notification.id == nt.post_id
|
|
|
|
) {
|
2018-01-04 19:52:25 +01:00
|
|
|
// 先頭にあるデータが同じなら、通知を更新しない
|
|
|
|
// このマーカーは端末再起動時にリセットされるので、再起動後は通知が出るはず
|
|
|
|
|
2018-04-10 09:08:30 +02:00
|
|
|
log.d("showNotification[${account.acct}] id=${item.notification.id} is already shown.")
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-08-30 08:51:32 +02:00
|
|
|
nt.updatePost(item.notification.id, item.notification.time_created_at)
|
2018-01-04 19:52:25 +01:00
|
|
|
|
2018-04-10 09:08:30 +02:00
|
|
|
log.d("showNotification[${account.acct}] creating notification(1)")
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
// 通知タップ時のPendingIntent
|
|
|
|
val intent_click = Intent(context, ActCallback::class.java)
|
|
|
|
intent_click.action = ActCallback.ACTION_NOTIFICATION_CLICK
|
2018-01-21 13:46:36 +01:00
|
|
|
intent_click.data =
|
2018-12-05 22:40:56 +01:00
|
|
|
"subwaytooter://notification_click/?db_id=${account.db_id}".toUri()
|
2018-11-02 01:09:31 +01:00
|
|
|
// FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY を付与してはいけない
|
2018-01-04 19:52:25 +01:00
|
|
|
intent_click.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
2018-01-21 13:46:36 +01:00
|
|
|
val pi_click = PendingIntent.getActivity(
|
|
|
|
context,
|
|
|
|
256 + account.db_id.toInt(),
|
|
|
|
intent_click,
|
|
|
|
PendingIntent.FLAG_UPDATE_CURRENT
|
|
|
|
)
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
// 通知を消去した時のPendingIntent
|
|
|
|
val intent_delete = Intent(context, EventReceiver::class.java)
|
|
|
|
intent_delete.action = EventReceiver.ACTION_NOTIFICATION_DELETE
|
|
|
|
intent_delete.putExtra(EXTRA_DB_ID, account.db_id)
|
2018-01-21 13:46:36 +01:00
|
|
|
val pi_delete = PendingIntent.getBroadcast(
|
|
|
|
context,
|
|
|
|
Integer.MAX_VALUE - account.db_id.toInt(),
|
|
|
|
intent_delete,
|
|
|
|
PendingIntent.FLAG_UPDATE_CURRENT
|
|
|
|
)
|
2018-01-04 19:52:25 +01:00
|
|
|
|
2018-04-10 09:08:30 +02:00
|
|
|
log.d("showNotification[${account.acct}] creating notification(2)")
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
val builder = if(Build.VERSION.SDK_INT >= 26) {
|
|
|
|
// Android 8 から、通知のスタイルはユーザが管理することになった
|
|
|
|
// NotificationChannel を端末に登録しておけば、チャネルごとに管理画面が作られる
|
|
|
|
val channel = NotificationHelper.createNotificationChannel(context, account)
|
|
|
|
NotificationCompat.Builder(context, channel.id)
|
|
|
|
} else {
|
|
|
|
NotificationCompat.Builder(context, "not_used")
|
|
|
|
}
|
|
|
|
|
|
|
|
builder
|
|
|
|
.setContentIntent(pi_click)
|
|
|
|
.setDeleteIntent(pi_delete)
|
|
|
|
.setAutoCancel(true)
|
|
|
|
.setSmallIcon(R.drawable.ic_notification) // ここは常に白テーマのアイコンを使う
|
2018-01-21 13:46:36 +01:00
|
|
|
.setColor(
|
|
|
|
ContextCompat.getColor(
|
|
|
|
context,
|
|
|
|
R.color.Light_colorAccent
|
|
|
|
)
|
|
|
|
) // ここは常に白テーマの色を使う
|
2018-01-04 19:52:25 +01:00
|
|
|
.setWhen(item.notification.time_created_at)
|
|
|
|
|
|
|
|
// Android 7.0 ではグループを指定しないと勝手に通知が束ねられてしまう。
|
|
|
|
// 束ねられた通知をタップしても pi_click が実行されないので困るため、
|
|
|
|
// アカウント別にグループキーを設定する
|
|
|
|
builder.setGroup(context.packageName + ":" + account.acct)
|
|
|
|
|
2018-04-10 09:08:30 +02:00
|
|
|
log.d("showNotification[${account.acct}] creating notification(3)")
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
if(Build.VERSION.SDK_INT < 26) {
|
|
|
|
|
|
|
|
var iv = 0
|
|
|
|
|
2018-01-21 13:46:36 +01:00
|
|
|
if(Pref.bpNotificationSound(pref)) {
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
var sound_uri : Uri? = null
|
|
|
|
|
|
|
|
try {
|
|
|
|
val acct = item.access_info.getFullAcct(item.notification.account)
|
2018-11-30 05:42:20 +01:00
|
|
|
sound_uri = AcctColor.getNotificationSound(acct).mayUri()
|
2018-01-04 19:52:25 +01:00
|
|
|
} catch(ex : Throwable) {
|
|
|
|
log.trace(ex)
|
|
|
|
}
|
|
|
|
|
|
|
|
if(sound_uri == null) {
|
2018-11-30 05:42:20 +01:00
|
|
|
sound_uri = account.sound_uri.mayUri()
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
var bSoundSet = false
|
|
|
|
if(sound_uri != null) {
|
|
|
|
try {
|
|
|
|
builder.setSound(sound_uri)
|
|
|
|
bSoundSet = true
|
|
|
|
} catch(ex : Throwable) {
|
|
|
|
log.trace(ex)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
if(! bSoundSet) {
|
|
|
|
iv = iv or NotificationCompat.DEFAULT_SOUND
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-10 09:08:30 +02:00
|
|
|
log.d("showNotification[${account.acct}] creating notification(4)")
|
2018-01-04 19:52:25 +01:00
|
|
|
|
2018-01-17 02:16:26 +01:00
|
|
|
if(Pref.bpNotificationVibration(pref)) {
|
2018-01-04 19:52:25 +01:00
|
|
|
iv = iv or NotificationCompat.DEFAULT_VIBRATE
|
|
|
|
}
|
|
|
|
|
2018-04-10 09:08:30 +02:00
|
|
|
log.d("showNotification[${account.acct}] creating notification(5)")
|
2018-01-04 19:52:25 +01:00
|
|
|
|
2018-01-17 02:16:26 +01:00
|
|
|
if(Pref.bpNotificationLED(pref)) {
|
2018-01-04 19:52:25 +01:00
|
|
|
iv = iv or NotificationCompat.DEFAULT_LIGHTS
|
|
|
|
}
|
|
|
|
|
2018-04-10 09:08:30 +02:00
|
|
|
log.d("showNotification[${account.acct}] creating notification(6)")
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
builder.setDefaults(iv)
|
|
|
|
}
|
|
|
|
|
2018-04-10 09:08:30 +02:00
|
|
|
log.d("showNotification[${account.acct}] creating notification(7)")
|
2018-01-04 19:52:25 +01:00
|
|
|
|
2019-01-16 14:27:26 +01:00
|
|
|
|
|
|
|
var a = getNotificationLine(item)
|
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
val acct = item.access_info.acct
|
2019-01-16 14:27:26 +01:00
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
if(data_list.size == 1) {
|
|
|
|
builder.setContentTitle(a)
|
|
|
|
builder.setContentText(acct)
|
|
|
|
} else {
|
|
|
|
val header = context.getString(R.string.notification_count, data_list.size)
|
|
|
|
builder.setContentTitle(header)
|
|
|
|
.setContentText(a)
|
|
|
|
|
|
|
|
val style = NotificationCompat.InboxStyle()
|
|
|
|
.setBigContentTitle(header)
|
|
|
|
.setSummaryText(acct)
|
|
|
|
for(i in 0 .. 4) {
|
|
|
|
if(i >= data_list.size) break
|
|
|
|
item = data_list[i]
|
2019-01-16 14:27:26 +01:00
|
|
|
a = getNotificationLine(item)
|
2018-01-04 19:52:25 +01:00
|
|
|
style.addLine(a)
|
|
|
|
}
|
|
|
|
builder.setStyle(style)
|
|
|
|
}
|
|
|
|
|
2018-04-10 09:08:30 +02:00
|
|
|
log.d("showNotification[${account.acct}] set notification...")
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
notification_manager.notify(notification_tag, NOTIFICATION_ID, builder.build())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun processInjectedData() {
|
|
|
|
while(inject_queue.size > 0) {
|
|
|
|
|
|
|
|
val data = inject_queue.poll()
|
|
|
|
|
|
|
|
val account = SavedAccount.loadAccount(context, data.account_db_id) ?: continue
|
|
|
|
|
|
|
|
val nr = NotificationTracking.load(data.account_db_id)
|
|
|
|
|
2018-08-18 12:58:14 +02:00
|
|
|
val duplicate_check = HashSet<EntityId>()
|
2018-01-04 19:52:25 +01:00
|
|
|
|
|
|
|
val dst_array = ArrayList<JSONObject>()
|
2018-01-21 13:46:36 +01:00
|
|
|
try {
|
2018-01-04 19:52:25 +01:00
|
|
|
// まずキャッシュされたデータを処理する
|
2018-01-21 13:46:36 +01:00
|
|
|
val last_data = nr.last_data
|
|
|
|
if(last_data != null) {
|
|
|
|
val array = last_data.toJsonArray()
|
2018-01-04 19:52:25 +01:00
|
|
|
for(i in array.length() - 1 downTo 0) {
|
|
|
|
val src = array.optJSONObject(i)
|
2018-09-17 20:06:15 +02:00
|
|
|
val id = getEntityOrderId(account, src)
|
|
|
|
if(id.notDefault) {
|
2018-01-21 13:46:36 +01:00
|
|
|
dst_array.add(src)
|
|
|
|
duplicate_check.add(id)
|
2018-04-10 09:08:30 +02:00
|
|
|
log.d("add old. id=${id}")
|
2018-01-21 13:46:36 +01:00
|
|
|
}
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
|
|
|
}
|
2018-01-21 13:46:36 +01:00
|
|
|
} catch(ex : JSONException) {
|
|
|
|
log.trace(ex)
|
2018-01-04 19:52:25 +01:00
|
|
|
}
|
2018-01-21 13:46:36 +01:00
|
|
|
|
2018-01-04 19:52:25 +01:00
|
|
|
for(item in data.list) {
|
|
|
|
try {
|
|
|
|
if(duplicate_check.contains(item.id)) {
|
2018-04-10 09:08:30 +02:00
|
|
|
log.d("skip duplicate. id=${item.id}")
|
2018-01-04 19:52:25 +01:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
duplicate_check.add(item.id)
|
|
|
|
|
|
|
|
val type = item.type
|
2018-09-17 20:06:15 +02:00
|
|
|
if(! account.canNotificationShowing(type)) {
|
2018-04-10 09:08:30 +02:00
|
|
|
log.d("skip by setting. id=${item.id}")
|
2018-01-04 19:52:25 +01:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
val src = item.json
|
|
|
|
src.put(KEY_TIME, item.time_created_at)
|
|
|
|
dst_array.add(src)
|
|
|
|
} catch(ex : JSONException) {
|
|
|
|
log.trace(ex)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// 新しい順にソート
|
|
|
|
Collections.sort(dst_array, Comparator { a, b ->
|
2018-01-21 13:46:36 +01:00
|
|
|
val la = a.parseLong(KEY_TIME) ?: 0
|
|
|
|
val lb = b.parseLong(KEY_TIME) ?: 0
|
2018-01-04 19:52:25 +01:00
|
|
|
// 新しい順
|
|
|
|
if(la < lb) return@Comparator + 1
|
|
|
|
if(la > lb) - 1 else 0
|
|
|
|
})
|
|
|
|
|
|
|
|
// 最新10件を保存
|
|
|
|
val d = JSONArray()
|
|
|
|
for(i in 0 .. 9) {
|
|
|
|
if(i >= dst_array.size) {
|
2018-04-10 09:08:30 +02:00
|
|
|
log.d("inject $i data.")
|
2018-01-04 19:52:25 +01:00
|
|
|
break
|
|
|
|
}
|
|
|
|
d.put(dst_array[i])
|
|
|
|
}
|
|
|
|
nr.last_data = d.toString()
|
|
|
|
|
|
|
|
nr.save()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun deleteCacheData(db_id : Long) {
|
|
|
|
|
|
|
|
if(SavedAccount.loadAccount(context, db_id) == null) {
|
|
|
|
// 無効なdb_id
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
val nr = NotificationTracking.load(db_id)
|
|
|
|
nr.last_data = JSONArray().toString()
|
|
|
|
nr.save()
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|