ページ

2013/12/30

2013年に @wada811 が読んだ技術書まとめ

2013年も終わりに近づいているので@wada811が読んだ技術書を振り返ってみようかと思います。
2012年はこちら→2012年に @wada811 が読んだ技術書まとめ | DevAchieve(約40冊)

2013/03/02 [書評]正規表現辞典 | DevAchieve

正規表現辞典は本当に良い。
通読しても良し、かいつまんで読んでも良し、辞書としても良しの三拍子揃った素晴らしい書籍です。
正規表現が使えれば大抵のテキスト処理は簡単にできるのでエンジニアもそうでない人も読んだほうが良いです。
ついでに僕の Amazon リンクから買うとアフィリエイト料が入ってなお良しです。
(最近、アフィリエイト料率が下がって全然儲からないです。つらい。)

2013/05/31 [書評]モバイルデザインパターン | DevAchieve

デザイン系の書籍は [書評]iPhoneアプリ設計の極意 ―思わずタップしたくなるアプリのデザイン に続いて2冊目。
会社にあったので読んでみた。内容的にはiPhoneアプリ設計の極意とそんなに変わらないかも。
それぞれのデザインパターンについて簡潔な説明でまとめた付録がわかりやすかった。どちらか一冊で良さそう。
もう一冊読むなら一味変えてインタフェースデザインの心理学を読んだほうが得るものありそう。
あと、ユーザビリティで有名っぽいニールセン博士のモバイル・ユーザビリティも微妙に気になってる。
へいしゃー!買ってくれー!間に合わなくなっても知らんぞー!!

2013/08/25 [書評]HTTPの教科書 | DevAchieve

概要を知るには良い感じの本です。挿絵とか文体とか読みやすくてわかりやすかったです。
Web 関連の技術や必要な知識についてHTTPの教科書のテイストで解説している本があったら
Web 系プログラマの新人教育に使えそうな気がします。
Web への攻撃技術という章でセキュリティ関連の解説もわかりやすく行われているので大変良いです。
(引越に際して売ろうかと思っていたけど取っておこうかな)

2013/08/29 [書評]達人に学ぶDB設計 徹底指南書 初級者で終わりたくないあなたへ | DevAchieve

データベース初心者がSQLを極めるためのオススメ書籍6冊の本のうち、読んだ本3冊目です。
ちなみに達人に学ぶ SQL徹底指南書は読んでません。
結構 SQL は書けるようになって複雑なのとか行けるようになったから
そこまで切羽詰まってないので達人に学ぶ SQL徹底指南書はいいかなぁって感じです。
それより DB 設計とかパフォーマンスチューニング的な所を身につけていきたいです。
DB 設計については達人に学ぶDB設計 徹底指南書が良かったので何とかなりましたが、
パフォーマンスチューニングはどこから知識を得ればいいんでしょうね?
数十万件規模のテーブルをジョインジョイントキィするとサービスをKOしてしまって
開発第二ラウンドが発生してしまうので勘弁願いたいんですよねぇ。

1年で書籍4冊の書評を書いた!

2012年は約40冊読んで25冊くらい書評書いたのに今年少なすぎじゃないか?アカン…。
まぁその分DevCameraの開発にかなりの時間を割いたし、
書籍を読む以上に Androider としての技術力が上がったから良いかな?
Android の NDK とか OpenGL, OpenCV のような特殊な事以外だったら
書籍無くてもリファレンスとか見てだいたい実装できそうな気がする。
まぁアニメーションとか UX 的な気持ちよさみたいな部分の実装能力はまだまだだけど。

