2014/12/20

Google Maps Android API v2 の Marker は Drag 後に Position がアップデートされない

Google Maps Android API v2 では以下のようにドラッグ可能な Marker を作ることができます。

Marker marker = mGoogleMap.addMarker(
        new MarkerOptions()
                .position(location)
                .draggable(true));


GoogleMap クラスは Marker の情報を保存しておいてくれないので、上記のように作成した Marker のインスタンスは何らかの形で自分で保持しておかないといけないという制約があります。
ところが、Marker をドラッグ後に Marker#getPosition() を呼んでも、ドラッグ前の Position しか取得できません。 「ドラッグした」というイベントを捕まえて、自分で更新しないといけないようですちょっとイマイチな仕様だと思うのは私だけでしょうか…?


具体的には以下のような実装で、ドラッグ後のインスタンスに差し替えればよいかと思います。
// Marker を保持するための Map
private Map<String, Marker> mMarkers = new HashMap<>();

@Override
public void onActivityCreated(@Nullable final Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);

    mMapView.getMapAsync(new OnMapReadyCallback() {
        @Override
        public void onMapReady(GoogleMap googleMap) {
            mMap = googleMap;

            // ドラッグ検知用のリスナ登録
            googleMap.setOnMarkerDragListener(mOnMarkerDragListener);

            // 地図上に Marker を表示し、mMarkers に保持する
            for (LatLng location : mLocations) {
                Marker marker = googleMap.addMarker(
                        new MarkerOptions()
                                .position(location)
                                .draggable(true));
                mMarkers.put(marker.getId(), marker);
            }
        }
    });
}

private final GoogleMap.OnMarkerDragListener mOnMarkerDragListener = 
        new GoogleMap.OnMarkerDragListener() {
    @Override
    public void onMarkerDragStart(Marker marker) {
    }

    @Override
    public void onMarkerDrag(Marker marker) {
    }

    @Override
    public void onMarkerDragEnd(Marker marker) {
        // ドラッグが終わったら、インスタンスを差し替える
        mMarkers.put(marker.getId(), marker);
    }
};


2014/12/11

Google Play Services 6.5 でライブラリが分割されたらしいけど、今のところ正しく動作しない様子

2014/12/28 追記
2014/12/28 現在、この問題は既に解決済みです。
どうも、play-services-base への依存関係が各ライブラリに記述されていなかったようです。

表題のように Google Play Services 6.5 でライブラリが分割されたとのこと。
これまでは、Map が使いたいだけなのに Ads とか Drive とかの API も同梱しないといけなかったのが、個別に別れたわけですね。

というわけで、早速使ってみたのですが、残念ながらうまく動作しませんでした。

Setting Up Google Play Services | Android Developers によると

build.gradle
dependencies {
    compile 'com.google.android.gms:play-services:6.5.+'
}



dependencies {
    compile 'com.google.android.gms:play-services-maps:6.5.+'
    compile 'com.google.android.gms:play-services-ads:6.5.+'
}

みたいに書き換えるとオッケーと書いてあるのですが、実際にそのようにして実行してみると

Caused by: java.lang.NoSuchFieldError: com.google.android.gms.R$styleable.AdsAttrs
       at com.google.android.gms.internal.bb.(Unknown Source)
       at com.google.android.gms.internal.bh.(Unknown Source)
       at com.google.android.gms.internal.bh.(Unknown Source)
       at com.google.android.gms.internal.bh.(Unknown Source)
       at com.google.android.gms.ads.AdView.(Unknown Source)
       at java.lang.reflect.Constructor.constructNative(Native Method)
       at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
       at android.view.LayoutInflater.createView(LayoutInflater.java:586)
       at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:680)
       at android.view.LayoutInflater.rInflate(LayoutInflater.java:739)
       at android.view.LayoutInflater.rInflate(LayoutInflater.java:742)
       at android.view.LayoutInflater.rInflate(LayoutInflater.java:742)
       at android.view.LayoutInflater.inflate(LayoutInflater.java:489)
       at android.view.LayoutInflater.inflate(LayoutInflater.java:396)
       at android.view.LayoutInflater.inflate(LayoutInflater.java:352)
       at android.support.v7.app.ActionBarActivityDelegateBase.setContentView(ActionBarActivityDelegateBase.java:228)
       at android.support.v7.app.ActionBarActivity.setContentView(ActionBarActivity.java:102)
       at com.kokufu.test.MainActivity.onCreate(MainActivity.java:84)
       at android.app.Activity.performCreate(Activity.java:4465)
       at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1049)
       at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1931)
       at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1992)
       at android.app.ActivityThread.access$600(ActivityThread.java:127)
       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1158)
       at android.os.Handler.dispatchMessage(Handler.java:99)
       at android.os.Looper.loop(Looper.java:137)
       at android.app.ActivityThread.main(ActivityThread.java:4441)
       at java.lang.reflect.Method.invokeNative(Native Method)
       at java.lang.reflect.Method.invoke(Method.java:511)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:823)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:590)
       at dalvik.system.NativeStart.main(Native Method)

