DRY

Web関連の技術の事、食事/お酒の事、旅行の事など

LiveWallpaperを試してみる

またまた遅ればせながらですが、デコメ、デコアニメ、着せ替え等は好きなユーザは多いので、Androidのライブ壁紙機能は試しておいて損は無いかなと。

今回は単純に画像(thumbnail.png)をCanvasで適当に移動させるだけの簡単な物を作りました。
重要なのは、AndroidManifest.xmlみたいですね。


<\?xml version="1.0" encoding="utf-8"?> ※(このブログの表示の関係上"\?"にしてます。正しくは"?"のみです)










「android:permission="android.permission.BIND_WALLPAPER」これを明記する必要があります。
の記載はライブ壁紙選択時に出てくる画面というかsettingの制御のようですが、とりあえずいらないのでexported=falseにしておきました。が効いて無い気が。。。


今回はres以下をこんな構成にちょっと変えました

└ res(+)
 ├drawable(+)
  └thumbnail.png
 ├values(+)
  └strings.xml
 ├xml(+)
  └settings.xml
  └wallpaper.xml

xml/wallpaper.xmlは以下のような記載です


<\?xml version="1.0" encoding="utf-8"?> ※(このブログの表示の関係上"\?"にしてます。正しくは"?"のみです)


values/strings.xmlは以下のような記載です
desctiptionタグの記載が、選択画面での説明文になってます。


<\?xml version="1.0" encoding="utf-8"?> ※(このブログの表示の関係上"\?"にしてます。正しくは"?"のみです)

K16WallpaperService
K16Wallpaper
Wallpaper Settings
K16 wallpaper. Presented by k16


ライブ壁紙はActivityではなくServiceとして起動するので、実体のファイルは以下のようになります
すみません。参考にさせて頂いたサイトがあったのですがまた失念してしまいました。


import android.os.Handler;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;

import android.view.MotionEvent;
import android.view.SurfaceHolder;

import android.content.SharedPreferences;
import android.content.res.*;

import android.service.wallpaper.WallpaperService;

import android.util.Log;


public class K16WallpaperService extends WallpaperService
{
public K16WallpaperService ()
{
}

@Override
public void onCreate ()
{
super.onCreate ();
}

@Override
public void onDestroy ()
{
super.onDestroy ();
}

@Override
public Engine onCreateEngine ()
{
return new WallpaperEngine (getResources ());
}

public class WallpaperEngine extends Engine
{
private final Handler m_handler = new Handler ();

private Bitmap m_image;
private int m_px = 0;
private int m_py = 0;
private int m_vx = 10;
private int m_vy = 10;

private boolean m_visible; // 表示状態
private int m_width;
private int m_height;

private final Runnable m_draw_thread = new Runnable ()
{
public void run ()
{
draw ();
}
};

public WallpaperEngine (Resources res)
{
m_image = BitmapFactory.decodeResource (res, R.drawable.thumbnail);
}

@Override
public void onCreate (SurfaceHolder surfaceHolder)
{
super.onCreate (surfaceHolder);
}

@Override
public void onDestroy ()
{
super.onDestroy ();
m_handler.removeCallbacks (m_draw_thread);
}

@Override
public void onVisibilityChanged (boolean m_visible)
{
this.m_visible = m_visible;
if (m_visible)
{
draw ();
}
else
{
m_handler.removeCallbacks (m_draw_thread);
}
}

@Override
public void onSurfaceChanged (SurfaceHolder holder,
int format, int width, int height)
{
super.onSurfaceChanged (holder, format, width, height);
this.m_width = width;
this.m_height= height;
draw ();
}

@Override
public void onSurfaceCreated (SurfaceHolder holder)
{
super.onSurfaceCreated (holder);
}

@Override
public void onSurfaceDestroyed (SurfaceHolder holder)
{
super.onSurfaceDestroyed (holder);
m_visible = false;
m_handler.removeCallbacks (m_draw_thread);
}

private void draw ()
{
SurfaceHolder holder = getSurfaceHolder ();
Canvas c = holder.lockCanvas ();

c.drawColor (Color.WHITE);
c.drawBitmap (m_image, m_px - 50, m_py - 50, null);

holder.unlockCanvasAndPost (c);

// 反転しておきましょか
if (m_px < 0 || m_width < m_px)
m_vx = -m_vx;

if (m_py < 0 || m_height < m_py)
m_vy = -m_vy;

m_px += m_vx;
m_py += m_vy;

m_handler.removeCallbacks (m_draw_thread);
if (m_visible)
m_handler.postDelayed (m_draw_thread, 100);
}
}
}

WallpaperServiceをextendsしたクラスを生成して、onCreate (), onDestroy (), onCreateEngine ()を最低オーバーライドします。
で描画の実体はEngineをextendsしたWallpaperEngineが担っています。
Handlerを利用して描画のリクエストに答えます。
今回はCanvasを利用して簡易的に作ってますが、OpenGL等で作ると面白いものがいろいろ出来そうですね。