読んだけど書評を書いていないのは以下の7冊かな。
【改訂新版】 Linuxコマンド ポケットリファレンス
良かった。手元においておきたい感じだけど
普段使うコマンドってそんなに変わらないから使用機会が少ないのが難点。
しかも家に置いてあるという。会社だったらググって解決なので使い所が難しい。
入門UNIXシェルプログラミング
まだ読み途中で積ん読になってる…。
興味はあるし、ちょっとしたシェルスクリプトで手間が省けたら嬉しいから学びたいけど
いかんせん使用機会の少なさがモチベーションの低さになってしまう。
入門 Androidアプリケーションテスト
UnitTest やろうというのが2013年の目標だったので買ってみた。
しかし、開発したアプリがカメラ機能がメインなのでテストがやりにくくて全然やってない。
仕事でカメラとか端末機能を使わないアプリでは UnitTest をやれたので良かったと思う。
Smashing Android UI レスポンシブUIとデザインパターン
デザイン系の書籍3冊目で、珍しく Android をテーマにしたデザインパターン本です。
凄く良い本だったし、ちゃんと書きます。放置しててごめんなさい…!
書きました→[書評]Smashing Android UI レスポンシブUIとデザインパターン | DevAchieve
OpenGL ES 2.0 Androidグラフィックスプログラミング
OpenGL ES を Android から呼び出す方法を解説している本です。コレも記事書きます…!
年をまたいでしまう…!ごめんなさい…!
書きました→[書評]OpenGL ES 2.0 Androidグラフィックスプログラミング | DevAchieve
Effective Java 第2版
ピアソン桐原社の技術書などが在庫限りという事で入手困難に陥る前に買いました。
記事書きます…!読んだらすぐに書評書かないとダメだ…!
書きました→[書評]Effective Java 第2版 | DevAchieve
プレゼンテーションZen 第2版
こちらもピアソン桐原社の書籍で、会社にあったので読みました。
4月になる前にこのへんの書籍の書評を書こう…!

Android も良いけど他のことももっと色々出来るようになっていこう!
そしてちゃんと書評記事を書こう!速さが足りない!

2013/12/23

[Android]Apache Commons Codec で NoSuchMethodError がでる

軽く SHA1 ハッシュするかー、どうせまた使うだろうから Util クラス作るかーと思って
実装方法調べるとアルゴリズム指定する所で文字列で指定するみたい。
このへん決まりきっているんだから定数欲しいよなー
もう作ってる人いるんじゃないのかー?と調べてみたら
Apache Commons Codec があったので使ってみた。

そしたら DigestUtils でエラーが出たので調べると StackOverflow の質問があった。
apache - Method not found using DigestUtils in Android - Stack Overflow
eclipse - NoSuchMethodError using commonc codec in Android application - Stack Overflow
読むのが面倒なのでコピーしてメソッド削ってメソッド名変えて
MessageDigest ってあまり馴染みがないのでクラス名を HashUtils にした。
add HashUtils · 6bb32f1 · wada811/AndroidLibrary-wada811
codec.StringUtils はクラス名衝突が起きる可能性が高かったので EncodingUtils にしておいた。

