2016/12/28

Android Studio でモジュールを削除する方法

Android Studio1 でモジュールを削除しようとする時、 モジュールを右クリックしてコンテキストメニューを探してしまったことが一度くらいあるはず。 ところが、コンテキストメニューには "Delete" という項目はないのです。

では、どう削除するのかというと、"Project Structure..." メニューから削除できます。

File → Project Structure...

Modules で 対象のモジュールを選択し、"Delete" ボタン(マイナスのアイコン) を押す。

知っていれば何てこと無いのだけど、ちょっと直感的ではないです。

参考

参考リンクを見るとわかるように、仕様がどんどん変わっているようなので、そのうちコンテキストメニューに入るかもしれません。

  1. チェックしたバージョンは 2.2.3 
?
2016/12/27

apk の署名を確認する方法2種

基本的には keytool を使っておけば良いと思うのですが、 apksigner でも確認出来たのでメモ。

署名済みの apk から署名を削除する方法

Debug Keystore で署名済みの apk が送られてきちゃった。みたいな事は時々あると思うのですが、そういう時は署名を削除して再署名すれば良いのです。

apk の署名とは

apk の署名は以下の 3つのファイルで構成されています1。 そのため、これらのファイルを削除すれば、未署名の apk になります。

  • META-INF/MANIFEST.MF
  • META-INF/CERT.RSA
  • META-INF/CERT.SF

注: CERT.RSA と CERT.SF は別の名前(ANDROID.RSA 等)になっている場合があります。

参考

署名を削除する

コマンドラインだと以下のようにすると良いでしょう。

1
2
3
4
$ zip -d app-debug.apk "META-INF/*"
deleting: META-INF/CERT.RSA
deleting: META-INF/CERT.SF
deleting: META-INF/MANIFEST.MF

この方法だと META-INF ディレクトリ自体を削除してしまうのですが、apk の場合 META-INF にこれ以外のファイルが入ることは無さそうなので2、これで大丈夫だと思われます。

以下のように apksigner verify をするとエラーが出ます。

1
2
3
$ apksigner verify app-debug.apk
DOES NOT VERIFY
ERROR: Missing META-INF/MANIFEST.MF

参考

再署名する

未署名の apk に後から署名する方法がそのまま使えます。 zipalign を忘れないようにしましょう。

未署名の apk に後から署名する方法 | 穀風

  1. 各ファイルの役割については参考リンクが詳しいので、そちらを参照してください 
  2. あくまで経験則。要確認。 
?

apksigner で未署名の apk に後から署名する方法

未署名の apk に署名するツールは、build-tools 24.0.3 以降から jarsigner の代わりに apksigner を使うようになりました。 zipalign の実行順序など細かいところが変わっていますのでご注意ください。

なお、署名済みの apk を未署名にする方法は以下を参照してください。

参考

Overview

全体の流れとしては以下のようになります。

  • zip のアライメント調整
  • 署名
  • 検証

具体的には以下。

$ zipalign -v -p 4 my-app-unsigned.apk my-app-unsigned-aligned.apk
$ apksigner sign --ks ~/.android/my-key.jks --ks-key-alias alias-name --out my-app.apk my-app-unsigned-aligned.apk
$ apksigner verify my-app.apk

apksignerzipalignandroid-sdk/build-tools/XX.X.X/ にあり、ここではパスが通ってることとします1

参考

一度経験すれば上記で十分だと思いますが、一応各手順の説明を書いておきます。

zipalign

署名する前に zip のアライメントを整える必要があります2

$ zipalign -v -p 4 my-app-unsigned.apk my-app-unsigned-aligned.apk
  • -v 冗長な情報表示
  • -p 全ての共有ファイルで同じアライメントを使う
  • 4 アライメント [byte]、少なくとも現在は 4 固定

参考

署名

署名ファイル my-key.jks は既に作成済みとします。作成方法は参考リンクを参照してください3

$ apksigner sign --ks ~/.android/my-key.jks --ks-key-alias alias-name --out my-app.apk my-app-unsigned-aligned.apk

--out を指定しなかった場合、元のファイルに上書きされます。 他の項目は自明ということで。

参考

検証

最後に apksigner を利用して検証します。

$ apksigner verify my-app.apk

成功した場合、何も表示されないので注意が必要です。

以下のように -v--print-certs をつけた方がわかりやすいかもしれません。

$ apksigner verify -v app-debug.apk
Verifies
Verified using v1 scheme (JAR signing): true
Verified using v2 scheme (APK Signature Scheme v2): true
Number of signers: 1
$ apksigner verify --print-certs app-debug.apk
Signer #1 certificate DN: CN=Android Debug, O=Android, C=US
Signer #1 certificate SHA-256 digest: ca17bec5dd187fdb4a859b22f5db82b5251ab68b041df5b05dd885432771d5cd
Signer #1 certificate SHA-1 digest: 13199c0c7fdf8734d904925f5aa425c537cf8e69
Signer #1 certificate MD5 digest: ef94f75750de7d124796eb7c5ed38cf2
  1. 実際には、build-tools のバージョン指定があってパスを通すのは結構面倒 
  2. build-tools 24.0.2 以前の zipalignjarsigner の実行以降に行っていたが、apksigner は実行前に行うとのこと 
  3. 上のほうに Android Studio での作成方法も書いてあります 
?
2016/12/20

Android の build.prop に設定値を追加する方法

Android のシステムプロパティを設定する方法は多々あります。

参考

システムプロパティのあれこれ | まくまく Android ノート

その中でもデバイス固有のプロパティを記述するのが /system/build.prop

OS を独自ビルドするような仕事をしていると、この /system/build.prop に設定値を追加したいことがあるのですが、このファイルの元になる情報が分散しているので、まとめておきます。

2016/12/15

MM-BTUD44 を Ubuntu 16.04 で使ってみた

Bluetooth ドングル MM-BTUD44 を買ってみました。

Ubuntu は対応OS に含まれていませんが、私のマシンでは問題なく使えました。

マザーボードは Intel H97 系の GIGABYTE GA-H97-D3H

あくまで、私の環境では動作したという報告になります。 メーカーのサポート対象ではないので、ご購入の際はあくまで 自己責任 でお願いします1

  1. お約束 
?
2016/12/03

Android で Wi-Fi Access Point との接続状態変化をイベントとして取得する

この記事は Android の Wi-Fi 実装に関する情報のまとめ の一部として書かれました

Wi-Fi の接続状態をイベントとして取得するには "android.net.wifi.STATE_CHANGE" action を監視する BroadcastReceiver を使います。

"android.net.wifi.WIFI_STATE_CHANGED" という action もありますが、こちらは、Wi-Fi 機能の状態変化を見るものです。 非常に紛らわしいので注意が必要です。

参考

穀風: Android で Wi-Fi 機能の有効・無効の変化をイベントとして取得する

BroadcastReceiver のコード

NetworkInfo が Extra として渡されるので、接続状態を確認することができます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class WifiConnectionWatcher extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { // "android.net.wifi.STATE_CHANGE"
            NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
            switch (info.getState()) {
                case DISCONNECTED:
                    break;
                case SUSPENDED:
                    break;
                case CONNECTING:
                    break;
                case CONNECTED:
                    break;
                case DISCONNECTING:
                    break;
                case UNKNOWN:
                    Log.e(TAG, "Wifi connection state is UNKNOWN");
                    break;
                default:
                    Log.e(TAG, "Wifi connection state is OTHER");
                    break;
            }
        }
    }
}

参考

穀風: Android で Wi-Fi の接続状態を確認する

登録方法1

AndroidManifest.xml に登録する方法

常に変化を監視したい場合は、以下のように BroadcastReceiver を AndroidManifest.xml に追記します。

1
2
3
4
5
<receiver android:name=".WifiConnectionWatcher">
    <intent-filter>
        <action android:name="android.net.wifi.STATE_CHANGED" />
    </intent-filter>