NoSuchFieldError が出てしまいました。
明らかに分割ミスであると思われます。うーん残念。

この問題、力技で回避する強者もいるみたいですが、
これまで通り全部コミコミにしておけば(以下)正常に動作しますので、修正されるのを待つのが得策かと思います。

build.gradle
dependencies {
    compile 'com.google.android.gms:play-services:6.5.+'
}

2014/10/05

AndroidStudio でビルドしたら MissingTranslation エラーが出た時の対処方法

Android Studio で Build → Generate Signed APK... とすると、以下のようなエラーが出てビルドできませんでした。

Error:(4) Error: "app_name" is not translated in "ja" (Japanese) [MissingTranslation]

「app_name を日本語に翻訳していないよ」というエラーなので、意味はわかります。
しかし、これはあえてなんです。全部の文言を日本語に翻訳するのは現実的ではありません。
せめて Warning にしておいてくれればいいのに。
きっと、これを Error にしたのは実際には Translation をあまり使わない英語圏の人たちなんでしょうねぇ。

というわけで、回避する方法を調べました。

個別に回避する方法1

以下のように string タグへ個別に translatable="false" をつけていきます。

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name"
        translatable="false">Test App</string>



</resources >

ある意味、正攻法と言える方法です。
しかし、多言語展開すると「日本語には翻訳するけどフランス語には翻訳しない」等、単純にいかないことも多そうです。
その場合、フランス語(翻訳しない言語)にも同じ文言を書くのが正しい方法なのだと思いますが、数が増えてくると無駄が多くなり、修正も大変になってきます。

なお、この方法は「本来翻訳しないはずのものを翻訳しちゃった」場合に Warning を出してくれます。

個別に回避する方法2

以下のように string タグへ個別に ignore 属性をつけていきます。

res/values/string.xml
<?xml version="1.0" encoding="utf-8"?>
<resources 
    xmlns:tools="http://schemas.android.com/tools">

    <string name="app_name" 
        tools:ignore="MissingTranslation">Test App</string>



</resources >

この方法は translatable を使った方法と同じように見えますが、「本来翻訳しないはずのものを翻訳しちゃった」場合に何の警告も出してくれないので、translatable を使った方が良いでしょう。

まとめて回避する方法1

以下のように ignore 属性をつける場所を親の resources タグにします。

res/values/string.xml
<?xml version="1.0" encoding="utf-8"?>
<resources 
    xmlns:tools="http://schemas.android.com/tools"
    tools:ignore="MissingTranslation">

    <string name="app_name">Test App</string>



</resources >

これで、各ファイル毎にまとめて回避できるようになります。
string_no_translate.xml とか別ファイルにしてグループ分けすると便利かもしれません。
それでも多言語展開した時の問題は残るわけですが。

まとめて回避する方法2

どうせ個々やグループ化で回避しても意味が無いのであれば、本当に全体を回避してしまうのも手かもしれませんとはいえ、lint を完全に切ってしまうのはお勧めは出来ません
以下のように、build.gradle に追記します。

app/build.gradle
android {
    compileSdkVersion 20
    buildToolsVersion '20.0.0'

    lintOptions {
        disable 'MissingTranslation'
    }

    // 省略
}

まとめて回避する方法3

以下のように lint.xml をモジュール直下に配置する方法でも回避可能です。

app/lint.xml
<?xml version="1.0" encoding="UTF-8"?>

<lint>

    <issue id="MissingTranslation" severity="ignore" />

</lint>

この方法、本来ならば severity を warning 等に変えることで、警告レベルを変更できるはずですが、現在のところAndroid Studio (Beta) 0.8.6
Android SDK Tools 23.0.2
どう指定しても ignore になってしまうようです。

2014/09/20

MapsActivity を表示したら Google Play開発者サービスの更新を求められてしまった場合の対処方法

Android で Google Maps v2 対応の MapsActivity や MapFragment を表示しようとすると、以下のようなエラーが出て表示できないことがあります。

「このアプリの実行には、Google Play開発者サービスの更新が必要です。」


このエラー、要は「Google Play開発者サービス」のバージョンが以下の状態になっているということです。

アプリをビルドする時にリンクしたバージョン > デバイスにインストールされているバージョン

そのため、普通は「更新」ボタンを押してデバイスにインストールされている「Google Play開発者サービス」を最新にすれば良いはずです。

問題点

しかし、そう簡単にいかない場合が存在します。
それは Android Studio がデフォルトでリンクするバージョンが Google Play で提供されているものより新しい場合です。
例えば、以下のように Android Studio のメニューから MapsActivity を追加します。

New → Google → Google Maps Activity

この時、dependencies には以下のように ver. 5.2.08 が追加されます(2014/9/19 現在)。

build.gradle
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.google.android.gms:play-services:5.2.08'
    compile 'com.android.support:appcompat-v7:20.0.0'
}

