[2/2] OmniControl: Add custom fingerprint icon support

OmniGears: custom fingerprint icon [2/2]
* Create a new entry in the dashbord
* Also move the haptic feedback toggle

[micky387]
* Remove Fp vibration toggle

[maxwen] new API to copy the image into SystemUI settings
To prevent any grant uri issues do it like wallpaper and
copy the image to a file SystemUI can read without issues

Change-Id: I77b2962c5f525d0828193b5871f197bed74becc6
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index fb7f4a8..61a9130 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
   <component name="CompilerConfiguration">
-    <bytecodeTargetLevel target="11" />
+    <bytecodeTargetLevel target="1.8" />
   </component>
 </project>
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 95858db..050a237 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -76,7 +76,7 @@
       </map>
     </option>
   </component>
-  <component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
     <output url="file://$PROJECT_DIR$/build/classes" />
   </component>
   <component name="ProjectType">
diff --git a/Android.bp b/Android.bp
index 405998a..27154ab 100644
--- a/Android.bp
+++ b/Android.bp
@@ -23,7 +23,8 @@
     "androidx.recyclerview_recyclerview",
     "OmniLib",
     "OmniLibCore",
-    "OmniPreferenceTheme"
+    "OmniPreferenceTheme",
+    "SystemUISharedLib",
   ],
 
   kotlincflags: ["-Xjvm-default=enable"],
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index cc17c54..a4ce9b7 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -17,6 +17,8 @@
     <uses-permission
         android:name="android.permission.CHANGE_OVERLAY_PACKAGES"
         tools:ignore="ProtectedPermissions" />
+    <uses-permission
+        android:name="android.permission.READ_MEDIA_IMAGES" />
 
     <application
         android:name=".App"
