การหมุนกรณีการใช้งานของ CameraX

หัวข้อนี้จะแสดงวิธีตั้งค่ากรณีการใช้งาน CameraX ภายในแอปเพื่อรับรูปภาพที่มีข้อมูลการหมุนที่ถูกต้อง ไม่ว่าจะเป็นจากกรณีการใช้งาน ImageAnalysis หรือ ImageCapture ดังนั้น

  • Analyzer ของ Use Case ImageAnalysis ควรได้รับเฟรมที่มีการหมุนที่ถูกต้อง
  • Use Case ImageCapture ควรถ่ายภาพโดยหมุนให้ถูกต้อง

คำศัพท์

หัวข้อนี้ใช้คำศัพท์ต่อไปนี้ คุณจึงควรทำความเข้าใจความหมายของคำแต่ละคำ

การวางแนวจอแสดงผล
ค่านี้หมายถึงด้านใดของอุปกรณ์ที่อยู่ในตำแหน่งขึ้น และอาจเป็นค่าใดค่าหนึ่งต่อไปนี้ แนวตั้ง แนวนอน แนวตั้งกลับด้าน หรือแนวนอนกลับด้าน
การหมุนจอแสดงผล
คือค่าที่ Display.getRotation() แสดงผล และแสดงองศาที่อุปกรณ์หมุนทวนเข็มนาฬิกาจากการวางแนวตามปกติ
การหมุนเวียนเป้าหมาย
ค่านี้แสดงจำนวนองศาในการหมุนอุปกรณ์ตามเข็มนาฬิกาเพื่อให้อยู่ในแนวตั้งตามปกติ

วิธีกำหนดการหมุนเป้าหมาย

ตัวอย่างต่อไปนี้แสดงวิธีกำหนดการหมุนเป้าหมายสำหรับอุปกรณ์โดยอิงตามการวางแนวตามปกติ

ตัวอย่างที่ 1: การวางแนวตั้งตามปกติ

ตัวอย่างอุปกรณ์: Pixel 3 XL

การวางแนวตามปกติ = แนวตั้ง
การวางแนวปัจจุบัน = แนวตั้ง

การหมุนจอแสดงผล = 0
การหมุนเป้าหมาย = 0

การวางแนวตามปกติ = แนวตั้ง
การวางแนวปัจจุบัน = แนวนอน

การหมุนจอแสดงผล = 90
การหมุนเป้าหมาย = 90

ตัวอย่างที่ 2: การวางแนวนอนตามธรรมชาติ

ตัวอย่างอุปกรณ์: Pixel C

การวางแนวตามปกติ = แนวนอน
การวางแนวปัจจุบัน = แนวนอน

การหมุนจอแสดงผล = 0
การหมุนเป้าหมาย = 0

การวางแนวตามปกติ = แนวนอน
การวางแนวปัจจุบัน = แนวตั้ง

การหมุนของจอแสดงผล = 270
การหมุนของเป้าหมาย = 270

การหมุนภาพ

ปลายสายใดที่เปิดอยู่ การวางแนวเซ็นเซอร์จะกำหนดใน Android เป็นค่าคงที่ ซึ่งแสดงองศา (0, 90, 180, 270) ที่เซ็นเซอร์หมุนจากด้านบนของอุปกรณ์เมื่ออุปกรณ์อยู่ในตำแหน่งปกติ สำหรับกรณีทั้งหมดในแผนภาพ การหมุนรูปภาพจะอธิบายวิธีหมุนข้อมูลตามเข็มนาฬิกาเพื่อให้ปรากฏในแนวตั้ง

ตัวอย่างต่อไปนี้แสดงลักษณะการหมุนของภาพโดยขึ้นอยู่กับการวางแนวเซ็นเซอร์กล้อง และยังถือว่ามีการตั้งค่าการหมุนเป้าหมายเป็นการหมุนของจอแสดงผล

ตัวอย่างที่ 1: เซ็นเซอร์หมุน 90 องศา

ตัวอย่างอุปกรณ์: Pixel 3 XL

การหมุนจอแสดงผล = 0
การวางแนวจอแสดงผล = แนวตั้ง
การหมุนรูปภาพ = 90

การหมุนจอแสดงผล = 90
การวางแนวจอแสดงผล = แนวนอน
การหมุนรูปภาพ = 0