</receiver>

登録方法2

コードで登録する方法

特定の Activity が起動している間だけ監視したい場合などは、以下のように動的に登録します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Activity 等の Context の中で
WifiConnectionWatcher mWifiConnectionWatcher = new WifiConnectionWatcher();
 
@Override
protected void onResume() {
    super.onResume();
 
    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); // "android.net.wifi.STATE_CHANGE"
 
    registerReceiver(mWifiConnectionWatcher, intentFilter);
}
 
@Override
protected void onPause() {
    super.onPause();
 
    unregisterReceiver(mWifiConnectionWatcher);
}

パーミッション

このコードを実行するのに特別なパーミッションは必要ありません。

2016/12/02

Android で Wi-Fi Access Point との接続を切断する

この記事は Android の Wi-Fi 実装に関する情報のまとめ の一部として書かれました

接続切断の方法は2種類ある

Wi-Fi Access Point との接続を切断するには WifiManager.disconnect() もしくは WifiManager.disableNetwork() を使用します。

WifiManager.disconnect() を使用した場合、状態は "Saved"1 になります。

WifiManager.disableNetwork() を使用した場合、状態は "Disabled"2になります。

"Disabled" といっても、登録してあるパスワード等が消えてしまったわけではありません。 設定画面から "Connect" をクリックしたり、 WifiManager.enableNetwork() を使用することでパスワード入力無しに再接続することが可能です。

違いはあるようで無い

では何が違うのかというと、自動で 再接続するかどうかのようです。 "Saved" の場合、何らかのタイミングで OS が再接続を試みる可能性がありますが、"Disabled" の場合はユーザー3が意思を持って接続しない限り再接続されません。 ただし、後述のようにデバイスを再起動した場合は "Disabled" でも再接続されるので、完全に無効化するわけではないようです。

参考

Android - What´s the difference between WifiManager disableNetwork() and disconnect() - Stack Overflow

また、"Saved" もどのタイミングで再接続されるのかは不定のようです。 上記のサイトでは WifiManager.disconnect() を呼んでから数秒後に再接続されたとありますが、 私の Nexus 7 (2012)4 では 一度 "Saved" 状態になると自動で再接続はされませんでした5

ただし、デバイスを再起動すると接続されます。これは "Disabled" でも同じ。 結局、私の Nexus 7 では、これらの違いがよくわかりませんでした。

コードで Wi-Fi の接続を制御したい場合、勝手に再接続されることはあまり想定しないと思われるので、 WifiManager.disableNetwork() を使っておくのが無難でしょう。

コード1

WifiManager.disconnect() を使用する方法です。

1
2
3
// Activity 等の Context 内で
WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);
boolean succeeded = wm.disconnect();

コード2

WifiManager.disableNetwork() を使用する方法です。

引数として Network ID が必要ですので、 WifiManager.getConnectionInfo() を使用して接続している Access Point の情報を取得します。

1
2
3
4
5
// Activity 等の Context 内で
WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);
WifiInfo wifiInfo = wm.getConnectionInfo();
 
boolean succeeded = wm.disableNetwork(wifiInfo.getNetworkId());

未接続状態でも wifiInfonull にならず、Network ID が -1 のインスタンスが返ってきます。

参考

穀風: Android で 接続している Wi-Fi Access Point の情報を取得する

パーミッション

WifiManager.disconnect()WifiManager.disableNetwork() を実行するには android.permission.CHANGE_WIFI_STATE パーミッションを AndroidManifest.xml で設定する必要があります。

1
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE">

また、 WifiManager.getConnectionInfo() を実行するには android.permission.ACCESS_WIFI_STATE パーミッションを AndroidManifest.xml で設定する必要があります。

1
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE">

  1. 日本語だと「保存済み」 
  2. 日本語だと「無効」 
  3. もしくは、android.permission.CHANGE_WIFI_STATE パーミッションを持ったアプリ 
  4. Android 4.4.4 
  5. 一度 Wi-Fi の圏外に出る等いろいろやってみたのですが 
?
2016/12/01

Android Support Library のバージョン一覧を取得する

使用できる Support Library のバージョン一覧は maven レポジトリの中を参照することで可能です。

2016/11/30

Android で Wi-Fi Access Point に接続する

この記事は Android の Wi-Fi 実装に関する情報のまとめ の一部として書かれました

コード

Wi-Fi Access Point に接続するには WifiManager.enableNetwork() を使用します。

既に登録済みの Access Point に接続する場合は、WifiManager.getConfiguredNetworks() を使って WifiConfiguration を取得します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Activity 等の Context の中で
WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);
 
// targetSsid が既に登録済みの場合
WifiConfiguration targetConfig = null;
for (WifiConfiguration config : wm.getConfiguredNetworks()) {
    if (config.SSID.equals('"' + targetSsid + '"')) { // Quote を取る方向の方が正しいかもしれないが、わかりやすさ重視で
        targetConfig = config;
        break;
    }
}
if (targetConfig != null) {
    wm.enableNetwork(targetConfig.networkId, true);
} else {
    // 登録されてなかった
}

まだ登録されていない Access Point の場合は WifiManager.addNetwork() で登録してから使用します。

参考

穀風: Android で Wi-Fi Access Point を登録する

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Activity 等の Context の中で
WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);
 
// targetSsid が未登録の場合は新規登録を先に行う
// 以下は WPA の例
WifiConfiguration targetConfig = new WifiConfiguration();
targetConfig.allowedKeyManagement.set(KeyMgmt.WPA_PSK);
targetConfig.SSID = '"' + targetSsid + '"';
targetConfig.preSharedKey = '"' + password + '"';
 
// 以下を実行しても targetConfig.networkId は更新されないので
// 返り値の networkId を使う
int networkId = wm.addNetwork(targetConfig);
 
if (networkId != -1) {
    wm.enableNetwork(networkId, true);
} else {
    // 登録失敗
}

パーミッション

WifiManager.enableNetwork() を実行するには android.permission.CHANGE_WIFI_STATE パーミッションを AndroidManifest.xml で設定する必要があります。

1
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />

また、 WifiManager.getConfiguredNetworks() を実行するには android.permission.ACCESS_WIFI_STATE パーミッションを AndroidManifest.xml で設定する必要があります。

1
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
2016/11/26

Android で登録済みの Wi-Fi Access Point を登録解除する

Android で Wi-Fi Access Point を登録する

2016/11/25

Android で Wi-Fi ScanResult を WifiConfiguration に変換する方法

2016/11/24

Android の Wi-Fi Access Point を表す ScanResult, WifiConfiguration, WifiInfo の違い

この記事は Android の Wi-Fi 実装に関する情報のまとめ の一部として書かれました

Android の Wi-Fi Access Point を表す情報は複数あります。 具体的には以下の4種類。

これらは同じような情報を持ちつつ用途が異なるので、ここで整理しておこうと思います。 (ただ、NetworkInfo は少し特殊なので、あえてタイトルには入れませんでした。)

2016/11/17

Ubuntu で Spell Hint をCTRL + ALT + H 以外のキーに割り当てる

Ubuntu で fcitx を使っていると、CTRL + ALT + H は Spell Hint の切り替えに割り当てられています。 しかし、めったに使わない機能なので、IDE 等で使用したい場合は不便です。

2016/11/11

KeePass 2 on Ubuntu でクリップボードにコピーができない

Ubuntu で KeePass1 を便利に使っていたのですが、 いつの日か、ユーザ名やパスワードをコピーできなくなってしまいました2

KeePass のバグかと思っていたのですが、依存するライブラリの問題だったようです。

参考

KeePass / Bugs / #1057 Copy to clipboard does not work at all (Linux mono)

i386 系の xsel をインストールすることで解決しました。

$ sudo apt-get install  xsel:i386
  1. Ver 2.32 
  2. Ctrl + c が動作しない 
?
2016/11/09

