forked from recloudstream/cloudstream
		
	android tv recc not working :(
This commit is contained in:
		
							parent
							
								
									c5d53d7621
								
							
						
					
					
						commit
						1bbbbce326
					
				
					 7 changed files with 197 additions and 34 deletions
				
			
		|  | @ -88,6 +88,7 @@ repositories { | |||
| } | ||||
| 
 | ||||
| dependencies { | ||||
|     implementation 'com.google.android.mediahome:video:1.0.0' | ||||
|     testImplementation 'org.json:json:20180813' | ||||
| 
 | ||||
|     implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" | ||||
|  | @ -95,10 +96,10 @@ dependencies { | |||
|     implementation 'androidx.appcompat:appcompat:1.4.1' | ||||
|     implementation 'com.google.android.material:material:1.5.0' | ||||
|     implementation 'androidx.constraintlayout:constraintlayout:2.1.3' | ||||
|     implementation 'androidx.navigation:navigation-fragment-ktx:2.5.0-alpha01' | ||||
|     implementation 'androidx.navigation:navigation-ui-ktx:2.5.0-alpha01' | ||||
|     implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.0' | ||||
|     implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0' | ||||
|     implementation 'androidx.navigation:navigation-fragment-ktx:2.5.0-alpha02' | ||||
|     implementation 'androidx.navigation:navigation-ui-ktx:2.5.0-alpha02' | ||||
|     implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1' | ||||
|     implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1' | ||||
|     testImplementation 'junit:junit:4.13.2' | ||||
|     androidTestImplementation 'androidx.test.ext:junit:1.1.3' | ||||
|     androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' | ||||
|  | @ -167,4 +168,6 @@ dependencies { | |||
| 
 | ||||
|     // for shimmer when loading | ||||
|     implementation 'com.facebook.shimmer:shimmer:0.5.0' | ||||
| 
 | ||||
|     implementation "androidx.tvprovider:tvprovider:1.0.0" | ||||
| } | ||||
|  | @ -9,6 +9,7 @@ | |||
|     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> | ||||
|     <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/> | ||||
|     <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/> | ||||
|     <uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" /> | ||||
| 
 | ||||
|     <uses-feature android:name="android.hardware.touchscreen" | ||||
|                   android:required="false"/> | ||||
|  | @ -137,6 +138,7 @@ | |||
|                 android:name="androidx.core.content.FileProvider" | ||||
|                 android:authorities="${applicationId}.provider" | ||||
|                 android:exported="false" | ||||
|                 android:enabled="true" | ||||
|                 android:grantUriPermissions="true"> | ||||
|             <meta-data | ||||
|                     android:name="android.support.FILE_PROVIDER_PATHS" | ||||
|  |  | |||
|  | @ -3,14 +3,16 @@ package com.lagradost.cloudstream3.ui.home | |||
| import android.view.LayoutInflater | ||||
| import android.view.View | ||||
| import android.view.ViewGroup | ||||
| import androidx.recyclerview.widget.DiffUtil | ||||
| import androidx.recyclerview.widget.RecyclerView | ||||
| import com.lagradost.cloudstream3.R | ||||
| import com.lagradost.cloudstream3.SearchResponse | ||||
| import com.lagradost.cloudstream3.ui.search.SearchClickCallback | ||||
| import com.lagradost.cloudstream3.ui.search.SearchResponseDiffCallback | ||||
| import com.lagradost.cloudstream3.ui.search.SearchResultBuilder | ||||
| 
 | ||||
| class HomeChildItemAdapter( | ||||
|     var cardList: List<SearchResponse>, | ||||
|     val cardList: MutableList<SearchResponse>, | ||||
|     val layout: Int = R.layout.home_result_grid, | ||||
|     private val nextFocusUp: Int? = null, | ||||
|     private val nextFocusDown: Int? = null, | ||||
|  | @ -44,6 +46,17 @@ class HomeChildItemAdapter( | |||
|         return (cardList[position].id ?: position).toLong() | ||||
|     } | ||||
| 
 | ||||
|     fun updateList(newList: List<SearchResponse>) { | ||||
|         val diffResult = DiffUtil.calculateDiff( | ||||
|             SearchResponseDiffCallback(this.cardList, newList) | ||||
|         ) | ||||
| 
 | ||||
|         cardList.clear() | ||||
|         cardList.addAll(newList) | ||||
| 
 | ||||
|         diffResult.dispatchUpdatesTo(this) | ||||
|     } | ||||
| 
 | ||||
|     class CardViewHolder | ||||
|     constructor( | ||||
|         itemView: View, private val clickCallback: (SearchClickCallback) -> Unit, private val itemCount: Int, | ||||
|  |  | |||
|  | @ -124,7 +124,7 @@ class HomeFragment : Fragment() { | |||
| 
 | ||||
|             val spanListener = { span: Int -> | ||||
|                 recycle.spanCount = span | ||||
|                 (recycle.adapter as SearchAdapter).notifyDataSetChanged() | ||||
|                 //(recycle.adapter as SearchAdapter).notifyDataSetChanged() | ||||
|             } | ||||
| 
 | ||||
|             configEvent += spanListener | ||||
|  | @ -133,7 +133,7 @@ class HomeFragment : Fragment() { | |||
|                 configEvent -= spanListener | ||||
|             } | ||||
| 
 | ||||
|             (recycle.adapter as SearchAdapter).notifyDataSetChanged() | ||||
|             //(recycle.adapter as SearchAdapter).notifyDataSetChanged() | ||||
| 
 | ||||
|             bottomSheetDialogBuilder.show() | ||||
|         } | ||||
|  | @ -399,7 +399,7 @@ class HomeFragment : Fragment() { | |||
|                 val randomSize = items.size | ||||
|                 home_main_poster_recyclerview?.adapter = | ||||
|                     HomeChildItemAdapter( | ||||
|                         items, | ||||
|                         items.toMutableList(), | ||||
|                         R.layout.home_result_big_grid, | ||||
|                         nextFocusUp = home_main_poster_recyclerview.nextFocusUpId, | ||||
|                         nextFocusDown = home_main_poster_recyclerview.nextFocusDownId | ||||
|  | @ -438,7 +438,7 @@ class HomeFragment : Fragment() { | |||
|                     val d = data.value | ||||
| 
 | ||||
|                     currentHomePage = d | ||||
|                     (home_master_recycler?.adapter as ParentItemAdapter?)?.updateList( | ||||
|                     (home_master_recycler?.adapter as? ParentItemAdapter?)?.updateList( | ||||
|                         d?.items?.mapNotNull { | ||||
|                             try { | ||||
|                                 HomePageList(it.name, it.list.filterSearchResponse()) | ||||
|  | @ -483,6 +483,7 @@ class HomeFragment : Fragment() { | |||
|                     home_loaded?.isVisible = false | ||||
|                 } | ||||
|                 is Resource.Loading -> { | ||||
|                     (home_master_recycler?.adapter as? ParentItemAdapter?)?.updateList(listOf()) | ||||
|                     home_loading_shimmer?.startShimmer() | ||||
|                     home_loading?.isVisible = true | ||||
|                     home_loading_error?.isVisible = false | ||||
|  | @ -556,13 +557,12 @@ class HomeFragment : Fragment() { | |||
|             home_bookmarked_parent_item_title?.text = getString(availableWatchStatusTypes.first.stringRes)*/ | ||||
|         } | ||||
| 
 | ||||
|         observe(homeViewModel.bookmarks) { pair -> | ||||
|             home_bookmarked_holder.isVisible = pair.first | ||||
|         observe(homeViewModel.bookmarks) { (isVis, bookmarks) -> | ||||
|             home_bookmarked_holder.isVisible = isVis | ||||
| 
 | ||||
|             val bookmarks = pair.second | ||||
|             (home_bookmarked_child_recyclerview?.adapter as HomeChildItemAdapter?)?.cardList = | ||||
|             (home_bookmarked_child_recyclerview?.adapter as? HomeChildItemAdapter?)?.updateList( | ||||
|                 bookmarks | ||||
|             home_bookmarked_child_recyclerview?.adapter?.notifyDataSetChanged() | ||||
|             ) | ||||
| 
 | ||||
|             home_bookmarked_child_more_info?.setOnClickListener { | ||||
|                 activity?.loadHomepageList( | ||||
|  | @ -576,9 +576,15 @@ class HomeFragment : Fragment() { | |||
| 
 | ||||
|         observe(homeViewModel.resumeWatching) { resumeWatching -> | ||||
|             home_watch_holder?.isVisible = resumeWatching.isNotEmpty() | ||||
|             (home_watch_child_recyclerview?.adapter as HomeChildItemAdapter?)?.cardList = | ||||
|             (home_watch_child_recyclerview?.adapter as? HomeChildItemAdapter?)?.updateList( | ||||
|                 resumeWatching | ||||
|             home_watch_child_recyclerview?.adapter?.notifyDataSetChanged() | ||||
|             ) | ||||
| 
 | ||||
|             //if (context?.isTvSettings() == true) { | ||||
|             //    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { | ||||
|             //        context?.addProgramsToContinueWatching(resumeWatching.mapNotNull { it as? DataStoreHelper.ResumeWatchingResult }) | ||||
|             //    } | ||||
|             //} | ||||
| 
 | ||||
|             home_watch_child_more_info?.setOnClickListener { | ||||
|                 activity?.loadHomepageList( | ||||
|  |  | |||
|  | @ -65,12 +65,12 @@ class ParentItemAdapter( | |||
|         fun bind(info: HomePageList) { | ||||
|             title.text = info.name | ||||
|             recyclerView.adapter = HomeChildItemAdapter( | ||||
|                 info.list, | ||||
|                 info.list.toMutableList(), | ||||
|                 clickCallback = clickCallback, | ||||
|                 nextFocusUp = recyclerView.nextFocusUpId, | ||||
|                 nextFocusDown = recyclerView.nextFocusDownId | ||||
|             ) | ||||
|             (recyclerView.adapter as HomeChildItemAdapter).notifyDataSetChanged() | ||||
|             //(recyclerView.adapter as HomeChildItemAdapter).notifyDataSetChanged() | ||||
| 
 | ||||
|             moreInfo.setOnClickListener { | ||||
|                 moreInfoClickCallback.invoke(info) | ||||
|  |  | |||
|  | @ -1183,9 +1183,9 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio | |||
|                     result_episode_loading?.isVisible = false | ||||
|                     if (result_episodes == null || result_episodes.adapter == null) return@observe | ||||
|                     currentEpisodes = episodes.value | ||||
|                     (result_episodes?.adapter as EpisodeAdapter?)?.cardList = episodes.value | ||||
|                     (result_episodes?.adapter as EpisodeAdapter?)?.updateLayout() | ||||
|                     (result_episodes?.adapter as EpisodeAdapter?)?.notifyDataSetChanged() | ||||
|                     (result_episodes?.adapter as? EpisodeAdapter?)?.cardList = episodes.value | ||||
|                     (result_episodes?.adapter as? EpisodeAdapter?)?.updateLayout() | ||||
|                     (result_episodes?.adapter as? EpisodeAdapter?)?.notifyDataSetChanged() | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| package com.lagradost.cloudstream3.utils | ||||
| 
 | ||||
| import android.annotation.SuppressLint | ||||
| import android.app.Activity | ||||
| import android.content.ComponentName | ||||
| import android.content.ContentValues | ||||
|  | @ -10,6 +11,7 @@ import android.database.Cursor | |||
| import android.media.AudioAttributes | ||||
| import android.media.AudioFocusRequest | ||||
| import android.media.AudioManager | ||||
| import android.media.tv.TvContract.Channels.COLUMN_INTERNAL_PROVIDER_ID | ||||
| import android.net.ConnectivityManager | ||||
| import android.net.NetworkCapabilities | ||||
| import android.net.Uri | ||||
|  | @ -18,20 +20,23 @@ import android.os.Environment | |||
| import android.os.ParcelFileDescriptor | ||||
| import android.provider.MediaStore | ||||
| import android.util.Log | ||||
| import androidx.annotation.RequiresApi | ||||
| import androidx.annotation.WorkerThread | ||||
| import androidx.appcompat.app.AppCompatActivity | ||||
| import androidx.tvprovider.media.tv.PreviewChannelHelper | ||||
| import androidx.tvprovider.media.tv.TvContractCompat | ||||
| import androidx.tvprovider.media.tv.WatchNextProgram | ||||
| import androidx.tvprovider.media.tv.WatchNextProgram.fromCursor | ||||
| import com.fasterxml.jackson.module.kotlin.readValue | ||||
| import com.google.android.gms.cast.framework.CastContext | ||||
| import com.google.android.gms.cast.framework.CastState | ||||
| import com.google.android.gms.common.ConnectionResult | ||||
| import com.google.android.gms.common.GoogleApiAvailability | ||||
| import com.google.android.gms.common.wrappers.Wrappers | ||||
| import com.lagradost.cloudstream3.MainActivity | ||||
| import com.lagradost.cloudstream3.R | ||||
| import com.lagradost.cloudstream3.SearchResponse | ||||
| import com.lagradost.cloudstream3.mapper | ||||
| import com.lagradost.cloudstream3.movieproviders.MeloMovieProvider | ||||
| import com.lagradost.cloudstream3.* | ||||
| import com.lagradost.cloudstream3.mvvm.logError | ||||
| import com.lagradost.cloudstream3.ui.result.ResultFragment | ||||
| import com.lagradost.cloudstream3.utils.Coroutines.ioSafe | ||||
| import com.lagradost.cloudstream3.utils.FillerEpisodeCheck.toClassDir | ||||
| import com.lagradost.cloudstream3.utils.JsUnpacker.Companion.load | ||||
| import com.lagradost.cloudstream3.utils.UIHelper.navigate | ||||
|  | @ -41,6 +46,136 @@ import java.net.URL | |||
| import java.net.URLDecoder | ||||
| 
 | ||||
| object AppUtils { | ||||
|     //fun Context.deleteFavorite(data: SearchResponse) { | ||||
|     //    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return | ||||
|     //    normalSafeApiCall { | ||||
|     //        val existingId = | ||||
|     //            getWatchNextProgramByVideoId(data.url, this).second ?: return@normalSafeApiCall | ||||
|     //        contentResolver.delete( | ||||
| // | ||||
|     //            TvContractCompat.buildWatchNextProgramUri(existingId), | ||||
|     //            null, null | ||||
|     //        ) | ||||
|     //    } | ||||
|     //} | ||||
|     @SuppressLint("RestrictedApi") | ||||
|     private fun buildWatchNextProgramUri( | ||||
|         context: Context, | ||||
|         card: DataStoreHelper.ResumeWatchingResult | ||||
|     ): WatchNextProgram { | ||||
|         val isSeries = !card.type.isMovieType() | ||||
|         val title = if (isSeries) { | ||||
|             context.getNameFull(card.name, card.episode, card.season) | ||||
|         } else { | ||||
|             card.name | ||||
|         } | ||||
| 
 | ||||
|         val builder = WatchNextProgram.Builder() | ||||
|             .setEpisodeTitle(title) | ||||
|             .setType( | ||||
|                 if (isSeries) { | ||||
|                     TvContractCompat.WatchNextPrograms.TYPE_TV_EPISODE | ||||
|                 } else TvContractCompat.WatchNextPrograms.TYPE_MOVIE | ||||
|             ) | ||||
|             .setWatchNextType(TvContractCompat.WatchNextPrograms.WATCH_NEXT_TYPE_CONTINUE) | ||||
|             .setTitle(title) | ||||
|             .setPosterArtUri(Uri.parse(card.posterUrl)) | ||||
|             .setIntentUri(Uri.parse(card.url)) //TODO FIX intent | ||||
|             .setInternalProviderId(card.url) | ||||
|         //.setLastEngagementTimeUtcMillis(System.currentTimeMillis()) | ||||
| 
 | ||||
|         card.watchPos?.let { | ||||
|             builder.setDurationMillis(it.duration.toInt()) | ||||
|             builder.setLastPlaybackPositionMillis(it.position.toInt()) | ||||
|         } | ||||
|         // .setLastEngagementTimeUtcMillis() //TODO | ||||
| 
 | ||||
|         if (isSeries) | ||||
|             card.episode?.let { | ||||
|                 builder.setEpisodeNumber(it) | ||||
|             } | ||||
| 
 | ||||
|         return builder.build() | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Find the Watch Next program for given id. | ||||
|      * Returns the first instance available. | ||||
|      */ | ||||
|     @SuppressLint("RestrictedApi") | ||||
|     // Suppress RestrictedApi due to https://issuetracker.google.com/138150076 | ||||
|     fun findFirstWatchNextProgram(context: Context, predicate: (Cursor) -> Boolean): | ||||
|             Pair<WatchNextProgram?, Long?> { | ||||
|         val COLUMN_WATCH_NEXT_ID_INDEX = 0 | ||||
| //        val COLUMN_WATCH_NEXT_INTERNAL_PROVIDER_ID_INDEX = 1 | ||||
| //        val COLUMN_WATCH_NEXT_COLUMN_BROWSABLE_INDEX = 2 | ||||
| 
 | ||||
|         val cursor = context.contentResolver.query( | ||||
|             TvContractCompat.WatchNextPrograms.CONTENT_URI, | ||||
|             WatchNextProgram.PROJECTION, | ||||
|             /* selection = */ null, | ||||
|             /* selectionArgs = */ null, | ||||
|             /* sortOrder= */ null | ||||
|         ) | ||||
|         cursor?.use { | ||||
|             if (it.moveToFirst()) { | ||||
|                 do { | ||||
|                     if (predicate(cursor)) { | ||||
|                         return fromCursor(cursor) to cursor.getLong(COLUMN_WATCH_NEXT_ID_INDEX) | ||||
|                     } | ||||
|                 } while (it.moveToNext()) | ||||
|             } | ||||
|         } | ||||
|         return null to null | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Query the Watch Next list and find the program with given videoId. | ||||
|      * Return null if not found. | ||||
|      */ | ||||
| 
 | ||||
|     @RequiresApi(Build.VERSION_CODES.O) | ||||
|     @SuppressLint("Range") | ||||
|     @Synchronized | ||||
|     private fun getWatchNextProgramByVideoId( | ||||
|         id: String, | ||||
|         context: Context | ||||
|     ): Pair<WatchNextProgram?, Long?> { | ||||
|         return findFirstWatchNextProgram(context) { cursor -> | ||||
|             (cursor.getString(cursor.getColumnIndex(COLUMN_INTERNAL_PROVIDER_ID)) == id) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // https://github.com/googlearchive/leanback-homescreen-channels/blob/master/app/src/main/java/com/google/android/tvhomescreenchannels/SampleTvProvider.java | ||||
|     @SuppressLint("RestrictedApi") | ||||
|     @WorkerThread | ||||
|     fun Context.addProgramsToContinueWatching(data: List<DataStoreHelper.ResumeWatchingResult>) { | ||||
|         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return | ||||
| 
 | ||||
|         ioSafe { | ||||
|             data.forEach { episodeInfo -> | ||||
|                 try { | ||||
|                     val (program, id) = getWatchNextProgramByVideoId(episodeInfo.url, this) | ||||
|                     val nextProgram = buildWatchNextProgramUri(this, episodeInfo) | ||||
| 
 | ||||
|                     // If the program is already in the Watch Next row, update it | ||||
|                     if (program != null && id != null) { | ||||
|                         PreviewChannelHelper(this).updateWatchNextProgram( | ||||
|                             nextProgram, | ||||
|                             id, | ||||
|                         ) | ||||
|                     } else { | ||||
|                         PreviewChannelHelper(this) | ||||
|                             .publishWatchNextProgram(nextProgram) | ||||
|                     } | ||||
|                 } catch (e: Exception) { | ||||
|                     logError(e) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @SuppressLint("Range") | ||||
|     fun getVideoContentUri(context: Context, videoFilePath: String): Uri? { | ||||
|         val cursor = context.contentResolver.query( | ||||
|             MediaStore.Video.Media.EXTERNAL_CONTENT_URI, arrayOf(MediaStore.Video.Media._ID), | ||||
|  | @ -94,14 +229,14 @@ object AppUtils { | |||
|         return mapper.writeValueAsString(this) | ||||
|     } | ||||
| 
 | ||||
|     inline fun <reified T> parseJson(value : String): T { | ||||
|     inline fun <reified T> parseJson(value: String): T { | ||||
|         return mapper.readValue(value) | ||||
|     } | ||||
| 
 | ||||
|     inline fun <reified T> tryParseJson(value : String): T? { | ||||
|     inline fun <reified T> tryParseJson(value: String): T? { | ||||
|         return try { | ||||
|             parseJson(value) | ||||
|         } catch (_ : Exception) { | ||||
|         } catch (_: Exception) { | ||||
|             null | ||||
|         } | ||||
|     } | ||||
|  | @ -168,7 +303,7 @@ object AppUtils { | |||
|         startAction: Int = 0, | ||||
|         startValue: Int = 0 | ||||
|     ) { | ||||
|         (this as AppCompatActivity?)?.loadResult(card.url, card.apiName, startAction, startValue) | ||||
|         (this as? AppCompatActivity?)?.loadResult(card.url, card.apiName, startAction, startValue) | ||||
|     } | ||||
| 
 | ||||
|     fun Activity.requestLocalAudioFocus(focusRequest: AudioFocusRequest?) { | ||||
|  | @ -230,6 +365,7 @@ object AppUtils { | |||
|     } | ||||
| 
 | ||||
|     // Copied from https://github.com/videolan/vlc-android/blob/master/application/vlc-android/src/org/videolan/vlc/util/FileUtils.kt | ||||
|     @SuppressLint("Range") | ||||
|     fun Context.getUri(data: Uri?): Uri? { | ||||
|         var uri = data | ||||
|         val ctx = this | ||||
|  | @ -245,11 +381,13 @@ object AppUtils { | |||
|                         arrayOf(MediaStore.MediaColumns.DISPLAY_NAME), null, null, null | ||||
|                     ) | ||||
|                     if (cursor != null && cursor.moveToFirst()) { | ||||
|                         val filename = cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME)) | ||||
|                             .replace("/", "") | ||||
|                         val filename = | ||||
|                             cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME)) | ||||
|                                 .replace("/", "") | ||||
|                         inputStream = ctx.contentResolver.openInputStream(data) | ||||
|                         if (inputStream == null) return data | ||||
|                         os = FileOutputStream(Environment.getExternalStorageDirectory().path + "/Download/" + filename) | ||||
|                         os = | ||||
|                             FileOutputStream(Environment.getExternalStorageDirectory().path + "/Download/" + filename) | ||||
|                         val buffer = ByteArray(1024) | ||||
|                         var bytesRead = inputStream.read(buffer) | ||||
|                         while (bytesRead >= 0) { | ||||
|  | @ -272,7 +410,8 @@ object AppUtils { | |||
|                     arrayOf(MediaStore.Video.Media.DATA), null, null, null | ||||
|                 )?.use { | ||||
|                     val columnIndex = it.getColumnIndexOrThrow(MediaStore.Video.Media.DATA) | ||||
|                     if (it.moveToFirst()) Uri.fromFile(File(it.getString(columnIndex))) ?: data else data | ||||
|                     if (it.moveToFirst()) Uri.fromFile(File(it.getString(columnIndex))) | ||||
|                         ?: data else data | ||||
|                 } | ||||
|                 //uri = MediaUtils.getContentMediaUri(data) | ||||
|                 /*} else if (data.authority == ctx.getString(R.string.tv_provider_authority)) { | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue