Not able to find memory leak

16
April 15, 2019, at 1:10 PM

I could see leakcanary complaining about memory leak in FingerprintManager.mContext in below code. I have tried static inner class, weakreferences in AsyncTask. Not able to find the code creating memory leak.

class BiometricHelper(var localListener: BiometricHelperListener, val fragmentActivity: FragmentActivity) : BiometricPrompt.AuthenticationCallback() {
    private val mListener: WeakReference<BiometricHelper.BiometricHelperListener> = WeakReference(localListener)
    private val mFragmentActivity: WeakReference<FragmentActivity> = WeakReference(fragmentActivity)
    val mEditor: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(TransfastApplication.getContext())

    private val TAG = BiometricHelper::class.java!!.getName()
    private var mkeyStore: KeyStore? = null
    private var mgenerator: KeyGenerator? = null
    private var mcipher: Cipher? = null
    private val KEYSTORE = "somevalue"
    private val PREFERENCES_KEY_IV = "somevalue"
    private val PREFERENCES_KEY_PASS = "somevalue"
    private var mcryptoObject: BiometricPrompt.CryptoObject? = null
    private var encrypting: Boolean = false
    private var mEmail: String? = null
    private var mPassword: String? = null

    private var mAuthTask: BiometricHelper.UserLoginTask? = null
    private lateinit var myBiometricPrompt: BiometricPrompt

    interface BiometricHelperListener {
        fun onAuthenticationSuccessful(result: BiometricPrompt.AuthenticationResult)
        fun onSavedUserAuthenticationSuccessful(password: String)
        fun onAuthenticationFailed(error: String)
        fun onFingerPrintRegistrationDenied()
        fun onNoBiometricSupport()
    }
    public fun registerAuthentication(email: String, password: String) {
        this.mEmail = email;
        this.mPassword = password;
        mAuthTask = UserLoginTask(this, mEditor, email, password)
        mAuthTask?.execute(null as Void?)
    }
    public fun authenticateExistingUser(email: String) {
        this.mEmail = email
        mAuthTask = UserLoginTask(this, mEditor);
        mAuthTask?.execute(null as Void?)
    }
    private fun startAuth(isRegister: Boolean) {
        val executor = Executors.newSingleThreadExecutor()
        myBiometricPrompt = BiometricPrompt(mFragmentActivity.get()!!, executor, this)
        if (mcryptoObject != null) {
            if (isRegister) {
                val promptInfo = BiometricPrompt.PromptInfo.Builder()
                        .setTitle("Enable Fingerprint")
                        .setSubtitle(mFragmentActivity.get()!!.resources.getString(R.string.fingerprint_confirm_message))
                        //.setDescription(mFragmentActivity.get()!!.resources.getString(R.string.fingerprint_confirm_description))
                        .setNegativeButtonText(mFragmentActivity.get()!!.resources.getString(R.string.text_cancel))
                        .build()
                myBiometricPrompt.authenticate(promptInfo, mcryptoObject!!)
            } else {
                val promptInfo = BiometricPrompt.PromptInfo.Builder()
                        .setTitle("Login")
                        .setSubtitle(mEmail)
                        .setNegativeButtonText(mFragmentActivity.get()!!.resources.getString(R.string.text_cancel))
                        .build()
                myBiometricPrompt.authenticate(promptInfo, mcryptoObject!!)
            }

        }
    }
    public fun isUserRegistered(): Boolean {
        if (mEditor.getString(PREFERENCES_KEY_PASS, null) != null) {
            return true;
        }
        return false;
    }