Android で付近の Wi-Fi Access Point を検索して一覧を取得する

2018/1/19 追記
ScanResult の level を RSSI に変換する方法を追記しました。

この記事は Android の Wi-Fi 実装に関する情報のまとめ の一部として書かれました

付近の Wi-Fi Access Point を検索する方法です。

コード

私の所持している端末では、Wi-Fi が ON になっている時、定期的に周囲の Access Point の検索が走っているようです1

最後に検索した結果を取得するには WifiManager.getScanResults() を使用します。

1
2
3
4
5
6
7
// Activity 等の Context の中で
WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);
List<ScanResult> scanResults = wm.getScanResults();
 
for (ScanResult scanResult : scanResults) {
    Log.d(TAG, scanResult.toString());
}

ただ、これだと最後に取得した結果がいつのものなのかわかりません2。 そこで、検索が完了した時に呼ばれる BroadcastReceiver を登録し、 その onReceive() の中で先のコードを実行することで、最新のものを取得することにします。

BroadcastReceiver ですが、 全てのアプリが受信可能な通知のため、「止める」という概念がありません。 そのため、以下のような特徴があります。

  • WifiManager.startScan() は呼ばなくても検索が実行されていることが多い3

  • stopScan() という method はない

  • onReceive() は何度も呼ばれる

止められないので、onReceive() が何度も呼ばれるのを防ぐには、unregisterReceiver() することになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class MainActivity extends Activity {
    @Override
    protected void onResume() {
        super.onResume();
 
        // 検索時に呼ばれる BroadcastReceiver を登録
        registerReceiver(mScanResultsReceiver,
                new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
 
        // startScan() は呼ばなくても scan が裏で走っていることが多いけど念の為
        WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);
        wm.startScan();
    }
 
    @Override
    protected void onPause() {
        super.onPause();
 
        try {
            unregisterReceiver(mScanResultsReceiver);
        } catch (IllegalArgumentException e) {
            // 既に登録解除されている場合
            // 事前に知るための API は用意されていない
        }
    }
 
    private final BroadcastReceiver mScanResultsReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            // intent に情報が乗っているわけではないので、
            // WifiManager の getScanResults で結果を取得
            WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);
            List<ScanResult> scanResults = wm.getScanResults();
 
            for (ScanResult scanResult : scanResults) {
                Log.d(TAG, scanResult.toString());
            }
 
            // onReceive() は何度も呼ばれるので、
            // 1度で終了させたい場合はここで unregister する
            try {
                unregisterReceiver(this);
            } catch (IllegalArgumentException e) {
                // 既に登録解除されている場合
                // 事前に知るための API は用意されていない
            }
        }
    };
}

取得できる結果

ScanResult には以下のような情報が入っています。

SSID : my-ssid
wifiSsid : my-ssid
BSSID : aa:bb:cc:dd:ee:ff
capabilities : [WPA-PSK-CCMP][WPA2-PSK-CCMP][WPS][ESS]
frequency : 2437
level : -93
timestamp : 713047320153

frequency は周波数です。この値を見ることで、どの Channel を使用しているかがわかります。

level は 電波強度で、単位は デシベルミリワット[dBm] です。 これを、表示形式(いわゆる RSSI)に変換するには WifiManager.calculateSignalLevel(int, int) を使用します。

また、API Level 23 (Marshmallow) 以降だと以下のような情報他も追加されています。 こちらは 5GHz 帯の情報になります。

centerFreq0 : 5610
centerFreq1 : 0 
channelWidth : 2

パーミッション

WifiManager.startScan() を実行するには android.permission.CHANGE_WIFI_STATE パーミッションを AndroidManifest.xml で設定する必要があります。

1
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />

また、 WifiManager.getScanResults() を実行するには android.permission.ACCESS_WIFI_STATE パーミッションを AndroidManifest.xml で設定する必要があります。

1
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE">

Android 6.0 以降では追加のパーミッションが必要

さらに、Android 6.0 (API Level 23 Marshmallow) 以降では android.permission.ACCESS_COARSE_LOCATION もしくは android.permission.ACCESS_FINE_LOCATION が必要で、 これらは動的に許可を得ねばなりません。

参考

穀風: Android 6.0 以降で WifiManager.getScanResults() を普通に実行しても結果が空のリストになってしまう

  1. 多分、様々なアプリが WifiManager.startScan() を実行しているから? 
  2. ScanResulttimestamp という情報が乗っているので、実はわかるのですが 
  3. 先にも書いたように、様々なアプリが WifiManager.startScan() を実行しているからだと思われます 
?
2016/11/08

Android 6.0 以降で WifiManager.getScanResults() を普通に実行しても結果が空のリストになってしまう

かつては WifiManager.getScanResults() を実行するのに必要なパーミッションは android.permission.ACCESS_WIFI_STATE だけでした。

しかし、そのパーミッションを記述していても、 Android 6.0 (API 23 Marshmallow) 以降で WifiManager.getScanResults() を実行すると スキャン結果が0個になって しまいます。

追加のパーミッションが必要になった

実は、 Android 6.0 以降では、WifiManager.getScanResults() を実行するのに以下の どちらか のパーミッションも必要になりました。 ただ、先に書いたように、このパーミッションを取得していなくても、Exception が発生するのではなく、空のリストが返ってくるため、問題に気づきにくいのが厄介な点です。

  • android.permission.ACCESS_FINE_LOCATION
  • android.permission.ACCESS_COARSE_LOCATION

参考

WifiManager#getScanResults() | Android Developers

パーミッションの取得方法

さらに、LOCATION 関連のパーミッションは AndroidManifest.xml に記述するだけでは不十分で、動的に許可を得ないといけない1ので注意が必要です。

以下に、android.permission.ACCESS_COARSE_LOCATION を取得する方法を示します。

まず、これまでと同様、AndroidManifest.xml に記述します。

1
2
3
4
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
 
<!-- getScanResults() にはこちらも必要 -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

加えて、Activity 等で動的にパーミッションを取得します。

以下に示したコードは必要最低限のものなので、具体的な実装は Android Developers も参考にしてください。

参考

Requesting Permissions at Run Time | Android Developers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class MainActivity extends Activity {
    private final int PERMISSIONS_REQUEST_CODE_ACCESS_COARSE_LOCATION = 0;
 
    @Override
    protected void onResume() {
        super.onResume();
 
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            // 既に許可されているか確認
            if (checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
                != PackageManager.PERMISSION_GRANTED) {
                // 許可されていなかったらリクエストする
                // ダイアログが表示される
                requestPermissions(
                        new String[]{
                                Manifest.permission.ACCESS_COARSE_LOCATION
                        },
                        PERMISSIONS_REQUEST_CODE_ACCESS_COARSE_LOCATION);
                return;
            }
        }
 
        logScanResults();
    }
 
    @Override
    public void onRequestPermissionsResult(
            int requestCode,
            @NonNull String[] permissions,
            @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
 
        if (requestCode == PERMISSIONS_REQUEST_CODE_ACCESS_COARSE_LOCATION
                && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // 許可された場合
            logScanResults();
        } else {
            // 許可されなかった場合
            // 何らかの対処が必要
        }
    }
 
    private void logScanResults() {
        WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);
        List<ScanResult> scanResults = wm.getScanResults();
 
        for (ScanResult scanResult : scanResults) {
            Log.d(TAG, scanResult.toString());
        }
    }
}

上記を実行すると、 以下のようなダイアログが表示され、ユーザーの許可を求めるようになります。

  1. これも Android 6.0 からの仕様 
?
2016/11/07

Android で 接続している Wi-Fi Access Point の情報を取得する

この記事は Android の Wi-Fi 実装に関する情報のまとめ の一部として書かれました

現在接続している Wi-Fi Access Point の情報を取得する方法です。

Access Point の情報は WifiInfo にまとまっているので、これを取得します。

参考

穀風: Android で Wi-Fi の接続状態を確認する

