Androidの「メディア」によるバッテリードレインかどうかを確認する方法

Android 6.0にアップデートしたXperia Z4くんがバッテリードレイン病にかかってしまった。とりあえず原因を探り、対処方法を見つけたのでメモしておく。

原因を調べる

まず「設定」→「バッテリー」→「電池使用量」を調べる。 f:id:ruby-U:20160502123522p:plain

この時点で明らかに「メディア」が怪しい。

もう少し詳細に調べる

PCが手元にあれば、adbを使ってtopを見ることができる。 f:id:ruby-U:20160502123738p:plain

ここで、/system/bin/sdcardとあるので、外付けSDカードが原因だ、というわけではないことに注意。これはそういう名前のサービスで、詳しくは Configuration Examples | Android Open Source Project などを参照されたし。

さらに詳細に調べる

端末がrootedなら、adbからsuしてlsofなどを叩くこともできる。詳しくは Finding open files with lsof などを参照されたし。

今回の原因

/storage/sdcard0/DCIM/.thumbnailsを消すと、android.process.mediaの暴走が止まった。思い当たることといえば、

  • 最近Googleの「フォト」アプリを更新した
  • BitTorrentSyncを使用して、DCIMフォルダをPCと同期している
  • 同期したファイルに、破損したものがあった(Lightroomへのインポート時に警告が表示された)

ぐらい。ひとまず問題があれば、怪しいフォルダを探して、PCやクラウドにバックアップした後、top | grep media を眺めながら、サクッと消してみるのがよさそう

Win8タブのコネクト スタンバイ時にBluetoothやWIFIを自動的に無効にする

Miix2くんはゲーム専用機

この間 Lenovo Miix 2 8 を買いまして、美少女紙芝居ゲーム専用機としてぼちぼち設定していたのですが、Bluetoothオーディオレシーバと接続した後、スリープにするとバッテリをものすごい勢いで消費して、全然スリープじゃなかったのでその対策を書いておきます。

接続先のBluetoothオーディオレシーバ

この光デジタル出力をアンプに接続しています。ノベルゲーで遊ぶ限りでは遅延も感じられませんし、(Miix2側の都合で)SBCでの接続になりますが、音も悪く無いです。

ゲーム用のストレージとして。64GBもあれば安心…なはず。

注意

Miix 2 8とBluetoothオーディオレシーバに関係した不具合として、スリープ後にペアリングは維持されているものの、本体側から音が出る、という症状があります。Bluetoothのオンオフで直るのですが、大変煩わしいので、必要であれば、原因である「Lenovo Bluetooth with Enhanced Data Rate Software」を削除してください。

デバイスの有効にしたり無効にしたりするにはDevConが使える

Win8.xで機内モードCUIから操作する方法を見つけられなかったので、DevConを使います。

デバイス マネージャーとして機能する DevCon コマンド ライン ユーティリティ

僕の環境でBluetoothを無効にするには

BT-down.vbs

CreateObject("WScript.Shell").Run "devcon\i386\devcon.exe disable ""BCMBTBUS\BLUETOOTH""", 0

有効にするには

BT-up.vbs

CreateObject("WScript.Shell").Run "devcon\i386\devcon.exe enable ""BCMBTBUS\BLUETOOTH""", 0

ちなみにWIFIを無効にするには

WIFI-down.vbs

CreateObject("WScript.Shell").Run "devcon\i386\devcon.exe disable ""SD\VID_02d0&PID_4324&FN_1""", 0

有効にするには

WIFI-up.vbs

CreateObject("WScript.Shell").Run "devcon\i386\devcon.exe enable ""SD\VID_02d0&PID_4324&FN_1""", 0

みたいな感じです。

タスクを登録する

タスクスケジューラーで、Kernel-Powerの506, 507イベントにスクリプトをフックします。

オンにするフック

f:id:ruby-U:20141104135351p:plain

f:id:ruby-U:20141104135353p:plain

