Feat[downloader]: game startup context awareness

This commit is contained in:
artdeell 2023-10-05 16:23:52 +03:00
parent 2b8a3c8ab5
commit 64daa0799f
10 changed files with 176 additions and 96 deletions

View File

@ -1,8 +1,7 @@
package net.kdt.pojavlaunch;
import static net.kdt.pojavlaunch.MainActivity.INTENT_MINECRAFT_VERSION;
import android.content.Intent;
import android.app.NotificationManager;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageButton;
@ -27,15 +26,17 @@ import net.kdt.pojavlaunch.extra.ExtraListener;
import net.kdt.pojavlaunch.fragments.MainMenuFragment;
import net.kdt.pojavlaunch.fragments.MicrosoftLoginFragment;
import net.kdt.pojavlaunch.fragments.SelectAuthFragment;
import net.kdt.pojavlaunch.mirrors.DownloadMirror;
import net.kdt.pojavlaunch.modloaders.modpacks.ModloaderInstallTracker;
import net.kdt.pojavlaunch.modloaders.modpacks.imagecache.IconCacheJanitor;
import net.kdt.pojavlaunch.prefs.LauncherPreferences;
import net.kdt.pojavlaunch.prefs.screens.LauncherPreferenceFragment;
import net.kdt.pojavlaunch.progresskeeper.ProgressKeeper;
import net.kdt.pojavlaunch.progresskeeper.TaskCountListener;
import net.kdt.pojavlaunch.services.ProgressServiceKeeper;
import net.kdt.pojavlaunch.tasks.AsyncMinecraftDownloader;
import net.kdt.pojavlaunch.tasks.AsyncVersionList;
import net.kdt.pojavlaunch.tasks.ContextAwareDoneListener;
import net.kdt.pojavlaunch.utils.NotificationUtils;
import net.kdt.pojavlaunch.value.launcherprofiles.LauncherProfiles;
import net.kdt.pojavlaunch.value.launcherprofiles.MinecraftProfile;
@ -53,6 +54,7 @@ public class LauncherActivity extends BaseActivity {
private ProgressLayout mProgressLayout;
private ProgressServiceKeeper mProgressServiceKeeper;
private ModloaderInstallTracker mInstallTracker;
private NotificationManager mNotificationManager;
/* Allows to switch from one button "type" to another */
private final FragmentManager.FragmentLifecycleCallbacks mFragmentCallbackListener = new FragmentManager.FragmentLifecycleCallbacks() {
@ -123,32 +125,24 @@ public class LauncherActivity extends BaseActivity {
}
String normalizedVersionId = AsyncMinecraftDownloader.normalizeVersionId(prof.lastVersionId);
JMinecraftVersionList.Version mcVersion = AsyncMinecraftDownloader.getListedVersion(normalizedVersionId);
new AsyncMinecraftDownloader(this, mcVersion, normalizedVersionId, new AsyncMinecraftDownloader.DoneListener() {
@Override
public void onDownloadDone() {
ProgressKeeper.waitUntilDone(()-> runOnUiThread(() -> {
try {
Intent mainIntent = new Intent(getBaseContext(), MainActivity.class);
mainIntent.putExtra(INTENT_MINECRAFT_VERSION, normalizedVersionId);
mainIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(mainIntent);
finish();
android.os.Process.killProcess(android.os.Process.myPid()); //You should kill yourself, NOW!
} catch (Throwable e) {
Tools.showError(getBaseContext(), e);
}
}));
}
@Override
public void onDownloadFailed(Throwable th) {
if(DownloadMirror.checkForTamperedException(LauncherActivity.this, th)) return;
if(th != null) Tools.showError(LauncherActivity.this, R.string.mc_download_failed, th);
}
});
new AsyncMinecraftDownloader(this,
mcVersion,
normalizedVersionId,
new ContextAwareDoneListener(this, normalizedVersionId)
);
return false;
};
private final TaskCountListener mDoubleLaunchPreventionListener = (tc)->{
// Hide the notification that starts the game if there are tasks executing.
// Prevents the user from trying to launch the game with tasks ongoing.
if(tc > 0) {
Tools.runOnUiThread(() ->
mNotificationManager.cancel(NotificationUtils.NOTIFICATION_ID_GAME_START)
);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -156,6 +150,8 @@ public class LauncherActivity extends BaseActivity {
IconCacheJanitor.runJanitor();
getWindow().setBackgroundDrawable(null);
bindViews();
mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
ProgressKeeper.addTaskCountListener(mDoubleLaunchPreventionListener);
ProgressKeeper.addTaskCountListener((mProgressServiceKeeper = new ProgressServiceKeeper(this)));
mSettingsButton.setOnClickListener(mSettingButtonListener);

View File

@ -12,7 +12,8 @@ import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import net.kdt.pojavlaunch.contextexecutor.ContextExecutorTask;
import net.kdt.pojavlaunch.value.NotificationConstants;
import net.kdt.pojavlaunch.utils.NotificationUtils;
import net.kdt.pojavlaunch.utils.NotificationUtils;
import java.io.Serializable;
@ -45,31 +46,23 @@ public class ShowErrorActivity extends Activity {
this.mThrowable = mThrowable;
this.mRolledMsg = mRolledMsg;
}
@Override
public void executeWithActivity(Activity activity) {
Tools.showError(activity, mRolledMsg, mThrowable);
Tools.showError(activity, mRolledMsg, mThrowable, true);
}
@Override
public void executeWithApplication(Context context) {
sendNotification(context, this);
Intent showErrorIntent = new Intent(context, ShowErrorActivity.class);
showErrorIntent.putExtra(ERROR_ACTIVITY_REMOTE_TASK, this);
NotificationUtils.sendBasicNotification(context,
R.string.notif_error_occured,
R.string.notif_error_occured_desc,
showErrorIntent,
NotificationUtils.PENDINGINTENT_CODE_SHOW_ERROR,
NotificationUtils.NOTIFICATION_ID_SHOW_ERROR
);
}
}
private static void sendNotification(Context context, RemoteErrorTask remoteErrorTask) {
Intent showErrorIntent = new Intent(context, ShowErrorActivity.class);
showErrorIntent.putExtra(ERROR_ACTIVITY_REMOTE_TASK, remoteErrorTask);
PendingIntent pendingIntent = PendingIntent.getActivity(context, NotificationConstants.PENDINGINTENT_CODE_SHOW_ERROR, showErrorIntent,
Build.VERSION.SDK_INT >=23 ? PendingIntent.FLAG_IMMUTABLE : 0);
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context, context.getString(R.string.notif_channel_id))
.setContentTitle(context.getString(R.string.notif_error_occured))
.setContentText(context.getString(R.string.notif_error_occured_desc))
.setSmallIcon(R.drawable.notif_icon)
.setContentIntent(pendingIntent);
notificationManager.notify(NotificationConstants.NOTIFICATION_ID_SHOW_ERROR, notificationBuilder.build());
}
}

View File

@ -548,7 +548,9 @@ public final class Tools {
public static void showError(final Context ctx, final String rolledMessage, final Throwable e) {
showError(ctx, R.string.global_error, rolledMessage, e, false, false);
}
public static void showError(final Context ctx, final String rolledMessage, final Throwable e, boolean exitIfOk) {
showError(ctx, R.string.global_error, rolledMessage, e, exitIfOk, false);
}
public static void showError(final Context ctx, final int titleId, final Throwable e, final boolean exitIfOk) {
showError(ctx, titleId, null, e, exitIfOk, false);
}

View File

@ -1,36 +1,24 @@
package net.kdt.pojavlaunch.modloaders.modpacks.api;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import androidx.core.app.NotificationCompat;
import net.kdt.pojavlaunch.LauncherActivity;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.Tools;
import net.kdt.pojavlaunch.modloaders.ModloaderDownloadListener;
import net.kdt.pojavlaunch.modloaders.modpacks.ModloaderInstallTracker;
import net.kdt.pojavlaunch.value.NotificationConstants;
import net.kdt.pojavlaunch.utils.NotificationUtils;
import java.io.File;
public class NotificationDownloadListener implements ModloaderDownloadListener {
private final NotificationCompat.Builder mNotificationBuilder;
private final NotificationManager mNotificationManager;
private final Context mContext;
private final ModLoader mModLoader;
public NotificationDownloadListener(Context context, ModLoader modLoader) {
mModLoader = modLoader;
mContext = context.getApplicationContext();
mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
mNotificationBuilder = new NotificationCompat.Builder(context, "channel_id")
.setContentTitle(context.getString(R.string.modpack_install_notification_title))
.setSmallIcon(R.drawable.notif_icon);
}
@Override
@ -38,32 +26,29 @@ public class NotificationDownloadListener implements ModloaderDownloadListener {
if(mModLoader.requiresGuiInstallation()) {
ModloaderInstallTracker.saveModLoader(mContext, mModLoader, downloadedFile);
Intent mainActivityIntent = new Intent(mContext, LauncherActivity.class);
Tools.runOnUiThread(() -> sendIntentNotification(mainActivityIntent, R.string.modpack_install_notification_success));
Tools.runOnUiThread(() -> NotificationUtils.sendBasicNotification(mContext,
R.string.modpack_install_notification_title,
R.string.modpack_install_notification_success,
mainActivityIntent,
NotificationUtils.PENDINGINTENT_CODE_DOWNLOAD_SERVICE,
NotificationUtils.NOTIFICATION_ID_DOWNLOAD_LISTENER
));
}
}
@Override
public void onDataNotAvailable() {
Tools.runOnUiThread(()->sendEmptyNotification(R.string.modpack_install_notification_data_not_available));
Tools.runOnUiThread(()->NotificationUtils.sendBasicNotification(mContext,
R.string.modpack_install_notification_title,
R.string.modpack_install_notification_success,
null,
NotificationUtils.PENDINGINTENT_CODE_DOWNLOAD_SERVICE,
NotificationUtils.NOTIFICATION_ID_DOWNLOAD_LISTENER
));
}
@Override
public void onDownloadError(Exception e) {
Tools.showErrorRemote(mContext, R.string.modpack_install_modloader_download_failed, e);
}
private void sendIntentNotification(Intent intent, int contentText) {
PendingIntent pendingInstallIntent =
PendingIntent.getActivity(mContext, NotificationConstants.PENDINGINTENT_CODE_DOWNLOAD_SERVICE,
intent, Build.VERSION.SDK_INT >=23 ? PendingIntent.FLAG_IMMUTABLE : 0);
mNotificationBuilder.setContentText(mContext.getText(contentText));
mNotificationBuilder.setContentIntent(pendingInstallIntent);
mNotificationManager.notify(NotificationConstants.NOTIFICATION_ID_DOWNLOAD_LISTENER, mNotificationBuilder.build());
}
private void sendEmptyNotification(int contentText) {
mNotificationBuilder.setContentText(mContext.getText(contentText));
mNotificationManager.notify(NotificationConstants.NOTIFICATION_ID_DOWNLOAD_LISTENER, mNotificationBuilder.build());
}
}

View File

@ -14,7 +14,7 @@ import androidx.core.content.ContextCompat;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.Tools;
import net.kdt.pojavlaunch.value.NotificationConstants;
import net.kdt.pojavlaunch.utils.NotificationUtils;
import java.lang.ref.WeakReference;
@ -39,7 +39,7 @@ public class GameService extends Service {
}
Intent killIntent = new Intent(getApplicationContext(), GameService.class);
killIntent.putExtra("kill", true);
PendingIntent pendingKillIntent = PendingIntent.getService(this, NotificationConstants.PENDINGINTENT_CODE_KILL_GAME_SERVICE
PendingIntent pendingKillIntent = PendingIntent.getService(this, NotificationUtils.PENDINGINTENT_CODE_KILL_GAME_SERVICE
, killIntent, Build.VERSION.SDK_INT >=23 ? PendingIntent.FLAG_IMMUTABLE : 0);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, "channel_id")
.setContentTitle(getString(R.string.lazy_service_default_title))
@ -47,7 +47,7 @@ public class GameService extends Service {
.addAction(android.R.drawable.ic_menu_close_clear_cancel, getString(R.string.notification_terminate), pendingKillIntent)
.setSmallIcon(R.drawable.notif_icon)
.setNotificationSilent();
startForeground(NotificationConstants.NOTIFICATION_ID_GAME_SERVICE, notificationBuilder.build());
startForeground(NotificationUtils.NOTIFICATION_ID_GAME_SERVICE, notificationBuilder.build());
return START_NOT_STICKY; // non-sticky so android wont try restarting the game after the user uses the "Quit" button
}

View File

@ -19,7 +19,7 @@ import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.Tools;
import net.kdt.pojavlaunch.progresskeeper.ProgressKeeper;
import net.kdt.pojavlaunch.progresskeeper.TaskCountListener;
import net.kdt.pojavlaunch.value.NotificationConstants;
import net.kdt.pojavlaunch.utils.NotificationUtils;
/**
* Lazy service which allows the process not to get killed.
@ -43,7 +43,7 @@ public class ProgressService extends Service implements TaskCountListener {
notificationManagerCompat = NotificationManagerCompat.from(getApplicationContext());
Intent killIntent = new Intent(getApplicationContext(), ProgressService.class);
killIntent.putExtra("kill", true);
PendingIntent pendingKillIntent = PendingIntent.getService(this, NotificationConstants.PENDINGINTENT_CODE_KILL_PROGRESS_SERVICE
PendingIntent pendingKillIntent = PendingIntent.getService(this, NotificationUtils.PENDINGINTENT_CODE_KILL_PROGRESS_SERVICE
, killIntent, Build.VERSION.SDK_INT >=23 ? PendingIntent.FLAG_IMMUTABLE : 0);
mNotificationBuilder = new NotificationCompat.Builder(this, "channel_id")
.setContentTitle(getString(R.string.lazy_service_default_title))
@ -64,7 +64,7 @@ public class ProgressService extends Service implements TaskCountListener {
}
Log.d("ProgressService", "Started!");
mNotificationBuilder.setContentText(getString(R.string.progresslayout_tasks_in_progress, ProgressKeeper.getTaskCount()));
startForeground(NotificationConstants.NOTIFICATION_ID_PROGRESS_SERVICE, mNotificationBuilder.build());
startForeground(NotificationUtils.NOTIFICATION_ID_PROGRESS_SERVICE, mNotificationBuilder.build());
if(ProgressKeeper.getTaskCount() < 1) stopSelf();
else ProgressKeeper.addTaskCountListener(this, false);

View File

@ -0,0 +1,73 @@
package net.kdt.pojavlaunch.tasks;
import static net.kdt.pojavlaunch.MainActivity.INTENT_MINECRAFT_VERSION;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import net.kdt.pojavlaunch.MainActivity;
import net.kdt.pojavlaunch.R;
import net.kdt.pojavlaunch.Tools;
import net.kdt.pojavlaunch.contextexecutor.ContextExecutor;
import net.kdt.pojavlaunch.contextexecutor.ContextExecutorTask;
import net.kdt.pojavlaunch.progresskeeper.ProgressKeeper;
import net.kdt.pojavlaunch.utils.NotificationUtils;
public class ContextAwareDoneListener implements AsyncMinecraftDownloader.DoneListener, ContextExecutorTask {
private final String mErrorString;
private final String mNormalizedVersionid;
public ContextAwareDoneListener(Context baseContext, String versionId) {
this.mErrorString = baseContext.getString(R.string.mc_download_failed);
this.mNormalizedVersionid = versionId;
}
private Intent createGameStartIntent(Context context) {
Intent mainIntent = new Intent(context, MainActivity.class);
mainIntent.putExtra(INTENT_MINECRAFT_VERSION, mNormalizedVersionid);
mainIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
return mainIntent;
}
@Override
public void onDownloadDone() {
ProgressKeeper.waitUntilDone(()->ContextExecutor.execute(this));
}
@Override
public void onDownloadFailed(Throwable throwable) {
if(throwable != null)
Tools.showErrorRemote(mErrorString, throwable);
}
@Override
public void executeWithActivity(Activity activity) {
try {
Intent gameStartIntent = createGameStartIntent(activity);
activity.startActivity(gameStartIntent);
activity.finish();
android.os.Process.killProcess(android.os.Process.myPid()); //You should kill yourself, NOW!
} catch (Throwable e) {
Tools.showError(activity.getBaseContext(), e);
}
}
@Override
public void executeWithApplication(Context context) {
Intent gameStartIntent = createGameStartIntent(context);
// Since the game is a separate process anyway, it does not matter if it gets invoked
// from somewhere other than the launcher activity.
// The only problem may arise if the launcher starts doing something when the user starts the notification.
// So, the notification is automatically removed once there are tasks ongoing in the ProgressKeeper
NotificationUtils.sendBasicNotification(context,
R.string.notif_download_finished,
R.string.notif_download_finished_desc,
gameStartIntent,
NotificationUtils.PENDINGINTENT_CODE_GAME_START,
NotificationUtils.NOTIFICATION_ID_GAME_START
);
// You should keep yourself safe, NOW!
// otherwise android does weird things...
}
}

View File

@ -0,0 +1,41 @@
package net.kdt.pojavlaunch.utils;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import androidx.core.app.NotificationCompat;
import net.kdt.pojavlaunch.R;
public class NotificationUtils {
public static final int NOTIFICATION_ID_PROGRESS_SERVICE = 1;
public static final int NOTIFICATION_ID_GAME_SERVICE = 2;
public static final int NOTIFICATION_ID_DOWNLOAD_LISTENER = 3;
public static final int NOTIFICATION_ID_SHOW_ERROR = 4;
public static final int NOTIFICATION_ID_GAME_START = 5;
public static final int PENDINGINTENT_CODE_KILL_PROGRESS_SERVICE = 1;
public static final int PENDINGINTENT_CODE_KILL_GAME_SERVICE = 2;
public static final int PENDINGINTENT_CODE_DOWNLOAD_SERVICE = 3;
public static final int PENDINGINTENT_CODE_SHOW_ERROR = 4;
public static final int PENDINGINTENT_CODE_GAME_START = 5;
public static void sendBasicNotification(Context context, int contentTitle, int contentText, Intent actionIntent,
int pendingIntentCode, int notificationId) {
PendingIntent pendingIntent = PendingIntent.getActivity(context, pendingIntentCode, actionIntent,
Build.VERSION.SDK_INT >=23 ? PendingIntent.FLAG_IMMUTABLE : 0);
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context, context.getString(R.string.notif_channel_id));
if(contentTitle != -1) notificationBuilder.setContentTitle(context.getString(contentTitle));
if(contentText != -1) notificationBuilder.setContentText(context.getString(contentText));
if(actionIntent != null) notificationBuilder.setContentIntent(pendingIntent);
notificationBuilder.setSmallIcon(R.drawable.notif_icon);
notificationManager.notify(notificationId, notificationBuilder.build());
}
}

View File

@ -1,12 +0,0 @@
package net.kdt.pojavlaunch.value;
public class NotificationConstants {
public static final int NOTIFICATION_ID_PROGRESS_SERVICE = 1;
public static final int NOTIFICATION_ID_GAME_SERVICE = 2;
public static final int NOTIFICATION_ID_DOWNLOAD_LISTENER = 3;
public static final int NOTIFICATION_ID_SHOW_ERROR = 4;
public static final int PENDINGINTENT_CODE_KILL_PROGRESS_SERVICE = 1;
public static final int PENDINGINTENT_CODE_KILL_GAME_SERVICE = 2;
public static final int PENDINGINTENT_CODE_DOWNLOAD_SERVICE = 3;
public static final int PENDINGINTENT_CODE_SHOW_ERROR = 4;
}

View File

@ -350,4 +350,6 @@
<string name="preference_download_source_description">Select a download mirror instead of using the official download server</string>
<string name="preference_verify_manifest_title">Verify game version manifest</string>
<string name="preference_verify_manifest_description">When enabled, the launcher will check the game version manifest along with the libraries.</string>
<string name="notif_download_finished">The game is ready to launch</string>
<string name="notif_download_finished_desc">Click here to start it!</string>
</resources>