ตัวอย่างที่ 2: เซ็นเซอร์หมุน 270 องศา

ตัวอย่างอุปกรณ์: Nexus 5X

การหมุนจอแสดงผล = 0
การวางแนวจอแสดงผล = แนวตั้ง
การหมุนรูปภาพ = 270

การหมุนจอแสดงผล = 90
การวางแนวจอแสดงผล = แนวนอน
การหมุนรูปภาพ = 180

ตัวอย่างที่ 3: เซ็นเซอร์หมุน 0 องศา

ตัวอย่างอุปกรณ์: Pixel C (แท็บเล็ต)

การหมุนจอแสดงผล = 0
การวางแนวจอแสดงผล = แนวนอน
การหมุนรูปภาพ = 0

การหมุนจอแสดงผล = 270
การวางแนวจอแสดงผล = แนวตั้ง
การหมุนรูปภาพ = 90

การคํานวณการหมุนของรูปภาพ

ImageAnalysis

Analyzer ของ ImageAnalysis รับรูปภาพจากกล้องในรูปแบบของ ImageProxy รูปภาพแต่ละรูปมีข้อมูลการหมุน ซึ่งเข้าถึงได้ผ่าน

val rotation = imageProxy.imageInfo.rotationDegrees

ค่านี้แสดงองศาที่รูปภาพต้องหมุนตามเข็มนาฬิกาเพื่อให้ตรงกับการหมุนเป้าหมายของ ImageAnalysis ในบริบทของแอป Android โดยปกติแล้วการหมุนเป้าหมายของ ImageAnalysis จะตรงกับการวางแนวของหน้าจอ

ImageCapture

มีการแนบการเรียกกลับกับอินสแตนซ์ ImageCapture เพื่อส่งสัญญาณเมื่อผลการจับภาพพร้อมใช้งาน ผลลัพธ์อาจเป็นรูปภาพที่จับภาพไว้หรือข้อผิดพลาด

เมื่อถ่ายภาพ แคลลแบ็กที่ระบุอาจเป็นประเภทใดประเภทหนึ่งต่อไปนี้

  • OnImageCapturedCallback: รับรูปภาพที่มีการเข้าถึงในหน่วยความจําในรูปแบบ ImageProxy
  • OnImageSavedCallback: เรียกใช้เมื่อจัดเก็บรูปภาพที่จับภาพไว้เรียบร้อยแล้วในตำแหน่งที่ระบุโดย ImageCapture.OutputFileOptions ตัวเลือกสามารถระบุ File, OutputStream หรือตําแหน่งใน MediaStore

การหมุนของรูปภาพที่จับภาพไว้ ไม่ว่ารูปแบบจะเป็นแบบใด (ImageProxy, File, OutputStream, MediaStore Uri) จะแสดงองศาการหมุนที่รูปภาพที่จับภาพไว้ต้องหมุนตามเข็มนาฬิกาเพื่อให้ตรงกับการหมุนเป้าหมายของ ImageCapture ซึ่งโดยทั่วไปแล้วในบริบทของแอป Android จะตรงกับการวางแนวของหน้าจอ

คุณสามารถเรียกข้อมูลการหมุนของรูปภาพที่จับภาพได้โดยใช้วิธีใดวิธีหนึ่งต่อไปนี้

ImageProxy

val rotation = imageProxy.imageInfo.rotationDegrees

File

val exif = Exif.createFromFile(file)
val rotation = exif.rotation

OutputStream

val byteArray = outputStream.toByteArray()
val exif = Exif.createFromInputStream(ByteArrayInputStream(byteArray))
val rotation = exif.rotation

MediaStore uri

val inputStream = contentResolver.openInputStream(outputFileResults.savedUri)
val exif = Exif.createFromInputStream(inputStream)
val rotation = exif.rotation

ยืนยันการหมุนของรูปภาพ

Use Case ImageAnalysis และ ImageCapture จะได้รับ ImageProxy จากกล้องหลังจากส่งคำขอจับภาพสำเร็จ ImageProxy จะรวมรูปภาพและข้อมูลเกี่ยวกับรูปภาพนั้นไว้ด้วยกัน รวมถึงการหมุน ข้อมูลการหมุนนี้แสดงองศาที่รูปภาพต้องหมุนเพื่อให้ตรงกับการหมุนเป้าหมายของกรณีการใช้งาน