この時、デバイスにインストール出来る最新の「Google Play開発者サービス」は バージョン 5.0.89 です(2014/9/19 現在)。

「アップデートの削除」ボタンが表示されていることからも最新のものがインストールされていることがわかると思います。
つまり、「更新」ボタンを押しても「アンインストール」か「開く」しか出てこず、更新できないわけです…

解決策

デバイスの方は新しくできないので、ビルドに使用するバージョンを下げるしかありません。
以下のように、play-services のバージョンをデバイスのものと合わせます。

build.gradle
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.google.android.gms:play-services:5.0.89'
    compile 'com.android.support:appcompat-v7:20.0.0'
}

これで動くようになるはずです。

2014/09/19

nextFocusForward は imeOptions に actionNext を設定していないと有効にならない

Android の EditText は属性に singleLine を指定していると、自動的に「次へ」というボタンがあらわれ使用しているソフトウェアキーボードによりますが、大抵のものは出てくるはずです、入力後に押すと次の View へ移動してくれるようになります。

この機能、便利なのですが、時々自分の意図していない View に飛んでしまうことがあります。
たとえば、以下のような画面で、矢印のように遷移して欲しいとします。
ところが、デフォルトのままだと、右の View に移動してしまい、想定する動作にはなりません。

こういうときは、nextFocusForward という属性を使用すればよいのですが、単に指定しただけでは駄目です。
以下のように imeOptions に actionNext を指定しないと nextFocusForward は有効にならないのです。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context=".MyActivity">

    <EditText
        android:id="@+id/hello"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello world!"
        android:singleLine="true"
        android:imeOptions="actionNext"
        android:nextFocusForward="@+id/hello2"/>

    <EditText
        android:id="@+id/hello2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/hello"
        android:text="Hello world 2"
        android:singleLine="true"
        android:imeOptions="actionNext"
        android:nextFocusForward="@+id/hello3"/>

    <EditText
        android:id="@+id/hello3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@+id/hello"
        android:text="Hello world 3"
        android:singleLine="true" />

</RelativeLayout>

気付くまでに、結構時間を使ってしまった…

2014/09/14

aapt を実行するも No such file or directory と出てコンパイルができなくなった

Ubuntu 14.04 LTS (64bit) を新規インストールしたマシンで Android プロジェクトをビルドしようとしたら、以下のようなエラーが出てビルド出来ませんでした。
R.java が作成されていないようです。
他のマシンではコンパイルが通っているプロジェクトをそのまま持ってきている形なので、プロジェクトには問題がないはず。

Error executing aapt: Cannot run program "/home/yusuke/local/android-studio/sdk/build-tools/19.1.0/aapt": error=2, No such file or directory: error=2, No such file or directory SQLiteViewer line 1 Android ADT Problem

もちろん、表示されているディレクトリに aapt ファイルは存在しているし、実行権限もあります。
困ったなぁと思って、もう一度よく見てみると、その下に Hint なるものが表示されていました。

Hint: On 64-bit systems, make sure the 32-bit libraries are installed: "sudo apt-get install ia32-libs" or on some systems, "sudo apt-get install lib32z1" SQLiteViewer  line 1 Android ADT Problem

なるほど。
ia32-libs か lib32z1 をインストールすればいいのね。14.04 LTS だと lib32z1 をインストールすれば良い様子。
また、lib32z1 をインストールしただけでは、 libstdc++.so.6 が無いって怒られるので、そちらもインストール。

$ sudo apt-get install lib32z1
$ sudo apt-get install lib32stdc++6

これで、無事ビルドが通るようになりました。

2014/08/17

onActivityResult 内で DialogFragment.show() を実行すると IllegalStateException が発生する

タイトルの通り、FragmentActivity.onActivityResult() 内で DialogFragment.show() を実行すると、以下の様に IllegalStateException が発生して落ちてしまいます。

Caused by: java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState

onActivityResult() が呼ばれる時には Activity は復活しているはずなので、IllegalStateException はおかしいと思うのですが…

調べてみると、Support Library v4 の既知のバグらしい。

確かに、android.support.v4.app.DialogFragmentandroid.app.DialogFragment に変更すると問題なく動作しました。

2.x 系のシェアは 15% 以下になってきたようなので、新規のアプリでは 2.x 系をサポートしないのが良いかもしれません。
とはいえ、既存のアプリは対応せざるを得ず、まだまだ Support Library を使う必要がありそうですが…

回避方法