/*
 * Copyright 2013 wada811<at.wada811@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package at.wada811.utils;
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import at.wada811.codec.EncodingUtils;
import at.wada811.codec.Hex;
import at.wada811.codec.MessageDigestAlgorithms;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class HashUtils {

    private static MessageDigest getDigest(final String algorithm){
        try{
            return MessageDigest.getInstance(algorithm);
        }catch(final NoSuchAlgorithmException e){
            throw new IllegalArgumentException(e);
        }
    }

    private static MessageDigest getMd5Digest(){
        return getDigest(MessageDigestAlgorithms.MD5);
    }

    private static MessageDigest getSha1Digest(){
        return getDigest(MessageDigestAlgorithms.SHA_1);
    }

    private static MessageDigest getSha256Digest(){
        return getDigest(MessageDigestAlgorithms.SHA_256);
    }

    private static MessageDigest getSha512Digest(){
        return getDigest(MessageDigestAlgorithms.SHA_512);
    }

    public static byte[] md5(final byte[] data){
        return getMd5Digest().digest(data);
    }

    public static byte[] md5(final String data){
        return md5(EncodingUtils.getBytesUtf8(data));
    }

    public static String toMD5(final byte[] data){
        return Hex.encodeHexString(md5(data));
    }

    public static String toMD5(final String data){
        return Hex.encodeHexString(md5(data));
    }

    public static byte[] sha1(final byte[] data){
        return getSha1Digest().digest(data);
    }

    public static byte[] sha1(final String data){
        return sha1(EncodingUtils.getBytesUtf8(data));
    }

    public static String toSHA1(final byte[] data){
        return Hex.encodeHexString(sha1(data));
    }

    public static String toSHA1(final String data){
        return Hex.encodeHexString(sha1(data));
    }

    public static byte[] sha256(final byte[] data){
        return getSha256Digest().digest(data);
    }

    public static byte[] sha256(final String data){
        return sha256(EncodingUtils.getBytesUtf8(data));
    }

    public static String toSHA256(final byte[] data){
        return Hex.encodeHexString(sha256(data));
    }

    public static String toSHA256(final String data){
        return Hex.encodeHexString(sha256(data));
    }

    public static byte[] sha512(final byte[] data){
        return getSha512Digest().digest(data);
    }

    public static byte[] sha512(final String data){
        return sha512(EncodingUtils.getBytesUtf8(data));
    }

    public static String toSHA512(final byte[] data){
        return Hex.encodeHexString(sha512(data));
    }

    public static String toSHA512(final String data){
        return Hex.encodeHexString(sha512(data));
    }
}
byte[] を使うか返すかするメソッド使うかなぁ?使用タイミングがわからないけど一応残しておいた。

2013/12/22

[Android]PRNGFixes makes SecureRandom more random

夏に Some SecureRandom Thoughts | Android Developers Blogという記事や
Androidの乱数生成機能の問題、36万以上のアプリに影響する恐れという記事があり、
へーとか思いながらスルーしていたんだけど SecureRandom を使う機会があったので
対策コードを適用した Application クラスを作ってみた。

add PRNGFixes · 1c3d52c · wada811/AndroidLibrary-wada811

/*
 * Copyright 2013 wada811<at.wada811@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package at.wada811.app;

import android.app.Application;
import at.wada811.security.PRNGFixes;
import java.security.SecureRandom;

/**
 * Fixes for the output of the default PRNG having low entropy.
 * {@link PRNGFixes} makes {@link SecureRandom} more random.
 * 
 * @see <a href="http://android-developers.blogspot.jp/2013/08/some-securerandom-thoughts.html">Some
 *      SecureRandom Thoughts | Android Developers Blog</a>
 */
public class AppFixedPRNG extends Application {

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

}

こんな感じで使おう!
public class App extends AppFixedPRNG {

}
<application android:name=".App" >

アプリ内課金のノンスとかも SecureRandom を使っているらしいし、忘れないようにしよう。

2013/12/21

[Android]Toastをカスタマイズして表示時間を変更したりアニメーションを変更したり即時非表示にしたり消えたことを検知したりしたい

この記事は Android Advent Calendar 2013 の 12月21日の記事ではありませんし、
よく考えたら私は Android Advent Calendar 2013 に参加もしていませんでしたが、
Androider の役に立つかもしれない Toast をカスタマイズしたものを公開します。

経緯

Toast の表示時間を Toast#LENGTH_LONG(約4秒),
Toast#LENGTH_SHORT(約2秒)から変更したい!
じゃあ探してみるか、と見つけたのが以下の2つ。
琴線探査: AndroidでToastの表示時間を自由に設定したりアニメーションなしで即非表示したい!
Android Toastの表示時間を長く/短くする 【Android TIPS】
前者はサイズ指定とかが面倒だったのと Toast#makeText の気軽さがなかったので微妙でした。
後者は Toast#makeText 限定でカスタム View を表示できなかったので使いませんでした。

もっと Toast ライクで自由の効く拡張的な実装はないのか!と憤ってしまったので
Toast の実装を眺めて Toast の拡張である Toaster を作ってみました。
Toaster | wada811/AndroidLibrary-wada811

Toast と Toaster の比較