コード

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Activity 等の Context の中で
WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);
 
WifiInfo wifiInfo = wm.getConnectionInfo();
 
// 取得できる情報
// SSID
// BSSID
// Hidden SSID
// Ip Address
// MAC Address
// Frequency
// RSSI
// Link Speed
// Network ID
// Supplicant State

接続していない場合、 null が返ってくるのではなくSupplicantStateINACTIVE なインスタンスが返ってきます。

パーミッション

このコードを実行するには android.permission.ACCESS_WIFI_STATE パーミッションを AndroidManifest.xml で設定する必要があります。

1
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE">
2016/10/30

Android エミュレーターの rootfs に永続的な編集を加える方法

Android エミュレーターで Read-only file system に書き込めるようにする で rootfs は揮発性だと書きました。 つまり、再起動すると加えた変更が元に戻ってしまうのです。

これは、rootfs が Initramfs という RAM 上に展開されるファイルシステムのためです。 RAM 上に展開されたファイルシステムに変更を加えても、その実体には変更が反映されないので 再起動後には消えてしまうというわけです。

この Initramfs、実体は cpio アーカイブを gzip 圧縮したものなので、以下のようにして永続的に変更を反映させることができます。

参考

第384回 Initramfsのしくみ:Ubuntu Weekly Recipe|gihyo.jp … 技術評論社

ramdisk.img の場所を確認

~/.android/avd/AVD_NAME/hardware-qemu.inidisk.ramdisk.path が rootfs の実体となるファイルを示しているので確認します。

disk.ramdisk.path = ANDROID-SDK_DIR/system-images/android-21/default/x86_64//ramdisk.img

ramdisk.img の展開

空のディレクトリ1を作り、先ほど確認した ramdisk.img を展開します。

$ mkdir ramdisk
$ cd ramdisk
$ gunzip -c ANDROID-SDK_DIR/system-images/android-21/default/x86_64/ramdisk.img | cpio -i

ramdisk dir 以下に ramdisk.img の中身が展開されているはずです。

編集

ファイルの追加や編集を行います。

起動に使用する設定ファイルやスクリプトを書き換えると、正常に起動しなくなる可能性があるので注意しましょう。

再アーカイブ

ramdisk ディレクトリにいることを確認し、以下のように圧縮します。

$ pwd
~/ramdisk
$ find . | cpio -R 0:0 -o -H newc | gzip > ../my_ramdisk.img

エミュレーターに登録

以下のようにして、ramdisk.img を入れ替えれば、エミュレーターからマウントされるようになります。

$ cd ANDROID-SDK_DIR/system-images/android-21/default/x86_64
$ mv ramdisk.img ramdisk.img.bak
$ cp ~/my_ramdisk.img ramdisk.img

本来ならば、元の ramdisk.img を書き換えるのは避けたいところです。 しかし、~/.android/avd/AVD_NAME/hardware-qemu.ini を編集してもエミュレーターの起動時にデフォルト設定に書きなおされてしまいます2。 そのため、今回は ramdisk.img を直接変更する方法をとりました。

  1. 名前は何でも良いです 
  2. GUI の AVD Manager が原因かと疑ったのですが、コマンドラインから起動しても同じでした 
?
2016/10/29

Android エミュレーターで Read-only file system に書き込めるようにする

エミュレーター、もしくは root をとったデバイスでも、Read-only file system としてマウントされているディレクトリは、書き込み・編集をすることが出来ません。

しかし、以下のように書き込み権限をつけてリマウントすれば、編集可能になります。

マウント状態の確認

adb shell でエミュレーターにログインし、以下のようにマウント状態を確認。 ro がついているのが Read-only file system です。

$ adb shell
# mount
rootfs / rootfs ro,relatime 0 0
tmpfs /dev tmpfs rw,nosuid,relatime,mode=755 0 0
devpts /dev/pts devpts rw,relatime,mode=600 0 0
proc /proc proc rw,relatime 0 0
sysfs /sys sysfs rw,relatime 0 0
debugfs /sys/kernel/debug debugfs rw,relatime 0 0
none /acct cgroup rw,relatime,cpuacct 0 0
none /sys/fs/cgroup tmpfs rw,relatime,mode=750,gid=1000 0 0
tmpfs /mnt/asec tmpfs rw,relatime,mode=755,gid=1000 0 0
tmpfs /mnt/obb tmpfs rw,relatime,mode=755,gid=1000 0 0
none /dev/cpuctl cgroup rw,relatime,cpu 0 0
/dev/block/vda /system ext4 ro,relatime,data=ordered 0 0
/dev/block/vdb /cache ext4 rw,nosuid,nodev,noatime,errors=panic,data=ordered 0 0
/dev/block/vdc /data ext4 rw,nosuid,nodev,noatime,errors=panic,data=ordered 0 0
/dev/block/vold/253:48 /mnt/media_rw/sdcard vfat rw,dirsync,nosuid,nodev,noexec,relatime,uid=1023,gid=1023,fmask=0007,dmask=0007,allow_utime=0020,codepage=437,iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-ro 0 0
/dev/block/vold/253:48 /mnt/secure/asec vfat rw,dirsync,nosuid,nodev,noexec,relatime,uid=1023,gid=1023,fmask=0007,dmask=0007,allow_utime=0020,codepage=437,iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-ro 0 0
/dev/fuse /storage/sdcard fuse rw,nosuid,nodev,relatime,user_id=1023,group_id=1023,default_permissions,allow_other 0 0
2016/10/28

bootanimation.zip は圧縮してはいけない

Android 起動時のアニメーションは /data/local/bootanimation.zip1/system/media/bootanimation.zip で決まります。

この bootanimation.zip ファイル、PNG ファイルを連番で用意し、構成ファイル desc.txt をトップに格納するというシンプルな構成なので簡単に作成できます。

参考

Deciphering Android's bootanimation.zip desc.txt

しかし、普通に zip ファイルを作るとうまく表示されません2。 この zip ファイル、無圧縮でなければいけないのです。

それに気づかず、数時間使ってしまったー3

無圧縮zip の作り方

Linux 系だと以下のように zip コマンドで作成します。

