ページ

2013/12/15

[Android]DayDream機能でスクリーンセーバーを作る

Android 4.2 から DayDream といういわゆるスクリーンセーバー機能が追加されました。
DayDream | Android 4.2 APIs | Android Developers
Daydream: Interactive Screen Savers | Android Developers Blog
DayDream が利用可能な状況は充電中、ドック接続中(車など)のみ(※)です。
(※ユーザーの設定により両方もしくはいずれか)
ユーザーの設定でスクリーンセーバーが有効になっていれば動作可能で、
画面がスリープする代わりに起動設定されているスクリーンセーバーが動作します。
詳しい設定方法は以下のサイトがわかりやすいです。
Android 4.2の新機能、Daydream(スクリーンセーバー)の使い方と設定方法。 - Android(アンドロイド)おすすめアプリ・カスタムニュース|AndroidLover.Net

DayDream の実装には DreamService という Service を継承したクラスを使用します。
DreamService は Activity のように扱うことができます。
その他にも onAttachedToWindow(), onDreamingStarted(), onDreamingStopped(), onDetachedFromWindow() という DreamService 特有のライフサイクルがあります。
詳しくは DreamService | Android Developers を見てください。

簡単なサンプルを作ってみました。wada811/AndroidLibrary@wada811

設定付き DayDream の作り方

<service
    android:name="at.wada811.android.library.demos.daydream.DayDreamDemo"
    android:exported="false"
    android:label="@string/DayDreamDemo" >
    <intent-filter>
        <action android:name="android.service.dreams.DreamService" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
    <meta-data
        android:name="android.service.dream"
        android:resource="@xml/daydream" />
</service>
<activity
    android:name="at.wada811.android.library.demos.daydream.DayDreamSettingsActivity"
    android:label="@string/app_name" >
</activity>
<dream xmlns:android="http://schemas.android.com/apk/res/android"
    android:settingsActivity="at.wada811.android.library.demos/.daydream.DayDreamSettingsActivity" />
public class DayDreamDemo extends DreamService {

    @Override
    public void onAttachedToWindow(){
        super.onAttachedToWindow();
        LogUtils.d();
        setInteractive(PreferenceUtils.getBoolean(getApplicationContext(), getString(R.string.setInteractive), true));
        setFullscreen(PreferenceUtils.getBoolean(getApplicationContext(), getString(R.string.setFullscreen), true));
        setScreenBright(PreferenceUtils.getBoolean(getApplicationContext(), getString(R.string.setScreenBright), true));
        setContentView(R.layout.daydream);
    }

    @Override
    public void onDreamingStarted(){
        super.onDreamingStarted();
        LogUtils.d();
    }

    @Override
    public void onDreamingStopped(){
        super.onDreamingStopped();
        LogUtils.d();
    }

    @Override
    public void onDetachedFromWindow(){
        super.onDetachedFromWindow();
        LogUtils.d();
    }
}
public class DayDreamSettingsActivity extends FragmentActivity {

    final DayDreamSettingsActivity self = this;

    @Override
    protected void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_daydream_settings);

        Switch switchInteractive = (Switch)findViewById(R.id.setInteractive);
        switchInteractive.setChecked(PreferenceUtils.getBoolean(self, getString(R.string.setInteractive), true));
        switchInteractive.setOnCheckedChangeListener(new OnCheckedChangeListener(){
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked){
                PreferenceUtils.putBoolean(self, getString(R.string.setInteractive), isChecked);
            }
        });
        Switch switchFullscreen = (Switch)findViewById(R.id.setFullscreen);
        switchFullscreen.setChecked(PreferenceUtils.getBoolean(self, getString(R.string.setFullscreen), true));
        switchFullscreen.setOnCheckedChangeListener(new OnCheckedChangeListener(){
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked){
                PreferenceUtils.putBoolean(self, getString(R.string.setFullscreen), isChecked);
            }
        });
        Switch switchScreenBright = (Switch)findViewById(R.id.setScreenBright);
        switchScreenBright.setChecked(PreferenceUtils.getBoolean(self, getString(R.string.setScreenBright), true));
        switchScreenBright.setOnCheckedChangeListener(new OnCheckedChangeListener(){
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked){
                PreferenceUtils.putBoolean(self, getString(R.string.setScreenBright), isChecked);
            }
        });
    }
}
以上!実装はすごく簡単です。
DreamService#setScreenBright(boolean) | Android Developers の効果がいまいちわかりませんでした。
DreamService は WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED と
WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD が設定されているので
KEYGUARD_LAYER にいるっぽいです。
参考
表示レイヤーについて - baroqueworksdevの日記
Y.A.M の 雑記帳: Android FLAG_DISMISS_KEYGUARD と FLAG_SHOW_WHEN_LOCKED を使う
Toast や DreamService から起動した Activity などは KEYGUARD_LAYER より下に表示されるので
見ることができません。
また、DreamService はあくまで「 Activity のように」、なので Fragment を扱うことができません。
面倒なので普通の Activity を使いたい場合は以下のように起動させてあげると良いです。

DayDream から Activity を起動する

<service
    android:name="at.wada811.android.library.demos.daydream.DayDreamLauncher"
    android:exported="false"
    android:label="@string/DayDreamLauncher" >
    <intent-filter>
        <action android:name="android.service.dreams.DreamService" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</service>
<activity
    android:name="at.wada811.android.library.demos.daydream.DayDreamActivity"
    android:label="@string/app_name" >
</activity>
public class DayDreamLauncher extends DreamService {

    @Override
    public void onAttachedToWindow(){
        super.onAttachedToWindow();
        Intent intent = new Intent(this, DayDreamActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);
    }
}
public class DayDreamActivity extends FragmentActivity {

    final DayDreamActivity self = this;

    @Override
    protected void onCreate(Bundle savedInstanceState){
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_daydream);
        LogUtils.d();

        Button button = (Button)findViewById(R.id.button);
        button.setOnClickListener(new OnClickListener(){
            @Override
            public void onClick(View v){
                AndroidUtils.showToast(self, R.string.touch);
            }
        });
    }
}
DreamService と同じようにフラグを設定してあげれば
Activity も DreamService と同じレイヤーで表示することが可能です。
参考
JB 4.2 Daydreamの制約を乗り越えようとしてみる。 #androidadvent2012 | Mobile Dev Blog (by @korodroid)
ぺろぺろ会でのDaydreamまとめ #JB42 - ReDo

利用状況がかなり限られていますが競合が少なそうな分野なのでインストールされやすそうですね。
Android4.2 以上の端末の普及とユーザーへのスクリーンセーバーが使えるという認知が広まればの話ですが…。