Toast Toaster
任意の表示時間の設定 ×
表示/非表示のアニメーション ×
一括キャンセル ×
非表示時のイベント通知 ×
表示時間にマイナスを設定すれば明示的にキャンセルするまで Toast を消さないようにすることもできます。
Web でよくあるチュートリアルで出てくるポップアップのように使えるかと思います。
アニメーションにこだわりたい開発者のためにアニメーションを設定できるようにしました。
アプリがバックグラウンドに回ったのに投稿ボタンを連打したせいで
「あれ?何かがおかしいようです。」「あれ?何か「あれ?「あれ?「あれ?何かがおかしいようです。」
のように Toast が残り続けることがないように一括キャンセルできるようにしました。
ToastOnDismiss の需要もありそうなのでリスナーをセットできるようにしました。

Toast と Toaster の対応

Toast Toaster
Toast 管理用 Service の起動・実行 システム Toaster
Toast 自体 Toast ToastBread
Toast の表示/非表示 TN Bread
ToastQueue の管理・表示/非表示のイベント通知 NotificationManagerService ToasterService
Toast はキュー管理の Service とその起動がシステム側に組み込まれているので
再現するには自分でキュー管理用の Service を実装する必要がありました。
また、その Service の起動も自分で行う必要があります。

Toaster の使い方

AndroidManifest.xml に Toast 管理用 Service を追加します。

<service android:name="at.wada811.widget.ToasterService" />

Toaster のインスタンス化

@Override
protected void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    mToaster = new Toaster(this);
}
自動的に Toast 管理用 Service が bind されます。
※ ServiceConnection が確立する僅かな時間の間に表示しようとした Toast は無視されます。

Toaster の電源を切る

@Override
protected void onPause(){
    super.onPause();
    mToaster.unplug();
}
自動的に Toast が全てキャンセルされ、Toast 管理用 Service が unbind されます。

動作サンプル

ToasterActivity.java | wada811/AndroidLibrary-wada811 を見てください。

Toaster ができるまで

Toast ってどうやって表示されているの?

システムに常駐している NotificationManagerService でキュー管理されています。
参考: Toast の表示時間を変更する | アンドロイドな日々
フレームワークのソースコードを見に行くと何を行っているかなんとなくわかります。
services/java/com/android/server/NotificationManagerService.java - platform/frameworks/base
OSS だとこういうのが簡単に確認できて素晴らしい。
Toast と Notification の処理が混ざっていたので Toast の部分を切り出したのがこちら。
ToasterService.java | wada811/AndroidLibrary-wada811

Toast は IPC ( Interprocess Communication: プロセス間通信) をしなければならないので
AIDL ( Android Interface Definition Language: Android インターフェイス定義言語 ) で
インターフェースを定義しています。
ITransientNotification.aidl - android-source-browsing - Android Source Browsing Test
Toaster では IPC をする必要がないので 普通にインターフェース ToasterCallback を定義しています。
Toaster$ToasterCallback | wada811/AndroidLibrary-wada811

Toast#makeText のデザインで表示したい

com.android.internal.R.layout.transient_notification を使っているようなので再現しましょう。
Cross Reference: /frameworks/base/core/res/res/layout/transient_notification.xml
<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/res/layout/transient_notification.xml
**
** Copyright 2006, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
**     http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="?android:attr/toastFrameBackground">
    <TextView
        android:id="@android:id/message"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:layout_gravity="center_horizontal"
        android:textAppearance="@style/TextAppearance.Toast"
        android:textColor="@color/bright_foreground_dark"
        android:shadowColor="#BB000000"
        android:shadowRadius="2.75"
        />
</LinearLayout>
上からAndroidソースコード検索サービス - Developer Collaboration Projectで検索していきましょう。
Search android:attr/toastFrameBackground
/frameworks/base/core/res/res/values/
themes.xml  208 <item name="toastFrameBackground">@android:drawable/toast_frame</item>
@android:drawable/toast_frame を SDK から画像リソースをコピーしてきます。
/{AndroidHome}/platforms/android-x/data/res/drawable-x/toast_frame.9.png
以下のページを TextAppearance.Toast で検索。
Diff - 1962e26^^2..1962e26^ - platform/frameworks/base - Git at Google
/core/res/res/values/styles.xml
+    <style name="TextAppearance.Toast">
+        <item name="android:fontFamily">sans-serif-condensed</item>
+    </style>
Search @color/bright_foreground_dark
/packages/apps/Stk/res/values/
colors.xml  19 <color name="bright_foreground_dark">#ffffffff</color>

