Aperture: Add an info chip

* As of now it only shows low battery warning and video settings

Change-Id: I4a0d10b13a6a4de3cb3e6392179cbf38b137ca14
diff --git a/app/src/main/java/org/lineageos/aperture/CameraActivity.kt b/app/src/main/java/org/lineageos/aperture/CameraActivity.kt
index b285e68..1b22884 100644
--- a/app/src/main/java/org/lineageos/aperture/CameraActivity.kt
+++ b/app/src/main/java/org/lineageos/aperture/CameraActivity.kt
@@ -8,7 +8,10 @@
 import android.animation.ValueAnimator
 import android.annotation.SuppressLint
 import android.app.KeyguardManager
+import android.content.BroadcastReceiver
+import android.content.Context
 import android.content.Intent
+import android.content.IntentFilter
 import android.content.pm.ActivityInfo
 import android.graphics.BitmapFactory
 import android.graphics.Color
@@ -101,6 +104,7 @@
 import org.lineageos.aperture.ui.CountDownView
 import org.lineageos.aperture.ui.GridView
 import org.lineageos.aperture.ui.HorizontalSlider
+import org.lineageos.aperture.ui.InfoChipView
 import org.lineageos.aperture.ui.LensSelectorLayout
 import org.lineageos.aperture.ui.LevelerView
 import org.lineageos.aperture.ui.LocationPermissionsDialog
@@ -147,6 +151,7 @@
     private val googleLensButton by lazy { findViewById<ImageButton>(R.id.googleLensButton) }
     private val gridButton by lazy { findViewById<Button>(R.id.gridButton) }
     private val gridView by lazy { findViewById<GridView>(R.id.gridView) }
+    private val infoChipView by lazy { findViewById<InfoChipView>(R.id.infoChipView) }
     private val lensSelectorLayout by lazy { findViewById<LensSelectorLayout>(R.id.lensSelectorLayout) }
     private val levelerView by lazy { findViewById<LevelerView>(R.id.levelerView) }
     private val micButton by lazy { findViewById<Button>(R.id.micButton) }
@@ -484,6 +489,14 @@
         }
     }
 
+    private val batteryBroadcastReceiver by lazy {
+        object : BroadcastReceiver() {
+            override fun onReceive(context: Context?, intent: Intent?) {
+                infoChipView.batteryIntent = intent
+            }
+        }
+    }
+
     enum class ShutterAnimation(val resourceId: Int) {
         InitPhoto(R.drawable.avd_photo_capture),
         InitVideo(R.drawable.avd_mode_video_photo),
@@ -577,6 +590,7 @@
         // Pass the view model to the views
         capturePreviewLayout.cameraViewModel = model
         countDownView.cameraViewModel = model
+        infoChipView.cameraViewModel = model
 
         // Restore settings from shared preferences
         gridMode = sharedPreferences.lastGridMode
@@ -1153,6 +1167,9 @@
             powerManager.addThermalStatusListener(onThermalStatusChangedListener)
         }
 
+        // Start observing battery status
+        registerReceiver(batteryBroadcastReceiver, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
+
         // Re-bind the use cases
         bindCameraUseCases()
     }
@@ -1169,6 +1186,9 @@
             powerManager.removeThermalStatusListener(onThermalStatusChangedListener)
         }
 
+        // Remove battery status receiver
+        unregisterReceiver(batteryBroadcastReceiver)
+
         super.onPause()
     }
 