ขั้นตอนการยืนยันการหมุนของรูปภาพ

หลักเกณฑ์การหมุนเป้าหมายของ ImageCapture/ImageAnalysis

เนื่องจากอุปกรณ์จำนวนมากไม่หมุนกลับเป็นแนวตั้งหรือแนวนอนโดยค่าเริ่มต้น แอป Android บางแอปจึงไม่รองรับการวางแนวเหล่านี้ การที่แอปรองรับหรือไม่รองรับฟีเจอร์นี้จะเปลี่ยนวิธีอัปเดตการหมุนเป้าหมายของ Use Case

ด้านล่างนี้คือ 2 ตารางที่ระบุวิธีทำให้การหมุนเป้าหมายของ Use Case ซิงค์กับการหมุนของจอแสดงผล รายการแรกแสดงวิธีดำเนินการโดยรองรับการวางแนวทั้ง 4 แบบ ส่วนรายการที่ 2 จะจัดการเฉพาะการวางแนวที่อุปกรณ์หมุนไปโดยค่าเริ่มต้นเท่านั้น

วิธีเลือกหลักเกณฑ์ที่จะปฏิบัติตามในแอป

  1. ยืนยันว่ากล้อง Activity ของแอปมีการล็อกการวางแนว การวางแนวที่ปลดล็อก หรือลบล้างการเปลี่ยนแปลงการกำหนดค่าการวางแนวหรือไม่

  2. ตัดสินใจว่าActivityกล้องของแอปควรรองรับการวางแนวของอุปกรณ์ทั้ง 4 แบบ (แนวตั้ง แนวตั้งกลับด้าน แนวนอน และแนวนอนกลับด้าน) หรือควรรองรับเฉพาะการวางแนวที่อุปกรณ์ที่ใช้งานรองรับโดยค่าเริ่มต้น

รองรับการวางแนวทั้ง 4 แบบ

ตารางนี้กล่าวถึงหลักเกณฑ์บางอย่างที่ควรทำในกรณีที่อุปกรณ์ไม่หมุนเป็นแนวตั้งกลับหัว การดำเนินการนี้ใช้ได้กับอุปกรณ์ที่ไม่หมุนเป็นแนวนอนกลับหัว

