mirror of
https://github.com/AngelAuraMC/Amethyst-Android.git
synced 2025-09-16 08:05:34 -04:00
Feat[notifs]: features for remote error reporting, unified notification constants
TODO: also use it for Minecraft downloads
This commit is contained in:
parent
2980afdd05
commit
f1e88e2068
@ -78,6 +78,9 @@
|
||||
android:name=".FatalErrorActivity"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize|keyboard|navigation"
|
||||
android:theme="@style/Theme.AppCompat.DayNight.Dialog" />
|
||||
<activity android:name=".ShowErrorActivity"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize|keyboard|navigation"
|
||||
android:theme="@style/Theme.AppCompat.DayNight.Dialog" />
|
||||
<activity
|
||||
android:name=".ExitActivity"
|
||||
android:configChanges="keyboardHidden|orientation|screenSize|keyboard|navigation"
|
||||
|
@ -24,6 +24,7 @@ import androidx.fragment.app.FragmentManager;
|
||||
import com.kdt.mcgui.ProgressLayout;
|
||||
import com.kdt.mcgui.mcAccountSpinner;
|
||||
|
||||
import net.kdt.pojavlaunch.contextexecutor.ContextExecutor;
|
||||
import net.kdt.pojavlaunch.fragments.MainMenuFragment;
|
||||
import net.kdt.pojavlaunch.fragments.MicrosoftLoginFragment;
|
||||
import net.kdt.pojavlaunch.extra.ExtraConstants;
|
||||
@ -183,12 +184,14 @@ public class LauncherActivity extends BaseActivity {
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
ContextExecutor.setActivity(this);
|
||||
mInstallTracker.attach();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
ContextExecutor.clearActivity();
|
||||
mInstallTracker.detach();
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@ import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import net.kdt.pojavlaunch.contextexecutor.ContextExecutor;
|
||||
import net.kdt.pojavlaunch.tasks.AsyncAssetManager;
|
||||
import net.kdt.pojavlaunch.utils.*;
|
||||
|
||||
@ -27,6 +28,7 @@ public class PojavApplication extends Application {
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
ContextExecutor.setApplication(this);
|
||||
Thread.setDefaultUncaughtExceptionHandler((thread, th) -> {
|
||||
boolean storagePermAllowed = (Build.VERSION.SDK_INT < 23 || Build.VERSION.SDK_INT >= 29 ||
|
||||
ActivityCompat.checkSelfPermission(PojavApplication.this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) && Tools.checkStorageRoot(PojavApplication.this);
|
||||
@ -78,8 +80,14 @@ public class PojavApplication extends Application {
|
||||
startActivity(ferrorIntent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@Override
|
||||
public void onTerminate() {
|
||||
super.onTerminate();
|
||||
ContextExecutor.clearApplication();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void attachBaseContext(Context base) {
|
||||
super.attachBaseContext(LocaleUtils.setLocale(base));
|
||||
}
|
||||
|
@ -0,0 +1,75 @@
|
||||
package net.kdt.pojavlaunch;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
import net.kdt.pojavlaunch.contextexecutor.ContextExecutorTask;
|
||||
import net.kdt.pojavlaunch.value.NotificationConstants;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class ShowErrorActivity extends Activity {
|
||||
|
||||
private static final String ERROR_ACTIVITY_REMOTE_TASK = "remoteTask";
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Intent intent = getIntent();
|
||||
if(intent == null) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
RemoteErrorTask remoteErrorTask = (RemoteErrorTask) intent.getSerializableExtra(ERROR_ACTIVITY_REMOTE_TASK);
|
||||
if(remoteErrorTask == null) {
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
remoteErrorTask.executeWithActivity(this);
|
||||
}
|
||||
|
||||
|
||||
public static class RemoteErrorTask implements ContextExecutorTask, Serializable {
|
||||
private final Throwable mThrowable;
|
||||
private final String mRolledMsg;
|
||||
|
||||
public RemoteErrorTask(Throwable mThrowable, String mRolledMsg) {
|
||||
this.mThrowable = mThrowable;
|
||||
this.mRolledMsg = mRolledMsg;
|
||||
}
|
||||
@Override
|
||||
public void executeWithActivity(Activity activity) {
|
||||
Tools.showError(activity, mRolledMsg, mThrowable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void executeWithApplication(Context context) {
|
||||
sendNotification(context, this);
|
||||
}
|
||||
}
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
@ -46,6 +46,7 @@ import androidx.fragment.app.FragmentTransaction;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import net.kdt.pojavlaunch.contextexecutor.ContextExecutor;
|
||||
import net.kdt.pojavlaunch.multirt.MultiRTUtils;
|
||||
import net.kdt.pojavlaunch.multirt.Runtime;
|
||||
import net.kdt.pojavlaunch.plugins.FFmpegPlugin;
|
||||
@ -595,6 +596,26 @@ public final class Tools {
|
||||
}
|
||||
}
|
||||
|
||||
public static void showErrorRemote(Throwable e) {
|
||||
showErrorRemote(null, e);
|
||||
}
|
||||
public static void showErrorRemote(Context context, int rolledMessage, Throwable e) {
|
||||
showErrorRemote(context.getString(rolledMessage), e);
|
||||
}
|
||||
public static void showErrorRemote(String rolledMessage, Throwable e) {
|
||||
// I WILL embrace layer violations because Android's concept of layers is STUPID
|
||||
// We live in the same process anyway, why make it any more harder with this needless
|
||||
// abstraction?
|
||||
// Also, to @TorchDragon in r/AndroidDev discord: if Android is not for general computing,
|
||||
// and all apps need so much babysitting, why did they put an SoC equivalent to
|
||||
// my main PC in power?
|
||||
|
||||
// Add your Context-related rage here
|
||||
ContextExecutor.execute(new ShowErrorActivity.RemoteErrorTask(e, rolledMessage));
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static void dialogOnUiThread(final Activity activity, final CharSequence title, final CharSequence message) {
|
||||
activity.runOnUiThread(()->dialog(activity, title, message));
|
||||
}
|
||||
|
@ -0,0 +1,75 @@
|
||||
package net.kdt.pojavlaunch.contextexecutor;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
|
||||
import net.kdt.pojavlaunch.Tools;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
public class ContextExecutor {
|
||||
private static WeakReference<Application> sApplication;
|
||||
private static WeakReference<Activity> sActivity;
|
||||
|
||||
|
||||
/**
|
||||
* Schedules a ContextExecutorTask to be executed. For more info on tasks, please read
|
||||
* ContextExecutorTask.java
|
||||
* @param contextExecutorTask the task to be executed
|
||||
*/
|
||||
public static void execute(ContextExecutorTask contextExecutorTask) {
|
||||
Tools.runOnUiThread(()->executeOnUiThread(contextExecutorTask));
|
||||
}
|
||||
|
||||
private static void executeOnUiThread(ContextExecutorTask contextExecutorTask) {
|
||||
Activity activity = getWeakReference(sActivity);
|
||||
if(activity != null) {
|
||||
contextExecutorTask.executeWithActivity(activity);
|
||||
return;
|
||||
}
|
||||
Application application = getWeakReference(sApplication);
|
||||
if(application != null) {
|
||||
contextExecutorTask.executeWithApplication(application);
|
||||
}else {
|
||||
throw new RuntimeException("ContextExecutor.execute() called before Application.onCreate!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Activity that this ContextExecutor will use for executing tasks
|
||||
* @param activity the activity to be used
|
||||
*/
|
||||
public static void setActivity(Activity activity) {
|
||||
sActivity = new WeakReference<>(activity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the Activity previously set, so thet ContextExecutor won't use it to execute tasks.
|
||||
*/
|
||||
public static void clearActivity() {
|
||||
if(sActivity != null)
|
||||
sActivity.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Application that will be used to execute tasks if the Activity won't be available.
|
||||
* @param application the application to use as the fallback
|
||||
*/
|
||||
public static void setApplication(Application application) {
|
||||
sApplication = new WeakReference<>(application);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the Application previously set, so that ContextExecutor will notify the user of a critical error
|
||||
* that is executing code after the application is ended by the system.
|
||||
*/
|
||||
public static void clearApplication() {
|
||||
if(sApplication != null)
|
||||
sApplication.clear();
|
||||
}
|
||||
|
||||
private static <T> T getWeakReference(WeakReference<T> weakReference) {
|
||||
if(weakReference == null) return null;
|
||||
return weakReference.get();
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package net.kdt.pojavlaunch.contextexecutor;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
|
||||
/**
|
||||
* A ContextExecutorTask is a task that can dynamically change its behaviour, based on the context
|
||||
* used for its execution. This can be used to implement for ex. error/finish notifications from
|
||||
* background threads that may live with the Service after the activity that started them died.
|
||||
*/
|
||||
public interface ContextExecutorTask {
|
||||
/**
|
||||
* ContextExecutor will execute this function first if a foreground Activity that was attached to the
|
||||
* ContextExecutor is available.
|
||||
* @param activity the activity
|
||||
*/
|
||||
void executeWithActivity(Activity activity);
|
||||
|
||||
/**
|
||||
* ContextExecutor will execute this function if a foreground Activity is not available, but the app
|
||||
* is still running.
|
||||
* @param context the application context
|
||||
*/
|
||||
void executeWithApplication(Context context);
|
||||
}
|
@ -57,7 +57,7 @@ public interface ModpackApi {
|
||||
if (loaderInfo == null) return;
|
||||
loaderInfo.getDownloadTask(new NotificationDownloadListener(context, loaderInfo)).run();
|
||||
}catch (IOException e) {
|
||||
// TODO: pass on the IOException to a relevant handler
|
||||
Tools.showErrorRemote(context, R.string.modpack_install_download_failed, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ 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 java.io.File;
|
||||
|
||||
@ -46,21 +47,21 @@ public class NotificationDownloadListener implements ModloaderDownloadListener {
|
||||
|
||||
@Override
|
||||
public void onDownloadError(Exception e) {
|
||||
Tools.runOnUiThread(()->sendEmptyNotification(R.string.modpack_install_notification_download_failed));
|
||||
Tools.showErrorRemote(mContext, R.string.modpack_install_modloader_download_failed, e);
|
||||
}
|
||||
|
||||
private void sendIntentNotification(Intent intent, int contentText) {
|
||||
PendingIntent pendingInstallIntent =
|
||||
PendingIntent.getActivity(mContext, 0,
|
||||
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(3, mNotificationBuilder.build());
|
||||
mNotificationManager.notify(NotificationConstants.NOTIFICATION_ID_DOWNLOAD_LISTENER, mNotificationBuilder.build());
|
||||
}
|
||||
|
||||
private void sendEmptyNotification(int contentText) {
|
||||
mNotificationBuilder.setContentText(mContext.getText(contentText));
|
||||
mNotificationManager.notify(3, mNotificationBuilder.build());
|
||||
mNotificationManager.notify(NotificationConstants.NOTIFICATION_ID_DOWNLOAD_LISTENER, mNotificationBuilder.build());
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import androidx.core.content.ContextCompat;
|
||||
|
||||
import net.kdt.pojavlaunch.R;
|
||||
import net.kdt.pojavlaunch.Tools;
|
||||
import net.kdt.pojavlaunch.value.NotificationConstants;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
@ -38,14 +39,15 @@ public class GameService extends Service {
|
||||
}
|
||||
Intent killIntent = new Intent(getApplicationContext(), GameService.class);
|
||||
killIntent.putExtra("kill", true);
|
||||
PendingIntent pendingKillIntent = PendingIntent.getService(this, 0, killIntent, Build.VERSION.SDK_INT >=23 ? PendingIntent.FLAG_IMMUTABLE : 0);
|
||||
PendingIntent pendingKillIntent = PendingIntent.getService(this, NotificationConstants.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))
|
||||
.setContentText(getString(R.string.notification_game_runs))
|
||||
.addAction(android.R.drawable.ic_menu_close_clear_cancel, getString(R.string.notification_terminate), pendingKillIntent)
|
||||
.setSmallIcon(R.drawable.notif_icon)
|
||||
.setNotificationSilent();
|
||||
startForeground(2, notificationBuilder.build());
|
||||
startForeground(NotificationConstants.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
|
||||
}
|
||||
|
||||
|
@ -19,6 +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;
|
||||
|
||||
/**
|
||||
* Lazy service which allows the process not to get killed.
|
||||
@ -42,7 +43,8 @@ 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, 0, killIntent, Build.VERSION.SDK_INT >=23 ? PendingIntent.FLAG_IMMUTABLE : 0);
|
||||
PendingIntent pendingKillIntent = PendingIntent.getService(this, NotificationConstants.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))
|
||||
.addAction(android.R.drawable.ic_menu_close_clear_cancel, getString(R.string.notification_terminate), pendingKillIntent)
|
||||
@ -62,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(1, mNotificationBuilder.build());
|
||||
startForeground(NotificationConstants.NOTIFICATION_ID_PROGRESS_SERVICE, mNotificationBuilder.build());
|
||||
if(ProgressKeeper.getTaskCount() < 1) stopSelf();
|
||||
else ProgressKeeper.addTaskCountListener(this, false);
|
||||
|
||||
|
@ -0,0 +1,12 @@
|
||||
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;
|
||||
}
|
@ -423,5 +423,9 @@
|
||||
<string name="modpack_install_notification_title">Pojav Modpack Installer</string>
|
||||
<string name="modpack_install_notification_success">Click here to finish modpack installation</string>
|
||||
<string name="modpack_install_notification_data_not_available">Failed to download mod loader information</string>
|
||||
<string name="modpack_install_notification_download_failed">Failed to download the mod loader files</string>
|
||||
<string name="modpack_install_modloader_download_failed">Failed to download the mod loader files</string>
|
||||
<string name="modpack_install_download_failed">Failed to download modpack files</string>
|
||||
|
||||
<string name="notif_error_occured">An error has occurred</string>
|
||||
<string name="notif_error_occured_desc">Click to see more details</string>
|
||||
</resources>
|
||||
|
Loading…
x
Reference in New Issue
Block a user