AR 眼镜之-系统通知定制(通知中心)-实现方案

news2024/11/15 12:47:36

目录

📂 前言

AR 眼镜系统版本

系统通知定制

1. 🔱 技术方案

1.1 技术方案概述

1.2 实现方案

1)通知弹窗消失

2)通知中心显示

2. 💠 通知弹窗消失

2.1 通知弹窗显示时长到期后自动消失

2.2 将通知添加到通知中心

3. ⚛️ 通知中心显示

3.1 统一处理通知

1)统一处理通知中心的通知添加

2)处理 AR 眼镜通知添加

3)处理手机端通知添加

4)通知信息数据类 NotifyInfo

3.2 分类显示 AR 眼镜以及手机端的通知

4. ✅ 小结

附录1:通知中心代码


📂 前言

AR 眼镜系统版本

        W517 Android9。

系统通知定制

        系统通知的底层 实现主要依赖 Android 原生通知模块 NotificationManagerService系统通知的上层 UI 主要依赖于继承 NotificationListenerService 去实现,实现过程如下图所示,主要分为三步:1)应用 A 通过 sendNotification 发送通知;2)Android 通知模块 NotificationManagerService 接收到通知;3、应用 B 通过继承 NotificationListenerService 监听到系统通知。对于底层实现感兴趣的同学可自行去深入了解,本文所讨论的系统通知实现方案主要针对于上层 UI。

        那么,Android 原生系统通知是怎样实现的呢?答案很简单:通过 SystemUI 应用实现,SystemUI 通过继承 NotificationListenerService 监听系统通知,然后显示在通知栏。

        但是,AR 眼镜系统与传统 Android 2D 存在较大显示与交互差异,且根据产品需求综合来看,本文采用类似 SystemUI 的方案,通知应用 通过继承 NotificationListenerService 实现系统通知的监听与显示。

1. 🔱 技术方案

1.1 技术方案概述

        通知应用 通过继承 NotificationListenerService 实现系统通知的监听与显示,上层 UI 主要包括:通知弹窗、通知中心,系统通知定制的实现方案将分为两个篇章展开,分别是 通知弹窗篇通知中心篇

1.2 实现方案

        接续上篇章 AR 眼镜之-系统通知定制(通知弹窗)-实现方案,showNotification 显示通知弹窗 View 后,需要处理通知弹窗消失的逻辑。

1)通知弹窗消失
  1. 通知弹窗显示时长到期后自动消失;

  2. 将通知添加到通知中心。

2)通知中心显示
  1. 统一处理通知;

  2. 分类显示 AR 眼镜以及手机端的通知。

2. 💠 通知弹窗消失

2.1 通知弹窗显示时长到期后自动消失

        一般通知弹窗会规定显示时长,比如 3s 后自动消失。

/**
 * 通知浮窗消失时间。单位:ms
 */
const val NOTIFICATION_DIALOG_TIME_OUT = 3000L

var timeOut: CountDownTimer? = null
timeOut = object : CountDownTimer(NOTIFICATION_DIALOG_TIME_OUT, 1000) {
    override fun onTick(millisUntilFinished: Long) {}
    override fun onFinish() {
        addNotificationBar(context, notificationManagerBean)
        timeOut?.cancel()
        timeOut = null
    }
}.start()

2.2 将通知添加到通知中心

// 弹窗消息通知消失后,添加到通知中心
addNotificationBar(context, notificationManagerBean)

/**
 * 将通知添加到通知列表
 */
private fun addNotificationBar(
    context: Context,
    notificationManagerBean: NotificationManagerBean,
) {
    if (notificationManagerBean.from == NotificationManagerBean.FROM_GLASS) {
        notificationManagerBean.glassNotification?.let {
            NotificationBarHelper.addNotification(context, it)
        }
    } else {
        notificationManagerBean.phoneNotification?.let {
            NotificationBarHelper.addNotification(context, it)
        }
    }
}

3. ⚛️ 通知中心显示

3.1 统一处理通知

1)统一处理通知中心的通知添加
object NotificationBarHelper {

    private val TAG = "NotificationBarHelper"

    private var instance: NotificationBar? = null

    @JvmStatic
    fun getInstance(context: Context): NotificationBar? {
        if (instance == null) {
            instance = NotificationBar(context, null)
        }
        return instance
    }

    @JvmStatic
    fun release() {
        instance = null
    }

    /**
     * AR 眼镜通知添加
     */
    @JvmStatic
    fun addNotification(context: Context, sbn: StatusBarNotification){}

