How To Fix Glide SSL Exception in Android Compose
This is a fix for Glide SSL Exception for loading an image in Android Compose. Trust anchor for certification path not found error.

In this post I will show you the steps I took to fix the Glide SSL Exception in Android.

Android Glide SSL Exception

Background

I am working on an Android app that will control your smart TV. A simple TV remote control in Android. The remote in my house is always missing. I know there are plenty on the Play Store but I wanted to create a less bloated app than the ones I found on the app store.

One of the TV's I own is a LG that uses Web OS. LGs website directs you to use ConnectSDK to develop an app that communicates with the TV.

This is the ConnectSDK website. https://connectsdk.com/en/latest/

The SDK is fairly straight forward.

I was quickly able to search for devices, select devices, simple communication ( cursor movements), launch apps, get device info, etc.

Where I Ran Into Issues with the SSL Handshake

I created a simple remote controller UI that also lists all the apps installed on the device via ConnectSDK in JSON format. The list has icon images for the apps. This would look good in the remote UI for the user.

When I try to load the images via Glide, I get the SSL Exception. The exception is more specifically stated as follows:

Caused by: javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.

The ConnectSDK library does contain a Websocket with proper authentication. I was unable to find a method to use to utilize the same Websocket for Glide.

I reached out to Stackoverflow as well as ConnectSDKs Github and was unsuccessful in getting a solution that worked for me.

My Questions Posted Below

The posts below show the different approaches I tried utilizing the ConnectSDK library.

android-connectsdk-display-the-app-icon-from-connected-tv-device-not-working-er

https://github.com/ConnectSDK/Connect-SDK-Android/issues/412

What I Did That Worked

I followed quite a few different posts and websites to make this work. This solution is a mix of the ConnectSDK library source, Glide library and various posts I read.

Helpful Links

glide-javax-net-ssl-sslhandshakeexception-java-security-cert-certpathvalidato

OkHttpStreamFetcher.java

how-to-fix-android-glide-ssl-exception

Steps

I added the necessary libraries and annotation processor. I used KSP as annotation processor.

build.gradle

Add to build.gradle (module)

plugins {    
    ....
    alias(libs.plugins.ksp)
}

....

dependencies {
....

    //for Glide
    ksp(libs.glide.ksp)
    implementation(libs.glide.compose)


}

Add to build.gradle (project)

plugins {    
    ....
    alias(libs.plugins.ksp) apply false
}

libs.versions.toml

Add to libs.versions.toml

[versions]
ksp = "2.0.21-1.0.27"
composeVersion = "1.0.0-beta01"

....
[libraries]
glide-compose = { module = "com.github.bumptech.glide:compose", version.ref = "composeVersion" }
....

[plugins]
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp"}

glidestuff

I created a few files in a separate package to keep things in order as much as possible. Needed decent structure. I named my package glidestuff off the main package.

Image showing the project package structure.

CustomOkHttpClient

Inside CustomOkHttpClient

import okhttp3.OkHttpClient
import java.security.SecureRandom
import java.security.cert.CertificateException
import java.security.cert.X509Certificate
import javax.net.ssl.HostnameVerifier
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.TrustManager
import javax.net.ssl.X509TrustManager


object CustomOkHttpClient {
    val customOkHttpClient: OkHttpClient
        get() {
            try {
                // Create a trust manager that does not validate certificate chains
                val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
                    @Throws(CertificateException::class)
                    override fun checkClientTrusted(
                        chain: Array<X509Certificate>,
                        authType: String
                    ) {
                    }

                    @Throws(CertificateException::class)
                    override fun checkServerTrusted(
                        chain: Array<X509Certificate>,
                        authType: String
                    ) {
                    }

                    override fun getAcceptedIssuers(): Array<X509Certificate> {
                        return arrayOf()
                    }
                }
                )

                // Install the all-trusting trust manager
                val sslContext = SSLContext.getInstance("SSL")
                sslContext.init(null, trustAllCerts, SecureRandom())

                // Create an ssl socket factory with our all-trusting manager
                val sslSocketFactory: SSLSocketFactory = sslContext.socketFactory

                val builder: OkHttpClient.Builder = OkHttpClient.Builder()
                builder.sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager)
                builder.hostnameVerifier(HostnameVerifier { hostname, session -> true })

                val okHttpClient: OkHttpClient = builder.build()
                return okHttpClient
            } catch (e: Exception) {
                throw RuntimeException(e)
            }
        }
}

Custom OkHttpGlideModule

Now inside CustomOkHttpGlideModule. Glide will use this to create a client connection. This can be customize for a variety of reasons, our reason is to bypass the SSL Handshake process.

import android.content.Context
import com.bumptech.glide.Glide
import com.bumptech.glide.Registry
import com.bumptech.glide.annotation.GlideModule
import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.module.AppGlideModule
import com.itgeek25.sampletvremote.glidestuff.CustomOkHttpClient.customOkHttpClient
import java.io.InputStream


@GlideModule
class CustomOkHttpGlideModule : AppGlideModule() {
    override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
        val client = customOkHttpClient
        registry.replace(
            GlideUrl::class.java, InputStream::class.java,
            OkHttpUrlLoader.Factory(client)
        )
    }
}

OkHttpStreamFetcher

Add to the OkHttpStreamFetcher