f:id:ruby-U:20141104135352p:plain

f:id:ruby-U:20141104135354p:plain

オフにするフック

f:id:ruby-U:20141104135347p:plain

f:id:ruby-U:20141104135348p:plain

f:id:ruby-U:20141104135349p:plain

f:id:ruby-U:20141104135350p:plain

以上で、コネクト スタンバイ中に自動的にBluetoothデバイスをオフにできるようになるはずです。切断されたBluetoothデバイスは、スタンバイからの復帰後、自動的に再接続されます。

ただし、Bluetoothで何らかのデバイスを使用していると、DevConでの操作が反映されないことがあります。この例だと、Bluetoothオーディオレシーバーで音楽を再生していると、DevConでBluetoothをオフにすることができません。「変更は再起動後に反映されます」というやつです。 ゲームを終了させる→スリープにする という手順が必要になります。

うまくLow Power Activityに落ちるとバッテリ消費量は5%/dayぐらい

このぐらいのバッテリー消費量だと、AndroidタブレットiPadに近い感覚で扱えますね。

f:id:ruby-U:20141104135345p:plain

f:id:ruby-U:20141104135346p:plain

夏休みの自由研究 「女騎士」 提出しました

まえがき

ここ数年ぼちぼちと英単語の暗記に取り組んでいるのですが、暗記カード(紙)やTOEIC対策アプリでの学習に主に効率・自由度などの面で限界を感じたので、よりオープンな暗記用ソフトウェア(SRS; Spaced Repetition learning Systems)を使用することにし、Windows, Mac, Linux, Android, iOSなど主要なプラットフォームに対応し、また定番のソフトウェアでもある Anki を新しい学習ツールとして選択しました。

Ankiはインストールした状態では空っぽで、学習するデータは自分で用意する必要があります。僕が覚えるのは英語で、英語学習に使えるデータは豊富にあります。例えば

単語

語義

  • E-DIC2
  • システムソフト電子辞典(Epwing辞書に変換可能なもの)
  • Epwing辞書

などを組み合わせて「表が単語、裏が語義の暗記カード」を(論理的には)作ることが可能ですが、しかし実際にこれを簡単に行うためのツールが見つからなかったため、時間を作って作成することにしました。

要件

当初考えていたのは

  • AnkiがサポートするCSV, TSVなどを扱えること。
  • 外部のコマンドを叩いて、データを取得できること。

といったふんわりとした要件で、Ankiのデッキを作るツール(概案) のようなものを考えていたのですが、どうもイマイチな気がしていました。そこでさらに調査を進めるうちに驚くべきツールを発見しました。AWKです。非常に有名なツールで、僕もそれまで幾度となく触ったことがあったのにも関わらず、実際には全くこのツールの真の価値を知らなかったわけです。

AWKは正に上記の要件を満たす素晴らしいツールに思えましたが、いくつかの問題があり、結局使用することはできませんでした。しかしAWKのコンセプトは素晴らしいもので、これをそのまま取り入れ、多少の変更を加えたものを実装することにしました。

WOK

AWKはオークと呼ぶらしいです。2014年現在、これと対になるような単語は一つしか思い浮かびません。WOman Knight、すなわち女騎士です。

rubyu/wok

WOKはAWKPython互換のCSVモジュールと、JavaとそしてScalaの豊富なライブラリを追加したものになります。

女騎士 vs オーク

Running a script

$ wok 'print("hello!")'
$ awk 'BEGIN { print "hello!" }'

Running a program file

$ wok -f program-file input
$ awk -f program-file input

Variables

$ wok -v name=value
$ awk -v name=value

Operating fields

In { _ foreach { row => 
  println(row(0)) 
}}
{ print $1 }

Filtering data

In { _ foreach {
  case row if row exists ("pattern".r.findFirstIn(_).isDefined) => 
    println(row: _*)
  case _ =>
}}
/pattern/ { print $0 }

Printing to a file