$ zip -0 bootanimation.zip desc.txt part0/* part1/*

実際にアニメーションを表示しているソースコード

バージョンによって微妙に挙動が変わっているので注意が必要です。

https://android.googlesource.com/platform/frameworks/base/+/master/cmds/bootanimation/

ブートアニメーションをいじろうという人は AOSP 等から自分でビルドする人かと思うので4、 コードは手元にあるという前提なのでしょう。

いろいろなブートアニメーション

蛇足ですが、様々なデバイスのブートアニメーションを集めたサイトがあったのでリンクしておきます。

Boot Animations « Droidboots

  1. Android 5.0 Lollipop 以降は /oem/media/bootanimation.zip に変更になった様子 
  2. 場合によっては起動すらしなくなっちゃうことも 
  3. 実は参考リンク先にも圧縮してはいけないと書いてありました 
  4. エミュレーターでも出来ますが、それやっても、あまり面白くないからなぁ 
?
2016/10/27

Android で Wi-Fi の接続状態を確認する

この記事は Android の Wi-Fi 実装に関する情報のまとめ の一部として書かれました

Wi-Fi 接続して、通信を行っているか等の状態を確認する方法です。

Android の Wi-Fi 接続状態を保持するクラスは WifiInfoNetwrokInfo の2種類があります。 前者は Wi-Fi に特化しているのに対し、後者は接続全般1をカバーします。 状況によって使い分けるのが良いでしょう。

WifiInfo を使うコード

wpa_supplicant が提供する情報を取得する方法です。

Wi-Fi 接続していない場合も null が返るのではなくINACTIVE な状態が返ります。

1
2
3
4
5
// Activity 等の Context 内で
WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);
WifiInfo wifiInfo = wm.getConnectionInfo();
 
WifiInfo.SupplicantState state = wifiInfo.getSupplicantState();

取得できる SupplicantState

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public enum SupplicantState implements Parcelable {
    DISCONNECTED,
    INTERFACE_DISABLED,
    INACTIVE,
    SCANNING,
    AUTHENTICATING,
    ASSOCIATING,
    ASSOCIATED,
    FOUR_WAY_HANDSHAKE,
    GROUP_HANDSHAKE,
    COMPLETED,
    DORMANT,
    UNINITIALIZED,
    INVALID
}

各状態については JavaDoc を参照のこと。

参考

SupplicantState | Android Developers

この SupplicantState ですが、JavaDoc の中に以下のような記述があります。

This is more fine-grained than most users will be interested in. In general, it is better to use NetworkInfo.State.

意訳すると以下のような感じでしょうか

この情報は多くのユーザーにとって詳細すぎる。一般的には NetworkInfo.State を使うほうが良い。

特別な理由が無いのであれば NetworkInfo を使っておいた方が良いのかもしれません。

NetworkInfo を使うコード1

getActiveNetworkInfo() で現在使用中のネットワーク情報を取得し、それが Wi-Fi かどうか確認するという方式です。

なお、ネットワーク接続がない状態だと networkInfonull になります。

1
2
3
4
5
6
7
8
9
10
11
12
13
// Activity 等の Context 内で
ConnectivityManager connectivityManager =
        (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
 
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
if (networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
        // シンプルな状態を取得
        NetworkInfo.State networkState = networkInfo.getState();
 
        // 詳細状態を取得
        // これを使うことはめったに無いと思われる
        NetworkInfo.DetailedState detailedState = networkInfo.getDetailedState();
}

取得できる State

1
2
3
4
5
6
7
8
public enum {
    CONNECTING,
    CONNECTED,
    SUSPENDED,
    DISCONNECTING,
    DISCONNECTED,
    UNKNOWN
}

取得できる DetailedState

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public enum {
    /** Ready to start data connection setup. */
    IDLE,
    /** Searching for an available access point. */
    SCANNING,
    /** Currently setting up data connection. */
    CONNECTING,
    /** Network link established, performing authentication. */
    AUTHENTICATING,
    /** Awaiting response from DHCP server in order to assign IP address information. */
    OBTAINING_IPADDR,
    /** IP traffic should be available. */
    CONNECTED,
    /** IP traffic is suspended */
    SUSPENDED,
    /** Currently tearing down data connection. */
    DISCONNECTING,
    /** IP traffic not available. */
    DISCONNECTED,
    /** Attempt to connect failed. */
    FAILED,
    /** Access to this network is blocked. */
    BLOCKED,
    /** Link has poor connectivity. */
    VERIFYING_POOR_LINK,
    /** Checking if network is a captive portal */
    CAPTIVE_PORTAL_CHECK
}

NetworkInfo を使うコード2 (API Level 21 Lollipop 以降)

getAllNetworks() で使用可能な 全ての ネットワークを取得して、その中から Wi-Fi のものをチェックする方法です。

この方法の落とし穴は、getAllNetworks()接続している ネットワーク一覧しか返さないというところです。 つまり、一般的な端末では getAllNetworks().length は大抵 0 か 1 になります2。 ただし、モバイル接続から Wi-Fi 接続に切り替わる瞬間、少しの間だけ 2 になることもあるようです3

そのため、この方法は実質「NetworkInfo を使うコード1」と一緒と言って良いでしょう。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Activity 等の Context 内で
ConnectivityManager connectivityManager =
        (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
 
for (Network network : connectivityManager.getAllNetworks()) {
    NetworkInfo networkInfo = connectivityManager.getNetworkInfo(network);
    if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
        // シンプルな状態を取得
        NetworkInfo.State state = networkInfo.getState();
 
        // 詳細状態を取得
        NetworkInfo.DetailedState detailedState = networkInfo.getDetailedState();
    }
}

取得できる状態は「NetworkInfo を使うコード1」と同じです。

NetworkInfo を使うコード3 (API Level 20 Kitkat 以前)

この方法は Lollipop 以降 Deprecated になりました。

なお、Wi-Fi 機能がハードウェア的に存在しない場合は、networkInfonull になります。

1
2
3
4
5
6
7
8
9
10
11
// Activity 等の Context 内で
ConnectivityManager connectivityManager =
        (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
 
NetworkInfo networkInfo = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
 
// シンプルな状態を取得
NetworkInfo.State state = networkInfo.getState();
 
// 詳細状態を取得
NetworkInfo.DetailedState detailedState = networkInfo.getDetailedState();

取得できる状態は「NetworkInfo を使うコード1」と同じです。

WifiInfo を使うためのパーミッション

WifiInfo を使うコードを実行するには android.permission.ACCESS_WIFI_STATE パーミッションを AndroidManifest.xml で設定する必要があります。

1
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

NetworkInfo を使うためのパーミッション

NetworkInfo を使うコードを実行するには android.permission.ACCESS_NETWORK_STATE パーミッションを AndroidManifest.xml で設定する必要があります。

1
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
  1. LTE等 
  2. 名前と挙動が合ってないし、あんまり使い勝手が良くないと思うのは私だけ? 
  3. 少なくとも、私の手元の Nexus 7 (2012) と Nexus 6P はそういう挙動でした 
?
2016/10/26

Android で登録済みの Wi-Fi Access Point 一覧を取得する

この記事は Android の Wi-Fi 実装に関する情報のまとめ の一部として書かれました

既に認証済みで接続可能な Wi-Fi Access Point の一覧を取得する方法です。

コード

1
2
3
4
5
6
7
8
9
10
11
12
// Activity 等の Context の中で
WifiManager wm = (WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE);
 
List<WifiConfiguration> wifiConfigurations = wm.getConfiguredNetworks();
if (wifiConfigurations != null) { // Wi-Fi が OFF の場合等は null の可能性がある
    for (WifiConfiguration configuration : wifiConfigurations) {
        // configuration.SSID;
        // configuration.networkId;
        // configuration.status;
        // ...etc
   }
}

パーミッション

このコードを実行するには android.permission.ACCESS_WIFI_STATE パーミッションを AndroidManifest.xml で設定する必要があります。

1
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE">
2016/10/21

Android Studio で Update&Restart がうまくいかない時の対処方法

Unity Launcher1 に Android Studio を登録してから、"Update&Restart" を押しても Update が行われなくなってしまいました。

Android Studio が終了するものの、その後何も起こらず、手動で起動してもアップデートされていないのです。

で、調べてみたところ、既に報告がありました。

参考

Android Studio: "Update & Restart" doesn't work - Stack Overflow

Android Studio を起動する時に余計なオプションをつけると、うまくいかないようです。

また、android-studio のインストールされているディレクトリの書き込み権限がない場合も同じような問題が起こります。 こちらはエラーメッセージにその旨が表示されるのでわかりやすいです。

  1. Ubuntu 16.04 LTS 
?
2016/10/20

Android で Wi-Fi 機能の有効・無効の変化をイベントとして取得する

この記事は Android の Wi-Fi 実装に関する情報のまとめ の一部として書かれました

Wi-Fi の状態変化を知るためには "android.net.wifi.WIFI_STATE_CHANGED" action を監視する BroadcastReceiver を使います。

BroadcastReceiver のコード

WifiManager.getWifiState() で取得できる状態に変化が起こった場合に呼ばれるコードです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class WifiStateWatcher extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { // "android.net.wifi.WIFI_STATE_CHANGED"
            // 変化前の状態を取得
            int previousState = intent.getIntExtra(
                    WifiManager.EXTRA_PREVIOUS_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN);
 
            // 変化後の状態を取得
            int state = intent.getIntExtra(
          WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN);
 
            // 取得できるのは以下の状態
            // WifiManager.WIFI_STATE_DISABLED
            // WifiManager.WIFI_STATE_DISABLING
            // WifiManager.WIFI_STATE_ENABLED
            // WifiManager.WIFI_STATE_ENABLING
            // WifiManager.WIFI_STATE_UNKNOWN
        }
    }
}

