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
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.


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.