回避方法を調べてみたのですが、大きく分けると次の 3通りに分類できそうです。

  1. Support Library を使わない
    新規で作成するのであれば、これが正攻法ですね。
    既存のアプリでも、2.x 系のサポートを終わらせて、移行するのも良いかもしれません。
    ただ、残念ながらそうもいかない場合も多いと思います。
  2. DialogFragment.show() を Override して使う
    DialogFragment.show() の中は以下のようになっています。

    android.support.v4.app.DialogFragment (Support Library rev. 20)
        public void show(FragmentManager manager, String tag) {
            mDismissed = false;
            mShownByMe = true;
            FragmentTransaction ft = manager.beginTransaction();
            ft.add(this, tag);
            ft.commit();
        }
    

    そこで、DialogFragment の子クラスで以下のように show() を Override します

    public class TestDialog extends DialogFragment {
        // 省略
    
        @Override
        public void show(FragmentManager manager, String tag) {
            // mDismissed = false;
            // mShownByMe = true;
            try {
                Field mDismissedField = DialogFragment.class.getDeclaredField("mDismissed");
                mDismissedField.setAccessible(true);
                mDismissedField.set(this, true);
                Field mShownByMeField = DialogFragment.class.getDeclaredField("mShownByMe");
                mShownByMeField.setAccessible(true);
                mShownByMeField.set(this, true);
            } catch (NoSuchFieldException |
                    IllegalAccessException |
                    IllegalArgumentException e) {
                throw new RuntimeException(e);
            }
            FragmentTransaction ft = manager.beginTransaction();
            ft.add(this, tag);
            ft.commitAllowingStateLoss();
        }
    
        // 省略
    }
    

    commit()commitAllowingStateLoss() に書き換えたいだけなのですが、リフレクションを使わないといけないため、とても見にくくなってしまいました。
    mDissmissedmShownByMe を無視して書いてある例もありましたが、あまりお勧めはできません。

    この方法、Dialog の状態を保存しておいて、表示の時に復活させようとすると失敗します。
    また、Support Library を更新するたびに、DialogFragment.show() の内容を確認して、修正があったら小クラスの方にも修正を加えなければなりません。
    結構面倒くさいです。
  3. onActivityResult() 内では Dialog を表示せず、onResumeFragments() 内で表示する
    現時点で最も現実的な解はこれかもしれません。
    onActivityResult() 内で Dialog を表示することを諦め、フラグを設定するだけにします。
    そして、onResumeFragments() 内でフラグをチェックして Dialog を表示するようにします。

    public class MainActivity extends FragmentActivity {
        private static final String TEST_DIALOG_TAG = "dialog";
        private static final int NO_DIALOG = -1;
        private int mShowDialogNo = NO_DIALOG;
    
        // 省略
    
        @Override
        protected void onActivityResult(int requestCode, int resultCode, Intent data){
            mShowDialogNo = requestCode;
        }
    
        @Override
        protected void onResumeFragments() {
            super.onResumeFragments();
    
            switch (mShowDialogNo) {
            case 1:
                DialogFragment dialog = (DialogFragment) getSupportFragmentManager().findFragmentByTag(TEST_DIALOG_TAG);
                if (dialog == null) {
                    dialog = new TestDialog();
                }
                dialog.show(getSupportFragmentManager(), TEST_DIALOG_TAG);
                break;
            default:
                break;
            }
            mShowDialog = NO_DIALOG;
        }
    }
    

    記述量は増えてしまいますが、仕方がありません。

補足

一部情報では Support Library rev. 18 で修正されたとあったのですが、rev. 20 で確認したところ、修正されている形跡はありませんでした。
残念ながら、2014年8月16日現在でもこの問題は存在するようです。

2014/08/12

Android の Log をリリース時にだけ表示しないようにする方法

Android に限らず、プログラムを書く上で Log を仕込むのは重要な作業です。
しかし、デバッグ用に出力した Log はリリースビルドでは出力しないようにしたいものです。
Java にはプリプロセッサがありませんので、Android アプリの開発時、私は以下の様な方法(もしくは、同様のライブラリを作成すること)でデバッグ時にログを出力しないようにしてきました。

if (BuildConfig.DEBUG) {
   LOG.d(TAG, "This is a log message");
}

しかし、先日購入した 50 Android Hacks 開発現場ですぐに役立つヒントとコード に、もっとエレガントな方法が載っていました。

それは ProGuard を利用して、後から Log を削除する方法です。
確かに、Android の開発環境ではリリース時にしか ProGuard がかからないようになっていますから、うってつけの方法と言えます何故、今まで気付かなかったのだろうか…

以下、Android Studio と Eclipse での具体的な方法です本の中の tips を勝手に公開してしまいました。著者の方々申し訳ありません。
Android Studio に関してなど+αがあるのでご容赦いただきたいです。
皆様、他にも良い tips が載っていますので、是非本を買ってください。なお、著者の方々と私には一切の利害関係はございません。


Android Studio

proguard-rules.pro に以下の記述を追加

proguard-rules.pro
-assumenosideeffects public class android.util.Log {
    public static *** v(...);
    public static *** d(...);
    public static *** i(...);
    public static *** w(...);
    public static *** e(...);
    public static *** wtf(...);
}

以下のように ProGuard を有効にします。
proguard-android-optimize.txt を使っているところに注意。デフォルトでは proguard-android.txt なので書き換える必要があります。