    /**
     * 手机端通知添加
     */
    @JvmStatic
    fun addNotification(context: Context, msg: MessageReqMsgNoti){}

}
2)处理 AR 眼镜通知添加
fun addNotification(context: Context, sbn: StatusBarNotification) {
        val notifyInfo = NotifyInfo()

        val packageName = sbn.packageName
        val postTime = sbn.postTime

        sbn.notification.apply {
            val smallIcon = smallIcon?.loadDrawable(context)
//            val largeIcon = getLargeIcon()?.loadDrawable(context)
            val title = extras.getString(Notification.EXTRA_TITLE, "")
            val content = sbn.notification.extras.getCharSequence(Notification.EXTRA_TEXT, "")
            val progress = extras.getInt(Notification.EXTRA_PROGRESS)
//            val progressMax = extras.getInt(Notification.EXTRA_PROGRESS_MAX)
//            val progressInd = extras.getBoolean(Notification.EXTRA_PROGRESS_INDETERMINATE)
//            val priority = priority

            val contentIntent = contentIntent
//            val deleteIntent = deleteIntent
//            val whenTime = `when`
//            val showWhen = extras.getBoolean(Notification.EXTRA_SHOW_WHEN)
//            val channelID = extras.getString(Notification.EXTRA_CHANNEL_ID, "")
//            val contentView = contentView
//            val bigContentView = bigContentView
//            val headsUpContentView = headsUpContentView

//            Log.e(
//                TAG,
//                "addNotification: notification\n" + "packageName = $packageName\n" + "postTime = $postTime\n" + "smallIcon = $smallIcon\n" + "largeIcon = $largeIcon\n" + "title = $title\n" + "content = $content\n" + "progress = $progress\n" + "progressMax = $progressMax\n" + "progressInd = $progressInd\n" + "priority = $priority\n" + "contentIntent = $contentIntent\n" + "deleteIntent = $deleteIntent\n" + "whenTime = $whenTime\n" + "showWhen = $showWhen\n" + "channelID = $channelID\n" + "contentView = $contentView\n" + "bigContentView = $bigContentView\n" + "headsUpContentView = $headsUpContentView\n"
//            )

//            actions?.forEach { action ->
                val actionIcon = action.getIcon()?.loadDrawable(context)
//                val actionIcon = null
//                val actionTitle = action.title
//                val actionIntent = action.actionIntent
//                Log.e(
//                    TAG,
//                    "addNotification: action\n" + "actionIcon = $actionIcon\n" + "actionTitle = $actionTitle\n" + "actionIntent = $actionIntent\n"
//                )
//            }

            notifyInfo.title = title
            notifyInfo.packageName = packageName
            notifyInfo.icon = smallIcon
            notifyInfo.msg = content.toString()
            notifyInfo.time = postTime
            notifyInfo.contentIntent = contentIntent

            val style = extras.getString(Notification.EXTRA_TEMPLATE, "")
            if (style.isNotEmpty()) {
                when (style) {
                    Notification.BigTextStyle::class.java.name -> {
                        val bigContentTitle =
                            sbn.notification.extras.getCharSequence(Notification.EXTRA_TITLE_BIG)
                        val summaryText =
                            sbn.notification.extras.getCharSequence(Notification.EXTRA_SUMMARY_TEXT)
                        val bigText =
                            sbn.notification.extras.getCharSequence(Notification.EXTRA_BIG_TEXT)
//                        Log.e(
//                            TAG,
//                            "addNotification: BigTextStyle\nbigContentTitle = $bigContentTitle\nsummaryText = $summaryText\nbigText = $bigText\n"
//                        )

                        if (bigContentTitle?.isNotEmpty() == true) notifyInfo.title =
                            bigContentTitle.toString()
                        if (bigText?.isNotEmpty() == true) notifyInfo.msg = bigText.toString()

                    }

                    Notification.BigPictureStyle::class.java.name -> {
                        val bigContentTitle =
                            sbn.notification.extras.getCharSequence(Notification.EXTRA_TITLE_BIG)
                        val summaryText =
                            sbn.notification.extras.getCharSequence(Notification.EXTRA_SUMMARY_TEXT)
                        val bigPicture =
                            sbn.notification.extras.getParcelable<Bitmap>(Notification.EXTRA_PICTURE)
//                        Log.e(
//                            TAG,
//                            "addNotification: BigPictureStyle\nbigContentTitle = $bigContentTitle\nsummaryText = $summaryText\nbigPicture = $bigPicture\n"
//                        )

                        if (bigContentTitle?.isNotEmpty() == true) notifyInfo.title =
                            bigContentTitle.toString()
                        if (bigPicture != null) notifyInfo.picture = bigPicture

                    }

                    Notification.InboxStyle::class.java.name -> {
                        val bigContentTitle =
                            sbn.notification.extras.getCharSequence(Notification.EXTRA_TITLE_BIG)
                        val summaryText =
                            sbn.notification.extras.getCharSequence(Notification.EXTRA_SUMMARY_TEXT)
                        val textLinesArray =
                            sbn.notification.extras.getCharSequenceArray(Notification.EXTRA_TEXT_LINES)
                        var textLines = ""
                        if (!textLinesArray.isNullOrEmpty()) {
                            val sb: StringBuilder = StringBuilder()
                            for (msg in textLinesArray) if (msg.isNotEmpty()) {
                                sb.append(msg.toString())
                                sb.append('\n')
                            }
                            textLines = sb.toString().trim { it <= ' ' }
                        }
//                        Log.e(
//                            TAG,
//                            "addNotification: BigPictureStyle\nbigContentTitle = $bigContentTitle\nsummaryText = $summaryText\ntextLines = $textLines\n"
//                        )

                        if (bigContentTitle?.isNotEmpty() == true) notifyInfo.title =
                            bigContentTitle.toString()
                        if (textLines.isNotEmpty()) notifyInfo.msg = textLines
                    }

                    Notification.MediaStyle::class.java.name -> {

                    }

                    Notification.DecoratedCustomViewStyle::class.java.name -> {

                    }

                    Notification.DecoratedMediaCustomViewStyle::class.java.name -> {

                    }

                    Notification.MessagingStyle::class.java.name -> {

                    }

//                Notification.CallStyle::class.java.name -> {
//
//                }

                    else -> {}
                }
            }

//            if (title.isNotEmpty()) {
            if (progress > 0) {
                notifyInfo.style = NOTIFICATION_STYLE_PROGRESS
                Log.i(TAG, "addNotification: update progress.progress = $progress,style = $style\n")
                instance?.updateGlassNotifyInfo(notifyInfo) ?: Log.e(
                    TAG, "addNotification: NotificationBar has not init..."
                )
            } else {
                Log.i(TAG, "addNotification: add notify.style = $style\n")
                instance?.addGlassNotifyInfo(notifyInfo) ?: Log.e(
                    TAG, "addNotification: NotificationBar has not init..."
                )
            }
//            } else {
//                Log.e(TAG, "addNotification: title is empty!!!")
//            }
        }
    }
