Start here
Empty widget template
The minimal project to duplicate and rename before creating your own interface and behavior.
Download empty template ZIP · 67 KB
CarBox Widget SDK
Create real Android mini-apps with Android Studio, Java or Kotlin, XML layouts and custom Views. CarBoxLauncher hosts them safely inside movable and resizable panels.
public final class SpeedWidgetActivity
extends CarBoxWidgetActivity {
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
setContentView(R.layout.speed_widget);
}
@Override
protected void onWidgetVisible() {
speedSensor.start();
}
@Override
protected void onWidgetHidden() {
speedSensor.stop();
}
}
Starter downloads
Both packages are standalone projects with the Widget SDK, Gradle Wrapper and complete development manual included.
Start here
The minimal project to duplicate and rename before creating your own interface and behavior.
Working example
A complete native widget built with Java, an XML layout and a custom Canvas View.
Architecture
Each widget is compiled as its own Android package, has no launcher icon and runs with its own UID. CarBoxLauncher discovers its Activity through a public manifest contract and renders it inside a VirtualDisplay panel.
Use any Android View, Canvas drawing, animations, databases, sensors and compatible Gradle libraries.
Widget code never runs inside the privileged CarBoxLauncher process. A broken widget cannot inherit launcher permissions.
Package the development output as a .cbwidget for distribution and updates through the CarBox Store.
Requirements
Use Android Studio with SDK Platform 34 and its bundled JDK 21. The current SDK supports boxes from Android 5.0 onward.
Quick start
Open the Widget project from the CarBoxLauncher source tree and duplicate empty-widget. Give the copy a permanent application ID before writing your interface.
Copy empty-widget and register the new folder as a Gradle module.
Change namespace, applicationId, Java package, widget name and carbox-widget.json ID.
Build the screen with XML and implement behavior in your Activity or custom Views.
Compile the APK, test it on a box and generate the .cbwidget container.
include ':my-widget'
project(':my-widget').projectDir = file('my-widget')
android {
namespace 'com.example.mywidget'
compileSdk 34
defaultConfig {
applicationId 'com.example.mywidget'
minSdk 21
targetSdk 34
versionCode 1
versionName '1.0.0'
}
}
dependencies {
implementation project(':carbox-widget-sdk')
}
Discovery contract
Never add the LAUNCHER category. Export only the CarBox widget action and declare API version plus initial panel dimensions.
<activity
android:name=".MyWidgetActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|density"
android:excludeFromRecents="true"
android:exported="true"
android:resizeableActivity="true">
<intent-filter>
<action android:name="com.ferra.carboxlauncher.action.CARBOX_WIDGET" />
<category android:name="com.ferra.carboxlauncher.category.CARBOX_WIDGET" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data
android:name="com.ferra.carboxlauncher.widget.API_VERSION"
android:value="1" />
<meta-data
android:name="com.ferra.carboxlauncher.widget.DEFAULT_WIDTH_DP"
android:value="320" />
<meta-data
android:name="com.ferra.carboxlauncher.widget.DEFAULT_HEIGHT_DP"
android:value="200" />
</activity>
Per-instance settings
Add a SECOND exported Activity with the settings action. In edit mode CarBox shows a gear on the panel's bottom-left corner that opens it — external to the widget. CarBox passes a stable per-instance id: namespace your preferences with it so two copies of the same widget (e.g. one digital clock, one analog) keep independent settings. When the settings close, CarBox reloads that widget so it re-reads its config.
// Without windowIsTranslucent the widget window is opaque and the
// VirtualDisplay composites it over black → a "transparent" background
// shows BLACK instead of the launcher wallpaper. Required on Theme.CarBoxWidget:
<style name="Theme.CarBoxWidget" parent="android:style/Theme.Material.NoActionBar">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
</style>
<activity
android:name=".SettingsActivity"
android:exported="true"
android:theme="@style/Theme.CarBoxWidgetSettings">
<intent-filter>
<action android:name="com.ferra.carboxlauncher.action.CARBOX_WIDGET_SETTINGS" />
<category android:name="com.ferra.carboxlauncher.category.CARBOX_WIDGET" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<style name="Theme.CarBoxWidgetSettings" parent="android:style/Theme.Material.NoActionBar">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowNoTitle">true</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
</style>
// per-instance config
String id = getIntent().getStringExtra(
CarBoxWidget.EXTRA_WIDGET_INSTANCE_ID);
SharedPreferences prefs = getSharedPreferences(
CarBoxWidget.configName(id), MODE_PRIVATE);
prefs.edit().putString("mode", "analog").apply();
// hide status + navigation bars
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
Runtime
Extend CarBoxWidgetActivity. Start timers, sensors and listeners only while visible, stop them when hidden, and adapt the UI whenever the panel size changes.
@Override
protected void onWidgetVisible() {
handler.post(updateTask);
}
@Override
protected void onWidgetHidden() {
handler.removeCallbacks(updateTask);
}
@Override
protected void onWidgetResize(int width, int height) {
gauge.setCompact(width < 320);
}
Build
Run the Gradle task from the Widget root. Install the debug APK during development, then add it from Creative Mode.
$env:JAVA_HOME = 'C:\Program Files\Android\Android Studio\jbr'
.\gradlew.bat :my-widget:packageCarBoxWidget
adb -s BOX_IP:5555 install -r `
my-widget\build\outputs\apk\debug\my-widget-debug.apk
Distribution
The container is a ZIP with a compiled plugin.apk and manifest.json. The CarBox server validates production packages before they are offered for installation.
Before publishing
Talk to the launcher
A widget runs inside a VirtualDisplay in its own process, so it cannot call the launcher directly — and calling startActivity() from a widget would open the app INSIDE the widget panel. The SDK exposes two channels: fire-and-forget commands (CarBoxWidgetCommands) and read-only state queries (CarBoxWidgetState). Import both from the Widget SDK.
// launch an app fullscreen (outside the panel)
CarBoxWidgetCommands.launchAppFullscreen(context, "com.spotify.music");
CarBoxWidgetCommands.launchFavorite(context, 1); // favorite 1..5
CarBoxWidgetCommands.openAppDrawer(context);
CarBoxWidgetCommands.openAssistant(context); // voice assistant
CarBoxWidgetCommands.speak(context, "Hello");
CarBoxWidgetCommands.openPhone(context);
CarBoxWidgetCommands.openContacts(context);
CarBoxWidgetCommands.call(context, "+391234567"); // or a contact name
CarBoxWidgetCommands.openNotifications(context);
CarBoxWidgetCommands.goToPage(context, 3);
CarBoxWidgetCommands.nextPage(context);
CarBoxWidgetCommands.previousPage(context);
CarBoxWidgetCommands.goHome(context);
CarBoxWidgetCommands.toggleEditMode(context);
CarBoxWidgetCommands.setEditMode(context, true);
CarBoxWidgetCommands.reloadPanels(context);
CarBoxWidgetCommands.setMode(context, "creative"); // or "dual"
CarBoxWidgetCommands.toggleMode(context);
CarBoxWidgetCommands.openSystemSettings(context, "wifi"); // wifi|bluetooth|data
CarBoxWidgetCommands.restartLauncher(context);
// follow the language selected in the launcher
String lang = CarBoxWidgetState.getLanguage(context); // "it" / "en"
applyLanguage(lang);
String mode = CarBoxWidgetState.getMode(context); // "creative" / "dual"
boolean creative = CarBoxWidgetState.isCreativeMode(context);
boolean editing = CarBoxWidgetState.isEditMode(context);
int page = CarBoxWidgetState.getCurrentPage(context);
int pages = CarBoxWidgetState.getPageCount(context);
State is exposed by a read-only ContentProvider. Every SDK query is defensive and returns the documented fallback when the launcher or provider is unavailable.
| Query | Provider column | Fallback |
|---|---|---|
getLanguage | language | "en" |
getMode | mode | "dual" |
isCreativeMode | mode | false |
isEditMode | edit_mode | false |
getCurrentPage | current_page | 0 |
getPageCount | page_count | 0 |
Read-only provider URI: content://com.ferra.carboxlauncher.state/config
| Command | Parameter | What it does |
|---|---|---|
launchAppFullscreen | package | Open an app fullscreen on the main display |
launchFavorite | 1–5 | Open launcher favorite N |
openAppDrawer | — | Open the app drawer |
openAssistant / speak | — / text | Voice assistant / text-to-speech |
openPhone / openContacts / call | — / — / number | Phone dialer, contacts, place a call via the paired phone |
openNotifications | — | Left-handle notifications panel |
goToPage / nextPage / previousPage / goHome | N / — | Navigate creative-mode pages |
toggleEditMode / setEditMode / reloadPanels | boolean for setEditMode | Toggle or explicitly set widget edit mode / reload panels |
setMode / toggleMode | creative|dual | Switch between creative and dual mode |
openSystemSettings | wifi|bluetooth|data | Open system settings |
restartLauncher | — | Restart the launcher |
Intent command = new Intent()
.putExtra(CarBoxWidgetCommands.EXTRA_COMMAND, "commandName")
.putExtra(CarBoxWidgetCommands.EXTRA_TEXT, "value");
CarBoxWidgetCommands.sendCommand(context, command);
| SDK constant | Wire value | Purpose |
|---|---|---|
LAUNCHER_PACKAGE | com.ferra.carboxlauncher | Explicit broadcast destination |
ACTION_WIDGET_COMMAND | com.ferra.carboxlauncher.action.WIDGET_COMMAND | Explicit broadcast action |
EXTRA_COMMAND | com.ferra.carboxlauncher.extra.COMMAND | Required command name |
EXTRA_PACKAGE_NAME | com.ferra.carboxlauncher.extra.PACKAGE_NAME | Android application package |
EXTRA_INT | com.ferra.carboxlauncher.extra.INT | Integer parameter |
EXTRA_BOOL | com.ferra.carboxlauncher.extra.BOOL | Boolean parameter |
EXTRA_TEXT | com.ferra.carboxlauncher.extra.TEXT | Text parameter |
Full working example: the CarBox Button widget — a fully configurable button (color, background transparency, image, label) where the settings let you pick any of these actions. It's the reference for using the whole API.
Available now
CarBoxLauncher can discover, install, update and remove validated .cbwidget packages directly from the Widget Store.