app/build.gradle
    buildTypes {
        release {
            runProguard true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }


Eclipse

proguard-project.txt に以下の記述を追加。

proguard-project.txt
-assumenosideeffects public class android.util.Log {
    public static *** v(...);
    public static *** d(...);
    public static *** i(...);
    public static *** w(...);
    public static *** e(...);
    public static *** wtf(...);
}

以下のように ProGuard を有効にします。
proguard-android-optimize.txt を使っているところに注意。デフォルトでは proguard-android.txt なので書き換える必要があります。

project.properties
proguard.config=${sdk.dir}/tools/proguard/proguard-android-optimize.txt:proguard-project.txt



2014/07/22

compileSdkVersion を 'android-L' にすると INSTALL_FAILED_OLDER_SDK が発生する

Android Studio を 0.8.1 にして New Project を作成すると、app/build.gradle は以下のようになっています。

app/build.gradle
apply plugin: 'com.android.application'

android {
    compileSdkVersion 'android-L'
    buildToolsVersion "20.0.0"

    defaultConfig {
        applicationId "com.kokufu.test"
        minSdkVersion 8
        targetSdkVersion 'L'
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            runProguard false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}
// 以下略

これ、そのままコンパイルすると、以下のようなエラーが発生してしまいます。
pkg: /data/local/tmp/com.kokufu.test
Failure [INSTALL_FAILED_OLDER_SDK]

もちろん、Android L (API 20, L preview) はインストールしてあります。

で、検索してみたところ、やっぱりバグみたいです。

というわけで、compileSdkVersion を 20 にしてやるとうまくコンパイル出来るようになりました。

app/build.gradle
apply plugin: 'com.android.application'

android {
    compileSdkVersion 20
    buildToolsVersion "20.0.0"

    defaultConfig {
        applicationId "com.kokufu.test"
        minSdkVersion 8
        targetSdkVersion 20
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            runProguard false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}
// 以下略

2014/07/11

Nexus10 用に Android 用有線LANアダプタを買ってみました

有線LANしか無い環境に Nexus 10 を持っていかなければならなくなったので、OTG対応有線LANアダプタを買ってみました。

注意
ただ、最初にお断りしておかねばなりません。
一応、動作はしたのですが、かなり微妙な動きになっているので、決してお勧め出来るものではありません。
「ちゃんと動作した」という報告を期待してこのページに来られた方には申し訳ないのですが、その点御了承の上、お読みください。

購入したのは以下。
Nexus 7 (2012) には対応しているけど、Nexus 7 (2013) には対応していないとのこと。この時点で、結構ドキドキ。



で、届いた商品がこちら。
型番は RCG-MULA01 のようです。
Model No は VK-88772A。Amazon の写真と微妙に違うなぁ。

で、Nexus 10 (Android 4.4.4) につないでみたところ、うんともすんとも言わず…
ダメかなーと思いながら、Nexus 10 を再起動してみると・・・動いたもちろん、WiFiは切っています。!!


よしよし、と思って使っていたのですが、次に使おうと思ってつなぐと動かず…。今度は再起動しても動かず。
しかし、USB を挿抜すると動きました。

現在、どのような状況下でうまく動くのかがハッキリわかっていません。
どうも、有効なIPアドレスが取れるという意味ですLANケーブルを刺した状態でUSBをつなぐのが最低条件のようです。
一度うまくいくと、USBを挿抜しても大丈夫なことが多いです。

今後、何かわかったら情報を更新したいと思います。
何か情報をお持ちの方がいらっしゃいましたら、是非教えて下さい。

ちなみに、Nexus 7 (2012) では今のところ問題なく動作しています。

2014/06/29

BeagleBoard-xM の USER ボタンでシャットダウンさせる方法

@ Ubuntu 13.10 (GNU/Linux 3.13.3-armv7-x10 armv7l)

私は、BeagleBoard-xM を MPD サーバとして使用しているのですが、キーボードもディスプレイも外してしまっています。
そのため、シャットダウンするためにはキーボードとディスプレイをつなぐか、SSH でログインしないといけません。
どっちにせよ、結構面倒くさいです。

というわけで、普段使われていない USER ボタンをシャットダウンボタンに変更してみました。
USER ボタン

/etc/init.d/user_button_shutdown.pl を作成
#!/usr/bin/perl

my $pid = fork;
die "$!" unless defined $pid;

my $comm = $ARGV[0];

if ($pid) {
  exit 0;
}

print $comm;
if ($comm ne "start") {
  exit 0;
}

open (INPUT, '/dev/input/event0') or die "$!";

my $inp = 0;
while( !($inp) ){
    read(INPUT, $inp, 1);
}
close(INPUT);

exec("poweroff");

exit 0;

起動時に実行されるようにする。
$ sudo chown root:root
$ sudo chmod 744
$ sudo update-rc.d user_button_shutdown.pl defaults
update-rc.d: warning: /etc/init.d/user_button_shutdown.pl missing LSB information
update-rc.d: see 
 Adding system startup for /etc/init.d/user_button_shutdown.pl ...
   /etc/rc0.d/K20user_button_shutdown.pl -> ../init.d/user_button_shutdown.pl
   /etc/rc1.d/K20user_button_shutdown.pl -> ../init.d/user_button_shutdown.pl
   /etc/rc6.d/K20user_button_shutdown.pl -> ../init.d/user_button_shutdown.pl
   /etc/rc2.d/S20user_button_shutdown.pl -> ../init.d/user_button_shutdown.pl
   /etc/rc3.d/S20user_button_shutdown.pl -> ../init.d/user_button_shutdown.pl
   /etc/rc4.d/S20user_button_shutdown.pl -> ../init.d/user_button_shutdown.pl
   /etc/rc5.d/S20user_button_shutdown.pl -> ../init.d/user_button_shutdown.pl

問題点

すごく雑に作ったので、 missing LSB information が出ちゃうけど、現実的には問題ないから放置しております…

あと、キーボード等がつながっていると、USR Switch が /dev/input/event1 や event2 に割り当てられてしまう問題が。
動的に確認する方法はないものだろうか?

2014/05/29

Android の Gmail で「この送信者からの画像を常に表示する」を押してしまった場合の解除方法

先日、Android の Gmail アプリで、間違えて「この送信者からの画像を常に表示する」を押してしまいました。


当然、解除出来るものかと思ったら、以下のような投稿が…
なぬ?Android からは出来ないのか?
とりあえず、PC 用の画面から設定変更する方法を試してみるも、どうも連動していない様子。
ちなみに、アカウントを削除して再登録してみるもダメ。

諦めかけたのですが、上記は1年以上も前の投稿。対応されている可能性もあると信じて、さらに調べてみたところ以下を発見。

「画像の許可を取り消す」という項目が。おぉ。出来そう!
注意点は設定項目一覧には無く、メニューボタンを押さないと出てこないところ。
全体設定は最初に確認したのですが、メニューボタン押さなかったなぁ。

2014/05/13

Aar をリリースしようとして artifact bundleRelease を使うとエラーになってしまう問題の回避方法

Gradle でビルドした成果物を Maven Repository に公開する方法が新しくなっていました。

まだ「試験的」な段階なので、即導入すべきものではないですが、aar と jar を同時に公開したい場合などに対応できそうなので試してみました。

build.gradle
apply plugin: 'android-library'
apply plugin: 'maven-publish'

android {
    // 省略
}

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
}

publishing {
    publications {
        aar(MavenPublication) {
            groupId 'com.kokufu.android.lib'
            artifactId 'sample-aar'
            version '1.0'

            artifact bundleRelease
        }
        jar(MavenPublication) {
            groupId 'com.kokufu.android.lib'
            artifactId 'sample-jar'
            version '1.0'

            artifact releaseJar
        }
    }
    repositories {
        maven {
            url "file:${projectDir}/maven-repo"
        }
    }
}

実行してみます。
$ ./gradlew publish

ところが、以下のようなエラーが。
* What went wrong:
A problem occurred configuring project ':SampleProject'.
> Cannot create a Publication named 'bundleRelease' because this container does not support creating elements by name alone. Please specify which subtype of Publication to create. Known subtypes are: MavenPublication

調べてみると、gradle android plugin 0.5.5 以降、"bundleRelease" という task は動的追加に変わったようです。

というわけで、artifact の実行を評価後にしてみました。

build.gradle
apply plugin: 'android-library'
apply plugin: 'maven-publish'

android {
    // 省略
}

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
}

publishing {
    publications {
        aar(MavenPublication) {
            groupId 'com.kokufu.android.lib'
            artifactId 'sample-aar'
            version '1.0'

            // artifact bundleRelease
        }
        jar(MavenPublication) {
            groupId 'com.kokufu.android.lib'
            artifactId 'sample-jar'
            version '1.0'

            // artifact releaseJar
        }
    }
    repositories {
        maven {
            url "file:${projectDir}/maven-repo"
        }
    }
}

afterEvaluate {
    publishing.publications.aar.artifact(bundleRelease)
    publishing.publications.jar.artifact(packageReleaseJar)
}

これで問題なく公開されるようになりました。
少々強引な気がします。他に良い方法をご存知の方は是非教えてください。

このへんは、android-library も Gradle も流動的な仕様のようですので、あまり深追いしな方が良いかもしれません。
また、すぐ使えなくなっちゃいそうですから。

ちなみに、しれっと書いていますが、releaseJar で jar ファイルも一緒に公開しています。


環境
Gradle 1.10
gradle android plugin 0.9.0

2014/03/14

Winbind をインストールしたのに NetBIOS nameでアクセスできない on Ubuntu 13.10

以前うまくいっていた方法で NetBIOS name の解決を試みるもうまくいかず

先日、大分古くなったノートPCに Ubuntu 13.10 環境を構築しました。 その際、winbind をインストールして、nsswitch.conf を編集したにも関わらず、NetBIOS で名前解決が出来ませんでした。

$ sudo apt-get install winbind

/etc/nsswitch.conf
hosts:          files mdns4_minimal [NOTFOUND=return] dns mdns4 wins

$ ping hoge
ping: unknown host hoge

libnss_wins を別途インストールしなければいけなくなった様子

あれれ?と思って、libnss_wins で検索してみたら以下の様な情報が。

おっと、そうですか。
libnss-winbind を別にインストールしないといけなくなったのね。

libnss_wins をインストール

以下のように、別途インストールすると NetBIOS name で名前解決出来るようになりました。

$ sudo apt-get install libnss-winbind

Samba の設定見なおしたりとか、結構無駄な時間を使ってしまった…orz

2014/02/22

BeagleBoard-xM 上に Ubuntu 環境を構築する (Windows 編)

以前、BeagleBoad-xM 上に Ubuntu 環境を構築する方法を書きました。

ただ、この方法、Ubuntuまたは、他の Linux でも可能なはず を使うこと前提で書いたので、今回は Windows で行う方法を書いてみようと思います。
と言っても、Cygwin 使って raw image を焼くだけなので、Linux でも可能です。
以下、Cygwin がインストールされ、基本的なコマンドもインストールされているものとして書いていきます。
参考
BeagleBoardUbuntu - eLinux.org
Method 1: Download a Complete Pre-Configured Image - raw microSD img

Raw Image を用意

Cygwin のコンソール上では以下のようにダウンロードします。
ブラウザ等を用いてもダウンロード可能でしょう。
$ wget https://rcn-ee.net/deb/microsd/saucy/bbxm-ubuntu-13.10-2014-02-16-2gb.img.xz

正しくダウンロード出来たか確認します。
$ echo "3cb914ae8fb848139ba7311b980b54c0 bbxm-ubuntu-13.10-2014-02-16-2gb.img.xz" | md5sum -c
bbxm-ubuntu-13.10-2014-02-16-2gb.img.xz: 完了

解凍します。
Cygwin の unxz で簡単に解凍できますが、Explzh for Windows 等でも解凍出来ました。
$ unxz bbxm-ubuntu-13.10-2014-02-16-2gb.img.xz

SD Card スロットの確認

SD Card をスロットに差し込み、そのデバイスノードを確認します。
$ cat /proc/partitions
major minor  #blocks  name

    8     0 250064896 sda
    8     1  13302784 sda1
    8     2    102400 sda2
    8     3 236657664 sda3
    8    16  15110144 sdb
    8    17  15106048 sdb1

今回、16GB の SDカードを用意しましたので、sdb が対象のデバイスノードだとわかります。

イメージの書き込み

Cygwin コンソールを管理者権限で実行します。

以下のように書き込みます。
注意:/dev/sdb はご自分のデバイスと読み替えてください。誤ったデバイスを指定すると、その内容を全消去してしまいます!
$ dd if=./bbxm-ubuntu-13.10-2014-02-16-2gb.img of=/dev/sdb
3481600+0 レコード入力
3481600+0 レコード出力
1782579200 バイト (1.8 GB) コピーされました、 16.0868 秒、 111 MB/秒

Cygwin が無くとも、Windows 上で dd を行うソフトが様々公開されているようなので、それらを使用すれば可能だと思います。
参考
私は使用していないのですが、以下の様なソフトです。
DD for Windows

パーティションサイズを広げる

起動したら、以下のユーザとパスワードでログインします。
User: ubuntu
pass: temppwd

現時点ではSDカードの 2GB しか使用していないので、サイズを広げます。
$ cd /opt/scripts/tools
$ git pull
$ sudo ./grow_partition.sh
$ sudo reboot

パスワード変更

必須ではないですが、セキュリティ的にはパスワードを変更しておいた方が良いでしょう。
$ passwd
Changing password for ubuntu.
(current) UNIX password: 
Enter new UNIX password: 
Retype new UNIX password: 
passwd: password updated successfully

以上でOSのインストール終了です。

2014/02/20

Windows の Bluetooth SPP で割り当てられた COMポートを変更する方法

Windows で Bluetooth SPP Client 機能を持ったデバイスとペアリングすると、自動的に COMポートが割り当てられます。
しかし、これが変なポートに割り当てられてしまうことも。
このポート番号、以下のようにして変更することが出来ます。

2014/02/19

Windows を Bluetooth SPP Server として使用する方法

Bluetooth SPP の Server 機能を持った機器と Windows をペアリングすると、基本的には COM ポートを自動生成してくれます。
逆に、SPP Client 機器と Windows を通信させたい場合は、Windows 側が Server として待ち受けなければいけませんが、その方法がちょっとわかりにくいので、書いておきたいと思います。

以下の Windows で動作確認をしています。
Windows 7 Professional SP1
Windows 8.1

2014/02/01

Android のログレベル使い分け

Android の Log には以下のようなログレベルが用意されていますが、その使い分けが明確に決まっているわけではありません。
  • Verbose
  • Debug
  • Info
  • Warn
  • Error
  • Assert
プロジェクトでルールが決まっている場合はそれに従えば良いのですが、個人の場合、使い分けに悩みます。
私も、しばらく試行錯誤してきたのですが、ある程度自分のルールが決まったので備忘録として書いておきます。また、そのうち変わるかもしれませんが。

Verbose

一時的なデバッグに使用。
例えば、以下のようにループ中のデータを確認したい場合等です。

        while ((buff = fileInputStream.read()) != -1) {
            Log.v(TAG, "read data " + buff);
            
            // Do something
        }

動作確認が終わった場合は、コードごと消してしまうのが前提。
このコードが残っていると、動作が非常に遅くなりますし、LogCat のバッファがあっという間に埋まってしまいます。
まさに Verbose (冗長)なログです。

リリースする前に検索をかけて Verbose を使用しているところが無いようにします。

Debug

名前のごとく、デバッグ用に使用します。
Verbose と違って定常的なデバッグログ出力用です。
自分(と内部構造に明るいメンバー)に動作状況がわかるような情報を提供します。
例えば、以下のように特定のメソッドが呼ばれたかどうかを観測するために使ったりします。

        view.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d(TAG, "onClick");
                
                // Do something
            }
        });