    public fun checkBiometricSupport(): Boolean {

        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) //currently support library does not support Android M
            return false;
        val keyguardManager = mFragmentActivity.get()?.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
        if (!keyguardManager.isKeyguardSecure) {
            false
        }
        val fingerprintManagerCompat = FingerprintManagerCompat.from(mFragmentActivity.get()!!)
        if (!fingerprintManagerCompat.hasEnrolledFingerprints()) {
            return false;
        }
        if (ActivityCompat.checkSelfPermission(mFragmentActivity.get()!!,
                        Manifest.permission.USE_BIOMETRIC) != PackageManager.PERMISSION_GRANTED) {
            false
        }
        return if (mFragmentActivity.get()!!.packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
            true
        } else true
    }

    private fun getKeyStore(): Boolean {
        try {
            mkeyStore = KeyStore.getInstance(KEYSTORE)
            mkeyStore?.load(null) // Create empty keystore
            return true
        } catch (e: KeyStoreException) {
        } catch (e: CertificateException) {
        } catch (e: NoSuchAlgorithmException) {
        } catch (e: IOException) {
        }
        return false
    }

    @TargetApi(Build.VERSION_CODES.M)
    fun createNewKey(forceCreate: Boolean): Boolean {
        try {
            if (forceCreate)
                mkeyStore?.deleteEntry(PrefManager.getInstance().keyAlias)
            if (!mkeyStore?.containsAlias(PrefManager.getInstance().keyAlias)!!) {
                mgenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEYSTORE)
                mgenerator?.init(KeyGenParameterSpec.Builder(PrefManager.getInstance().keyAlias,
                        KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
                        .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
                        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
                        .setUserAuthenticationRequired(true)
                        .build()
                )
                mgenerator?.generateKey()
            }
            return true
        } catch (e: Exception) {
        }
        return false
    }
    private fun getCipher(): Boolean {
        try {
            mcipher = Cipher.getInstance(
                    KeyProperties.KEY_ALGORITHM_AES + "/"
                            + KeyProperties.BLOCK_MODE_CBC + "/"
                            + KeyProperties.ENCRYPTION_PADDING_PKCS7)
            return true
        } catch (e: NoSuchAlgorithmException) {
        } catch (e: NoSuchPaddingException) {
        }
        return false
    }

    public fun isNewFingerprintEnrolled(): Boolean {
        getKeyStore()
        getCipher()
        return !initCipher(Cipher.DECRYPT_MODE);
    }
    @TargetApi(Build.VERSION_CODES.M)
    public fun initCipher(mode: Int): Boolean {
        try {
            mkeyStore?.load(null)
            val keyspec = mkeyStore?.getKey(PrefManager.getInstance().keyAlias, null) as SecretKey
            if (mode == Cipher.ENCRYPT_MODE) {
                mcipher?.init(mode, keyspec)
                var localEditor = mEditor.edit()
                localEditor.putString(PREFERENCES_KEY_IV, Base64.encodeToString(mcipher?.getIV(), Base64.NO_WRAP)).commit()
            } else {
                val iv = Base64.decode(mEditor.getString(PREFERENCES_KEY_IV, ""), Base64.NO_WRAP)
                val ivspec = IvParameterSpec(iv)
                mcipher?.init(mode, keyspec, ivspec)
            }
            return true
        } catch (e: Exception) {
            clearSavedData()
        }
        return false
    }
    @TargetApi(Build.VERSION_CODES.M)
    private fun initCryptObject(): Boolean {
        try {
            mcryptoObject = mcipher?.let { BiometricPrompt.CryptoObject(it) }
            return true
        } catch (ex: Exception) {
        }
        return false
    }
    fun encryptString(password: String) {
        try {
            val bytes = mcipher?.doFinal(password.toByteArray())
            val encryptedText = Base64.encodeToString(bytes, Base64.NO_WRAP)
            var localEditor = mEditor.edit()
            localEditor.putString(PREFERENCES_KEY_PASS, encryptedText).commit()
        } catch (e: Exception) {
            clearSavedData()
        }
    }
    fun decryptString(cipherText: String): String? {
        try {
            val bytes = Base64.decode(cipherText, Base64.NO_WRAP)
            val finalText = String(mcipher!!.doFinal(bytes))
            return finalText;
        } catch (e: Exception) {
            clearSavedData()
        }
        return null;
    }

    class UserLoginTask : AsyncTask<Void, Void, Boolean> {
        private val mEmail: String?
        private val mPassword: String?
        private val mRegister: Boolean? // if false, authenticate instead
        private var biometricHelper: WeakReference<BiometricHelper>
        private var editor: WeakReference<SharedPreferences>
        internal constructor(biometricHelper: BiometricHelper,
                             editor: SharedPreferences,
                             email: String, password: String) {
            this.biometricHelper = WeakReference<BiometricHelper>(biometricHelper)
            this.editor = WeakReference<SharedPreferences>(editor)
            mEmail = email
            mPassword = password
            mRegister = true
        }
        internal constructor(biometricHelper: BiometricHelper,
                             editor: SharedPreferences) {
            this.biometricHelper = WeakReference(biometricHelper)
            this.editor = WeakReference(editor)
            mRegister = false
            mEmail = null
            mPassword = null
        }
        override fun doInBackground(vararg params: Void): Boolean? {
            if (!biometricHelper.get()!!.getKeyStore())
                return false
            if (!biometricHelper.get()!!.createNewKey(false))
                return false
            if (!biometricHelper.get()!!.getCipher())
                return false
            // Inside doInBackground
            if (mRegister!!) {
                biometricHelper.get()!!.encrypting = true
                if (!biometricHelper.get()!!.initCipher(Cipher.ENCRYPT_MODE))
                    return false
            } else {
                biometricHelper.get()!!.encrypting = false
                if (!biometricHelper.get()!!.initCipher(Cipher.DECRYPT_MODE))
                    return false
            }
            return if (!biometricHelper.get()!!.initCryptObject()) false else true
        }
        override fun onPostExecute(success: Boolean) {
            if (success && !isCancelled) {
                mRegister?.let { biometricHelper.get()!!.startAuth(it) }
            } else {
                //something not right proceed with normal login
                biometricHelper.get()!!.onFingerprintRegistrationDenied()
            }
        }
    }
    fun onFingerprintRegistrationDenied() {
        mListener.get()?.onFingerPrintRegistrationDenied()
    }

    override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
        // super.onAuthenticationError(errorCode, errString)
        Logger.d("onAuthenticationError ****  " + errorCode + "***   " + errString)
        if ((errorCode == BiometricPrompt.ERROR_NEGATIVE_BUTTON || errorCode == BiometricPrompt.ERROR_USER_CANCELED) && encrypting) {
            mListener.get()?.onFingerPrintRegistrationDenied()
        } else if (errorCode == BiometricPrompt.ERROR_CANCELED || errorCode == BiometricPrompt.ERROR_LOCKOUT || errorCode == BiometricPrompt.ERROR_LOCKOUT_PERMANENT) {
            myBiometricPrompt.cancelAuthentication()
            mListener.get()?.onAuthenticationFailed(errString.toString())
        } else {
            super.onAuthenticationError(errorCode, errString)
            //    listener.onAuthenticationFailed(errString.toString())
        }
    }

    override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
        if (encrypting) {
            mPassword?.let { encryptString(it) }
            mListener.get()?.onAuthenticationSuccessful(result)
        } else {
            var encryptedPassword = mEditor.getString(PREFERENCES_KEY_PASS, "");
            val decryptedPassword = decryptString(encryptedPassword)
            if (decryptedPassword != null) {
                mListener.get()?.onSavedUserAuthenticationSuccessful(decryptedPassword)
            } else {
                mListener.get()?.onAuthenticationFailed(SignInFragment.FINGERPRINT_ERROR)
            }
        }

    }
    fun clearSavedData() {
        var localEditor = mEditor.edit()
        localEditor.remove(PREFERENCES_KEY_IV).commit();
        localEditor.remove(PREFERENCES_KEY_PASS).commit();
        try {
            mkeyStore?.deleteEntry(PrefManager.getInstance().keyAlias);
        } catch (ex: java.lang.Exception) {
        } finally {
            PrefManager.getInstance().remove(PrefManager.Key.PREF_KEY_ALIAS)
        }
    }

    fun onDestroy() {
        if (mAuthTask != null) {
            mAuthTask?.cancel(true);
            mAuthTask = null
        }
    }
}

Attaching screenshot of message from leakcanary. UserLoginTask class is used to register as well signin the user. BiometricHelper class is used for Fingerprint Authentication.

READ ALSO
How to perform background service while battery optimization is active

How to perform background service while battery optimization is active

I currently use the Firebase JobDipatcher to perform a periodic task in the backgroundThe problem is the background service only gets executed when battery optimization is disabled and standby state of the app is manually set to ACTIVE under developer...

37
Unable to connect to mysql in docker container from another docker container

Unable to connect to mysql in docker container from another docker container

I have two docker containers: mysql and spring boot serviceService container is not connecting to mysql container(failing on the deploy) with following exception: CommunicationsException: Communications link failure So far I have tried using docker --link,...

37
How to Group result from MSQL db by month from a milliseconds column?

How to Group result from MSQL db by month from a milliseconds column?

I'm trying to get total number of item based on month from MSQL database using SLIM framework, When i display data i get result but separated even if there are in the same monthMy code are,

27
i am getting error while using NEW.id in database triggers

i am getting error while using NEW.id in database triggers

i am trying to create a trigger for inserting dataand I want to get the current id for that author or user who is inserting or change in table

29