mirror of
git://f0xx.org/android_cast
synced 2026-07-29 05:17:39 +03:00
tray bar rework
This commit is contained in:
@@ -14,6 +14,7 @@ package com.foxx.androidcast;
|
|||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.foxx.androidcast.CastTrayNotifier;
|
||||||
import com.foxx.androidcast.commercial.CommercialFeatures;
|
import com.foxx.androidcast.commercial.CommercialFeatures;
|
||||||
import com.foxx.androidcast.crash.CrashReporter;
|
import com.foxx.androidcast.crash.CrashReporter;
|
||||||
import com.foxx.androidcast.dev.DevAdbWifiKeeper;
|
import com.foxx.androidcast.dev.DevAdbWifiKeeper;
|
||||||
@@ -46,5 +47,6 @@ public class AndroidCastApplication extends Application {
|
|||||||
CommercialFeatures.refreshFromPreferences(this);
|
CommercialFeatures.refreshFromPreferences(this);
|
||||||
RemoteAccessCoordinator.onBootIfNeeded(this);
|
RemoteAccessCoordinator.onBootIfNeeded(this);
|
||||||
DevAdbWifiKeeper.ensureStarted(this);
|
DevAdbWifiKeeper.ensureStarted(this);
|
||||||
|
CastTrayNotifier.ensureTrayService(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
92
app/src/main/java/com/foxx/androidcast/CastTrayContent.java
Normal file
92
app/src/main/java/com/foxx/androidcast/CastTrayContent.java
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
package com.foxx.androidcast;
|
||||||
|
|
||||||
|
import com.foxx.androidcast.remoteaccess.RemoteAccessMode;
|
||||||
|
import com.foxx.androidcast.remoteaccess.RemoteAccessStatusStore;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/** Pure text assembly for the single persistent tray notification. */
|
||||||
|
public final class CastTrayContent {
|
||||||
|
public static final class Body {
|
||||||
|
public final String summary;
|
||||||
|
public final String bigText;
|
||||||
|
|
||||||
|
public Body(String summary, String bigText) {
|
||||||
|
this.summary = summary != null ? summary : "";
|
||||||
|
this.bigText = bigText != null ? bigText : "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private CastTrayContent() {}
|
||||||
|
|
||||||
|
public static Body build(
|
||||||
|
String idleTapToOpen,
|
||||||
|
String castActive,
|
||||||
|
String adbWaiting,
|
||||||
|
String adbLineFormat,
|
||||||
|
String raPollFormat,
|
||||||
|
String raConnected,
|
||||||
|
String raSessionFormat,
|
||||||
|
boolean casting,
|
||||||
|
boolean adbEnabled,
|
||||||
|
String adbConnectLine,
|
||||||
|
RemoteAccessMode raMode,
|
||||||
|
RemoteAccessStatusStore.Snapshot raSnap) {
|
||||||
|
if (casting) {
|
||||||
|
return new Body(castActive, castActive);
|
||||||
|
}
|
||||||
|
List<String> lines = new ArrayList<>();
|
||||||
|
String raLine = remoteAccessLine(raPollFormat, raConnected, raSessionFormat, raMode, raSnap);
|
||||||
|
if (raLine != null && !raLine.isEmpty()) {
|
||||||
|
lines.add(raLine);
|
||||||
|
}
|
||||||
|
if (adbEnabled) {
|
||||||
|
String adbLine = adbConnectLine == null || adbConnectLine.isEmpty()
|
||||||
|
? adbWaiting
|
||||||
|
: String.format(adbLineFormat, adbConnectLine);
|
||||||
|
lines.add(adbLine);
|
||||||
|
}
|
||||||
|
if (!lines.isEmpty()) {
|
||||||
|
String joined = joinLines(lines);
|
||||||
|
return new Body(lines.get(0), joined);
|
||||||
|
}
|
||||||
|
return new Body(idleTapToOpen, idleTapToOpen);
|
||||||
|
}
|
||||||
|
|
||||||
|
static String remoteAccessLine(
|
||||||
|
String pollFormat,
|
||||||
|
String connected,
|
||||||
|
String sessionFormat,
|
||||||
|
RemoteAccessMode mode,
|
||||||
|
RemoteAccessStatusStore.Snapshot snap) {
|
||||||
|
if (mode == null || !mode.isActive()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (snap != null && RemoteAccessStatusStore.STATE_SUCCESS.equals(snap.state)) {
|
||||||
|
if (snap.sessionId != null && !snap.sessionId.isEmpty()) {
|
||||||
|
String line = String.format(sessionFormat, snap.sessionId);
|
||||||
|
if (mode == RemoteAccessMode.RSSH) {
|
||||||
|
return line + " (RSSH)";
|
||||||
|
}
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
if (mode == RemoteAccessMode.RSSH) {
|
||||||
|
return connected + " (RSSH)";
|
||||||
|
}
|
||||||
|
return connected;
|
||||||
|
}
|
||||||
|
return String.format(pollFormat, mode.name());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String joinLines(List<String> lines) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < lines.size(); i++) {
|
||||||
|
if (i > 0) {
|
||||||
|
sb.append('\n');
|
||||||
|
}
|
||||||
|
sb.append(lines.get(i));
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,5 @@
|
|||||||
package com.foxx.androidcast;
|
package com.foxx.androidcast;
|
||||||
|
|
||||||
/*********************************************************************
|
|
||||||
* CastTrayNotifier.java
|
|
||||||
* Created at: Sun 17 May 2026 18:17:14 +0200
|
|
||||||
* Updated at: Wed 20 May 2026 15:17:13 +0200 by Anton Afanasyeu <a.afanasieff@gmail.com>
|
|
||||||
* Commit: 5d8e82d2e60a21fff3138d2a394ee4e8b4c6dcb8
|
|
||||||
* class CastTrayNotifier
|
|
||||||
* Contributors:
|
|
||||||
* - Anton Afanasyeu <a.afanasieff@gmail.com> (3 commits, 156 lines)
|
|
||||||
* - Cursor Agent (project assistant)
|
|
||||||
* Digest: SHA256 292d7f278b9726120e113e803105641b313de93e2efa129dd3af428d1aa68033
|
|
||||||
**********************************************************************/
|
|
||||||
import android.app.ForegroundServiceStartNotAllowedException;
|
import android.app.ForegroundServiceStartNotAllowedException;
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
import android.app.NotificationChannel;
|
import android.app.NotificationChannel;
|
||||||
@@ -24,50 +13,69 @@ import android.util.Log;
|
|||||||
|
|
||||||
import androidx.core.app.NotificationCompat;
|
import androidx.core.app.NotificationCompat;
|
||||||
|
|
||||||
|
import com.foxx.androidcast.dev.DevAdbConnectInfo;
|
||||||
|
import com.foxx.androidcast.dev.DevAdbWifiKeeper;
|
||||||
|
import com.foxx.androidcast.remoteaccess.RemoteAccessMode;
|
||||||
|
import com.foxx.androidcast.remoteaccess.RemoteAccessStatusStore;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Persistent status-bar icon while cast services run (when enabled in settings).
|
* Single persistent status-bar notification for Android Cast (prod + dev modes).
|
||||||
*/
|
*/
|
||||||
public final class CastTrayNotifier {
|
public final class CastTrayNotifier {
|
||||||
private static final String TAG = "CastTrayNotifier";
|
private static final String TAG = "CastTrayNotifier";
|
||||||
public static final String CHANNEL_TRAY = "cast_tray";
|
public static final String CHANNEL_TRAY = "cast_tray";
|
||||||
public static final int ID_TRAY = 3;
|
public static final int ID_TRAY = 3;
|
||||||
|
|
||||||
|
/** Legacy per-feature notification ids — cancelled on every refresh. */
|
||||||
|
public static final int LEGACY_DEV_ADB_ID = 50391;
|
||||||
|
public static final int LEGACY_RA_ID = 0x7a00;
|
||||||
|
public static final int LEGACY_RA_VPN_ID = 0x7a01;
|
||||||
|
|
||||||
private CastTrayNotifier() {}
|
private CastTrayNotifier() {}
|
||||||
|
|
||||||
/** Sync tray with cast service lifecycle (call from foreground services). */
|
/** Sync tray content with app / dev service state. */
|
||||||
public static void refresh(Context context) {
|
public static void refresh(Context context) {
|
||||||
Context app = context.getApplicationContext();
|
Context app = context.getApplicationContext();
|
||||||
if (!AppPreferences.isShowTrayIcon(app)) {
|
cancelLegacyNotifications(app);
|
||||||
|
if (!shouldMaintainTray(app)) {
|
||||||
|
stopTrayService(app);
|
||||||
hide(app);
|
hide(app);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ensureTrayService(app);
|
ensureTrayService(app);
|
||||||
|
publish(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Start idle tray when an activity is foreground (API 31+ blocks FGS from
|
|
||||||
* {@link android.app.Application#onCreate()}).
|
|
||||||
*/
|
|
||||||
public static void syncWhenForeground(Context context) {
|
public static void syncWhenForeground(Context context) {
|
||||||
if (AppPreferences.isShowTrayIcon(context)) {
|
if (shouldMaintainTray(context)) {
|
||||||
ensureTrayService(context);
|
ensureTrayService(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** User toggled the preference — show or hide immediately. */
|
|
||||||
public static void onPreferenceChanged(Context context) {
|
public static void onPreferenceChanged(Context context) {
|
||||||
Context app = context.getApplicationContext();
|
refresh(context);
|
||||||
if (AppPreferences.isShowTrayIcon(app)) {
|
|
||||||
ensureTrayService(app);
|
|
||||||
} else {
|
|
||||||
stopTrayService(app);
|
|
||||||
hide(app);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean shouldMaintainTray(Context context) {
|
||||||
|
if (AppPreferences.isShowTrayIcon(context)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return hasDevTrayContent(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean hasDevTrayContent(Context context) {
|
||||||
|
if (!BuildConfig.DEBUG) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (AppPreferences.isDevAdbWifiKeeperEnabled(context)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return AppPreferences.getDevRemoteAccessMode(context).isActive();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ensureTrayService(Context context) {
|
public static void ensureTrayService(Context context) {
|
||||||
Context app = context.getApplicationContext();
|
Context app = context.getApplicationContext();
|
||||||
if (!AppPreferences.isShowTrayIcon(app)) {
|
if (!shouldMaintainTray(app)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Intent intent = new Intent(app, TrayForegroundService.class);
|
Intent intent = new Intent(app, TrayForegroundService.class);
|
||||||
@@ -92,34 +100,60 @@ public final class CastTrayNotifier {
|
|||||||
context.getApplicationContext().stopService(new Intent(context, TrayForegroundService.class));
|
context.getApplicationContext().stopService(new Intent(context, TrayForegroundService.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Foreground tray — not swipe-dismissible on stock Android. */
|
/** Foreground tray owned by {@link TrayForegroundService}. */
|
||||||
public static void startForegroundTray(Service service) {
|
public static void startForegroundTray(Service service) {
|
||||||
if (!AppPreferences.isShowTrayIcon(service)) {
|
if (!shouldMaintainTray(service)) {
|
||||||
service.stopSelf();
|
service.stopSelf();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
createChannel(service);
|
createChannel(service);
|
||||||
boolean idle = !CastActiveState.isSenderCasting() && !CastActiveState.isReceiverListening();
|
Notification n = buildNotification(service);
|
||||||
Notification n = buildNotification(service, idle);
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
service.startForeground(ID_TRAY, n, android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE);
|
service.startForeground(
|
||||||
|
ID_TRAY,
|
||||||
|
n,
|
||||||
|
android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE);
|
||||||
} else {
|
} else {
|
||||||
service.startForeground(ID_TRAY, n);
|
service.startForeground(ID_TRAY, n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void show(Context context, boolean idle) {
|
/** Other foreground services reuse the same tray notification id. */
|
||||||
if (AppPreferences.isShowTrayIcon(context)) {
|
public static void startForeground(Service service, int fgsType) {
|
||||||
ensureTrayService(context);
|
createChannel(service);
|
||||||
return;
|
Notification n = buildNotification(service);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && fgsType != 0) {
|
||||||
|
service.startForeground(ID_TRAY, n, fgsType);
|
||||||
|
} else {
|
||||||
|
service.startForeground(ID_TRAY, n);
|
||||||
}
|
}
|
||||||
hide(context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Notification buildNotification(Context context, boolean idle) {
|
public static Notification buildNotification(Context context) {
|
||||||
String body = idle
|
createChannel(context);
|
||||||
? context.getString(R.string.tray_cast_idle)
|
boolean casting = CastActiveState.isSenderCasting() || CastActiveState.isReceiverListening();
|
||||||
: context.getString(R.string.tray_cast_active);
|
boolean adbEnabled = BuildConfig.DEBUG && AppPreferences.isDevAdbWifiKeeperEnabled(context);
|
||||||
|
RemoteAccessMode raMode = BuildConfig.DEBUG
|
||||||
|
? AppPreferences.getDevRemoteAccessMode(context)
|
||||||
|
: RemoteAccessMode.DISABLED;
|
||||||
|
RemoteAccessStatusStore.Snapshot raSnap = RemoteAccessStatusStore.load(context);
|
||||||
|
DevAdbConnectInfo adbInfo = adbEnabled ? DevAdbWifiKeeper.getLastInfo(context) : null;
|
||||||
|
String adbLine = adbInfo != null ? adbInfo.primaryConnectLine() : "";
|
||||||
|
|
||||||
|
CastTrayContent.Body body = CastTrayContent.build(
|
||||||
|
context.getString(R.string.tray_cast_idle),
|
||||||
|
context.getString(R.string.tray_cast_active),
|
||||||
|
context.getString(R.string.dev_adb_wifi_notification_waiting),
|
||||||
|
context.getString(R.string.dev_adb_wifi_notification_line),
|
||||||
|
context.getString(R.string.dev_remote_access_notification_poll),
|
||||||
|
context.getString(R.string.dev_remote_access_notification_connected),
|
||||||
|
context.getString(R.string.dev_remote_access_notification_session),
|
||||||
|
casting,
|
||||||
|
adbEnabled,
|
||||||
|
adbLine,
|
||||||
|
raMode,
|
||||||
|
raSnap);
|
||||||
|
|
||||||
PendingIntent pi = PendingIntent.getActivity(
|
PendingIntent pi = PendingIntent.getActivity(
|
||||||
context, 0, CastActivityTracker.resumeIntent(context),
|
context, 0, CastActivityTracker.resumeIntent(context),
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
||||||
@@ -128,9 +162,10 @@ public final class CastTrayNotifier {
|
|||||||
PendingIntent deletePi = PendingIntent.getBroadcast(
|
PendingIntent deletePi = PendingIntent.getBroadcast(
|
||||||
context, 1, deleted,
|
context, 1, deleted,
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
||||||
return new NotificationCompat.Builder(context, CHANNEL_TRAY)
|
|
||||||
|
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_TRAY)
|
||||||
.setContentTitle(context.getString(R.string.app_name))
|
.setContentTitle(context.getString(R.string.app_name))
|
||||||
.setContentText(body)
|
.setContentText(body.summary)
|
||||||
.setSmallIcon(R.drawable.ic_notification)
|
.setSmallIcon(R.drawable.ic_notification)
|
||||||
.setContentIntent(pi)
|
.setContentIntent(pi)
|
||||||
.setDeleteIntent(deletePi)
|
.setDeleteIntent(deletePi)
|
||||||
@@ -142,8 +177,31 @@ public final class CastTrayNotifier {
|
|||||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||||
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
||||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||||
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
|
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE);
|
||||||
.build();
|
if (!body.bigText.equals(body.summary)) {
|
||||||
|
builder.setStyle(new NotificationCompat.BigTextStyle().bigText(body.bigText));
|
||||||
|
}
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void publish(Context context) {
|
||||||
|
if (!shouldMaintainTray(context)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
NotificationManager nm = context.getSystemService(NotificationManager.class);
|
||||||
|
if (nm != null) {
|
||||||
|
nm.notify(ID_TRAY, buildNotification(context));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void cancelLegacyNotifications(Context context) {
|
||||||
|
NotificationManager nm = context.getSystemService(NotificationManager.class);
|
||||||
|
if (nm == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
nm.cancel(LEGACY_DEV_ADB_ID);
|
||||||
|
nm.cancel(LEGACY_RA_ID);
|
||||||
|
nm.cancel(LEGACY_RA_VPN_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void hide(Context context) {
|
private static void hide(Context context) {
|
||||||
|
|||||||
@@ -1,19 +1,10 @@
|
|||||||
package com.foxx.androidcast;
|
package com.foxx.androidcast;
|
||||||
|
|
||||||
/*********************************************************************
|
|
||||||
* TrayForegroundService.java
|
|
||||||
* Created at: Sun 17 May 2026 22:49:49 +0200
|
|
||||||
* Updated at: Wed 20 May 2026 15:17:13 +0200 by Anton Afanasyeu <a.afanasieff@gmail.com>
|
|
||||||
* Commit: 5d8e82d2e60a21fff3138d2a394ee4e8b4c6dcb8
|
|
||||||
* class TrayForegroundService
|
|
||||||
* Contributors:
|
|
||||||
* - Anton Afanasyeu <a.afanasieff@gmail.com> (2 commits, 48 lines)
|
|
||||||
* - Cursor Agent (project assistant)
|
|
||||||
* Digest: SHA256 5697518674c876a08eb78c08ca151686a079c96d2d3dc238c32666d83b5e88b8
|
|
||||||
**********************************************************************/
|
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.os.Handler;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
@@ -22,15 +13,31 @@ import androidx.annotation.Nullable;
|
|||||||
* (non-FGS status notifications are dismissible on Android 13+).
|
* (non-FGS status notifications are dismissible on Android 13+).
|
||||||
*/
|
*/
|
||||||
public class TrayForegroundService extends Service {
|
public class TrayForegroundService extends Service {
|
||||||
|
private static final long HEARTBEAT_MS = 90_000L;
|
||||||
|
|
||||||
|
private final Handler handler = new Handler(Looper.getMainLooper());
|
||||||
|
private final Runnable heartbeat = new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (!CastTrayNotifier.shouldMaintainTray(TrayForegroundService.this)) {
|
||||||
|
stopSelf();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CastTrayNotifier.startForegroundTray(TrayForegroundService.this);
|
||||||
|
handler.postDelayed(this, HEARTBEAT_MS);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
CastTrayNotifier.startForegroundTray(this);
|
CastTrayNotifier.startForegroundTray(this);
|
||||||
|
handler.postDelayed(heartbeat, HEARTBEAT_MS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
if (!AppPreferences.isShowTrayIcon(this)) {
|
if (!CastTrayNotifier.shouldMaintainTray(this)) {
|
||||||
stopSelf();
|
stopSelf();
|
||||||
return START_NOT_STICKY;
|
return START_NOT_STICKY;
|
||||||
}
|
}
|
||||||
@@ -40,6 +47,7 @@ public class TrayForegroundService extends Service {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
|
handler.removeCallbacks(heartbeat);
|
||||||
stopForeground(STOP_FOREGROUND_REMOVE);
|
stopForeground(STOP_FOREGROUND_REMOVE);
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,6 @@ public class TrayNotificationReceiver extends BroadcastReceiver {
|
|||||||
if (!AppPreferences.isShowTrayIcon(context)) {
|
if (!AppPreferences.isShowTrayIcon(context)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CastTrayNotifier.ensureTrayService(context);
|
CastTrayNotifier.refresh(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,19 @@
|
|||||||
package com.foxx.androidcast.dev;
|
package com.foxx.androidcast.dev;
|
||||||
|
|
||||||
import android.app.Notification;
|
|
||||||
import android.app.NotificationChannel;
|
|
||||||
import android.app.NotificationManager;
|
|
||||||
import android.app.PendingIntent;
|
|
||||||
import android.content.ClipData;
|
import android.content.ClipData;
|
||||||
import android.content.ClipboardManager;
|
import android.content.ClipboardManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
import android.net.Network;
|
import android.net.Network;
|
||||||
import android.net.NetworkCapabilities;
|
import android.net.NetworkCapabilities;
|
||||||
import android.net.NetworkRequest;
|
import android.net.NetworkRequest;
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.core.app.NotificationCompat;
|
|
||||||
|
|
||||||
import com.foxx.androidcast.AppPreferences;
|
import com.foxx.androidcast.AppPreferences;
|
||||||
import com.foxx.androidcast.BuildConfig;
|
import com.foxx.androidcast.BuildConfig;
|
||||||
import com.foxx.androidcast.DeveloperSettingsActivity;
|
import com.foxx.androidcast.CastTrayNotifier;
|
||||||
import com.foxx.androidcast.R;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
@@ -33,9 +24,6 @@ import java.util.concurrent.Executors;
|
|||||||
|
|
||||||
/** Debug-only: re-enable adb wifi on network changes and publish connect info. */
|
/** Debug-only: re-enable adb wifi on network changes and publish connect info. */
|
||||||
public final class DevAdbWifiKeeper {
|
public final class DevAdbWifiKeeper {
|
||||||
private static final String CHANNEL_ID = "dev_adb_wifi";
|
|
||||||
private static final int NOTIFICATION_ID = 50391;
|
|
||||||
|
|
||||||
private static final ExecutorService WORKER = Executors.newSingleThreadExecutor(r -> {
|
private static final ExecutorService WORKER = Executors.newSingleThreadExecutor(r -> {
|
||||||
Thread t = new Thread(r, "DevAdbWifiKeeper");
|
Thread t = new Thread(r, "DevAdbWifiKeeper");
|
||||||
t.setDaemon(true);
|
t.setDaemon(true);
|
||||||
@@ -146,7 +134,6 @@ public final class DevAdbWifiKeeper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void start() {
|
private void start() {
|
||||||
ensureNotificationChannel();
|
|
||||||
loadCachedJsonIntoHttpServer();
|
loadCachedJsonIntoHttpServer();
|
||||||
httpServer.start();
|
httpServer.start();
|
||||||
registerNetworkCallback();
|
registerNetworkCallback();
|
||||||
@@ -172,11 +159,7 @@ public final class DevAdbWifiKeeper {
|
|||||||
private void shutdown() {
|
private void shutdown() {
|
||||||
unregisterNetworkCallback();
|
unregisterNetworkCallback();
|
||||||
httpServer.stop();
|
httpServer.stop();
|
||||||
NotificationManager nm =
|
CastTrayNotifier.refresh(appContext);
|
||||||
(NotificationManager) appContext.getSystemService(Context.NOTIFICATION_SERVICE);
|
|
||||||
if (nm != null) {
|
|
||||||
nm.cancel(NOTIFICATION_ID);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerNetworkCallback() {
|
private void registerNetworkCallback() {
|
||||||
@@ -224,7 +207,7 @@ public final class DevAdbWifiKeeper {
|
|||||||
Log.d(DevAdbConnectInfo.LOG_TAG, "refresh reason=" + reason
|
Log.d(DevAdbConnectInfo.LOG_TAG, "refresh reason=" + reason
|
||||||
+ " secure=" + info.secureSettingsGranted
|
+ " secure=" + info.secureSettingsGranted
|
||||||
+ " connect=" + info.connect);
|
+ " connect=" + info.connect);
|
||||||
MAIN.post(() -> updateNotification(info));
|
MAIN.post(() -> CastTrayNotifier.refresh(appContext));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeJsonFile(String json) {
|
private void writeJsonFile(String json) {
|
||||||
@@ -239,53 +222,4 @@ public final class DevAdbWifiKeeper {
|
|||||||
Log.w(DevAdbConnectInfo.LOG_TAG, "write adb_connect.json failed", e);
|
Log.w(DevAdbConnectInfo.LOG_TAG, "write adb_connect.json failed", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ensureNotificationChannel() {
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
NotificationManager nm =
|
|
||||||
(NotificationManager) appContext.getSystemService(Context.NOTIFICATION_SERVICE);
|
|
||||||
if (nm == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
NotificationChannel channel = new NotificationChannel(
|
|
||||||
CHANNEL_ID,
|
|
||||||
appContext.getString(R.string.dev_adb_wifi_notification_channel),
|
|
||||||
NotificationManager.IMPORTANCE_LOW);
|
|
||||||
channel.setDescription(appContext.getString(R.string.dev_adb_wifi_notification_channel_desc));
|
|
||||||
nm.createNotificationChannel(channel);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateNotification(DevAdbConnectInfo info) {
|
|
||||||
if (!AppPreferences.isDevAdbWifiKeeperEnabled(appContext)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
NotificationManager nm =
|
|
||||||
(NotificationManager) appContext.getSystemService(Context.NOTIFICATION_SERVICE);
|
|
||||||
if (nm == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String line = info.primaryConnectLine();
|
|
||||||
String text = line.isEmpty()
|
|
||||||
? appContext.getString(R.string.dev_adb_wifi_notification_waiting)
|
|
||||||
: appContext.getString(R.string.dev_adb_wifi_notification_line, line);
|
|
||||||
Intent openDev = new Intent(appContext, DeveloperSettingsActivity.class);
|
|
||||||
openDev.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
||||||
PendingIntent content = PendingIntent.getActivity(
|
|
||||||
appContext,
|
|
||||||
0,
|
|
||||||
openDev,
|
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
|
||||||
Notification notification = new NotificationCompat.Builder(appContext, CHANNEL_ID)
|
|
||||||
.setSmallIcon(R.drawable.ic_launcher_foreground)
|
|
||||||
.setContentTitle(appContext.getString(R.string.dev_adb_wifi_notification_title))
|
|
||||||
.setContentText(text)
|
|
||||||
.setStyle(new NotificationCompat.BigTextStyle().bigText(text))
|
|
||||||
.setContentIntent(content)
|
|
||||||
.setOngoing(true)
|
|
||||||
.setOnlyAlertOnce(true)
|
|
||||||
.build();
|
|
||||||
nm.notify(NOTIFICATION_ID, notification);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
package com.foxx.androidcast.remoteaccess;
|
package com.foxx.androidcast.remoteaccess;
|
||||||
|
|
||||||
import android.app.AlarmManager;
|
import android.app.AlarmManager;
|
||||||
import android.app.Notification;
|
|
||||||
import android.app.NotificationChannel;
|
|
||||||
import android.app.NotificationManager;
|
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@@ -13,12 +10,9 @@ import android.os.IBinder;
|
|||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.core.app.NotificationCompat;
|
|
||||||
|
|
||||||
import com.foxx.androidcast.AppPreferences;
|
import com.foxx.androidcast.AppPreferences;
|
||||||
import com.foxx.androidcast.DeveloperSettingsActivity;
|
import com.foxx.androidcast.CastTrayNotifier;
|
||||||
import com.foxx.androidcast.DeviceInfo;
|
import com.foxx.androidcast.DeviceInfo;
|
||||||
import com.foxx.androidcast.R;
|
|
||||||
import com.foxx.androidcast.remoteaccess.control.RemoteAccessControlClient;
|
import com.foxx.androidcast.remoteaccess.control.RemoteAccessControlClient;
|
||||||
import com.foxx.androidcast.vpn.VpnServiceClient;
|
import com.foxx.androidcast.vpn.VpnServiceClient;
|
||||||
|
|
||||||
@@ -44,8 +38,6 @@ public final class RemoteAccessService extends Service {
|
|||||||
public static final String ACTION_DISABLE_ONCE = "com.foxx.androidcast.remoteaccess.DISABLE_ONCE";
|
public static final String ACTION_DISABLE_ONCE = "com.foxx.androidcast.remoteaccess.DISABLE_ONCE";
|
||||||
public static final String EXTRA_MODE = "mode";
|
public static final String EXTRA_MODE = "mode";
|
||||||
|
|
||||||
private static final int NOTIF_ID = 0x7a00;
|
|
||||||
private static final String CHANNEL_ID = "remote_access";
|
|
||||||
private static final int MIN_POLL_MS = 60_000;
|
private static final int MIN_POLL_MS = 60_000;
|
||||||
private static final int MAX_POLL_MS = 420_000;
|
private static final int MAX_POLL_MS = 420_000;
|
||||||
|
|
||||||
@@ -102,8 +94,9 @@ public final class RemoteAccessService extends Service {
|
|||||||
if (ACTION_STOP.equals(action)) {
|
if (ACTION_STOP.equals(action)) {
|
||||||
cancelPoll(this);
|
cancelPoll(this);
|
||||||
RemoteAccessTunnelHelper.tearDownAll(this);
|
RemoteAccessTunnelHelper.tearDownAll(this);
|
||||||
stopForeground(true);
|
stopForeground(STOP_FOREGROUND_REMOVE);
|
||||||
stopSelf();
|
stopSelf();
|
||||||
|
CastTrayNotifier.refresh(this);
|
||||||
return START_NOT_STICKY;
|
return START_NOT_STICKY;
|
||||||
}
|
}
|
||||||
if (ACTION_DISABLE_ONCE.equals(action)) {
|
if (ACTION_DISABLE_ONCE.equals(action)) {
|
||||||
@@ -118,8 +111,11 @@ public final class RemoteAccessService extends Service {
|
|||||||
stop(this);
|
stop(this);
|
||||||
return START_NOT_STICKY;
|
return START_NOT_STICKY;
|
||||||
}
|
}
|
||||||
ensureChannel();
|
int fgsType = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
|
||||||
startForeground(NOTIF_ID, buildNotification(mode));
|
? android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE
|
||||||
|
: 0;
|
||||||
|
CastTrayNotifier.startForeground(this, fgsType);
|
||||||
|
CastTrayNotifier.ensureTrayService(this);
|
||||||
loadOrCreateSessionRandom();
|
loadOrCreateSessionRandom();
|
||||||
checkSessionExpiry();
|
checkSessionExpiry();
|
||||||
final RemoteAccessMode pollMode = mode;
|
final RemoteAccessMode pollMode = mode;
|
||||||
@@ -309,22 +305,7 @@ public final class RemoteAccessService extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateNotificationConnected(String sessionId, String tunnel) {
|
private void updateNotificationConnected(String sessionId, String tunnel) {
|
||||||
NotificationManager nm = getSystemService(NotificationManager.class);
|
CastTrayNotifier.refresh(this);
|
||||||
if (nm == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String text = sessionId.isEmpty()
|
|
||||||
? getString(R.string.dev_remote_access_notification_connected)
|
|
||||||
: getString(R.string.dev_remote_access_notification_session, sessionId);
|
|
||||||
if ("ssh_reverse".equals(tunnel)) {
|
|
||||||
text = text + " (RSSH)";
|
|
||||||
}
|
|
||||||
nm.notify(NOTIF_ID, new NotificationCompat.Builder(this, CHANNEL_ID)
|
|
||||||
.setSmallIcon(R.drawable.ic_notification)
|
|
||||||
.setContentTitle(getString(R.string.dev_remote_access_notification_title))
|
|
||||||
.setContentText(text)
|
|
||||||
.setOngoing(true)
|
|
||||||
.build());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void scheduleNextPoll() {
|
private void scheduleNextPoll() {
|
||||||
@@ -355,33 +336,6 @@ public final class RemoteAccessService extends Service {
|
|||||||
am.cancel(pi);
|
am.cancel(pi);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Notification buildNotification(RemoteAccessMode mode) {
|
|
||||||
Intent open = new Intent(this, DeveloperSettingsActivity.class);
|
|
||||||
PendingIntent pi = PendingIntent.getActivity(this, 0, open,
|
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
|
||||||
return new NotificationCompat.Builder(this, CHANNEL_ID)
|
|
||||||
.setSmallIcon(R.drawable.ic_notification)
|
|
||||||
.setContentTitle(getString(R.string.dev_remote_access_notification_title))
|
|
||||||
.setContentText(getString(R.string.dev_remote_access_notification_poll, mode.name()))
|
|
||||||
.setContentIntent(pi)
|
|
||||||
.setOngoing(true)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ensureChannel() {
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
NotificationManager nm = getSystemService(NotificationManager.class);
|
|
||||||
if (nm == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
NotificationChannel ch = new NotificationChannel(CHANNEL_ID,
|
|
||||||
getString(R.string.dev_remote_access_notification_channel),
|
|
||||||
NotificationManager.IMPORTANCE_LOW);
|
|
||||||
nm.createNotificationChannel(ch);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static JSONObject buildDeviceMeta(Context context) throws Exception {
|
private static JSONObject buildDeviceMeta(Context context) throws Exception {
|
||||||
JSONObject device = new JSONObject();
|
JSONObject device = new JSONObject();
|
||||||
device.put("name", AppPreferences.getUsername(context));
|
device.put("name", AppPreferences.getUsername(context));
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package com.foxx.androidcast.remoteaccess;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
|
||||||
|
import com.foxx.androidcast.CastTrayNotifier;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Last remote-access / VPN tunnel event for developer network self-test.
|
* Last remote-access / VPN tunnel event for developer network self-test.
|
||||||
*/
|
*/
|
||||||
@@ -63,6 +65,7 @@ public final class RemoteAccessStatusStore {
|
|||||||
.putString(KEY_SESSION_ID, sessionId != null ? sessionId : "")
|
.putString(KEY_SESSION_ID, sessionId != null ? sessionId : "")
|
||||||
.putLong(KEY_UPDATED_MS, System.currentTimeMillis())
|
.putLong(KEY_UPDATED_MS, System.currentTimeMillis())
|
||||||
.apply();
|
.apply();
|
||||||
|
CastTrayNotifier.refresh(context.getApplicationContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Snapshot load(Context context) {
|
public static Snapshot load(Context context) {
|
||||||
@@ -92,7 +95,7 @@ public final class RemoteAccessStatusStore {
|
|||||||
public final String sessionId;
|
public final String sessionId;
|
||||||
public final long updatedMs;
|
public final long updatedMs;
|
||||||
|
|
||||||
Snapshot(
|
public Snapshot(
|
||||||
String type,
|
String type,
|
||||||
String state,
|
String state,
|
||||||
String method,
|
String method,
|
||||||
|
|||||||
@@ -1,22 +1,15 @@
|
|||||||
package com.foxx.androidcast.vpn;
|
package com.foxx.androidcast.vpn;
|
||||||
|
|
||||||
import android.app.Notification;
|
|
||||||
import android.app.NotificationChannel;
|
|
||||||
import android.app.NotificationManager;
|
|
||||||
import android.app.PendingIntent;
|
|
||||||
import android.app.Service;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.ServiceInfo;
|
||||||
import android.net.VpnService;
|
import android.net.VpnService;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.ParcelFileDescriptor;
|
import android.os.ParcelFileDescriptor;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.core.app.NotificationCompat;
|
import com.foxx.androidcast.CastTrayNotifier;
|
||||||
|
|
||||||
import com.foxx.androidcast.DeveloperSettingsActivity;
|
|
||||||
import com.foxx.androidcast.R;
|
|
||||||
import com.foxx.androidcast.remoteaccess.WireGuardQuickConfig;
|
import com.foxx.androidcast.remoteaccess.WireGuardQuickConfig;
|
||||||
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
@@ -28,9 +21,6 @@ public final class RemoteAccessVpnService extends VpnService {
|
|||||||
public static final String ACTION_DOWN = "com.foxx.androidcast.vpn.VPN_DOWN";
|
public static final String ACTION_DOWN = "com.foxx.androidcast.vpn.VPN_DOWN";
|
||||||
public static final String EXTRA_WG_CONFIG = "wg_config";
|
public static final String EXTRA_WG_CONFIG = "wg_config";
|
||||||
|
|
||||||
private static final int NOTIF_ID = 0x7a01;
|
|
||||||
private static final String CHANNEL_ID = "remote_access_vpn";
|
|
||||||
|
|
||||||
private ParcelFileDescriptor tun;
|
private ParcelFileDescriptor tun;
|
||||||
|
|
||||||
public static boolean startWithConfig(Context context, String wgConfig) {
|
public static boolean startWithConfig(Context context, String wgConfig) {
|
||||||
@@ -61,14 +51,18 @@ public final class RemoteAccessVpnService extends VpnService {
|
|||||||
String action = intent != null ? intent.getAction() : null;
|
String action = intent != null ? intent.getAction() : null;
|
||||||
if (ACTION_DOWN.equals(action)) {
|
if (ACTION_DOWN.equals(action)) {
|
||||||
tearDown();
|
tearDown();
|
||||||
stopForeground(true);
|
stopForeground(STOP_FOREGROUND_REMOVE);
|
||||||
stopSelf();
|
stopSelf();
|
||||||
|
CastTrayNotifier.refresh(this);
|
||||||
return START_NOT_STICKY;
|
return START_NOT_STICKY;
|
||||||
}
|
}
|
||||||
if (ACTION_UP.equals(action)) {
|
if (ACTION_UP.equals(action)) {
|
||||||
String cfg = intent.getStringExtra(EXTRA_WG_CONFIG);
|
String cfg = intent.getStringExtra(EXTRA_WG_CONFIG);
|
||||||
ensureChannel();
|
int fgsType = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
|
||||||
startForeground(NOTIF_ID, buildNotification("WireGuard remote access"));
|
? ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE
|
||||||
|
: 0;
|
||||||
|
CastTrayNotifier.startForeground(this, fgsType);
|
||||||
|
CastTrayNotifier.ensureTrayService(this);
|
||||||
establishTun(cfg);
|
establishTun(cfg);
|
||||||
return START_STICKY;
|
return START_STICKY;
|
||||||
}
|
}
|
||||||
@@ -102,6 +96,7 @@ public final class RemoteAccessVpnService extends VpnService {
|
|||||||
b.setMtu(1280);
|
b.setMtu(1280);
|
||||||
tun = b.establish();
|
tun = b.establish();
|
||||||
Log.i(TAG, "VPN interface established");
|
Log.i(TAG, "VPN interface established");
|
||||||
|
CastTrayNotifier.refresh(this);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, "VPN establish failed", e);
|
Log.e(TAG, "VPN establish failed", e);
|
||||||
}
|
}
|
||||||
@@ -117,33 +112,6 @@ public final class RemoteAccessVpnService extends VpnService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Notification buildNotification(String text) {
|
|
||||||
Intent open = new Intent(this, DeveloperSettingsActivity.class);
|
|
||||||
PendingIntent pi = PendingIntent.getActivity(this, 0, open,
|
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
|
||||||
return new NotificationCompat.Builder(this, CHANNEL_ID)
|
|
||||||
.setSmallIcon(R.drawable.ic_notification)
|
|
||||||
.setContentTitle(getString(R.string.dev_remote_access_notification_title))
|
|
||||||
.setContentText(text)
|
|
||||||
.setContentIntent(pi)
|
|
||||||
.setOngoing(true)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ensureChannel() {
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
NotificationManager nm = getSystemService(NotificationManager.class);
|
|
||||||
if (nm == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
NotificationChannel ch = new NotificationChannel(CHANNEL_ID,
|
|
||||||
getString(R.string.dev_remote_access_notification_channel),
|
|
||||||
NotificationManager.IMPORTANCE_LOW);
|
|
||||||
nm.createNotificationChannel(ch);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
tearDown();
|
tearDown();
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package com.foxx.androidcast;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import com.foxx.androidcast.remoteaccess.RemoteAccessMode;
|
||||||
|
import com.foxx.androidcast.remoteaccess.RemoteAccessStatusStore;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class CastTrayContentTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void prodIdle_showsTapToOpen() {
|
||||||
|
CastTrayContent.Body body = CastTrayContent.build(
|
||||||
|
"Tap to open",
|
||||||
|
"Cast active",
|
||||||
|
"Waiting for Wi‑Fi",
|
||||||
|
"adb connect %1$s",
|
||||||
|
"Polling BE (%1$s)",
|
||||||
|
"Connected",
|
||||||
|
"Session %1$s",
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
RemoteAccessMode.DISABLED,
|
||||||
|
null);
|
||||||
|
assertEquals("Tap to open", body.summary);
|
||||||
|
assertEquals("Tap to open", body.bigText);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void casting_overridesDevLines() {
|
||||||
|
CastTrayContent.Body body = CastTrayContent.build(
|
||||||
|
"Tap to open",
|
||||||
|
"Cast active",
|
||||||
|
"Waiting",
|
||||||
|
"adb %1$s",
|
||||||
|
"Poll %1$s",
|
||||||
|
"Connected",
|
||||||
|
"Session %1$s",
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
"192.168.1.1:5555",
|
||||||
|
RemoteAccessMode.RSSH,
|
||||||
|
snapshot(RemoteAccessStatusStore.STATE_WAIT, ""));
|
||||||
|
assertEquals("Cast active", body.summary);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void devMode_combinesRaAndAdbLines() {
|
||||||
|
RemoteAccessStatusStore.Snapshot snap =
|
||||||
|
snapshot(RemoteAccessStatusStore.STATE_WAIT, "");
|
||||||
|
CastTrayContent.Body body = CastTrayContent.build(
|
||||||
|
"Tap to open",
|
||||||
|
"Cast active",
|
||||||
|
"Waiting for Wi‑Fi",
|
||||||
|
"adb connect %1$s",
|
||||||
|
"Polling BE (%1$s)",
|
||||||
|
"Connected",
|
||||||
|
"Session %1$s",
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
"10.0.0.5:5555",
|
||||||
|
RemoteAccessMode.RSSH,
|
||||||
|
snap);
|
||||||
|
assertEquals("Polling BE (RSSH)", body.summary);
|
||||||
|
assertTrue(body.bigText.contains("Polling BE (RSSH)"));
|
||||||
|
assertTrue(body.bigText.contains("adb connect 10.0.0.5:5555"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void remoteAccess_connectedSession_appendsRsshTag() {
|
||||||
|
RemoteAccessStatusStore.Snapshot snap =
|
||||||
|
snapshot(RemoteAccessStatusStore.STATE_SUCCESS, "abc-123");
|
||||||
|
String line = CastTrayContent.remoteAccessLine(
|
||||||
|
"Polling BE (%1$s)",
|
||||||
|
"Connected",
|
||||||
|
"Connected · session %1$s",
|
||||||
|
RemoteAccessMode.RSSH,
|
||||||
|
snap);
|
||||||
|
assertEquals("Connected · session abc-123 (RSSH)", line);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RemoteAccessStatusStore.Snapshot snapshot(String state, String sessionId) {
|
||||||
|
return new RemoteAccessStatusStore.Snapshot(
|
||||||
|
"rssh", state, "", "", "", "", sessionId, 0L);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user