val path = "list" !<
In { _ foreach { _ =>
  path.println(NF)
}}
{ print NF > "list" }

Executing a system command

In { _ foreach { row =>
  val res = Seq("echo", row(0)) #| Seq("grep", "angel") !>
  println(res.string)
  println(res.code)
}}

女騎士の拡張された部分

Typed variables

# The following command is equivalent to 
# var name = `value`
$ wok -v@char name=value

# The following command is equivalent to 
# var name = "value"
$ wok -v@str name=value

# The following command is equivalent to 
# var name = """value"""
$ wok -v@rawstr name=value

Quoting

// Setting Quote(mode=Min, quote='"') to Reader
FQ = Quote Min 

// Setting Quote(mode=All, quote='"', escape='\\') to Writer
OFQ = Quote All Q('"') E('\\')

Encoding

// Setting Codec to Reader
CD = Codec("UTF-8")

// Setting Codec to Writer
OCD = Codec("UTF-16")

サンプルスクリプト

rubyu/wok-scripts より

指定した列を元に、Epwingから辞書引きした列を追加する

eb-html.wok

#!/usr/bin/wok -f

/** eb-html.wok
  * 
  * Usage:
  *   java -jar wok-0.1.0.jar -f eb-html.wok -v src=0 -v dst=1 -v@rawstr dic=path_to_epwing -v@rawstr ebmap=path_to_ebmap < input.tsv > output.tsv
  *
  * Option:
  *   -v src:           the index number of the source field
  *   -v dst:           the index number of the destination field
  *   -v@rawstr dic:    path to a directory containing Epwing's CATALOGS file
  *   -v@rawstr ebmap:  path to a EBWin3/4 map file corresponds to a EPWING dictionary specified by option `dic`
  */

FS = '\t'
FQ = Quote.Min
OFS = '\t'
OFQ = Quote.All

val eb = "ebquery-0.3.1.jar"

if (eb nonExistent) 
  eb write Resource.fromURL("https://bitbucket.org/rubyu/ebquery/downloads/ebquery-0.3.1.jar").bytes

def query(s: String) = Seq("java", "-Dfile.encoding=UTF-8", "-jar", eb, "-d", dic, "-f", "html", "-m", "tx,sb,sp,ec,ls", "--ebmap", ebmap, s).!>.string

In { _ 
  .filter (_ isDefinedAt src)
  .map (_.padTo(dst+1, ""))
  .map (row => row.updated(dst, query(row(src))))
  .foreach (row => println(row: _*))
}

list1.txt

a
abacus
abalone

ここでlist1.txtを単語のリストとして、以下のコマンドを実行できます。

> java -jar wok-0.1.0.jar -f eb-html.wok -v src=0 -v dst=1 -v@rawstr dic=C:\dic\KENE7J5 -v@rawstr ebmap=C:\dic\KENE7J5.MAP list1.txt > list2.txt

list2.txt

