mirror of
git://f0xx.org/android_cast
synced 2026-07-29 05:58:14 +03:00
snap
This commit is contained in:
@@ -15,7 +15,6 @@ import android.app.Application;
|
||||
import android.content.Context;
|
||||
|
||||
import com.foxx.androidcast.crash.CrashReporter;
|
||||
import com.foxx.androidcast.crash.CrashWatcherService;
|
||||
import com.foxx.androidcast.media.CodecPriorityCatalog;
|
||||
|
||||
/** Applies saved theme and locale at process start. */
|
||||
@@ -31,8 +30,6 @@ public class AndroidCastApplication extends Application {
|
||||
CodecPriorityCatalog.init(this);
|
||||
CastLocaleHelper.applyStoredLocale(this);
|
||||
CastThemeHelper.applyNightMode(this);
|
||||
CastTrayNotifier.syncOnAppStart(this);
|
||||
CrashReporter.install(this);
|
||||
CrashWatcherService.scheduleUploadSweep(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ public final class CastActivityTracker {
|
||||
public static void onResume(Activity activity) {
|
||||
if (activity != null) {
|
||||
lastActivityClass = activity.getClass();
|
||||
CastTrayNotifier.syncWhenForeground(activity);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ package com.foxx.androidcast;
|
||||
* - Cursor Agent (project assistant)
|
||||
* Digest: SHA256 292d7f278b9726120e113e803105641b313de93e2efa129dd3af428d1aa68033
|
||||
**********************************************************************/
|
||||
import android.app.ForegroundServiceStartNotAllowedException;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
@@ -19,6 +20,7 @@ import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
@@ -26,6 +28,7 @@ import androidx.core.app.NotificationCompat;
|
||||
* Persistent status-bar icon while cast services run (when enabled in settings).
|
||||
*/
|
||||
public final class CastTrayNotifier {
|
||||
private static final String TAG = "CastTrayNotifier";
|
||||
public static final String CHANNEL_TRAY = "cast_tray";
|
||||
public static final int ID_TRAY = 3;
|
||||
|
||||
@@ -41,9 +44,14 @@ public final class CastTrayNotifier {
|
||||
ensureTrayService(app);
|
||||
}
|
||||
|
||||
/** Apply saved preference at process start (idle tray when enabled). */
|
||||
public static void syncOnAppStart(Context context) {
|
||||
onPreferenceChanged(context);
|
||||
/**
|
||||
* Start idle tray when an activity is foreground (API 31+ blocks FGS from
|
||||
* {@link android.app.Application#onCreate()}).
|
||||
*/
|
||||
public static void syncWhenForeground(Context context) {
|
||||
if (AppPreferences.isShowTrayIcon(context)) {
|
||||
ensureTrayService(context);
|
||||
}
|
||||
}
|
||||
|
||||
/** User toggled the preference — show or hide immediately. */
|
||||
@@ -63,11 +71,21 @@ public final class CastTrayNotifier {
|
||||
return;
|
||||
}
|
||||
Intent intent = new Intent(app, TrayForegroundService.class);
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
app.startForegroundService(intent);
|
||||
} else {
|
||||
app.startService(intent);
|
||||
}
|
||||
} catch (ForegroundServiceStartNotAllowedException e) {
|
||||
Log.w(TAG, "tray FGS deferred (not foreground): " + e.getMessage());
|
||||
} catch (IllegalStateException e) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
Log.w(TAG, "tray FGS deferred: " + e.getMessage());
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void stopTrayService(Context context) {
|
||||
|
||||
@@ -11,8 +11,8 @@ package com.foxx.androidcast.crash;
|
||||
* - Cursor Agent (project assistant)
|
||||
* Digest: SHA256 5254fee83476d4a80db718169cbc13b61b18697148a8625baab8df0833b1d78f
|
||||
**********************************************************************/
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
import com.foxx.androidcast.stats.SessionStatsContext;
|
||||
@@ -29,6 +29,9 @@ public final class CrashReporter {
|
||||
|
||||
public static void install(Context context) {
|
||||
Context app = context.getApplicationContext();
|
||||
if (!isMainProcess(app)) {
|
||||
return;
|
||||
}
|
||||
if (app instanceof android.app.Application) {
|
||||
CrashRuntimeContext.init((android.app.Application) app);
|
||||
}
|
||||
@@ -89,12 +92,10 @@ public final class CrashReporter {
|
||||
}
|
||||
|
||||
private static void wakeWatcher(Context context) {
|
||||
try {
|
||||
Intent i = new Intent(context, CrashWatcherService.class);
|
||||
i.setAction(CrashWatcherService.ACTION_UPLOAD_PENDING);
|
||||
context.startService(i);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "wake watcher failed: " + e.getMessage());
|
||||
CrashWatcherService.requestImmediateSweep(context, true);
|
||||
}
|
||||
|
||||
private static boolean isMainProcess(Context context) {
|
||||
return context.getPackageName().equals(Application.getProcessName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
|
||||
import com.foxx.androidcast.util.IoUtils;
|
||||
@@ -28,6 +29,7 @@ import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* Standalone process ({@code :crashwatcher}) — uploads pending crash JSON even if the main
|
||||
@@ -37,30 +39,31 @@ public final class CrashWatcherService extends Service {
|
||||
private static final String TAG = "CrashWatcher";
|
||||
public static final String ACTION_UPLOAD_PENDING = "com.foxx.androidcast.crash.UPLOAD_PENDING";
|
||||
private static final int ALARM_REQUEST = 4102;
|
||||
private static final long IMMEDIATE_DEBOUNCE_MS = 30_000L;
|
||||
|
||||
private static final AtomicBoolean SWEEP_RUNNING = new AtomicBoolean(false);
|
||||
private static volatile long lastImmediateRequestElapsedMs;
|
||||
|
||||
/** One immediate sweep (debounced) plus the next periodic alarm. */
|
||||
public static void scheduleUploadSweep(Context context) {
|
||||
try {
|
||||
Intent i = new Intent(context, CrashWatcherService.class);
|
||||
i.setAction(ACTION_UPLOAD_PENDING);
|
||||
context.startService(i);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "scheduleUploadSweep: " + e.getMessage());
|
||||
Context app = context.getApplicationContext();
|
||||
schedulePeriodicAlarm(app);
|
||||
requestImmediateSweep(app);
|
||||
}
|
||||
CrashSettings settings = CrashSettingsStore.load(context);
|
||||
if (!settings.enabled) {
|
||||
return;
|
||||
}
|
||||
AlarmManager alarm = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
||||
|
||||
/** Alarm-only periodic upload; does not start the service now. */
|
||||
public static void schedulePeriodicAlarm(Context context) {
|
||||
Context app = context.getApplicationContext();
|
||||
CrashSettings settings = CrashSettingsStore.load(app);
|
||||
AlarmManager alarm = (AlarmManager) app.getSystemService(Context.ALARM_SERVICE);
|
||||
if (alarm == null) {
|
||||
return;
|
||||
}
|
||||
Intent periodic = new Intent(context, CrashWatcherService.class);
|
||||
periodic.setAction(ACTION_UPLOAD_PENDING);
|
||||
PendingIntent pi = PendingIntent.getService(
|
||||
context,
|
||||
ALARM_REQUEST,
|
||||
periodic,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
||||
PendingIntent pi = pendingAlarmIntent(app);
|
||||
if (!settings.enabled) {
|
||||
alarm.cancel(pi);
|
||||
return;
|
||||
}
|
||||
long trigger = System.currentTimeMillis() + settings.uploadIntervalMs;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
alarm.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, trigger, pi);
|
||||
@@ -69,13 +72,59 @@ public final class CrashWatcherService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
public static void requestImmediateSweep(Context context) {
|
||||
requestImmediateSweep(context, false);
|
||||
}
|
||||
|
||||
/** Start a sweep now if one is not already running (coalesces duplicate requests). */
|
||||
public static void requestImmediateSweep(Context context, boolean bypassDebounce) {
|
||||
Context app = context.getApplicationContext();
|
||||
if (!CrashSettingsStore.load(app).enabled) {
|
||||
return;
|
||||
}
|
||||
if (!bypassDebounce) {
|
||||
long now = SystemClock.elapsedRealtime();
|
||||
synchronized (CrashWatcherService.class) {
|
||||
if (now - lastImmediateRequestElapsedMs < IMMEDIATE_DEBOUNCE_MS) {
|
||||
return;
|
||||
}
|
||||
lastImmediateRequestElapsedMs = now;
|
||||
}
|
||||
}
|
||||
if (SWEEP_RUNNING.get()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Intent i = new Intent(app, CrashWatcherService.class);
|
||||
i.setAction(ACTION_UPLOAD_PENDING);
|
||||
app.startService(i);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "requestImmediateSweep: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static PendingIntent pendingAlarmIntent(Context app) {
|
||||
Intent periodic = new Intent(app, CrashWatcherService.class);
|
||||
periodic.setAction(ACTION_UPLOAD_PENDING);
|
||||
return PendingIntent.getService(
|
||||
app,
|
||||
ALARM_REQUEST,
|
||||
periodic,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
if (!SWEEP_RUNNING.compareAndSet(false, true)) {
|
||||
stopSelf(startId);
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
new Thread(() -> {
|
||||
try {
|
||||
runSweep();
|
||||
} finally {
|
||||
scheduleUploadSweep(getApplicationContext());
|
||||
SWEEP_RUNNING.set(false);
|
||||
schedulePeriodicAlarm(getApplicationContext());
|
||||
stopSelf(startId);
|
||||
}
|
||||
}, "CrashWatcherSweep").start();
|
||||
|
||||
Reference in New Issue
Block a user