Android: Convert StartupHandler to Kotlin

This commit is contained in:
Simonx22 2026-03-20 13:14:06 -04:00
parent f074cdb08b
commit b5b5b6fb7d
2 changed files with 118 additions and 129 deletions

View File

@ -1,129 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.utils;
import android.app.Activity;
import android.content.ClipData;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import androidx.fragment.app.FragmentActivity;
import androidx.lifecycle.LifecycleOwner;
import androidx.preference.PreferenceManager;
import org.dolphinemu.dolphinemu.NativeLibrary;
import org.dolphinemu.dolphinemu.activities.EmulationActivity;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Objects;
public final class StartupHandler
{
private static final String SESSION_TIMESTAMP = "SESSION_TIMESTAMP";
public static void HandleInit(FragmentActivity parent)
{
// Ask the user if he wants to enable analytics if we haven't yet.
Analytics.checkAnalyticsInit(parent);
// Set up and/or sync Android TV channels
if (TvUtil.isLeanback(parent))
TvUtil.scheduleSyncingChannel(parent);
String[] gamesToLaunch = getGamesFromIntent(parent.getIntent());
if (gamesToLaunch != null && gamesToLaunch.length > 0)
{
// Start the emulation activity, send the ISO passed in and finish the main activity
EmulationActivity.launch(parent, gamesToLaunch, false, true);
}
}
private static String[] getGamesFromIntent(Intent intent)
{
// Priority order when looking for game paths in an intent:
//
// Specifying multiple discs (for multi-disc games) is prioritized over specifying a single
// disc. But most of the time, only a single disc will have been specified anyway.
//
// Specifying content URIs (compatible with scoped storage) is prioritized over raw paths.
// The intention is that if a frontend app specifies both a content URI and a raw path, newer
// versions of Dolphin will work correctly under scoped storage, while older versions of Dolphin
// (which don't use scoped storage and don't support content URIs) will also work.
// 1. Content URI, multiple
ClipData clipData = intent.getClipData();
if (clipData != null)
{
String[] uris = new String[clipData.getItemCount()];
for (int i = 0; i < uris.length; i++)
{
uris[i] = Objects.toString(clipData.getItemAt(i).getUri());
}
return uris;
}
// 2. Content URI, single
Uri uri = intent.getData();
if (uri != null)
return new String[]{uri.toString()};
Bundle extras = intent.getExtras();
if (extras != null)
{
// 3. File path, multiple
String[] paths = extras.getStringArray("AutoStartFiles");
if (paths != null)
return paths;
// 4. File path, single
String path = extras.getString("AutoStartFile");
if (!TextUtils.isEmpty(path))
return new String[]{path};
}
// Nothing was found
return null;
}
private static Instant getSessionTimestamp(Context context)
{
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
long timestamp = preferences.getLong(SESSION_TIMESTAMP, 0);
return Instant.ofEpochMilli(timestamp);
}
/**
* Called on activity stop / to set timestamp to "now".
*/
public static void updateSessionTimestamp(Context context)
{
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor sPrefsEditor = preferences.edit();
sPrefsEditor.putLong(SESSION_TIMESTAMP, Instant.now().toEpochMilli());
sPrefsEditor.apply();
}
/**
* Called on activity start. Generates analytics start event if it's a fresh start of the app, or
* if it's a start after a long period of the app not being used (during which time the process
* may be restarted for power/memory saving reasons, although app state persists).
*/
public static void reportStartToAnalytics(Context context, boolean firstStart)
{
final Instant sessionTimestamp = getSessionTimestamp(context);
final Instant now = Instant.now();
if (firstStart || now.isAfter(sessionTimestamp.plus(6, ChronoUnit.HOURS)))
{
// Just in case: ensure start event won't be accidentally sent too often.
updateSessionTimestamp(context);
new AfterDirectoryInitializationRunner().runWithoutLifecycle(
NativeLibrary::ReportStartToAnalytics);
}
}
}

View File

@ -0,0 +1,118 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.utils
import android.content.ClipData
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.text.TextUtils
import androidx.fragment.app.FragmentActivity
import androidx.preference.PreferenceManager
import org.dolphinemu.dolphinemu.NativeLibrary
import org.dolphinemu.dolphinemu.activities.EmulationActivity
import java.time.Instant
import java.time.temporal.ChronoUnit
object StartupHandler {
private const val SESSION_TIMESTAMP = "SESSION_TIMESTAMP"
@JvmStatic
fun HandleInit(parent: FragmentActivity) {
// Ask the user if he wants to enable analytics if we haven't yet.
Analytics.checkAnalyticsInit(parent)
// Set up and/or sync Android TV channels
if (TvUtil.isLeanback(parent)) {
TvUtil.scheduleSyncingChannel(parent)
}
val gamesToLaunch = getGamesFromIntent(parent.intent)
if (gamesToLaunch != null && gamesToLaunch.isNotEmpty()) {
// Start the emulation activity, send the ISO passed in and finish the main activity
EmulationActivity.launch(parent, gamesToLaunch, false, true)
}
}
private fun getGamesFromIntent(intent: Intent): Array<String>? {
// Priority order when looking for game paths in an intent:
//
// Specifying multiple discs (for multi-disc games) is prioritized over specifying a single
// disc. But most of the time, only a single disc will have been specified anyway.
//
// Specifying content URIs (compatible with scoped storage) is prioritized over raw paths.
// The intention is that if a frontend app specifies both a content URI and a raw path, newer
// versions of Dolphin will work correctly under scoped storage, while older versions of Dolphin
// (which don't use scoped storage and don't support content URIs) will also work.
// 1. Content URI, multiple
val clipData: ClipData? = intent.clipData
if (clipData != null) {
val uris = Array(clipData.itemCount) { i ->
clipData.getItemAt(i).uri?.toString() ?: "null"
}
return uris
}
// 2. Content URI, single
val uri: Uri? = intent.data
if (uri != null) {
return arrayOf(uri.toString())
}
val extras: Bundle? = intent.extras
if (extras != null) {
// 3. File path, multiple
val paths = extras.getStringArray("AutoStartFiles")
if (paths != null) {
return paths
}
// 4. File path, single
val path = extras.getString("AutoStartFile")
if (!TextUtils.isEmpty(path)) {
return arrayOf(path!!)
}
}
// Nothing was found
return null
}
private fun getSessionTimestamp(context: Context): Instant {
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
val timestamp = preferences.getLong(SESSION_TIMESTAMP, 0)
return Instant.ofEpochMilli(timestamp)
}
/**
* Called on activity stop / to set timestamp to "now".
*/
@JvmStatic
fun updateSessionTimestamp(context: Context) {
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
val prefsEditor = preferences.edit()
prefsEditor.putLong(SESSION_TIMESTAMP, Instant.now().toEpochMilli())
prefsEditor.apply()
}
/**
* Called on activity start. Generates analytics start event if it's a fresh start of the app, or
* if it's a start after a long period of the app not being used (during which time the process
* may be restarted for power/memory saving reasons, although app state persists).
*/
@JvmStatic
fun reportStartToAnalytics(context: Context, firstStart: Boolean) {
val sessionTimestamp = getSessionTimestamp(context)
val now = Instant.now()
if (firstStart || now.isAfter(sessionTimestamp.plus(6, ChronoUnit.HOURS))) {
// Just in case: ensure start event won't be accidentally sent too often.
updateSessionTimestamp(context)
AfterDirectoryInitializationRunner().runWithoutLifecycle(
NativeLibrary::ReportStartToAnalytics
)
}
}
}