そこそこ大変でしたがここまで調べてまとめたのが以下のファイルです。
AndroidLibrary@wada811/res/layout/toast.xml | wada811/AndroidLibrary-wada811
Toaster では makeText で Toast 標準のデザインと変わらない表示を出すことができるようになっています。

表示/非表示のアニメーションを Toast 標準に合わせたい

プログラムから使うだけなら android.R.style.Animation_Toast を使えばいいです。
改造するなら以下を調査・変更する必要があります。
core/res/res/values/styles.xml - platform/frameworks/base を Animation.Toast で検索。
<style name="Animation.Toast">
    <item name="windowEnterAnimation">@anim/toast_enter</item>
    <item name="windowExitAnimation">@anim/toast_exit</item>
</style>
toast_enter.xml, toast_exit.xml は /{AndroidHome}/platforms/android-x/data/res/anim/ にあります。
<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/res/anim/fade_in.xml
**
** Copyright 2007, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License"); 
** you may not use this file except in compliance with the License. 
** You may obtain a copy of the License at 
**
**     http://www.apache.org/licenses/LICENSE-2.0 
**
** Unless required by applicable law or agreed to in writing, software 
** distributed under the License is distributed on an "AS IS" BASIS, 
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
** See the License for the specific language governing permissions and 
** limitations under the License.
*/
-->

<alpha xmlns:android="http://schemas.android.com/apk/res/android"
        android:interpolator="@interpolator/decelerate_quad"
        android:fromAlpha="0.0" android:toAlpha="1.0"
        android:duration="@android:integer/config_longAnimTime" />
<?xml version="1.0" encoding="utf-8"?>
<!--
/* //device/apps/common/res/anim/fade_out.xml
**
** Copyright 2007, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License"); 
** you may not use this file except in compliance with the License. 
** You may obtain a copy of the License at 
**
**     http://www.apache.org/licenses/LICENSE-2.0 
**
** Unless required by applicable law or agreed to in writing, software 
** distributed under the License is distributed on an "AS IS" BASIS, 
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
** See the License for the specific language governing permissions and 
** limitations under the License.
*/
-->

<alpha xmlns:android="http://schemas.android.com/apk/res/android"
        android:interpolator="@interpolator/accelerate_quad" 
        android:fromAlpha="1.0" android:toAlpha="0.0"
        android:duration="@android:integer/config_longAnimTime" 
/>

Toast 管理用 Service を作りたい

以下のサイトが参考になりました。
Androidでの常駐型Serviceを使う方法(LocalServiceによる常駐アプリ) | Tech Booster
Androidのバックグラウンド処理に使うServiceのまとめメモ - リア充爆発日記
Service を使ったことがほとんどなかったので使いこなせるようになりたいですが
いまいちよくわからないので詳しく色々解説している書籍など欲しいです。

その他

基本全部コピペでリネーム、Toaster 独自機能の追加でなんとかなりました。

次の人?

Android Advent Calendar 2013じゃないのでいませんでした。。。
もし使ってみてもらって問題があったり、微妙なところがあったら PR してください。
よろしくお願いします。

2013/12/17

[Android]SectionedIndexerを使ってIndex付きListFragmentを作る

電話帳によくある右側のアレを実装してみたくなって検索してみたら
SectionedIndexer がそれっぽいということがわかりました。
じゃあ作ってみようということで作ってみたのがこちら↓です。
add IndexingListFragment · bd3ef29 · wada811/AndroidLibrary-wada811

IndexingListAdapter が implements している SectionIndexer のメソッドの
getSections, getPositionForSection, getSectionForPosition がキモです。
SectionIndexer | Android Developers

そして作ってみたものの動画がこちらです。

