古事連記帖

趣味のこと、技術的なこと、適当につらつら書きます。

.NET MAUI (Android) で電波強度の取得をしてみる

手元で試せるのがAndroidしかないので、Androidのみとなりますが、.NET MAUIで携帯ネットワーク通信やWiFi接続の電波強度を取得して表示するということをやってみます。MAUIとしては現在のネットワーク接続が何かはわかりますが、電波強度などまではわかりません。そこでプラットフォーム固有のAPIを使用して取得することを試みます。

プラットフォーム固有のAPIを使用するので、作成するcsファイルはすべて「Platform/Android」配下に置きます。また、余計な考慮をしたくないので、この説明中の「最小ターゲットAndroidフレームワーク」は「Android 10.0 (APIレベル29)」にしてあります。

携帯ネットワーク通信の電波強度を取得する

Android API Level 31 以降とそれより前で必要な物が変わります。最低サポートバージョンを Android 12にする場合は気にしなくていいですが、それより前をサポートする場合はそれぞれに考慮する必要があります。
つまり、Android 12より前のバージョンでは、「Android.Telephony」名前空間の「TelephonyManager」クラス(Android)「Listen()」メソッド(Android) を使用しますが、12以降のバージョンでは非推奨となり、「Android.Telephony.TelephonyManager.RegisterTelephonyCallback()」メソッド(Android) を使います。
そして、前者「Listen()」メソッドで必要なコールバックを受けるクラスは「Android.Telephony.PhoneStateListener」クラス(Android) を継承し、後者「RegisterTelephonyCallback()」は「Android.Telephony.TelephonyCallback」クラス(Android) の継承、および「Android.Telephony.TelephonyCallback.ISignalStrengthsListener」インターフェース(Android) の実装をしたものを用意します。ここで、両方が必要になる場合、特に後者で必要なクラスとインターフェースはAPI Level 31から追加になったもので、30以前のOS、Android 11以前では存在しないため変数として宣言できませんので、両方に対応できるよう、別でインターフェースなどを用意しそれを型として変数宣言する方法を採るといいかもしれません。

ちなみに、この情報を取得するのに必要な権限はありません。適当に権限をなくしてエミュレーターや実機に流して実行してみましたが、特に怒られることはなかったです。

using Android.Content;
using Android.Telephony;

public class NetworkStrengthWatcher
{
    /// <summary>
    /// 携帯ネットワーク通信に関するコールバックをまるめるインターフェース
    /// </summary>
    private interface ITelephonyState
    {
        /// <summary>
        /// 電波強度
        /// </summary>
        int Level { get; }
    }

    /// <summary>
    /// Android API Level 31 以降用
    /// </summary>
    private class TelephonyState : 
        TelephonyCallback, 
        TelephonyCallback.ISignalStrengthsListener, 
        ITelephonyState
    {
        public int Level { get; private set; }

        public void OnSignalStrengthsChanged(SignalStrength signalStrength)
        {
            // 電波強度を見やすいように 0 から 4 にしてくれているプロパティを使う
            this.Level = signalStrength.Level;
        }
    }

    /// <summary>
    /// Android API Level 30 以前用
    /// </summary>
    private class TelephonyStateOlder : PhoneStateListener, ITelephonyState
    {
        public int Level { get; private set; }

        public override void OnSignalStrengthsChanged(SignalStrength? signalStrength)
        {
            if (OperatingSystem.IsAndroidVersionAtLeast(31))
            {
                return;
            }

            base.OnSignalStrengthsChanged(signalStrength);

            if (signalStrength is null)
            {
                return;
            }

            // 電波強度を見やすいように 0 から 4 にしてくれているプロパティを使う
            this.Level = signalStrength.Level;
        }
    }
}

あとは、変数宣言の型はインターフェースで、実際の代入にはAPI Levelに応じたものを入れ込めばOKです。

private ITelephonyState telephonyState;

public NetworkStrengthWatcher()
{
    if (OperatingSystem.IsAndroidVersionAtLeast(31))
    {
        this.telephonyState = new TelephonyState();
    }
    else
    {
        this.telephonyState = new TelephonyStateOlder();
    }
}

