วันนี้ขอเล่าสู่กันฟังเกี่ยวกับเรื่อง Security เล็กๆน้อยๆซักหน่อย เนื่องจากที่ผ่านมาเจ้าของบล็อกได้ทำโปรเจคที่ค่อนข้างซีเรียสในเรื่องความปลอดภัยพอสมควร ซึ่งหนึ่งใน Issue ที่เกี่ยวข้องกับ Security ก็มีเรื่องของ Recent App นี่แหละ

สำหรับแอปบางตัวที่ค่อนข้างซีเรียสเรื่องความปลอดภัยนั้นการที่มีภาพหน้าจอไปโผล่อยู่ใน Recent App ก็เป็นอันตรายอย่างหนึ่งเช่นกัน เพราะถ้ามีมีใครมาแอบดูข้อมูลอะไรบางอย่าง หนึ่งในนั้นก็คือการกด Recent App ดูนั่นเองครับ (เพราะบางแอปมีเรื่องของ Session Timeout ถ้ากดเข้าไปในแอป)

เพราะแอปที่แสดงอยู่ใน Recent App นั้นจะถูก Snapshot ภาพหน้าจอล่าสุดมาแสดง

ถ้าข้อมูลข้างในนั้นค่อนข้างเป็นความลับ ไม่อยากให้ข้อมูลมาโชว์โจ่งแจ้งแบบนี้ ต้องทำยังไงล่ะ?

นี่ก็คือโจทย์ที่เจ้าของบล็อกต้องไปทำการบ้านมาครับ และก็ได้คำตอบดังนี้

วิธีที่ 1 : กำหนดให้หน้านั้นๆเป็นแบบ Secure

ไม่ว่าจะเป็นหน้าใดๆก็ตามที่แสดงอยู่ในหน้าจอจะสามารถกำหนดให้เป็นแบบ Secure ได้ด้วยคำสั่ง

window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
// หรือ
window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)

โดยให้ประกาศไว้ใน onCreate ก่อนที่จะทำการ Inflate Layout เข้ามาแสดง

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
    window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)
    setContentView(...)
}

เพียงเท่านี้เวลากดดู Recent App ก็จะเห็นภาพหน้าจอแอปเป็นสีขาวล้วนหรือดำล้วน (ขึ้นอยู่กับเวอร์ชันแอนดรอยด์)

แต่วิธีนี้จะทำให้หน้านั้นๆของแอปอยู่ในโหมด Secure ที่จะทำให้ Screen Capture ไม่ได้เช่นกัน เวลาที่ผู้ใช้กด Screen Capture จะแจ้งให้เห็นบนหน้าจอแบบนี้

แต่ละรุ่น ยี่ห้อ และเวอร์ชันอาจจะแสดงผลไม่เหมือนกัน

ข้อเสียของวิธีก็คือหน้านั้นก็จะ Screen Capture ไม่ได้ด้วยนั่นเอง เพราะเจ้าของบล็อกเจอกรณีที่ว่า ไม่อยากให้แสดงภาพหน้าจอใน Recent App แต่อยากให้ Screen Capture ได้ (เพราะจะกดเซฟภาพหน้าจอเวลาเทสแล้วเจอบั๊ก)

ดังนั้นถ้าจะใช้วิธีนี้ก็ต้องยอมให้มัน Screen Capture ไม่ได้ด้วยนะ

วิธีที่ 2 : ไม่ต้องแสดงใน Recent App ซะเลย

เอ้อ แก้ปัญหาโคตรตรงจุดเลยเนอะ ในเมื่อไม่อยากให้แสดง Snapshot ใน Recent App ดังนั้นก็ซ่อนเลยสิ! โดยประกาศใน Android Manifest ด้วยคำสั่ง

android:excludeFromRecents="true"

ให้ประกาศไว้ใน Activity ที่ต้องการแบบนี้

<!-- AndroidManifest.xml -->
<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    
    ...

    <activity
        android:name=".ExclusiveContentActivity"
        android:excludeFromRecents="true" />
        
</application>

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

ในกรณีที่ซ่อนจาก Recent App เวลาย่อหรือปิดแอปก็จะถูกซ่อนจาก Recent App ทันที แต่ถ้ากดปุ่ม Recent App (หรือ App Switch) จะยังแสดงอยู่เพื่อให้กลับมาที่แอปได้ทันที (แต่ถ้ากดออกจากหน้า Recent App ก็จะหายไปอยู่ดี)

ดังนั้นวิธีนี้จะช่วยตัดปัญหาจาก Recent App ได้เลย เพราะมันไม่แสดงแล้ว!! แถม Screen Capture ภายในแอปได้อยู่นะเออ

