HarmonyOS Flutter Wakelock Plugin (Screen Wake Lock Management)

HarmonyOS Flutter Wakelock Plugin (Screen Wake Lock Management)

I. MethodChannel Implementation

1. Flutter Code Implementation

Defining the Wakelock API

// Singleton pattern for Wakelock API
static WakelockPlatformInterface _instance = MethodChannelWakelock();

// Message encoding/decoding for Wakelock state
class ToggleMessage {
  bool? enable;

  Object encode() {
    final Map<Object?, Object?> pigeonMap = <Object?, Object?>{};
    pigeonMap['enable'] = enable;
    return pigeonMap;
  }

  static ToggleMessage decode(Object message) {
    final Map<Object?, Object?> pigeonMap = message as Map<Object?, Object?>;
    return ToggleMessage()..enable = pigeonMap['enable'] as bool?;
  }
}

class IsEnabledMessage {
  bool? enabled;

  Object encode() {
    final Map<Object?, Object?> pigeonMap = <Object?, Object?>{};
    pigeonMap['enabled'] = enabled;
    return pigeonMap;
  }

  static IsEnabledMessage decode(Object message) {
    final Map<Object?, Object?> pigeonMap = message as Map<Object?, Object?>;
    return IsEnabledMessage()..enabled = pigeonMap['enabled'] as bool?;
  }
}

// Custom codec for Wakelock messages
class _WakelockApiCodec extends StandardMessageCodec {
  const _WakelockApiCodec();

  @override
  void writeValue(WriteBuffer buffer, Object? value) {
    if (value is IsEnabledMessage) {
      buffer.putUint8(128);
      writeValue(buffer, value.encode());
    } else if (value is ToggleMessage) {
      buffer.putUint8(129);
      writeValue(buffer, value.encode());
    } else {
      super.writeValue(buffer, value);
    }
  }

  @override
  Object? readValueOfType(int type, ReadBuffer buffer) {
    switch (type) {
      case 128:
        return IsEnabledMessage.decode(readValue(buffer)!);
      case 129:
        return ToggleMessage.decode(readValue(buffer)!);
      default:
        return super.readValueOfType(type, buffer);
    }
  }
}

// Wakelock API interface
class WakelockApi {
  final BinaryMessenger? _binaryMessenger;

  WakelockApi({BinaryMessenger? binaryMessenger})
      : _binaryMessenger = binaryMessenger;

  static const MessageCodec<Object?> codec = _WakelockApiCodec();

  // Toggle screen wake lock state
  Future<void> toggle(ToggleMessage arg_msg) async {
    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
      'dev.flutter.pigeon.WakelockApi.toggle',
      codec,
      binaryMessenger: _binaryMessenger,
    );
    final Map<Object?, Object?>? replyMap =
        await channel.send(<Object>[arg_msg]) as Map<Object?, Object?>?;
    if (replyMap == null) {
      throw PlatformException(
        code: 'channel-error',
        message: 'Unable to establish connection on channel.',
        details: null,
      );
    } else if (replyMap['error'] != null) {
      final Map<Object?, Object?> error =
          (replyMap['error'] as Map<Object?, Object?>?)!;
      throw PlatformException(
        code: (error['code'] as String?)!,
        message: error['message'] as String?,
        details: error['details'],
      );
    }
  }

  // Check current wake lock state
  Future<IsEnabledMessage> isEnabled() async {
    final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
      'dev.flutter.pigeon.WakelockApi.isEnabled',
      codec,
      binaryMessenger: _binaryMessenger,
    );
    final Map<Object?, Object?>? replyMap =
        await channel.send(null) as Map<Object?, Object?>?;
    if (replyMap == null) {
      throw PlatformException(
        code: 'channel-error',
        message: 'Channel connection failed.',
        details: null,
      );
    } else if (replyMap['error'] != null) {
      final Map<Object?, Object?> error =
          (replyMap['error'] as Map<Object?, Object?>?)!;
      throw PlatformException(
        code: (error['code'] as String?)!,
        message: error['message'] as String?,
        details: error['details'],
      );
    } else {
      return (replyMap['result'] as IsEnabledMessage?)!;
    }
  }
}

2. OHOS Code Implementation

Integrating with Flutter Engine

import { Wakelock } from './Wakelock';
import { AbilityPluginBinding } from '@ohos/flutter_ohos/src/main/ets/embedding/engine/plugins/ability/AbilityPluginBinding';

const TAG = "WakelockPlugin";

export class WakelockPlugin implements FlutterPlugin, AbilityAware {
  private pluginBinding: FlutterPluginBinding | null = null;
  private wakelock: Wakelock | null = null;

  getUniqueClassName(): string {
    return "WakelockPlugin";
  }

  onAttachedToAbility(binding: AbilityPluginBinding): void {
    this.wakelock = new Wakelock(binding.getAbility().context);
    if (this.pluginBinding != null) {
      Messages.setup(this.pluginBinding.getBinaryMessenger(), this.wakelock);
    }
  }

  onDetachedFromAbility(): void {
    this.wakelock = null;
  }

  onAttachedToEngine(binding: FlutterPluginBinding): void {
    this.pluginBinding = binding;
  }

  onDetachedFromEngine(binding: FlutterPluginBinding): void {
    this.pluginBinding = null;
  }
}

OHOS Wakelock Logic

import { Log, Any } from '@ohos/flutter_ohos';
import { ToggleMessage, IsEnabledMessage } from './Messages';
import common from '@ohos.app.ability.common';
import window from '@ohos.window';

const TAG = "Wakelock.ohos";

export interface WakelockApi {
  toggle(msg: ToggleMessage): Promise<void>;
  isEnabled(): IsEnabledMessage;
}

export class Wakelock implements WakelockApi {
  private enabled: boolean = false;
  context?: common.UIAbilityContext;

  constructor(context: common.UIAbilityContext) {
    this.context = context;
  }

  async toggle(message: ToggleMessage): Promise<void> {
    if (!this.context) throw new NoAbilityError();
    return new Promise((resolve, reject) => {
      window.getLastWindow(this.context).then((windowInstance) => {
        const isKeepScreenOn = message.enable as boolean;
        Log.i(TAG, `Setting keepScreenOn to ${isKeepScreenOn}`);
        windowInstance.setWindowKeepScreenOn(isKeepScreenOn)
          .then(() => {
            this.enabled = isKeepScreenOn;
            resolve();
          })
          .catch((err: Any) => {
            this.enabled = false;
            reject(err);
            Log.e(TAG, `Error setting wake lock: ${JSON.stringify(err)}`);
          });
      });
    });
  }

  isEnabled(): IsEnabledMessage {
    if (!this.context) throw new NoAbilityError();
    return new IsEnabledMessage()..enabled = this.enabled;
  }
}

export class NoAbilityError extends Error {
  constructor() {
    super("Wakelock requires a foreground Ability context.");
  }
}

II. Wakelock Usage Example

Enable Wake Lock

// Request screen wake lock
await WakelockApi().toggle(ToggleMessage()..enable = true);

Check Wake Lock Status

final status = await WakelockApi().isEnabled();
print("Wake lock is ${status.enabled ? 'enabled' : 'disabled'}");

III. Key Features

  • Screen Wake Lock Management: Prevents device screen from dimming or turning off.
  • Cross-Platform Compatibility: Works with both Flutter and HarmonyOS.
  • Error Handling: Robust exception handling for edge cases (e.g., no active Ability).

Leave a Reply