つづいて、状態のコールバックを受け取るための購読処理をおこないます。携帯ネットワーク通信にかかわる情報は「Android.Telephony.TelephonyManager」クラスを使用します。これは「MainActivity」のインスタンスから取得できるので、今回作成したクラスにそのインスタンスを渡してもらい、呼び出します。

private MainActivity activity;
private TelephonyManager? telephonyManager;
private ITelephonyState telephonyState;

public NetworkStrengthWatcher(MainActivity mainActivity)
{
    this.activity = mainActivity;

    // これで取れるはず。GetSystemService の返り値が nullable なので as を使い、
    // 受け取りも nullable にしておきます
    this.telephonyManager = mainActivity.GetSystemService(Context.TelephonyService) as TelephonyManager;

    if (OperatingSystem.IsAndroidVersionAtLeast(31))
    {
        this.telephonyState = new TelephonyState();
    }
    else
    {
        this.telephonyState = new TelephonyStateOlder();
    }
}

次に購読開始のメソッドを用意します。前述の通り、API Versionによって呼び出すメソッドは変わり、また必要なコールバッククラスも変わりますので、バージョンで分岐させつつ呼び出します。コールバッククラスはインターフェースでどちらが入ってもいいようにしてありましたので、ここで本来必要になる実装型に変換してあげればOKです。

public void Listen()
{
    if (OperatingSystem.IsAndroidVersionAtLeast(31))
    {
        // 本当は MainExecutor プロパティは nullable なので見た方がいいかも。ここでは知ったこっちゃない扱いにしておきます。
        this.telephonyManager?.RegisterTelephonyCallback(this.activity.MainExecutor!, (TelephonyState)this.telephonyState);
    }
    else
    {
        this.telephonyManager?.Listen((TelephonyStateOlder)this.telephonyState, PhoneStateListenerFlags.SignalStrengths);
    }
}

念のため購読解除も用意しておきましょう。こちらもAPI Versionによって呼び出すメソッドが変わります。31以降の場合は「UnregisterTelephonyCallback()」メソッドを呼び出すと止まりますが、それより前の場合は明確な停止メソッドがありませんので、「Listen()」メソッドのフラグに「PhoneStateListenerFlags.None」を渡すことになります。

public void Stop()
{
    if (OperatingSystem.IsAndroidVersionAtLeast(31))
    {
        // 素直なメソッドがあるのでそれを使えば止まる
        this.telephonyManager?.UnregisterTelephonyCallback((TelephonyState)this.telephonyState);
    }
    else
    {
        // None を入れてリッスンすると止まる
        this.telephonyManager?.Listen((TelephonyStateOlder)this.telephonyState, PhoneStateListenerFlags.None);
    }
}

これで準備はOKです。あとは実際に使うところですが、いったんここではMainActivityクラス内で宣言し、リッスン開始をするようにします。

// プロジェクトを作った際のいろいろ書いてあるところは省略してあります。
public class MainActivity : MauiAppCompatActivity
{
    private NetworkStrengthWatcher? networkStrengthWatcher;

    protected override void OnStart()
    {
        base.OnStart();

        this.networkStrengthWatcher ??= new NetworkStrengthWatcher(this);
        // サクッとスタート
        this.networkStrengthWatcher.Listen();
    }

    protected override void OnDestroy()
    {
        // ここで止める
        this.networkStrengthWatcher?.Stop();

        base.OnDestroy();
    }
}

もうあとは実行すれば情報がおりてきます。ログもなんも出してないので、コールバッククラスの「OnSignalStrengthsChanged()」メソッド内でなんとなくログを出すようにしておけばよいと思います。
エミュレーターでネットワークの状態をポチポチといじってあげれば、ログにもその情報が逐一入ってきているのがわかります。

ログの出力例

いったんここまでまとめ

長くなったので、携帯ネットワーク通信の分で一旦締めます。ここまでで書いたコードは以下のgistに適当に貼っておきました。
次はWiFiの情報について書きます。
gist.github.com