Aurelius is a small, offline Android home-screen widget that displays either a bundled Stoic thought or one of your own personal messages and changes it approximately once per hour.
The intended distribution method is Google Play Internal Testing, with the app kept private and installed only by the selected tester account. Aurelius does not need to be published publicly to Google Play.
This README is also intended to act as a study guide and deployment record for the project, documenting the important setup and release steps used to build and install the app.
- Native Android app written in Kotlin.
- Home-screen widget with either a Stoic quote + attribution or a personal card.
- Two global content modes: Stoic quotes and My messages.
- Add, edit, duplicate, disable, delete, and reorder personal cards, organized into searchable collections, in the companion app.
- Personal messages are stored locally on the device.
- 72 bundled Stoic quotations/adaptations.
- Pseudo-random content from the selected mode on each widget update or manual refresh.
- Resizable from a compact approximately 2×2 layout, with 4×2 as the preferred initial size on Android 12 and newer.
- Responsive compact, standard, and large typography as the widget is resized.
- System, dark, light, and Android 12+ wallpaper-derived color themes.
- Solid or transparent background, three font sizes, and optional attribution.
- No internet permission, API, account, ads, analytics, server, or remote database.
- Tapping the widget opens the small companion app.
- Tapping the displayed quote or attribution immediately chooses another pseudo-random quote.
- Companion app previews the selected content, manages searchable custom-card collections, switches modes, and lets you manually refresh installed widgets.
- Companion app uses a Material 3 "warm marble & parchment" design, with light/dark palettes.
Stoic quotes is the default mode and preserves the original behaviour for existing installations. Quotes are selected from the bundled offline collection and may include attribution.
My messages selects from personal cards created in the companion app. The dedicated Custom Cards screen supports search, collections, duplication, temporary disabling, deletion, and long-press drag ordering. Cards are limited to 500 characters and may include an optional source. Personal card text is title-cased on display, while the original input is retained for editing. The widget can rotate through every enabled card or one selected collection. Global light- and dark-mode text colors are selected with an RGB slider color picker under Widget appearance and apply to every card; the picker is unavailable when the Wallpaper colors theme is active, since that theme always uses wallpaper-derived text colors. Cards stay on the device in versioned SharedPreferences storage. The mode cannot be enabled until at least one enabled card exists. Disabling or deleting the final enabled card automatically returns Aurelius to Stoic mode so the widget is never left blank.
The selected mode applies to all installed Aurelius widgets. Switching modes or changing the personal-message collection refreshes the widgets immediately. Personal messages are excluded from Android backup; uninstalling Aurelius or clearing its app data removes them.
The widget asks Android to update it every 3,600,000 ms (one hour) using AppWidgetProviderInfo.updatePeriodMillis.
Android may batch or delay widget updates to protect battery life, so this should be understood as roughly hourly, not an exact alarm at HH:00:00. Each update selects pseudo-random content from the active mode; a manual refresh does the same.
Application name: Aurelius
Application ID: com.donalgeraghty.stoicwidget
Minimum SDK: 26 (Android 8.0)
Target SDK: 37
Compile SDK: 37
The application ID is the Android package name used by Google Play. It should be treated as stable once Aurelius is registered in Play Console.
- Android Studio compatible with Android Gradle Plugin 9.2
- JDK 17+
- Android SDK 37
- A Google Play Developer personal account
- A private Android upload keystore
- Network access for Gradle to fetch dependencies (including the Material Components library) on first sync/build; the built app itself requests no internet permission
Workflow:
.github/workflows/build-apk.yml
This runs on pushes and pull requests to main and produces a development/debug APK artifact named:
Aurelius-debug-apk
The debug APK is useful for emulators and unmanaged Android devices. It is not the intended installation method for a managed work phone and should not be uploaded to Google Play.
Workflow:
.github/workflows/build-play-release.yml
This is a manually triggered workflow that builds a signed Android App Bundle (.aab) for Google Play Internal Testing.
It asks for:
version_code— positive integer that must increase for each Play upload.version_name— user-visible version such as1.0or1.1.
The resulting artifact is named approximately:
Aurelius-play-internal-v1.0
and contains:
app-release.aab
A Personal Google Play Developer account is sufficient for this project because Aurelius is a personal/hobby application rather than an organisation-owned product.
After the developer account is verified, create a new app in Google Play Console.
Suggested values:
App name: Aurelius
App or game: App
Free or paid: Free
Package name / Application ID: com.donalgeraghty.stoicwidget
The package name must match the applicationId in the Android project.
The upload key is created with the Java keytool utility.
If running keytool gives:
Command 'keytool' not found
install Java 17:
sudo apt update
sudo apt install openjdk-17-jre-headlessVerify that keytool is now available:
keytool -helpThe Aurelius build is also configured around Java/JVM 17, so this keeps the local tooling consistent with the project.
Run:
keytool -genkeypair \
-v \
-keystore ~/aurelius-upload.jks \
-alias aurelius \
-keyalg RSA \
-keysize 2048 \
-validity 10000This creates:
~/aurelius-upload.jks
During creation, keytool asks for information including:
- keystore password
- key password, depending on the Java/keytool flow
- name / organisational identity fields
The important values to remember are the passwords and the alias.
For this project the alias is:
aurelius
Google Play uses Play App Signing for the final distributed application.
The local aurelius-upload.jks is the upload key. GitHub Actions uses it to sign the .aab before the bundle is uploaded to Google Play.
The upload key should never be committed to the repository.
Keep at least two secure backups of:
aurelius-upload.jks
For example:
- one encrypted cloud backup
- one offline backup
Also securely record:
- keystore password
- alias:
aurelius - key password
Do not store the actual passwords or raw keystore in this README or in Git history.
GitHub Actions secrets are text values, so the binary .jks file is converted to a single-line Base64 string.
Run:
base64 -w 0 ~/aurelius-upload.jks > ~/aurelius-upload.base64.txtCheck that the Base64 file exists:
ls -lh ~/aurelius-upload.base64.txtTo display the value for copying into GitHub:
cat ~/aurelius-upload.base64.txtThe output will be one very long line.
The Base64 file is only a text representation of the keystore. It is still secret material and must be protected just like the .jks file.
In GitHub open:
DonalGeraghty/Aurelius
→ Settings
→ Secrets and variables
→ Actions
→ New repository secret
Create exactly these four repository secrets:
Value:
The complete single-line output from:
cat ~/aurelius-upload.base64.txtValue:
The password chosen for aurelius-upload.jks.
Value:
aurelius
Value:
The password for the aurelius key entry.
If the same password was used for both the keystore and the key, then AURELIUS_KEYSTORE_PASSWORD and AURELIUS_KEY_PASSWORD will contain the same value.
Only the names of these secrets belong in source control.
Never commit:
- the
.jksfile - the Base64 keystore contents
- keystore passwords
- key passwords
.gitignore is configured to exclude common keystore and Base64 export files, but this is only an additional safeguard. Secrets still need to be handled carefully.
The workflow reads:
AURELIUS_KEYSTORE_BASE64
AURELIUS_KEYSTORE_PASSWORD
AURELIUS_KEY_ALIAS
AURELIUS_KEY_PASSWORD
It then:
- validates that all required secrets exist
- decodes the Base64 keystore into a temporary
.jksfile on the GitHub runner - supplies the signing values to Gradle as environment variables
- builds the release Android App Bundle with
:app:bundleRelease - uploads the resulting
.aabas a GitHub Actions artifact
The temporary keystore exists only on the GitHub Actions runner for that build.
In GitHub open:
Actions
→ Build Google Play Internal Test
→ Run workflow
For the first release use:
version_code: 1
version_name: 1.0
The workflow should produce an artifact similar to:
Aurelius-play-internal-v1.0
Download the artifact and extract it.
Inside should be:
app-release.aab
This .aab is the file uploaded to Google Play Console.
In Google Play Console open the Aurelius app and go to:
Testing
→ Internal testing
→ Create new release
Upload:
app-release.aab
If Google asks about Play App Signing during initial setup, follow the Play Console flow to enable it for the app.
In the Internal testing section, add the Google account that should be allowed to install Aurelius.
For a private one-person setup, the tester list can contain only that account.
Use the same Google account that is signed into the Google Play Store on the target Android phone.
Save and publish the internal test release.
Google Play will provide an opt-in / tester link.
Open that link on the Android phone while signed into the tester Google account.
Accept the invitation and install Aurelius through Google Play.
The app remains on the Internal testing track and does not need to be released publicly.
After Aurelius is installed:
- long-press an empty area of the Android home screen
- choose Widgets
- find Aurelius
- drag the widget onto the home screen
The widget should display a pseudo-random quote and can be resized using the launcher's widget resize handles.
Whenever a new build is uploaded to Google Play, the version_code must increase.
Example sequence:
Release 1: version_code 1, version_name 1.0
Release 2: version_code 2, version_name 1.1
Release 3: version_code 3, version_name 1.2
A normal update flow is therefore:
Make code changes
↓
Commit/push to GitHub
↓
Verify the debug CI build
↓
Actions → Build Google Play Internal Test
↓
Increase version_code
↓
Download new app-release.aab
↓
Google Play Console → Internal testing
↓
Create/upload new release
↓
Publish to internal testers
↓
Update Aurelius from Google Play
The same upload keystore and GitHub secrets are reused for future releases.
Local machine
aurelius-upload.jks
↓ Base64
GitHub Actions Secret
AURELIUS_KEYSTORE_BASE64
↓ decoded temporarily
GitHub Actions runner
aurelius-upload.jks
↓ signs bundle
app-release.aab
↓ upload
Google Play Console
↓ Play App Signing
Google Play Store
↓
Internal tester phone
This separation means the signing key material is never stored directly in the repository.
- Clone/download this repository.
- Open the project folder in Android Studio.
- Allow Android Studio to install SDK 37 or compatible Gradle tooling if requested.
- Run the
appconfiguration on an emulator or permitted Android device. - Long-press an empty area of the home screen.
- Choose Widgets → Aurelius and drag it onto the home screen.
Android Studio/ADB installation is a development option only. A managed device may prevent developer-installed apps; Google Play Internal Testing is the intended distribution route for that situation.
app/src/main/
├── java/com/donalgeraghty/stoicwidget/
│ ├── ColorPickerDialog.kt
│ ├── ContentMode.kt
│ ├── CustomCardsActivity.kt
│ ├── MainActivity.kt
│ ├── MessageText.kt
│ ├── PersonalMessage.kt
│ ├── PersonalMessageRepository.kt
│ ├── Quote.kt
│ ├── QuoteRepository.kt
│ ├── StoicWidgetProvider.kt
│ ├── WidgetAppearance.kt
│ ├── WidgetContentSelector.kt
│ └── WidgetPreferences.kt
└── res/
├── drawable/
├── layout/
├── values/
└── xml/stoic_widget_info.xml
Aurelius is intentionally self-contained:
- no network permission
- no account/login
- no backend
- no advertising
- no analytics
- no remote quote or message service
Stoic quotes are bundled in the app and personal messages are stored locally, so both modes continue to work offline.
- Quote frequency setting (hourly / 3-hourly / daily).
- Filter by philosopher.
- Favourite quotes.
- Tap action to advance manually.
- Share quote.
- Exact-hour scheduling as an optional advanced mode.
The bundled text uses concise adaptations of ideas from classical Stoic works rather than claiming a specific modern translation. This keeps attribution clear while avoiding dependence on a particular copyrighted translation.
A shorter companion guide is also available at: