From c0692970619546823d6268b10f812e16a75cc6e7 Mon Sep 17 00:00:00 2001 From: Chenhe Date: Wed, 29 Jul 2020 16:19:12 +0800 Subject: [PATCH 01/11] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=9C=AA=E5=8C=B9?= =?UTF-8?q?=E9=85=8D=E7=89=B9=E5=88=AB=E5=85=B3=E5=BF=83=E5=8A=A8=E6=80=81?= =?UTF-8?q?=E6=8E=A8=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix #8 --- .../qqnotifyevo/core/NotificationProcessor.kt | 96 +++++++++++++------ .../qqnotifyevo/service/NevoDecorator.kt | 7 ++ .../service/NotificationMonitorService.kt | 5 + app/src/main/res/values/strings.xml | 2 + 4 files changed, 83 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/cc/chenhe/qqnotifyevo/core/NotificationProcessor.kt b/app/src/main/java/cc/chenhe/qqnotifyevo/core/NotificationProcessor.kt index f3f2f37..c9a9ff6 100644 --- a/app/src/main/java/cc/chenhe/qqnotifyevo/core/NotificationProcessor.kt +++ b/app/src/main/java/cc/chenhe/qqnotifyevo/core/NotificationProcessor.kt @@ -24,7 +24,15 @@ abstract class NotificationProcessor(context: Context) { companion object { private const val TAG = "NotificationProcessor" + /** + * 用于在优化后的通知中保留原始来源标记。通过 [Notification.extras] 提取。 + * + * 值为 [Int] 类型,TAG_* 常量。 + */ + const val NOTIFICATION_EXTRA_TAG = "qqevo.tag" + private const val CONVERSATION_NAME_QZONE = "QZone" + private const val CONVERSATION_NAME_QZONE_SPECIAL = "QZoneSpecial" // 特别关心空间动态推送 @Retention(AnnotationRetention.SOURCE) @IntDef(TAG_UNKNOWN, TAG_QQ, TAG_QQ_LITE, TAG_TIM) @@ -82,17 +90,18 @@ abstract class NotificationProcessor(context: Context) { val msgTitlePattern: Pattern = Pattern.compile("^(\\[特别关心])?.*?(?: \\((\\d+)条新消息\\))?$") // Q空间动态 - // title: QQ空间动态(共x条未读) - // ticker: 详情(例如xxx评论了你) + // title(与我相关): QQ空间动态(共x条未读); (特别关心): QQ空间动态 + // ticker(与我相关): 详情(例如xxx评论了你); (特别关心): 【特别关心】昵称:内容 // text: 与 ticker 相同 + // 注意:与我相关动态、特别关心动态是两个独立的通知,不会互相覆盖。 /** - * 匹配 QQ 空间 Ticker. + * 匹配 QQ 空间 Title. * * Group: 1新消息数目 */ @VisibleForTesting - val qzonePattern: Pattern = Pattern.compile("^QQ空间动态\\(共(\\d+)条未读\\)$") + val qzonePattern: Pattern = Pattern.compile("^QQ空间动态(?:\\(共(\\d+)条未读\\))?$") // 隐藏消息详情 // title: QQ @@ -111,6 +120,8 @@ abstract class NotificationProcessor(context: Context) { private val ctx: Context = context.applicationContext + private val qzoneSpecialTitle = context.getString(R.string.notify_qzone_special_title) + private val qqHistory = ArrayList() private val qqLiteHistory = ArrayList() private val timHistory = ArrayList() @@ -122,22 +133,24 @@ abstract class NotificationProcessor(context: Context) { } /** - * 清除此来源所有通知,并清空历史记录。 + * 清空此来源所有会话(包括 QQ 空间)历史记录。 * * @param tag 来源标记。 */ fun clearHistory(@SourceTag tag: Int) { Timber.tag(TAG).v("Clear history. tag=$tag") - when (tag) { - TAG_QQ -> { - qqHistory.clear() - } - TAG_QQ_LITE -> { - qqLiteHistory.clear() - } - TAG_TIM -> { - timHistory.clear() - } + getHistoryMessage(tag).clear() + } + + /** + * 清空此来源特别关心 QQ 空间动态推送历史记录。不清除与我相关的动态或其他聊天消息。 + * + * @param tag 来源标记。 + */ + private fun clearQzoneSpecialHistory(@SourceTag tag: Int) { + Timber.tag(TAG).d("Clear QZone history. tag=$tag") + getHistoryMessage(tag).removeIf { + it.name == qzoneSpecialTitle } } @@ -218,13 +231,27 @@ abstract class NotificationProcessor(context: Context) { // QQ空间 if (isQzone && !content.isNullOrEmpty()) { - avatarManager.saveAvatar(CONVERSATION_NAME_QZONE.hashCode(), getNotifyLargeIcon(context, original)) - - val conversation = addMessage(tag, context.getString(R.string.notify_qzone_channel_name), content, null, - avatarManager.getAvatar(CONVERSATION_NAME_QZONE.hashCode()), original.contentIntent, - original.deleteIntent) - deleteOldMessage(conversation, matchQzoneNum(title)) - Timber.tag(TAG).d("[QZone] Ticker: $ticker") + val num = matchQzoneNum(title) + val conversation: Conversation + if (num == -1) { + // 特别关心动态推送 + avatarManager.saveAvatar(CONVERSATION_NAME_QZONE_SPECIAL.hashCode(), + getNotifyLargeIcon(context, original)) + conversation = addMessage(tag, qzoneSpecialTitle, content, null, + avatarManager.getAvatar(CONVERSATION_NAME_QZONE_SPECIAL.hashCode()), original.contentIntent, + original.deleteIntent) + // 由于特别关心动态推送的通知没有显示未读消息个数,所以这里无法提取并删除多余的历史消息。 + // Workaround: 在通知删除回调下来匹配并清空特别关心动态历史记录。 + Timber.tag(TAG).d("[QZoneSpecial] Ticker: $ticker") + } else { + // 与我相关的动态 + avatarManager.saveAvatar(CONVERSATION_NAME_QZONE.hashCode(), getNotifyLargeIcon(context, original)) + conversation = addMessage(tag, qzoneSpecialTitle, content, null, + avatarManager.getAvatar(CONVERSATION_NAME_QZONE.hashCode()), original.contentIntent, + original.deleteIntent) + deleteOldMessage(conversation, num) + Timber.tag(TAG).d("[QZone] Ticker: $ticker") + } return renewQzoneNotification(context, tag, conversation, sbn, original) } @@ -274,6 +301,17 @@ abstract class NotificationProcessor(context: Context) { return null } + fun onNotificationRemoved(sbn: StatusBarNotification, reason: Int) { + val tag = sbn.notification.extras.getInt(NOTIFICATION_EXTRA_TAG, TAG_UNKNOWN) + if (tag == TAG_UNKNOWN) return + val title = sbn.notification.extras.getString(Notification.EXTRA_TITLE) + Timber.tag(TAG).v("onNotificationRemoved: Tag=$tag, Reason=$reason, Title=$title") + if (title == qzoneSpecialTitle) { + // 清除 QQ 空间特别关心动态推送历史记录 + clearQzoneSpecialHistory(tag) + } + } + /** * 提取新消息个数。 */ @@ -294,15 +332,17 @@ abstract class NotificationProcessor(context: Context) { /** * 提取空间未读消息个数。 + * + * @return 动态未读消息个数。若是特别关心推送则返回 `-1`。[title] 为空或不匹配则返回 `0`。 */ private fun matchQzoneNum(title: String?): Int { if (title.isNullOrEmpty()) return 0 qzonePattern.matcher(title).also { matcher -> if (matcher.matches()) { - return matcher.group(1)!!.toInt() + return matcher.group(1)?.toInt() ?: -1 } } - return 1 + return 0 } /** @@ -361,13 +401,15 @@ abstract class NotificationProcessor(context: Context) { setIcon(context, builder, tag, channel == NotifyChannel.QZONE) - return builder.build() + return builder.build().apply { + extras.putInt(NOTIFICATION_EXTRA_TAG, tag) + } } protected fun createQZoneNotification(context: Context, @SourceTag tag: Int, conversation: Conversation, original: Notification): Notification { val style = NotificationCompat.MessagingStyle(Person.Builder() - .setName(context.getString(R.string.notify_qzone_channel_name)).build()) + .setName(context.getString(R.string.notify_qzone_title)).build()) conversation.messages.forEach { msg -> style.addMessage(msg.content, msg.time, msg.person) } @@ -429,7 +471,7 @@ abstract class NotificationProcessor(context: Context) { TAG_TIM -> timHistory TAG_QQ_LITE -> qqLiteHistory TAG_QQ -> qqHistory - else -> qqHistory + else -> throw RuntimeException("Unknown tag: $tag.") } } diff --git a/app/src/main/java/cc/chenhe/qqnotifyevo/service/NevoDecorator.kt b/app/src/main/java/cc/chenhe/qqnotifyevo/service/NevoDecorator.kt index 2772dfc..7742090 100644 --- a/app/src/main/java/cc/chenhe/qqnotifyevo/service/NevoDecorator.kt +++ b/app/src/main/java/cc/chenhe/qqnotifyevo/service/NevoDecorator.kt @@ -7,6 +7,7 @@ import android.content.Intent import android.content.IntentFilter import android.os.IBinder import android.os.Process +import android.service.notification.StatusBarNotification import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry @@ -165,4 +166,10 @@ class NevoDecorator : NevoDecoratorService(), LifecycleOwner { mutable.color = newNotification.color return true } + + override fun onNotificationRemoved(sbn: StatusBarNotification?, reason: Int): Boolean { + if (sbn == null || getMode(this) != MODE_NEVO) return false + processor.onNotificationRemoved(sbn, reason) + return false + } } \ No newline at end of file diff --git a/app/src/main/java/cc/chenhe/qqnotifyevo/service/NotificationMonitorService.kt b/app/src/main/java/cc/chenhe/qqnotifyevo/service/NotificationMonitorService.kt index 7c871ef..8c9b874 100644 --- a/app/src/main/java/cc/chenhe/qqnotifyevo/service/NotificationMonitorService.kt +++ b/app/src/main/java/cc/chenhe/qqnotifyevo/service/NotificationMonitorService.kt @@ -93,6 +93,11 @@ class NotificationMonitorService : NotificationListenerService(), InnerNotificat processor.resolveNotification(ctx, sbn.packageName, sbn) } + override fun onNotificationRemoved(sbn: StatusBarNotification?, rankingMap: RankingMap?, reason: Int) { + if (sbn == null || getMode(this) != MODE_LEGACY) return + processor.onNotificationRemoved(sbn, reason) + super.onNotificationRemoved(sbn, rankingMap, reason) + } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ab2cf7a..f733962 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -31,6 +31,8 @@ 空间动态 QQ 空间动态通知 + 空间动态 + 特别关心动态 %1$d条新消息 %1$d条新动态 From 6b364af0ca7489384ad3abf3f2d8e53741fe7faf Mon Sep 17 00:00:00 2001 From: Chenhe Date: Wed, 29 Jul 2020 16:54:18 +0800 Subject: [PATCH 02/11] =?UTF-8?q?=E6=94=AF=E6=8C=81=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=98=AF=E5=90=A6=E4=BB=8E=E6=9C=80=E8=BF=91?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E5=88=97=E8=A1=A8=E9=9A=90=E8=97=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 3 +-- .../cc/chenhe/qqnotifyevo/preference/AdvancedFr.kt | 11 +++++++++++ .../cc/chenhe/qqnotifyevo/preference/PreferenceAty.kt | 9 +++++++++ .../cc/chenhe/qqnotifyevo/utils/PreferencesUtils.kt | 4 ++++ app/src/main/res/values/strings.xml | 5 +++++ app/src/main/res/xml/pref_advanced.xml | 5 +++++ 6 files changed, 35 insertions(+), 2 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 78fb605..4d67a5e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -70,8 +70,7 @@ diff --git a/app/src/main/java/cc/chenhe/qqnotifyevo/preference/AdvancedFr.kt b/app/src/main/java/cc/chenhe/qqnotifyevo/preference/AdvancedFr.kt index 2e2a195..66fdc8b 100644 --- a/app/src/main/java/cc/chenhe/qqnotifyevo/preference/AdvancedFr.kt +++ b/app/src/main/java/cc/chenhe/qqnotifyevo/preference/AdvancedFr.kt @@ -37,6 +37,7 @@ class AdvancedFr : PreferenceFragmentCompat() { true } } + findPreference("show_in_recent")!!.summaryProvider = ShowInRecentSummaryProvider() deleteLog = findPreference("delete_log")!! refreshLogSize() } @@ -104,4 +105,14 @@ class AdvancedFr : PreferenceFragmentCompat() { return builder.toString() } } + + private inner class ShowInRecentSummaryProvider : Preference.SummaryProvider { + + private val summaries = requireContext().resources.getStringArray(R.array.pref_show_in_recent_summaries) + + override fun provideSummary(preference: SwitchPreferenceCompat): CharSequence { + return if (preference.isChecked) summaries[0] else summaries[1] + } + + } } \ No newline at end of file diff --git a/app/src/main/java/cc/chenhe/qqnotifyevo/preference/PreferenceAty.kt b/app/src/main/java/cc/chenhe/qqnotifyevo/preference/PreferenceAty.kt index 8412371..0314739 100644 --- a/app/src/main/java/cc/chenhe/qqnotifyevo/preference/PreferenceAty.kt +++ b/app/src/main/java/cc/chenhe/qqnotifyevo/preference/PreferenceAty.kt @@ -4,6 +4,7 @@ import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import cc.chenhe.qqnotifyevo.R +import cc.chenhe.qqnotifyevo.utils.getShowInRecent class PreferenceAty : AppCompatActivity() { @@ -15,4 +16,12 @@ class PreferenceAty : AppCompatActivity() { .commit() } + override fun onBackPressed() { + if (getShowInRecent(this)) { + super.onBackPressed() + } else { + finishAndRemoveTask() + } + } + } diff --git a/app/src/main/java/cc/chenhe/qqnotifyevo/utils/PreferencesUtils.kt b/app/src/main/java/cc/chenhe/qqnotifyevo/utils/PreferencesUtils.kt index 6fa2360..e80c40e 100644 --- a/app/src/main/java/cc/chenhe/qqnotifyevo/utils/PreferencesUtils.kt +++ b/app/src/main/java/cc/chenhe/qqnotifyevo/utils/PreferencesUtils.kt @@ -79,6 +79,10 @@ fun fetchAvatarCachePeriod(context: Context): LiveData { } } +fun getShowInRecent(context: Context): Boolean { + return PreferenceManager.getDefaultSharedPreferences(context).getBoolean("show_in_recent", true) +} + fun fetchLog(context: Context): SpBooleanLiveData = SpBooleanLiveData(PreferenceManager .getDefaultSharedPreferences(context), "log", false, init = true) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f733962..bc1e40a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -99,6 +99,11 @@ 删除头像缓存 删除 Nevo 通知渠道 + 在「最近使用的应用」中显示 + + 便于锁定最近任务以免被杀 + 按返回键退出后将从最近任务界面隐藏 + 调试 记录日志 开启后将记录应用日志并明文保存在本地,其中包含您的通知详情,请注意隐私安全。\n请不要从应用外部删除日志文件以免影响完整性。 diff --git a/app/src/main/res/xml/pref_advanced.xml b/app/src/main/res/xml/pref_advanced.xml index b4376b9..988ea51 100644 --- a/app/src/main/res/xml/pref_advanced.xml +++ b/app/src/main/res/xml/pref_advanced.xml @@ -16,6 +16,11 @@ app:key="delete_nevo_channel" app:title="@string/pref_delete_nevo_channel" /> + + Date: Wed, 29 Jul 2020 22:06:53 +0800 Subject: [PATCH 03/11] =?UTF-8?q?=E6=96=B0=E5=A2=9ENevo=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=E4=B8=8B=E5=90=88=E5=B9=B6=E6=B6=88=E6=81=AF=E7=9A=84=E6=8F=90?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 7 +-- .../qqnotifyevo/BootCompletedReceiver.kt | 23 --------- .../cc/chenhe/qqnotifyevo/MyApplication.kt | 36 +++++++++----- .../cc/chenhe/qqnotifyevo/StaticReceiver.kt | 26 ++++++++++ .../core/InnerNotificationProcessor.kt | 6 +-- .../core/NevoNotificationProcessor.kt | 46 ++++++++++++++++- .../qqnotifyevo/core/NotificationProcessor.kt | 29 +++++++---- .../qqnotifyevo/preference/AdvancedFr.kt | 38 ++++++++++++++ .../preference/MainPreferenceFr.kt | 5 ++ .../qqnotifyevo/preference/PreferenceAty.kt | 49 ++++++++++++++++++- .../qqnotifyevo/utils/PreferencesUtils.kt | 17 +++++++ .../java/cc/chenhe/qqnotifyevo/utils/Utils.kt | 11 ++++- .../drawable/ic_notify_action_dnot_show.xml | 10 ++++ .../drawable/ic_notify_action_learn_more.xml | 10 ++++ .../drawable/{ic_qq.xml => ic_notify_qq.xml} | 0 .../{ic_qzone.xml => ic_notify_qzone.xml} | 0 .../{ic_tim.xml => ic_notify_tim.xml} | 0 .../main/res/drawable/ic_notify_warning.xml | 10 ++++ app/src/main/res/values/strings.xml | 13 +++++ app/src/main/res/xml/pref_advanced.xml | 4 ++ 20 files changed, 285 insertions(+), 55 deletions(-) delete mode 100644 app/src/main/java/cc/chenhe/qqnotifyevo/BootCompletedReceiver.kt create mode 100644 app/src/main/java/cc/chenhe/qqnotifyevo/StaticReceiver.kt create mode 100644 app/src/main/res/drawable/ic_notify_action_dnot_show.xml create mode 100644 app/src/main/res/drawable/ic_notify_action_learn_more.xml rename app/src/main/res/drawable/{ic_qq.xml => ic_notify_qq.xml} (100%) rename app/src/main/res/drawable/{ic_qzone.xml => ic_notify_qzone.xml} (100%) rename app/src/main/res/drawable/{ic_tim.xml => ic_notify_tim.xml} (100%) create mode 100644 app/src/main/res/drawable/ic_notify_warning.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4d67a5e..e227474 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -61,17 +61,18 @@ android:value="cc.chenhe.qqnotifyevo.preference.PreferenceAty" /> - - + + + android:label="@string/activity_splash" + android:launchMode="singleInstance"> diff --git a/app/src/main/java/cc/chenhe/qqnotifyevo/BootCompletedReceiver.kt b/app/src/main/java/cc/chenhe/qqnotifyevo/BootCompletedReceiver.kt deleted file mode 100644 index 4cc992d..0000000 --- a/app/src/main/java/cc/chenhe/qqnotifyevo/BootCompletedReceiver.kt +++ /dev/null @@ -1,23 +0,0 @@ -package cc.chenhe.qqnotifyevo - -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import cc.chenhe.qqnotifyevo.service.NotificationMonitorService -import cc.chenhe.qqnotifyevo.utils.MODE_LEGACY -import cc.chenhe.qqnotifyevo.utils.getMode - -/** - * 系统启动完成广播接收器 - * # - */ -class BootCompletedReceiver : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - if (intent.action == Intent.ACTION_BOOT_COMPLETED) { - if (getMode(context) == MODE_LEGACY) { - val start = Intent(context, NotificationMonitorService::class.java) - context.startService(start) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/cc/chenhe/qqnotifyevo/MyApplication.kt b/app/src/main/java/cc/chenhe/qqnotifyevo/MyApplication.kt index 7e3b8c5..5f236ef 100644 --- a/app/src/main/java/cc/chenhe/qqnotifyevo/MyApplication.kt +++ b/app/src/main/java/cc/chenhe/qqnotifyevo/MyApplication.kt @@ -1,10 +1,12 @@ package cc.chenhe.qqnotifyevo import android.app.Application +import android.app.NotificationChannel import android.app.NotificationChannelGroup import android.app.NotificationManager -import android.content.Context -import android.os.Build +import android.media.AudioAttributes +import android.media.RingtoneManager +import androidx.core.app.NotificationManagerCompat import cc.chenhe.qqnotifyevo.log.CrashHandler import cc.chenhe.qqnotifyevo.log.ReleaseTree import cc.chenhe.qqnotifyevo.utils.* @@ -70,17 +72,27 @@ class MyApplication : Application() { } private fun registerNotificationChannel() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - Timber.tag(TAG).d("Register system notification channels") - val group = NotificationChannelGroup(NOTIFY_GROUP_ID, getString(R.string.notify_group_base)) - - (getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager)?.apply { - createNotificationChannelGroup(group) - for (channel in getNotificationChannels(this@MyApplication, false)) { - channel.group = group.id - createNotificationChannel(channel) - } + Timber.tag(TAG).d("Register system notification channels") + val group = NotificationChannelGroup(NOTIFY_QQ_GROUP_ID, getString(R.string.notify_group_base)) + + + val att = AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT) + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .build() + val tipChannel = NotificationChannel(NOTIFY_SELF_TIPS_CHANNEL_ID, + getString(R.string.notify_self_tips_channel_name), + NotificationManager.IMPORTANCE_DEFAULT).apply { + setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION), att) + } + + NotificationManagerCompat.from(this).apply { + createNotificationChannelGroup(group) + for (channel in getNotificationChannels(this@MyApplication, false)) { + channel.group = group.id + createNotificationChannel(channel) } + createNotificationChannel(tipChannel) } } diff --git a/app/src/main/java/cc/chenhe/qqnotifyevo/StaticReceiver.kt b/app/src/main/java/cc/chenhe/qqnotifyevo/StaticReceiver.kt new file mode 100644 index 0000000..b2637fb --- /dev/null +++ b/app/src/main/java/cc/chenhe/qqnotifyevo/StaticReceiver.kt @@ -0,0 +1,26 @@ +package cc.chenhe.qqnotifyevo + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import androidx.core.app.NotificationManagerCompat +import cc.chenhe.qqnotifyevo.service.NotificationMonitorService +import cc.chenhe.qqnotifyevo.utils.* + +class StaticReceiver : BroadcastReceiver() { + + override fun onReceive(context: Context, intent: Intent) { + when (intent.action) { + Intent.ACTION_BOOT_COMPLETED -> { + if (getMode(context) == MODE_LEGACY) { + val start = Intent(context, NotificationMonitorService::class.java) + context.startService(start) + } + } + ACTION_MULTI_MSG_DONT_SHOW -> { + NotificationManagerCompat.from(context).cancel(NOTIFY_ID_MULTI_MSG) + nevoMultiMsgTip(context, false) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/cc/chenhe/qqnotifyevo/core/InnerNotificationProcessor.kt b/app/src/main/java/cc/chenhe/qqnotifyevo/core/InnerNotificationProcessor.kt index a0d40dc..cc3bf76 100644 --- a/app/src/main/java/cc/chenhe/qqnotifyevo/core/InnerNotificationProcessor.kt +++ b/app/src/main/java/cc/chenhe/qqnotifyevo/core/InnerNotificationProcessor.kt @@ -1,9 +1,9 @@ package cc.chenhe.qqnotifyevo.core import android.app.Notification -import android.app.NotificationManager import android.content.Context import android.service.notification.StatusBarNotification +import androidx.core.app.NotificationManagerCompat import cc.chenhe.qqnotifyevo.utils.NotifyChannel import timber.log.Timber import java.util.* @@ -51,7 +51,7 @@ class InnerNotificationProcessor( else -> null } Timber.tag(TAG).v("Clear all evolutionary notifications.") - (context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).apply { + NotificationManagerCompat.from(context).apply { ids?.forEach { id -> cancel(id) } ids?.clear() } @@ -59,7 +59,7 @@ class InnerNotificationProcessor( private fun sendNotification(context: Context, @NotificationProcessor.Companion.SourceTag tag: Int, id: Int, notification: Notification) { - (context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).notify(id, notification) + NotificationManagerCompat.from(context).notify(id, notification) addNotifyId(tag, id) } diff --git a/app/src/main/java/cc/chenhe/qqnotifyevo/core/NevoNotificationProcessor.kt b/app/src/main/java/cc/chenhe/qqnotifyevo/core/NevoNotificationProcessor.kt index e06ec71..5cf1d6f 100644 --- a/app/src/main/java/cc/chenhe/qqnotifyevo/core/NevoNotificationProcessor.kt +++ b/app/src/main/java/cc/chenhe/qqnotifyevo/core/NevoNotificationProcessor.kt @@ -1,15 +1,27 @@ package cc.chenhe.qqnotifyevo.core import android.app.Notification +import android.app.PendingIntent import android.content.Context +import android.content.Intent import android.service.notification.StatusBarNotification -import cc.chenhe.qqnotifyevo.utils.NotifyChannel +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import cc.chenhe.qqnotifyevo.R +import cc.chenhe.qqnotifyevo.StaticReceiver +import cc.chenhe.qqnotifyevo.preference.PreferenceAty +import cc.chenhe.qqnotifyevo.utils.* /** * 配合 [cc.chenhe.qqnotifyevo.service.NevoDecorator] 使用的通知处理器,直接创建并返回优化后的通知。 */ class NevoNotificationProcessor(context: Context) : NotificationProcessor(context) { + companion object { + private const val REQ_MULTI_MSG_LEARN_MORE = 1 + private const val REQ_MULTI_MSG_DONT_SHOW = 2 + } + override fun renewQzoneNotification(context: Context, tag: Int, conversation: Conversation, sbn: StatusBarNotification, original: Notification): Notification { return createQZoneNotification(context, tag, conversation, original) @@ -21,4 +33,36 @@ class NevoNotificationProcessor(context: Context) : NotificationProcessor(contex return createConversationNotification(context, tag, channel, conversation, original) } + override fun onMultiMessageDetected() { + super.onMultiMessageDetected() + if (nevoMultiMsgTip(ctx)) { + val dontShow = PendingIntent.getBroadcast(ctx, REQ_MULTI_MSG_DONT_SHOW, + Intent(ctx, StaticReceiver::class.java).also { + it.action = ACTION_MULTI_MSG_DONT_SHOW + }, PendingIntent.FLAG_UPDATE_CURRENT) + + val learnMore = PendingIntent.getActivity(ctx, REQ_MULTI_MSG_LEARN_MORE, + Intent(ctx, PreferenceAty::class.java).also { + it.putExtra(PreferenceAty.EXTRA_NEVO_MULTI_MSG, true) + it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + }, PendingIntent.FLAG_UPDATE_CURRENT) + + val style = NotificationCompat.BigTextStyle() + .setBigContentTitle(ctx.getString(R.string.notify_multi_msg_title)) + .bigText(ctx.getString(R.string.notify_multi_msg_content)) + val n = NotificationCompat.Builder(ctx, NOTIFY_SELF_TIPS_CHANNEL_ID) + .setStyle(style) + .setAutoCancel(true) + .setContentTitle(ctx.getString(R.string.notify_multi_msg_title)) + .setContentText(ctx.getString(R.string.notify_multi_msg_content)) + .setSmallIcon(R.drawable.ic_notify_warning) + .setContentIntent(learnMore) + .addAction(R.drawable.ic_notify_action_dnot_show, ctx.getString(R.string.dont_show), dontShow) + .addAction(R.drawable.ic_notify_action_learn_more, ctx.getString(R.string.learn_more), learnMore) + .build() + + NotificationManagerCompat.from(ctx).notify(NOTIFY_ID_MULTI_MSG, n) + } + } + } \ No newline at end of file diff --git a/app/src/main/java/cc/chenhe/qqnotifyevo/core/NotificationProcessor.kt b/app/src/main/java/cc/chenhe/qqnotifyevo/core/NotificationProcessor.kt index c9a9ff6..d25bb08 100644 --- a/app/src/main/java/cc/chenhe/qqnotifyevo/core/NotificationProcessor.kt +++ b/app/src/main/java/cc/chenhe/qqnotifyevo/core/NotificationProcessor.kt @@ -118,7 +118,7 @@ abstract class NotificationProcessor(context: Context) { } - private val ctx: Context = context.applicationContext + protected val ctx: Context = context.applicationContext private val qzoneSpecialTitle = context.getString(R.string.notify_qzone_special_title) @@ -154,6 +154,13 @@ abstract class NotificationProcessor(context: Context) { } } + /** + * 检测到合并消息的回调。 + * + * 合并消息:有 x 个联系人给你发过来y条新消息 + */ + protected open fun onMultiMessageDetected() {} + /** * 创建优化后的QQ空间通知。 * @@ -210,7 +217,7 @@ abstract class NotificationProcessor(context: Context) { val content = original.extras.getString(Notification.EXTRA_TEXT) val ticker = original.tickerText?.toString() - // 多个消息 + // 合并消息 // title: QQ // ticker: 昵称:内容 // text: 有 x 个联系人给你发过来y条新消息 @@ -223,6 +230,10 @@ abstract class NotificationProcessor(context: Context) { Timber.tag(TAG).v("Title: $title; Ticker: $ticker; QZone: $isQzone; Multi: $isMulti; Content: $content") + if (isMulti) { + onMultiMessageDetected() + } + // 隐藏消息详情 if (ticker != null && ticker == content && hideMsgPattern.matcher(ticker).matches()) { Timber.tag(TAG).v("Hidden message content, skip.") @@ -447,18 +458,18 @@ abstract class NotificationProcessor(context: Context) { private fun setIcon(context: Context, builder: NotificationCompat.Builder, tag: Int, isQzone: Boolean) { if (isQzone) { - builder.setSmallIcon(R.drawable.ic_qzone) + builder.setSmallIcon(R.drawable.ic_notify_qzone) return } when (getIconMode(context)) { ICON_AUTO -> when (tag) { - TAG_QQ, TAG_QQ_LITE -> R.drawable.ic_qq - TAG_TIM -> R.drawable.ic_tim - else -> R.drawable.ic_qq + TAG_QQ, TAG_QQ_LITE -> R.drawable.ic_notify_qq + TAG_TIM -> R.drawable.ic_notify_tim + else -> R.drawable.ic_notify_qq } - ICON_QQ -> R.drawable.ic_qq - ICON_TIM -> R.drawable.ic_tim - else -> R.drawable.ic_qq + ICON_QQ -> R.drawable.ic_notify_qq + ICON_TIM -> R.drawable.ic_notify_tim + else -> R.drawable.ic_notify_qq }.let { iconRes -> builder.setSmallIcon(iconRes) } } diff --git a/app/src/main/java/cc/chenhe/qqnotifyevo/preference/AdvancedFr.kt b/app/src/main/java/cc/chenhe/qqnotifyevo/preference/AdvancedFr.kt index 66fdc8b..bfc5fc2 100644 --- a/app/src/main/java/cc/chenhe/qqnotifyevo/preference/AdvancedFr.kt +++ b/app/src/main/java/cc/chenhe/qqnotifyevo/preference/AdvancedFr.kt @@ -2,8 +2,12 @@ package cc.chenhe.qqnotifyevo.preference import android.content.Intent import android.os.Bundle +import android.provider.Settings +import android.provider.Settings.EXTRA_APP_PACKAGE +import android.provider.Settings.EXTRA_CHANNEL_ID import android.widget.Toast import androidx.appcompat.app.AlertDialog +import androidx.core.app.NotificationManagerCompat import androidx.preference.ListPreference import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat @@ -13,6 +17,7 @@ import cc.chenhe.qqnotifyevo.R import cc.chenhe.qqnotifyevo.core.AvatarManager import cc.chenhe.qqnotifyevo.utils.* + class AdvancedFr : PreferenceFragmentCompat() { private lateinit var deleteLog: Preference @@ -44,6 +49,22 @@ class AdvancedFr : PreferenceFragmentCompat() { override fun onPreferenceTreeClick(preference: Preference?): Boolean { when (preference?.key) { + "reset_tips" -> { + nevoMultiMsgTip(requireContext(), true) + Toast.makeText(requireContext(), R.string.done, Toast.LENGTH_SHORT).show() + if (NotificationManagerCompat.from(requireContext()) + .getNotificationChannel(NOTIFY_SELF_TIPS_CHANNEL_ID)?.importance == + NotificationManagerCompat.IMPORTANCE_NONE) { + AlertDialog.Builder(requireContext()) + .setTitle(R.string.tip) + .setMessage(R.string.pref_reset_tips_notify_dialog) + .setPositiveButton(R.string.confirm) { _, _ -> + openTipsNotificationSetting() + } + .setNegativeButton(R.string.cancel, null) + .show() + } + } "delete_avatar_cache" -> { AvatarManager.get(getAvatarDiskCacheDir(requireContext()), getAvatarCachePeriod(requireContext())) .clearCache() @@ -70,6 +91,23 @@ class AdvancedFr : PreferenceFragmentCompat() { return super.onPreferenceTreeClick(preference) } + private fun openTipsNotificationSetting() { + val intent = Intent().apply { + action = Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS + putExtra(EXTRA_APP_PACKAGE, requireContext().packageName) + putExtra(EXTRA_CHANNEL_ID, NOTIFY_SELF_TIPS_CHANNEL_ID) + } + try { + startActivity(intent) + } catch (e: Exception) { + val b = Intent().apply { + action = Settings.ACTION_APP_NOTIFICATION_SETTINGS + putExtra(EXTRA_APP_PACKAGE, requireContext().packageName) + } + startActivity(b) + } + } + private fun refreshLogSize() { val files = getLogDir(requireContext()).listFiles { f -> f.isFile } val size = files?.sumByDouble { f -> f.length().toDouble() } ?: 0.0 diff --git a/app/src/main/java/cc/chenhe/qqnotifyevo/preference/MainPreferenceFr.kt b/app/src/main/java/cc/chenhe/qqnotifyevo/preference/MainPreferenceFr.kt index 08b52b6..6a31019 100644 --- a/app/src/main/java/cc/chenhe/qqnotifyevo/preference/MainPreferenceFr.kt +++ b/app/src/main/java/cc/chenhe/qqnotifyevo/preference/MainPreferenceFr.kt @@ -11,6 +11,7 @@ import android.widget.Toast import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import androidx.lifecycle.observe +import androidx.preference.ListPreference import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import cc.chenhe.qqnotifyevo.BuildConfig @@ -53,6 +54,10 @@ class MainPreferenceFr : PreferenceFragmentCompat() { findPreference("version_code")?.summary = getString(R.string.pref_version_code, getVersion(ctx)) } + fun setMode(@Mode mode: Int) { + findPreference("mode")?.value = mode.toString() + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) diff --git a/app/src/main/java/cc/chenhe/qqnotifyevo/preference/PreferenceAty.kt b/app/src/main/java/cc/chenhe/qqnotifyevo/preference/PreferenceAty.kt index 0314739..72ba373 100644 --- a/app/src/main/java/cc/chenhe/qqnotifyevo/preference/PreferenceAty.kt +++ b/app/src/main/java/cc/chenhe/qqnotifyevo/preference/PreferenceAty.kt @@ -1,19 +1,64 @@ package cc.chenhe.qqnotifyevo.preference +import android.content.Intent import android.os.Bundle - +import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.NotificationManagerCompat import cc.chenhe.qqnotifyevo.R +import cc.chenhe.qqnotifyevo.StaticReceiver +import cc.chenhe.qqnotifyevo.utils.ACTION_MULTI_MSG_DONT_SHOW +import cc.chenhe.qqnotifyevo.utils.MODE_LEGACY +import cc.chenhe.qqnotifyevo.utils.NOTIFY_ID_MULTI_MSG import cc.chenhe.qqnotifyevo.utils.getShowInRecent class PreferenceAty : AppCompatActivity() { + companion object { + /** + * 由 Nevo 模式下检测到合并消息所发出使用提示的通知跳转过来。 + * + * 值为 [Boolean] 类型 = true. + */ + const val EXTRA_NEVO_MULTI_MSG = "nevo_multi_msg" + } + + private lateinit var mainPreferenceFr: MainPreferenceFr + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.preference_layout) + mainPreferenceFr = MainPreferenceFr() supportFragmentManager.beginTransaction() - .replace(R.id.frameLayout, MainPreferenceFr()) + .replace(R.id.frameLayout, mainPreferenceFr) .commit() + + showNevoMultiMsgDialogIfNeeded() + } + + override fun onNewIntent(intent: Intent?) { + super.onNewIntent(intent) + setIntent(intent) + showNevoMultiMsgDialogIfNeeded() + } + + private fun showNevoMultiMsgDialogIfNeeded() { + if (intent?.extras?.getBoolean(EXTRA_NEVO_MULTI_MSG, false) == true) { + NotificationManagerCompat.from(this).cancel(NOTIFY_ID_MULTI_MSG) + AlertDialog.Builder(this) + .setTitle(R.string.tip) + .setMessage(R.string.multi_msg_dialog) + .setNeutralButton(R.string.multi_msg_dialog_neutral, null) + .setPositiveButton(R.string.multi_msg_dialog_positive) { _, _ -> + mainPreferenceFr.setMode(MODE_LEGACY) + } + .setNegativeButton(R.string.dont_show) { _, _ -> + sendBroadcast(Intent(this, StaticReceiver::class.java).also { + it.action = ACTION_MULTI_MSG_DONT_SHOW + }) + } + .show() + } } override fun onBackPressed() { diff --git a/app/src/main/java/cc/chenhe/qqnotifyevo/utils/PreferencesUtils.kt b/app/src/main/java/cc/chenhe/qqnotifyevo/utils/PreferencesUtils.kt index e80c40e..3908420 100644 --- a/app/src/main/java/cc/chenhe/qqnotifyevo/utils/PreferencesUtils.kt +++ b/app/src/main/java/cc/chenhe/qqnotifyevo/utils/PreferencesUtils.kt @@ -5,6 +5,7 @@ import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.os.Build import androidx.annotation.IntDef +import androidx.core.content.edit import androidx.lifecycle.LiveData import androidx.lifecycle.Transformations import androidx.preference.PreferenceManager @@ -35,6 +36,22 @@ const val ICON_TIM = 2 @IntDef(ICON_AUTO, ICON_QQ, ICON_TIM) annotation class Icon +// --------------------------------------------------------- +// Tips +// --------------------------------------------------------- +private const val PREF_NEVO_MULTI_MSG_TIP = "tip_nevo_multi_msg" + + +fun nevoMultiMsgTip(context: Context, shouldShow: Boolean) { + PreferenceManager.getDefaultSharedPreferences(context).edit { + putBoolean(PREF_NEVO_MULTI_MSG_TIP, shouldShow) + } +} + +fun nevoMultiMsgTip(context: Context): Boolean = PreferenceManager + .getDefaultSharedPreferences(context).getBoolean(PREF_NEVO_MULTI_MSG_TIP, true) + + // --------------------------------------------------------- // Functions // --------------------------------------------------------- diff --git a/app/src/main/java/cc/chenhe/qqnotifyevo/utils/Utils.kt b/app/src/main/java/cc/chenhe/qqnotifyevo/utils/Utils.kt index d971994..78c3c46 100644 --- a/app/src/main/java/cc/chenhe/qqnotifyevo/utils/Utils.kt +++ b/app/src/main/java/cc/chenhe/qqnotifyevo/utils/Utils.kt @@ -15,14 +15,21 @@ import java.io.File const val ACTION_DELETE_NEVO_CHANNEL = "deleteNevoChannel" +/** 不再显示关于 Nevo模式下遇到合并消息的使用提示。此 ACTION 用于注册静态接收器。 */ +const val ACTION_MULTI_MSG_DONT_SHOW = "dnotShowNevoMultiMsgTips" + // Android O+ 通知渠道 id const val NOTIFY_FRIEND_CHANNEL_ID = "QQ_Friend" const val NOTIFY_FRIEND_SPECIAL_CHANNEL_ID = "QQ_Friend_Special" const val NOTIFY_GROUP_CHANNEL_ID = "QQ_Group" const val NOTIFY_QZONE_CHANNEL_ID = "QQ_Zone" +const val NOTIFY_SELF_TIPS_CHANNEL_ID = "Tips" + +/** Nevo 模式下检测到合并消息的提示。 */ +const val NOTIFY_ID_MULTI_MSG = 100 -// 自身默认的通知类别 -const val NOTIFY_GROUP_ID = "base" +// 自身转发QQ消息的通知渠道组 +const val NOTIFY_QQ_GROUP_ID = "base" const val GITHUB_URL = "https://github.com/liangchenhe55/QQ-Notify-Evolution/releases" const val MANUAL_URL = "https://github.com/liangchenhe55/QQ-Notify-Evolution/wiki" diff --git a/app/src/main/res/drawable/ic_notify_action_dnot_show.xml b/app/src/main/res/drawable/ic_notify_action_dnot_show.xml new file mode 100644 index 0000000..138c8c6 --- /dev/null +++ b/app/src/main/res/drawable/ic_notify_action_dnot_show.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_notify_action_learn_more.xml b/app/src/main/res/drawable/ic_notify_action_learn_more.xml new file mode 100644 index 0000000..fed2fb1 --- /dev/null +++ b/app/src/main/res/drawable/ic_notify_action_learn_more.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_qq.xml b/app/src/main/res/drawable/ic_notify_qq.xml similarity index 100% rename from app/src/main/res/drawable/ic_qq.xml rename to app/src/main/res/drawable/ic_notify_qq.xml diff --git a/app/src/main/res/drawable/ic_qzone.xml b/app/src/main/res/drawable/ic_notify_qzone.xml similarity index 100% rename from app/src/main/res/drawable/ic_qzone.xml rename to app/src/main/res/drawable/ic_notify_qzone.xml diff --git a/app/src/main/res/drawable/ic_tim.xml b/app/src/main/res/drawable/ic_notify_tim.xml similarity index 100% rename from app/src/main/res/drawable/ic_tim.xml rename to app/src/main/res/drawable/ic_notify_tim.xml diff --git a/app/src/main/res/drawable/ic_notify_warning.xml b/app/src/main/res/drawable/ic_notify_warning.xml new file mode 100644 index 0000000..9832cee --- /dev/null +++ b/app/src/main/res/drawable/ic_notify_warning.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bc1e40a..3a6e446 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -4,6 +4,8 @@ 已发送请求 操作完成 提示 + 不再提示 + 了解详情 小时 @@ -19,6 +21,13 @@ QQ 通知进化 适配原生分渠道通知;使用原生铃声与振动适配可穿戴设备;支持多组会话与历史消息。 + + 检测到合并的消息 + 对于旧版 QQ 或 TIM 等会自动合并会话的版本,建议使用传统模式。 + 对于旧版 QQ 或 TIM 等部分版本,若有多个未读会话将合并为「有x个联系人给你发过来y条新消息」一个通知。\n由于 Nevo 限制此情况只能显示最近的一个会话,建议改用传统模式或更新 QQ 版本。 + 使用传统模式 + 下次再说 + 仅传统模式有效,Nevo 模式请前往 Nevo 应用设置 Q进化- @@ -30,6 +39,8 @@ QQ 群消息通知 空间动态 QQ 空间动态通知 + + 使用提示 空间动态 特别关心动态 @@ -97,6 +108,8 @@ 86400000 604800000 + 重置使用提示 + 使用提示通知已被关闭,是否打开? 删除头像缓存 删除 Nevo 通知渠道 在「最近使用的应用」中显示 diff --git a/app/src/main/res/xml/pref_advanced.xml b/app/src/main/res/xml/pref_advanced.xml index 988ea51..e7bad3e 100644 --- a/app/src/main/res/xml/pref_advanced.xml +++ b/app/src/main/res/xml/pref_advanced.xml @@ -16,6 +16,10 @@ app:key="delete_nevo_channel" app:title="@string/pref_delete_nevo_channel" /> + + Date: Fri, 31 Jul 2020 13:43:39 +0800 Subject: [PATCH 04/11] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E7=89=B9=E5=88=AB=E5=85=B3=E5=BF=83=E9=80=9A=E7=9F=A5=E5=89=8D?= =?UTF-8?q?=E7=BC=80=E9=80=89=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../qqnotifyevo/core/NotificationProcessor.kt | 72 +++++++++++++--- .../qqnotifyevo/utils/PreferencesUtils.kt | 3 + app/src/main/res/values/strings.xml | 82 ++++++++++++------- app/src/main/res/xml/pref_advanced.xml | 58 +++++++------ .../core/NotificationProcessorTest.kt | 23 ++++++ 5 files changed, 172 insertions(+), 66 deletions(-) diff --git a/app/src/main/java/cc/chenhe/qqnotifyevo/core/NotificationProcessor.kt b/app/src/main/java/cc/chenhe/qqnotifyevo/core/NotificationProcessor.kt index d25bb08..958c005 100644 --- a/app/src/main/java/cc/chenhe/qqnotifyevo/core/NotificationProcessor.kt +++ b/app/src/main/java/cc/chenhe/qqnotifyevo/core/NotificationProcessor.kt @@ -56,7 +56,7 @@ abstract class NotificationProcessor(context: Context) { // 群聊消息 // title: 群名 | 群名 (x条新消息) // ticker: 昵称(群名):消息内容 - // text: 昵称: 消息内容 + // text: 昵称: 消息内容 //特别关注前缀:[有关注的内容] /** * 匹配群聊消息 Ticker. @@ -68,6 +68,14 @@ abstract class NotificationProcessor(context: Context) { @VisibleForTesting val groupMsgPattern: Pattern = Pattern.compile("^(.+?)\\((.+)\\):([\\s\\S]+)$") + /** + * 匹配群聊消息 Content. + * + * Group: 1[有关注的内容 + */ + @VisibleForTesting + val groupMsgContentPattern: Pattern = Pattern.compile("^(\\[有关注的内容])?[\\s\\S]+") + // 私聊消息 // title: 昵称 | 昵称 (x条新消息) //特别关心前缀:[特别关心] // ticker: 昵称: 消息内容 @@ -250,7 +258,7 @@ abstract class NotificationProcessor(context: Context) { getNotifyLargeIcon(context, original)) conversation = addMessage(tag, qzoneSpecialTitle, content, null, avatarManager.getAvatar(CONVERSATION_NAME_QZONE_SPECIAL.hashCode()), original.contentIntent, - original.deleteIntent) + original.deleteIntent, false) // 由于特别关心动态推送的通知没有显示未读消息个数,所以这里无法提取并删除多余的历史消息。 // Workaround: 在通知删除回调下来匹配并清空特别关心动态历史记录。 Timber.tag(TAG).d("[QZoneSpecial] Ticker: $ticker") @@ -259,7 +267,7 @@ abstract class NotificationProcessor(context: Context) { avatarManager.saveAvatar(CONVERSATION_NAME_QZONE.hashCode(), getNotifyLargeIcon(context, original)) conversation = addMessage(tag, qzoneSpecialTitle, content, null, avatarManager.getAvatar(CONVERSATION_NAME_QZONE.hashCode()), original.contentIntent, - original.deleteIntent) + original.deleteIntent, false) deleteOldMessage(conversation, num) Timber.tag(TAG).d("[QZone] Ticker: $ticker") } @@ -277,12 +285,16 @@ abstract class NotificationProcessor(context: Context) { val name = matcher.group(1) ?: return null val groupName = matcher.group(2) ?: return null val text = matcher.group(3) ?: return null + + val contentMatcher = groupMsgContentPattern.matcher(content!!) + val special = contentMatcher.matches() && contentMatcher.group(1) != null + if (!isMulti) avatarManager.saveAvatar(groupName.hashCode(), getNotifyLargeIcon(context, original)) - val conversation = addMessage(tag, name, text, groupName, - avatarManager.getAvatar(name.hashCode()), original.contentIntent, original.deleteIntent) + val conversation = addMessage(tag, name, text, groupName, avatarManager.getAvatar(name.hashCode()), + original.contentIntent, original.deleteIntent, special) deleteOldMessage(conversation, if (isMulti) 0 else matchMessageNum(title)) - Timber.tag(TAG).d("[Group] Name: $name; Group: $groupName; Text: $text") + Timber.tag(TAG).d("[${if (special) "GroupS" else "Group"}] Name: $name; Group: $groupName; Text: $text") return renewConversionNotification(context, tag, NotifyChannel.GROUP, conversation, sbn, original) } } @@ -297,10 +309,10 @@ abstract class NotificationProcessor(context: Context) { if (!isMulti) avatarManager.saveAvatar(name.hashCode(), getNotifyLargeIcon(context, original)) val conversation = addMessage(tag, name, text, null, avatarManager.getAvatar(name.hashCode()), - original.contentIntent, original.deleteIntent) + original.contentIntent, original.deleteIntent, special) deleteOldMessage(conversation, if (isMulti) 0 else matchMessageNum(titleMatcher)) return if (special) { - Timber.tag(TAG).d("[Special] Name: $name; Text: $text") + Timber.tag(TAG).d("[FriendS] Name: $name; Text: $text") renewConversionNotification(context, tag, NotifyChannel.FRIEND_SPECIAL, conversation, sbn, original) } else { Timber.tag(TAG).d("[Friend] Name: $name; Text: $text") @@ -422,7 +434,7 @@ abstract class NotificationProcessor(context: Context) { val style = NotificationCompat.MessagingStyle(Person.Builder() .setName(context.getString(R.string.notify_qzone_title)).build()) conversation.messages.forEach { msg -> - style.addMessage(msg.content, msg.time, msg.person) + style.addMessage(msg) } val num = conversation.messages.size val subtext = if (num > 1) context.getString(R.string.notify_subtext_qzone_num, num) else null @@ -446,7 +458,7 @@ abstract class NotificationProcessor(context: Context) { style.isGroupConversation = true } conversation.messages.forEach { msg -> - style.addMessage(msg.content, msg.time, msg.person) + style.addMessage(msg) } val num = conversation.messages.size val subtext = if (num > 1) context.getString(R.string.notify_subtext_message_num, num) else null @@ -455,6 +467,34 @@ abstract class NotificationProcessor(context: Context) { avatarManager.getAvatar(conversation.name.hashCode()), original, subtext) } + private fun NotificationCompat.MessagingStyle.addMessage(message: Message) { + var name = message.person.name + if (message.special && showSpecialPrefix(ctx)) { + // 添加特别关心或关注前缀 + name = if (isGroupConversation) + ctx.getString(R.string.special_group_prefix) + name + else + ctx.getString(R.string.special_prefix) + name + } + + val person = if (name == message.person.name) { + message.person + } else { + message.person.clone(name) + } + addMessage(message.content, message.time, person) + } + + private fun Person.clone(newName: CharSequence? = null): Person { + return Person.Builder() + .setBot(this.isBot) + .setIcon(this.icon) + .setImportant(this.isImportant) + .setKey(this.key) + .setName(newName ?: this.name) + .setUri(this.uri) + .build() + } private fun setIcon(context: Context, builder: NotificationCompat.Builder, tag: Int, isQzone: Boolean) { if (isQzone) { @@ -488,9 +528,14 @@ abstract class NotificationProcessor(context: Context) { /** * 加入历史消息记录。 + * + * @param name 发送者昵称。 + * @param content 消息内容。 + * @param group 群组名。`null` 表示非群组消息。 + * @param special 是否来自特别关心或特别关注。 */ private fun addMessage(@SourceTag tag: Int, name: String, content: String, group: String?, icon: Bitmap?, - contentIntent: PendingIntent, deleteIntent: PendingIntent): Conversation { + contentIntent: PendingIntent, deleteIntent: PendingIntent, special: Boolean): Conversation { var conversation: Conversation? = null // 以会话名为标准寻找已存在的会话 for (item in getHistoryMessage(tag)) { @@ -511,7 +556,7 @@ abstract class NotificationProcessor(context: Context) { conversation = Conversation(group != null, group ?: name, contentIntent, deleteIntent) getHistoryMessage(tag).add(conversation) } - conversation.messages.add(Message(name, icon, content)) + conversation.messages.add(Message(name, icon, content, special)) return conversation } @@ -544,8 +589,9 @@ abstract class NotificationProcessor(context: Context) { * @param name 发送者昵称。 * @param icon 头像。 * @param content 消息内容。 + * @param special 是否来自特别关心或特别关注。仅在聊天消息中有效。 */ - protected data class Message(val name: String, val icon: Bitmap?, val content: String) { + protected data class Message(val name: String, val icon: Bitmap?, val content: String, val special: Boolean) { val person: Person = Person.Builder() .setIcon(icon?.let { IconCompat.createWithBitmap(it) }) .setName(name) diff --git a/app/src/main/java/cc/chenhe/qqnotifyevo/utils/PreferencesUtils.kt b/app/src/main/java/cc/chenhe/qqnotifyevo/utils/PreferencesUtils.kt index 3908420..6b6d39c 100644 --- a/app/src/main/java/cc/chenhe/qqnotifyevo/utils/PreferencesUtils.kt +++ b/app/src/main/java/cc/chenhe/qqnotifyevo/utils/PreferencesUtils.kt @@ -84,6 +84,9 @@ fun getIconMode(context: Context): Int { } } +fun showSpecialPrefix(context: Context): Boolean = PreferenceManager.getDefaultSharedPreferences(context) + .getBoolean("show_special_prefix", false) + fun getAvatarCachePeriod(context: Context): Long { val s = PreferenceManager.getDefaultSharedPreferences(context).getString("avatar_cache_period", "0") ?: "0" return s.toLong() diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3a6e446..89402f5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -6,6 +6,8 @@ 提示 不再提示 了解详情 + [特别关心] + [特别关注] 小时 @@ -28,7 +30,11 @@ 使用传统模式 下次再说 - + + + 仅传统模式有效,Nevo 模式请前往 Nevo 应用设置 Q进化- 联系人消息 @@ -39,9 +45,14 @@ QQ 群消息通知 空间动态 QQ 空间动态通知 - + 使用提示 + + + 空间动态 特别关心动态 %1$d条新消息 @@ -52,7 +63,11 @@ 通知监听服务未运行 请到「必要权限」里授予通知访问权,并确认已允许本应用自启动/后台运行。 - + + + 基础 工作模式 @@ -63,7 +78,6 @@ 1 2 - 必要权限 通知访问权 @@ -76,7 +90,6 @@ 已禁用 已停用 未停用 - 通知 系统设置 @@ -94,9 +107,41 @@ 1 2 + + 关于 + 使用手册 + 模式选择 · 双重通知 · 最佳实践 + 捐助 + 请选择扶贫方式 + + 支付宝 + + 打开支付宝失败 + 开放源代码 + 版本号:%1$s + 关于 + + + + GitHub 发布页 + + - 高级选项 + + 通知 + 显示特别关心前缀 + 添加[特别关心]或群聊中[特别关注]前缀 + + 其他 头像缓存刷新间隔 10分钟 @@ -117,34 +162,11 @@ 便于锁定最近任务以免被杀 按返回键退出后将从最近任务界面隐藏 + 调试 记录日志 开启后将记录应用日志并明文保存在本地,其中包含您的通知详情,请注意隐私安全。\n请不要从应用外部删除日志文件以免影响完整性。 删除日志 %1$d个日志 总大小%2$s 删除所有日志? - - - 关于 - 使用手册 - 模式选择 · 双重通知 · 最佳实践 - 捐助 - 请选择扶贫方式 - - 支付宝 - - 打开支付宝失败 - 开放源代码 - 版本号:%1$s - 关于 - - - - GitHub 发布页 diff --git a/app/src/main/res/xml/pref_advanced.xml b/app/src/main/res/xml/pref_advanced.xml index e7bad3e..4ad34ad 100644 --- a/app/src/main/res/xml/pref_advanced.xml +++ b/app/src/main/res/xml/pref_advanced.xml @@ -1,29 +1,41 @@ - - - - - - - - - + + + + + + + + + + + + + + + + + diff --git a/app/src/test/java/cc/chenhe/qqnotifyevo/core/NotificationProcessorTest.kt b/app/src/test/java/cc/chenhe/qqnotifyevo/core/NotificationProcessorTest.kt index 777c8c0..a406833 100644 --- a/app/src/test/java/cc/chenhe/qqnotifyevo/core/NotificationProcessorTest.kt +++ b/app/src/test/java/cc/chenhe/qqnotifyevo/core/NotificationProcessorTest.kt @@ -9,6 +9,11 @@ class NotificationProcessorTest { return "$nickName($groupName):$message" } + private fun generateGroupContent(nickName: String, message: String, special: Boolean): String { + val prefix = if (special) "[有关注的内容]" else "" + return "$prefix$nickName: $message" + } + private fun generateFriendTicker(nickName: String, message: String): String { return "$nickName: $message" } @@ -54,6 +59,24 @@ class NotificationProcessorTest { matcher.matches().shouldBeFalse() } + @Test + fun group_special_content_match() { + val content = generateGroupContent("(id1)", "Yea", true) + val matcher = NotificationProcessor.groupMsgContentPattern.matcher(content) + + matcher.matches().shouldBeTrue() + matcher.group(1).shouldNotBeNullOrEmpty() + } + + @Test + fun group_nonSpecial_content_match() { + val content = generateGroupContent("(id1)", "Yea", false) + val matcher = NotificationProcessor.groupMsgContentPattern.matcher(content) + + matcher.matches().shouldBeTrue() + matcher.group(1).shouldBeNull() + } + @Test fun friend_ticker_match() { val ticker = generateFriendTicker("Alice", "hi") From cbd5cead583929f1b0c17d508479a1655ac3baa8 Mon Sep 17 00:00:00 2001 From: Chenhe Date: Fri, 31 Jul 2020 17:38:03 +0800 Subject: [PATCH 05/11] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E7=89=B9=E5=88=AB?= =?UTF-8?q?=E5=85=B3=E6=B3=A8=E7=BE=A4=E6=B6=88=E6=81=AF=E7=9A=84=E9=80=9A?= =?UTF-8?q?=E7=9F=A5=E6=B8=A0=E9=81=93=E9=80=89=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../qqnotifyevo/core/NotificationProcessor.kt | 6 +++- .../qqnotifyevo/utils/PreferencesUtils.kt | 30 ++++++++++++------- app/src/main/res/values/strings.xml | 9 ++++++ app/src/main/res/xml/pref_advanced.xml | 8 +++++ 4 files changed, 41 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/cc/chenhe/qqnotifyevo/core/NotificationProcessor.kt b/app/src/main/java/cc/chenhe/qqnotifyevo/core/NotificationProcessor.kt index 958c005..99c07e0 100644 --- a/app/src/main/java/cc/chenhe/qqnotifyevo/core/NotificationProcessor.kt +++ b/app/src/main/java/cc/chenhe/qqnotifyevo/core/NotificationProcessor.kt @@ -295,7 +295,11 @@ abstract class NotificationProcessor(context: Context) { original.contentIntent, original.deleteIntent, special) deleteOldMessage(conversation, if (isMulti) 0 else matchMessageNum(title)) Timber.tag(TAG).d("[${if (special) "GroupS" else "Group"}] Name: $name; Group: $groupName; Text: $text") - return renewConversionNotification(context, tag, NotifyChannel.GROUP, conversation, sbn, original) + val channel = if (special && specialGroupMsgChannel(ctx)) + NotifyChannel.FRIEND_SPECIAL + else + NotifyChannel.GROUP + return renewConversionNotification(context, tag, channel, conversation, sbn, original) } } diff --git a/app/src/main/java/cc/chenhe/qqnotifyevo/utils/PreferencesUtils.kt b/app/src/main/java/cc/chenhe/qqnotifyevo/utils/PreferencesUtils.kt index 6b6d39c..fe0cb0a 100644 --- a/app/src/main/java/cc/chenhe/qqnotifyevo/utils/PreferencesUtils.kt +++ b/app/src/main/java/cc/chenhe/qqnotifyevo/utils/PreferencesUtils.kt @@ -1,6 +1,7 @@ package cc.chenhe.qqnotifyevo.utils import android.content.Context +import android.content.SharedPreferences import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.os.Build @@ -36,6 +37,8 @@ const val ICON_TIM = 2 @IntDef(ICON_AUTO, ICON_QQ, ICON_TIM) annotation class Icon +private fun sp(context: Context): SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) + // --------------------------------------------------------- // Tips // --------------------------------------------------------- @@ -43,7 +46,7 @@ private const val PREF_NEVO_MULTI_MSG_TIP = "tip_nevo_multi_msg" fun nevoMultiMsgTip(context: Context, shouldShow: Boolean) { - PreferenceManager.getDefaultSharedPreferences(context).edit { + sp(context).edit { putBoolean(PREF_NEVO_MULTI_MSG_TIP, shouldShow) } } @@ -58,7 +61,7 @@ fun nevoMultiMsgTip(context: Context): Boolean = PreferenceManager @Mode fun getMode(context: Context): Int { - val mode = PreferenceManager.getDefaultSharedPreferences(context).getString("mode", "0") ?: "0" + val mode = sp(context).getString("mode", "0") ?: "0" return when (mode.toInt()) { 1 -> MODE_NEVO 2 -> MODE_LEGACY @@ -67,7 +70,7 @@ fun getMode(context: Context): Int { } fun fetchMode(context: Context): LiveData { - val source = SpStringLiveData(PreferenceManager.getDefaultSharedPreferences(context), "mode", "0", true) + val source = SpStringLiveData(sp(context), "mode", "0", true) return Transformations.map(source) { src -> src!!.toInt() } @@ -75,7 +78,7 @@ fun fetchMode(context: Context): LiveData { @Icon fun getIconMode(context: Context): Int { - val icon = PreferenceManager.getDefaultSharedPreferences(context).getString("icon_mode", "0") ?: "0" + val icon = sp(context).getString("icon_mode", "0") ?: "0" return when (icon.toInt()) { 0 -> ICON_AUTO 1 -> ICON_QQ @@ -84,27 +87,32 @@ fun getIconMode(context: Context): Int { } } -fun showSpecialPrefix(context: Context): Boolean = PreferenceManager.getDefaultSharedPreferences(context) - .getBoolean("show_special_prefix", false) +fun showSpecialPrefix(context: Context): Boolean = sp(context).getBoolean("show_special_prefix", false) + +/** + * 特别关注的群消息通知渠道。 + * + * @return `true` 为特别关心渠道,`false` 为群消息渠道。 + */ +fun specialGroupMsgChannel(context: Context): Boolean = sp(context).getString("special_group_channel", "group") == "special" fun getAvatarCachePeriod(context: Context): Long { - val s = PreferenceManager.getDefaultSharedPreferences(context).getString("avatar_cache_period", "0") ?: "0" + val s = sp(context).getString("avatar_cache_period", "0") ?: "0" return s.toLong() } fun fetchAvatarCachePeriod(context: Context): LiveData { - val source = SpStringLiveData(PreferenceManager.getDefaultSharedPreferences(context), "avatar_cache_period", "0", true) + val source = SpStringLiveData(sp(context), "avatar_cache_period", "0", true) return Transformations.map(source) { src -> src?.toLong() ?: 0L } } fun getShowInRecent(context: Context): Boolean { - return PreferenceManager.getDefaultSharedPreferences(context).getBoolean("show_in_recent", true) + return sp(context).getBoolean("show_in_recent", true) } -fun fetchLog(context: Context): SpBooleanLiveData = SpBooleanLiveData(PreferenceManager - .getDefaultSharedPreferences(context), "log", false, init = true) +fun fetchLog(context: Context): SpBooleanLiveData = SpBooleanLiveData(sp(context), "log", false, init = true) fun getVersion(context: Context): String { var versionName = "" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 89402f5..65ece0b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -140,6 +140,15 @@ 通知 显示特别关心前缀 添加[特别关心]或群聊中[特别关注]前缀 + 特别关注群消息的通知渠道 + + 群消息 + 特别关心消息 + + + group + special + 其他 头像缓存刷新间隔 diff --git a/app/src/main/res/xml/pref_advanced.xml b/app/src/main/res/xml/pref_advanced.xml index 4ad34ad..483d92e 100644 --- a/app/src/main/res/xml/pref_advanced.xml +++ b/app/src/main/res/xml/pref_advanced.xml @@ -8,6 +8,14 @@ app:key="show_special_prefix" app:summary="@string/pref_advanced_show_special_prefix_summary" app:title="@string/pref_advanced_show_special_prefix" /> + + From ab3e87eb7bce8d7327dbc33f9dada9f6b282c509 Mon Sep 17 00:00:00 2001 From: Chenhe Date: Fri, 31 Jul 2020 18:34:48 +0800 Subject: [PATCH 06/11] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E5=8C=96=E6=98=B5=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../qqnotifyevo/core/NotificationProcessor.kt | 24 ++++++++++++++++++- .../qqnotifyevo/preference/AdvancedFr.kt | 21 ++++++++++++---- .../qqnotifyevo/utils/PreferencesUtils.kt | 14 +++++++++++ app/src/main/res/values/strings.xml | 4 ++++ app/src/main/res/xml/pref_advanced.xml | 13 ++++++++++ 5 files changed, 71 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/cc/chenhe/qqnotifyevo/core/NotificationProcessor.kt b/app/src/main/java/cc/chenhe/qqnotifyevo/core/NotificationProcessor.kt index 99c07e0..a25fd92 100644 --- a/app/src/main/java/cc/chenhe/qqnotifyevo/core/NotificationProcessor.kt +++ b/app/src/main/java/cc/chenhe/qqnotifyevo/core/NotificationProcessor.kt @@ -472,7 +472,10 @@ abstract class NotificationProcessor(context: Context) { } private fun NotificationCompat.MessagingStyle.addMessage(message: Message) { - var name = message.person.name + var name = message.person.name!! + + name = formatNicknameIfNeeded(name) + if (message.special && showSpecialPrefix(ctx)) { // 添加特别关心或关注前缀 name = if (isGroupConversation) @@ -489,6 +492,25 @@ abstract class NotificationProcessor(context: Context) { addMessage(message.content, message.time, person) } + private fun formatNicknameIfNeeded(name: CharSequence): CharSequence { + if (!wrapNickname(ctx)) { + return name + } + var newName = name + val wrapper = nicknameWrapper(ctx) + if (wrapper != null) { + newName = wrapper.replace("\$n", name.toString()) + if (newName == wrapper) { + Timber.tag(TAG).e("Nickname wrapper is invalid, reset preference. wrapper=$wrapper") + resetNicknameWrapper(ctx) + } + } else { + Timber.tag(TAG).e("Nickname wrapper is null, reset preference.") + resetNicknameWrapper(ctx) + } + return newName + } + private fun Person.clone(newName: CharSequence? = null): Person { return Person.Builder() .setBot(this.isBot) diff --git a/app/src/main/java/cc/chenhe/qqnotifyevo/preference/AdvancedFr.kt b/app/src/main/java/cc/chenhe/qqnotifyevo/preference/AdvancedFr.kt index bfc5fc2..1055e6d 100644 --- a/app/src/main/java/cc/chenhe/qqnotifyevo/preference/AdvancedFr.kt +++ b/app/src/main/java/cc/chenhe/qqnotifyevo/preference/AdvancedFr.kt @@ -8,10 +8,7 @@ import android.provider.Settings.EXTRA_CHANNEL_ID import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.core.app.NotificationManagerCompat -import androidx.preference.ListPreference -import androidx.preference.Preference -import androidx.preference.PreferenceFragmentCompat -import androidx.preference.SwitchPreferenceCompat +import androidx.preference.* import cc.chenhe.qqnotifyevo.MyApplication import cc.chenhe.qqnotifyevo.R import cc.chenhe.qqnotifyevo.core.AvatarManager @@ -25,6 +22,22 @@ class AdvancedFr : PreferenceFragmentCompat() { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.pref_advanced, rootKey) + findPreference("nickname_wrapper")!!.apply { + setOnBindEditTextListener { et -> et.isSingleLine = true } + setOnPreferenceChangeListener { _, new -> + val newWrapper: String = new as? String ?: "" + if (newWrapper.indexOf("\$n") == -1) { + AlertDialog.Builder(requireContext()) + .setTitle(R.string.tip) + .setMessage(R.string.pref_advanced_nickname_wrapper_invalid_message) + .setPositiveButton(R.string.confirm, null) + .show() + false + } else { + true + } + } + } findPreference("avatar_cache_period")!!.summaryProvider = AvatarCachePeriodSummaryProvider() findPreference("log")!!.setOnPreferenceChangeListener { pref, new -> if (new as Boolean) { diff --git a/app/src/main/java/cc/chenhe/qqnotifyevo/utils/PreferencesUtils.kt b/app/src/main/java/cc/chenhe/qqnotifyevo/utils/PreferencesUtils.kt index fe0cb0a..b3b4db8 100644 --- a/app/src/main/java/cc/chenhe/qqnotifyevo/utils/PreferencesUtils.kt +++ b/app/src/main/java/cc/chenhe/qqnotifyevo/utils/PreferencesUtils.kt @@ -96,6 +96,20 @@ fun showSpecialPrefix(context: Context): Boolean = sp(context).getBoolean("show_ */ fun specialGroupMsgChannel(context: Context): Boolean = sp(context).getString("special_group_channel", "group") == "special" +fun wrapNickname(context: Context): Boolean = sp(context).getBoolean("wrap_nickname", false) + +fun nicknameWrapper(context: Context): String? = sp(context).getString("nickname_wrapper", null) + +/** + * 禁用格式化昵称,且将格式重置为默认值。 + */ +fun resetNicknameWrapper(context: Context) { + sp(context).edit { + putBoolean("wrap_nickname", false) + putString("nickname_wrapper", "[\$n]") + } +} + fun getAvatarCachePeriod(context: Context): Long { val s = sp(context).getString("avatar_cache_period", "0") ?: "0" return s.toLong() diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 65ece0b..e7ab1c5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -149,6 +149,10 @@ group special + 格式化昵称 + 自定义格式 + 使用 $n 指代原始昵称,其他字符将原样保留。如果你喜欢输入 Emoji 也行。 + 自定义格式必须包含 $n 变量。 其他 头像缓存刷新间隔 diff --git a/app/src/main/res/xml/pref_advanced.xml b/app/src/main/res/xml/pref_advanced.xml index 483d92e..3668e80 100644 --- a/app/src/main/res/xml/pref_advanced.xml +++ b/app/src/main/res/xml/pref_advanced.xml @@ -16,6 +16,19 @@ app:key="special_group_channel" app:title="@string/pref_advanced_special_group_channel" app:useSimpleSummaryProvider="true" /> + + + + From 104d7422f3b0e20dde1d470e37bbb65ba771ddaa Mon Sep 17 00:00:00 2001 From: Chenhe Date: Sat, 1 Aug 2020 17:06:40 +0800 Subject: [PATCH 07/11] =?UTF-8?q?=E5=AE=8C=E6=88=90=E5=BA=94=E7=94=A8?= =?UTF-8?q?=E5=8D=87=E7=BA=A7=E6=95=B0=E6=8D=AE=E8=BF=81=E7=A7=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 1 + app/src/main/AndroidManifest.xml | 2 + .../cc/chenhe/qqnotifyevo/MyApplication.kt | 3 + .../qqnotifyevo/preference/PreferenceAty.kt | 63 +++++- .../qqnotifyevo/preference/UpgradingFr.kt | 15 ++ .../qqnotifyevo/service/UpgradeService.kt | 192 ++++++++++++++++++ .../chenhe/qqnotifyevo/utils/UpgradeUtils.kt | 49 +++++ .../java/cc/chenhe/qqnotifyevo/utils/Utils.kt | 7 + .../main/res/drawable/ic_notify_upgrade.xml | 10 + ...eference_layout.xml => aty_preference.xml} | 2 +- app/src/main/res/layout/fr_upgrading.xml | 30 +++ app/src/main/res/values/strings.xml | 13 ++ 12 files changed, 375 insertions(+), 12 deletions(-) create mode 100644 app/src/main/java/cc/chenhe/qqnotifyevo/preference/UpgradingFr.kt create mode 100644 app/src/main/java/cc/chenhe/qqnotifyevo/service/UpgradeService.kt create mode 100644 app/src/main/java/cc/chenhe/qqnotifyevo/utils/UpgradeUtils.kt create mode 100644 app/src/main/res/drawable/ic_notify_upgrade.xml rename app/src/main/res/layout/{preference_layout.xml => aty_preference.xml} (80%) create mode 100644 app/src/main/res/layout/fr_upgrading.xml diff --git a/app/build.gradle b/app/build.gradle index 58d2464..5a5433d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -53,6 +53,7 @@ dependencies { androidTestImplementation('androidx.test.espresso:espresso-core:3.1.0', { exclude group: 'com.android.support', module: 'support-annotations' }) + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation "androidx.preference:preference-ktx:1.1.1" implementation "androidx.core:core-ktx:1.3.1" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e227474..97fd6c6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -81,6 +81,8 @@ + + \ No newline at end of file diff --git a/app/src/main/java/cc/chenhe/qqnotifyevo/MyApplication.kt b/app/src/main/java/cc/chenhe/qqnotifyevo/MyApplication.kt index 5f236ef..7b15dd1 100644 --- a/app/src/main/java/cc/chenhe/qqnotifyevo/MyApplication.kt +++ b/app/src/main/java/cc/chenhe/qqnotifyevo/MyApplication.kt @@ -9,6 +9,7 @@ import android.media.RingtoneManager import androidx.core.app.NotificationManagerCompat import cc.chenhe.qqnotifyevo.log.CrashHandler import cc.chenhe.qqnotifyevo.log.ReleaseTree +import cc.chenhe.qqnotifyevo.service.UpgradeService import cc.chenhe.qqnotifyevo.utils.* import timber.log.Timber @@ -38,6 +39,8 @@ class MyApplication : Application() { Timber.tag(TAG).i("= App Create") Timber.tag(TAG).i("==================================================\n") registerNotificationChannel() + + UpgradeService.startIfNecessary(this) } private fun setupTimber(enableLog: Boolean) { diff --git a/app/src/main/java/cc/chenhe/qqnotifyevo/preference/PreferenceAty.kt b/app/src/main/java/cc/chenhe/qqnotifyevo/preference/PreferenceAty.kt index 72ba373..963b52a 100644 --- a/app/src/main/java/cc/chenhe/qqnotifyevo/preference/PreferenceAty.kt +++ b/app/src/main/java/cc/chenhe/qqnotifyevo/preference/PreferenceAty.kt @@ -1,16 +1,18 @@ package cc.chenhe.qqnotifyevo.preference +import android.content.BroadcastReceiver +import android.content.Context import android.content.Intent +import android.content.IntentFilter import android.os.Bundle import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.core.app.NotificationManagerCompat +import androidx.localbroadcastmanager.content.LocalBroadcastManager import cc.chenhe.qqnotifyevo.R import cc.chenhe.qqnotifyevo.StaticReceiver -import cc.chenhe.qqnotifyevo.utils.ACTION_MULTI_MSG_DONT_SHOW -import cc.chenhe.qqnotifyevo.utils.MODE_LEGACY -import cc.chenhe.qqnotifyevo.utils.NOTIFY_ID_MULTI_MSG -import cc.chenhe.qqnotifyevo.utils.getShowInRecent +import cc.chenhe.qqnotifyevo.service.UpgradeService +import cc.chenhe.qqnotifyevo.utils.* class PreferenceAty : AppCompatActivity() { @@ -23,19 +25,58 @@ class PreferenceAty : AppCompatActivity() { const val EXTRA_NEVO_MULTI_MSG = "nevo_multi_msg" } - private lateinit var mainPreferenceFr: MainPreferenceFr + private var mainPreferenceFr: MainPreferenceFr? = null + + private var upgradeReceiver: BroadcastReceiver? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.preference_layout) - mainPreferenceFr = MainPreferenceFr() - supportFragmentManager.beginTransaction() - .replace(R.id.frameLayout, mainPreferenceFr) - .commit() + setContentView(R.layout.aty_preference) + + if (!UpgradeService.isRunningOrPrepared()) { + initPreferenceFragment() + } else { + supportFragmentManager.beginTransaction() + .replace(R.id.frameLayout, UpgradingFr()) + .commit() + upgradeReceiver = object : BroadcastReceiver() { + override fun onReceive(ctx: Context, i: Intent?) { + if (i?.action == ACTION_APPLICATION_UPGRADE_COMPLETE) { + LocalBroadcastManager.getInstance(this@PreferenceAty).unregisterReceiver(this) + upgradeReceiver = null + initPreferenceFragment() + } + } + } + LocalBroadcastManager.getInstance(this).registerReceiver(upgradeReceiver!!, + IntentFilter(ACTION_APPLICATION_UPGRADE_COMPLETE)) + if (!UpgradeService.isRunningOrPrepared()) { + // 避免极端情况下在注册监听器之前更新完成 + initPreferenceFragment() + } + } showNevoMultiMsgDialogIfNeeded() } + override fun onDestroy() { + super.onDestroy() + upgradeReceiver?.also { receiver -> + LocalBroadcastManager.getInstance(this@PreferenceAty).unregisterReceiver(receiver) + } + } + + private fun initPreferenceFragment() { + if (mainPreferenceFr != null) { + return + } + mainPreferenceFr = MainPreferenceFr().also { fr -> + supportFragmentManager.beginTransaction() + .replace(R.id.frameLayout, fr) + .commit() + } + } + override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) setIntent(intent) @@ -50,7 +91,7 @@ class PreferenceAty : AppCompatActivity() { .setMessage(R.string.multi_msg_dialog) .setNeutralButton(R.string.multi_msg_dialog_neutral, null) .setPositiveButton(R.string.multi_msg_dialog_positive) { _, _ -> - mainPreferenceFr.setMode(MODE_LEGACY) + mainPreferenceFr?.setMode(MODE_LEGACY) } .setNegativeButton(R.string.dont_show) { _, _ -> sendBroadcast(Intent(this, StaticReceiver::class.java).also { diff --git a/app/src/main/java/cc/chenhe/qqnotifyevo/preference/UpgradingFr.kt b/app/src/main/java/cc/chenhe/qqnotifyevo/preference/UpgradingFr.kt new file mode 100644 index 0000000..d4dadda --- /dev/null +++ b/app/src/main/java/cc/chenhe/qqnotifyevo/preference/UpgradingFr.kt @@ -0,0 +1,15 @@ +package cc.chenhe.qqnotifyevo.preference + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import cc.chenhe.qqnotifyevo.R + +class UpgradingFr : Fragment() { + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fr_upgrading, container, false) + } +} \ No newline at end of file diff --git a/app/src/main/java/cc/chenhe/qqnotifyevo/service/UpgradeService.kt b/app/src/main/java/cc/chenhe/qqnotifyevo/service/UpgradeService.kt new file mode 100644 index 0000000..abf2ab8 --- /dev/null +++ b/app/src/main/java/cc/chenhe/qqnotifyevo/service/UpgradeService.kt @@ -0,0 +1,192 @@ +package cc.chenhe.qqnotifyevo.service + +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.Service +import android.content.Context +import android.content.Intent +import android.os.IBinder +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import androidx.core.content.edit +import androidx.core.os.UserManagerCompat +import androidx.localbroadcastmanager.content.LocalBroadcastManager +import androidx.preference.PreferenceManager +import cc.chenhe.qqnotifyevo.R +import cc.chenhe.qqnotifyevo.utils.ACTION_APPLICATION_UPGRADE_COMPLETE +import cc.chenhe.qqnotifyevo.utils.NOTIFY_ID_UPGRADE +import cc.chenhe.qqnotifyevo.utils.NOTIFY_SELF_FOREGROUND_SERVICE_CHANNEL_ID +import cc.chenhe.qqnotifyevo.utils.UpgradeUtils +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import timber.log.Timber + +@Suppress("FunctionName") +class UpgradeService : Service() { + + companion object { + + private const val TAG = "UpgradeService" + + private const val EXTRA_OLD_VERSION = "old" + private const val EXTRA_CURRENT_VERSION = "new" + + private const val VERSION_2_0_2 = 20023 + + private var instance: UpgradeService? = null + private var preparedToRunning = false + + /** + * 是否正在运行或者正准备运行。 + */ + fun isRunningOrPrepared(): Boolean { + return preparedToRunning || isRunning() + } + + private fun isRunning(): Boolean { + return try { + // 如果服务被强制结束,标记没有释放,那么此处会抛出异常。 + instance?.ping() ?: false + } catch (e: Exception) { + false + } + } + + fun startIfNecessary(context: Context): Boolean { + val old: Long = UpgradeUtils.getOldVersion(context) + val new: Long = UpgradeUtils.getCurrentVersion(context) + if (old == new) { + Timber.tag(TAG).d("Old version equals to the current, no need to upgrade. v=$new") + return false + } else if (old > new) { + // should never happen + Timber.tag(TAG).e("Current version is lower than old version! current=$new, old=$old") + return false + } + + // old < new + return if (shouldPerformUpgrade(old)) { + preparedToRunning = true + val i = Intent(context.applicationContext, UpgradeService::class.java).apply { + putExtra(EXTRA_OLD_VERSION, old) + putExtra(EXTRA_CURRENT_VERSION, new) + } + context.startService(i) + true + } else { + Timber.tag(TAG).i("No need to perform data migration, update version code directly $old → $new.") + UpgradeUtils.setOldVersion(context, new) + false + } + } + + private fun shouldPerformUpgrade(old: Long): Boolean { + return old in 1..VERSION_2_0_2 + } + } + + private var running = false + + private lateinit var ctx: Context + + private fun ping() = true + + override fun onCreate() { + super.onCreate() + instance = this + ctx = this.application + createNotificationChannel() + } + + override fun onDestroy() { + instance = null + super.onDestroy() + } + + private fun createNotificationChannel() { + val channel = NotificationChannel(NOTIFY_SELF_FOREGROUND_SERVICE_CHANNEL_ID, + getString(R.string.notify_self_foreground_service_channel_name), + NotificationManager.IMPORTANCE_LOW).apply { + description = getString(R.string.notify_self_foreground_service_channel_name_des) + } + NotificationManagerCompat.from(this).createNotificationChannel(channel) + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + if (!running) { + running = true + preparedToRunning = false + + val notify = NotificationCompat.Builder(this, NOTIFY_SELF_FOREGROUND_SERVICE_CHANNEL_ID) + .setSmallIcon(R.drawable.ic_notify_upgrade) + .setContentTitle(getString(R.string.notify_upgrade)) + .setContentText(getString(R.string.notify_upgrade_text)) + .setPriority(NotificationCompat.PRIORITY_LOW) + .setOngoing(true) + .setOnlyAlertOnce(true) + .build() + startForeground(NOTIFY_ID_UPGRADE, notify) + + GlobalScope.launch { + val old = intent!!.getLongExtra(EXTRA_OLD_VERSION, -10) + val new = intent.getLongExtra(EXTRA_CURRENT_VERSION, -10) + if (old == -10L || new == -10L) { + Timber.tag(TAG).e("onStartCommand: unknown version. old=$old, new=$new") + complete(false, 0) + } else { + startUpgrade(old, new) + } + } + } else { + preparedToRunning = false + } + return super.onStartCommand(intent, flags, startId) + } + + /** + * 升级完成后调用此函数。 + */ + private fun complete(success: Boolean, currentVersion: Long) { + if (success) { + Timber.tag(TAG).d("Upgrade complete.") + UpgradeUtils.setOldVersion(this, currentVersion) + } else { + Timber.tag(TAG).e("Upgrade error!") + } + LocalBroadcastManager.getInstance(this).sendBroadcast(Intent(ACTION_APPLICATION_UPGRADE_COMPLETE)) + stopForeground(true) + stopSelf() + } + + private suspend fun startUpgrade(oldVersion: Long, currentVersion: Long) = withContext(Dispatchers.Main) { + Timber.tag(TAG).d("Start upgrade process. $oldVersion → $currentVersion") + + if (oldVersion in 1..VERSION_2_0_2) { + migrate_1_to_2_0_2() + } + + complete(true, currentVersion) + } + + private suspend fun migrate_1_to_2_0_2() = withContext(Dispatchers.Main) { + if (UserManagerCompat.isUserUnlocked(ctx)) { + Timber.tag(TAG).d("Remove deprecated preferences.") + PreferenceManager.getDefaultSharedPreferences(ctx).edit { + remove("friend_vibrate") + remove("friend_ringtone") + remove("group_notify") + remove("group_ringtone") + remove("group_vibrate") + remove("qzone_notify") + remove("qzone_ringtone") + remove("qzone_vibrate") + } + } + } + + override fun onBind(i: Intent?): IBinder? { + return null + } +} \ No newline at end of file diff --git a/app/src/main/java/cc/chenhe/qqnotifyevo/utils/UpgradeUtils.kt b/app/src/main/java/cc/chenhe/qqnotifyevo/utils/UpgradeUtils.kt new file mode 100644 index 0000000..aebe01f --- /dev/null +++ b/app/src/main/java/cc/chenhe/qqnotifyevo/utils/UpgradeUtils.kt @@ -0,0 +1,49 @@ +package cc.chenhe.qqnotifyevo.utils + +import android.content.Context +import android.content.SharedPreferences +import android.content.pm.PackageManager +import android.os.Build +import androidx.core.content.edit + +object UpgradeUtils { + + private const val NAME = "upgrade" + private const val ITEM_OLD_VERSION = "oldVersion" + + private fun Context.deviceProtected(): Context { + return if (isDeviceProtectedStorage) + this + else + createDeviceProtectedStorageContext() + } + + private fun deviceProtectedSp(context: Context, name: String = NAME): SharedPreferences { + return context.deviceProtected().getSharedPreferences(name, Context.MODE_PRIVATE) + } + + fun getOldVersion(context: Context): Long { + return deviceProtectedSp(context).getLong(ITEM_OLD_VERSION, 0L) + } + + fun setOldVersion(context: Context, value: Long) { + deviceProtectedSp(context).edit { + putLong(ITEM_OLD_VERSION, value) + } + } + + fun getCurrentVersion(context: Context): Long { + try { + val pi = context.deviceProtected().packageManager.getPackageInfo(context.packageName, 0) + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + pi.longVersionCode + } else { + @Suppress("DEPRECATION") + pi.versionCode.toLong() + } + } catch (e: PackageManager.NameNotFoundException) { + e.printStackTrace() + } + return 0L + } +} \ No newline at end of file diff --git a/app/src/main/java/cc/chenhe/qqnotifyevo/utils/Utils.kt b/app/src/main/java/cc/chenhe/qqnotifyevo/utils/Utils.kt index 78c3c46..7943ced 100644 --- a/app/src/main/java/cc/chenhe/qqnotifyevo/utils/Utils.kt +++ b/app/src/main/java/cc/chenhe/qqnotifyevo/utils/Utils.kt @@ -18,16 +18,23 @@ const val ACTION_DELETE_NEVO_CHANNEL = "deleteNevoChannel" /** 不再显示关于 Nevo模式下遇到合并消息的使用提示。此 ACTION 用于注册静态接收器。 */ const val ACTION_MULTI_MSG_DONT_SHOW = "dnotShowNevoMultiMsgTips" +/** 应用更新迁移数据完成的广播。 */ +const val ACTION_APPLICATION_UPGRADE_COMPLETE = "applicationUpgradeComplete" + // Android O+ 通知渠道 id const val NOTIFY_FRIEND_CHANNEL_ID = "QQ_Friend" const val NOTIFY_FRIEND_SPECIAL_CHANNEL_ID = "QQ_Friend_Special" const val NOTIFY_GROUP_CHANNEL_ID = "QQ_Group" const val NOTIFY_QZONE_CHANNEL_ID = "QQ_Zone" const val NOTIFY_SELF_TIPS_CHANNEL_ID = "Tips" +const val NOTIFY_SELF_FOREGROUND_SERVICE_CHANNEL_ID = "ForegroundService" /** Nevo 模式下检测到合并消息的提示。 */ const val NOTIFY_ID_MULTI_MSG = 100 +/** 升级前台服务的通知 */ +const val NOTIFY_ID_UPGRADE = 101 + // 自身转发QQ消息的通知渠道组 const val NOTIFY_QQ_GROUP_ID = "base" diff --git a/app/src/main/res/drawable/ic_notify_upgrade.xml b/app/src/main/res/drawable/ic_notify_upgrade.xml new file mode 100644 index 0000000..f29edd9 --- /dev/null +++ b/app/src/main/res/drawable/ic_notify_upgrade.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/preference_layout.xml b/app/src/main/res/layout/aty_preference.xml similarity index 80% rename from app/src/main/res/layout/preference_layout.xml rename to app/src/main/res/layout/aty_preference.xml index 3426e2d..dc5dea9 100644 --- a/app/src/main/res/layout/preference_layout.xml +++ b/app/src/main/res/layout/aty_preference.xml @@ -2,4 +2,4 @@ + android:layout_height="match_parent" /> diff --git a/app/src/main/res/layout/fr_upgrading.xml b/app/src/main/res/layout/fr_upgrading.xml new file mode 100644 index 0000000..9a15193 --- /dev/null +++ b/app/src/main/res/layout/fr_upgrading.xml @@ -0,0 +1,30 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e7ab1c5..a1c7948 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -47,6 +47,8 @@ QQ 空间动态通知 使用提示 + 前台服务 + 一些重要的正在执行的操作 + 升级中 + 正在迁移数据,请勿强行停止。 + Nevo 插件服务未运行 请尝试在 Nevo 中重新启用本插件,并确认已允许 Nevo 和本应用自启动/后台运行。 通知监听服务未运行 @@ -182,4 +188,11 @@ 删除日志 %1$d个日志 总大小%2$s 删除所有日志? + + + + 升级中\n正在迁移数据,请勿强行停止。 + From fc1be7eff25d6bc2de729e8ae8b17f1617838ade Mon Sep 17 00:00:00 2001 From: Chenhe Date: Sat, 1 Aug 2020 17:40:16 +0800 Subject: [PATCH 08/11] =?UTF-8?q?=E9=80=82=E9=85=8D=E9=A6=96=E9=80=89?= =?UTF-8?q?=E9=A1=B9=E7=9A=84=20device=20protected=20storage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/cc/chenhe/qqnotifyevo/preference/AdvancedFr.kt | 1 + .../cc/chenhe/qqnotifyevo/preference/MainPreferenceFr.kt | 1 + .../java/cc/chenhe/qqnotifyevo/preference/PermissionFr.kt | 1 + .../java/cc/chenhe/qqnotifyevo/service/UpgradeService.kt | 5 ++++- .../java/cc/chenhe/qqnotifyevo/utils/PreferencesUtils.kt | 6 +++--- 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/cc/chenhe/qqnotifyevo/preference/AdvancedFr.kt b/app/src/main/java/cc/chenhe/qqnotifyevo/preference/AdvancedFr.kt index 1055e6d..b9b6a26 100644 --- a/app/src/main/java/cc/chenhe/qqnotifyevo/preference/AdvancedFr.kt +++ b/app/src/main/java/cc/chenhe/qqnotifyevo/preference/AdvancedFr.kt @@ -20,6 +20,7 @@ class AdvancedFr : PreferenceFragmentCompat() { private lateinit var deleteLog: Preference override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + preferenceManager.setStorageDeviceProtected() setPreferencesFromResource(R.xml.pref_advanced, rootKey) findPreference("nickname_wrapper")!!.apply { diff --git a/app/src/main/java/cc/chenhe/qqnotifyevo/preference/MainPreferenceFr.kt b/app/src/main/java/cc/chenhe/qqnotifyevo/preference/MainPreferenceFr.kt index 6a31019..8d3ba43 100644 --- a/app/src/main/java/cc/chenhe/qqnotifyevo/preference/MainPreferenceFr.kt +++ b/app/src/main/java/cc/chenhe/qqnotifyevo/preference/MainPreferenceFr.kt @@ -43,6 +43,7 @@ class MainPreferenceFr : PreferenceFragmentCompat() { } override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + preferenceManager.setStorageDeviceProtected() setPreferencesFromResource(R.xml.pref_main, rootKey) findPreference("donate")?.isVisible = !BuildConfig.PLAY diff --git a/app/src/main/java/cc/chenhe/qqnotifyevo/preference/PermissionFr.kt b/app/src/main/java/cc/chenhe/qqnotifyevo/preference/PermissionFr.kt index b7a0bb3..0192987 100644 --- a/app/src/main/java/cc/chenhe/qqnotifyevo/preference/PermissionFr.kt +++ b/app/src/main/java/cc/chenhe/qqnotifyevo/preference/PermissionFr.kt @@ -32,6 +32,7 @@ class PermissionFr : PreferenceFragmentCompat() { } override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + preferenceManager.setStorageDeviceProtected() setPreferencesFromResource(R.xml.pref_permission, rootKey) notification = findPreference("notf_permit")!! accessibility = findPreference("aces_permit")!! diff --git a/app/src/main/java/cc/chenhe/qqnotifyevo/service/UpgradeService.kt b/app/src/main/java/cc/chenhe/qqnotifyevo/service/UpgradeService.kt index abf2ab8..1bc248e 100644 --- a/app/src/main/java/cc/chenhe/qqnotifyevo/service/UpgradeService.kt +++ b/app/src/main/java/cc/chenhe/qqnotifyevo/service/UpgradeService.kt @@ -172,8 +172,11 @@ class UpgradeService : Service() { private suspend fun migrate_1_to_2_0_2() = withContext(Dispatchers.Main) { if (UserManagerCompat.isUserUnlocked(ctx)) { + Timber.tag(TAG).d("Move default preferences to device protected area.") + val deviceCtx = ctx.createDeviceProtectedStorageContext() + deviceCtx.moveSharedPreferencesFrom(ctx, ctx.packageName + "_preferences") Timber.tag(TAG).d("Remove deprecated preferences.") - PreferenceManager.getDefaultSharedPreferences(ctx).edit { + PreferenceManager.getDefaultSharedPreferences(deviceCtx).edit { remove("friend_vibrate") remove("friend_ringtone") remove("group_notify") diff --git a/app/src/main/java/cc/chenhe/qqnotifyevo/utils/PreferencesUtils.kt b/app/src/main/java/cc/chenhe/qqnotifyevo/utils/PreferencesUtils.kt index b3b4db8..b36ee34 100644 --- a/app/src/main/java/cc/chenhe/qqnotifyevo/utils/PreferencesUtils.kt +++ b/app/src/main/java/cc/chenhe/qqnotifyevo/utils/PreferencesUtils.kt @@ -37,7 +37,8 @@ const val ICON_TIM = 2 @IntDef(ICON_AUTO, ICON_QQ, ICON_TIM) annotation class Icon -private fun sp(context: Context): SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) +private fun sp(context: Context): SharedPreferences = PreferenceManager + .getDefaultSharedPreferences(context.createDeviceProtectedStorageContext()) // --------------------------------------------------------- // Tips @@ -51,8 +52,7 @@ fun nevoMultiMsgTip(context: Context, shouldShow: Boolean) { } } -fun nevoMultiMsgTip(context: Context): Boolean = PreferenceManager - .getDefaultSharedPreferences(context).getBoolean(PREF_NEVO_MULTI_MSG_TIP, true) +fun nevoMultiMsgTip(context: Context): Boolean = sp(context).getBoolean(PREF_NEVO_MULTI_MSG_TIP, true) // --------------------------------------------------------- From 4b2a7999d08bb35e43926508f3e8086acab23b45 Mon Sep 17 00:00:00 2001 From: Chenhe Date: Sat, 1 Aug 2020 17:51:23 +0800 Subject: [PATCH 09/11] =?UTF-8?q?=E8=B0=83=E6=95=B4Nevo=E9=80=9A=E7=9F=A5?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E6=96=87=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/res/values/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a1c7948..5b585cc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -35,7 +35,7 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~--> - 仅传统模式有效,Nevo 模式请前往 Nevo 应用设置 + 仅传统模式有效 Q进化- 联系人消息 QQ 私聊消息通知 @@ -101,7 +101,7 @@ 系统设置 前往系统原生页面设置不同渠道的声音与振动 Nevo 设置 - 前往 Nevo 页面设置不同渠道的声音与振动。\n由于 Nevo 限制需要触发一次通知对应渠道才会显示。 + 前往 Nevo 页面设置不同渠道的声音与振动(内源模式自行前往 QQ 页面设置)\n由于 Nevo 限制需要触发一次通知对应渠道才会显示。 图标样式 自动 From 605ff7a72bb4ca1083f7591ddfd8ad25d30f4b8c Mon Sep 17 00:00:00 2001 From: Chenhe Date: Sat, 1 Aug 2020 17:59:18 +0800 Subject: [PATCH 10/11] =?UTF-8?q?=E8=B0=83=E6=95=B4=E9=80=9A=E7=9F=A5?= =?UTF-8?q?=E6=A0=8F=E5=9B=BE=E6=A0=87=E9=A2=9C=E8=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/cc/chenhe/qqnotifyevo/core/NotificationProcessor.kt | 2 +- app/src/main/res/values/colors.xml | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/cc/chenhe/qqnotifyevo/core/NotificationProcessor.kt b/app/src/main/java/cc/chenhe/qqnotifyevo/core/NotificationProcessor.kt index a25fd92..d2aaf97 100644 --- a/app/src/main/java/cc/chenhe/qqnotifyevo/core/NotificationProcessor.kt +++ b/app/src/main/java/cc/chenhe/qqnotifyevo/core/NotificationProcessor.kt @@ -403,7 +403,7 @@ abstract class NotificationProcessor(context: Context) { val channelId = getChannelId(channel) val color = ContextCompat.getColor(context, - if (channel == NotifyChannel.QZONE) R.color.colorQzone else R.color.colorPrimary) + if (channel == NotifyChannel.QZONE) R.color.colorQzoneIcon else R.color.colorConversationIcon) @Suppress("DEPRECATION") val builder = NotificationCompat.Builder(context, channelId) diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index a17a49c..12c234c 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,5 +1,8 @@ #4284F3 - #fece00 + + + #fece00 + #1ED0FC From 6b8fee8f339191c7e19fb5c026875b8a91960b28 Mon Sep 17 00:00:00 2001 From: Chenhe Date: Sat, 1 Aug 2020 18:12:26 +0800 Subject: [PATCH 11/11] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20Nevo=20=E8=B7=B3?= =?UTF-8?q?=E8=BD=AC=E4=B8=8E=E6=9C=AA=E5=AE=89=E8=A3=85=E6=8F=90=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../preference/MainPreferenceFr.kt | 28 +++++++++++++++++-- app/src/main/res/values/strings.xml | 6 ++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/cc/chenhe/qqnotifyevo/preference/MainPreferenceFr.kt b/app/src/main/java/cc/chenhe/qqnotifyevo/preference/MainPreferenceFr.kt index 8d3ba43..66c21d1 100644 --- a/app/src/main/java/cc/chenhe/qqnotifyevo/preference/MainPreferenceFr.kt +++ b/app/src/main/java/cc/chenhe/qqnotifyevo/preference/MainPreferenceFr.kt @@ -1,6 +1,5 @@ package cc.chenhe.qqnotifyevo.preference -import android.app.AlertDialog import android.content.Context import android.content.Intent import android.net.Uri @@ -8,6 +7,7 @@ import android.os.Bundle import android.provider.Settings import android.view.View import android.widget.Toast +import androidx.appcompat.app.AlertDialog import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import androidx.lifecycle.observe @@ -78,9 +78,14 @@ class MainPreferenceFr : PreferenceFragmentCompat() { if (model.mode.value == MODE_NEVO) { serviceWarning.setTitle(R.string.warning_nevo_service) serviceWarning.setSummary(R.string.warning_nevo_service_summary) + serviceWarning.setOnPreferenceClickListener { + startNevoApp() + true + } } else if (model.mode.value == MODE_LEGACY) { serviceWarning.setTitle(R.string.warning_monitor_service) serviceWarning.setSummary(R.string.warning_monitor_service_summary) + serviceWarning.onPreferenceChangeListener = null } serviceWarning.isVisible = true } @@ -112,6 +117,23 @@ class MainPreferenceFr : PreferenceFragmentCompat() { return super.onPreferenceTreeClick(preference) } + private fun startNevoApp() { + try { + Intent().let { + it.action = Intent.ACTION_MAIN + it.addCategory(Intent.CATEGORY_LAUNCHER) + it.setPackage("com.oasisfeng.nevo") + startActivity(it) + } + } catch (e: Exception) { + AlertDialog.Builder(requireContext()) + .setTitle(R.string.tip) + .setMessage(R.string.main_nevo_not_install) + .setPositiveButton(R.string.confirm, null) + .show() + } + } + private suspend fun checkServiceRunning() { withContext(Dispatchers.Default) { if (model.mode.value == MODE_NEVO) @@ -124,7 +146,7 @@ class MainPreferenceFr : PreferenceFragmentCompat() { } private fun donate() { - AlertDialog.Builder(context) + AlertDialog.Builder(requireContext()) .setTitle(R.string.pref_donate_message) .setSingleChoiceItems(R.array.pref_donate_options, -1) { _, i -> startAliPay() @@ -133,7 +155,7 @@ class MainPreferenceFr : PreferenceFragmentCompat() { } private fun showInfo() { - AlertDialog.Builder(context) + AlertDialog.Builder(requireContext()) .setTitle(getString(R.string.about_dialog_title)) .setMessage(getString(R.string.about_dialog_message)) .setNeutralButton(R.string.about_dialog_github) { _, _ -> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5b585cc..7b0a7c4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -189,6 +189,12 @@ %1$d个日志 总大小%2$s 删除所有日志? + + + Nevo(女娲石)可能未安装,请先安装或切换到传统模式。\n详情请阅读使用手册。 +