word definition
a a1, A1 /éı/→音声 ( as, a's, As, A's ...
abucus ab・a・cus /ǽbəkəs/ ( 〜・es, ―ci...
abalone ab・a・lo・ne /æ̀bəlóʊni/ 〔貝〕 ...

指定した列に、外部の頻度順データ等でのランキング列を追加する

rank.wok

#!/usr/bin/wok -f

/** rank.wok
  * 
  * Usage:
  *   java -jar wok-0.1.0.jar -f rank.wok -v src=0 -v dst=1 -v grp=1 -v@rawstr ranking=list.txt < input.tsv > output.tsv
  *
  * Option:
  *   -v src:               the index number of the source field
  *   -v dst:               the index number of the destination field
  *   -v grp:               the unit size of grouping the rank value; this value must be larger than zero
  *   -v@rawstr ranking:    path to a ranking file
  */

FS = '\t'
FQ = Quote.Min
OFS = '\t'
OFQ = Quote.All

val rank = In.from(ranking) { _
  .zipWithIndex
  .map { case (row, i) => row(0) -> i }
  .toMap
}

In { _
  .filter (_ isDefinedAt src)
  .map (_.padTo(dst+1, ""))
  .map { row =>
    val k = row(src)
    rank.get(k) match {
      case Some(v) => row.updated(dst, s"${v / grp + 1}")
      case None => row
    }
  }
  .foreach (row => println(row: _*))
}

ranking.txt

a
aa
ab
aah
aardvark
abucus
abaca
abalone

ここでlist2.txtを単語のリストとして、以下のコマンドを実行できます。

> java -jar wok-0.1.0.jar -f rank.wok -v src=0 -v dst=2 -v grp=1 -v@rawstr ranking=ranking.txt list2.txt > list3.txt

list3.txt

word definition rank
a a1, A1 /éı/→音声 ( as, a's, As, A's ... 1
abucus ab・a・cus /ǽbəkəs/ ( 〜・es, ―ci... 6
abalone ab・a・lo・ne /æ̀bəlóʊni/ 〔貝〕 ... 8

あとがき

我々には夏休みに何かを作ろうとする習性が刷り込まれているのではないか、そしてそれは義務教育が我々にもたらす最も素晴らしいものの一つではないか、などと思ったりします。

さて、夏休みどころか、さらに数ヶ月をまるまる投じて制作した本ソフトウェアですが、起動時にScalaファイルをコンパイルするためメモリを大量に消費する、同じくコンパイルのために数秒の待ち時間が発生する、記述が冗長になるなどの様々な短所はありますが、一方で、クォートが扱える、コンパイル時に型エラーがないことが保証される、ScalaのCollectionフレームワークに乗っかれるなどの長所もあります。

英語学習用デッキを生成するためのツールとして十分な機能を備えてはいますが、CUI操作に慣れていなければ難しくもあります。このあたりは今後の課題とします。

WindowsでProcessBuilderの文字コード周りがどうなるか検証した

外部のプログラムにデータを渡して、その結果を得たい。 標準入出力では問題が起こりそうにないが、コマンドライン引数まわりは怪しく思える。 Windows以外の、システムのデフォルトエンコーディングUTF-8な環境なら何も問題なくイケそうだが、Windowsではどうか。ここではコマンドライン引数について検証する。

こんなのを用意して、

object OuterProcess {
  def build(commands: List[List[String]]): Option[ProcessBuilder] = {
    if (commands.nonEmpty) {
      build(commands.tail) match {
        case Some(x) => Some(commands.head #| x)
        case None => Some(commands.head)
      }
    } else {
      None
    }
  }
  def call(commands: List[List[String]]): String = {
    build(commands) match {
      case Some(x) => x !!
      case None => ""
    }
  }
}

-Dfile.encoding=UTF-8な設定で以下のテストを実施した。

class OuterProcessTest extends SpecificationWithJUnit {
  "OuterProcess.call" should {
    "receive UTF-8 encoded output" in {
      OuterProcess.call(List(
        List("cmd", "/c", "type", "text.txt")
      )) mustEqual("éindʒəl") //success
    }
    "pass unicode arguments to a program" in {
      OuterProcess.call(List(
        List("cmd", "/c", "echo", "éindʒəl")
      )) mustEqual("éindʒəl") //fail
    }
    "pass unicode arguments to a program" in {
      OuterProcess.call(List(
        List("cmd", "/c", "chcp", "65001", "&&", "cmd", "/c", "echo", "éindʒəl")
      )) mustEqual("éindʒəl") //success
    }
  }
}

というわけで、引数は正しくUnicodeなまま、対象のプログラムに渡されている。そして、このechoのように、プログラムがUnicodeな引数を正しく扱えるかどうかは、そのプログラム自体(またはその設定など)に依存するようだ。例えば、Windowsで動くgrepを用意して以下のテストを行う場合、Gowgrepでは失敗してGnupackのものでは成功する。これは前者がUnicode(マルチバイト全般?)を与えた時にうまく動作せず、後者では問題ないため。

class OuterProcessTest extends SpecificationWithJUnit {
  "OuterProcess.call" should {
    "connect programs" in {
      OuterProcess.call(List(
        List("cmd", "/c", "chcp", "65001", "&&", "cmd", "/c", "echo", "éindʒəl"),
        List("grep", "éindʒəl")
      )) mustEqual("éindʒəl")
    }
  }
}

210円でiPhone5のケースにストラップを装着する方法

iPhone5を購入して、その足で100円ショップのセリアに寄って液晶保護シートとクリアケースを確保したのが昨日のこと。思いのほかケースの出来がよく、かなり満足なのですが、残念ながらストラップホールがありません。ケースに穴を開けてストラップを通すのもアリなんでしょうが、ケースの強度低下や、ストラップのヒモとiPhone本体との干渉などの不安材料があり、別の方法がないかなぁと考えました。

この改造のためのパーツを集めた100円ショップがちいさいダイソーだったので、他のショップにはもっといいものがあるかもしれませんが、今回はこんな感じです。

材料

  • 結束バンドベース (20mm角)
  • 超強力両面テープ (幅19mm)

方法

  1. 結束バンドベースにあらかじめ張ってある両面テープを剥がし、超強力両面テープ(19mm角)に張り替えます。
  2. クリアケースの好きな位置に貼ります。
  3. 完成!

どうでしょうか? それほど不恰好ではないと思います。つるつるのケースはどうしても手から離れやすいので、ストラップがあれば安心です。

Kobo Touchで目次画面の現在地が後方にズレる問題の対策

Kobo TouchはどうやらEPUBの目次に全てのページが存在していることを前提としているようで、

metadata.opfのspine内のN番目のitemref要素を開いている = toc.ncxのN番目のnavPoint要素が現在地ねっ!

と解釈するようです。目次画面を開いたとき、現在地が後方にズレるのはこれが原因です。楽天Koboから大手出版社の小説のサンプル版をいくつかダウンロードしてみましたが、普通にズレていましたので、あまり気にする人はいないのかもしれません。(というか使ってる人がそもそもいない…?)ですが、今自分がどこを開いているのかがわからないというのは、用途によってはかなり致命的です。

ところで、自炊した画像データやPDFからEPUBを作成するにはChainLPが大変便利で、僕はこのツールでデータをEPUBに変換しています。ChainLPで作成したEPUBは、一枚の画像(を表示するためのページ)と一つのitemrefが1対1になっています。このため、データに目次を設定し、出来上がったEPUBをKobo Touchで閲覧する場合、目次の現在位置が後方に(正確には、開いているページがN番目のページであった場合、N番目の目次に)ズレることになります。悲しいことですが、悲しんでばかりもいられません。少し考えると、Kobo Touchのこの仕様への対策がいくつか思いつきました。

対策A 全てのページを目次に登録する

目次の動作がすごく遅くなり、実用上問題がありました。また、意味のある目次項目が、便宜上加えられた無意味な目次項目に埋もれてしまい、加えてその位置が不規則に分散してしまうため、非常に使いづらいです。これはやめておいたほうがよいでしょう。

対策B 目次ごとにページを統合する

要するに

ページ番号 見出し
1 表紙
2
3 まえがき
4
5 第一章
6

とある場合、

ページ番号 見出し
1, 2 表紙
3, 4 まえがき
5, 6 第一章

のようにセクションごとにページをまとめてしまうわけです。この方法が今のところ最良だと思います。ページ送りも問題なく行え、またセクション内のでページ送りは、単一のページに格納する場合より高速なようです。 ChainLPで作成したEPUBに、この変更を施すスクリプトが以下です。

ところで、Kobo Touchで目次を使って別の位置にジャンプしたあと、”戻る”的な操作がうまく動作しないのはどうしてなんでしょうね…? しかたなく毎回しおりを挟んで、あとでそこに戻っていますが…。不便!