これで起動すると、メニューボタンの押下やホーム画面の空いてるとこ長押しで「ライブ壁紙」メニューが出てくるので選択すると今作成したライブ壁紙が出来ているはずです。




それで、いちいちOSの選択画面を起動せずに、自前のActivityとかからライブ壁紙が設定できないかといろいろ試したのですが、結果的には今はダメっぽいです。
恐らく最終的に設定を担っているクラスが見えないんですね。。。

一応軌跡を。

AndroidManifest.xmlをこんな感じに記載するとActivity+Serviceが登録できるので、まずは変更します。


<\?xml version="1.0" encoding="utf-8"?> ※(このブログの表示の関係上"\?"にしてます。正しくは"?"のみです)












http://www.kiwidoc.com/java/l/p/android/android/5/p/android.app/c/IWallpaperManager
http://www.devdaily.com/java/jwarehouse/android/services/java/com/android/server/WallpaperManagerService.java.shtml
http://www.androidjavadoc.com/2.3/android/app/WallpaperManager.html#getIWallpaperManager()

この辺のリンクとかを調べていると、ServiceStartActivityは下記のような感じかなと思うのですが。。。ダメなんです
getIWallpaperManager ()が見えないんですね。


import java.io.IOException;

import android.app.Activity;
import android.app.WallpaperManager;
import android.app.WallpaperInfo;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

public class ServiceStartActivity extends Activity implements OnClickListener
{
private WallpaperManager m_wm;
private Button m_button = null;

@Override
protected void onCreate (Bundle savedInstanceState)
{
super.onCreate (savedInstanceState);
setContentView (R.layout.main);
m_button = (Button)findViewById (R.id.service_start_button);
m_button.setOnClickListener (this);

// WindowManager の取得
m_wm = WallpaperManager.getInstance (this);

// 壁紙の最小の幅,最小の高さの取得
int width = m_wm.getDesiredMinimumWidth ();
int height = m_wm.getDesiredMinimumHeight ();
}

@Override
public void onClick (View v)
{
/* 多分イメージ的にはこんな感じじゃないかな〜??? 的な */
Intent intent = new Intent (ServiceStartActivity.this, K16WallpaperService.class);
//startService (intent);

try
{
// でもここのコンパイルが通らない
m_wm.getIWallpaperManager ().setWallpaperComponent (intent.getComponent ());
}
catch (IOException e)
{
e.printStackTrace ();
}
}
}

ちなみにsetWallpaperを普通に使ったら、thumbnail.pngが固定で背景になっただけでした(笑)そりゃそうだ。

LiveWallpaperPreviewを頂いて整形するとこんな感じで、setLiveWallpaper辺りだと思うんですけどね。名前的に


import android.app.Activity;
import android.app.WallpaperManager;
import android.app.WallpaperInfo;
import android.app.Dialog;
import android.service.wallpaper.IWallpaperConnection;
import android.service.wallpaper.IWallpaperService;
import android.service.wallpaper.IWallpaperEngine;
import android.service.wallpaper.WallpaperSettingsActivity;
import android.content.ServiceConnection;
import android.content.Intent;
import android.content.Context;
import android.content.ComponentName;
import android.os.RemoteException;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.view.ViewGroup;
import android.view.Window;
import android.view.LayoutInflater;
import android.util.Log;
import android.widget.TextView;