diff --git a/app/src/main/java/org/omnirom/control/FingerprintSettingsFragment.kt b/app/src/main/java/org/omnirom/control/FingerprintSettingsFragment.kt
new file mode 100644
index 0000000..ab573d0
--- /dev/null
+++ b/app/src/main/java/org/omnirom/control/FingerprintSettingsFragment.kt
@@ -0,0 +1,249 @@
+/*
+ *  Copyright (C) 2023 The OmniROM Project
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+package org.omnirom.control
+
+import android.app.Activity
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.graphics.Color
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.ColorDrawable
+import android.net.Uri
+import android.os.Bundle
+import android.os.IBinder
+import android.provider.MediaStore
+import android.provider.Settings
+import android.text.TextUtils
+import android.util.Log
+import androidx.activity.result.contract.ActivityResultContract
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.preference.Preference
+import androidx.preference.SwitchPreference
+import com.android.systemui.shared.omni.IOmniSystemUiProxy
+import java.io.File
+import java.io.FileOutputStream
+
+
+class FingerprintSettingsFragment : AbstractSettingsFragment() {
+    private val TAG = "FingerprintSettingsFragment"
+    private val FINGERPRINT_CUSTOM_ICON_SELECT = "custom_fingerprint_icon_select"
+    private val FINGERPRINT_CUSTOM_ICON_ENABLE = "custom_fingerprint_icon_enable"
+    private val UFPSIMAGE_FILE_NAME = "ufpsImage"
+
+    class PickSinglePhotoContract : ActivityResultContract<Void?, Uri?>() {
+        override fun createIntent(context: Context, input: Void?): Intent {
+            return Intent(Intent(MediaStore.ACTION_PICK_IMAGES)).apply { type = "image/*" }
+        }
+
+        override fun parseResult(resultCode: Int, intent: Intent?): Uri? {
+            return intent.takeIf { resultCode == Activity.RESULT_OK }?.data
+        }
+    }
+
+    private val pickImage =
+        registerForActivityResult(PickSinglePhotoContract()) { uri ->
+            Log.d(TAG, "pickImage uri = " + uri)
+            if (uri != null) {
+                // TODO we should do some check if the image is even a useful one eg size
+                try {
+                    requireContext().contentResolver.openInputStream(uri).use { inputStream ->
+                        val bitmap = BitmapFactory.decodeStream(inputStream)
+                        systemUIProxy?.setUfpsImageBitmap(bitmap)
+                        savePickerIcon(bitmap)
+                    }
+
+                    Settings.System.putString(
+                        requireContext().contentResolver,
+                        Settings.System.OMNI_CUSTOM_FP_ICON_UPDATE,
+                        System.currentTimeMillis().toString()
+                    )
+                } catch (e: Exception) {
+                    Log.d(TAG, "pickImage", e);
+                }
+            }
+        }
+
+
+    private val mSystemUIProxyService: ServiceConnection = object : ServiceConnection {
+        override fun onServiceConnected(name: ComponentName, service: IBinder) {
+            Log.d(
+                TAG,
+                "onServiceConnected $name $service"
+            )
+            systemUIProxy = IOmniSystemUiProxy.Stub.asInterface(service)
+        }
+
+        override fun onServiceDisconnected(name: ComponentName) {
+            Log.d(TAG, "onServiceDisconnected $name")
+        }
+    }
+
+    private var mSystemUIProxyBound = false
+    private var systemUIProxy: IOmniSystemUiProxy? = null
+    private var mFilePicker: Preference? = null
+    private var mEnableCustom: SwitchPreference? = null
+
+
+    private fun bindSystemUIProxyService() {
+        if (!mSystemUIProxyBound) {
+            Log.d(TAG, "bindSystemUIProxyService")
+            val serviceIntent = Intent("android.intent.action.OMNI_SYSTEMUI_SERVICE")
+                .setPackage("com.android.systemui")
+            try {
+                mSystemUIProxyBound = requireActivity().bindService(
+                    serviceIntent,
+                    mSystemUIProxyService,
+                    Context.BIND_AUTO_CREATE
+                )
+            } catch (e: SecurityException) {
+                Log.e(
+                    TAG,
+                    "Unable to bind because of security error",
+                    e
+                )
+            }
+        }
+    }
+
+    private fun unbindSystemUIProxyService() {
+        if (mSystemUIProxyBound) {
+            Log.d(TAG, "unbindSystemUIProxyService")
+            requireActivity().unbindService(mSystemUIProxyService)
+            mSystemUIProxyBound = false
+        }
+    }
+
+    override fun getFragmentTitle(): String {
+        return getString(R.string.fprint_title)
+    }
+
+    override fun getFragmentSummary(): String {
+        return getString(R.string.fprint_summary)
+    }
+
+    override fun getFragmentIcon(): Int {
+        return R.drawable.ic_settings_fingerprint
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+        unbindSystemUIProxyService()
+    }
+
+    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+        setPreferencesFromResource(R.xml.fingerprint_preferences, rootKey)
+
+        mEnableCustom = findPreference<SwitchPreference>(FINGERPRINT_CUSTOM_ICON_ENABLE)
+        mEnableCustom?.let {
+            it.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
+                if (newValue as Boolean) {
+                    if (getCustomImagePath().exists()) {
+                        enablePickerIcon()
+                    }
+                } else {
+                    systemUIProxy?.resetUfpsImage()
+                }
+                true
+            }
+        }
+        mFilePicker = findPreference(FINGERPRINT_CUSTOM_ICON_SELECT)
+        mFilePicker?.let {
+            bindSystemUIProxyService()
+
+            if (getCustomImagePath().exists()) {
+                setPickerIcon()
+            }
+
+            it.onPreferenceClickListener = Preference.OnPreferenceClickListener {
+                pickImage.launch(null)
+                true
+            }
+        }
+    }
+
+    private fun getCustomImagePath(): File {
+        return File(requireContext().filesDir, UFPSIMAGE_FILE_NAME)
+    }
+
+    private fun setPickerIcon() {
+        try {
+            val parcelFileDescriptor = requireContext().contentResolver.openFileDescriptor(
+                Uri.fromFile(getCustomImagePath()),
+                "r"
+            )
+            parcelFileDescriptor?.let { it ->
+                val bitmap = BitmapFactory.decodeFileDescriptor(it.fileDescriptor)
+                it.close()
+                val d = BitmapDrawable(resources, bitmap)
+                mFilePicker?.icon = d
+            }
+        } catch (e: Exception) {
+            Log.e(
+                TAG,
+                "setPickerIcon",
+                e
+            )
+        }
+    }
+
+    private fun enablePickerIcon() {
+        try {
+            val parcelFileDescriptor = requireContext().contentResolver.openFileDescriptor(
+                Uri.fromFile(getCustomImagePath()),
+                "r"
+            )
+            parcelFileDescriptor?.let { it ->
+                val bitmap = BitmapFactory.decodeFileDescriptor(it.fileDescriptor)
+                it.close()
+                systemUIProxy?.setUfpsImageBitmap(bitmap)
+            }
+        } catch (e: Exception) {
+            Log.e(
+                TAG,
+                "setPickerIcon",
+                e
+            )
+        }
+    }
+
+    private fun savePickerIcon(bitmap: Bitmap) {
+        try {
+            val fos = FileOutputStream(getCustomImagePath())
+            bitmap.compress(Bitmap.CompressFormat.PNG, 90, fos)
+            fos.close()
+            val d = BitmapDrawable(resources, bitmap)
+            mFilePicker?.icon = d
+        } catch (e: Exception) {
+            Log.e(
+                TAG,
+                "savePickerIcon",
+                e
+            )
+        }
+    }
+
+    private fun deletePickerIcon() {
+        if (getCustomImagePath().exists()) {
+            getCustomImagePath().delete()
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/omnirom/control/GridViewFragment.kt b/app/src/main/java/org/omnirom/control/GridViewFragment.kt
index 8236e56..81f95e4 100644
--- a/app/src/main/java/org/omnirom/control/GridViewFragment.kt
+++ b/app/src/main/java/org/omnirom/control/GridViewFragment.kt
@@ -27,6 +27,8 @@
 import android.widget.*
 import androidx.fragment.app.Fragment
 
+import com.android.internal.util.ArrayUtils;
+
 import org.omnirom.omnilibcore.utils.DeviceUtils
 
 class GridViewFragment() : Fragment() {
@@ -84,7 +86,7 @@
                 AppListFragment()
             )
         )
-        if (getResources().getBoolean(com.android.internal.R.bool.config_intrusiveBatteryLed)) {
+        if (resources.getBoolean(com.android.internal.R.bool.config_intrusiveBatteryLed)) {
             gridItems.add(
                 FragmentGridItem(
                     R.string.batterylight_title,
@@ -120,6 +122,16 @@
                 )
             )
         }
+        if (resources.getBoolean(R.bool.config_has_udfps)) {
+            gridItems.add(
+                FragmentGridItem(
+                    R.string.fprint_title,
+                    R.string.fprint_summary,
+                    R.drawable.ic_settings_fingerprint,
+                    FingerprintSettingsFragment()
+                )
+            )
+        }
         gridItems.add(
             FragmentGridItem(
                 R.string.lockscreen_item_title,
diff --git a/app/src/main/res/drawable/ic_settings_fingerprint.xml b/app/src/main/res/drawable/ic_settings_fingerprint.xml
new file mode 100644
index 0000000..37eb6f1
--- /dev/null
+++ b/app/src/main/res/drawable/ic_settings_fingerprint.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:height="24dp"
+    android:width="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?android:textColorPrimary">
+
+    <path android:fillColor="@android:color/white"
+          android:pathData="M17.81,4.47C17.73,4.47 17.65,4.45 17.58,4.41C15.66,3.42 14,3 12,3C10.03,3 8.15,3.47 6.44,4.41C6.2,4.54 5.9,4.45 5.76,4.21C5.63,3.97 5.72,3.66 5.96,3.53C7.82,2.5 9.86,2 12,2C14.14,2 16,2.47 18.04,3.5C18.29,3.65 18.38,3.95 18.25,4.19C18.16,4.37 18,4.47 17.81,4.47M3.5,9.72C3.4,9.72 3.3,9.69 3.21,9.63C3,9.47 2.93,9.16 3.09,8.93C4.08,7.53 5.34,6.43 6.84,5.66C10,4.04 14,4.03 17.15,5.65C18.65,6.42 19.91,7.5 20.9,8.9C21.06,9.12 21,9.44 20.78,9.6C20.55,9.76 20.24,9.71 20.08,9.5C19.18,8.22 18.04,7.23 16.69,6.54C13.82,5.07 10.15,5.07 7.29,6.55C5.93,7.25 4.79,8.25 3.89,9.5C3.81,9.65 3.66,9.72 3.5,9.72M9.75,21.79C9.62,21.79 9.5,21.74 9.4,21.64C8.53,20.77 8.06,20.21 7.39,19C6.7,17.77 6.34,16.27 6.34,14.66C6.34,11.69 8.88,9.27 12,9.27C15.12,9.27 17.66,11.69 17.66,14.66A0.5,0.5 0 0,1 17.16,15.16A0.5,0.5 0 0,1 16.66,14.66C16.66,12.24 14.57,10.27 12,10.27C9.43,10.27 7.34,12.24 7.34,14.66C7.34,16.1 7.66,17.43 8.27,18.5C8.91,19.66 9.35,20.15 10.12,20.93C10.31,21.13 10.31,21.44 10.12,21.64C10,21.74 9.88,21.79 9.75,21.79M16.92,19.94C15.73,19.94 14.68,19.64 13.82,19.05C12.33,18.04 11.44,16.4 11.44,14.66A0.5,0.5 0 0,1 11.94,14.16A0.5,0.5 0 0,1 12.44,14.66C12.44,16.07 13.16,17.4 14.38,18.22C15.09,18.7 15.92,18.93 16.92,18.93C17.16,18.93 17.56,18.9 17.96,18.83C18.23,18.78 18.5,18.96 18.54,19.24C18.59,19.5 18.41,19.77 18.13,19.82C17.56,19.93 17.06,19.94 16.92,19.94M14.91,22C14.87,22 14.82,22 14.78,22C13.19,21.54 12.15,20.95 11.06,19.88C9.66,18.5 8.89,16.64 8.89,14.66C8.89,13.04 10.27,11.72 11.97,11.72C13.67,11.72 15.05,13.04 15.05,14.66C15.05,15.73 16,16.6 17.13,16.6C18.28,16.6 19.21,15.73 19.21,14.66C19.21,10.89 15.96,7.83 11.96,7.83C9.12,7.83 6.5,9.41 5.35,11.86C4.96,12.67 4.76,13.62 4.76,14.66C4.76,15.44 4.83,16.67 5.43,18.27C5.53,18.53 5.4,18.82 5.14,18.91C4.88,19 4.59,18.87 4.5,18.62C4,17.31 3.77,16 3.77,14.66C3.77,13.46 4,12.37 4.45,11.42C5.78,8.63 8.73,6.82 11.96,6.82C16.5,6.82 20.21,10.33 20.21,14.65C20.21,16.27 18.83,17.59 17.13,17.59C15.43,17.59 14.05,16.27 14.05,14.65C14.05,13.58 13.12,12.71 11.97,12.71C10.82,12.71 9.89,13.58 9.89,14.65C9.89,16.36 10.55,17.96 11.76,19.16C12.71,20.1 13.62,20.62 15.03,21C15.3,21.08 15.45,21.36 15.38,21.62C15.33,21.85 15.12,22 14.91,22Z" />
+</vector>
diff --git a/app/src/main/res/values/config.xml b/app/src/main/res/values/config.xml
index 08e2d57..9d32956 100644
--- a/app/src/main/res/values/config.xml
+++ b/app/src/main/res/values/config.xml
@@ -12,4 +12,7 @@
 
     <!-- Does devices support fast charging battery led -->
     <bool name="config_FastChargingLedSupported">false</bool>
+
+    <!-- udfps support. -->
+    <bool name="config_has_udfps">false</bool>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index b138726..3917599 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -146,4 +146,11 @@
     <string name="incall_vibrate_disconnect_summary">Device vibrate on call disconnect</string>
     <string name="monet_overlay_enable_title">Enable dynamic colors based on wallpaper - Monet</string>
     <string name="system_colors_title">System colors</string>
+
+    <!-- Fingerprint -->
+    <string name="fprint_title">Fingerprint</string>
+    <string name="fprint_summary">Fingerprint Options</string>
+    <string name="fingerprint_category_custom_icon">Custom icon</string>
+    <string name="custom_fp_icon_enable_title">Use custom icon</string>
+    <string name="custom_fp_icon_select_title">Select custom icon</string>
 </resources>
diff --git a/app/src/main/res/xml/fingerprint_preferences.xml b/app/src/main/res/xml/fingerprint_preferences.xml
new file mode 100644
index 0000000..408d602
--- /dev/null
+++ b/app/src/main/res/xml/fingerprint_preferences.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?><!--  Copyright (C) 2018 The OmniROM Project
+  This program is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation, either version 2 of the License, or
+  (at your option) any later version.
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+  GNU General Public License for more details.
+  You should have received a copy of the GNU General Public License
+  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ -->
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    android:key="fingerprint_p">
+
+    <PreferenceCategory
+        android:key="category_fingerprint_custom_icon"
+        android:title="@string/fingerprint_category_custom_icon">
+        <SwitchPreference
+            android:defaultValue="false"
+            android:key="custom_fingerprint_icon_enable"
+            android:title="@string/custom_fp_icon_enable_title" />
+        <Preference
+            android:dependency="custom_fingerprint_icon_enable"
+            android:key="custom_fingerprint_icon_select"
+            android:title="@string/custom_fp_icon_select_title" />
+    </PreferenceCategory>
+</PreferenceScreen>