スクロールするとスクロールバーのつまみが画面外へ消えていくという現象に悩まされています。
つまみを持ってスクロールすればセクションヘッダーがそれに合って変わっているのがわかりますが、
普通にスクロールするとつまみは画面外へ消えていってしまいます。

ゴチャゴチャ書き換えていたら、スクロールしてもつまみは画面外へ消えないけど
逆につまみを持ってスクロールした時にセクションヘッダーとフキダシが合わないという現象が起きました。
あちらを立てればこちらが立たず…。
誰か解決できたら教えて下さい…。

2013/12/16

[Android]端末が振られたことを加速度センサーから検出する

せっかく SensorFragment を作ったんだから何かサンプルを作りたかったのだけれど
ようやくそれっぽいものができたので公開してみる。
[Android]加速度センサーのノイズ除去とSensorFragment | DevAchieve
もうちょっと面白みがあると良いんだけど何も思いつかなかったから
android.hlidskialf.com/blog/code/android-shake-detection-listener参考に
パクってシェイクを検知する Activity を作った。
SensorActivity.java | wada811/AndroidLibrary-wada811

public class SensorActivity extends FragmentActivity implements SensorCallbackProvider {

    final SensorActivity self = this;

    private static final int SPEED_THRESHOLD = 25;
    private static final int SHAKE_TIMEOUT = 500;
    private static final int SHAKE_DURATION = 1000;
    private static final int SHAKE_COUNT = 3;
    private int mShakeCount = 0;
    private long mLastTime = 0;
    private long mLastAccel = 0;
    private long mLastShake = 0;
    private float mLastX, mLastY, mLastZ = 0;

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

        ArrayList<Integer> sensorTypes = new ArrayList<Integer>();
        sensorTypes.add(Sensor.TYPE_LINEAR_ACCELERATION);
        SensorFragment sensorFragment = SensorFragment.newInstance(sensorTypes);
        getSupportFragmentManager().beginTransaction().add(sensorFragment, SensorFragment.TAG).commit();
    }

    @Override
    public SensorCallback getSensorCallback(){
        return new SensorCallback(){
            @Override
            public void onLinearAccelerationSensorChanged(float x, float y, float z){
                LogUtils.v("x: " + String.format("%+01.2f", x) + ", y: " + String.format("%+01.2f", y) + ", z: " + String.format("%+01.2f", z));
                boolean isShaked = detectShake(x, y, z);
                if(isShaked){
                    AndroidUtils.showToast(self, "shaked!");
                }
            }
        };
    }

    public boolean detectShake(float x, float y, float z){
        boolean isShaked = false;
        // シェイク時間のチェック
        long now = System.currentTimeMillis();
        if(mLastTime == 0){
            mLastTime = now;
        }
        // SHAKE_TIMEOUT までに次の加速を検知しなかったら mShakeCount をリセット
        if(now - mLastAccel > SHAKE_TIMEOUT){
            mShakeCount = 0;
        }
        // 速度を算出する
        long diff = now - mLastTime;
        float speed = Math.abs(x + y + z - mLastX - mLastY - mLastZ) / diff * 10000;
        LogUtils.d("speed: " + speed);
        if(speed > SPEED_THRESHOLD){
            // mShakeCount の加算、SHAKE_COUNT を超えているかのチェック
            // 最後のシェイク時間から SHAKE_DURATION 経過しているかチェック
            if(++mShakeCount >= SHAKE_COUNT && now - mLastShake > SHAKE_DURATION){
                mLastShake = now;
                mShakeCount = 0;
                isShaked = true;
            }
            // SPEED_THRESHOLD を超える速度を検出した時刻をセット
            mLastAccel = now;
        }
        mLastTime = now;
        mLastX = x;
        mLastY = y;
        mLastZ = z;
        return isShaked;
    }

}
それぞれの閾値が適切かどうかはわからないです。
歩いていて手に持った端末が振られたことを検知していたから Xperia A にはちょうどいいかもしれません。
<ポイント>
1.機種によって加速度センサーの閾値が違う。
2.機種によって加速度センサーの搭載位置が違う。
→同じくらいの強さで振っても機種によって取得できる値がまったく違う。
3.端末持つ位置でも当然変わってくる。
加速度センサーを利用した自社Androidアプリ『鮭 – Shake』をリリース!! | R-Labs
これを調整するの難しそうです。
収集した実測値の標準偏差を算出して調整幅のMAX値とMIN値を決定しています。
加速度センサーを利用した自社Androidアプリ『鮭 – Shake』をリリース!! | R-Labs
これが一番良い対処法かもしれません。
このへんは調整だからコスパと重要度と精度を天秤にかけてあげなければいけなさそうです。

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 以上の端末の普及とユーザーへのスクリーンセーバーが使えるという認知が広まればの話ですが…。

