From bd34c66592c5f247163deb1633327e07ba057037 Mon Sep 17 00:00:00 2001 From: LagradOst Date: Sat, 17 Jul 2021 17:56:26 +0200 Subject: [PATCH] download stuff --- .../lagradost/cloudstream3/MainActivity.kt | 28 +- .../cloudstream3/ui/player/PlayerFragment.kt | 5 + .../cloudstream3/ui/result/EpisodeAdapter.kt | 1 + .../cloudstream3/ui/result/ResultFragment.kt | 336 +++++++++++------- .../lagradost/cloudstream3/utils/DataStore.kt | 1 + .../utils/VideoDownloadManager.kt | 28 +- 6 files changed, 261 insertions(+), 138 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 9502aca9..d712b4f8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -5,6 +5,7 @@ import android.content.Intent import android.content.pm.PackageManager import android.os.Build import android.os.Bundle +import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.navigation.findNavController import androidx.navigation.ui.AppBarConfiguration @@ -21,6 +22,9 @@ import com.lagradost.cloudstream3.receivers.VideoDownloadRestartReceiver import com.lagradost.cloudstream3.services.RESTART_ALL_DOWNLOADS_AND_QUEUE import com.lagradost.cloudstream3.services.START_VALUE_KEY import com.lagradost.cloudstream3.services.VideoDownloadKeepAliveService +import com.lagradost.cloudstream3.utils.DataStore.getKey +import com.lagradost.cloudstream3.utils.DataStore.getKeys +import com.lagradost.cloudstream3.utils.DataStore.removeKeys import com.lagradost.cloudstream3.utils.VideoDownloadManager import kotlinx.android.synthetic.main.fragment_result.* @@ -126,7 +130,7 @@ class MainActivity : AppCompatActivity() { mainContext = this setupSimpleStorage() - if(!storage.isStorageAccessGranted(StorageId.PRIMARY)) { + if (!storage.isStorageAccessGranted(StorageId.PRIMARY)) { storage.requestStorageAccess(REQUEST_CODE_STORAGE_ACCESS) } @@ -163,7 +167,29 @@ class MainActivity : AppCompatActivity() { // val mServiceIntent = Intent(this, mYourService::class.java).putExtra(START_VALUE_KEY, RESTART_ALL_DOWNLOADS_AND_QUEUE) // this.startService(mServiceIntent) //} +//settingsManager.getBoolean("disable_automatic_data_downloads", true) && + if ( isUsingMobileData()) { + Toast.makeText(this, "Downloads not resumed on mobile data", Toast.LENGTH_LONG).show() + } else { + val keys = getKeys(VideoDownloadManager.KEY_RESUME_PACKAGES) + val resumePkg = keys.mapNotNull { k -> getKey(k) } + // To remove a bug where this is permanent + removeKeys(VideoDownloadManager.KEY_RESUME_PACKAGES) + + for (pkg in resumePkg) { // ADD ALL CURRENT DOWNLOADS + VideoDownloadManager.downloadFromResume(this, pkg, false) + } + + // ADD QUEUE + // array needed because List gets cast exception to linkedList for some unknown reason + val resumeQueue = + getKey>(VideoDownloadManager.KEY_RESUME_QUEUE_PACKAGES) + + resumeQueue?.sortedBy { it.index }?.forEach { + VideoDownloadManager.downloadFromResume(this, it.pkg) + } + } /* val castContext = CastContext.getSharedInstance(applicationContext) fun buildMediaQueueItem(video: String): MediaQueueItem { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerFragment.kt index b342f9e4..644c7be0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerFragment.kt @@ -76,6 +76,7 @@ import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.DataStore.setKey import com.lagradost.cloudstream3.utils.DataStoreHelper.setViewPos import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.VIDEO_PLAYER_BRIGHTNESS import com.lagradost.cloudstream3.utils.getId import kotlinx.android.synthetic.main.fragment_player.* import kotlinx.android.synthetic.main.player_custom_layout.* @@ -413,6 +414,8 @@ class PlayerFragment : Fragment() { ) // 0.05f *if (diffY > 0) 1 else -1 brightness_overlay?.alpha = alpha + context?.setKey(VIDEO_PLAYER_BRIGHTNESS, alpha) + progressBarRight?.max = 100 * 100 progressBarRight?.progress = ((1f - alpha) * 100 * 100).toInt() } @@ -764,6 +767,8 @@ class PlayerFragment : Fragment() { playerResizeEnabled = settingsManager.getBoolean("player_resize_enabled", true) doubleTapEnabled = settingsManager.getBoolean("double_tap_enabled", false) + brightness_overlay?.alpha = context?.getKey(VIDEO_PLAYER_BRIGHTNESS, 0f) ?: 0f + isInPlayer = true // NEED REFERENCE TO MAIN ACTIVITY FOR PIP navigationBarHeight = requireContext().getNavigationBarHeight() diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt index 2afbdd2c..1ea36165 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt @@ -21,6 +21,7 @@ import com.google.android.gms.cast.framework.CastState import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.UIHelper.hideSystemUI import com.lagradost.cloudstream3.UIHelper.isCastApiAvailable +import com.lagradost.cloudstream3.UIHelper.isConnectedToChromecast import com.lagradost.cloudstream3.utils.getId import kotlinx.android.synthetic.main.result_episode.view.episode_holder import kotlinx.android.synthetic.main.result_episode.view.episode_text diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt index 106a1b7f..797231f2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt @@ -1,8 +1,9 @@ package com.lagradost.cloudstream3.ui.result import android.annotation.SuppressLint -import android.content.Context -import android.content.Intent +import android.content.* +import android.content.Context.CLIPBOARD_SERVICE +import android.content.Intent.* import android.net.Uri import android.os.Bundle import android.text.SpannableStringBuilder @@ -16,6 +17,8 @@ import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.content.ContextCompat +import androidx.core.content.ContextCompat.getSystemService +import androidx.core.content.FileProvider import androidx.core.text.color import androidx.core.widget.NestedScrollView import androidx.fragment.app.Fragment @@ -31,6 +34,7 @@ import com.google.android.gms.cast.framework.CastState import com.google.android.material.button.MaterialButton import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.APIHolder.getApiFromName +import com.lagradost.cloudstream3.UIHelper.checkWrite import com.lagradost.cloudstream3.UIHelper.colorFromAttribute import com.lagradost.cloudstream3.UIHelper.fixPaddingStatusbar import com.lagradost.cloudstream3.UIHelper.getStatusBarHeight @@ -39,6 +43,7 @@ import com.lagradost.cloudstream3.UIHelper.isConnectedToChromecast import com.lagradost.cloudstream3.UIHelper.popCurrentPage import com.lagradost.cloudstream3.UIHelper.popupMenuNoIcons import com.lagradost.cloudstream3.UIHelper.popupMenuNoIconsAndNoStringres +import com.lagradost.cloudstream3.UIHelper.requestRW import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.ui.WatchType @@ -54,6 +59,7 @@ import com.lagradost.cloudstream3.utils.VideoDownloadManager.sanitizeFilename import jp.wasabeef.glide.transformations.BlurTransformation import kotlinx.android.synthetic.main.fragment_result.* import kotlinx.coroutines.Job +import java.io.File const val MAX_SYNO_LENGH = 300 @@ -268,13 +274,16 @@ class ResultFragment : Fragment() { var currentLinks: ArrayList? = null var currentSubs: ArrayList? = null - suspend fun requireLinks(isCasting: Boolean = false): Boolean { + val showTitle = episodeClick.data.name ?: "Episode ${episodeClick.data.episode}" + + suspend fun requireLinks(isCasting: Boolean): Boolean { val currentLinksTemp = if (allEpisodes.containsKey(episodeClick.data.id)) allEpisodes[episodeClick.data.id] else null val currentSubsTemp = - if (allEpisodes.containsKey(episodeClick.data.id)) allEpisodes[episodeClick.data.id] else null + if (allEpisodesSubs.containsKey(episodeClick.data.id)) allEpisodesSubs[episodeClick.data.id] else null if (currentLinksTemp != null && currentLinksTemp.size > 0) { currentLinks = currentLinksTemp + currentSubs = currentSubsTemp return true } @@ -318,11 +327,105 @@ class ResultFragment : Fragment() { return false } + fun aquireSingeExtractorLink(links: List, title: String, callback: (ExtractorLink) -> Unit) { + val builder = AlertDialog.Builder(requireContext(), R.style.AlertDialogCustom) + + builder.setTitle(title) + builder.setItems(links.map { it.name }.toTypedArray()) { dia, which -> + callback.invoke(links[which]) + dia?.dismiss() + } + builder.create().show() + } + + fun aquireSingeExtractorLink(title: String, callback: (ExtractorLink) -> Unit) { + aquireSingeExtractorLink(currentLinks ?: return, title, callback) + } + + fun startChromecast(startIndex: Int) { + val eps = currentEpisodes ?: return + context?.startCast( + apiName ?: return, + currentIsMovie ?: return, + currentHeaderName, + currentPoster, + episodeClick.data.index, + eps, + sortUrls(currentLinks ?: return), + currentSubs ?: return, + startTime = episodeClick.data.getRealPosition(), + startIndex = startIndex + ) + } + + fun startDownload(links: List) { + val isMovie = currentIsMovie ?: return + val titleName = sanitizeFilename(currentHeaderName ?: return) + + val meta = VideoDownloadManager.DownloadEpisodeMetadata( + episodeClick.data.id, + titleName, + apiName ?: return, + episodeClick.data.poster ?: currentPoster, + episodeClick.data.name, + if (isMovie) null else episodeClick.data.season, + if (isMovie) null else episodeClick.data.episode + ) + + val folder = when (currentType) { + TvType.Anime -> "Anime/$titleName" + TvType.Movie -> "Movies" + TvType.TvSeries -> "TVSeries/$titleName" + TvType.ONA -> "ONA" + else -> null + } + + context?.let { ctx -> + // SET VISUAL KEYS + ctx.setKey( + DOWNLOAD_HEADER_CACHE, (currentId ?: return@let).toString(), + VideoDownloadHelper.DownloadHeaderCached( + apiName, + url ?: return@let, + currentType ?: return@let, + currentHeaderName ?: return@let, + currentPoster ?: return@let, + currentId ?: return@let + ) + ) + + val epData = episodeClick.data + ctx.setKey( + DOWNLOAD_EPISODE_CACHE, + epData.id.toString(), + VideoDownloadHelper.DownloadEpisodeCached( + epData.name, + epData.poster, + epData.episode, + epData.season, + epData.id, + currentId ?: return@let, + epData.rating, + epData.descript + ) + ) + + // DOWNLOAD VIDEO + VideoDownloadManager.downloadEpisode( + ctx, + url ?: return, + folder, + meta, + links + ) + } + } + val isLoaded = when (episodeClick.action) { ACTION_PLAY_EPISODE_IN_PLAYER -> true ACTION_CHROME_CAST_EPISODE -> requireLinks(true) ACTION_CHROME_CAST_MIRROR -> requireLinks(true) - else -> requireLinks() + else -> requireLinks(false) } if (!isLoaded) return@main // CANT LOAD @@ -330,7 +433,7 @@ class ResultFragment : Fragment() { ACTION_SHOW_OPTIONS -> { val builder = AlertDialog.Builder(requireContext(), R.style.AlertDialogCustom) var dialog: AlertDialog? = null - builder.setTitle(episodeClick.data.name) + builder.setTitle(showTitle) val options = requireContext().resources.getStringArray(R.array.episode_long_click_options) val optionsValues = requireContext().resources.getIntArray(R.array.episode_long_click_options_values) @@ -364,22 +467,96 @@ class ResultFragment : Fragment() { dialog = builder.create() dialog.show() } + ACTION_COPY_LINK -> { + aquireSingeExtractorLink("Copy Link") { link -> + val serviceClipboard = + (requireContext().getSystemService(CLIPBOARD_SERVICE) as ClipboardManager?) + ?: return@aquireSingeExtractorLink + val clip = ClipData.newPlainText(link.name, link.url) + serviceClipboard.setPrimaryClip(clip) + Toast.makeText(requireContext(), "Text Copied", Toast.LENGTH_SHORT).show() + } + } + ACTION_PLAY_EPISODE_IN_BROWSER -> { + aquireSingeExtractorLink("Play in Browser") { link -> + val i = Intent(Intent.ACTION_VIEW) + i.data = Uri.parse(link.url) + startActivity(i) + } + } + + ACTION_CHROME_CAST_MIRROR -> { + aquireSingeExtractorLink("Cast Mirror") { link -> + val mirrorIndex = currentLinks?.indexOf(link) ?: -1 + startChromecast(if (mirrorIndex == -1) 0 else mirrorIndex) + } + } ACTION_CHROME_CAST_EPISODE -> { + startChromecast(0) + } - val eps = currentEpisodes ?: return@main - context?.startCast( - apiName ?: return@main, - currentIsMovie ?: return@main, - currentHeaderName, - currentPoster, - episodeClick.data.index, - eps, - sortUrls(currentLinks ?: return@main), - currentSubs ?: return@main, - startTime = episodeClick.data.getRealPosition(), + ACTION_PLAY_EPISODE_IN_EXTERNAL_PLAYER -> { + if (activity?.checkWrite() != true) { + activity?.requestRW() + if (activity?.checkWrite() == true) return@main + } + val data = currentLinks ?: return@main + val subs = currentSubs + + val outputDir = requireContext().cacheDir + val outputFile = File.createTempFile("mirrorlist", ".m3u8", outputDir) + var text = "#EXTM3U" + if (subs != null) { + for (sub in subs) { + text += "\n#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"${sub.lang}\",DEFAULT=NO,AUTOSELECT=NO,FORCED=NO,LANGUAGE=\"${sub.lang}\",URI=\"${sub.url}\"" + } + } + for (link in data.sortedBy { -it.quality }) { + text += "\n#EXTINF:, ${link.name}\n${link.url}" + } + outputFile.writeText(text) + val VLC_PACKAGE = "org.videolan.vlc" + val VLC_INTENT_ACTION_RESULT = "org.videolan.vlc.player.result" + val VLC_COMPONENT: ComponentName = + ComponentName(VLC_PACKAGE, "org.videolan.vlc.gui.video.VideoPlayerActivity") + val REQUEST_CODE = 42 + + val FROM_START = -1 + val FROM_PROGRESS = -2 + + val vlcIntent = Intent(VLC_INTENT_ACTION_RESULT) + + vlcIntent.setPackage(VLC_PACKAGE) + vlcIntent.addFlags(FLAG_GRANT_PERSISTABLE_URI_PERMISSION) + vlcIntent.addFlags(FLAG_GRANT_PREFIX_URI_PERMISSION) + vlcIntent.addFlags(FLAG_GRANT_READ_URI_PERMISSION) + vlcIntent.addFlags(FLAG_GRANT_WRITE_URI_PERMISSION) + + vlcIntent.setDataAndType( + FileProvider.getUriForFile( + requireActivity(), + requireActivity().applicationContext.packageName + ".provider", + outputFile + ), "video/*" ) + + val startId = FROM_PROGRESS + + var position = startId + if (startId == FROM_START) { + position = 1 + } else if (startId == FROM_PROGRESS) { + position = 0 + } + + vlcIntent.putExtra("position", position) + + vlcIntent.component = VLC_COMPONENT + + activity?.startActivityForResult(vlcIntent, REQUEST_CODE) + } ACTION_PLAY_EPISODE_IN_PLAYER -> { @@ -401,69 +578,21 @@ class ResultFragment : Fragment() { .commit() } } + ACTION_RELOAD_EPISODE -> { viewModel.loadEpisode(episodeClick.data, false) } + ACTION_DOWNLOAD_EPISODE -> { - val isMovie = currentIsMovie ?: return@main - val titleName = sanitizeFilename(currentHeaderName ?: return@main) + startDownload(currentLinks ?: return@main) + } - val meta = VideoDownloadManager.DownloadEpisodeMetadata( - episodeClick.data.id, - titleName, - apiName ?: return@main, - episodeClick.data.poster ?: currentPoster, - episodeClick.data.name, - if (isMovie) null else episodeClick.data.season, - if (isMovie) null else episodeClick.data.episode - ) - - val folder = when (currentType) { - TvType.Anime -> "Anime/$titleName" - TvType.Movie -> "Movies" - TvType.TvSeries -> "TVSeries/$titleName" - TvType.ONA -> "ONA" - else -> null - } - - context?.let { ctx -> - // SET VISUAL KEYS - ctx.setKey( - DOWNLOAD_HEADER_CACHE, (currentId ?: return@let).toString(), - VideoDownloadHelper.DownloadHeaderCached( - apiName, - url ?: return@let, - currentType ?: return@let, - currentHeaderName ?: return@let, - currentPoster ?: return@let, - currentId ?: return@let - ) - ) - - val epData = episodeClick.data - ctx.setKey( - DOWNLOAD_EPISODE_CACHE, - epData.id.toString(), - VideoDownloadHelper.DownloadEpisodeCached( - epData.name, - epData.poster, - epData.episode, - epData.season, - epData.id, - currentId ?: return@let, - epData.rating, - epData.descript - ) - ) - - // DOWNLOAD VIDEO - VideoDownloadManager.downloadEpisode( - ctx, - url ?: return@main, - folder, - meta, - currentLinks ?: return@main - ) + ACTION_DOWNLOAD_MIRROR -> { + aquireSingeExtractorLink( + (currentLinks ?: return@main).filter { !it.isM3u8 }, + "Download Mirror" + ) { link -> + startDownload(listOf(link)) } } } @@ -620,63 +749,6 @@ class ResultFragment : Fragment() { } } - fun playEpisode(data: ArrayList?, episodeIndex: Int) { - if (data != null) { - -/* -if (activity?.checkWrite() != true) { - activity?.requestRW() - if (activity?.checkWrite() == true) return -} - -val outputDir = context!!.cacheDir -val outputFile = File.createTempFile("mirrorlist", ".m3u8", outputDir) -var text = "#EXTM3U"; -for (link in data.sortedBy { -it.quality }) { - text += "\n#EXTINF:, ${link.name}\n${link.url}" -} -outputFile.writeText(text) -val VLC_PACKAGE = "org.videolan.vlc" -val VLC_INTENT_ACTION_RESULT = "org.videolan.vlc.player.result" -val VLC_COMPONENT: ComponentName = - ComponentName(VLC_PACKAGE, "org.videolan.vlc.gui.video.VideoPlayerActivity") -val REQUEST_CODE = 42 - -val FROM_START = -1 -val FROM_PROGRESS = -2 - -val vlcIntent = Intent(VLC_INTENT_ACTION_RESULT) - -vlcIntent.setPackage(VLC_PACKAGE) -vlcIntent.addFlags(FLAG_GRANT_PERSISTABLE_URI_PERMISSION) -vlcIntent.addFlags(FLAG_GRANT_PREFIX_URI_PERMISSION) -vlcIntent.addFlags(FLAG_GRANT_READ_URI_PERMISSION) -vlcIntent.addFlags(FLAG_GRANT_WRITE_URI_PERMISSION) - -vlcIntent.setDataAndType(FileProvider.getUriForFile(activity!!, - activity!!.applicationContext.packageName + ".provider", - outputFile), "video/*") - -val startId = FROM_PROGRESS - -var position = startId -if (startId == FROM_START) { - position = 1 -} else if (startId == FROM_PROGRESS) { - position = 0 -} - -vlcIntent.putExtra("position", position) -//vlcIntent.putExtra("title", episodeName) - -vlcIntent.setComponent(VLC_COMPONENT) - -activity?.startActivityForResult(vlcIntent, REQUEST_CODE) -*/ - */ - } - } - if (d.plot != null) { var syno = d.plot!! if (syno.length > MAX_SYNO_LENGH) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt index e895a997..2d6a171a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt @@ -8,6 +8,7 @@ import com.fasterxml.jackson.module.kotlin.KotlinModule const val DOWNLOAD_HEADER_CACHE = "download_header_cache" const val DOWNLOAD_EPISODE_CACHE = "download_episode_cache" +const val VIDEO_PLAYER_BRIGHTNESS = "video_player_alpha" const val PREFERENCES_NAME: String = "rebuild_preference" diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt index dff49eb3..3e8f1bb8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt @@ -10,6 +10,7 @@ import android.net.Uri import android.os.Build import android.os.Environment import android.provider.MediaStore +import android.widget.Toast import androidx.annotation.DrawableRes import androidx.annotation.RequiresApi import androidx.core.app.NotificationCompat @@ -638,8 +639,6 @@ object VideoDownloadManager { return } - val dQueue = downloadQueue.toList().mapIndexed { index, any -> DownloadQueueResumePackage(index, any) } - context.setKey(KEY_RESUME_QUEUE_PACKAGES, dQueue) currentDownloads.add(id) main { @@ -729,9 +728,28 @@ object VideoDownloadManager { return context.getKey(KEY_RESUME_PACKAGES, id.toString()) } - fun downloadFromResume(context: Context, pkg: DownloadResumePackage) { - downloadQueue.addLast(pkg) - downloadCheck(context) + fun downloadFromResume(context: Context, pkg: DownloadResumePackage, setKey: Boolean = true) { + if (!currentDownloads.any { it == pkg.item.ep.id }) { + if (currentDownloads.size == maxConcurrentDownloads) { + main { + Toast.makeText( + context, + "${pkg.item.ep.mainName}${pkg.item.ep.episode?.let { " Episode $it " } ?: " "}queued", + Toast.LENGTH_SHORT + ).show() + } + } + downloadQueue.addLast(pkg) + downloadCheck(context) + if (setKey) saveQueue(context) + } + } + + private fun saveQueue(context: Context) { + val dQueue = + downloadQueue.toList().mapIndexed { index, any -> DownloadQueueResumePackage(index, any) } + .toTypedArray() + context.setKey(KEY_RESUME_QUEUE_PACKAGES, dQueue) } fun isMyServiceRunning(context: Context, serviceClass: Class<*>): Boolean {