อย่างที่นักพัฒนารู้กันดีว่านับตั้งแต่ Target API Level 31 (Android 12) เป็นต้นมา จะต้องกำหนด Flag ที่ชื่อว่า PendingIntent.FLAG_MUTABLE หรือ PendingIntent.FLAG_IMMUTABLE ให้กับ Pending Intent ด้วยเสมอ ไม่เช่นนั้นจะทำให้แอปพัง

ยกตัวอย่างเช่น เจ้าของบล็อกสร้าง Pending Intent สำหรับ Notification ขึ้นมาโดยไม่ได้กำหนด Flag ดังกล่าว เมื่อแอปทำงานบน Android 12 หรือสูงกว่าก็จะทำให้แอปพังและได้ IllegalArgumentException แทน

FATAL EXCEPTION: main
Process: com.akexorcist.sleepingforless, PID: 10890
java.lang.IllegalArgumentException: com.akexorcist.notification.pendingintentflag: Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent.
Strongly consider using FLAG_IMMUTABLE, only use FLAG_MUTABLE if some functionality depends on the PendingIntent being mutable, e.g. if it needs to be used with inline replies or bubbles.

และเจ้าของบล็อกก็ไม่รู้เหมือนกันว่าทำไมมันถึงพัง แต่ที่รู้คือต้องใส่ PendingIntent.FLAG_IMMUTABLE หรือ PendingIntent.FLAG_MUTABLE เพื่อให้แอปทำงานได้ปกติ

แล้วเราทำไปทำไมกันนะ 🤔?

สำหรับผู้ที่หลงเข้ามาอ่านคนใดอยากรู้จักกับ Pending Intent มากกว่านี้สามารถตามไปอ่านเพิ่มเติมได้จากบทความ Intent และ Pending Intent — ส่งใจ ไปหาเธอ

Intent และ Pending Intent — ส่งใจ ไปหาเธอ
คงไม่มีนักพัฒนาคนไหนที่ไม่รู้จักกับ Intent เนอะ เพราะว่าเป็นหนึ่งในพื้นฐานที่จะได้เรียนและทำความรู้จักกับมันในช่วงแรกๆ

ช่องโหว่ด้านความปลอดภัยใน Pending Intent

Pending Intent เป็นหนึ่งในข้อมูลที่สามารถรับส่งระหว่าง App Component ได้ รวมไปถึงการส่งข้อมูลข้ามระหว่างแอปด้วยเช่นกัน แต่ที่นักพัฒนาน่าจะคุ้นเคยกันมากที่สุดก็คือการใช้งานร่วมกับ Notification เพื่อให้ผู้ใช้กด Notification แล้วเปิด Activity พร้อมแนบข้อมูลมาให้ด้วย

และเดิมทีนั้น Pending Intent เป็นแบบ Mutable มาตลอด ทำให้เพิ่มข้อมูล (Fill In) บางอย่างเข้าไปใน Pending Intent ได้ ทำให้มีโอกาสที่จะกลายเป็นช่องโหว่ในบางรูปแบบได้ เพราะอาจจะถูก App In The Middle ดักข้อมูลระหว่างทางหรือเพิ่มข้อมูลเพื่อหลอกให้ส่งข้อมูลกลับไปหาแอปเหล่านั้นได้

สามารถอ่านเพิ่มเติมได้ที่ "Having fun with secure messengers and Android Wear and Android Auto" by Artem Chaykin at CanSecWest2016 (หาวีดีโอย้อนหลังไม่เจอ)

Immutable Pending Intent ใน API Level 23 (Android 6.0 Marshmallow)

เนื่องจากมีแค่ Pending Intent ในบางกรณีเท่านั้นที่จำเป็นต้องเพิ่มข้อมูลเข้าไปทีหลัง จึงทำให้ทีมแอนดรอยด์เพิ่ม PendingIntent.FLAG_IMMUTABLE เข้ามาใน API Level 23 (Android 6.0 Marshmallow) เพื่อไม่ให้เพิ่มข้อมูล (Fill In) เข้าไปใน Pending Intent ทีหลังได้

val pendingIntent = PendingIntent.getActivity(
    context = context,
    requestCode = 0,
    intent = Intent(intentAction),
    flags = PendingIntent.FLAG_IMMUTABLE,
)

หรือจะใช้ร่วมกับ Flag ตัวอื่นที่ไม่ใช่ FLAG_MUTABLE ก็ได้เช่นกัน

val pendingIntent = PendingIntent.getActivity(
    context = context,
    requestCode = 0,
    intent = Intent(intentAction),
    flags = PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_ONE_SHOT,
)

โดยใน API Level 30 (Android 11) จะต้องกำหนด FLAG_MUTABLE เพื่อทำให้ Pending Intent เป็น Mutable

และแอปที่กำหนด Target API Level 31 (Android 12) หรือสูงกว่าจะบังคับให้นักพัฒนากำหนด FLAG_MUTABLE หรือ FLAG_IMMUTABLE ให้กับ Pending Intent ด้วยเสมอ

และนั่นก็คือที่มาของบทความนี้นั่นเอง

Pending Intent แบบไหนควรกำหนดเป็น Mutable และแบบไหนเป็น Immutable

มี Pending Intent แค่บางรูปแบบเท่านั้นที่ต้องกำหนดเป็น Mutable โดยจะมีดังนี้

  • Pending Intent ใน Notification ที่รองรับการตอบกลับผ่าน Notification Actions (Direct reply actions in notifications)
  • Pending Intent ใน Notification ที่ทำงานร่วมกับ Android Auto ผ่านคลาส CarAppExtender
  • Pending Intent ใน Notification สำหรับบทสนทนา (Conversation) ที่รองรับ Bubble API
  • การกำหนด Pending Intent เพื่อขอตำแหน่งปัจจุบันของอุปกรณ์ (Location Access) ในคำสั่ง requestLocationUpdates ของ LocationManager
  • การกำหนด Pending Intent ใน AlarmManager

นอกเหนือจากนั้นให้กำหนดเป็น Immutable ทั้งหมด

สรุป

เนื่องจาก Pending Intent ถูกออกแบบให้เป็น Mutable มาตั้งแต่แรกและทำให้เกิดช่องโหว่ด้านความปลอดภัยสำหรับแอปที่มีการใช้งาน Pending Intent ในบางรูปแบบ จึงทำให้ระบบแอนดรอยด์รองรับ Immutable ใน Pending Intent เพิ่มเข้ามาในภายหลัง

ในขณะเดียวกัน นักพัฒนาแต่ละคนก็อาจจะต้องการใช้งาน Pending Intent แบบ Mutable ด้วยเหตุผลบางอย่าง จึงทำให้ระบบแอนดรอยด์บังคับให้ Pending Intent เป็น Immutable ทั้งหมดไม่ได้ และนั่นคือที่มาที่นักพัฒนาต้องกำหนดค่าดังกล่าวด้วยตัวเองยังไงล่ะ

แหล่งข้อมูลอ้างอิง