Xamarin.Forms楽しいです。ここ最近毎日触ってます。

先日からタイマーのアプリを作っていまして、そこでとある現象にぶち当たりました。

  • タイマー起動中に画面がスリープモードに移行する

スリープ状態になると当然タイマーは止まります。
そんなアプリはタイマーアプリではありません。困りました。

タイマー起動中に放置しててもスリープ状態にさせないために設定をしていきます。

PCL

今回はスリープしないモードとスリープするモードをメソッド呼び出して切り替えられるように作りたいと思います。
スリープ云々の設定はiOSやAndroid側に書くことになるので、それらを呼び出すinterfaceを作ってやります。

Timer/Interfaces/ISleepScreen.cs
public interface ISleepScreen
{
    void SleepDisabled();

    void SleepEnabled();
}

iOS

iOSの場合、UIApplication.SharedApplication.IdleTimerDisabledを変更することで実現可能だそうです。
trueでスリープしない状態、falseでスリープする状態となります。

Timer.iOS/SleepScreen.cs
using UIKit;

[assembly: Dependency(typeof(SleepScreen))]
namespace Timer.iOS
{
    public class SleepScreen : ISleepScreen
    {
        public void SleepDisabled()
        {
            UIApplication.SharedApplication.IdleTimerDisabled = true;
        }

        public void SleepEnabled()
        {
            UIApplication.SharedApplication.IdleTimerDisabled = false;
        }
    }
}

Android

OnCreateWindow.AddFlags(WindowManagerFlags.KeepScreenOn);するとスリープを抑制できるようです。

Timer.Android/MainActivity.cs
namespace Timer.Droid
{
    [Activity(Label = "Timer", Icon = "@drawable/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
    public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
    {
        protected override void OnCreate(Bundle bundle)
        {
            TabLayoutResource = Resource.Layout.Tabbar;
            ToolbarResource = Resource.Layout.Toolbar;

            base.OnCreate(bundle);

            global::Xamarin.Forms.Forms.Init(this, bundle);
            LoadApplication(new App());

            Window.AddFlags(WindowManagerFlags.KeepScreenOn); 
        }
    }
}

スリープ抑制をメソッド呼び出しで管理できてなくない?

できてない。
だけれども調べたところどうやら非推奨らしい。
http://monobook.org/wiki/Xamarin.Android/%E3%83%87%E3%82%A3%E3%82%B9%E3%83%97%E3%83%AC%E3%82%A4%E3%82%92%E3%82%B9%E3%83%AA%E3%83%BC%E3%83%97%E3%81%95%E3%81%9B%E3%81%AA%E3%81%84

解決策:wakelock
この方法は権限が必要であり、ひとたびディスプレイのスリープを抑制するとアクティビティが遷移しようが何をしようが明示的に解除しない限り延々と抑制される。あまりオススメできない。 よって省略する。

でも制御したいよね

wakelockをちょっと調べてみた。
https://developer.xamarin.com/api/type/Android.OS.PowerManager/
https://developer.xamarin.com/api/type/Android.OS.PowerManager+WakeLock/
https://developer.xamarin.com/api/member/Android.OS.PowerManager.NewWakeLock/p/Android.OS.WakeLockFlags/System.String/

wekelocknewWakeLockで作成したら、それに対してacqure()release()を行う必要があるみたい。
GetSystemServiceがどこにあるのか最初わからず苦労しました。

Timer.Android/SleepScreen.cs
using Android.OS;
using Android.Content;
using Xamarin.Forms;

[assembly: Dependency(typeof(SleepScreen))]
namespace Timer.Droid
{
    public class SleepScreen : ISleepScreen
    {
        PowerManager.WakeLock wl;
        public void SleepDisabled()
        {
            if (wl != null) return;
            PowerManager pm = (PowerManager)(Forms.Context.GetSystemService(Context.PowerService));
            wl = pm.NewWakeLock(WakeLockFlags.ScreenBright | WakeLockFlags.OnAfterRelease, "My Tag");

            wl.Acquire();
        }

        public void SleepEnabled()
        {
            if (wl != null) 
            {
                wl.Release();
                wl = null;
            }
        }
    }
}

Acqure()Release()を別メソッドで呼び出す以上、同一インスタンスを使い続ける必要があります。
DependencyService.Get<T>()で呼び出すときは以下のようにオプションをつけましょう。

DependencyService.Get<ISleepScreen>(DependencyFetchTarget.GlobalInstance).SleepDisabled();

DependencyService.Get<ISleepScreen>(DependencyFetchTarget.GlobalInstance).SleepEnabled();

DependencyFetchTarget.GlobalInstanceを指定することで同じインスタンスで取得できます。
https://developer.xamarin.com/api/type/Xamarin.Forms.DependencyFetchTarget/

また、wakelockを使用するには権限が必要です。
Timer.Android/Properties/AndroidManifest.xmlで権限を与えることができます。
必要なアクセス許可WakeLockを指定しましょう。
スクリーンショット 2017-12-06 22.28.38.png

これでSleepDisabled()SleepEnabled()でスリープ状態の制御ができるようになりました。

おわりに

iOSはあれでいいとして、Androidがなんだかややこしいですね。
Acquire()を呼び出したらRelease()も呼び出す必要があるので、もろもろ考えると素直にwakelockではなくKeepScreenOnを使うのがよさそうな感じもします。どうするのが正解かはよくわからないです。
今回のような形で対応できることがわかったので、必要な際はこのように実装することにします。

明日は kmy72 さんです。よろしくお願いします。