3)处理手机端通知添加
fun addNotification(context: Context, msg: MessageReqMsgNoti) {
        val notifyInfo = NotifyInfo(
            msg.title,
            AppUtils.getPhoneAppName(context, msg),
            AppUtils.getPhoneAppIcon(context, msg),
            msg.text,
            msg.timeStamp
        )

        if (msg.title.isNotEmpty()) {
            instance?.addPhoneNotifyInfo(notifyInfo) ?: Log.e(
                TAG, "addNotification: phone —— NotificationBar has not init..."
            )
        } else {
            Log.e(TAG, "addNotification: phone —— title is empty!!!")
        }
    }
4)通知信息数据类 NotifyInfo
data class NotifyInfo(
    var title: String = "",
    var packageName: String = "",
    var icon: Drawable? = null,
    var msg: String = "",
    var time: Long = System.currentTimeMillis(),
    var style: String = "", // ProgressStyle
    var type: Int = 0,
    var location: String = "",
    var picture: Bitmap? = null,
    var foldInfoList: MutableList<NotifyInfo> = mutableListOf(),
    var contentIntent: PendingIntent? = null,
)

3.2 分类显示 AR 眼镜以及手机端的通知

        此部分主要在 UI 布局设计这块,如下图所示:

        

        通知中心,由于没有特殊复杂的产品设计,本文不再赘述,主要注意如下几点即可:

  1. 使用两个 Table 页去区分 AR 眼镜与手机端的通知;

  2. 然后使用 RecyclerView 列表去展示对应通知;

  3. 同时处理点击 Table 页时对应通知的数据切换;

  4. 处理清除按钮对通知的清除;

        

具体通知中心的代码,参考附录1。

4. ✅ 小结

        对于系统通知定制(通知中心),本文只是一个基础实现方案,更多业务细节请参考产品逻辑去实现。

        另外,由于本人能力有限,如有错误,敬请批评指正,谢谢。


附录1:通知中心代码

