headwind_plugin 0.2.1
headwind_plugin: ^0.2.1 copied to clipboard
Flutter plugin that bridges Headwind MDM (h-mdm.com) per-device managed configuration to Dart. Android-only: binds to the on-device com.hmdm.launcher and streams config snapshots.
headwind_plugin #
A small, project-agnostic Flutter plugin that reads per-device managed configuration on Android and streams it to Dart, from two sources:
- The Headwind MDM SDK — the on-device binding to the
com.hmdm.launcherapp already installed on managed devices. This is the primary source because it performs a live launcher read. - Android
RestrictionsManager— standard managed configuration / app restrictions delivered by an EMM or DPC. Used as a fallback for any attribute the SDK does not provide, so non-Headwind EMMs still work.
Both sources are Android-only. On any other platform the plugin degrades cleanly to "not connected / no values".
How attributes resolve #
preferSdk |
Source order |
|---|---|
false (default) |
The Headwind SDK, then RestrictionsManager for attributes the SDK omits. |
true |
The Headwind SDK only; RestrictionsManager is bypassed. |
What's inside #
- Dart
HeadwindMdm— connect, refresh, read attributes, togglepreferSdk, and asnapshotsstream of{attr: value}maps. - Android
HeadwindPlugin(Kotlin) — a pureFlutterPluginthat binds the Headwind SDK with the application context, readsRestrictionsManageras a fallback (re-reading on the system restrictions-changed broadcast), and pushes snapshots back to Dart. No customActivityrequired in the host app. - The Headwind SDK AAR, bundled as a local Maven artifact under
android/libs/maven/com/hmdm/hmdm/1.1.8/, and the<queries><package android:name="com.hmdm.launcher"/></queries>manifest entry.
Usage #
import 'package:headwind_plugin/headwind_plugin.dart';
final mdm = HeadwindMdm(const [
'api_base_url',
'app_color',
]);
mdm.snapshots.listen((values) {
// e.g. {'api_base_url': 'http://localhost', 'app_color': '#4A6F00'}
});
final connected = await mdm.connect(); // false on non-Android devices
To read exclusively from the Headwind SDK, pass preferSdk: true at
construction, or flip it at runtime:
final mdm = HeadwindMdm(const ['app_color'], preferSdk: true);
// ...or later:
await mdm.setPreferSdk(true); // bypass RestrictionsManager
await mdm.setPreferSdk(false); // back to SDK + RestrictionsManager fallback
Each setPreferSdk pushes a fresh snapshot reflecting the new source order.
By default the plugin re-syncs every time the app returns to the foreground
(refreshOnResume: true), so values follow the MDM dashboard without an app
restart or a manual "update configuration" tap. Pass refreshOnResume: false
to opt out and drive refresh() yourself.
API #
| Member | Description |
|---|---|
HeadwindMdm(attrs, {refreshOnResume = true, preferSdk = false}) |
Watches attrs. |
Stream<Map<String,String>> snapshots |
Full snapshot on connect and on change. |
Future<bool> connect() |
Bind sources; emits the initial snapshot. |
Future<bool> refresh() |
Ask the launcher to re-sync the server, then re-read. |
Future<bool> setPreferSdk(bool) |
Toggle SDK-only vs RestrictionsManager+SDK; re-emits. |
bool preferSdk |
Whether config is read from the SDK only. |
Future<bool> isManaged() |
Whether the device is managed (SDK or restrictions). |
Future<String?> getDeviceId() |
Headwind device id, if available. |
bool isConnected |
Whether the last connect() succeeded. |
Future<void> dispose() |
Release observer, channel handler and stream. |
Android RestrictionsManager #
RestrictionsManager is Android's standard managed app configuration API. When
an EMM, DPC, or Headwind MDM pushes app restrictions for the host app package,
Android exposes them to the app as an applicationRestrictions bundle.
This plugin reads that bundle as a fallback, using the same keys passed to
HeadwindMdm, when the Headwind SDK does not provide a value:
final mdm = HeadwindMdm(const [
'api_base_url',
'app_color',
]);
For the example above, the Android side looks for restrictions named exactly
api_base_url, app_color. Values are returned to
Dart in the regular snapshots stream:
mdm.snapshots.listen((values) {
final baseUrl = values['api_base_url'];
});
Resolution is per attribute:
| Situation | Result |
|---|---|
| SDK returns a non-empty value | Use the Headwind SDK value. |
| SDK omits the value and a restriction key exists | Use the RestrictionsManager value. |
preferSdk: true |
Skip RestrictionsManager and read only from the SDK. |
No host Activity changes are required. The native plugin reads restrictions
with the application context and registers for
ACTION_APPLICATION_RESTRICTIONS_CHANGED, so pushed changes are re-read and
emitted without app restart. refresh() also emits a fresh snapshot immediately
because restrictions are local and synchronous, even if the Headwind SDK is not
connected.
connect() returns true on Android when either source can serve config: the
SDK connects, or the restrictions bundle is non-empty. isManaged() is also
true when the SDK reports a managed device or when the restrictions bundle is
non-empty.
Consuming from another project #
Add it as a path (or git) dependency:
dependencies:
headwind_plugin:
path: ../headwind_plugin
Then flutter pub get. The plugin auto-registers — no changes to your
MainActivity or AndroidManifest.xml are needed.
Runtime note. The SDK is bundled as a local Maven artifact under
android/libs/maven/com/hmdm/hmdm/1.1.8/and referenced ascom.hmdm:hmdm:1.1.8from the plugin Gradle file. Because the plugin is consumed as a source subproject, it should land on the host app's runtime classpath automatically.
Example app #
An Android example app is available in example/. It watches
api_base_url, app_color, and route_auto_sync_time, then shows connection
state, managed-device state, device id, source mode, and the latest config
snapshot.
cd example
flutter run
For a managed-device test, configure those keys for package
com.example.headwind_plugin_example in Headwind MDM or another DPC that
supports Android app restrictions.
License #
The headwind_plugin source code is licensed under the MIT License.
Third-party software #
This plugin bundles the Headwind MDM Android SDK
(android/libs/maven/com/hmdm/hmdm/1.1.8/hmdm-1.1.8.aar),
which is licensed separately under the
Apache License, Version 2.0.
Headwind MDM: Open Source Android Mobile Device Management Software Project website: https://h-mdm.com Source: https://github.com/h-mdm/hmdm-android (c) 2019 Headwind Solutions LLC (http://www.h-sms.com)
Its license and required attribution notice live next to the .aar in
android/libs/ — see
NOTICE and
LICENSE-APACHE-2.0. Anyone who
redistributes this plugin (or the bundled .aar) must keep those two files
intact.
Notes / gotchas #
connect()returnsfalse(never throws) on non-Android platforms. On Android it reportstruewhenever a source can serve config — the SDK bound, orRestrictionsManagerreachable (unlesspreferSdkis set, which requires the SDK).RestrictionsManagervalues refresh on the systemACTION_APPLICATION_RESTRICTIONS_CHANGEDbroadcast; no polling needed.- The launcher's config-changed callback is not reliable for third-party
apps, so
refresh()re-reads on a short schedule (≈0.8–6 s) after triggering a server sync. Duplicate snapshots are cheap; listeners can diff them. - Attributes left blank in the Headwind dashboard arrive as empty strings — callers decide whether to treat blank as "absent".