Downloads

I may be recruiting a team of testers some time as a requirement for Google Play Store. In the meantime users preferring dedicated apps can download simple Android WebView wrappers here. Offline versions could be provided if requested, but would require a lot of images.

No permissions are required of your device other than the WebView's own local storage to save app data and user preferences. Please click the icons below to download, then follow the instructions.

World Flags

World Flags icon

Film Stars

Film Stars icon

US States

US States icon

GB Counties

GB Counties icon

Historic Buildings

Historic Buildings icon

Coe Operations Universal Key Facts Launcher

Key Facts

Key Facts icon

There was an error in the initial release of Key Facts due to an omission in its manifest file that caused it to reload with changes of orientation. The corrected installer was uploaded at 15:35 GMT 04/10/2026. Please accept my apologies if you downloaded and installed the version posted earlier that day.

Key Facts can now be installed directly in Chrome on Android devices. Please see the News page for further information.

Instructions

Because I am not registered with Play Store, you may be presented with the Google Play Protect Screen. Tap 'More details' for the second screen then 'Install anyway', as shown below. You may delete the apk file when finished.

Installation Screenshot Installation Screenshot

To install on a child's phone or tablet with the Family Link app, you need to temporarily disable the 'Apps from Unknown Sources' setting.

Open Family Link on the parent's device and select the child's profile. Navigate to Device Settings: 'Controls', 'Device' then 'Settings'. Locate and select 'Apps from Unknown Sources' and toggle to the 'On' position.

Download the apk file to the child's device, then click and follow the prompts to install. Restore the Unknown Sources setting after the app is installed.

Android App Source Code

The above apps do no more than show their corresponding web pages in a WebView, launch any clicked hyperlinks in your default web browser and handle keeping the screen on as the following Main Activity source code shows.

There are more calls than strictly necessary to prevent the screen staying on while the app is inactive. My own issues were likely related to Developer options settings and putting my phone on charge when the screen was on.

package com.example.worldflags; import android.app.AlertDialog; import android.content.Intent; import android.content.res.Configuration; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; import android.os.Looper; import android.view.View; import android.webkit.WebResourceError; import android.webkit.WebResourceRequest; import android.webkit.WebResourceResponse; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; import androidx.activity.OnBackPressedCallback; import android.webkit.JavascriptInterface; import android.widget.ProgressBar; public class MainActivity extends AppCompatActivity { private String rolling = "0"; // Rolling Display private WebView mywebView; private ProgressBar progressBar; private final Handler myHandler = new Handler(Looper.getMainLooper()); // Handler to receive update UI message private final Runnable updateUI = () -> { // Update the UI to keep screen on in Rolling Display Mode screenOn(rolling.equals("1")); }; private void screenOn(boolean keep) { mywebView.setKeepScreenOn(keep); mywebView.invalidate(); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); return insets; }); getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) { @Override public void handleOnBackPressed() { // Ensure screen doesn't stay on screenOn(false); if (mywebView.canGoBack()) { mywebView.goBack(); } else { finish(); } } }); mywebView = findViewById(R.id.webview); progressBar = findViewById(R.id.progressBar); mywebView.setWebViewClient(new WebViewClient() { // Override default error messages @Override public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) { view.loadUrl("about:blank"); showErrorDlg(); } @Override public void onReceivedHttpError(final WebView view, final WebResourceRequest request, WebResourceResponse errorResponse) { view.loadUrl("about:blank"); showErrorDlg(); } @Override public void onPageFinished(WebView view, String url) { progressBar.setVisibility(View.GONE); super.onPageFinished(view, url); } }); WebSettings webSettings = mywebView.getSettings(); webSettings.setCacheMode(WebSettings.LOAD_DEFAULT); webSettings.setJavaScriptEnabled(true); // Enable DOM storage API for localStorage webSettings.setDomStorageEnabled(true); // Enable pinch zoom webSettings.setBuiltInZoomControls(true); webSettings.setUseWideViewPort(true); webSettings.setDisplayZoomControls((false)); webSettings.setSupportZoom(true); mywebView.addJavascriptInterface(new WebAppInterface(), "AndroidInterface"); */ mywebView.loadUrl("https://coeoperations.com/wf/wf.html"); } // Prevent WebView reloading on orientation change - also requires // android:configChanges="orientation|screenSize" in AndroidManifest.xml @Override public void onConfigurationChanged(@NonNull Configuration newConfig) { super.onConfigurationChanged(newConfig); } @Override protected void onPause() { // Ensure screen doesn't stay on screenOn(false); super.onPause(); } @Override protected void onResume() { // App coming to foreground screenOn(rolling.equals("1")); super.onResume(); } public class WebAppInterface { @JavascriptInterface public void postMessage(String message) { // Message from JavaScript... if (message.equals("0") || message.equals("1")) { //... with updated Display Mode - needs posting to UI thread rolling = message; myHandler.post(updateUI); } else if (!message.isBlank()) { //... with URL Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(message)); startActivity(browserIntent); } } } public void showErrorDlg() { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle("World Flags") .setIcon(R.drawable.ic_launcher_foreground) .setMessage("Network Error. Please check your Internet connection.") .setCancelable(false) .setNegativeButton("OK", (dialog, id) -> { dialog.cancel(); finish(); }); AlertDialog alert = builder.create(); progressBar.setVisibility(View.GONE); alert.show(); } }