class NotificationBar(context: Context, attrs: AttributeSet?) : ConstraintLayout(context, attrs),
    BarMonitor {

    val glassNotifyInfoList: MutableList<NotifyInfo> = mutableListOf()
    val phoneNotifyInfoList: MutableList<NotifyInfo> = mutableListOf()

    private lateinit var mRootView: View
    private lateinit var notifyClear: View
    private lateinit var notifyRecyclerView: AGGRecyclerView
    private lateinit var notifyNoMsgLayout: LinearLayout
    private lateinit var notifyNoMsgText: AGGTextView

    private lateinit var glassTitle: AGGTextView
    private lateinit var phoneTitle: AGGTextView
    private lateinit var foldPage: NotificationFoldPage

    private val TAG = NotificationBar::class.java.simpleName
    private val NOTIFY_COUNT_NUM = 100
    private val NOTIFY_FOLD_COUNT_NUM = 3
    private val notifyAdapter by lazy { NotifyAdapter() }

    private var isClickNotifyGlassBtn = false
    private var curOpenedFoldViewNotifyInfo: NotifyInfo? = null

    init {
        initView()
        initNotifyTitle()
        initLiveDataBus()
        initViewStatus()
    }

    override fun dispatchKeyEvent(event: KeyEvent): Boolean {
        if (event.keyCode == KeyEvent.KEYCODE_BACK) {
            Log.i(TAG, "dispatchKeyEvent: KEYCODE_BACK")
            LiveDataBus.get().with(Constants.NOTIFICATION_EVENT_BUS_CLOSE_FOLD_PAGE).value = true
        }
        return super.dispatchKeyEvent(event)
    }

    override fun isBarVisible(): Boolean = mRootView.visibility == VISIBLE

    fun updateBarVisible(visible: Boolean) {
        if (visible) {
            if (mRootView.visibility != VISIBLE) mRootView.visibility = VISIBLE
        } else {
            if (mRootView.visibility == VISIBLE) mRootView.visibility = INVISIBLE
        }
        callBarStateChanged()
    }

    fun addGlassNotifyInfo(notifyInfo: NotifyInfo) {
        judgeFoldNotifyInfo(notifyInfo, glassNotifyInfoList)
        if (isClickNotifyGlassBtn) {
            notifyAdapter.addInfoList(glassNotifyInfoList)
            notifyDataSetChanged()
        }
        notifyClear.visibility = VISIBLE
    }

    fun addPhoneNotifyInfo(notifyInfo: NotifyInfo) {
        judgeFoldNotifyInfo(notifyInfo, phoneNotifyInfoList)
        if (!isClickNotifyGlassBtn) {
            notifyAdapter.addInfoList(phoneNotifyInfoList)
            notifyDataSetChanged()
        }
        notifyClear.visibility = VISIBLE
    }

    fun updateGlassNotifyInfo(notifyInfo: NotifyInfo) {
        var isUpdate = false
        for (i in 0 until glassNotifyInfoList.size) {
            if (glassNotifyInfoList[i].title == notifyInfo.title) {
                glassNotifyInfoList[i] = notifyInfo
                isUpdate = true
                break
            }
        }

        if (isClickNotifyGlassBtn && isUpdate) {
            notifyAdapter.updateInfo(notifyInfo)
            notifyDataSetChanged()
        }

        if (!isUpdate) addGlassNotifyInfo(notifyInfo)
    }

    @SuppressLint("NotifyDataSetChanged")
    fun notifyDataSetChanged() {
        if (glassNotifyInfoList.isEmpty() && phoneNotifyInfoList.isEmpty()) {
            notifyRecyclerView.visibility = INVISIBLE
            notifyNoMsgLayout.visibility = VISIBLE
            notifyClear.visibility = INVISIBLE
        } else {
            notifyAdapter.notifyDataSetChanged()
            notifyRecyclerView.scrollToPosition(0)
            notifyRecyclerView.visibility = VISIBLE
            notifyNoMsgLayout.visibility = INVISIBLE
            notifyClear.visibility = VISIBLE
        }
    }

    fun initViewStatus() {
        phoneTitle.setBackgroundResource(R.drawable.notification_bar_title_item_bg_select)
        glassTitle.setBackgroundColor(Color.TRANSPARENT)
        notifyAdapter.addInfoList(phoneNotifyInfoList)
        if (phoneNotifyInfoList.isEmpty()) {
            notifyRecyclerView.visibility = INVISIBLE
            notifyNoMsgLayout.visibility = VISIBLE
            updateNoMsgText()
            if (glassNotifyInfoList.isEmpty()) notifyClear.visibility = INVISIBLE
            else notifyClear.visibility = VISIBLE
        } else {
            notifyDataSetChanged()
            notifyRecyclerView.visibility = VISIBLE
            notifyNoMsgLayout.visibility = INVISIBLE
        }
    }

    private fun initView() {
        mRootView = LayoutInflater.from(context)
            .inflate(R.layout.notification_layout_notificationbar, this, true)
        notifyClear = mRootView.findViewById(R.id.notify_clear)
        notifyRecyclerView = mRootView.findViewById(R.id.notify_list)
        notifyNoMsgLayout = mRootView.findViewById(R.id.notify_no_msg_layout)
        notifyNoMsgText = mRootView.findViewById(R.id.notify_no_msg_text)
        glassTitle = mRootView.findViewById(R.id.glassTitle)
        phoneTitle = mRootView.findViewById(R.id.phoneTitle)
        notifyRecyclerView.apply {
            layoutManager = LinearLayoutManager(context)
            addItemDecoration(SpacesItemDecoration((10 * context.resources.displayMetrics.density + 0.5f).toInt()))
            adapter = notifyAdapter
            isFocusableInTouchMode = true
        }
        foldPage = mRootView.findViewById(R.id.fold_page)

        mRootView.postDelayed({
            registerPackageRemoved(context)
            register(context)
        }, 3000)
        // 重启/开机后,AR眼镜自动连接上手机时,通知中心不一定收到ACTION_LINK_STATE_CHANGED状态的回调,于是在重启/开机一定时间后主动获取状态。
        mRootView.postDelayed({ updateNoMsgText() }, 15000)
    }

    @SuppressLint("NotifyDataSetChanged")
    private fun initNotifyTitle() {
        notifyClear.setOnClickListener {
            SoundPoolTools.playNotifyClear(context.applicationContext)

            if (notifyAdapter.itemCount == 0) {
                handleClearNotification()
            } else {
                notifyRecyclerView.clearAllItem(object : AnimatorListener {
                    override fun onAnimationStart(p0: Animator) {}

                    override fun onAnimationEnd(p0: Animator) {
                        handleClearNotification()
                    }

                    override fun onAnimationCancel(p0: Animator) {}

                    override fun onAnimationRepeat(p0: Animator) {}
                })
            }
        }
        glassTitle.setOnHoverListener { v, event ->
            if (event.action == MotionEvent.ACTION_HOVER_ENTER) {
                glassTitle.setBackgroundResource(R.drawable.notification_bar_title_item_bg_foc)
            } else if (event.action == MotionEvent.ACTION_HOVER_EXIT) {
                if (isClickNotifyGlassBtn) {
                    glassTitle.setBackgroundResource(R.drawable.notification_bar_title_item_bg_select)
                } else {
                    glassTitle.setBackgroundColor(Color.TRANSPARENT)
                }
            }
            false
        }
        glassTitle.setOnClickListener {
            if (!isClickNotifyGlassBtn) {
                isClickNotifyGlassBtn = true
                glassTitle.setBackgroundResource(R.drawable.notification_bar_title_item_bg_select)
                phoneTitle.setBackgroundColor(Color.TRANSPARENT)
                notifyAdapter.addInfoList(glassNotifyInfoList)
                if (glassNotifyInfoList.isEmpty()) {
                    notifyRecyclerView.visibility = INVISIBLE
                    notifyNoMsgLayout.visibility = VISIBLE
                    updateNoMsgText()
                    if (phoneNotifyInfoList.isEmpty()) notifyClear.visibility = INVISIBLE
                    else notifyClear.visibility = VISIBLE
                } else {
                    notifyDataSetChanged()

                    notifyRecyclerView.visibility = VISIBLE
                    notifyNoMsgLayout.visibility = INVISIBLE
                }
            }
        }
        phoneTitle.setOnHoverListener { v, event ->
            if (event.action == MotionEvent.ACTION_HOVER_ENTER) {
                phoneTitle.setBackgroundResource(R.drawable.notification_bar_title_item_bg_foc)
            } else if (event.action == MotionEvent.ACTION_HOVER_EXIT) {
                if (isClickNotifyGlassBtn) {
                    phoneTitle.setBackgroundColor(Color.TRANSPARENT)
                } else {
                    phoneTitle.setBackgroundResource(R.drawable.notification_bar_title_item_bg_select)
                }
            }
            false
        }
        phoneTitle.setOnClickListener {
            if (isClickNotifyGlassBtn) {
                isClickNotifyGlassBtn = false
                initViewStatus()
            }
        }
    }

    private fun initLiveDataBus() {
        LiveDataBus.get().with(
            Constants.NOTIFICATION_EVENT_BUS_OPEN_FOLD_PAGE, NotifyInfo::class.java
        ).observeForever {
            curOpenedFoldViewNotifyInfo = it
            foldPage.visibility = VISIBLE
            foldPage.addFoldNotifyInfo(it.foldInfoList)
        }
        LiveDataBus.get()
            .with(Constants.NOTIFICATION_EVENT_BUS_CLOSE_FOLD_PAGE, Boolean::class.java)
            .observeForever {
                curOpenedFoldViewNotifyInfo = null
                foldPage.visibility = GONE
            }
        LiveDataBus.get().with(Constants.NOTIFICATION_EVENT_BUS_CLEAR_FOLD_VIEW, String::class.java)
            .observeForever {
                curOpenedFoldViewNotifyInfo = null
                foldPage.visibility = GONE
                clearCurFoldItem(
                    if (isClickNotifyGlassBtn) glassNotifyInfoList else phoneNotifyInfoList, it
                )
            }
        LiveEventBus.get(NOTIFICATION_PHONE_LINK_STATE_CHANGED, Boolean::class.java)
            .observeForever { updateNoMsgText() }
    }

    private fun registerPackageRemoved(context: Context) {
        val filter = IntentFilter()
        filter.addAction(Intent.ACTION_PACKAGE_REMOVED)
        filter.addDataScheme("package")
        context.registerReceiver(object : BroadcastReceiver() {
            override fun onReceive(context: Context, intent: Intent) {
                val pkgName = intent.data?.schemeSpecificPart
                Log.d("NotificationBar", "onReceive: $pkgName removed")

                val tempRemoveList: MutableList<NotifyInfo> = mutableListOf()
                for (i in 0 until glassNotifyInfoList.size) {
                    if (glassNotifyInfoList[i].packageName == pkgName) {
                        tempRemoveList.add(glassNotifyInfoList[i])
                    }
                }

                if (tempRemoveList.isNotEmpty()) {
                    glassNotifyInfoList.removeAll(tempRemoveList)
                    if (isClickNotifyGlassBtn) {
                        if (glassNotifyInfoList.isEmpty() && phoneNotifyInfoList.isEmpty()) {
                            notifyClear.visibility = INVISIBLE
                        } else {
                            notifyClear.visibility = VISIBLE
                        }
                        notifyAdapter.addInfoList(glassNotifyInfoList)
                        notifyDataSetChanged()
                        updateNoMsgText()
                    }

                    curOpenedFoldViewNotifyInfo?.let {
                        if (pkgName == it.packageName) {
                            LiveDataBus.get().with(Constants.NOTIFICATION_EVENT_BUS_CLEAR_FOLD_VIEW)
                                .setValue(pkgName)
                        }
                    }
                }
            }
        }, filter, null, Handler())
    }

    private fun register(context: Context) {
        val filter = IntentFilter()
        filter.addAction(Intent.ACTION_TIME_TICK)
        filter.addAction(Intent.ACTION_TIME_CHANGED)
        filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED)
        filter.addAction(Intent.ACTION_TIMEZONE_CHANGED)
        context.registerReceiver(object : BroadcastReceiver() {
            override fun onReceive(context: Context, intent: Intent) {
                notifyAllTime()
            }
        }, filter, null, Handler())
    }

    private fun updateNoMsgText() {
        if (notifyNoMsgLayout.isVisible) {
            if (isClickNotifyGlassBtn) {
                notifyNoMsgText.text = context.getString(R.string.notification_glass_no_msg)
            } else {
                if (PhoneNotificationHelper.isConnectPhone()) {
                    notifyNoMsgText.text = context.getString(R.string.notification_glass_no_msg)
                } else {
                    notifyNoMsgText.text = context.getString(R.string.notification_phone_no_msg)
                }
            }
        }
    }

    private fun notifyAllTime() {
        if (glassNotifyInfoList.isNotEmpty() || phoneNotifyInfoList.isNotEmpty()) {
            notifyAdapter.notifyItemRangeChanged(0, notifyAdapter.itemCount, "update_time")
        }
    }

    /**
     * 同一应用3条及以上通知,折叠显示判断
     */
    private fun judgeFoldNotifyInfo(info: NotifyInfo, infoList: MutableList<NotifyInfo>) {
        val foldInfoList: MutableList<NotifyInfo> = mutableListOf()
        for (i in 0 until infoList.size) {
            if (infoList[i].packageName == info.packageName) {
                if (infoList[i].foldInfoList.size >= NOTIFY_FOLD_COUNT_NUM) { // 已经折叠
                    if (infoList[i].foldInfoList.size == NOTIFY_COUNT_NUM) infoList[i].foldInfoList.remove(
                        infoList[i].foldInfoList[NOTIFY_COUNT_NUM - 1]
                    )
                    infoList[i].foldInfoList.add(0, info)

                    // 移到最前方
                    val notifyInfoTemp = infoList[i]
                    infoList.remove(notifyInfoTemp)
                    infoList.add(0, notifyInfoTemp)

                    //  更新折叠页面
                    curOpenedFoldViewNotifyInfo?.let {
                        if (it.packageName == info.packageName) {
                            foldPage.updateFoldNotifyInfo(notifyInfoTemp.foldInfoList)
                            curOpenedFoldViewNotifyInfo = notifyInfoTemp
                        }
                    }
                    return
                } else {
                    foldInfoList.add(infoList[i])
                }
            }
        }
        foldInfoList.add(0, info)

        // 判断是否折叠通知
        if (foldInfoList.size >= NOTIFY_FOLD_COUNT_NUM) {
            info.type = Constants.NOTIFICATION_TYPE_FOLD
            info.foldInfoList = foldInfoList
            infoList.removeAll(foldInfoList)
            infoList.add(0, info)
        } else {
            if (infoList.size == NOTIFY_COUNT_NUM) infoList.remove(infoList[NOTIFY_COUNT_NUM - 1])
            infoList.add(0, info)
        }
    }

    /**
     * 清除当前的折叠通知Item项
     */
    @SuppressLint("NotifyDataSetChanged")
    private fun clearCurFoldItem(notifyInfoList: MutableList<NotifyInfo>, pkgName: String) {
        val tempNotifyInfoList: MutableList<NotifyInfo> = mutableListOf()
        for (i in 0 until notifyInfoList.size) {
            if (notifyInfoList[i].packageName == pkgName) {
                tempNotifyInfoList.add(notifyInfoList[i])
            }
        }
        notifyInfoList.removeAll(tempNotifyInfoList)
        notifyAdapter.addInfoList(notifyInfoList)
        if (notifyInfoList.isEmpty()) {
            notifyAdapter.notifyDataSetChanged()
            notifyRecyclerView.visibility = INVISIBLE
            notifyNoMsgLayout.visibility = VISIBLE
            updateNoMsgText()
            if (glassNotifyInfoList.isEmpty() && phoneNotifyInfoList.isEmpty()) {
                notifyClear.visibility = INVISIBLE
            } else {
                notifyClear.visibility = VISIBLE
            }
        } else {
            notifyDataSetChanged()
        }
    }

    private fun handleClearNotification() {
        glassNotifyInfoList.clear()
        phoneNotifyInfoList.clear()
        notifyAdapter.clear()

        notifyRecyclerView.visibility = INVISIBLE
        notifyNoMsgLayout.visibility = VISIBLE
        updateNoMsgText()
        notifyClear.visibility = INVISIBLE
    }

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2092286.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue3中ref绑定的节点顺序错乱

