android: Add UI setting for experimental features (#40054)

These commits add a new settings screen to the app, then add:
1) a setting to disable a developer-targeted UI element (an indicator
about whether the app is polling continuously for events)
2) a toggle for experimental web platform features

The page needs to be reloaded after switching the toggle before any
changes can be observed.

Testing: Manually tested by visiting https://developer.mozilla.org with
the setting enabled and disabled. No automated testing for Android yet.
Fixes: #39791

---------

Signed-off-by: Josh Matthews <josh@joshmatthews.net>
This commit is contained in:
Josh Matthews
2025-10-22 09:29:57 -04:00
committed by GitHub
parent 6357fc8f1c
commit 855445983d
13 changed files with 177 additions and 6 deletions

View File

@@ -172,4 +172,5 @@ dependencies {
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.9.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.3")
implementation("androidx.preference:preference-ktx:1.2.0")
}

View File

@@ -43,6 +43,11 @@
<data android:mimeType="application/xhtml+xml"/>
</intent-filter>
</activity>
<activity android:name=".SettingsActivity"
android:configChanges="density|keyboardHidden|navigation|orientation|screenSize|uiMode"
android:exported="true"
android:theme="@style/AppTheme">
</activity>
</application>
</manifest>

View File

@@ -9,8 +9,10 @@ import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.system.ErrnoException;
import android.system.Os;
import android.util.Log;
@@ -45,6 +47,17 @@ public class MainActivity extends Activity implements Servo.Client {
boolean mCanGoBack;
MediaSession mMediaSession;
class Settings {
Settings(SharedPreferences preferences) {
showAnimatingIndicator = preferences.getBoolean("animating_indicator", false);
experimental = preferences.getBoolean("experimental", false);
}
boolean showAnimatingIndicator;
boolean experimental;
}
Settings mSettings;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -61,6 +74,8 @@ public class MainActivity extends Activity implements Servo.Client {
mIdleText = findViewById(R.id.redrawing);
mCanGoBack = false;
updateSettingsIfNecessary(true);
mBackButton.setEnabled(false);
mFwdButton.setEnabled(false);
@@ -82,7 +97,7 @@ public class MainActivity extends Activity implements Servo.Client {
Intent intent = getIntent();
String args = intent.getStringExtra("servoargs");
String log = intent.getStringExtra("servolog");
mServoView.setServoArgs(args, log);
mServoView.setServoArgs(args, log, mSettings.experimental);
if (Intent.ACTION_VIEW.equals(intent.getAction())) {
mServoView.loadUri(intent.getData().toString());
@@ -126,6 +141,11 @@ public class MainActivity extends Activity implements Servo.Client {
}
// From activity_main.xml:
public void onSettingsClicked(View v) {
Intent myIntent = new Intent(this, SettingsActivity.class);
startActivity(myIntent);
}
public void onReloadClicked(View v) {
mServoView.reload();
}
@@ -243,6 +263,7 @@ public class MainActivity extends Activity implements Servo.Client {
public void onResume() {
mServoView.onResume();
super.onResume();
updateSettingsIfNecessary(false);
}
@Override
@@ -291,4 +312,31 @@ public class MainActivity extends Activity implements Servo.Client {
mMediaSession.setPositionState(duration, position, playbackRate);
}
public void onAnimatingIndicatorPrefChanged(boolean value) {
if (value) {
mIdleText.setVisibility(View.VISIBLE);
} else {
mIdleText.setVisibility(View.GONE);
}
}
public void onExperimentalPrefChanged(boolean value) {
mServoView.setExperimentalMode(value);
}
public void updateSettingsIfNecessary(boolean force) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
Settings updated = new Settings(preferences);
if (force || updated.showAnimatingIndicator != mSettings.showAnimatingIndicator) {
onAnimatingIndicatorPrefChanged(updated.showAnimatingIndicator);
}
if (force || updated.experimental != mSettings.experimental) {
onExperimentalPrefChanged(updated.experimental);
}
mSettings = updated;
}
}

View File

@@ -0,0 +1,22 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
package org.servo.servoshell;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
public class SettingsActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.settings_container, new SettingsFragment())
.commit();
}
}

View File

@@ -0,0 +1,14 @@
package org.servo.servoshell;
import android.os.Bundle;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.SwitchPreferenceCompat;
public class SettingsFragment extends PreferenceFragmentCompat {
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.preferences, rootKey);
SwitchPreferenceCompat animatingPref = findPreference("animating_indicator");
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@@ -126,7 +126,19 @@
android:onClick="onForwardClicked"
android:src="@drawable/history_forward" />
<ImageButton
android:id="@+id/settingsbutton"
style="@android:style/Widget.Material.ImageButton"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:scaleType="centerInside"
android:adjustViewBounds="true"
android:onClick="onSettingsClicked"
android:src="@drawable/kebab" />
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="org.servo.servoshell.SettingsActivity">
<androidx.fragment.app.FragmentContainerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/settings_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="org.servo.servoshell.SettingsFragment" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,15 @@
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<SwitchPreferenceCompat
android:key="experimental"
android:defaultValue="false"
android:title="Enable experimental web platform features"/>
<SwitchPreferenceCompat
android:key="animating_indicator"
android:defaultValue="false"
android:title="Enable idle/animating indicator"/>
</PreferenceScreen>

View File

@@ -67,6 +67,8 @@ public class JNIServo {
public native void mediaSessionAction(int action);
public native void setExperimentalMode(boolean enable);
public static class ServoOptions {
public String args;
public String url;
@@ -77,6 +79,7 @@ public class JNIServo {
public String logStr;
public String gstDebugStr;
public boolean enableLogs = false;
public boolean experimentalMode = false;
}
public static class ServoCoordinates {

View File

@@ -167,6 +167,10 @@ public class Servo {
mRunCallback.inGLThread(() -> mJNI.mediaSessionAction(action));
}
public void setExperimentalMode(boolean enable) {
mRunCallback.inGLThread(() -> mJNI.setExperimentalMode(enable));
}
public interface Client {
void onAlert(String message);

View File

@@ -59,6 +59,7 @@ public class ServoView extends SurfaceView
private ScaleGestureDetector mScaleGestureDetector;
private OverScroller mScroller;
private boolean mExperimentalMode;
private boolean mZooming;
private float mZoomFactor = 1;
private boolean mRedrawing;
@@ -95,9 +96,10 @@ public class ServoView extends SurfaceView
mClient = client;
}
public void setServoArgs(String args, String log) {
public void setServoArgs(String args, String log, boolean experimentalMode) {
mServoArgs = args;
mServoLog = log;
mExperimentalMode = experimentalMode;
}
// RunCallback
@@ -362,6 +364,12 @@ public class ServoView extends SurfaceView
mServo.mediaSessionAction(action);
}
public void setExperimentalMode(boolean enable) {
if (mServo != null) {
mServo.setExperimentalMode(enable);
}
}
class GLThread extends Thread implements SurfaceHolder.Callback {
private Activity mActivity;
private ServoView mServoView;
@@ -384,6 +392,7 @@ public class ServoView extends SurfaceView
options.coordinates = coords;
options.enableLogs = true;
options.enableSubpixelTextAntialiasing = true;
options.experimentalMode = mServoView.mExperimentalMode;
DisplayMetrics metrics = new DisplayMetrics();
mActivity.getWindowManager().getDefaultDisplay().getMetrics(metrics);