headwind_plugin 0.2.0
headwind_plugin: ^0.2.0 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:
- Android
RestrictionsManager— standard managed configuration / app restrictions delivered by an EMM or DPC (Headwind MDM included, when it pushes app restrictions). This is the primary source. - The Headwind MDM SDK — the on-device binding to the
com.hmdm.launcherapp already installed on managed devices. Used as a fallback for any attributeRestrictionsManagerdoes not provide, or — via thepreferSdktoggle — as the sole source (this plugin's original behaviour).
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) |
RestrictionsManager, then the SDK for attributes it 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 readsRestrictionsManager(re-reading on the system restrictions-changed broadcast), binds the Headwind SDK with the application context, 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 RestrictionsManager + SDK 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 first, using the same keys passed to
HeadwindMdm:
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 |
|---|---|
| Restriction key exists and has a non-empty value | Use the RestrictionsManager value. |
| Restriction key is absent or empty | Fall back to the Headwind SDK 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 is usable: the SDK
connects, or RestrictionsManager can be read. 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.
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".