สถานการณ์ หลักเกณฑ์ โหมดหน้าต่างเดียว โหมดแยกหน้าจอหลายหน้าต่าง
การวางแนวที่ปลดล็อกอยู่ ตั้งค่ากรณีการใช้งานทุกครั้งที่สร้าง Activity เช่น ใน onCreate() callback ของ Activity
ใช้ onOrientationChanged() ของ OrientationEventListener ในการเรียกกลับ ให้อัปเดตการหมุนเวียนเป้าหมายของ Use Case ซึ่งจะจัดการในกรณีที่ระบบไม่สร้าง Activity ขึ้นมาใหม่แม้ว่าจะมีการเปลี่ยนแปลงการวางแนวแล้ว เช่น เมื่อหมุนอุปกรณ์ 180 องศา รวมถึงจัดการเมื่อจอแสดงผลอยู่ในแนวตั้งกลับหัวและอุปกรณ์ไม่หมุนเป็นแนวตั้งกลับหัวโดยค่าเริ่มต้น รวมถึงจัดการกรณีที่ Activity ไม่ได้สร้างขึ้นใหม่เมื่ออุปกรณ์หมุน (เช่น 90 องศา) กรณีนี้จะเกิดขึ้นในอุปกรณ์ขนาดเล็กเมื่อแอปแสดงครึ่งหน้าจอ และในอุปกรณ์ขนาดใหญ่เมื่อแอปแสดง 2 ใน 3 ของหน้าจอ
ไม่บังคับ: ตั้งค่าพร็อพเพอร์ตี้ screenOrientation ของ Activity เป็น fullSensor ในไฟล์ AndroidManifest วิธีนี้ช่วยให้ UI ตั้งตรงเมื่ออุปกรณ์อยู่ในแนวตั้งกลับหัว และช่วยให้ระบบสร้าง Activity ขึ้นมาใหม่ทุกครั้งที่อุปกรณ์หมุน 90 องศา ไม่ส่งผลต่ออุปกรณ์ที่ไม่ได้หมุนเป็นแนวตั้งโดยค่าเริ่มต้น ระบบไม่รองรับโหมดหลายหน้าต่างขณะที่จอแสดงผลอยู่ในการวางแนวตั้งกลับหัว
การวางแนวที่ล็อกอยู่ ตั้งค่า Use Case เพียงครั้งเดียวเมื่อสร้าง Activity เป็นครั้งแรก เช่น ในการเรียกกลับ onCreate() ของ Activity
ใช้ onOrientationChanged() ของ OrientationEventListener ในการเรียกกลับ ให้อัปเดตการหมุนเป้าหมายของ Use Case ยกเว้นตัวอย่าง รวมถึงจัดการกรณีที่ Activity ไม่ได้สร้างขึ้นใหม่เมื่ออุปกรณ์หมุน (เช่น 90 องศา) กรณีนี้จะเกิดขึ้นในอุปกรณ์ขนาดเล็กเมื่อแอปแสดงครึ่งหน้าจอ และในอุปกรณ์ขนาดใหญ่เมื่อแอปแสดง 2 ใน 3 ของหน้าจอ
Orientation configChanges overridden ตั้งค่า Use Case เพียงครั้งเดียวเมื่อสร้าง Activity เป็นครั้งแรก เช่น ในการเรียกกลับ onCreate() ของ Activity
ใช้ onOrientationChanged() ของ OrientationEventListener ในการเรียกกลับ ให้อัปเดตการหมุนเวียนเป้าหมายของ Use Case รวมถึงจัดการกรณีที่ Activity ไม่ได้สร้างขึ้นใหม่เมื่ออุปกรณ์หมุน (เช่น 90 องศา) กรณีนี้จะเกิดขึ้นในอุปกรณ์ขนาดเล็กเมื่อแอปแสดงครึ่งหน้าจอ และในอุปกรณ์ขนาดใหญ่เมื่อแอปแสดง 2 ใน 3 ของหน้าจอ
ไม่บังคับ: ตั้งค่าพร็อพเพอร์ตี้ screenOrientation ของกิจกรรมเป็น fullSensor ในไฟล์ AndroidManifest ช่วยให้ UI ตั้งตรงเมื่ออุปกรณ์อยู่ในแนวตั้งกลับหัว ไม่ส่งผลต่ออุปกรณ์ที่ไม่ได้หมุนเป็นแนวตั้งโดยค่าเริ่มต้น ระบบไม่รองรับโหมดหลายหน้าต่างขณะที่จอแสดงผลอยู่ในแนวตั้งกลับหัว

รองรับเฉพาะการวางแนวที่อุปกรณ์รองรับ

รองรับเฉพาะการวางแนวที่อุปกรณ์รองรับโดยค่าเริ่มต้น (ซึ่งอาจหรือไม่รวมถึงการวางแนวตั้ง/แนวนอนกลับด้าน)

สถานการณ์ หลักเกณฑ์ โหมดแยกหน้าจอหลายหน้าต่าง
การวางแนวที่ปลดล็อก ตั้งค่ากรณีการใช้งานทุกครั้งที่สร้าง Activity เช่น ใน onCreate() callback ของ Activity
ใช้ onDisplayChanged() ของ DisplayListener ในการเรียกกลับ ให้อัปเดตการหมุนเป้าหมายของ Use Case เช่น เมื่ออุปกรณ์หมุน 180 องศา รวมถึงจัดการกรณีที่ Activity ไม่ได้สร้างขึ้นใหม่เมื่ออุปกรณ์หมุน (เช่น 90 องศา) กรณีนี้จะเกิดขึ้นในอุปกรณ์ขนาดเล็กเมื่อแอปแสดงครึ่งหน้าจอ และในอุปกรณ์ขนาดใหญ่เมื่อแอปแสดง 2 ใน 3 ของหน้าจอ
การวางแนวที่ล็อกอยู่ ตั้งค่า Use Case เพียงครั้งเดียวเมื่อสร้าง Activity เป็นครั้งแรก เช่น ในการเรียกกลับ onCreate() ของ Activity
ใช้ onOrientationChanged() ของ OrientationEventListener ในการเรียกกลับ ให้อัปเดตการหมุนเวียนเป้าหมายของ Use Case รวมถึงจัดการกรณีที่ Activity ไม่ได้สร้างขึ้นใหม่เมื่ออุปกรณ์หมุน (เช่น 90 องศา) กรณีนี้จะเกิดขึ้นในอุปกรณ์ขนาดเล็กเมื่อแอปแสดงครึ่งหน้าจอ และในอุปกรณ์ขนาดใหญ่เมื่อแอปแสดง 2 ใน 3 ของหน้าจอ
Orientation configChanges overridden ตั้งค่า Use Case เพียงครั้งเดียวเมื่อสร้าง Activity เป็นครั้งแรก เช่น ในการเรียกกลับ onCreate() ของ Activity
ใช้ onDisplayChanged() ของ DisplayListener ในการเรียกกลับ ให้อัปเดตการหมุนเป้าหมายของ Use Case เช่น เมื่ออุปกรณ์หมุน 180 องศา รวมถึงจัดการกรณีที่ Activity ไม่ได้สร้างขึ้นใหม่เมื่ออุปกรณ์หมุน (เช่น 90 องศา) กรณีนี้จะเกิดขึ้นในอุปกรณ์ขนาดเล็กเมื่อแอปแสดงครึ่งหน้าจอ และในอุปกรณ์ขนาดใหญ่เมื่อแอปแสดง 2 ใน 3 ของหน้าจอ