import android.util.Log
import com.bumptech.glide.Priority
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.HttpException
import com.bumptech.glide.load.data.DataFetcher
import com.bumptech.glide.load.data.DataFetcher.DataCallback
import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.util.ContentLengthInputStream
import com.bumptech.glide.util.Preconditions
import okhttp3.Call
import okhttp3.Callback
import okhttp3.Request
import okhttp3.Response
import okhttp3.ResponseBody
import java.io.IOException
import java.io.InputStream
import kotlin.concurrent.Volatile


/** Fetches an [InputStream] using the okhttp library.  */
class OkHttpStreamFetcher(client: Call.Factory, private val url: GlideUrl) : DataFetcher<InputStream>,
    Callback {
        private val client: Call.Factory = client
        private var stream: InputStream? = null
        private var responseBody: ResponseBody? = null


    // call may be accessed on the main thread while the object is in use on other threads. All other
    // accesses to variables may occur on different threads, but only one at a time.
    @Volatile
    private var call: Call? = null
    private var callback: DataCallback<in InputStream>? = null
        
    override fun loadData(
        priority: Priority, callback: DataCallback<in InputStream>
    ) {
        val requestBuilder: Request.Builder = Request.Builder().url(url.toStringUrl())
        for ((key, value) in url.headers) {
            requestBuilder.addHeader(key, value)
        }
        val request: Request = requestBuilder.build()
        this.callback = callback

        call = client.newCall(request)
        call!!.enqueue(this)
    }

    override fun onFailure(call: Call, e: IOException) {
        if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "OkHttp failed to obtain result", e)
        }

        callback!!.onLoadFailed(e)
    }

    override fun onResponse(call: Call, response: Response) {
        responseBody = response.body
        if (response.isSuccessful) {
            val contentLength = Preconditions.checkNotNull(responseBody).contentLength()
            stream = ContentLengthInputStream.obtain(responseBody!!.byteStream(), contentLength)
            callback!!.onDataReady(stream)
        } else {
            callback!!.onLoadFailed(HttpException(response.message, response.code))
        }
    }

    override fun cleanup() {
        try {
            if (stream != null) {
                stream!!.close()
            }
        } catch (e: IOException) {
            // Ignored
        }
        if (responseBody != null) {
            responseBody!!.close()
        }
        callback = null
    }

    override fun cancel() {
        val local = call
        local?.cancel()
    }

    override fun getDataClass(): Class<InputStream> {
        return InputStream::class.java
    }

    override fun getDataSource(): DataSource {
        return DataSource.REMOTE
    }

    companion object {
        private const val TAG = "OkHttpFetcher"
    }
}

OkHttpUrlLoader

Add to OkHttpUrlLoader

import com.bumptech.glide.load.Options
import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.load.model.ModelLoader
import com.bumptech.glide.load.model.ModelLoader.LoadData
import com.bumptech.glide.load.model.ModelLoaderFactory
import com.bumptech.glide.load.model.MultiModelLoaderFactory
import okhttp3.Call
import okhttp3.OkHttpClient
import java.io.InputStream
import kotlin.concurrent.Volatile


/** A simple model loader for fetching media over http/https using OkHttp.  */
class OkHttpUrlLoader // Public API.
    (private val client: OkHttpClient) : ModelLoader<GlideUrl, InputStream> {
    override fun handles(url: GlideUrl): Boolean {
        return true
    }

    override fun buildLoadData(
        model: GlideUrl, width: Int, height: Int, options: Options
    ): LoadData<InputStream>? {
        return LoadData<InputStream>(model, OkHttpStreamFetcher(client as Call.Factory, model))
    }

    /** The default factory for [OkHttpUrlLoader]s.  */ // Public API.
    class Factory
    /** Constructor for a new Factory that runs requests using a static singleton client.  */ @JvmOverloads constructor(
        private val client: OkHttpClient = internalClient!! as OkHttpClient
    ) :
        ModelLoaderFactory<GlideUrl, InputStream> {
        /**
         * Constructor for a new Factory that runs requests using given client.
         *
         * @param client this is typically an instance of `OkHttpClient`.
         */

        override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<GlideUrl, InputStream> {
            return OkHttpUrlLoader(client)
        }

        override fun teardown() {
            // Do nothing, this instance doesn't own the client.
        }

        companion object {
            @Volatile
            private var internalClient: Factory? = null
                get() {
                    if (field == null) {
                        synchronized(Factory::class.java) {
                            if (field == null) {
                            //had an issue with below line, but after following breadcrumbs of where it would be needed, I realized my case it wasn't important
                                //field = OkHttpClient()
                            }
                        }
                    }
                    return field
                }
        }
    }
}

AndroidManifest.xml

Now open your AndroidManifest.xml and add this inside your application tag

        <meta-data
            android:name="com.itgeek25.sampletvremote.data.glidestuff.CustomOkHttpGlideModule"
            android:value="AppGlideModule"/>

The tag and name is to point to your module you created in step #6 inside the CustomOkHttpGlideModule.class.

Now Test

MainActivity

I will just show you how I used Glide to load the icon image. There is quite a bit more about Glide that I need to learn but to get this to work, the basic element implementation is needed.

                    GlideImage(
                        model = appItem.icon,
                        contentDescription = appItem.title,
                        modifier = Modifier
                            .padding(10.dp)
                            .size(100.dp),
                    )

Hopefully this works for you. It took quite a bit of time for me to get this to work for my use case with a lot of trial and error.