参考

穀風: Android で Wi-Fi 機能が現在有効かどうか調べる

登録方法1

AndroidManifest.xml に登録する方法

常に変化を監視したい場合は、以下のように BroadcastReceiver を AndroidManifest.xml に追記します。

1
2
3
4
5
<receiver android:name=".WifiStateWatcher">
    <intent-filter>
        <action android:name="android.net.wifi.WIFI_STATE_CHANGED" />
    </intent-filter>
</receiver>

登録方法2

コードで登録する方法

特定の Activity が起動している間だけ監視したい場合などは、以下のように動的に登録します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Activity 等の Context の中で
WifiStateWatcher mWifiStateWatcher = new WifiStateWatcher();
 
@Override
protected void onResume() {
    super.onResume();
 
    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); // "android.net.wifi.WIFI_STATE_CHANGED"
 
    registerReceiver(mWifiStateWatcher, intentFilter);
}
 
@Override
protected void onPause() {
    super.onPause();
 
    unregisterReceiver(mWifiStateWatcher);
}

パーミッション

このコードを実行するのに特別なパーミッションは必要ありません。

2016/10/19

Android で Wi-Fi 機能を有効・無効にする

この記事は Android の Wi-Fi 実装に関する情報のまとめ の一部として書かれました

Wi-Fi 機能の有効・無効を切り替えるには、WifiManager.setWifiEnabled() を実行します。

コード

1
2
3
4
5
6
7
8
// Activity 等の Context 内で
WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);
 
// 有効にする
wm.setWifiEnabled(true);
 
// 無効にする
wm.setWifiEnabled(false);

パーミッション

このコードを実行するには android.permission.CHANGE_WIFI_STATE パーミッションを AndroidManifest.xml で設定する必要があります。

1
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE">

現実的な実装

実際は、以下のように Wi-Fi の状態を確認してから使用することが多くなると思います1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Click する度に On/OFF を切り替える
@Override
public void onClick(View v) {
    WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);
 
    if (wm.isWifiEnabled()) {
        // Wi-Fi が有効な場合は無効にする
        wm.setWifiEnabled(false);
    } else {
        // 有効にしている最中は意味がないので、状態を確認
        // ただし、WIFI_STATE_ENABLING 中に setWifiEnabled() を呼んでも問題はない
        if (wm.getWifiState() != WifiManager.WIFI_STATE_ENABLING) {
            wm.setWifiEnabled(true)
        }
    }
}
参考
[穀風: Android で Wi-Fi 機能が現在有効かどうか調べる](http://kokufu.blogspot.jp/2016/10/android-wi-fi_60.html)
  1. 以下のコードを実行する場合は、android.permission.ACCESS_WIFI_STATE パーミッションも設定する必要があります 
?

Android で Wi-Fi 機能が現在有効かどうか調べる

この記事は Android の Wi-Fi 実装に関する情報のまとめ の一部として書かれました

Wi-Fi 機能の ON/OFF を調べます。 また、Wi-Fi の ON/OFF 切り替え時には状態があり、より正確な制御をするためには、状態を確認する必要があります。

コード

1
2
3
4
5
6
7
8
9
10
11
12
13
// Activity 等の Context 内で
 
// Wi-Fi ON/OFF の取得
WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);
boolean isWifiEnabled = wm.isWifiEnabled();
 
// 状態の取得
// WifiManager.WIFI_STATE_DISABLED
// WifiManager.WIFI_STATE_DISABLING
// WifiManager.WIFI_STATE_ENABLED
// WifiManager.WIFI_STATE_ENABLING
// WifiManager.WIFI_STATE_UNKNOWN
int wifiState = wm.getWifiState()

パーミッション

このコードを実行するには android.permission.ACCESS_WIFI_STATE パーミッションを AndroidManifest.xml で設定する必要があります。

1
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE">

isWifiEnabled と wifiState との関係

android.net.wifi.WifiManager のソースコード1は以下のようになっています。 つまり、Wi-Fi の変化時、isWifiEnabledfalse であり、細かい状態を wifiState から取得しなければなりません。

1
2
3
public boolean isWifiEnabled() {
    return getWifiState() == WIFI_STATE_ENABLED;
}

GUI上での表示

GUI 上では Wi-Fi の設定から確認可能な項目です。

設定 → 無線とネットワーク → Wi-Fi

  1. Android 7.0 Nougat 
?

Android の Wi-Fi 実装に関する情報のまとめ

ここ数日、Android の Wi-Fi まわりの実装をしていて、その際に必要になった情報をまとめておこうと思います。

Android で Wi-Fi 機能が備わっているか調べる

この記事は Android の Wi-Fi 実装に関する情報のまとめ の一部として書かれました

ハードウェアとして Wi-Fi 機能が備わっているか調べる方法です。

コード

1
2
3
// Activity 等の Context 内で
PackageManager pm = getPackageManager();
boolean hasWifi = pm.hasSystemFeature(PackageManager.FEATURE_WIFI);

パーミッション

特別なパーミッションは必要ありません

2016/10/06

Proguard が "Ignoring InnerClasses attribute for an anonymous inner class" という Warning を出した場合の修正方法

Android Studio を更新したら、Proguard が "Ignoring InnerClasses attribute for an anonymous inner class" という Warning を大量に吐くようになってしまいました。

2016/09/13

インストールしているのに Sources for 'Android API 24 Platform' not found. が出てしまう

@Android Studio 2.1.3

compiledSdkVersion を 24 にしたところ、Sources for 'Android API 24 Platform' not found. が出るようになってしまいました。 以下のようにソースコードはインストールしているのにも関わらず。

File → Settings → Appearance & Behavior → System Settings → Android SDK

Refresh を押してもウンともスンとも言いません。

2016/09/08

React.render is not a function

今更ながら React を使い始めました。 しかし、サンプルを動作させようとしたところ、render function が無いとエラーが発生。

Uncaught TypeError: React.render is not a function
2016/08/24

Android Studio で Documentation がポップアップされるようになったので修正

Ubuntu 16.04 LTS
Android Studio 2.1.3

Android Studio をクリーンインストールしたら、Ctrl + Space するたび1Documentation なるウィンドウがポップアップするようになってしまいました。 これ、とても鬱陶しいので修正しました。

2016/08/04

最新版の update-java-alternatives は javac を更新してくれない

2018/2/12 追記
java-common 0.59 で対応されたようです。
ただし、java-common 0.59 の適用は Ubuntu 17.10 からです。ご注意を。

@Ubuntu 16.04 LTS

先日、update-java-alternatives に Oracle java も追加する という投稿をしたのですが、 使っていると想定した動作をしていないことがわかりました。

2016/07/31

update-java-alternatives に Oracle java も追加する

@Ubuntu 16.04 LTS

2016/8/4 追記
最新の update-java-alternatives は javac 等を更新してくれないバグがあります。 一時的なものだと思われますが、ご注意ください。

穀風: 最新版の update-java-alternatives は javac を更新してくれない

update-java-alternatives には --install のようなコマンドがないため、簡単に対象を追加することは出来ません1

ただ、その中身は単なる update-alternatives2 のラッパースクリプトなので、動作を理解すれば、任意の JDK (JRE) を手動で登録して、切り替えられるようにすることが可能です。

今回は Oracle から jdk-8u101 をダウンロードして追加してみました。

2016/07/13

KeePass2 on Ubuntu の文字化けを直す

Ubuntu 16.04 LTS にしたら KeePass2 のサーチ窓が文字化けするようになってしまいました1。 各項目の文字化けは以前対応したのですが、検索窓は初めて。

2016/07/12

Ubuntu のシステムフォントを変更する方法

@Ubuntu 16.04 LTS