リリース時、コードは残しますがログが出ないように細工します。(細工については後述)

Info

これもデバッグに必要な情報を出力するのに使用します。
Debug との違いは、そのプロダクトがライブラリだったと仮定し、ブラックボックスとして使っているユーザにも意味のある情報を提供するために使用する点です。
例えば、以下のように読み込んだファイルサイズを出力する等です。

        int size = 0;
        while ((buff = fileInputStream.read()) != -1) {
            // Do something
            size++;
        }
        Log.i(TAG, "The size of read file is " + size);

Debug との違いが微妙なので、判断が難しいこともありますが、あまり厳密に考えないのがポイントです。
リリース時、コードは残しますがログが出ないように細工します。(細工については後述)
ただし、ライブラリとして提供する場合は、ログを出しておく場合もあります。

Warn

本来、起こってはいけないが、場合によっては起こりえるエラーが発生した場合に使用します。
例えば、以下のようにファイルが存在しなかった場合等です。

        try {
            fileInputStream = new FileInputStream(file);
        } catch (FileNotFoundException e) {
            Log.w(TAG, "File not found. : " + file, e);
        }

Warn は必然的に catch句の中に記述されることが多くなります。
余談ですが、catch句の中に記述されるログは上述のように Stack Trace も表示するようにしています。

