Live Notifications และ Now Bar ใน Samsung One UI 7 แบบฉบับนักพัฒนา

Live Notifications และ Now Bar เป็นหนึ่งในฟีเจอร์ใหม่ของ Samsung ที่เปิดให้ใช้งานบน One UI 7 เป็นต้นไป จึงเกิดเป็นไอเดียสำหรับนักพัฒนาที่อยากจะให้แอปของตัวเองใช้ประโยชน์จากฟีเจอร์นี้บ้าง โดยในบทความนี้จะมาอธิบายรายละเอียดเกี่ยวกับฟีเจอร์ดังกล่าวที่นักพัฒนาควรรู้

TL;DR - Samsung เปิดให้ใช้งานเฉพาะบางแอปเท่านั้น ดังนั้นเนื้อหาในบทความนี้จึงนำไปใช้งานจริงไม่ได้ เขียนขึ้นมาเพื่อสนองความต้องการของเจ้าของบล็อกเท่านั้น

รู้จักกับ Live Notifications และ Now Bar

เป็นฟีเจอร์ของ Samsung ที่จะนำ Ongoing Notification ของแอปภายในเครื่องมาแสดงให้เด่นชัดมากขึ้นในรูปแบบของ Live Notifications

ตัวอย่าง Live Notifications บน One UI 7

และสามารถทำให้แสดงอยู่ใน Now Bar ที่หน้า Lock Screen เพื่อให้ผู้ใช้กดใช้งานหรือควบคุมการทำงานของแอปเหล่านั้นได้ เช่น นาฬิกาจับเวลา, YouTube, หรือ Spotify เป็นต้น

ตัวอย่าง Now Bar บน One UI 7

โดยมีเงื่อนไขว่าจะต้องเป็น Ongoing Notification จากการทำงานหรือแอปที่อนุญาตเท่านั้น ไม่ได้ออกแบบมาให้แอปทั่วไปใช้งานได้

รองรับทันทีที่ใช้งาน Media Playback

แอปที่มี Media Playback และเล่นแบบ Background ได้ จะรองรับ Live Notifications และ Now Bar ในทันที เพราะเป็นการทำงานที่ฟีเจอร์ดังกล่าวรองรับตั้งแต่แรกอยู่แล้ว

ตัวอย่างการแสดงผลของ Media Playback บน Live Notifications และ Now Bar

และนักพัฒนาสามารถใช้ MediaSessionService ของ AndroidX Media3 เพื่อสร้าง Media Playback ที่รองรับ Live Notifications และ Now Bar ได้ในทันที

รองรับเฉพาะแอปใน Whitelist ของทาง Samsung

ถ้าต้องการสร้าง Ongoing Notification สำหรับ Foreground Service ที่รองรับ Live Notifications และ Now Bar จะต้องเป็นแอปที่อยู่ใน Whitelist ของทาง Samsung เท่านั้น ไม่ใช่ทุกแอปที่จะใช้งานฟีเจอร์ดังกล่าวได้

Whitelist ที่ว่าจะดูจาก Package Name ของแอป

ในกรณีที่แอปอยู่ใน Whitelist ดังกล่าว จะต้องเพิ่มโค้ดเพื่อทำให้ Ongoing Notification รองรับการทำงานของ Live Notifications และ Now Bar โดยทั้ง 2 ฟีเจอร์จะต้องใช้โค้ดแยกกัน

การกำหนดค่าสำหรับ Live Notifications และ Now Bar

แอปจะต้องกำหนด Metadata ใน Android Manifest เพื่อบอกให้ One UI รู้ว่าแอปนั้นมี Ongoing Notification ที่ต้องการรองรับ Live Notifications และ Now Bar

<meta-data
    android:name="com.samsung.android.support.ongoing_activity"
    android:value="true" />

และการกำหนดค่าให้กับการแสดงผลใน Live Notifications กับ Now Bar จะส่งข้อมูลเป็น Bundle ไปใน Notifiction

val extras = bundleOf(
    // Required
    "android.ongoingActivityNoti.style" to 1,

    // Live Notifications & Now Bar configuration
)

val notification = NotificationCompat.Builder(/* ... */)
    /* ... */
    .setExtras(extras)
    .build()
ต้องกำหนดค่า android.ongoingActivityNoti.style เป็น 1 เสมอ

โดยที่ Live Notifcations และ Now Bar จะมีค่าที่ต้องกำหนดต่างกัน และค่าบางอย่างของ Now Bar จะกำหนดแยกหรือใช้ค่าเดียวกับใน Live Notifications ก็ได้

การกำหนดค่าสำหรับ Live Notifications

การแสดงผลของ Live Notification จะแบ่งเป็น 2 ส่วน คือ Live Notifications ที่จะแสดงเป็น Section แยกออกจาก Notification ตัวอื่น ๆ ใน Notification Drawer

Live Notifications จะแสดงผลอยู่บนพื้นที่แยกออกมาจาก Notification ทั่วไป

