通知用のPendingIntentを作る時に毎回異なるuriを指定する

This commit is contained in:
tateisu 2020-01-16 18:31:14 +09:00
parent 31fa437fc5
commit 7f4b93dc38
5 changed files with 125 additions and 105 deletions

View File

@ -18,8 +18,6 @@ class ActCallback : AppCompatActivity() {
companion object { companion object {
private val log = LogCategory("ActCallback") private val log = LogCategory("ActCallback")
const val ACTION_NOTIFICATION_CLICK = "notification_click"
internal val last_uri = AtomicReference<Uri>(null) internal val last_uri = AtomicReference<Uri>(null)
internal val sent_intent = AtomicReference<Intent>(null) internal val sent_intent = AtomicReference<Intent>(null)

View File

@ -1815,9 +1815,11 @@ class ActMain : AppCompatActivity()
// subwaytooter://notification_click/?db_id=(db_id) // subwaytooter://notification_click/?db_id=(db_id)
val dataIdString = uri.getQueryParameter("db_id") val dataIdString = uri.getQueryParameter("db_id")
if(dataIdString != null) { if(dataIdString != null) {
PollingWorker.queueNotificationClicked(this, uri)
try { try {
val dataId = dataIdString.toLong(10) val dataId = dataIdString.toLong()
val type = uri.getQueryParameter("type") ?: ""
val account = SavedAccount.loadAccount(this@ActMain, dataId) val account = SavedAccount.loadAccount(this@ActMain, dataId)
if(account != null) { if(account != null) {
var column = app_state.column_list.firstOrNull { var column = app_state.column_list.firstOrNull {
@ -1836,14 +1838,10 @@ class ActMain : AppCompatActivity()
ColumnType.NOTIFICATIONS ColumnType.NOTIFICATIONS
) )
} }
// 通知を読み直す // 通知を読み直す
if(! column.bInitialLoading) { if(! column.bInitialLoading) {
column.startLoading() column.startLoading()
} }
PollingWorker.queueNotificationClicked(this, dataId, type)
} }
} catch(ex : Throwable) { } catch(ex : Throwable) {
log.trace(ex) log.trace(ex)

View File

@ -22,13 +22,8 @@ class EventReceiver : BroadcastReceiver() {
Intent.ACTION_MY_PACKAGE_REPLACED -> Intent.ACTION_MY_PACKAGE_REPLACED ->
PollingWorker.queuePackageReplaced(context) PollingWorker.queuePackageReplaced(context)
ACTION_NOTIFICATION_DELETE -> { ACTION_NOTIFICATION_DELETE ->
PollingWorker.queueNotificationDeleted( PollingWorker.queueNotificationDeleted( context,intent.data)
context,
intent.getLongExtra(PollingWorker.EXTRA_DB_ID, - 1L),
intent.getStringExtra(PollingWorker.EXTRA_NOTIFICATION_TYPE) ?: ""
)
}
else -> log.e("onReceive: unsupported action %s", action) else -> log.e("onReceive: unsupported action %s", action)
} }

View File

@ -81,6 +81,7 @@ class PollingWorker private constructor(contextArg : Context) {
const val EXTRA_TAG = "tag" const val EXTRA_TAG = "tag"
const val EXTRA_TASK_ID = "task_id" const val EXTRA_TASK_ID = "task_id"
const val EXTRA_NOTIFICATION_TYPE = "notification_type" const val EXTRA_NOTIFICATION_TYPE = "notification_type"
const val EXTRA_NOTIFICATION_ID = "notificationId"
const val APP_SERVER = "https://mastodon-msg.juggler.jp" const val APP_SERVER = "https://mastodon-msg.juggler.jp"
@ -303,26 +304,37 @@ class PollingWorker private constructor(contextArg : Context) {
} }
fun queueNotificationDeleted(context : Context, db_id : Long, trackingType : String) { private fun decodeNotificationUri(uri:Uri?):JsonObject?{
uri ?: return null
return jsonObject {
putNotNull(
EXTRA_DB_ID,
uri.getQueryParameter("db_id")?.toLongOrNull()
)
putNotNull(
EXTRA_NOTIFICATION_TYPE,
uri.getQueryParameter("type")?.notEmpty()
)
putNotNull(
EXTRA_NOTIFICATION_ID,
uri.getQueryParameter("notificationId")?.notEmpty()
)
}
}
fun queueNotificationDeleted(context : Context, uri : Uri?) {
try { try {
val data = jsonObject { val params = decodeNotificationUri(uri) ?: return
putNotNull(EXTRA_DB_ID, db_id) addTask(context,false,TASK_NOTIFICATION_DELETE,params)
putNotNull(EXTRA_NOTIFICATION_TYPE, trackingType)
}
addTask(context, true, TASK_NOTIFICATION_DELETE, data)
} catch(ex : JsonException) { } catch(ex : JsonException) {
log.trace(ex) log.trace(ex)
} }
} }
fun queueNotificationClicked(context : Context, db_id : Long, trackingType : String) { fun queueNotificationClicked(context : Context, uri:Uri?) {
try { try {
val data = jsonObject { val params = decodeNotificationUri(uri) ?: return
putNotNull(EXTRA_DB_ID, db_id) addTask(context, true, TASK_NOTIFICATION_CLICK, params)
putNotNull(EXTRA_NOTIFICATION_TYPE, trackingType)
}
addTask(context, true, TASK_NOTIFICATION_CLICK, data)
} catch(ex : JsonException) { } catch(ex : JsonException) {
log.trace(ex) log.trace(ex)
} }
@ -815,6 +827,12 @@ class PollingWorker private constructor(contextArg : Context) {
} }
private fun TrackingType.trackingTypeName()=when(this){
TrackingType.NotReply ->NotificationHelper.TRACKING_NAME_DEFAULT
TrackingType.Reply -> NotificationHelper.TRACKING_NAME_REPLY
TrackingType.All -> NotificationHelper.TRACKING_NAME_DEFAULT
}
internal inner class TaskRunner { internal inner class TaskRunner {
lateinit var job : JobItem lateinit var job : JobItem
@ -822,6 +840,8 @@ class PollingWorker private constructor(contextArg : Context) {
val error_instance = ArrayList<String>() val error_instance = ArrayList<String>()
fun runTask(job : JobItem, taskId : Int, taskData : JsonObject) { fun runTask(job : JobItem, taskId : Int, taskData : JsonObject) {
try { try {
log.d("(runTask: taskId=${taskId}") log.d("(runTask: taskId=${taskId}")
@ -910,32 +930,36 @@ class PollingWorker private constructor(contextArg : Context) {
TASK_NOTIFICATION_DELETE -> { TASK_NOTIFICATION_DELETE -> {
val db_id = taskData.long(EXTRA_DB_ID) val db_id = taskData.long(EXTRA_DB_ID)
val type = val type = TrackingType.parseStr(taskData.string(EXTRA_NOTIFICATION_TYPE))
when(TrackingType.parseStr(taskData.string(EXTRA_NOTIFICATION_TYPE))) { val typeName = type.trackingTypeName()
TrackingType.Reply -> NotificationHelper.TRACKING_NAME_REPLY val id = taskData.string(EXTRA_NOTIFICATION_ID)
else -> NotificationHelper.TRACKING_NAME_DEFAULT log.d("Notification deleted! db_id=$db_id,type=$type,id=$id")
}
log.d("Notification deleted! db_id=$db_id,type=$type")
if(db_id != null) { if(db_id != null) {
NotificationTracking.updateRead(db_id, type) NotificationTracking.updateRead(db_id, typeName)
} }
return return
} }
TASK_NOTIFICATION_CLICK -> { TASK_NOTIFICATION_CLICK -> {
val db_id = taskData.long(EXTRA_DB_ID) val db_id = taskData.long(EXTRA_DB_ID)
val type = val type = TrackingType.parseStr(taskData.string(EXTRA_NOTIFICATION_TYPE))
when(TrackingType.parseStr(taskData.string(EXTRA_NOTIFICATION_TYPE))) { val typeName = type.trackingTypeName()
TrackingType.Reply -> NotificationHelper.TRACKING_NAME_REPLY val id = taskData.string(EXTRA_NOTIFICATION_ID).notEmpty()
else -> NotificationHelper.TRACKING_NAME_DEFAULT log.d("Notification clicked! db_id=$db_id,type=$type,id=$id")
}
log.d("Notification clicked! db_id=$db_id,type=$type")
if(db_id != null) { if(db_id != null) {
// 通知をキャンセル // 通知をキャンセル
notification_manager.cancel(db_id.toString(), NOTIFICATION_ID) val notification_tag = when(typeName) {
"" -> "${db_id}/_"
else -> "${db_id}/$typeName"
}
if( id != null){
val itemTag = "$notification_tag/$id"
notification_manager.cancel(itemTag, NOTIFICATION_ID)
}else{
notification_manager.cancel(notification_tag, NOTIFICATION_ID)
}
// DB更新処理 // DB更新処理
NotificationTracking.updateRead(db_id, type) NotificationTracking.updateRead(db_id, typeName)
} }
return return
} }
@ -1276,7 +1300,7 @@ class PollingWorker private constructor(contextArg : Context) {
continue continue
} }
createNotification(itemTag) { builder -> createNotification(itemTag,notificationId = item.notification.id.toString()) { builder ->
val myAcct = item.access_info.acct val myAcct = item.access_info.acct
@ -1292,7 +1316,7 @@ class PollingWorker private constructor(contextArg : Context) {
.setSummaryText(myAcct) .setSummaryText(myAcct)
.bigText(content) .bigText(content)
) )
}else{ } else {
builder.setContentText(myAcct) builder.setContentText(myAcct)
} }
@ -1427,54 +1451,11 @@ class PollingWorker private constructor(contextArg : Context) {
private fun createNotification( private fun createNotification(
notification_tag : String, notification_tag : String,
notificationId : String? = null,
setContent : (builder : NotificationCompat.Builder) -> Unit setContent : (builder : NotificationCompat.Builder) -> Unit
) { ) {
log.d("showNotification[${account.acct}] creating notification(1)") log.d("showNotification[${account.acct}] creating notification(1)")
val midInt = Int.MAX_VALUE shr 1
// 通知タップ時のPendingIntent
val intent_click = Intent(context, ActCallback::class.java)
intent_click.action = ActCallback.ACTION_NOTIFICATION_CLICK
intent_click.data =
"subwaytooter://notification_click/?db_id=${account.db_id}&type=${trackingType.str}"
.toUri()
// FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY を付与してはいけない
intent_click.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
val pi_click = PendingIntent.getActivity(
context,
when(trackingType) {
TrackingType.Reply -> midInt + account.db_id.toInt()
else -> 256 + account.db_id.toInt()
},
intent_click,
PendingIntent.FLAG_UPDATE_CURRENT
)
// 通知を消去した時の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)
intent_delete.putExtra(EXTRA_NOTIFICATION_TYPE, trackingType.str)
val pi_delete = PendingIntent.getBroadcast(
context,
when(trackingType) {
TrackingType.Reply -> {
midInt - account.db_id.toInt()
}
else -> {
Integer.MAX_VALUE - account.db_id.toInt()
}
},
intent_delete,
PendingIntent.FLAG_UPDATE_CURRENT
)
log.d("showNotification[${account.acct}] creating notification(2)")
val builder = if(Build.VERSION.SDK_INT >= 26) { val builder = if(Build.VERSION.SDK_INT >= 26) {
// Android 8 から、通知のスタイルはユーザが管理することになった // Android 8 から、通知のスタイルはユーザが管理することになった
// NotificationChannel を端末に登録しておけば、チャネルごとに管理画面が作られる // NotificationChannel を端末に登録しておけば、チャネルごとに管理画面が作られる
@ -1488,19 +1469,63 @@ class PollingWorker private constructor(contextArg : Context) {
NotificationCompat.Builder(context, "not_used") NotificationCompat.Builder(context, "not_used")
} }
builder.apply {
builder.setContentIntent(pi_click)
builder.setDeleteIntent(pi_delete) val params = listOf(
builder.setAutoCancel(true) "db_id" to account.db_id.toString(),
builder.setSmallIcon(R.drawable.ic_notification) // ここは常に白テーマのアイコンを使う "type" to trackingType.str,
"notificationId" to notificationId
// ここは常に白テーマの色を使う ).mapNotNull{
builder.color = ContextCompat.getColor(context, R.color.Light_colorAccent) val second = it.second
if( second == null ){
// Android 7.0 ではグループを指定しないと勝手に通知が束ねられてしまう。 null
// 束ねられた通知をタップしても pi_click が実行されないので困るため、 }else{
// アカウント別にグループキーを設定する "${it.first.encodePercent()}=${second.encodePercent()}"
builder.setGroup(context.packageName + ":" + account.acct) }
}.joinToString("&")
setContentIntent(
PendingIntent.getActivity(
context,
257,
Intent(context, ActCallback::class.java).apply {
data =
"subwaytooter://notification_click/?$params".toUri()
// FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY を付与してはいけない
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
},
PendingIntent.FLAG_UPDATE_CURRENT
)
)
setDeleteIntent(
PendingIntent.getBroadcast(
context,
257,
Intent(context, EventReceiver::class.java).apply {
action = EventReceiver.ACTION_NOTIFICATION_DELETE
data =
"subwaytooter://notification_delete/?$params".toUri()
},
PendingIntent.FLAG_UPDATE_CURRENT
)
)
setAutoCancel(true)
// 常に白テーマのアイコンを使う
setSmallIcon(R.drawable.ic_notification)
// 常に白テーマの色を使う
builder.color = ContextCompat.getColor(context, R.color.Light_colorAccent)
// Android 7.0 ではグループを指定しないと勝手に通知が束ねられてしまう。
// 束ねられた通知をタップしても pi_click が実行されないので困るため、
// アカウント別にグループキーを設定する
setGroup(context.packageName + ":" + account.acct)
}
log.d("showNotification[${account.acct}] creating notification(3)") log.d("showNotification[${account.acct}] creating notification(3)")
@ -1648,5 +1673,4 @@ class PollingWorker private constructor(contextArg : Context) {
} }
notification_manager.notify(NOTIFICATION_ID_ERROR, builder.build()) notification_manager.notify(NOTIFICATION_ID_ERROR, builder.build())
} }
} }

View File

@ -11,6 +11,7 @@ import okhttp3.Response
import okhttp3.WebSocket import okhttp3.WebSocket
import okhttp3.WebSocketListener import okhttp3.WebSocketListener
import java.net.ProtocolException import java.net.ProtocolException
import java.net.SocketException
import java.util.* import java.util.*
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicReference import java.util.concurrent.atomic.AtomicReference
@ -340,7 +341,11 @@ internal class StreamReader(
* listener will be made. * listener will be made.
*/ */
override fun onFailure(webSocket : WebSocket, t : Throwable, response : Response?) { override fun onFailure(webSocket : WebSocket, t : Throwable, response : Response?) {
log.e(t, "WebSocket onFailure. url=%s .", webSocket.request().url) if( t is SocketException && t.message == "Socket is closed"){
log.w("WebSocket is closed. url=${webSocket.request().url}")
}else {
log.e(t, "WebSocket onFailure. url=${webSocket.request().url}")
}
bListening.set(false) bListening.set(false)
handler.removeCallbacks(proc_reconnect) handler.removeCallbacks(proc_reconnect)