การวางแนวที่ปลดล็อก

Activity มีการกําหนดการวางแนวแบบปลดล็อกเมื่อการวางแนวของการแสดงผล (เช่น แนวตั้งหรือแนวนอน) ตรงกับการวางแนวของอุปกรณ์ ยกเว้นการวางแนวแนวตั้ง/แนวนอนกลับด้าน ซึ่งอุปกรณ์บางรุ่นไม่รองรับโดยค่าเริ่มต้น หากต้องการบังคับให้อุปกรณ์หมุนไปยังการวางแนวทั้ง 4 แบบ ให้ตั้งค่าพร็อพเพอร์ตี้ screenOrientation ของ Activity เป็น fullSensor

ในโหมดหลายหน้าต่าง อุปกรณ์ที่ไม่รองรับการกลับแนวตั้ง/แนวนอนโดยค่าเริ่มต้นจะไม่หมุนกลับเป็นแนวตั้ง/แนวนอน แม้ว่าจะตั้งค่าพร็อพเพอร์ตี้ screenOrientation เป็น fullSensor ก็ตาม

<!-- The Activity has an unlocked orientation, but might not rotate to reverse
portrait/landscape in single-window mode if the device doesn't support it by
default. -->
<activity android:name=".UnlockedOrientationActivity" />

<!-- The Activity has an unlocked orientation, and will rotate to all four
orientations in single-window mode. -->
<activity
   android:name=".UnlockedOrientationActivity"
   android:screenOrientation="fullSensor" />

การวางแนวที่ล็อกอยู่

จอแสดงผลจะล็อกการวางแนวไว้เมื่ออยู่ในการวางแนวเดียวกัน (เช่น แนวตั้งหรือแนวนอน) ไม่ว่าอุปกรณ์จะวางในแนวใดก็ตาม ซึ่งทำได้โดยระบุพร็อพเพอร์ตี้ screenOrientation ของ Activity ภายในการประกาศในไฟล์ AndroidManifest.xml

เมื่อจอแสดงผลมีการวางแนวที่ล็อกไว้ ระบบจะไม่ทำลายและสร้าง Activity ขึ้นมาใหม่เมื่อมีการหมุนอุปกรณ์

<!-- The Activity keeps a portrait orientation even as the device rotates. -->
<activity
   android:name=".LockedOrientationActivity"
   android:screenOrientation="portrait" />

ลบล้างการเปลี่ยนแปลงการกำหนดค่าการวางแนว

เมื่อ Activity ลบล้างการเปลี่ยนแปลงการกำหนดค่าการวางแนว ระบบจะไม่ทำลายและสร้างใหม่เมื่อการวางแนวของอุปกรณ์มีการเปลี่ยนแปลง แต่ระบบจะอัปเดต UI ให้ตรงกับการวางแนวของอุปกรณ์

<!-- The Activity's UI might not rotate in reverse portrait/landscape if the
device doesn't support it by default. -->
<activity
   android:name=".OrientationConfigChangesOverriddenActivity"
   android:configChanges="orientation|screenSize" />