いつの間にか System Settings... からシステムフォントの設定が消えていました1。 普段、日本語化しないで使っているため必要なかったのですが、いざ変更しようとするとこれは不便。 というわけで、方法をメモっておきます2

2016/07/05

Atom で notification を表示する方法

2016/06/23

Atom で Package をリロードする方法

Atom の Package 開発時には、都度 Package をリロードして変更を反映させる必要があります。
調べてみたところ、特定の Package をリロードするのではなく、Atom 自身を再起動させるという方針のようです。

参考
Package: Modifying Text の "Reload the Package"

2016/05/28

"Gradle version x.xx is required" が出たときの対処方法

2016/05/16

Chrome 間でブックマーク等の同期が出来なくなった場合の対処方法

自分用メモ

Chrome の同期機能がうまく動かなかったら、 Chrome Sync にアクセスして「同期をリセット」してみると良い。

その後、Chrome の設定からログインし直す。

2016/05/13

Emacs で日本語を保存しようとすると "undecided-unix cannot encode" というエラーが出るようになったので修正してみた

日本語のメッセージ保存でエラー

私は Git のエディタとして Emacs を使用しています。
つい最近まで問題なく使用していたのですが、ここ最近、日本語(マルチバイト)のメッセージを保存しようとすると以下のようなエラーが出るようになってしまいました。
都度、コーディングシステムを指定すれば保存は出来るのですが、不便なので原因を探ってみました。

2016/04/24

Python の FTP_TLS で "522 Data connections must be encrypted." というエラーが出たら

login の後に FTP_TLS.prot_p() を呼ぶ

1
2
3
4
5
6
7
8
from ftplib import FTP_TLS
 
ftp = FTP_TLS(site, user, password)
try:
    ftp.prot_p()
    # 何か処理
finally:
    ftp.quit()
2016/04/21

Python 3 で配列の型を一括変換する方法

自分のための備忘録として (for Python 31)

以下のような文字列配列を int に変更する

1
2
>>> data
['12', '345', '6789']

map を使う方法

1
2
>>> list(map(int, data))
[12, 345, 6789]

Python 3.1 より以前は list() は必要ないので注意。

参考
Python3でmapがmap objectを返す(ようになった)件 | swimmingpython blog

リスト内包表記2を使う方法

1
2
>>> [ int(x) for x in data]
[12, 345, 6789]
  1. 動作確認は Python 3.4.3 
  2. List Comprehensions 
?
2016/04/18

Nautilus を開くと常に隠しファイルが表示されるようになってしまった

@Ubuntu 15.10

いつの頃からか Nautilus を開くと、いつも隠しファイルが表示されるようになってしまいました。 Ctrl-h で表示されなくなるものの、Window を閉じてもう一度開くとまた表示されてしまいます。

gnome のバグだった

別に大きな問題になるわけではないのですが、気持ち悪いので調べてみたところ gnome のバグみたいです。 2015年になって再発した模様。

参考
Bug #1171852 “Hidden files are displayed by default” : Bugs : Ubuntu GNOME

回避方法

私の環境では以下の方法で問題は起こらなくなりました。

$ dconf-editor

gtk → settings → file-chooser → show-hidden を消す

ただ、この方法はあくまで一時的な回避方法です。 Software Update をかけたりすると、元に戻ることがあります1。 次のメジャーアップデートまでにはちゃんと直ると良いのですが2

  1. そう頻繁ではないのですが、その都度直さなければなりません 
  2. 他力本願だなぁ… 
?
2016/03/18

m2ts ファイルを無劣化で mov に変換する方法

AVCHD で保存した動画を一気に変換する必要があったのでメモ。

2016/03/12

marked.js の code highlight を SyntaxHighlighter にする

2016/03/11

Blogger を Markdown で書けるようにしたけど、Blockquote が表示されない

marked.js を使用すれば Blogger でも Markdown で記事が書けそうなので、ちょっとやってみました。

参考
BloggerでMarkdown書けるようにした。 - Qiita

ただ、そのままだと Blockquote が上手く処理できず、ちょっと工夫してやる必要があったので書いておきます1

2016/02/14

AdView は動的追加にしないとメモリリークする

私が Google Play で公開している Dive! Log! というアプリに少し大きめの画像を扱う機能を追加したところ、どうもメモリリークしているような挙動を示すようになりました。 Heap Dump を見てみたところ、Google AdMob の AdView まわりが参照を握っている様子。
こういう時の常套手段として、AdView しかないアプリを作って動作を確認してみることにしました。

2016/02/11

Android 6.0 で EXTERNAL_STORAGE へアクセス許可しても即時反映されない

Android 6.0 以降から個別のパーミッション設定が可能になった

Android 6.0 Marshmallow 以降では各パーミッションを個別に設定できるようになりました。 ユーザにとっては有り難い機能ですが、開発者としてはエラーの原因が増えるので辛いところです。

Storage 関連のパーミッションはすぐに反映されない

特に android.permission.WRITE_EXTERNAL_STORAGEandroid.permission.READ_EXTERNAL_STORAGE は許可を動的に取得してもすぐに反映されないので注意が必要です。
というのも、External Storage へのアクセスは Linux の基本機能であるファイルへのアクセス制限を用いて実現されているからです。
具体的には sdcard_rw というグループへのアクセス許可があるかどうかを Linux Kernel が判断しています。そして、実行中のプロセスに関しては設定が反映されません1

反映させるためにはアプリを再起動する

少なくとも現時点では、この問題を回避する方法はアプリごと再起動するしか解が無いように思います。

具体例

実際のコードを載せておきます2
まず、Activity の onResume など使用する直前にパーミッションを確認します。

1
2
3
4
5
6
7
8
9
10
@Override
protected void onResume() {
    super.onResume();
 
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(this,
                new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
                REQUEST_PERMISSIONS_WRITE_EXTERNAL_STORAGE);
    }
}

WRITE_EXTERNAL_STORAGE への許可がない場合、以下のようにユーザへ許可を求めるダイアログが表示されます。

ユーザの対応が onRequestPermissionsResult で返ってくるので、対応を決めます。一般的なパーミッションだとそのまま処理を続行することになりますが、Storage 系のパーミッションの場合、以下のように アプリの再起動をして設定を反映させます3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
        @NonNull int[] grantResults) {
    switch (requestCode) {
        case REQUEST_PERMISSIONS_WRITE_EXTERNAL_STORAGE:
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // 許可された場合
                // 設定を反映させるためにアプリを再起動
                Intent activityIntent = new Intent(this, ThisActivity.class); // 自Activity を指定
                int pendingIntentId = 1;
                PendingIntent pendingIntent = PendingIntent.getActivity(this, pendingIntentId, activityIntent, PendingIntent.FLAG_CANCEL_CURRENT);
                AlarmManager mgr = (AlarmManager)getSystemService(ALARM_SERVICE);
                mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, pendingIntent);
                System.exit(0);
            } else {
                // 許可されなかった場合
                // TODO エラーメッセージを表示して Activity を終了させる等
            }
            break;
        default:
            // Do nothing
            break;
    }
}

参考

  1. Android OS の動作が今後変わる可能性はありますが 
  2. 最低限のものなので、細かいお作法に関しては公式のドキュメントを参考にしてください 
  3. Activity ではなくアプリプロセスの再起動が必要です 
?
2016/01/27

drawable の横にビックリマークが表示されるのはエラーではない

@ Android Studio 1.4

Android Studio で layout を編集していると drawable の横にビックリマーク (Exclamation mark) が表示されることがあります。
結論からいうと、これはエラーではありません。
Android Studio がサムネイルをレンダリング出来なかったために表示されるマークです。
大抵の場合、drawable に XML を指定している場合に表示されるはずです。

このエリアにサムネイル表示される機能は便利ですが、この表示の仕方はちょっと紛らわしいですね。
2016/01/13

VirtualBox の Shared Folders でシンボリックリンクを作れるようにする