public class LiveWallpaperPreview extends Activity
{
static final String EXTRA_LIVE_WALLPAPER_INTENT = "android.live_wallpaper.intent";
static final String EXTRA_LIVE_WALLPAPER_SETTINGS = "android.live_wallpaper.settings";
static final String EXTRA_LIVE_WALLPAPER_PACKAGE = "android.live_wallpaper.package";

private static final String LOG_TAG = "LiveWallpaperPreview";
private WallpaperManager mWallpaperManager;
private WallpaperConnection mWallpaperConnection;
private String mSettings;
private String mPackageName;
private Intent mWallpaperIntent;
private View mView;
private Dialog mDialog;


static void showPreview(Activity activity, int code, Intent intent, WallpaperInfo info) {
Intent preview = new Intent(activity, LiveWallpaperPreview.class);
preview.putExtra(EXTRA_LIVE_WALLPAPER_INTENT, intent);
preview.putExtra(EXTRA_LIVE_WALLPAPER_SETTINGS, info.getSettingsActivity());
preview.putExtra(EXTRA_LIVE_WALLPAPER_PACKAGE, info.getPackageName());
activity.startActivityForResult(preview, code);
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

Bundle extras = getIntent().getExtras();
mWallpaperIntent = (Intent) extras.get(EXTRA_LIVE_WALLPAPER_INTENT);
if (mWallpaperIntent == null) {
setResult(RESULT_CANCELED);
finish();
}

setContentView(R.layout.live_wallpaper_preview);
mView = findViewById(R.id.configure);

mSettings = extras.getString(EXTRA_LIVE_WALLPAPER_SETTINGS);
mPackageName = extras.getString(EXTRA_LIVE_WALLPAPER_PACKAGE);
if (mSettings == null) {
mView.setVisibility(View.GONE);
}

mWallpaperManager = WallpaperManager.getInstance(this);

mWallpaperConnection = new WallpaperConnection(mWallpaperIntent);
}

public void setLiveWallpaper(View v)
{
try
{
mWallpaperManager.getIWallpaperManager().setWallpaperComponent(
mWallpaperIntent.getComponent());
mWallpaperManager.setWallpaperOffsetSteps(0.5f, 0.0f);
mWallpaperManager.setWallpaperOffsets(v.getRootView().getWindowToken(), 0.5f, 0.0f);
setResult(RESULT_OK);
} catch (RemoteException e) {
// do nothing
} catch (RuntimeException e) {
Log.w(LOG_TAG, "Failure setting wallpaper", e);
}
finish();
}

@SuppressWarnings({"UnusedDeclaration"})
public void configureLiveWallpaper(View v) {
Intent intent = new Intent();
intent.setComponent(new ComponentName(mPackageName, mSettings));
intent.putExtra(WallpaperSettingsActivity.EXTRA_PREVIEW_MODE, true);
startActivity(intent);
}

@Override
public void onResume() {
super.onResume();
if (mWallpaperConnection != null && mWallpaperConnection.mEngine != null) {
try {
mWallpaperConnection.mEngine.setVisibility(true);
} catch (RemoteException e) {
// Ignore
}
}
}

@Override
public void onPause() {
super.onPause();
if (mWallpaperConnection != null && mWallpaperConnection.mEngine != null) {
try {
mWallpaperConnection.mEngine.setVisibility(false);
} catch (RemoteException e) {
// Ignore
}
}
}

@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();

showLoading();

mView.post(new Runnable() {
public void run() {
if (!mWallpaperConnection.connect()) {
mWallpaperConnection = null;
}
}
});
}

private void showLoading() {
LayoutInflater inflater = LayoutInflater.from(this);
TextView content = (TextView) inflater.inflate(R.layout.live_wallpaper_loading, null);

mDialog = new Dialog(this, android.R.style.Theme_Black);

Window window = mDialog.getWindow();
WindowManager.LayoutParams lp = window.getAttributes();

lp.width = WindowManager.LayoutParams.MATCH_PARENT;
lp.height = WindowManager.LayoutParams.MATCH_PARENT;
window.setType(WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA);

mDialog.setContentView(content, new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
));
mDialog.show();
}

@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();

if (mDialog != null) mDialog.dismiss();

if (mWallpaperConnection != null) {
mWallpaperConnection.disconnect();
}
mWallpaperConnection = null;
}

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mWallpaperConnection != null && mWallpaperConnection.mEngine != null) {
MotionEvent dup = MotionEvent.obtainNoHistory(ev);
try {
mWallpaperConnection.mEngine.dispatchPointer(dup);
} catch (RemoteException e) {
}
}

if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}

class WallpaperConnection extends IWallpaperConnection.Stub implements ServiceConnection {
final Intent mIntent;
IWallpaperService mService;
IWallpaperEngine mEngine;
boolean mConnected;

WallpaperConnection(Intent intent) {
mIntent = intent;
}

public boolean connect() {
synchronized (this) {
if (!bindService(mIntent, this, Context.BIND_AUTO_CREATE)) {
return false;
}

mConnected = true;
return true;
}
}

public void disconnect() {
synchronized (this) {
mConnected = false;
if (mEngine != null) {
try {
mEngine.destroy();
} catch (RemoteException e) {
// Ignore
}
mEngine = null;
}
unbindService(this);
mService = null;
}
}

public void onServiceConnected(ComponentName name, IBinder service) {
if (mWallpaperConnection == this) {
mService = IWallpaperService.Stub.asInterface(service);
try {
final View view = mView;
final View root = view.getRootView();
mService.attach(this, view.getWindowToken(),
WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY,
true, root.getWidth(), root.getHeight());
} catch (RemoteException e) {
Log.w(LOG_TAG, "Failed attaching wallpaper; clearing", e);
}
}
}

public void onServiceDisconnected(ComponentName name) {
mService = null;
mEngine = null;
if (mWallpaperConnection == this) {
Log.w(LOG_TAG, "Wallpaper service gone: " + name);
}
}

public void attachEngine(IWallpaperEngine engine) {
synchronized (this) {
if (mConnected) {
mEngine = engine;
try {
engine.setVisibility(true);
} catch (RemoteException e) {
// Ignore
}
} else {
try {
engine.destroy();
} catch (RemoteException e) {
// Ignore
}
}
}
}

public ParcelFileDescriptor setWallpaper(String name) {
return null;
}
}
}

なんか根本的に考えが違うのかな???
もっとNativeの方追わないとわからない?