ExtensionManager.kt 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. package eu.kanade.tachiyomi.extension
  2. import android.content.Context
  3. import com.jakewharton.rxrelay.BehaviorRelay
  4. import eu.kanade.tachiyomi.data.preference.PreferencesHelper
  5. import eu.kanade.tachiyomi.data.preference.getOrDefault
  6. import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
  7. import eu.kanade.tachiyomi.extension.model.Extension
  8. import eu.kanade.tachiyomi.extension.model.InstallStep
  9. import eu.kanade.tachiyomi.extension.model.LoadResult
  10. import eu.kanade.tachiyomi.extension.util.ExtensionInstallReceiver
  11. import eu.kanade.tachiyomi.extension.util.ExtensionInstaller
  12. import eu.kanade.tachiyomi.extension.util.ExtensionLoader
  13. import eu.kanade.tachiyomi.source.SourceManager
  14. import eu.kanade.tachiyomi.util.launchNow
  15. import kotlinx.coroutines.async
  16. import rx.Observable
  17. import rx.android.schedulers.AndroidSchedulers
  18. import rx.schedulers.Schedulers
  19. import uy.kohesive.injekt.Injekt
  20. import uy.kohesive.injekt.api.get
  21. /**
  22. * The manager of extensions installed as another apk which extend the available sources. It handles
  23. * the retrieval of remotely available extensions as well as installing, updating and removing them.
  24. * To avoid malicious distribution, every extension must be signed and it will only be loaded if its
  25. * signature is trusted, otherwise the user will be prompted with a warning to trust it before being
  26. * loaded.
  27. *
  28. * @param context The application context.
  29. * @param preferences The application preferences.
  30. */
  31. class ExtensionManager(
  32. private val context: Context,
  33. private val preferences: PreferencesHelper = Injekt.get()
  34. ) {
  35. /**
  36. * API where all the available extensions can be found.
  37. */
  38. private val api = ExtensionGithubApi()
  39. /**
  40. * The installer which installs, updates and uninstalls the extensions.
  41. */
  42. private val installer by lazy { ExtensionInstaller(context) }
  43. /**
  44. * Relay used to notify the installed extensions.
  45. */
  46. private val installedExtensionsRelay = BehaviorRelay.create<List<Extension.Installed>>()
  47. /**
  48. * List of the currently installed extensions.
  49. */
  50. var installedExtensions = emptyList<Extension.Installed>()
  51. private set(value) {
  52. field = value
  53. installedExtensionsRelay.call(value)
  54. }
  55. /**
  56. * Relay used to notify the available extensions.
  57. */
  58. private val availableExtensionsRelay = BehaviorRelay.create<List<Extension.Available>>()
  59. /**
  60. * List of the currently available extensions.
  61. */
  62. var availableExtensions = emptyList<Extension.Available>()
  63. private set(value) {
  64. field = value
  65. availableExtensionsRelay.call(value)
  66. updatedInstalledExtensionsStatuses(value)
  67. }
  68. /**
  69. * Relay used to notify the untrusted extensions.
  70. */
  71. private val untrustedExtensionsRelay = BehaviorRelay.create<List<Extension.Untrusted>>()
  72. /**
  73. * List of the currently untrusted extensions.
  74. */
  75. var untrustedExtensions = emptyList<Extension.Untrusted>()
  76. private set(value) {
  77. field = value
  78. untrustedExtensionsRelay.call(value)
  79. }
  80. /**
  81. * The source manager where the sources of the extensions are added.
  82. */
  83. private lateinit var sourceManager: SourceManager
  84. /**
  85. * Initializes this manager with the given source manager.
  86. */
  87. fun init(sourceManager: SourceManager) {
  88. this.sourceManager = sourceManager
  89. initExtensions()
  90. ExtensionInstallReceiver(InstallationListener()).register(context)
  91. }
  92. /**
  93. * Loads and registers the installed extensions.
  94. */
  95. private fun initExtensions() {
  96. val extensions = ExtensionLoader.loadExtensions(context)
  97. installedExtensions = extensions
  98. .filterIsInstance<LoadResult.Success>()
  99. .map { it.extension }
  100. installedExtensions
  101. .flatMap { it.sources }
  102. // overwrite is needed until the bundled sources are removed
  103. .forEach { sourceManager.registerSource(it, true) }
  104. untrustedExtensions = extensions
  105. .filterIsInstance<LoadResult.Untrusted>()
  106. .map { it.extension }
  107. }
  108. /**
  109. * Returns the relay of the installed extensions as an observable.
  110. */
  111. fun getInstalledExtensionsObservable(): Observable<List<Extension.Installed>> {
  112. return installedExtensionsRelay.asObservable()
  113. }
  114. /**
  115. * Returns the relay of the available extensions as an observable.
  116. */
  117. fun getAvailableExtensionsObservable(): Observable<List<Extension.Available>> {
  118. return availableExtensionsRelay.asObservable()
  119. }
  120. /**
  121. * Returns the relay of the untrusted extensions as an observable.
  122. */
  123. fun getUntrustedExtensionsObservable(): Observable<List<Extension.Untrusted>> {
  124. return untrustedExtensionsRelay.asObservable()
  125. }
  126. /**
  127. * Finds the available extensions in the [api] and updates [availableExtensions].
  128. */
  129. fun findAvailableExtensions() {
  130. api.findExtensions()
  131. .onErrorReturn { emptyList() }
  132. .subscribeOn(Schedulers.io())
  133. .observeOn(AndroidSchedulers.mainThread())
  134. .subscribe { availableExtensions = it }
  135. }
  136. /**
  137. * Sets the update field of the installed extensions with the given [availableExtensions].
  138. *
  139. * @param availableExtensions The list of extensions given by the [api].
  140. */
  141. private fun updatedInstalledExtensionsStatuses(availableExtensions: List<Extension.Available>) {
  142. if (availableExtensions.isEmpty()) {
  143. return
  144. }
  145. val mutInstalledExtensions = installedExtensions.toMutableList()
  146. var changed = false
  147. for ((index, installedExt) in mutInstalledExtensions.withIndex()) {
  148. val pkgName = installedExt.pkgName
  149. val availableExt = availableExtensions.find { it.pkgName == pkgName }
  150. if (availableExt == null && !installedExt.isObsolete) {
  151. mutInstalledExtensions[index] = installedExt.copy(isObsolete = true)
  152. changed = true
  153. } else if (availableExt != null) {
  154. val hasUpdate = availableExt.versionCode > installedExt.versionCode
  155. if (installedExt.hasUpdate != hasUpdate) {
  156. mutInstalledExtensions[index] = installedExt.copy(hasUpdate = hasUpdate)
  157. changed = true
  158. }
  159. }
  160. }
  161. if (changed) {
  162. installedExtensions = mutInstalledExtensions
  163. }
  164. }
  165. /**
  166. * Returns an observable of the installation process for the given extension. It will complete
  167. * once the extension is installed or throws an error. The process will be canceled if
  168. * unsubscribed before its completion.
  169. *
  170. * @param extension The extension to be installed.
  171. */
  172. fun installExtension(extension: Extension.Available): Observable<InstallStep> {
  173. return installer.downloadAndInstall(api.getApkUrl(extension), extension)
  174. }
  175. /**
  176. * Returns an observable of the installation process for the given extension. It will complete
  177. * once the extension is updated or throws an error. The process will be canceled if
  178. * unsubscribed before its completion.
  179. *
  180. * @param extension The extension to be updated.
  181. */
  182. fun updateExtension(extension: Extension.Installed): Observable<InstallStep> {
  183. val availableExt = availableExtensions.find { it.pkgName == extension.pkgName }
  184. ?: return Observable.empty()
  185. return installExtension(availableExt)
  186. }
  187. /**
  188. * Sets the result of the installation of an extension.
  189. *
  190. * @param downloadId The id of the download.
  191. * @param result Whether the extension was installed or not.
  192. */
  193. fun setInstallationResult(downloadId: Long, result: Boolean) {
  194. installer.setInstallationResult(downloadId, result)
  195. }
  196. /**
  197. * Uninstalls the extension that matches the given package name.
  198. *
  199. * @param pkgName The package name of the application to uninstall.
  200. */
  201. fun uninstallExtension(pkgName: String) {
  202. installer.uninstallApk(pkgName)
  203. }
  204. /**
  205. * Adds the given signature to the list of trusted signatures. It also loads in background the
  206. * extensions that match this signature.
  207. *
  208. * @param signature The signature to whitelist.
  209. */
  210. fun trustSignature(signature: String) {
  211. val untrustedSignatures = untrustedExtensions.map { it.signatureHash }.toSet()
  212. if (signature !in untrustedSignatures) return
  213. ExtensionLoader.trustedSignatures += signature
  214. val preference = preferences.trustedSignatures()
  215. preference.set(preference.getOrDefault() + signature)
  216. val nowTrustedExtensions = untrustedExtensions.filter { it.signatureHash == signature }
  217. untrustedExtensions -= nowTrustedExtensions
  218. val ctx = context
  219. launchNow {
  220. nowTrustedExtensions
  221. .map { extension ->
  222. async { ExtensionLoader.loadExtensionFromPkgName(ctx, extension.pkgName) }
  223. }
  224. .map { it.await() }
  225. .forEach { result ->
  226. if (result is LoadResult.Success) {
  227. registerNewExtension(result.extension)
  228. }
  229. }
  230. }
  231. }
  232. /**
  233. * Registers the given extension in this and the source managers.
  234. *
  235. * @param extension The extension to be registered.
  236. */
  237. private fun registerNewExtension(extension: Extension.Installed) {
  238. installedExtensions += extension
  239. extension.sources.forEach { sourceManager.registerSource(it) }
  240. }
  241. /**
  242. * Registers the given updated extension in this and the source managers previously removing
  243. * the outdated ones.
  244. *
  245. * @param extension The extension to be registered.
  246. */
  247. private fun registerUpdatedExtension(extension: Extension.Installed) {
  248. val mutInstalledExtensions = installedExtensions.toMutableList()
  249. val oldExtension = mutInstalledExtensions.find { it.pkgName == extension.pkgName }
  250. if (oldExtension != null) {
  251. mutInstalledExtensions -= oldExtension
  252. extension.sources.forEach { sourceManager.unregisterSource(it) }
  253. }
  254. mutInstalledExtensions += extension
  255. installedExtensions = mutInstalledExtensions
  256. extension.sources.forEach { sourceManager.registerSource(it) }
  257. }
  258. /**
  259. * Unregisters the extension in this and the source managers given its package name. Note this
  260. * method is called for every uninstalled application in the system.
  261. *
  262. * @param pkgName The package name of the uninstalled application.
  263. */
  264. private fun unregisterExtension(pkgName: String) {
  265. val installedExtension = installedExtensions.find { it.pkgName == pkgName }
  266. if (installedExtension != null) {
  267. installedExtensions -= installedExtension
  268. installedExtension.sources.forEach { sourceManager.unregisterSource(it) }
  269. }
  270. val untrustedExtension = untrustedExtensions.find { it.pkgName == pkgName }
  271. if (untrustedExtension != null) {
  272. untrustedExtensions -= untrustedExtension
  273. }
  274. }
  275. /**
  276. * Listener which receives events of the extensions being installed, updated or removed.
  277. */
  278. private inner class InstallationListener : ExtensionInstallReceiver.Listener {
  279. override fun onExtensionInstalled(extension: Extension.Installed) {
  280. registerNewExtension(extension.withUpdateCheck())
  281. }
  282. override fun onExtensionUpdated(extension: Extension.Installed) {
  283. registerUpdatedExtension(extension.withUpdateCheck())
  284. }
  285. override fun onExtensionUntrusted(extension: Extension.Untrusted) {
  286. untrustedExtensions += extension
  287. }
  288. override fun onPackageUninstalled(pkgName: String) {
  289. unregisterExtension(pkgName)
  290. }
  291. }
  292. /**
  293. * Extension method to set the update field of an installed extension.
  294. */
  295. private fun Extension.Installed.withUpdateCheck(): Extension.Installed {
  296. val availableExt = availableExtensions.find { it.pkgName == pkgName }
  297. if (availableExt != null && availableExt.versionCode > versionCode) {
  298. return copy(hasUpdate = true)
  299. }
  300. return this
  301. }
  302. }