デフォルトでは Shared Folders 上にシンボリックリンクを作成することはできない

VirtualBox のクライアントOS が Unix系の場合、その Shared Folders 上でシンボリックリンクを貼りたいということはよくあるかと思います。
しかし、普通に Shared Folders を設定しただけでは、クライアントOS上で以下のようなエラーが出てしまいます。

$ cd /media/sf_Documents/env
$ ln -s lib lib64
ln: failed to create symbolic link `lib64': Read-only file system

シンボリックリンク作成可能にする

ホストOS上で以下を実行します。
最後の 1 を忘れるとエラーになりますのでご注意を。

$ VBoxManage setextradata "${VMの名前}" VBoxInternal2/SharedFoldersEnableSymlinksCreate/${SharedFolderの名前} 1

きちんと設定されているか確認する

ホストOS上で以下を実行して、SharedFoldersEnableSymlinksCreate が設定されていれば次の起動からシンボリックリンクの作成が有効になっているはずです。

$ VBoxManage getextradata "${VMの名前}" enumerate           [~]
Key: GUI/LastCloseAction, Value: PowerOff
Key: GUI/LastGuestSizeHint, Value: 720,400
Key: GUI/LastNormalWindowPosition, Value: 583,142,960,432,max
Key: VBoxInternal2/SharedFoldersEnableSymlinksCreate/${SharedFolderの名前}, Value: 1

2016/01/12

Android Studio で Gradle wrapper をアップグレードする方法

Gradle wrapper は便利ですが、 場合によっては新しい機能を使うため等、Gradle のバージョンを上げなければならない時があります。
そんな時に Gradle wrapper で使用している Gradle のバージョンを上げる方法です1

Gradle wrapper を使っていることを確認する

File → Settings → Build, Execution, Deployment → Build Tools → Gradle

Use default gradle wrapper (recommended) が選択されていることを確認します。

Gradle wrapper を更新する

プロジェクトのトップディレクトリで、以下を実行します。
2.8 のところは対象のバージョンに変更してください。

$ ${Android-studio}/gradle/gradle-2.8/bin/gradle wrapper --gradle-version 2.8

Android Studio でさらに更新する

この状態でプロジェクトのトップディレクトリ直下にある build.gradle を開きます。
すると、以下のようなメッセージが出るので、OK, apply suggestion! を押せばオーケーです

You can configure Gradle wrapper to use distribution with sources. It will provide IDE with Gradle API/DSL documentation.


  1. 基本的、ビルドが通っている間は無闇にビルドツールのバージョンを安易に変えるべきではありません 
?
2016/01/11

'defaultConfig' cannot be applied to '(Groovy.lang.Closure)' が出た時の対処方法

Android Studio1 でタイトルのような Warning が出るようになってしまいました。

これは Android Studio を手作業で更新するなどして、指定した gradle のバージョンがインストールされていない場合2に起こるようです。

修正方法としては、gradle wrapper を使うのが良さそうです。

File → Settings → Build, Execution, Deployment → Build Tools → Gradle

Use default gradle wrapper (recommended) を選択。

gradle wrapper を使いたくない場合は Use local gradle distribution を選択し、Gradle home のパスを正しく設定しなおしてもオーケーです。

  1. Android Studio ver. 1.5.1 
  2. android-studio ディレクトリ中に存在しない時 
?
2016/01/05

LD_LIBRARY_PATH を設定しても反映されないことがある

@ Ubuntu 14.04 LTS

システム全体でライブラリパスを指定したかったので、/etc/environment に以下を追記したのですが反映されませんでした1

/etc/environment
1
LD_LIBRARY_PATH=/usr/local/hoge/lib

LD_LIBRARY_PATH の設定が反映されないファイルがある

調べてみると、大分前からそういう仕様だそうです2

LD_LIBRARY_PATH についての説明から抜粋

Note: You can only set this environment variable inside an interactive shell. Since Ubuntu 9.04 Jaunty Jackalope, LD_LIBRARY_PATH cannot be set in $HOME/.profile, /etc/profile, nor /etc/environment files. You must use /etc/ld.so.conf.d/*.conf configuration files. See Launchpad bug #366728 for more information.

(意訳)
この環境変数はインタラクティブシェルの中でのみ設定できます。Ubuntu 9.04 以降では、LD_LIBRARY_PATH を $HOME/.profile, /etc/profile, /etc/environment の中で設定することは出来ません。 /etc/ld.so.conf.d/*.conf の中で設定する必要があります。さらに詳しい情報は Launchpad bug #366728 を参照してください。


というわけで、以下のファイルに LD_LIBRARY_PATH を記述しても反映されません。
  • $HOME/.profile
  • /etc/profile
  • /etc/environment

解決方法1: システム全体で使いたい場合

/etc/ld.so.conf.d 以下に新たなファイルを作り、各行にライブラリパスを追加します。

/etc/ld.so.conf.d/hoge.conf
1
2
3
# コメントも書けます
/usr/local/hoge/lib
/usr/local/hoge/share/lib

/etc/ld.so.cache を再構築するため、以下を実行します。

$ sudo ldconfig

ビルドしてみると、リンクが通るはずです。
ただ、この状態で LD_LIBRARY_PATH 環境変数を見てみても、先ほどのパスは追加されていません。
その代わり、以下のようにリンクされるライブラリのリストを取得することができます。

$ ldconfig -p
1418 libs found in cache `/etc/ld.so.cache'
        libzinnia.so.0 (libc6,x86-64) => /usr/lib/libzinnia.so.0
        libzephyr.so.4 (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libzephyr.so.4
        libzeitgeist-2.0.so.0 (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libzeitgeist-2.0.so.0
        libzeitgeist-1.0.so.1 (libc6,x86-64) => /usr/lib/libzeitgeist-1.0.so.1
        libz.so.1 (libc6,x86-64) => /lib/x86_64-linux-gnu/libz.so.1
        libz.so.1 (libc6) => /usr/lib32/libz.so.1
...

解決方法2: 特定ユーザの設定ファイルに記述する

特定のユーザだけが使いたいのであれば、 $HOME/.bashrc 等、インタラクティブシェルにのみ反映されるようなファイルに記述すればオーケーです。

  1. 他の環境変数は反映されているのに 
  2. 知らなかった…
    システムワイドに LD_LIBRARY_PATH を指定することってあまり無いからなー 
?
2016/01/04

テキストモードで起動する for Ubuntu

たまにしか使わないので、備忘録として1

一度だけの場合

GRUB の画面が出てきたらカウントダウンが終わる前に e を押します。
GRUB の画面が出てこない場合は、起動直後に ESC を押すと出てくるはず2

GRUB の画面
ここで e を押す

ブートコマンドの編集モードに入るので、quiet splashtext に変更します。

1
2
3
< linux      /boot/vmlinuz-x.xx.x-xx-generic root=UUID=yyyy ro  quiet splash $vt_handoff
---
> linux      /boot/vmlinuz-x.xx.x-xx-generic root=UUID=yyyy ro  text $vt_handoff

ブートコマンドの編集画面

構成によっては少々見づらいかもしれませんが、ゆっくり探すとあるはずです。
変更し終わったら、 Ctrl-x もしくは F11 でブートします。

この方法では、再起動すると元の設定に戻ります。
グラフィックボードのドライバをインストールする場合など、一時的に CUI にしたい場合に有用です。

永続的に変更する場合

設定ファイルを以下のように編集します。

/etc/default/grub
1
2
3
4
11c11
< GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"
---
> GRUB_CMDLINE_LINUX_DEFAULT="text"

その後、update-grub をしないと設定が反映されないので注意3

$ sudo update-grub


  1. この方法はブートローダとして GRUB を使っている場合にのみ有効です 
  2. それでも出てこない場合は、ブートローダに GRUB を使っていない可能性があります 
  3. これを忘れて何度も再起動したことがあります 
?