นักพัฒนาสามารถกำหนดรูปแบบในการแสดงผลได้ทั้งหมด 3 รูปแบบด้วยกัน

  • Standard: แสดงข้อความ 2 ส่วนที่เรียกว่า Primary และ Secondary
  • Progress: แสดงแถบ Progress
  • Custom: แสดง UI ที่สร้างจาก RemoteViews
หรือจะกำหนดให้แสดง 2-3 รูปแบบในตัวเดียวกันก็ได้

การแสดงผลในรูปแบบ Standard จะต้องกำหนดข้อความ Primary เสมอ ส่วน Secondary จะกำหนดหรือไม่ก็ได้

val context: Context = /* ... */
val extras = bundleOf(
    /* ... */
    // Required
    "android.ongoingActivityNoti.primaryInfo" to "Akexorcist",
    // Optional
    "android.ongoingActivityNoti.secondaryInfo" to "Hello, Live Notifications",
    // Optional
    "android.ongoingActivityNoti.secondaryInfoIcon" to Icon.createWithResource(context, R.drawable.ic_celebration),
)
ตัวอย่าง Live Notifications แบบ Standard

และอีกส่วนของ Live Notifications จะเรียกว่า Chip ที่แสดงเป็นแถบอยู่มุมซ้ายบนของหน้าจอตลอดเวลาเมื่อซ่อน Notification Drawer

ตัวอย่างการแสดงผลของ Chip ใน Live Notifications

โดยส่วนที่เป็น Chip จะแสดงได้แค่ไอคอนและข้อความในพื้นที่ที่จำกัดเท่านั้น ถ้าไม่ได้กำหนดค่าเหล่านี้ก็จะใช้ค่าที่กำหนดไว้ใน Notification Drawer ให้โดยอัตโนมัติ

val context: Context = /* ... */
val extras = bundleOf(
	/* ... */
    // Optional
    "android.ongoingActivityNoti.chipBgColor" to context.getColor(R.color.chip_background),
    // Optional (default with notification's small icon)
    "android.ongoingActivityNoti.chipIcon" to Icon.createWithResource(context, R.drawable.ic_chip_location)
    // Optional (default with primary info)
    "android.ongoingActivityNoti.chipExpandedText" to "Akexorcist"
)

การกำหนดสีใน Notification จะไม่มีผลเมื่อแสดงใน Live Notifications แต่ค่าสีที่กำหนดไว้ใน Chip จะถูกนำมาใช้เป็นส่วนหนึ่งของสีพื้นหลังใน Notification Drawer ที่มีลักษณะเป็นสีแบบ Gradient

ตัวอย่างสีใน Chip ที่ถูกนำไปใช้เป็นสีพื้นหลังใน Notification Drawer

ถ้ามีการกำหนด Action Button ไว้ใน Notification จะต้องกำหนดค่าให้ Live Notifications นำไปแสดงผลด้วย

val extras = bundleOf(
    /* ... */
    "android.ongoingActivityNoti.actionType" to 1,
    "android.ongoingActivityNoti.actionPrimarySet" to 0,
)

val pausePendingIntent: PendingIntent = /* ... */
val stopPendingIntent: PendingIntent = /* ... */

val notification = NotificationCompat.Builder(/* ... */)
    /* ... */
    .addAction(R.drawable.ic_notification_pause, "Pause", pausePendingIntent)
    .addAction(R.drawable.ic_notification_stop, "Stop", stopPendingIntent)
    .build()
ตัวอย่าง Notification Action บน Live Notifications

สำหรับการกำหนดรูปแบบเป็น Progress หรือ Custom จะมีผลใน Notification Drawer เท่านั้น ไม่มีผลกับการแสดงผลใน Chip

การแสดงผลแบบ Progress

การแสดงผลแบบ Progress สามารถกำหนดค่าต่าง ๆ ของ Progress ได้ดังนี้

