mirror of
git://f0xx.org/android_cast
synced 2026-07-29 05:58:14 +03:00
Add Diagnostics tab to developer settings with async probes.
Split settings into Settings and Diagnostics tabs; run background read-only tests for codecs, camera/audio modes, network reachability, and casting/display extras. DevDiagnosticsReport.toJson() prepares future BE upload. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -35,11 +35,16 @@ import com.foxx.androidcast.receiver.av.ReceiverAudioPreset;
|
||||
import com.foxx.androidcast.receiver.av.ReceiverAvRuntime;
|
||||
import com.foxx.androidcast.receiver.av.ReceiverVideoPreset;
|
||||
import com.foxx.androidcast.sender.SenderResolutionUiMode;
|
||||
import com.foxx.androidcast.dev.DevDiagnosticsController;
|
||||
import com.foxx.androidcast.stats.ui.SessionStatsAnalyzerActivity;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import com.google.android.material.textfield.TextInputEditText;
|
||||
|
||||
/** Hidden developer settings (opened via app-info area easter egg). */
|
||||
public class DeveloperSettingsActivity extends AppCompatActivity {
|
||||
private DevDiagnosticsController diagnosticsController;
|
||||
private android.view.View settingsPanel;
|
||||
private android.view.View diagnosticsPanel;
|
||||
private Spinner videoPresetSpinner;
|
||||
private Spinner audioPresetSpinner;
|
||||
private SeekBar intensitySeek;
|
||||
@@ -57,6 +62,11 @@ public class DeveloperSettingsActivity extends AppCompatActivity {
|
||||
setContentView(R.layout.activity_developer_settings);
|
||||
setTitle(R.string.developer_settings_title);
|
||||
|
||||
settingsPanel = findViewById(R.id.dev_panel_settings);
|
||||
diagnosticsPanel = findViewById(R.id.dev_panel_diagnostics);
|
||||
diagnosticsController = new DevDiagnosticsController(this);
|
||||
bindDeveloperTabs();
|
||||
|
||||
CheckBox debugOverlay = findViewById(R.id.check_receiver_debug_overlay);
|
||||
CheckBox codecScores = findViewById(R.id.check_show_codec_priority_scores);
|
||||
CheckBox sessionStats = findViewById(R.id.check_grab_session_stats);
|
||||
@@ -94,6 +104,14 @@ public class DeveloperSettingsActivity extends AppCompatActivity {
|
||||
bindCommercialSection();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
if (diagnosticsController != null) {
|
||||
diagnosticsController.onDestroy();
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
saveAvPresets();
|
||||
@@ -378,6 +396,36 @@ public class DeveloperSettingsActivity extends AppCompatActivity {
|
||||
void write(Context context, boolean value);
|
||||
}
|
||||
|
||||
private void bindDeveloperTabs() {
|
||||
TabLayout tabs = findViewById(R.id.dev_settings_tabs);
|
||||
if (tabs == null) {
|
||||
return;
|
||||
}
|
||||
tabs.addTab(tabs.newTab().setText(R.string.dev_tab_settings));
|
||||
tabs.addTab(tabs.newTab().setText(R.string.dev_tab_diagnostics));
|
||||
tabs.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
|
||||
@Override
|
||||
public void onTabSelected(TabLayout.Tab tab) {
|
||||
boolean diagnostics = tab.getPosition() == 1;
|
||||
settingsPanel.setVisibility(diagnostics ? android.view.View.GONE : android.view.View.VISIBLE);
|
||||
diagnosticsPanel.setVisibility(diagnostics ? android.view.View.VISIBLE : android.view.View.GONE);
|
||||
if (diagnostics) {
|
||||
diagnosticsController.onDiagnosticsTabShown();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTabUnselected(TabLayout.Tab tab) {}
|
||||
|
||||
@Override
|
||||
public void onTabReselected(TabLayout.Tab tab) {
|
||||
if (tab.getPosition() == 1) {
|
||||
diagnosticsController.onDiagnosticsTabShown();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void bindDeveloperCheckbox(CheckBox box, boolean checked, BoolPrefWriter writer) {
|
||||
if (box == null) {
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
package com.foxx.androidcast.dev;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.foxx.androidcast.R;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/** Runs async diagnostics and binds results to the Diagnostics tab cards. */
|
||||
public final class DevDiagnosticsController {
|
||||
public enum Section {
|
||||
HW(R.id.dev_diag_hw, R.string.dev_diag_hw_title),
|
||||
CAMERA(R.id.dev_diag_camera, R.string.dev_diag_camera_title),
|
||||
AUDIO(R.id.dev_diag_audio, R.string.dev_diag_audio_title),
|
||||
NETWORK(R.id.dev_diag_network, R.string.dev_diag_network_title),
|
||||
CASTING(R.id.dev_diag_casting, R.string.dev_diag_casting_title);
|
||||
|
||||
final int includeId;
|
||||
final int titleRes;
|
||||
|
||||
Section(int includeId, int titleRes) {
|
||||
this.includeId = includeId;
|
||||
this.titleRes = titleRes;
|
||||
}
|
||||
}
|
||||
|
||||
private final AppCompatActivity activity;
|
||||
private final View diagnosticsRoot;
|
||||
private final Map<Section, CardViews> cards = new LinkedHashMap<>();
|
||||
private final ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
private final Handler main = new Handler(Looper.getMainLooper());
|
||||
private final AtomicBoolean cancel = new AtomicBoolean(false);
|
||||
private final AtomicBoolean running = new AtomicBoolean(false);
|
||||
|
||||
private DevDiagnosticsReport lastReport;
|
||||
|
||||
public DevDiagnosticsController(AppCompatActivity activity) {
|
||||
this.activity = activity;
|
||||
this.diagnosticsRoot = activity.findViewById(R.id.dev_panel_diagnostics);
|
||||
for (Section s : Section.values()) {
|
||||
View cardRoot = diagnosticsRoot.findViewById(s.includeId);
|
||||
TextView title = cardRoot.findViewById(R.id.dev_diag_card_title);
|
||||
TextView body = cardRoot.findViewById(R.id.dev_diag_card_body);
|
||||
ProgressBar spinner = cardRoot.findViewById(R.id.dev_diag_card_spinner);
|
||||
title.setText(activity.getString(s.titleRes));
|
||||
body.setText(R.string.dev_diag_pending);
|
||||
cards.put(s, new CardViews(activity, body, spinner));
|
||||
}
|
||||
Button stop = diagnosticsRoot.findViewById(R.id.btn_dev_diag_stop);
|
||||
stop.setOnClickListener(v -> stopTests());
|
||||
}
|
||||
|
||||
public void onDiagnosticsTabShown() {
|
||||
if (!running.get()) {
|
||||
startTests();
|
||||
}
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
cancel.set(true);
|
||||
executor.shutdownNow();
|
||||
}
|
||||
|
||||
public void stopTests() {
|
||||
cancel.set(true);
|
||||
}
|
||||
|
||||
/** Last completed report (for future BE upload). */
|
||||
public DevDiagnosticsReport getLastReport() {
|
||||
return lastReport;
|
||||
}
|
||||
|
||||
private void startTests() {
|
||||
cancel.set(false);
|
||||
running.set(true);
|
||||
for (CardViews c : cards.values()) {
|
||||
c.showRunning();
|
||||
}
|
||||
executor.execute(this::runSuite);
|
||||
}
|
||||
|
||||
private void runSuite() {
|
||||
Map<String, String> sectionText = new LinkedHashMap<>();
|
||||
try {
|
||||
runSection(Section.HW, () ->
|
||||
DevDiagnosticsProbe.probeHardwareAcceleration(activity), sectionText);
|
||||
runSection(Section.CAMERA, () ->
|
||||
DevDiagnosticsProbe.probeCameraCapture(activity), sectionText);
|
||||
runSection(Section.AUDIO, () ->
|
||||
DevDiagnosticsProbe.probeAudioCapture(activity), sectionText);
|
||||
runSection(Section.NETWORK, () ->
|
||||
DevDiagnosticsProbe.probeNetwork(activity, cancel), sectionText);
|
||||
runSection(Section.CASTING, () ->
|
||||
DevDiagnosticsProbe.probeCastingExtras(activity), sectionText);
|
||||
} finally {
|
||||
if (!cancel.get()) {
|
||||
lastReport = new DevDiagnosticsReport(System.currentTimeMillis(), sectionText);
|
||||
}
|
||||
main.post(() -> {
|
||||
running.set(false);
|
||||
if (cancel.get()) {
|
||||
for (CardViews c : cards.values()) {
|
||||
c.showStopped();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void runSection(Section section, Probe probe, Map<String, String> out) {
|
||||
if (cancel.get()) {
|
||||
main.post(() -> cards.get(section).showStopped());
|
||||
return;
|
||||
}
|
||||
main.post(() -> cards.get(section).showRunning());
|
||||
String text;
|
||||
try {
|
||||
text = probe.run();
|
||||
} catch (Exception e) {
|
||||
text = "Error: " + e.getMessage();
|
||||
}
|
||||
if (cancel.get()) {
|
||||
main.post(() -> cards.get(section).showStopped());
|
||||
return;
|
||||
}
|
||||
out.put(section.name().toLowerCase(Locale.ROOT), text);
|
||||
String finalText = text;
|
||||
main.post(() -> cards.get(section).showResult(finalText));
|
||||
}
|
||||
|
||||
private interface Probe {
|
||||
String run();
|
||||
}
|
||||
|
||||
private static final class CardViews {
|
||||
private final AppCompatActivity activity;
|
||||
private final TextView body;
|
||||
private final ProgressBar spinner;
|
||||
|
||||
CardViews(AppCompatActivity activity, TextView body, ProgressBar spinner) {
|
||||
this.activity = activity;
|
||||
this.body = body;
|
||||
this.spinner = spinner;
|
||||
}
|
||||
|
||||
void showRunning() {
|
||||
spinner.setVisibility(View.VISIBLE);
|
||||
body.setText(activity.getString(R.string.dev_diag_running));
|
||||
}
|
||||
|
||||
void showResult(String text) {
|
||||
spinner.setVisibility(View.GONE);
|
||||
body.setText(text);
|
||||
}
|
||||
|
||||
void showStopped() {
|
||||
spinner.setVisibility(View.GONE);
|
||||
body.setText(activity.getString(R.string.dev_diag_stopped));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
package com.foxx.androidcast.dev;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.hardware.camera2.CameraAccessException;
|
||||
import android.hardware.camera2.CameraCharacteristics;
|
||||
import android.hardware.camera2.CameraManager;
|
||||
import android.hardware.camera2.params.StreamConfigurationMap;
|
||||
import android.media.MediaCodecInfo;
|
||||
import android.media.MediaCodecList;
|
||||
import android.media.MediaFormat;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.util.Size;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.foxx.androidcast.AppPreferences;
|
||||
import com.foxx.androidcast.BuildConfig;
|
||||
import com.foxx.androidcast.PlatformCastSupport;
|
||||
import com.foxx.androidcast.crash.CrashSettingsStore;
|
||||
import com.foxx.androidcast.display.WiredDisplayMonitor;
|
||||
import com.foxx.androidcast.media.codec.jni.NativeCodecBridge;
|
||||
import com.foxx.androidcast.sender.CodecCatalog;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/** Read-only device probes for developer diagnostics (no user interaction). */
|
||||
public final class DevDiagnosticsProbe {
|
||||
private static final int HTTP_TIMEOUT_MS = 6_000;
|
||||
|
||||
private DevDiagnosticsProbe() {}
|
||||
|
||||
public static String probeHardwareAcceleration(Context context) {
|
||||
CodecCatalog.invalidateCapabilities();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("API ").append(Build.VERSION.SDK_INT).append('\n');
|
||||
sb.append("libvpx JNI: ").append(NativeCodecBridge.isLibvpxAvailable() ? "yes" : "no").append('\n');
|
||||
sb.append('\n').append("Video encoders:\n");
|
||||
appendVideoCodecLines(sb, true);
|
||||
sb.append('\n').append("Video decoders:\n");
|
||||
appendVideoCodecLines(sb, false);
|
||||
sb.append('\n').append("MIME summary (catalog):\n");
|
||||
sb.append(" advertised enc: ").append(join(CodecCatalog.localEncoderMimes())).append('\n');
|
||||
sb.append(" advertised dec: ").append(join(CodecCatalog.localDecoderMimes()));
|
||||
return sb.toString().trim();
|
||||
}
|
||||
|
||||
public static String probeCameraCapture(Context context) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("Camera2 capture modes (cast settings):\n");
|
||||
List<String> modes = new ArrayList<>();
|
||||
modes.add(context.getString(com.foxx.androidcast.R.string.capture_full_screen));
|
||||
if (PlatformCastSupport.isCaptureUserChoiceSupported()) {
|
||||
modes.add(context.getString(com.foxx.androidcast.R.string.capture_user_choice));
|
||||
} else {
|
||||
modes.add(context.getString(com.foxx.androidcast.R.string.capture_user_choice_unavailable,
|
||||
Build.VERSION.SDK_INT));
|
||||
}
|
||||
if (PlatformCastSupport.isCameraCaptureModeSupported()) {
|
||||
modes.add(context.getString(com.foxx.androidcast.R.string.capture_camera_rear));
|
||||
if (PlatformCastSupport.hasFrontCamera(context)) {
|
||||
modes.add(context.getString(com.foxx.androidcast.R.string.capture_camera_front));
|
||||
}
|
||||
} else {
|
||||
modes.add(context.getString(com.foxx.androidcast.R.string.capture_camera_unavailable));
|
||||
}
|
||||
modes.add(context.getString(com.foxx.androidcast.R.string.capture_calibration_test));
|
||||
for (String m : modes) {
|
||||
sb.append(" • ").append(m).append('\n');
|
||||
}
|
||||
sb.append('\n').append("Physical cameras:\n");
|
||||
appendCameraDevices(context, sb);
|
||||
return sb.toString().trim();
|
||||
}
|
||||
|
||||
public static String probeAudioCapture(Context context) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean api = PlatformCastSupport.isAudioCaptureSupported();
|
||||
sb.append("Playback capture (MediaProjection): ")
|
||||
.append(api ? "supported (API 29+)" : "unavailable").append('\n');
|
||||
boolean micPerm = ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO)
|
||||
== PackageManager.PERMISSION_GRANTED;
|
||||
sb.append("RECORD_AUDIO granted: ").append(micPerm ? "yes" : "no").append('\n');
|
||||
sb.append('\n').append("Runtime modes (AudioCapture):\n");
|
||||
if (!api) {
|
||||
sb.append(" • (none — API < 29)\n");
|
||||
} else {
|
||||
sb.append(" • playback\n");
|
||||
sb.append(" • playback+mic (when mic available)\n");
|
||||
sb.append(" • mic (calibration / no projection)\n");
|
||||
}
|
||||
sb.append('\n').append("Voice codecs (settings):\n");
|
||||
for (CodecCatalog.AudioCodecChoice c : CodecCatalog.audioCodecChoicesForUi()) {
|
||||
sb.append(" • ").append(c.codec.name())
|
||||
.append(c.selectable ? "" : " (N/A)")
|
||||
.append('\n');
|
||||
}
|
||||
return sb.toString().trim();
|
||||
}
|
||||
|
||||
public static String probeNetwork(Context context, AtomicBoolean cancel) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(connectivitySummary(context)).append('\n');
|
||||
String crashUrl = resolveCrashUploadUrl(context);
|
||||
sb.append('\n').append(probeHttp("Crash upload", crashUrl, cancel)).append('\n');
|
||||
String otaUrl = resolveOtaUrl(context);
|
||||
if (!otaUrl.isEmpty()) {
|
||||
sb.append('\n').append(probeHttp("OTA channel", otaUrl, cancel));
|
||||
}
|
||||
return sb.toString().trim();
|
||||
}
|
||||
|
||||
public static String probeCastingExtras(Context context) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
PackageManager pm = context.getPackageManager();
|
||||
sb.append("Wi‑Fi Direct (OS): ")
|
||||
.append(pm.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT) ? "yes" : "no")
|
||||
.append('\n');
|
||||
sb.append("Miracast / Wi‑Fi Display: ")
|
||||
.append(miracastSummary(pm))
|
||||
.append(" (in-app cast uses MediaProjection, not WFD)\n");
|
||||
WiredDisplayMonitor monitor = WiredDisplayMonitor.getInstance(context);
|
||||
monitor.ensureRegistered();
|
||||
sb.append('\n').append("HDMI / external display:\n ")
|
||||
.append(monitor.getSummaryLine().replace("\n", "\n "))
|
||||
.append('\n');
|
||||
sb.append('\n').append("MediaProjection screen capture: ")
|
||||
.append(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? "available" : "N/A")
|
||||
.append('\n');
|
||||
sb.append("App/screen picker (API 34+): ")
|
||||
.append(PlatformCastSupport.isCaptureUserChoiceSupported() ? "yes" : "no")
|
||||
.append('\n');
|
||||
sb.append("Google Cast resolver: ")
|
||||
.append(resolveCastIntent(pm) ? "activity found" : "none")
|
||||
.append('\n');
|
||||
sb.append("Nearby / Share resolver: ")
|
||||
.append(resolveNearbyShare(pm) ? "activity found" : "none");
|
||||
return sb.toString().trim();
|
||||
}
|
||||
|
||||
private static void appendVideoCodecLines(StringBuilder sb, boolean encoder) {
|
||||
List<String> lines = new ArrayList<>();
|
||||
MediaCodecList list = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
|
||||
for (MediaCodecInfo info : list.getCodecInfos()) {
|
||||
if (info.isEncoder() != encoder) {
|
||||
continue;
|
||||
}
|
||||
boolean hw = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && info.isHardwareAccelerated();
|
||||
for (String type : info.getSupportedTypes()) {
|
||||
if (type == null || !type.startsWith("video/")) {
|
||||
continue;
|
||||
}
|
||||
lines.add(String.format(Locale.US, " %s [%s] %s", type, hw ? "HW" : "SW", info.getName()));
|
||||
}
|
||||
}
|
||||
if (lines.isEmpty()) {
|
||||
sb.append(" (none)\n");
|
||||
return;
|
||||
}
|
||||
for (String line : lines) {
|
||||
sb.append(line).append('\n');
|
||||
}
|
||||
}
|
||||
|
||||
private static void appendCameraDevices(Context context, StringBuilder sb) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
sb.append(" (Camera2 modes need API 29+)\n");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
CameraManager mgr = context.getSystemService(CameraManager.class);
|
||||
if (mgr == null) {
|
||||
sb.append(" (no CameraManager)\n");
|
||||
return;
|
||||
}
|
||||
for (String id : mgr.getCameraIdList()) {
|
||||
CameraCharacteristics ch = mgr.getCameraCharacteristics(id);
|
||||
Integer facing = ch.get(CameraCharacteristics.LENS_FACING);
|
||||
String facingStr = facing == null ? "?" :
|
||||
facing == CameraCharacteristics.LENS_FACING_FRONT ? "front" :
|
||||
facing == CameraCharacteristics.LENS_FACING_BACK ? "back" : "other";
|
||||
StreamConfigurationMap map = ch.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
|
||||
int outputs = 0;
|
||||
if (map != null) {
|
||||
Size[] sizes = map.getOutputSizes(android.graphics.ImageFormat.YUV_420_888);
|
||||
outputs = sizes != null ? sizes.length : 0;
|
||||
}
|
||||
sb.append(" • id=").append(id)
|
||||
.append(" ").append(facingStr)
|
||||
.append(", YUV outputs=").append(outputs)
|
||||
.append('\n');
|
||||
}
|
||||
} catch (CameraAccessException | SecurityException e) {
|
||||
sb.append(" Error: ").append(e.getMessage()).append('\n');
|
||||
}
|
||||
}
|
||||
|
||||
private static String connectivitySummary(Context context) {
|
||||
ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
|
||||
if (cm == null) {
|
||||
return "Connectivity: unknown";
|
||||
}
|
||||
android.net.Network net = cm.getActiveNetwork();
|
||||
if (net == null) {
|
||||
return "Connectivity: no active network";
|
||||
}
|
||||
NetworkCapabilities caps = cm.getNetworkCapabilities(net);
|
||||
if (caps == null) {
|
||||
return "Connectivity: active network (no caps)";
|
||||
}
|
||||
List<String> parts = new ArrayList<>();
|
||||
if (caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
|
||||
parts.add("Wi‑Fi");
|
||||
}
|
||||
if (caps.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
|
||||
parts.add("cellular");
|
||||
}
|
||||
if (caps.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) {
|
||||
parts.add("ethernet");
|
||||
}
|
||||
if (caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
|
||||
parts.add("internet");
|
||||
}
|
||||
return "Connectivity: " + (parts.isEmpty() ? "other" : String.join(", ", parts));
|
||||
}
|
||||
|
||||
private static String probeHttp(String label, String url, AtomicBoolean cancel) {
|
||||
if (cancel != null && cancel.get()) {
|
||||
return label + ": stopped";
|
||||
}
|
||||
if (url == null || url.trim().isEmpty()) {
|
||||
return label + ": (no URL configured)";
|
||||
}
|
||||
String trimmed = url.trim();
|
||||
long t0 = System.currentTimeMillis();
|
||||
try {
|
||||
String host = Uri.parse(trimmed).getHost();
|
||||
if (host != null && !host.isEmpty()) {
|
||||
InetAddress addr = InetAddress.getByName(host);
|
||||
long dnsMs = System.currentTimeMillis() - t0;
|
||||
if (cancel != null && cancel.get()) {
|
||||
return label + ": stopped";
|
||||
}
|
||||
HttpURLConnection conn = (HttpURLConnection) new URL(trimmed).openConnection();
|
||||
conn.setConnectTimeout(HTTP_TIMEOUT_MS);
|
||||
conn.setReadTimeout(HTTP_TIMEOUT_MS);
|
||||
conn.setRequestMethod("HEAD");
|
||||
conn.setInstanceFollowRedirects(true);
|
||||
int code = conn.getResponseCode();
|
||||
long totalMs = System.currentTimeMillis() - t0;
|
||||
conn.disconnect();
|
||||
return label + ": HTTP " + code + ", DNS " + dnsMs + " ms, total " + totalMs + " ms"
|
||||
+ " (" + addr.getHostAddress() + ")";
|
||||
}
|
||||
return label + ": invalid URL";
|
||||
} catch (IOException e) {
|
||||
return label + ": fail — " + e.getClass().getSimpleName() + ": " + e.getMessage()
|
||||
+ " (" + (System.currentTimeMillis() - t0) + " ms)";
|
||||
}
|
||||
}
|
||||
|
||||
private static String resolveCrashUploadUrl(Context context) {
|
||||
return CrashSettingsStore.load(context).uploadUrl;
|
||||
}
|
||||
|
||||
private static String resolveOtaUrl(Context context) {
|
||||
String url = AppPreferences.getOtaChannelUrl(context);
|
||||
if (url.isEmpty() && BuildConfig.OTA_CHANNEL_URL_DEFAULT != null) {
|
||||
url = BuildConfig.OTA_CHANNEL_URL_DEFAULT.trim();
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
private static String miracastSummary(PackageManager pm) {
|
||||
if (pm.hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)) {
|
||||
return "likely via OS Wi‑Fi Display stack";
|
||||
}
|
||||
return "not reported";
|
||||
}
|
||||
|
||||
private static boolean resolveCastIntent(PackageManager pm) {
|
||||
Intent i = new Intent("android.settings.CAST_SETTINGS");
|
||||
return i.resolveActivity(pm) != null;
|
||||
}
|
||||
|
||||
private static boolean resolveNearbyShare(PackageManager pm) {
|
||||
Intent i = new Intent(Intent.ACTION_SEND);
|
||||
i.setType("video/*");
|
||||
return i.resolveActivity(pm) != null;
|
||||
}
|
||||
|
||||
private static String join(List<String> list) {
|
||||
if (list == null || list.isEmpty()) {
|
||||
return "(none)";
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
if (i > 0) {
|
||||
sb.append(", ");
|
||||
}
|
||||
sb.append(list.get(i));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.foxx.androidcast.dev;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* In-memory diagnostics snapshot for developer settings.
|
||||
* {@link #toJson()} is ready for a future BE upload endpoint (not wired yet).
|
||||
*/
|
||||
public final class DevDiagnosticsReport {
|
||||
public final long collectedAtEpochMs;
|
||||
public final String deviceModel;
|
||||
public final int sdkInt;
|
||||
public final Map<String, String> sections;
|
||||
|
||||
public DevDiagnosticsReport(long collectedAtEpochMs, Map<String, String> sections) {
|
||||
this.collectedAtEpochMs = collectedAtEpochMs;
|
||||
this.deviceModel = Build.MANUFACTURER + " " + Build.MODEL;
|
||||
this.sdkInt = Build.VERSION.SDK_INT;
|
||||
this.sections = new LinkedHashMap<>(sections);
|
||||
}
|
||||
|
||||
public JSONObject toJson() {
|
||||
JSONObject root = new JSONObject();
|
||||
try {
|
||||
root.put("schema_version", 1);
|
||||
root.put("kind", "dev_diagnostics");
|
||||
root.put("collected_at_ms", collectedAtEpochMs);
|
||||
root.put("device_model", deviceModel);
|
||||
root.put("sdk_int", sdkInt);
|
||||
JSONObject body = new JSONObject();
|
||||
for (Map.Entry<String, String> e : sections.entrySet()) {
|
||||
body.put(e.getKey(), e.getValue());
|
||||
}
|
||||
root.put("sections", body);
|
||||
root.put("sections_array", sectionsToArray());
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
private JSONArray sectionsToArray() {
|
||||
JSONArray arr = new JSONArray();
|
||||
for (Map.Entry<String, String> e : sections.entrySet()) {
|
||||
JSONObject item = new JSONObject();
|
||||
try {
|
||||
item.put("id", e.getKey());
|
||||
item.put("text", e.getValue());
|
||||
arr.put(item);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
}
|
||||
57
app/src/main/res/layout/activity_dev_diagnostics_panel.xml
Normal file
57
app/src/main/res/layout/activity_dev_diagnostics_panel.xml
Normal file
@@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/dev_diag_intro"
|
||||
android:textSize="13sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_dev_diag_stop"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/dev_diag_stop" />
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_weight="1"
|
||||
android:fillViewport="true">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/dev_diag_cards"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<include
|
||||
android:id="@+id/dev_diag_hw"
|
||||
layout="@layout/view_dev_diag_card" />
|
||||
|
||||
<include
|
||||
android:id="@+id/dev_diag_camera"
|
||||
layout="@layout/view_dev_diag_card" />
|
||||
|
||||
<include
|
||||
android:id="@+id/dev_diag_audio"
|
||||
layout="@layout/view_dev_diag_card" />
|
||||
|
||||
<include
|
||||
android:id="@+id/dev_diag_network"
|
||||
layout="@layout/view_dev_diag_card" />
|
||||
|
||||
<include
|
||||
android:id="@+id/dev_diag_casting"
|
||||
layout="@layout/view_dev_diag_card" />
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</LinearLayout>
|
||||
315
app/src/main/res/layout/activity_dev_settings_panel.xml
Normal file
315
app/src/main/res/layout/activity_dev_settings_panel.xml
Normal file
@@ -0,0 +1,315 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/check_receiver_debug_overlay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/dev_show_receiver_debug_overlay" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/check_show_codec_priority_scores"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/dev_show_codec_priority_scores" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/check_grab_session_stats"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/dev_grab_session_stats" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/check_receiver_adaptive_display"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/dev_receiver_adaptive_display" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="28dp"
|
||||
android:text="@string/dev_sender_resolution_ui_section"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/dev_sender_resolution_ui_mode"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinner_dev_sender_resolution_ui_mode"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/dev_sender_resolution_ui_hint"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="28dp"
|
||||
android:text="@string/dev_av_presets_section"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/dev_av_video_preset"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinner_dev_video_preset"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/dev_av_audio_preset"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinner_dev_audio_preset"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/label_dev_av_intensity"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/dev_av_intensity"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/seek_dev_av_intensity"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:max="10"
|
||||
android:progress="5" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_dev_av_intensity_value"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/dev_av_presets_hint"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_open_session_stats"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:text="@string/session_stats_open" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_reload_codecs_json"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/dev_reload_codecs_json" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/dev_reload_codecs_json_hint"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="28dp"
|
||||
android:text="@string/ota_section_title"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_ota_installed_version"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textSize="13sp" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:hint="@string/ota_manifest_url_hint">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/edit_ota_manifest_url"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textUri" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/check_ota_on_launch"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:checked="true"
|
||||
android:text="@string/ota_check_on_launch" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_check_ota"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/ota_check_now" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/ota_section_hint"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="28dp"
|
||||
android:text="@string/dev_wired_display_section"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_wired_display_status"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textSize="13sp" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/check_wired_display_hints"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/dev_wired_display_hints" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/dev_external_capture_policy"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinner_external_capture_policy"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_external_capture_policy_note"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/check_usb_tether_transport"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/dev_usb_tether_transport" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/dev_wired_display_hint"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="28dp"
|
||||
android:text="@string/dev_commercial_section"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/check_commercial_ads"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/dev_commercial_ads" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/check_commercial_iap"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/dev_commercial_iap" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/check_commercial_play_store"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/dev_commercial_play_store" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_open_play_store_listing"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/dev_open_play_store_listing" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_request_play_review"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/dev_request_play_review" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_query_iap_products"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/dev_query_iap_products" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/dev_commercial_hint"
|
||||
android:textSize="12sp" />
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
@@ -1,323 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="true">
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/dev_settings_tabs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp">
|
||||
app:tabMode="fixed"
|
||||
app:tabGravity="fill" />
|
||||
|
||||
<TextView
|
||||
<FrameLayout
|
||||
android:id="@+id/dev_settings_tab_host"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/developer_settings_title"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/check_receiver_debug_overlay"
|
||||
<include
|
||||
android:id="@+id/dev_panel_settings"
|
||||
layout="@layout/activity_dev_settings_panel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:text="@string/dev_show_receiver_debug_overlay" />
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/check_show_codec_priority_scores"
|
||||
<include
|
||||
android:id="@+id/dev_panel_diagnostics"
|
||||
layout="@layout/activity_dev_diagnostics_panel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/dev_show_codec_priority_scores" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/check_grab_session_stats"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/dev_grab_session_stats" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/check_receiver_adaptive_display"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/dev_receiver_adaptive_display" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="28dp"
|
||||
android:text="@string/dev_sender_resolution_ui_section"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/dev_sender_resolution_ui_mode"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinner_dev_sender_resolution_ui_mode"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/dev_sender_resolution_ui_hint"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="28dp"
|
||||
android:text="@string/dev_av_presets_section"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/dev_av_video_preset"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinner_dev_video_preset"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/dev_av_audio_preset"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinner_dev_audio_preset"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/label_dev_av_intensity"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/dev_av_intensity"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/seek_dev_av_intensity"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:max="10"
|
||||
android:progress="5" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_dev_av_intensity_value"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/dev_av_presets_hint"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_open_session_stats"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:text="@string/session_stats_open" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_reload_codecs_json"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/dev_reload_codecs_json" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/dev_reload_codecs_json_hint"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="28dp"
|
||||
android:text="@string/ota_section_title"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_ota_installed_version"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textSize="13sp" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:hint="@string/ota_manifest_url_hint">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/edit_ota_manifest_url"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textUri" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/check_ota_on_launch"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:checked="true"
|
||||
android:text="@string/ota_check_on_launch" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_check_ota"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/ota_check_now" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/ota_section_hint"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="28dp"
|
||||
android:text="@string/dev_wired_display_section"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_wired_display_status"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textSize="13sp" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/check_wired_display_hints"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/dev_wired_display_hints" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/dev_external_capture_policy"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinner_external_capture_policy"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_external_capture_policy_note"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/check_usb_tether_transport"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/dev_usb_tether_transport" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/dev_wired_display_hint"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="28dp"
|
||||
android:text="@string/dev_commercial_section"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/check_commercial_ads"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/dev_commercial_ads" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/check_commercial_iap"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/dev_commercial_iap" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/check_commercial_play_store"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/dev_commercial_play_store" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_open_play_store_listing"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="@string/dev_open_play_store_listing" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_request_play_review"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/dev_request_play_review" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btn_query_iap_products"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/dev_query_iap_products" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/dev_commercial_hint"
|
||||
android:textSize="12sp" />
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
44
app/src/main/res/layout/view_dev_diag_card.xml
Normal file
44
app/src/main/res/layout/view_dev_diag_card.xml
Normal file
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
app:cardUseCompatPadding="true">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="14dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dev_diag_card_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dev_diag_card_body"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:fontFamily="monospace"
|
||||
android:textSize="12sp"
|
||||
android:textIsSelectable="true" />
|
||||
</LinearLayout>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/dev_diag_card_spinner"
|
||||
style="?android:attr/progressBarStyle"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_gravity="top|end"
|
||||
android:visibility="gone" />
|
||||
</FrameLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
@@ -196,6 +196,18 @@
|
||||
<string name="licenses_title">Лицензии</string>
|
||||
<string name="licenses_load_error">Не могу найти факл лицензий</string>
|
||||
<string name="developer_settings_title">Для разработчиков</string>
|
||||
<string name="dev_tab_settings">Настройки</string>
|
||||
<string name="dev_tab_diagnostics">Диагностика</string>
|
||||
<string name="dev_diag_intro">Поддерживаемые возможности — быстрые проверки без ввода. Запускаются в фоне при открытии вкладки.</string>
|
||||
<string name="dev_diag_stop">Остановить тесты</string>
|
||||
<string name="dev_diag_pending">Ожидание…</string>
|
||||
<string name="dev_diag_running">Выполняется…</string>
|
||||
<string name="dev_diag_stopped">Остановлено</string>
|
||||
<string name="dev_diag_hw_title">Аппаратное ускорение (видео)</string>
|
||||
<string name="dev_diag_camera_title">Захват с камеры</string>
|
||||
<string name="dev_diag_audio_title">Захват звука</string>
|
||||
<string name="dev_diag_network_title">Сеть</string>
|
||||
<string name="dev_diag_casting_title">Другой вывод / трансляция</string>
|
||||
<string name="dev_sender_resolution_ui_section">Разрешение отправителя (тест)</string>
|
||||
<string name="dev_sender_resolution_ui_mode">Режим списка разрешений</string>
|
||||
<string name="dev_sender_resolution_ui_separated">Раздельный (Оригинал / 720p / 480p / Авто)</string>
|
||||
|
||||
@@ -207,6 +207,18 @@
|
||||
<string name="licenses_title">Licenses</string>
|
||||
<string name="licenses_load_error">Could not load license text.</string>
|
||||
<string name="developer_settings_title">Developer settings</string>
|
||||
<string name="dev_tab_settings">Settings</string>
|
||||
<string name="dev_tab_diagnostics">Diagnostics</string>
|
||||
<string name="dev_diag_intro">Supported features — quick read-only probes. Tests run in the background when you open this tab.</string>
|
||||
<string name="dev_diag_stop">Stop tests</string>
|
||||
<string name="dev_diag_pending">Waiting…</string>
|
||||
<string name="dev_diag_running">Running…</string>
|
||||
<string name="dev_diag_stopped">Stopped</string>
|
||||
<string name="dev_diag_hw_title">Hardware acceleration (video)</string>
|
||||
<string name="dev_diag_camera_title">Camera capture</string>
|
||||
<string name="dev_diag_audio_title">Sound capture</string>
|
||||
<string name="dev_diag_network_title">Network</string>
|
||||
<string name="dev_diag_casting_title">Other casting / display</string>
|
||||
<string name="dev_sender_resolution_ui_section">Sender resolution UI (test)</string>
|
||||
<string name="dev_sender_resolution_ui_mode">Resolution preset mode</string>
|
||||
<string name="dev_sender_resolution_ui_separated">Separated (Original / 720p / 480p / Auto)</string>
|
||||
|
||||
Reference in New Issue
Block a user