问题复现 眨眼睛这个是修正过了的&#xff0c;小友的应该是ref直接绑定navigationTextList对吧&#xff0c; 按正常想法肯定是既然这个数组会动态更新&#xff0c;我只需要index不就能确定是哪个节点啦&#xff0c;倘若只是静态数据应该不会有什么问题&#xff0c; ⚠️但如果出…

想了个创业的点子问老婆,她说你这不就是外包公司吗

年近35&#xff0c;老在想着万一毕业了咋整&#xff0c;其他的技能也不会&#xff0c;只能去“吉祥三保”、“铁人三项”了&#xff0c;但是吧又不甘心这十来年的“手艺”&#xff0c;又想着这几年大环境下那么多失业的同行&#xff0c;是不是也都有这方面的需求&#xff0c;于…

#读书#经济#《宏观经济学》by N.Gregory Mankiw 第十版 - 第2篇 古典理论:长期中的经济 - 第6章 开放的经济 - 6.3 汇率

在前面两节讨论了产品与服务以及资本在国家之间的流动之后&#xff0c;本节深入讨论了进行这些交易的价格&#xff0c;即两个国家之间进行贸易往来时使用的汇率。从介绍实际汇率和名义汇率的概念开始&#xff0c;讨论实际汇率和贸易余额的关系、两种汇率的决定因素以及财政政策…