แต่ปัญหาก็คือ การเอาแอปออกจาก Recent App เป็นอะไรที่แย่มากๆในแง่ของ User Experience ครับ เพราะการสลับแอปของแอนดรอยด์นั้นเป็นอะไรที่ใช้งานบ่อยมาก ถ้าแอปของผู้ที่หลงเข้ามาอ่านไม่แสดงใน Recent App กลายเป็นว่าเมื่อผู้ใช้ย่อแอปไปทำงานอื่นชั่วคราว ถ้าอยากจะกลับเข้ามาใหม่ก็ต้องไปกดจากไอคอนใน Launcher หรือ App Drawer เท่านั้น

รับรองว่าผู้ใช้ต้องหงุดหงิดและอารมณ์เสียแน่นอน

ไม่มีวิธีอื่นที่ดีกว่านี้แล้วหรอ?

นั่นสิ… ถ้าถามว่าเจ้าของบล็อกอยากได้แบบไหน ก็คงอยากได้แบบ

  • ภาพที่แสดงใน Recent App เป็นภาพโลโก้ของแอปโดยมีพื้นหลังสีขาว
  • ยังคง Screen Capture ได้อยู่เหมือนเดิม

ซึ่งมันควรจะทำแบบนั้นได้ครับ แต่ก็ตลกร้ายนิดหน่อยที่ทำแบบนั้นไม่ได้ครับ

ทั้งๆที่แอนดรอยด์นั้นก็ออกแบบมาให้คลาส Activity มี Override Method ตัวหนึ่งที่ชื่อว่า onCreateThumbnail เพื่อให้ทำอะไรแบบนั้นได้

class MainActivity : AppCompatActivity() {
    ...
    override fun onCreateThumbnail(outBitmap: Bitmap?, canvas: Canvas?): Boolean {
        ...
        return true
    }
}

เพียงแค่ Override Method นี้ใน Activity ที่ต้องการ ผู้ที่หลงเข้ามาอ่านก็สามารถกำหนดภาพที่อยากจะให้แสดงใน Recent App ได้ตามต้องการเลย โดยกำหนดภาพที่ต้องการลงใน Bitmap ซะ แล้ว Return ค่าเป็น True ก็จะทำให้ Activity ดึงภาพ Bitmap ที่กำหนดไว้ไปแสดงแทนการ Snapshot หน้าจอ Activity ณ ตอนนั้น

เฮ้ย มันมีคำสั่งแบบนี้ด้วยเรอะ!! ทำไมไม่ใช้ตั้งแต่แรกล่ะ?

นั่นก็เพราะว่า Method ตัวนี้มันดันมีปัญหาอยู่น่ะสิ มันเป็น Method ที่ไม่เคยถูกเรียกใช้งานเลยสักครั้ง ทั้งๆที่มันมีมานานมากตั้งแต่แอนดรอยด์เวอร์ชันแรกๆ ซึ่งก็จะนักพัฒนาบางคนแจ้งปัญหากันไปนานแล้ว จนมาถึงเวอร์ชันปัจจุบันนี้มันก็ยังใช้ไม่ได้!!!

ลองไปตามอ่านกันได้ใน Issue 29370: Activity’s onCreateThumbnail() is never called on Android 4.0.3/4

เป็นหนึ่งในเรื่องตลกร้ายของแอนดรอยด์เลยก็ว่าได้…

ซึ่งบน iOS นั้นสามารถทำได้อยู่แล้ว อยากจะทำให้ภาพเบลอก็ทำได้เหมือนกัน!!

สรุป

เรื่อง Snapshot ที่แสดงอยู่ใน Recent App ถือว่าเป็นหนึ่งใน Issue สำคัญในด้าน Security เลยก็ว่าได้ แต่ด้วยปัญหาจากตัวแอนดรอยด์เองที่ไม่ยอมแก้ไขเสียที จึงทำให้ไม่สามารถแก้ปัญหาได้ เพราะแอปส่วนใหญ่นั้นอยากซ่อน Snapshot จาก Recent App เท่านั้น ยังคงอยากให้ Screen Capture ได้อยู่ และไม่ได้อยากซ่อนแอปจาก Recent App โดยตรง จึงกลายเป็นว่าหลายๆแอปต้องยอมปล่อย Issue ให้ผ่านไปอย่างน่าเสียดาย

ยกตัวอย่างเช่น

Mobile Banking App ที่แน่นอนว่าไม่อยากให้ข้อมูลผู้ใช้แสดงใน Recent App แน่ๆ แต่ผู้ใช้ส่วนใหญ่ก็ยังต้องการ Screen Capture อยู่ โดยเฉพาะตอนที่โอนเงินให้ใครซักคน ก็จะ Screen Capture แล้วส่งภาพหลักฐานการโอนให้ปลายทางดู ซึ่งบนแอนดรอยด์ทำไม่ได้และต้องยอมให้ปล่อยผ่านไปนั่นเอง

ก็ได้แต่ทำใจ แล้วบอกไปว่าทำไม่ได้ครับ..