App 2.16.3: full screen stays with drop-downs and dialogs

Opening a drop-down or a dialog brought the car's bars back until it
closed: each is a window of its own, and the system shows the bars when
a window without the full-screen flags takes focus.

- The drop-downs (upload frequency, tyre pressure unit, accent colour)
  are now single-choice buttons on the page: no extra window, one tap
  instead of two, full touch targets, the choice in the accent colour.
- Dialogs (About, update, pairing, phone settings, trip reset, date
  picker) are shown unfocusable, given the activity's full-screen
  flags, and only then allowed focus. None of them takes typing, which
  an unfocusable window would block.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QLJfZQZeAKAwSqViTmLzki
This commit is contained in:
2026-10-18 20:05:26 +02:00
co-authored by Claude Opus 5.5
parent 98ec3f3fcf
commit f614ab08be
14 changed files with 117 additions and 73 deletions
@@ -126,7 +126,7 @@ public class MainActivity extends AppCompatActivity {
/** Upload page: live telemetry switch and cadence. */
private SwitchMaterial liveTelemetrySwitch, boostSwitch, voiceKeySwitch, fullScreenSwitch;
private TextView steeringKeysText;
private android.widget.Spinner intervalSpinner;
private com.google.android.material.chip.ChipGroup intervalChoices;
private TextInputLayout lowSocLayout;
private TextInputEditText lowSocInput;
@@ -305,6 +305,26 @@ public class MainActivity extends AppCompatActivity {
window.widthPixels, window.heightPixels, config.screenWidthDp, config.screenHeightDp));
}
/**
* Shows a dialog without bringing the car's bars back. A dialog is a window of its own, and
* the system shows the bars when a window without the full-screen flags takes focus. So it
* is shown unfocusable, given the activity's flags, and only then allowed focus. None of the
* dialogs here take typing, which an unfocusable window would block.
*/
@SuppressWarnings("deprecation") // the WindowInsetsController API starts at 30; this is 28
private void showKeepingFullScreen(android.app.Dialog dialog) {
android.view.Window window = dialog.getWindow();
if (window == null || !UploadSettings.fullScreen(prefs)) {
dialog.show();
return;
}
window.setFlags(android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
dialog.show();
window.getDecorView().setSystemUiVisibility(getWindow().getDecorView().getSystemUiVisibility());
window.clearFlags(android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
}
private static final String STATE_PAGE = "page";
@Override
@@ -332,7 +352,7 @@ public class MainActivity extends AppCompatActivity {
.setView(content)
.create();
content.<MaterialButton>findViewById(R.id.about_close).setOnClickListener(v -> dialog.dismiss());
dialog.show();
showKeepingFullScreen(dialog);
if (dialog.getWindow() != null) {
dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
android.util.DisplayMetrics metrics = getResources().getDisplayMetrics();
@@ -440,7 +460,7 @@ public class MainActivity extends AppCompatActivity {
.setView(panel)
.setCancelable(false)
.create();
dialog.show();
showKeepingFullScreen(dialog);
UpdateHook.checkInBackground(this, new UpdateHook.Listener() {
@Override public void onProgress(int percent) {
if (percent < 0) {
@@ -457,11 +477,11 @@ public class MainActivity extends AppCompatActivity {
@Override public void onResult(String message) {
button.setEnabled(true);
dialog.dismiss();
new MaterialAlertDialogBuilder(MainActivity.this)
showKeepingFullScreen(new MaterialAlertDialogBuilder(MainActivity.this)
.setTitle(R.string.update_dialog_title)
.setMessage(message)
.setPositiveButton(android.R.string.ok, null)
.show();
.create());
}
});
}
@@ -796,7 +816,7 @@ public class MainActivity extends AppCompatActivity {
qr.setVisibility(View.GONE);
code.setVisibility(View.GONE);
dialog.setOnDismissListener(d -> cancelled[0] = true);
dialog.show();
showKeepingFullScreen(dialog);
}
/** Shows the request: QR code, the code in the sentence and below it. UI thread. */
@@ -1032,7 +1052,7 @@ public class MainActivity extends AppCompatActivity {
applyFullScreen();
});
liveTelemetrySwitch = servicePane.findViewById(R.id.abrp_upload_switch);
intervalSpinner = servicePane.findViewById(R.id.interval_spinner);
intervalChoices = servicePane.findViewById(R.id.interval_choices);
boostSwitch = servicePane.findViewById(R.id.boost_switch);
lowSocInput = servicePane.findViewById(R.id.low_soc_input);
lowSocLayout = servicePane.findViewById(R.id.low_soc_layout);
@@ -1105,47 +1125,52 @@ public class MainActivity extends AppCompatActivity {
}, "abrp-test").start();
}
/**
* A single choice as buttons on the page (choice_chip.xml), in place of a drop-down: a
* drop-down opens a window of its own that is not full screen, and the car's bars came back
* while it was open. Rebinding replaces the buttons, so it may be called again.
*/
private void bindChoices(com.google.android.material.chip.ChipGroup group, String[] labels, int selected,
java.util.function.IntConsumer onPick) {
group.setOnCheckedStateChangeListener(null);
group.removeAllViews();
for (int i = 0; i < labels.length; i++) {
com.google.android.material.chip.Chip chip = (com.google.android.material.chip.Chip)
getLayoutInflater().inflate(R.layout.choice_chip, group, false);
chip.setId(View.generateViewId());
chip.setText(labels[i]);
chip.setTag(i);
group.addView(chip);
if (i == selected) chip.setChecked(true);
}
group.setOnCheckedStateChangeListener((g, checkedIds) -> {
if (checkedIds.isEmpty()) return;
View chosen = g.findViewById(checkedIds.get(0));
if (chosen != null) onPick.accept((Integer) chosen.getTag());
});
}
/** The accent colour; a change recreates the activity so every view takes it. */
private void bindAccentColor() {
android.widget.Spinner spinner = settingsPane.findViewById(R.id.accent_spinner);
AccentColor[] colors = AccentColor.values();
String[] labels = new String[colors.length];
for (int i = 0; i < colors.length; i++) labels[i] = colors[i].label;
android.widget.ArrayAdapter<String> adapter = new android.widget.ArrayAdapter<>(
this, R.layout.spinner_value, labels);
adapter.setDropDownViewResource(R.layout.spinner_dropdown);
spinner.setAdapter(adapter);
AccentColor current = AccentColor.fromPref(prefs.getString(AccentColor.PREF_KEY, null));
spinner.setSelection(current.ordinal());
spinner.setOnItemSelectedListener(new android.widget.AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(android.widget.AdapterView<?> parent, View view, int position, long id) {
if (colors[position] == current) return;
prefs.edit().putString(AccentColor.PREF_KEY, colors[position].name()).apply();
recreate();
}
@Override public void onNothingSelected(android.widget.AdapterView<?> parent) { }
bindChoices(settingsPane.findViewById(R.id.accent_choices), labels, current.ordinal(), position -> {
if (colors[position] == current) return;
prefs.edit().putString(AccentColor.PREF_KEY, colors[position].name()).apply();
recreate();
});
}
/** Tyre pressure unit: display only, read on every refresh of the Overview. */
private void bindTireUnit() {
android.widget.Spinner spinner = settingsPane.findViewById(R.id.tire_unit_spinner);
PressureUnit[] units = PressureUnit.values();
String[] labels = new String[units.length];
for (int i = 0; i < units.length; i++) labels[i] = units[i].label;
android.widget.ArrayAdapter<String> adapter = new android.widget.ArrayAdapter<>(
this, R.layout.spinner_value, labels);
adapter.setDropDownViewResource(R.layout.spinner_dropdown);
spinner.setAdapter(adapter);
spinner.setSelection(PressureUnit.fromPref(prefs.getString(PressureUnit.PREF_KEY, null)).ordinal());
spinner.setOnItemSelectedListener(new android.widget.AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(android.widget.AdapterView<?> parent, View view, int position, long id) {
prefs.edit().putString(PressureUnit.PREF_KEY, units[position].name()).apply();
}
@Override public void onNothingSelected(android.widget.AdapterView<?> parent) { }
});
int current = PressureUnit.fromPref(prefs.getString(PressureUnit.PREF_KEY, null)).ordinal();
bindChoices(settingsPane.findViewById(R.id.tire_unit_choices), labels, current, position ->
prefs.edit().putString(PressureUnit.PREF_KEY, units[position].name()).apply());
}
/** The service reads these settings on every tick, so a change applies without a restart. */
@@ -1161,19 +1186,9 @@ public class MainActivity extends AppCompatActivity {
: getResources().getQuantityString(R.plurals.interval_seconds, sec, sec);
if (sec == current.intervalSec) selected = i;
}
android.widget.ArrayAdapter<String> adapter = new android.widget.ArrayAdapter<>(
this, R.layout.spinner_value, labels);
adapter.setDropDownViewResource(R.layout.spinner_dropdown);
intervalSpinner.setAdapter(adapter);
intervalSpinner.setSelection(selected);
intervalSpinner.setOnItemSelectedListener(new android.widget.AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(android.widget.AdapterView<?> parent, View view, int position, long id) {
bindChoices(intervalChoices, labels, selected, position ->
prefs.edit().putInt(UploadSettings.KEY_INTERVAL_SEC,
UploadSettings.INTERVAL_CHOICES_SEC[position]).apply();
}
@Override public void onNothingSelected(android.widget.AdapterView<?> parent) { }
});
UploadSettings.INTERVAL_CHOICES_SEC[position]).apply());
boostSwitch.setOnCheckedChangeListener(null);
boostSwitch.setChecked(current.boostLowSoc);
@@ -1462,13 +1477,14 @@ public class MainActivity extends AppCompatActivity {
if (consumptionPeriod != ConsumptionTracker.Period.TRIP_A && consumptionPeriod != ConsumptionTracker.Period.TRIP_B) return;
String name = getString(consumptionPeriod == ConsumptionTracker.Period.TRIP_A
? R.string.consumption_trip_a : R.string.consumption_trip_b);
new MaterialAlertDialogBuilder(this).setTitle(getString(R.string.trip_reset_title, name))
showKeepingFullScreen(new MaterialAlertDialogBuilder(this)
.setTitle(getString(R.string.trip_reset_title, name))
.setMessage(R.string.trip_reset_message)
.setNegativeButton(R.string.pair_cancel, null)
.setPositiveButton(R.string.trip_reset_confirm, (d, w) -> {
consumptionTracker.reset(consumptionPeriod);
refreshConsumption();
}).show();
}).create());
});
}
@@ -1671,7 +1687,7 @@ public class MainActivity extends AppCompatActivity {
dialog.getDatePicker().setMaxDate(System.currentTimeMillis());
dialog.getDatePicker().setMinDate(java.time.LocalDate.now().minusMonths(4)
.atStartOfDay(zone).toInstant().toEpochMilli());
dialog.show();
showKeepingFullScreen(dialog);
}
/**