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

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

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

คำศัพท์

หัวข้อนี้ใช้คำศัพท์ต่อไปนี้ ดังนั้นการทำความเข้าใจความหมายของแต่ละคำจึงเป็นสิ่งสำคัญ

การวางแนวจอแสดงผล
ค่านี้หมายถึงด้านของอุปกรณ์ที่อยู่ในตำแหน่งหงายขึ้น และอาจเป็นค่าใดค่าหนึ่งจาก 4 ค่า ได้แก่ แนวตั้ง แนวนอน แนวตั้งกลับด้าน หรือแนวนอนกลับด้าน
การหมุนจอแสดงผล
ค่านี้เป็นค่าที่แสดงโดย 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

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

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

  • 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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

สถานการณ์ หลักเกณฑ์ โหมดแยกหน้าจอแบบหลายหน้าต่าง
การวางแนวแบบปลดล็อก ตั้งค่ากรณีการใช้งานทุกครั้งที่สร้าง Activity เช่น ใน Callback onCreate() ของ Activity
ใช้ onDisplayChanged() ของ DisplayListener ภายใน Callback ให้อัปเดตการหมุนเป้าหมายของ Use Case เช่น เมื่ออุปกรณ์หมุน 180 องศา นอกจากนี้ยังจัดการกรณีที่ไม่มีการสร้าง Activity ขึ้นมาใหม่เมื่ออุปกรณ์หมุน (เช่น 90 องศา) ซึ่งจะเกิดขึ้นในอุปกรณ์รูปแบบขนาดเล็กเมื่อแอปใช้พื้นที่ครึ่งหน้าจอ และในอุปกรณ์ขนาดใหญ่เมื่อแอปใช้พื้นที่ 2 ใน 3 ของหน้าจอ
ล็อกการวางแนว ตั้งค่า Use Case เพียงครั้งเดียวเมื่อสร้าง Activity เป็นครั้งแรก เช่น ใน Callback onCreate() ของ Activity
ใช้onOrientationChanged()ของ OrientationEventListener ภายใน Callback ให้อัปเดตการหมุนเป้าหมายของ Use Case นอกจากนี้ยังจัดการกรณีที่ไม่มีการสร้าง Activity ขึ้นมาใหม่เมื่ออุปกรณ์หมุน (เช่น 90 องศา) ซึ่งจะเกิดขึ้นในอุปกรณ์รูปแบบขนาดเล็กเมื่อแอปใช้พื้นที่ครึ่งหน้าจอ และในอุปกรณ์ขนาดใหญ่เมื่อแอปใช้พื้นที่ 2 ใน 3 ของหน้าจอ
ลบล้าง configChanges ของการวางแนว ตั้งค่า Use Case เพียงครั้งเดียวเมื่อสร้าง Activity เป็นครั้งแรก เช่น ใน Callback onCreate() ของ Activity
ใช้ onDisplayChanged() ของ DisplayListener ภายใน Callback ให้อัปเดตการหมุนเป้าหมายของ 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 : ComponentActivity() {
    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 ช่วยให้คุณอัปเดตการหมุนเป้าหมายของ Use Case ของกล้องได้อย่างต่อเนื่องเมื่อการวางแนวของอุปกรณ์เปลี่ยนแปลง

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 ช่วยให้คุณอัปเดตการหมุนเป้าหมายของ Use Case ของกล้องในบางสถานการณ์ได้ เช่น เมื่อระบบไม่ทำลายและสร้าง 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)
    }
}