リリース時、コードは残しますがログが出ないように細工します。(細工については後述)
ただし、ライブラリとして提供する場合は、ログを出しておく場合もあります。

Error

プログラムの構造上起こりえないエラーが発生した時に使用します。
例えば、先ほどの FileNotFoundException も、事前にそのファイルを生成することが保証されているのであれば、Error を使用します。

        if (!file.exists()) {
            // Do something
            return;
        }
        try {
            fileInputStream = new FileInputStream(file);
        } catch (FileNotFoundException e) {
            Log.e(TAG, "File not found. : " + file, e);
        }

上記の例でも、ファイルの存在確認を行った直後に、別のプログラム等にファイルが消されたりすると Error が起こります。
そこまで考慮してプログラムを書いているのであれば、Warn を使用し、そのようなことを考慮していないのであれば Error とするわけです。
基本的には、自分が起こりえないと思っている、もしくは起こる可能性が限りなく低いと思っている事象に対して Error を使います。

Error は、リリース時にもログが出るようにしておきます。

Assert

基本的に使用しません。
このログレベルはシステムに重大な問題が起こった場合のものなので、一般プログラマが使用することは無いと思います。

ログが出ないように細工する件について

私はリリース時にログが出ないように細工をしています。
Java にはプリプロセッサが無いので、ログの出力を切り替えられるクラスを用意しています。
具体的には以下の様なクラスです。

ただ、実際はもっと簡単なテンプレートを用意して使っています。
というのも、個人的には、この手のクラスはライブラリ化するより、都度作った方が良いと思っているので。

2014/8/12 追記

2014/01/03

Proguard を使うと res/raw に "file:///android_res/raw/xxx" でアクセス出来なくなってしまう

Android の res/raw ディレクトリは URI を使ってアクセスすることが出来ます。
以下のように WebView に読み込ませたい場合などは便利です。

WebView webView = (WebView) findViewById(R.id.webview);
webView.loadUrl("file:///android_res/raw/index.html");

しかし、Proguard を使用すると、上記の記述でアクセスすることが出来なくなってしまいます。
こういう場合は、proguard-project.txt に以下の記述を加えれば回避できます。

proguard-project.txt
-keep class **.R$*
-keepclassmembers class **.R$raw

ただし、この方法では res/raw 以下のファイル名全てがそのまま残りますのでご注意を。
特定のファイルだけ残したい場合は以下の Stack Overflow のようにすれば可能です。