九、JMeter之压力测试

文章目录 一、什么是压力测试二、压力测试关注点&#xff08;一&#xff09;压力测试分为两种测试场景&#xff08;二&#xff09;压测设置参数1.线程数&#xff1a;用于设置并发数量&#xff0c;也就是多少个用户同时访问2.Rame-Up Period(in seconds)&#xff1a;控制每隔多少…

广电手机卡靠谱吗?

广电手机卡&#xff0c;作为中国广播电视网络集团有限公司&#xff08;简称“中国广电”&#xff09;官方发行的手机卡&#xff0c;是中国第四大运营商推出的移动通信产品。其靠谱性可以从以下几个方面进行评估&#xff1a; 一、网络覆盖与信号质量 网络覆盖广泛&#xff1a;广…

MDK 复制hex文件到根目录并加上日期 bat 脚本

工程目录示例 copy.bat ::关闭命令行显示 echo off :: GBK chcp 936 >nul setlocal EnableDelayedExpansion:设置文件夹路径 set "FolderName.\Objects":: set "FolderName.\Objects" :: #;copy %FolderName%\*.hex ..\*.hex //复制到根目录 :: c…

C++ 洛谷 哈希表(对应题库:哈希,hash)习题集及代码

马上就开学了&#xff0c;又一个卷季&#xff0c;不写点东西怎么行呢&#xff1f;辣么&#xff0c;我不准备写那些dalao们都懂得&#xff0c;熟练的&#xff0c;想来想去&#xff0c;最终还是写哈希表吧&#xff01;提供讲解&题目&代码解析哦&#xff01; 奉上题目链接…

