Current section

Files

Jump to
dala lib dala device android.ex
Raw

lib/dala/device/android.ex

defmodule Dala.Device.Android do
@moduledoc """
Android-specific device events. Symmetrical with `Dala.Device.IOS`.
Messages are tagged `:dala_device_android`:
{:dala_device_android, event}
{:dala_device_android, event, payload}
## Subscribe
Dala.Device.Android.subscribe()
## Status
Android event surfacing is **pending implementation**. The Elixir API is
stable; subscribing succeeds but no events fire until
`ProcessLifecycleObserver` and `ComponentCallbacks2` are wired up in the
generated app's Java/Kotlin side. Tracked in `PLAN.md` under "Native event
surface — Batch 1".
## Planned events
- `:doze_mode_changed` — Doze mode entered/exited (no iOS counterpart)
- `:idle_mode_changed` — light idle mode (`ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED`)
- `:trim_memory` with level — `ComponentCallbacks2.onTrimMemory(level)`
- `:airplane_mode_changed``ACTION_AIRPLANE_MODE_CHANGED`
- `:user_present``ACTION_USER_PRESENT` (device unlocked)
- all cross-platform `Dala.Device` events re-emitted under this tag too
"""
use GenServer
@spec start_link(keyword()) :: GenServer.on_start()
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
@doc "Subscribe the calling process to Android-specific device events."
@spec subscribe() :: :ok
def subscribe do
GenServer.call(__MODULE__, {:subscribe, self()})
end
@doc "Unsubscribe the calling process."
@spec unsubscribe() :: :ok
def unsubscribe do
GenServer.call(__MODULE__, {:unsubscribe, self()})
end
# ── GenServer ─────────────────────────────────────────────────────────────
@impl true
def init(_opts), do: {:ok, %{subscribers: %{}, monitors: %{}}}
@impl true
def handle_call({:subscribe, pid}, _from, state) do
state = put_subscriber(state, pid)
{:reply, :ok, state}
end
@impl true
def handle_call({:unsubscribe, pid}, _from, state) do
state = drop_subscriber(state, pid)
{:reply, :ok, state}
end
@impl true
def handle_call(:__test_subscribers__, _from, state) do
{:reply, state.subscribers, state}
end
@impl true
def handle_info({:dala_device_android, _} = msg, state) do
fan_out(state, msg)
{:noreply, state}
end
def handle_info({:dala_device_android, _, _} = msg, state) do
fan_out(state, msg)
{:noreply, state}
end
def handle_info({:DOWN, _ref, :process, pid, _reason}, state) do
{:noreply, drop_subscriber(state, pid)}
end
def handle_info(_other, state), do: {:noreply, state}
defp put_subscriber(state, pid) do
monitors =
case Map.get(state.monitors, pid) do
nil -> Map.put(state.monitors, pid, Process.monitor(pid))
_ref -> state.monitors
end
%{state | subscribers: Map.put(state.subscribers, pid, true), monitors: monitors}
end
defp drop_subscriber(state, pid) do
case Map.pop(state.monitors, pid) do
{nil, monitors} ->
%{state | monitors: monitors}
{ref, monitors} ->
Process.demonitor(ref, [:flush])
%{state | subscribers: Map.delete(state.subscribers, pid), monitors: monitors}
end
end
defp fan_out(state, msg) do
Enum.each(state.subscribers, fn {pid, _} -> send(pid, msg) end)
end
end