val context: Context = /* ... */
val extras = bundleOf(
    /* ... */
    // Standard
    "android.ongoingActivityNoti.primaryInfo" to "Akexorcist",
    "android.ongoingActivityNoti.secondaryInfo" to "Hello, Live Notifications",
    "android.ongoingActivityNoti.secondaryInfoIcon" to Icon.createWithResource(context, R.drawable.ic_celebration),
    
    // Progress
    "android.ongoingActivityNoti.progress" to 25,
    "android.ongoingActivityNoti.progressMax" to 100,
    "android.ongoingActivityNoti.progressSegments.icon" to Icon.createWithResource(context, R.drawable.ic_snowboarding),
    "android.ongoingActivityNoti.progressSegments.progressColor" to context.getColor(R.color.progress_current),
    "android.ongoingActivityNoti.progressSegments" to arrayOf(
        bundleOf(
            "android.ongoingActivityNoti.progressSegments.segmentStart" to 0.0f,
            "android.ongoingActivityNoti.progressSegments.segmentColor" to context.getColor(R.color.progress_segment_1),
        ),
        bundleOf(
            "android.ongoingActivityNoti.progressSegments.segmentStart" to 0.0f,
            "android.ongoingActivityNoti.progressSegments.segmentColor" to context.getColor(R.color.progress_segment_2),
        ),
)
ตัวอย่างการแสดงผลแบบ Standard และ Progress ร่วมกัน

การแสดงผลแบบ Custom ด้วย RemoteViews

การแสดงผลแบบ Custom จะต้องสร้าง RemoteViews แบบเดียวกับที่ใช้ใน Notification ทั่วไป และจะต้องกำหนดค่าเพิ่มเติมเพื่อให้ Live Notifications นำ RemoteViews มาแสดง และจะต้องกำหนดข้อความสำหรับ Chip ด้วย

val remoteViews: RemoteViews = /* ... */
val extras = bundleOf(
    /* ... */
    "android.ongoingActivityNoti.chronometerRemoteView" to remoteViews,
    "android.ongoingActivityNoti.chronometerRemoteViewPosition" to 1,
"android.ongoingActivityNoti.chronometerRemoteViewTag" to "ongoing_remote_views_tag",
    // Required for custom style
    "android.ongoingActivityNoti.chipExpandedText" to "Akexorcist"
)
ตัวอย่างการแสดงผลแบบ Custom ด้วย RemoteViews

โดยการแสดงผลแบบ Custom ด้วย RemoteViews จะแทนที่การแสดงผลแค่ส่วนที่เป็นข้อความ Primary เท่านั้น จึงนำไปแสดงร่วมกับข้อความ Secondary และ Progress ได้ด้วย

ตัวอย่างการแสดงผลแบบ Custom ด้วย RemoteViews ร่วมกับการแสดงผลแบบอื่น

การกำหนดค่าสำหรับ Now Bar

สำหรับ Now Bar จะมีการใช้ข้อมูลบางส่วนของ Live Notifications ด้วย ดังนั้นการกำหนดค่าสำหรับ Now Bar จะมีแค่บางส่วนที่ต้องกำหนดเพิ่มเติมเท่านั้น

val context: Context = /* ... */
val extras = bundleOf(
    /* ... */
    // Live Notifications
    "android.ongoingActivityNoti.primaryInfo" to "Akexorcist",
    "android.ongoingActivityNoti.secondaryInfo" to "Hello, Live Notifications",

    // Now Bar
    "android.ongoingActivityNoti.nowbarPrimaryInfo" to "Android"
    "android.ongoingActivityNoti.nowbarSecondaryInfo" to "Hello, Now Bar"
)
0:00
/
ตัวอย่างการแสดงผลบน Now Bar

โดย Now Bar จะรองรับการแสดงผลแค่แบบ Standard และ Custom ด้วย RemoteViews เท่านั้น ไม่รองรับการแสดงผลแบบ Progress เหมือนกับใน Live Notifications

สำหรับการแสดงผลแบบ Custom ด้วย RemoteViews จะต้องกำหนดค่าสำหรับ Now Bar เพิ่มเข้าไปเพื่อให้แสดงผลได้อย่างถูกต้อง

val remoteViews: RemoteViews = /* ... */
val extras = bundleOf(
    /* ... */
    // Live Notifications
    "android.ongoingActivityNoti.chronometerRemoteView" to bigRemoteViews,
    "android.ongoingActivityNoti.chronometerRemoteViewPosition" to 1,
    "android.ongoingActivityNoti.chronometerRemoteViewTag" to "ongoing_remote_views_tag",

    // Now Bar
    "android.ongoingActivityNoti.nowbarChronometerPosition" to 1,
)
ตัวอย่างการแสดงผลแบบ Custom ด้วย RemoteViews บน Now Bar

และเนื่องจาก Now Bar มีพื้นที่ในการแสดงผลที่น้อยกว่า Live Notifications จึงควรจัด UI ให้เหมาะสมกับการแสดงผลบน Now Bar ด้วย

Live Notifications และ Now Bar จะรองรับ Live Updates ใน Android 16

ถึงแม้ว่าแอปทั่วไปยังใช้ความสามารถของ Live Notifications และ Now Bar ใน One UI 7 ไม่ได้ แต่ด้วยการมาของ Live Updates บน Android 16 และทาง Samsung ยืนยันว่าใน One UI เวอร์ชันถัดไปจะรองรับฟีเจอร์ดังกล่าวให้โดยอัตโนมัติ

"Live Updates will work with Samsung's Now Bar, which appears on the lock screen, notifications screen, and in the status bar. Google is also working with OnePlus, OPPO, and Xiaomi to make this feature work on their phones." - SamMobile (Source)

นั่นหมายความว่าบน One UI ที่เป็น Android 16 (คาดว่าเป็น One UI 8) นักพัฒนาแค่เรียกใช้งาน Android Platform API ตามปกติก็จะได้ความสามารถของ Live Notifications และ Now Bar เมื่อทำงานบนอุปกรณ์แอนดรอยด์ของ Samsung ได้ในทันที