LVS的加权轮询算法

http://kb.linuxvirtualserver.org/wiki/Weighted_Round-Robin_Scheduling 加权轮循调度是为了更好地处理不同处理能力的服务器。每个服务器都可以被分配一个权重&#xff0c;一个表示处理能力的整数值。权值较高的服务器比权值较低的服务器首先接收到新连接&#xff0c;权值较…

Django异步查询并下载CSV文件

Django异步查询并下载CSV文件 通过循环遍历数据库,自动生成CSV文件的表头和内容. Django V5.1 1. 视图 1.1 将同步的数据库查询转换为异步 async def get_blogs():# 使用sync_to_async包装Queryset方法blog_list await sync_to_async(Blog.objects.all)()return blog_list…

心觉:你的潜意识信念系统符合第一性原理吗

想要释放潜意识的力量&#xff0c;以及想要吸引力法则发挥作用 每天进行积极的自我暗示非常重要 自我暗示辅以视觉化目标及实现目标后的喜悦&#xff0c;其实就是重塑潜意识的过程 举个例子&#xff0c;比如你现在月收入5000&#xff0c;你想实现月入5万 怎么做到 你现在月…

5.11 飞行控制——定点飞行

文章目录 5.11 飞行控制——定点飞行5.11.1 加入三轴位置的飞行硬件系统 FLY(s)5.11.2 数学模型——三轴位置系统&#xff08;1&#xff09;x、y轴位置系统的微分方程&#xff08;2&#xff09;z轴位置系统的微分方程&#xff08;3&#xff09;三轴位置系统的状态空间方程 5.11…