diff --git a/app/src/main/java/org/lineageos/aperture/ui/InfoChipView.kt b/app/src/main/java/org/lineageos/aperture/ui/InfoChipView.kt
new file mode 100644
index 0000000..0fcaa22
--- /dev/null
+++ b/app/src/main/java/org/lineageos/aperture/ui/InfoChipView.kt
@@ -0,0 +1,175 @@
+/*
+ * SPDX-FileCopyrightText: 2023 The LineageOS Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.lineageos.aperture.ui
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.content.Context
+import android.content.Intent
+import android.os.BatteryManager
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.widget.ImageView
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.view.isVisible
+import androidx.core.view.updateLayoutParams
+import androidx.lifecycle.findViewTreeLifecycleOwner
+import org.lineageos.aperture.R
+import org.lineageos.aperture.camera.CameraMode
+import org.lineageos.aperture.camera.CameraViewModel
+import org.lineageos.aperture.utils.Rotation
+import kotlin.math.roundToInt
+
+class InfoChipView @JvmOverloads constructor(
+    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : ConstraintLayout(context, attrs, defStyleAttr) {
+    // Views
+    private val lowBatteryImageView by lazy { findViewById<ImageView>(R.id.lowBatteryImageView) }
+    private val videoMicMutedImageView by lazy { findViewById<ImageView>(R.id.videoMicMutedImageView) }
+
+    // System services
+    private val layoutInflater = context.getSystemService(LayoutInflater::class.java)
+
+    private val shortAnimationDuration by lazy {
+        resources.getInteger(android.R.integer.config_shortAnimTime).toLong()
+    }
+
+    internal var batteryIntent: Intent? = null
+        set(value) {
+            field = value
+
+            update()
+        }
+    internal var cameraViewModel: CameraViewModel? = null
+        set(value) {
+            val lifecycleOwner = findViewTreeLifecycleOwner() ?: return
+
+            field?.let {
+                // Unregister
+                it.screenRotation.removeObservers(lifecycleOwner)
+                it.cameraMode.removeObservers(lifecycleOwner)
+                it.videoMicMode.removeObservers(lifecycleOwner)
+            }
+
+            field = value
+
+            value?.let { cameraViewModel ->
+                cameraViewModel.screenRotation.observe(lifecycleOwner) {
+                    updateRotation()
+                }
+                cameraViewModel.cameraMode.observe(lifecycleOwner) {
+                    update()
+                }
+                cameraViewModel.videoMicMode.observe(lifecycleOwner) {
+                    update()
+                }
+            }
+        }
+
+    init {
+        layoutInflater.inflate(R.layout.info_chip_view, this)
+    }
+
+    private fun update() {
+        val cameraViewModel = cameraViewModel ?: return
+
+        val cameraMode = cameraViewModel.cameraMode.value ?: return
+        val videoMicMode = cameraViewModel.videoMicMode.value ?: return
+
+        // Get the new visibility values
+        val lowBatteryImageViewVisible = batteryIntent?.let {
+            val level = it.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
+            val scale = it.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
+
+            val batteryPercentage = ((level * 100) / scale.toFloat()).roundToInt()
+
+            batteryPercentage <= 15
+        } ?: false
+        val videoMicMutedImageViewVisible = cameraMode == CameraMode.VIDEO && !videoMicMode
+
+        val shouldBeVisible = listOf(
+            lowBatteryImageViewVisible,
+            videoMicMutedImageViewVisible,
+        ).any { it }
+
+        val postUpdate = {
+            // Set the visibility on the views
+            lowBatteryImageView.isVisible = lowBatteryImageViewVisible
+            videoMicMutedImageView.isVisible = videoMicMutedImageViewVisible
+
+            updateRotation()
+        }
+
+        if (shouldBeVisible && !isVisible) {
+            postUpdate()
+
+            // Set the content view to 0% opacity but visible, so that it is visible
+            // (but fully transparent) during the animation.
+            alpha = 0f
+            isVisible = true
+
+            // Animate the content view to 100% opacity, and clear any animation
+            // listener set on the view.
+            animate()
+                .alpha(1f)
+                .setDuration(shortAnimationDuration)
+                .setListener(null)
+        } else if (!shouldBeVisible && isVisible) {
+            animate()
+                .alpha(0f)
+                .setDuration(shortAnimationDuration)
+                .setListener(object : AnimatorListenerAdapter() {
+                    override fun onAnimationEnd(animation: Animator) {
+                        isVisible = false
+
+                        postUpdate()
+                    }
+                })
+        } else {
+            postUpdate()
+        }
+    }
+
+    private fun updateRotation() {
+        val cameraViewModel = cameraViewModel ?: return
+
+        val screenRotation = cameraViewModel.screenRotation.value ?: return
+
+        val compensationValue = screenRotation.compensationValue.toFloat()
+
+        updateLayoutParams<LayoutParams> {
+            startToStart = when (screenRotation) {
+                Rotation.ROTATION_0,
+                Rotation.ROTATION_90,
+                Rotation.ROTATION_180 -> R.id.viewFinder
+                Rotation.ROTATION_270 -> LayoutParams.UNSET
+            }
+            endToEnd = when (screenRotation) {
+                Rotation.ROTATION_0,
+                Rotation.ROTATION_90,
+                Rotation.ROTATION_180 -> LayoutParams.UNSET
+                Rotation.ROTATION_270 -> R.id.viewFinder
+            }
+        }
+
+        rotation = compensationValue
+
+        measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
+
+        translationX = when (screenRotation) {
+            Rotation.ROTATION_0,
+            Rotation.ROTATION_180 -> 0F
+            Rotation.ROTATION_90 -> -((measuredWidth - measuredHeight) / 2).toFloat()
+            Rotation.ROTATION_270 -> ((measuredWidth - measuredHeight) / 2).toFloat()
+        }
+        translationY = when (screenRotation) {
+            Rotation.ROTATION_0,
+            Rotation.ROTATION_180 -> 0F
+            Rotation.ROTATION_90,
+            Rotation.ROTATION_270 -> -((measuredHeight - measuredWidth) / 2).toFloat()
+        }
+    }
+}
diff --git a/app/src/main/res/drawable/ic_battery_1_bar.xml b/app/src/main/res/drawable/ic_battery_1_bar.xml
new file mode 100644
index 0000000..ddda245
--- /dev/null
+++ b/app/src/main/res/drawable/ic_battery_1_bar.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="#FFFFFF"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M17,5v16c0,0.55 -0.45,1 -1,1H8c-0.55,0 -1,-0.45 -1,-1V5c0,-0.55 0.45,-1 1,-1h2V2h4v2h2C16.55,4 17,4.45 17,5zM15,6H9v12h6V6z" />
+</vector>
diff --git a/app/src/main/res/drawable/info_chip_view_background.xml b/app/src/main/res/drawable/info_chip_view_background.xml
new file mode 100644
index 0000000..75cfd4e
--- /dev/null
+++ b/app/src/main/res/drawable/info_chip_view_background.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     SPDX-FileCopyrightText: 2023 The LineageOS Project
+     SPDX-License-Identifier: Apache-2.0
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <corners android:radius="32dp" />
+</shape>
diff --git a/app/src/main/res/layout/activity_camera.xml b/app/src/main/res/layout/activity_camera.xml
index d5f7799..414001f 100644
--- a/app/src/main/res/layout/activity_camera.xml
+++ b/app/src/main/res/layout/activity_camera.xml
@@ -64,6 +64,18 @@
         app:layout_constraintStart_toStartOf="@+id/viewFinder"
         app:layout_constraintTop_toTopOf="@+id/viewFinder" />
 
+    <org.lineageos.aperture.ui.InfoChipView
+        android:id="@+id/infoChipView"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="12dp"
+        android:layout_marginEnd="12dp"
+        android:layout_marginStart="12dp"
+        android:layout_marginTop="12dp"
+        android:visibility="gone"
+        app:layout_constraintStart_toStartOf="@+id/viewFinder"
+        app:layout_constraintTop_toTopOf="@+id/viewFinder" />
+
     <org.lineageos.aperture.ui.CountDownView
         android:id="@+id/countDownView"
         android:layout_width="0dp"
diff --git a/app/src/main/res/layout/info_chip_view.xml b/app/src/main/res/layout/info_chip_view.xml
new file mode 100644
index 0000000..54a24ce
--- /dev/null
+++ b/app/src/main/res/layout/info_chip_view.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     SPDX-FileCopyrightText: 2023 The LineageOS Project
+     SPDX-License-Identifier: Apache-2.0
+-->
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    style="@style/Theme.Aperture.Camera.InfoChip"
+    android:layout_width="wrap_content"
+    android:layout_height="32dp"
+    android:background="@drawable/info_chip_view_background"
+    android:backgroundTint="#222222"
+    android:minWidth="32dp"
+    android:orientation="horizontal"
+    android:paddingHorizontal="12dp"
+    android:paddingVertical="8dp">
+
+    <ImageView
+        android:id="@+id/lowBatteryImageView"
+        style="@style/Theme.Aperture.Camera.InfoChip.ImageView"
+        android:src="@drawable/ic_battery_1_bar"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:tint="#EE5555" />
+
+    <ImageView
+        android:id="@+id/videoMicMutedImageView"
+        style="@style/Theme.Aperture.Camera.InfoChip.ImageView"
+        android:layout_marginStart="8dp"
+        android:src="@drawable/ic_mic_off"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/lowBatteryImageView"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_goneMarginStart="0dp"
+        app:tint="@android:color/white" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index 36001b9..0891a12 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -100,4 +100,13 @@
         <item name="android:textSize">36sp</item>
         <item name="android:drawablePadding">10dp</item>
     </style>
+
+    <!-- Info chip -->
+    <style name="Theme.Aperture.Camera.InfoChip" />
+
+    <style name="Theme.Aperture.Camera.InfoChip.ImageView">
+        <item name="android:layout_width">16dp</item>
+        <item name="android:layout_height">16dp</item>
+        <item name="android:visibility">gone</item>
+    </style>
 </resources>