<!-- The Activity's UI will rotate to all 4 orientations in single-window
mode. -->
<activity
   android:name=".OrientationConfigChangesOverriddenActivity"
   android:configChanges="orientation|screenSize"
   android:screenOrientation="fullSensor" />

การตั้งค่ากรณีการใช้งานของกล้อง

ในสถานการณ์ที่อธิบายข้างต้น คุณตั้งค่า Use Case ของกล้องได้เมื่อสร้าง Activity เป็นครั้งแรก

ในกรณีที่ Activity มีการวางแนวที่ปลดล็อกอยู่ การตั้งค่านี้จะดำเนินการทุกครั้งที่อุปกรณ์หมุน เนื่องจากระบบจะทำลายและสร้าง Activity ขึ้นมาใหม่เมื่อการวางแนวเปลี่ยนแปลง ผลที่ได้คือ Use Case จะตั้งค่าการหมุนเป้าหมายให้ตรงกับการวางแนวของจอแสดงผลโดยค่าเริ่มต้นทุกครั้ง

ในกรณีที่ Activity มีการวางแนวที่ล็อกไว้หรือลบล้างการเปลี่ยนแปลงการกำหนดค่าการวางแนว การตั้งค่านี้จะทําเพียงครั้งเดียวเมื่อสร้าง Activity เป็นครั้งแรก

class CameraActivity : AppCompatActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)

       val cameraProcessFuture = ProcessCameraProvider.getInstance(this)
       cameraProcessFuture.addListener(Runnable {
          val cameraProvider = cameraProcessFuture.get()

          // By default, the use cases set their target rotation to match the
          // display’s rotation.
          val preview = buildPreview()
          val imageAnalysis = buildImageAnalysis()
          val imageCapture = buildImageCapture()

          cameraProvider.bindToLifecycle(
              this, cameraSelector, preview, imageAnalysis, imageCapture)
       }, mainExecutor)
   }
}

การตั้งค่า OrientationEventListener

การใช้ OrientationEventListener ช่วยให้คุณอัปเดตการหมุนเป้าหมายของกรณีการใช้งานกล้องได้อย่างต่อเนื่องเมื่อการวางแนวของอุปกรณ์เปลี่ยนแปลง

class CameraActivity : AppCompatActivity() {

    private val orientationEventListener by lazy {
        object : OrientationEventListener(this) {
            override fun onOrientationChanged(orientation: Int) {
                if (orientation == ORIENTATION_UNKNOWN) {
                    return
                }

                val rotation = when (orientation) {
                     in 45 until 135 -> Surface.ROTATION_270
                     in 135 until 225 -> Surface.ROTATION_180
                     in 225 until 315 -> Surface.ROTATION_90
                     else -> Surface.ROTATION_0
                 }

                 imageAnalysis.targetRotation = rotation
                 imageCapture.targetRotation = rotation
            }
        }
    }

    override fun onStart() {
        super.onStart()
        orientationEventListener.enable()
    }

    override fun onStop() {
        super.onStop()
        orientationEventListener.disable()
    }
}

การตั้งค่า DisplayListener

การใช้ DisplayListener ช่วยให้คุณอัปเดตกรณีการใช้งานการหมุนเป้าหมายของกล้องได้ในบางสถานการณ์ เช่น เมื่อระบบไม่ทำลายและสร้าง Activity อีกครั้งหลังจากที่อุปกรณ์หมุน 180 องศา

class CameraActivity : AppCompatActivity() {

    private val displayListener = object : DisplayManager.DisplayListener {
        override fun onDisplayChanged(displayId: Int) {
            if (rootView.display.displayId == displayId) {
                val rotation = rootView.display.rotation
                imageAnalysis.targetRotation = rotation
                imageCapture.targetRotation = rotation
            }
        }

        override fun onDisplayAdded(displayId: Int) {
        }

        override fun onDisplayRemoved(displayId: Int) {
        }
    }

    override fun onStart() {
        super.onStart()
        val displayManager = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
        displayManager.registerDisplayListener(displayListener, null)
    }

    override fun onStop() {
        super.onStop()
        val displayManager = getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
        displayManager.unregisterDisplayListener(displayListener)
    }
}