MybatisPlus的基本使用

文章目录 介绍特性工作流程图添加依赖Spring Boot2Spring Boot3 配置定义Mapper接口并继承BaseMapperServer 接口自定义 Service 接口继承 IServie 接口自定义 Service 实现类&#xff0c;实现自定义接口并继承 ServiceImpl 添加Config类常用注解:TableNameTableIdTableFieldTa…

css中 display block属性的用法

前言 display:block是一个css属性&#xff0c;用于控制元素的显示方式。当元素的display属性设置为block时&#xff0c;元素会以块级元素的方式进行显示。 块级元素&#xff08;block-level element&#xff09;是指在HTML中以块的形式展示并独占一行的元素。与块级元素相对的…

MyBatis-Plus 入门与进阶教程

本教程将带领你快速上手 MyBatis-Plus&#xff0c;涵盖其基本功能、常用注解以及插件的使用。我们将通过代码实例一步步展示如何在实际项目中应用 MyBatis-Plus。 1. 快速开始 1.1 添加依赖 在 pom.xml 中添加以下依赖&#xff1a; <dependency><groupId>com.b…

【Hexo系列】【7】Butterfly主题使用及美化

本期将为大家讲解Hexo Butterfly主题的使用。 1. 主题介绍 hexo-theme-butterfly是基于 Molunerfinn 的 hexo-theme-melody 的基础上进行开发的&#xff0c;当前版本是v4.13.0。 主题官网&#xff1a;https://github.com/jerryc127/hexo-theme-butterfly 官网效果图&#x…

Unity(2022.3.41LTS) - 3D关节

目录 零. 简介 一、关节的类型及特点 二、关节的使用方法 三、关节的应用场景 四. 实例效果 零. 简介 在 Unity 中&#xff0c;关节&#xff08;Joints&#xff09;是实现物理模拟和复杂交互效果的重要组件。以下是对 Unity 关节更详细的介绍&#xff1a; 一、关节的类型…

JDBC中的execute, executeQuery, 和 executeUpdate方法区别

JDBC中的execute, executeQuery, 和 executeUpdate方法区别 1、execute(String sql)2、executeQuery(String sql)3、executeUpdate(String sql) &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 1、execute(String sql) 功能&#xff1a;执…

Day89 代码随想录打卡|贪心算法篇---划分字母区间

题目&#xff08;leecode T763&#xff09;&#xff1a; 给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段&#xff0c;同一字母最多出现在一个片段中。 注意&#xff0c;划分结果需要满足&#xff1a;将所有划分结果按顺序连接&#xff0c;得到的字符串仍然是 s …

国产数据库打败Oracle?不存在的!

XC背景下国产数据库替代工程进行的如火如荼&#xff0c;数据库圈特别是Oracle的从业人员&#xff0c;既感受到深深的危机感&#xff0c;又带着些许的不甘&#xff0c;这种不甘主要来自于技术层面。 技术人员也有武士道精神&#xff0c;谁能打败我我服谁&#xff0c;谁的技术比…

如何判断儿童是否患有自闭症

自闭症&#xff0c;也被称为孤独症&#xff0c;是一种复杂的神经发育障碍&#xff0c;其症状通常在儿童早期就开始显现。面对孩子的成长过程&#xff0c;家长和教育者如何准确判断孩子是否患有自闭症&#xff0c;是一个至关重要的问题。 我们需要关注孩子的社交行为。自闭症儿童…