summaryrefslogtreecommitdiff
path: root/Source/Android
diff options
context:
space:
mode:
authorJordan Woyak <jordan.woyak@gmail.com>2026-04-05 18:37:57 -0500
committerGitHub <noreply@github.com>2026-04-05 18:37:57 -0500
commit3bc6815c8bead6497b4bd088b40dfd7d2ee8aa00 (patch)
tree9b9d5d68deb950d4fafb1206a8710b51ff8f1733 /Source/Android
parent50fd4cca564078a1a1eaa8275d7ca59e7211d4e6 (diff)
parentdbeca0d1342022050ffd56b6b4bcdcaf0728fcfd (diff)
Merge pull request #14503 from Simonx22/android/convert-syncprogramsjobservice-to-kotlin
Android: Convert SyncProgramsJobService to Kotlin
Diffstat (limited to 'Source/Android')
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/SyncProgramsJobService.java164
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/SyncProgramsJobService.kt156
2 files changed, 156 insertions, 164 deletions
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/SyncProgramsJobService.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/SyncProgramsJobService.java
deleted file mode 100644
index 7e4dacf9bc..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/SyncProgramsJobService.java
+++ /dev/null
@@ -1,164 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.dolphinemu.dolphinemu.services;
-
-import android.app.job.JobParameters;
-import android.app.job.JobService;
-import android.content.Context;
-import android.net.Uri;
-import android.os.AsyncTask;
-import android.os.PersistableBundle;
-import android.util.Log;
-
-import androidx.tvprovider.media.tv.Channel;
-import androidx.tvprovider.media.tv.PreviewProgram;
-import androidx.tvprovider.media.tv.TvContractCompat;
-
-import org.dolphinemu.dolphinemu.R;
-import org.dolphinemu.dolphinemu.model.GameFile;
-import org.dolphinemu.dolphinemu.ui.platform.PlatformTab;
-import org.dolphinemu.dolphinemu.utils.AppLinkHelper;
-import org.dolphinemu.dolphinemu.utils.TvUtil;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-public class SyncProgramsJobService extends JobService
-{
- private static final String TAG = "SyncProgramsJobService";
-
- private SyncProgramsTask mSyncProgramsTask;
-
- @Override
- public boolean onStartJob(final JobParameters jobParameters)
- {
- Log.d(TAG, "onStartJob(): " + jobParameters);
- final long channelId = getChannelId(jobParameters);
- if (channelId == -1L)
- {
- Log.d(TAG, "Failed to find channel");
- return false;
- }
-
- mSyncProgramsTask =
- new SyncProgramsTask(getApplicationContext())
- {
- @Override
- protected void onPostExecute(Boolean finished)
- {
- super.onPostExecute(finished);
- mSyncProgramsTask = null;
- jobFinished(jobParameters, !finished);
- }
- };
- mSyncProgramsTask.execute(channelId);
- return true;
- }
-
- @Override
- public boolean onStopJob(JobParameters jobParameters)
- {
- if (mSyncProgramsTask != null)
- {
- mSyncProgramsTask.cancel(true);
- }
- return true;
- }
-
- private long getChannelId(JobParameters jobParameters)
- {
- PersistableBundle extras = jobParameters.getExtras();
- return extras.getLong(TvContractCompat.EXTRA_CHANNEL_ID, -1L);
- }
-
- private static class SyncProgramsTask extends AsyncTask<Long, Void, Boolean>
- {
- private Context context;
- private List<GameFile> updatePrograms;
-
- private SyncProgramsTask(Context context)
- {
- this.context = context;
- updatePrograms = new ArrayList<>();
- }
-
- /**
- * Determines which channel to update, get the game files for the channel,
- * then updates the list
- */
- @Override
- protected Boolean doInBackground(Long... channelIds)
- {
- List<Long> params = Arrays.asList(channelIds);
- if (!params.isEmpty())
- {
- for (Long channelId : params)
- {
- Channel channel = TvUtil.getChannelById(context, channelId);
- for (PlatformTab platformTab : PlatformTab.values())
- {
- if (channel != null &&
- channel.getAppLinkIntentUri().equals(AppLinkHelper.buildBrowseUri(platformTab)))
- {
- getGamesByPlatform(platformTab);
- syncPrograms(channelId);
- }
- }
- }
- }
- return true;
- }
-
- private void getGamesByPlatform(PlatformTab platformTab)
- {
- updatePrograms = GameFileCacheManager.getGameFilesForPlatformTab(platformTab);
- }
-
- private void syncPrograms(long channelId)
- {
- Log.d(TAG, "Sync programs for channel: " + channelId);
- deletePrograms(channelId);
- createPrograms(channelId);
- }
-
- private void createPrograms(long channelId)
- {
- for (GameFile game : updatePrograms)
- {
- PreviewProgram previewProgram = buildProgram(channelId, game);
-
- context.getContentResolver()
- .insert(
- TvContractCompat.PreviewPrograms.CONTENT_URI,
- previewProgram.toContentValues());
- }
- }
-
- private void deletePrograms(long channelId)
- {
- context.getContentResolver().delete(
- TvContractCompat.buildPreviewProgramsUriForChannel(channelId),
- null,
- null);
- }
-
- private PreviewProgram buildProgram(long channelId, GameFile game)
- {
- Uri appLinkUri = AppLinkHelper.buildGameUri(channelId, game.getGameId());
- Uri banner = TvUtil.buildBanner(game, context);
- if (banner == null)
- banner = TvUtil.getUriToResource(context, R.drawable.banner_tv);
-
- PreviewProgram.Builder builder = new PreviewProgram.Builder();
- builder.setChannelId(channelId)
- .setType(TvContractCompat.PreviewProgramColumns.TYPE_GAME)
- .setTitle(game.getTitle())
- .setDescription(game.getDescription())
- .setPosterArtUri(banner)
- .setPosterArtAspectRatio(TvContractCompat.PreviewPrograms.ASPECT_RATIO_2_3)
- .setIntentUri(appLinkUri);
- return builder.build();
- }
- }
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/SyncProgramsJobService.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/SyncProgramsJobService.kt
new file mode 100644
index 0000000000..2d9ad354fb
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/services/SyncProgramsJobService.kt
@@ -0,0 +1,156 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.services
+
+import android.app.job.JobParameters
+import android.app.job.JobService
+import android.content.ContentValues
+import android.content.Context
+import android.media.tv.TvContract
+import android.util.Log
+import androidx.tvprovider.media.tv.TvContractCompat
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.currentCoroutineContext
+import kotlinx.coroutines.ensureActive
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import org.dolphinemu.dolphinemu.R
+import org.dolphinemu.dolphinemu.model.GameFile
+import org.dolphinemu.dolphinemu.ui.platform.PlatformTab
+import org.dolphinemu.dolphinemu.utils.AppLinkHelper.buildBrowseUri
+import org.dolphinemu.dolphinemu.utils.AppLinkHelper.buildGameUri
+import org.dolphinemu.dolphinemu.utils.TvUtil
+
+class SyncProgramsJobService : JobService() {
+ private val syncProgramsScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
+ private var syncProgramsJob: Job? = null
+
+ override fun onStartJob(jobParameters: JobParameters): Boolean {
+ Log.d(TAG, "onStartJob(): $jobParameters")
+ val channelId = getChannelId(jobParameters)
+ if (channelId == -1L) {
+ Log.d(TAG, "Failed to find channel")
+ return false
+ }
+
+ syncProgramsJob?.cancel()
+ syncProgramsJob = syncProgramsScope.launch {
+ val finished = try {
+ withContext(Dispatchers.IO) {
+ SyncProgramsTask(applicationContext).run(channelId)
+ }
+ } catch (_: CancellationException) {
+ return@launch
+ } catch (e: RuntimeException) {
+ Log.e(TAG, "Failed to sync programs", e)
+ false
+ }
+
+ syncProgramsJob = null
+ jobFinished(jobParameters, !finished)
+ }
+
+ return true
+ }
+
+ override fun onStopJob(jobParameters: JobParameters): Boolean {
+ syncProgramsJob?.cancel()
+ syncProgramsJob = null
+ return true
+ }
+
+ override fun onDestroy() {
+ syncProgramsScope.cancel()
+ super.onDestroy()
+ }
+
+ private fun getChannelId(jobParameters: JobParameters): Long {
+ val extras = jobParameters.extras
+ return extras.getLong(TvContractCompat.EXTRA_CHANNEL_ID, -1L)
+ }
+
+ private class SyncProgramsTask(private val context: Context) {
+ private var updatePrograms: List<GameFile> = emptyList()
+
+ /**
+ * Determines which channel to update, get the game files for the channel,
+ * then updates the list
+ */
+ suspend fun run(vararg channelIds: Long): Boolean {
+ for (channelId in channelIds) {
+ currentCoroutineContext().ensureActive()
+
+ val channel = TvUtil.getChannelById(context, channelId)
+ for (platformTab in PlatformTab.entries) {
+ if (channel?.appLinkIntentUri == buildBrowseUri(platformTab)) {
+ updatePrograms =
+ GameFileCacheManager.getGameFilesForPlatformTab(platformTab)
+ if (!syncPrograms(channelId)) {
+ return false
+ }
+ }
+ }
+ }
+
+ return true
+ }
+
+ private suspend fun syncPrograms(channelId: Long): Boolean {
+ Log.d(TAG, "Sync programs for channel: $channelId")
+ deletePrograms(channelId)
+ return createPrograms(channelId)
+ }
+
+ private suspend fun createPrograms(channelId: Long): Boolean {
+ for (game in updatePrograms) {
+ currentCoroutineContext().ensureActive()
+
+ val previewProgram = buildProgram(channelId, game)
+ context.contentResolver.insert(
+ TvContractCompat.PreviewPrograms.CONTENT_URI, previewProgram
+ )
+ }
+
+ return true
+ }
+
+ private fun deletePrograms(channelId: Long) {
+ context.contentResolver.delete(
+ TvContractCompat.buildPreviewProgramsUriForChannel(channelId), null, null
+ )
+ }
+
+ private fun buildProgram(channelId: Long, game: GameFile): ContentValues {
+ val appLinkUri = buildGameUri(channelId, game.getGameId())
+ var banner = TvUtil.buildBanner(game, context)
+ if (banner == null) {
+ banner = TvUtil.getUriToResource(context, R.drawable.banner_tv)
+ }
+
+ val values = ContentValues()
+ values.put(TvContractCompat.PreviewPrograms.COLUMN_CHANNEL_ID, channelId)
+ values.put(
+ TvContractCompat.PreviewPrograms.COLUMN_TYPE,
+ TvContractCompat.PreviewProgramColumns.TYPE_GAME
+ )
+ values.put(TvContract.Programs.COLUMN_TITLE, game.getTitle())
+ values.put(TvContract.Programs.COLUMN_SHORT_DESCRIPTION, game.getDescription())
+ values.put(TvContract.Programs.COLUMN_POSTER_ART_URI, banner.toString())
+ values.put(
+ TvContractCompat.PreviewPrograms.COLUMN_POSTER_ART_ASPECT_RATIO,
+ TvContractCompat.PreviewPrograms.ASPECT_RATIO_2_3
+ )
+ values.put(TvContractCompat.PreviewPrograms.COLUMN_INTENT_URI, appLinkUri.toString())
+ return values
+ }
+ }
+
+ companion object {
+ private const val TAG = "SyncProgramsJobService"
+ }
+}