Current section

Files

Jump to
dala lib dala device ios.ex
Raw

lib/dala/device/ios.ex

defmodule Dala.Device.Ios do
@moduledoc """
iOS-specific device events. Subscribers receive events that either have no
Android counterpart (e.g. `:protected_data_did_become_available`) or carry
extra iOS fidelity beyond what `Dala.Device` exposes cross-platform.
Messages are tagged `:dala_device_ios`:
{:dala_device_ios, event}
{:dala_device_ios, event, payload}
## Subscribe
Dala.Device.Ios.subscribe()
## Events
All cross-platform `Dala.Device` events are also re-emitted here under the
same atom (so users targeting iOS only can subscribe just to this module
and get the full event set). Plus iOS-only:
- `:protected_data_will_become_unavailable` — device about to lock; data
protection class A files become unreadable shortly after.
- `:protected_data_did_become_available` — device unlocked.
- `:audio_route_changed` — output route changed (headphones plugged in,
Bluetooth connected, etc.).
## Raw queries
When `Dala.Device` normalizes values (e.g. thermal `:nominal/:fair/...`),
this module exposes the raw native values for diagnostics.
Dala.Device.Ios.raw_thermal_state() # 0..3 (NSProcessInfoThermalState)
"""
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 iOS-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
@doc """
Raw `NSProcessInfoThermalState` value (0=nominal, 1=fair, 2=serious, 3=critical).
Provided for diagnostics. Most code should use `Dala.Device.thermal_state/0`
which returns a normalized atom shared with Android.
"""
@spec raw_thermal_state() :: 0 | 1 | 2 | 3
def raw_thermal_state do
case Dala.Device.Device.thermal_state() do
:nominal -> 0
:fair -> 1
:serious -> 2
:critical -> 3
end
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_ios, _} = msg, state) do
fan_out(state, msg)
{:noreply, state}
end
def handle_info({:dala_device_ios, _, _} = 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