2013/12/14

[Android]端末起動時にサービスを起動する

常駐サービスを実装したい場合は端末起動時に自動的にサービスを起動しておきたいので
端末起動時のイベントを通知する Broadcast Intent を受信できるようにして、
そこからサービスを起動したいと思います。

参考
システムの起動時にサービスを実行する « Tech Booster
App Install Location | Android Developers
AndroidのBOOT_COMPLETEDの受信とテスト | 9ensanのLifeHack

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    android:installLocation="internalOnly"
    …>
android.intent.action.BOOT_COMPLETED は 外部SDが端末にマウントされる前にブロードキャストされるので
外部SDにインストールされている場合、Broadcast Intent を受け取ることができません。
また、アプリが外部SDにインストールされている場合、外部SDがアンマウントされると
Service が kill され、マウントしても再開されません。
Intent#ACTION_EXTERNAL_APPLICATIONS_AVAILABLE | Android Developers
Broadcast Intent を受け取って自分で再開する必要があります。

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<receiver
    android:name=".BootReceiver"
    android:permission="android.permission.RECEIVE_BOOT_COMPLETED" >
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</receiver>
Manifest とreceiver にそれぞれ Permission が必要なようです。
public class BootReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent){
        if(Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())){
            context.startService(new Intent(context, BootService.class));
        }
    }

}
上記のように BroadcastReceiver で Service を開始します。

以下のコマンドを実行することで BOOT_COMPLETED のテストが可能です。
adb shell am broadcast -a android.intent.action.BOOT_COMPLETED

Android 4.0 以上からはアプリを外部SDに移すことができなくなったようで、
android:installLocation="preferExternal" を指定していても動きましたが
Xperia A が良い感じにしてくれているだけかもしれませんし、
Android 4.0 未満では動かなくなると思うので
android:installLocation="internalOnly" を指定しておいたほうが良いと思います。
最近の端末は内部容量が 24G とかあってもはやアプリのサイズを気にする必要がほとんど無いんですね。

2013/12/07

[Android]android.graphics.Matrix の set, pre, post メソッドの違い

画像を色々変換しようと思って Matrix クラスを使ってみたら、
Rotate, Scale, Skew, Translate のメソッドにそれぞれ set, pre, post メソッドがあって
どういうことだろうと思ったら以下のサイトを見て解決した。
android.graphics.Matrixの動作 - あおいろ日記
Matrix は行列だし、行列は Matrix だった。
行列といえば掛ける順番で結果が変わるのだから
pre, post 系メソッドは前から掛けるか、後ろから掛けるかの違いで、
set 系メソッドは行列の初期化ということだった。

これだけだとアレなので行う変換の順番によって結果が異なるという例を上げておこうと思う。

回転してから移動


動くかどうかは試してないけどコードで書くならこんな感じ?
Matrix matrix = new Matrix();
matrix.postRotate(90);
matrix.postTranslate(100, 0);

移動してから回転


動くかどうかは試してないけどコードで書くならこんな感じ?
Matrix matrix = new Matrix();
matrix.postRotate(90);
matrix.preTranslate(100, 0);
下のような感じでもいいのかもしれないけど、
回転してから移動と処理順を合わせて条件によって Translate の pre, post 切り替える方が賢いと思う。
Matrix matrix = new Matrix();
matrix.postTranslate(100, 0);
matrix.postRotate(90);