main

square/leakcanary

Last updated at: 29/12/2023 09:50

LeakUiAppService.kt

TLDR

This file contains the implementation of the LeakUiAppService class, which is a service in the LeakCanary application. The service receives heap analysis data and handles storing it in the heapRepository. The service also handles copying and saving heap dump files.

Classes

LeakUiAppService

The LeakUiAppService class extends the Service class and is annotated with @AndroidEntryPoint. It implements the behavior of receiving heap analysis data and storing it in the heapRepository. It also handles copying and saving heap dump files. The class has the following important methods:

  • onBind(intent: Intent): IBinder: This method is called when another component wants to bind with the service. It returns an instance of the binder object, allowing the other component to interact with the service.

Methods

There are no additional methods in this file.

package org.leakcanary.service

import android.app.Service
import android.content.Intent
import android.net.Uri
import android.os.Binder
import android.os.IBinder
import dagger.hilt.android.AndroidEntryPoint
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import javax.inject.Inject
import org.leakcanary.data.HeapRepository
import org.leakcanary.internal.LeakUiApp
import org.leakcanary.internal.ParcelableHeapAnalysis
import shark.HeapAnalysisFailure
import shark.HeapAnalysisSuccess
import shark.SharkLog

@AndroidEntryPoint
class LeakUiAppService : Service() {

  @Inject lateinit var heapRepository: HeapRepository

  // TODO Stubs can be longer lived than the outer service, handle
  //  manually clearing out the stub reference to the service.
  private val binder = object : LeakUiApp.Stub() {

    override fun sendHeapAnalysis(
      heapAnalysis: ParcelableHeapAnalysis,
      heapDumpUri: Uri
    ) {
      val heapDumpDir = File(filesDir, "heapdumps")
      if (!heapDumpDir.exists()) {
        check(heapDumpDir.mkdirs()) {
          "Failed to create directory $heapDumpDir"
        }
      }

      val sourceHeapAnalysis = heapAnalysis.wrapped

      val destination = File(heapDumpDir, sourceHeapAnalysis.heapDumpFile.name)

      val updatedHeapAnalysis = when (sourceHeapAnalysis) {
        is HeapAnalysisSuccess -> {
          sourceHeapAnalysis.copy(heapDumpFile = destination)
        }

        is HeapAnalysisFailure -> {
          sourceHeapAnalysis.copy(heapDumpFile = destination)
        }
      }

      val parcelFileDescriptor = contentResolver.openFileDescriptor(heapDumpUri, "r")
      if (parcelFileDescriptor != null) {
        parcelFileDescriptor.use {
          FileInputStream(it.fileDescriptor).use { inputStream ->
            FileOutputStream(destination).use { fos ->
              val sourceChannel = inputStream.channel
              sourceChannel.transferTo(0, sourceChannel.size(), fos.channel)
            }
          }
        }
      } else {
        SharkLog.d { "ContentProvider crashed, skipping copy of $heapDumpUri" }
      }

      // TODO Use sendHeapAnalysis as a trigger to check if this is the first ever linking and
      //  if we need to import any past analysis. This should start background work that
      //  asks the app for all analysis between 2 times: last analysis that we know and this
      //  analysis time.
      val callerPackageName = packageManager.getNameForUid(Binder.getCallingUid())!!
      // TODO maybe return an intent for notification?
      heapRepository.insertHeapAnalysis(callerPackageName, updatedHeapAnalysis)
    }
  }

  override fun onBind(intent: Intent): IBinder {
    // TODO Return null if we can't handle the caller's version
    return binder
  }
}