hg cloneでcertificate verify failed

httpsのリポジトリからcloneしようとしたらSSLのverifyエラーが発生しました。

PS C:\repo> hg clone https://repository.sample.com/hg/test/ C:\repo\test
abort: error: _ssl.c:490: error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed

環境はWindows、サーバのSSL証明書はオレオレ証明書です。

解決策はさっくりとverify無視すればOKです。

    --insecure          do not verify server certificate (ignoring
                        web.cacerts config)
PS C:\repo> hg clone https://repository.sample.com/hg/test/ C:\repo\test --insecure

Warningは出ますが、これで使えます。

push/pull時もinsecure付けないといけないのは難点ですね。

ちゃんと解決する方法は公式ドキュメントに書いてあるっぽいのですが、気力なくなってしまったのでそのうち…

CACertificates – Mercurial


ローカルで作成していたリポジトリをリモートサーバーに設置する方法

個人でローカルのMercurialを使って開発していたけど、リモートサーバーに置きたくなった場合の作業です。

基本的には、ローカルのリポジトリファイル一式をリモートサーバーに持って行ってhg cloneするだけでOKです。
とりあえず最終状態をコミットしておきます。

$ cd test_local
$ hg commit -m "move to remote"
$ tar cvzf test_local.tar.gz ./test_local

で、test_local.tar.gzをリモートサーバーに転送します。

リモートサーバーでは、適当に/home以下にでも配置して展開します。今回はApacheを動かしているwwwユーザのhomeディレクトリで展開します。

$ cd /home/www
$ tar xvzf test_local.tar.gz
$ chmod www:www -R ./test_local

他のリポジトリが置いてある場所と同じ場所(今回は/var/lib/hg)に移動します。

$ cd /var/lib/hg
$ mkdir test_local
$ cd test_local
$ hg clone /home/www/test_local .

.hg/hgrcは新しいパスに上書きされます。ここからはpushしないので、消してしまっても大丈夫だと思います。

で、hgweb.configのpathsを追加します。

[paths]
test_local = /var/lib/hg/test_local

サーバ側はこれだけです。

ローカル側は、hg pullにソースパスを指定すればそこから差分を取得できます。

が、面倒なので.hg/hgrcを編集して、hg pullコマンドだけでpullできるようにします。

[paths]
default = https://user1:password@repository.sample.com/hg/test_local

ユーザ名とパスワードもURLに含めることによって認証の手間も解決できます。


httpでアクセスできるMercurialサーバを作る

サーバはCentOS 5.8です。Mercurialは2.2.2を使います。

まずは下準備で、Mercurialをインストールします。yum install mercurialでインストールできるバージョンは古いので、easy_installを使います。

# yum install gcc python python-devel python-setuptools
# easy_install mercurial

PythonとMercurialがインストールされているか確認します。以下のコマンドで何もエラーがでなければOKです。

# python
>>> import mercurial
>>> [Crtl+D]

Mercurialのリポジトリからhgweb.cgiを探します。自分の環境ではMercurialをeasy_installでインストールしたのでhgweb.cgiが入っていませんでした。

なので、リポジトリから落としてきます。

http://selenic.com/hg/file/85a358df5bbb/hgweb.cgi

以前は複数のリポジトリを公開するためには、hgwebdir.cgiというファイルを使うことになっていましたが、統合されたようで、hgweb.cgiでどちらもできるようになっています。

下記の日本語訳のページは、情報が古いので注意してください。

JapaneseCGI_Install – Mercurial

英語ページを参照するのが正しいです。

PublishingRepositories – Mercurial

Apache用にwwwユーザを作ってあるので、/home/www/hgweb.cgiに設置しました。hgweb.cgiを開いて、configの行を変更します。

config = "/home/www/hgweb.config"

hgweb.cgiには実行権限を付けておきます。

# chmod u+x /home/www/hgweb.cgi

同じディレクトリにhgweb.configを作成します。

[web]
encoding = UTF-8

[paths]
test = /var/lib/hg/test

リポジトリのパスは/var/lib/hg以下に設置することにしました。ドキュメントでは/home/user/hgの下に置くことになっています。

# mkdir /var/lib/hg
# chown www:www /var/lib/hg
# su www
$ cd /var/lib/hg
$ mkdir test
$ cd ./test
$ hg init

wwwユーザがhgを使えるようにしておきます。/home/www/に.hgrcを作成してユーザ情報を書き込みます。

$ vi ~/.hgrc
[ui]
username = webadmin <webadmin@sample.com>

で、/var/lib/hg/testに適当にファイルを作ってコミットします。コミットがひとつもないと、Webからアクセスできません。

$ touch index.html
$ hg add index.html
$ hg commit -m "first commit"

次にApacheの設定です。ScriptAliasをVirtualHostの中にでも書いておきます。

ScriptAlias /hg "/home/www/hgweb.cgi"

ここまでできたら、Apacheを再起動します。

# apachectl restart

これで、http://repository.sample.com/hg/testのようにアクセスできます。ブラウザでも、hg cloneコマンドでも可能です。

リポジトリを追加するときは、/var/lib/hg以下にディレクトリを新規作成して、そのディレクトリでhg initを実行、/home/www/hgweb.configに追記の手順です。

次に認証を追加します。Apacheの設定ファイルの、先ほどのScriptAliasの下にLocationディレクティブを追加します。

<Location /hg>
    AuthType Basic
    AuthName "Mercurial repositories"
    AuthUserFile /home/www/hg/hgusers
    AuthGroupFile /home/www/hg/hggroups
    Require valid-user
</Location>

ユーザとグループで管理できるようにしました。(グループ作りましたけど意味はなかったです)

# su www
$ mkdir ~/hg
$ cd ~/hg
$ htpasswd -c ./hgusers user1

同じ場所にhggroupsファイルも作成してグループを定義します。svnのdavのようにここでリポジトリのアクセス権限を設定したりすることはできません。

testgroup:user1

グループの書き方はこちらを参照のこと。

ユーザー認証によるアクセス制限(ベーシック認証編)(2/2)

webからのpushができるようにします。/home/www/.hgrcにセクションを追加します。

[web]
allow_push = *

この設定は各リポジトリの.hg/hgrcに書いても大丈夫のようです。そうすれば、レポジトリごとに制御できます。

問題はグループごとにアクセスできるリポジトリを制御することなのですが、これは簡単にはできないようです。

ユーザごとには読み込み制御が可能です。allow_readを使います。

この場合は、/var/lib/hg/test/.hg/hgrc(なければ作成)にwebセクションを書きます。

[web]
allow_read = user1

これで大丈夫です。トップページのリストにも、allow_readに含まれていないユーザはリポジトリが表示されません。allow_readは書かなければallow_read=*としたことと同じになります。他にもdeny_readが使えます。

参考

configuration – Mercurial set per user rights – Server Fault

それと、httpsを使わない場合はpush_ssl = falseをhgweb.configのwebセクションに書いておく必要があります。httpsでアクセスさせる場合はそのままでOKです。


Androidの設定画面で使うカスタムPreferenceを作る

PreferenceActivityを使ってそこそこ簡単に設定画面を作れるのは良いのですが、特別な機能を持った項目を追加したい場合があります。

今回は、設定画面からWebのページへジャンプする項目を作る要件があったので、Preferenceを拡張して実装しました。

単純にPreferenceをres/xml/pref.xmlに書いて、PreferenceActivityの中でクリックイベントを設定することもできますが、なるべくコードをすっきりさせたいですし、再利用性も考えてLinkPreferenceというクラスを作成します。リンク先をXMLのアトリビュートに書くだけでジャンプ先を指定できます。

まずはpref.xmlにLinkPreferenceを追加します。

res/xml/pref.xml

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:hoge="http://schemas.android.com/apk/res/hoge" >

    <info.loadlimits.android.preference.LinkPreference
        android:key="link_preference"
        android:title="サイトをブラウザで表示"
        hoge:url="http://blog.loadlimits.info/" />

</PreferenceScreen>

名前空間は適当に定義しておきます。どうせここ以外では使われません。3行目にxmlns:hogeを指定します。8行目で指定されたURLがジャンプ先になります。

res/values/attr.xml

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

    <attr name="url" format="string" />

    <declare-styleable name="LinkPreference">
        <attr name="android:key" />
        <attr name="android:title" />
        <attr name="android:summary" />
        <attr name="url" />
    </declare-styleable>

</resources>

attr.xmlで新しく追加するプロパティを定義します。declare-styleableを指定することで、R.styleable.LinkPreferenceやR.styleable.LinkPreference_urlが自動的に定義されます。

あとは実際のLinkPreferenceの中身を書けば完了です。

src/info/loadlimits/android/preference/LinkPreference.java

package info.loadlimits.android.preference;

import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
import android.net.Uri;
import android.preference.Preference;
import android.util.AttributeSet;

public class LinkPreference extends Preference {

    private String mUrl;

    public LinkPreference(Context context) {
        this(context, null);
    }

    public LinkPreference(Context context, AttributeSet attrs) {
        super(context, attrs);

        TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.LinkPreference);
        mUrl = a.getString(R.styleable.LinkPreference_url);
        a.recycle();
    }

    @Override
    protected void onClick() {
        Uri uri = Uri.parse(mUrl);
        Intent intent = new Intent(Intent.ACTION_VIEW, uri);
        getContext().startActivity(intent);
    }

}

こういうカスタムPreferenceや、他のカスタムViewも含めてどこかでまとめて公開して再利用がしやすいようにしたいですね。


AndroidのMediaPlayerで動画が表示されない現象

Nexus Oneで動画プレイヤーを開発していて、Galaxy S2で試したら音は鳴るけど映像が表示されないという状況がありました。

問題点としては以下の2つ。

  • 動画の縦横サイズが取れない
  • ホルダーのサイズを固定しても出力されない

どうやらMediaPlayer.createがハマりどころのようです。

以下がNexus Oneで動いていたコードです。

            mMediaPlayer = MediaPlayer.create(this, Uri.parse(moviefile));
            mMediaPlayer.setOnCompletionListener(this);
            mMediaPlayer.setDisplay(holder);
            
            int videoHeight = mMediaPlayer.getVideoHeight();
            int videoWidth = mMediaPlayer.getVideoWidth();

これが、Galaxy S2に持ってくると、getVideoWidthとgetVideoHeightが0を返します。

結論としては、以下のように書き換えました。

            // mMediaPlayer = MediaPlayer.create(this, Uri.parse(moviefile));
            mMediaPlayer = new MediaPlayer();
            mMediaPlayer.reset();
            mMediaPlayer.setDataSource(this, Uri.parse(moviefile));
            // setDisplayをprepareの前に持ってこないといけない
            mMediaPlayer.setDisplay(holder);
            mMediaPlayer.setOnCompletionListener(this);
            mMediaPlayer.setOnPreparedListener(this);
            mMediaPlayer.prepare();

(中略)

    @Override
    public void onPrepared(MediaPlayer mp) {
        int videoHeight = mp.getVideoHeight();
        int videoWidth = mp.getVideoWidth();

prepareメソッドを呼んだ後でも、やっぱりビデオサイズは取れないので、OnPreparedListenerを登録して、そちらで動画サイズを取得する必要があります。

prepareAsyncがあるのだからprepare後は当然mMediaPlayerからサイズ取れると思ったのですが。

prepareの動作が端末(バージョン?)ごとにちょっと違うみたいですね。

それと、以下のエラーが出ますが原因不明です。特に問題は起きていないのですが…

06-04 14:16:56.665: E/Surface(16331): Surface::init token -2 identity 278

06-04 14:16:56.670: E/MediaPlayer-JNI(16331): setDataSource: outside path in JNI is �x@

結局VideoView使うのが一番楽かと。


Symfony2のdoctrine2をenumに対応させる

doctrine2がenumに対応していないので、既存のデータベースからEntityを作ろうとした時にエラーが出ることがあります。

$ php app/console doctrine:mapping:convert yml ./src/Test/HogeBundle/Resources/config/doctrine/metadata/orm --from-database --force

  [Doctrine\DBAL\DBALException]
  Unknown database type enum requested, Doctrine\DBAL\Platforms\MySqlPlatform may not support it.

その場合は、doctrine2のドキュメントによると、enumをstringに関連づければ良いようです。

Mysql Enums — Doctrine 2 ORM 2.1 documentation

BundleのbootでEntityManagerを取得して、マッピングを行います。

<?php

namespace Test\HogeBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;

class TestHogeBundle extends Bundle
{
    public function boot() {
        $em = $this->container->get('doctrine.orm.default_entity_manager');
        $conn = $em->getConnection();
        $conn->getDatabasePlatform()->registerDoctrineTypeMapping('enum', 'string');
    }
}

他の方法として、Enumタイプを作成して関連付けることもできるようです。

※追記

app/config/config.phpに書く方法が紹介されていました。

Doctrine の DBAL レイヤーの使用方法 | Symfony2日本語ドキュメント

$container->loadFromExtension('doctrine', array(
    'dbal' => array(
        'connections' => array(
            'default' => array(
                'mapping_types' => array(
                    'enum'  => 'string',
                ),
            ),
        ),
    ),
));

試していませんが、これでもいいらしいです。


Node.jsはPowerPCで使えないという話

LinuxでPowerPCのアーキテクチャを選択する場合は注意が必要です。

わざわざWindows版もMac版も用意されているのだからアーキテクチャはほぼ不問なんだと思ってました…

OpenBlocks(debian squeeze)にNode.jsをnvmからインストールしようとしたところ、WAFがエラーを出しました。

root@squeeze:~# nvm install v0.6.18
######################################################################## 100.0%
Checking for program g++ or c++          : /usr/bin/g++
Checking for program cpp                 : /usr/bin/cpp
Checking for program ar                  : /usr/bin/ar
Checking for program ranlib              : /usr/bin/ranlib
Checking for g++                         : ok
Checking for program gcc or cc           : /usr/bin/gcc
Checking for program ar                  : /usr/bin/ar
Checking for program ranlib              : /usr/bin/ranlib
Checking for gcc                         : ok
Checking for library dl                  : yes
Checking for openssl                     : not found
Checking for function SSL_library_init   : yes
Checking for header openssl/crypto.h     : yes
Checking for library util                : yes
Traceback (most recent call last):
  File "/root/nvm/src/node-v0.6.18/tools/waf-light", line 158, in <module>
    Scripting.prepare(t, cwd, VERSION, wafdir)
  File "/root/nvm/src/node-v0.6.18/tools/wafadmin/Scripting.py", line 145, in prepare
    prepare_impl(t, cwd, ver, wafdir)
  File "/root/nvm/src/node-v0.6.18/tools/wafadmin/Scripting.py", line 135, in prepare_impl
    main()
  File "/root/nvm/src/node-v0.6.18/tools/wafadmin/Scripting.py", line 188, in main
    fun(ctx)
  File "/root/nvm/src/node-v0.6.18/tools/wafadmin/Scripting.py", line 241, in configure
    conf.sub_config([''])
  File "/root/nvm/src/node-v0.6.18/tools/wafadmin/Configure.py", line 221, in sub_config
    self.recurse(k, name='configure')
  File "/root/nvm/src/node-v0.6.18/tools/wafadmin/Utils.py", line 634, in recurse
    f(self)
  File "/root/nvm/src/node-v0.6.18/wscript", line 399, in configure
    conf.env['DEST_CPU'] = canonical_cpu_type(conf.env['DEST_CPU'])
  File "/root/nvm/src/node-v0.6.18/wscript", line 60, in canonical_cpu_type
    " but NOT '" + arch + "'.")
Exception: supported architectures are arm, ia32, x64 but NOT 'powerpc'.
nvm: install v0.6.18 failed!

nvmでしか試していませんが、恐らくnodeをmakeしても同じです。これはV8エンジンがPowerPCに非対応なのが原因です。http://code.google.com/p/v8/

あとはこの辺り参照。

Is it possible to make Node.js use Rhino as the Javascript engine? – Stack Overflow

Issue 692 – waf – cannot build nodejs (uses waf) on OS X ppc – The meta build system – Google Project Hosting

一応、V8をPowerPCで動くようにするプロジェクトはあるみたいですが…

https://github.com/ic/v8-powerpc

Long-term goal: Get Node.js and Google Chrome to PowerPC users.

らしいので気長に待つしか…

あとはSpiderMonkeyにV8のAPI実装してNodeに結合するspidernodeですね。

https://github.com/zpao/spidernode

ちょっと求めているものとは違うんですよね…動くのかわからないし。

あー、どうしよっかなー…


AndroidのMessage.sendToTarget()の動作を調べた

com.android.internal.app.AlertControllerを読んでます。

とりあえずsendToTargetはHandlerに対して、sendMessage(this)をしているだけです。

ここのHandlerはMessage.obtain(handler, what, listener)で指定したhandlerです。
thisはもちろんMessage。

ではHandler.sendMessage(Message msg)は何をしているのかという話です。

Handler.sendMessage(Message msg)はHandler.sendMessageDelayed(Message msg, 0)を呼んでいるだけで、sendMessageDelayedではsendMessageAtTimeを呼んでいるだけです。
sendMessageAtTimeを呼び出すときにシステムクロックの現在時刻を引数に渡しています。

Handler.sendMessageAtTimeは、MessageQueueにMessageオブジェクトと時刻を渡してキューイングしています。

あとはAndroidの肝であるLooperがメッセージを実行します。
msg.target.dispatchMessage(msg);

msg.targetは先ほどのHandlerです。

Handler.dispatchMessage(msg)は、msg.callbackが定義されていればmsg.callback.run()を実行しますが、前述のobtainでMessageオブジェクトを作った場合はcallbackは定義されていないので、代わりにHandlerのインスタンスを作ったときに渡されたコールバックがあれば、そちらを実行します。

大抵の場合、コールバックをコンストラクタに渡さずに、Handlerを継承するかしてhandleMessageをオーバーライドするかと思います。
オーバーライドしないと、Handler.handleMessageは何もしません。
    public void handleMessage(Message msg) {
    }

ということで、結局、Looperのキューを介して自前のHandlerに処理が戻ってくるという流れでした。
処理をHandlerにまとめて書くのでなければ、handleMessageを書かずにMessageにコールバックを渡しておくというのでもいいですね。
ただし、Message.obtain(Handler h, Runnable callback)で渡しておく必要があります。
その場合、Runnableが渡せるのはHandler.obtainMessageにはないので、Message.obtainでMessageを作ることになります。

何というか、一目瞭然な図が欲しい。


WinSerでnode.jsをWindowsのサービスにする

そんな用途がある人は相当少ないとは思うのですが、仕事柄、ニッチな要求ばかりがやってきます。

さて、node.js用に書かれたプログラムを、Windowsでサービス登録してバックグラウンドで自動実行する方法です。

WinSer: node.js applications as windows services

npmコマンドでインストールできます。

npm install winser

実際のプログラムをサービス化するにあたって、package.jsonを作成しておく必要があります。package.jsonのnameがそのままサービス名として登録されます。他の項目はどうでもいいです。

コマンドプロンプトを管理者権限で実行します。

C:\> chcp 437

日本語環境の場合、chcpコマンドでコマンドプロンプトを英語モードにしないと、
You must run this tool as an administrator
というエラーが出ます。

そして、自分の作ったプログラムのディレクトリに移動して、
C:\path\to\node_modules\.bat\winser.cmd -i
でサービスに登録されます。

cmdファイル経由ではなく、
node.exe C:\path\to\winser\winser -i
でもOKです。

ちなみに、サービス自体はnssmが登録します。

NSSM – the Non-Sucking Service Manager

ファイアウォールを通す場合は、nssmを指定します。


Cordova(PhoneGap)でAndroid開発する際の勘所

PhoneGapの練習も兼ねてアプリひとつ作りました。

ヨルニンゲン (Jorningen) ※現在公開中のバージョンはJavaに書き直しました

https://play.google.com/store/apps/details?id=info.play_smart.android.Jorningen

device-2012-05-21-143445

生活時間帯が標準時間とズレている人が、自分の中では今何時なのかを把握するための時計アプリです。
例えば明け方6時に寝る人は、朝6時を夜12時のつもりとして登録しておくと、自分時間を表示しておけます。
分単位で、自分の中での今の時間を入力できるところが、世界時計とはちょっとコンセプトの違うところです。
アプリ名は夜人間ですが、昼人間も使えます。

で、AndroidアプリでPhoneGapを使ってみたのでハマりそうな部分やポイントを書いておきます。ちなみに使ったバージョンは1.7.0です。xui.jsとデータの保存にlawnchairを使いました。

Apache Cordova

PhoneGapのAndroid版は、名前がCordovaになったので検索時など注意しましょう。2011年のセットアップ記事とか見るとハマるかも。

ダウンロードはこちらから。ここからダウンロードできるのはgithub上にあるtarballです。
PhoneGap

思い切りが必要

結構思い切らないとPhoneGapを使うのは難しいです。Javaが必要な部分はJavaで書いて連携させればいいとか、思わないほうがいいです。すべてJavaScriptでやる気持ちでやりましょう。

なるべくJavaを使わない

双方の呼び出しは何とかなりますが、オブジェクトの共有はしにくいです。

OptionMenuは諦める

メニューボタンを押したときの挙動をJava側に書くことはできますが、メニューを出し分けたければJSの状態を常にJava側にフィードバックしておかないといけなくなります。

x$(document).on("menubutton", func);でJS側で完結させましょう。

代わりに設定ページに遷移させる

ページ遷移は、一枚のHTMLの中に複数のdivを作っておいて、切り替えて使うのが良さそうです。以下のサンプルが参考になると思います。

https://github.com/alunny/phonegap-start

上記サンプルのlawnchairはちょっとバージョン古いので、気をつけてください。

sqlite returned: error code = 14, msg = cannot open file at source line 25467

lawnchairを使っていて、このエラーが表示されたりしますが、動作に支障はないと気付くのに結構時間を使いました…

開発環境的な面では楽

Chromeとデベロッパーツールでほぼ開発できるので、実機への転送は画面の確認程度です。

ただし、chromeではdevicereadyイベントが起きないので、その辺の処理が必要なら開発時用と実機用で分ける必要はあります。

xui.jsは機能的にはかなり弱い

http://phonegap.com/tools

http://xuijs.com/

xui.jsがよく使われるようですが、機能はかなり少ないです。そのかわり、軽いです。どうも自分でextendして使うような感じです。見た目に関してはサポートしてくれないので、その辺の手間もかけたくない場合はSenchaTouchとかjQuery Mobileとかの方がいいと思います。

多言語化しにくい

https://github.com/phonegap/phonegap-plugins/tree/master/Android

Globalizationプラグインは地域とか言語を取得してくれるだけなので、文章を差し替えたりはできません。

assetsからindex.htmlを読み込む際にoncreateで切り分けるのも可能ですが、文章だけ変えたindex.htmlのコピーを作ることになるのでちょっと更新が手間になります。

でも多分values/string.xmlとか使ってしまうとブラウザでの開発がやりにくくなるので、JSのフレームワークの機能に頼るのがベターではないかと思います。

JSでnavigator.languageを取得しても英語にしかならない

さっき多言語化しにくいと書きましたが、言語を切り分けようとしてnavigator.languageを参照してもenしか返ってきません。

http://groups.google.com/group/phonegap/browse_thread/thread/f6b6ba2021ee7fb4/239e7ecb71619579

上記URLの方法でUserAgentから言語を取り出すようです。

ちなみにアプリの起動中では切り替わらないようで、アプリの強制終了→起動をしないと言語設定が有効になりません。

戻るボタンでアプリから抜けても、プロセスが終了しない

ひとつ上の言語切り替えとセットで少しハマりました。これは別にいいのかな。

Ripple使うと少し楽

Chromeの拡張のRipple Mobile Environment Emulatorをインストールすると、画面サイズを端末ごとに合わせてくれたり、位置情報のエミュレーションをしてくれたりします。

Chrome ウェブストア – Ripple Mobile Environment Emulator (Beta)

ローカルのファイルを読ませるためには、プラグインにローカルファイルへのアクセスの許可をする必要があります。

Chromeの拡張機能を管理→Ripple→アイコン左の三角をクリックして詳細を表示→ファイルのURLへのアクセスを許可するにチェック、でOKです。

でも画面サイズがうまく一致しなかったです。謎。

それと、スタンドアローン版もあるのですが、ローカルファイルの指定の仕方がわからず、結局使ってません。それと、バージョンが微妙に古いです。

機能とか使い方とか、別エントリでまとめようと思います。

ネットワークのパーミッションが必要

ネットワークを使わなくても、android.permission.ACCESS_NETWORK_STATEが必要です。これがないとSecurityExceptionが発生します。

他のパーミッションは全部消しても大丈夫です。

結論

結論としては、簡単なアプリならいいかな、という一般的な感想ですが、とりあえずJSのフレームワーク選定はものすごく重要です。自由にレイアウトできる軽量な画面遷移フレームワークがあったらいいかな、と。

あとはHTML+CSSを組むのにやたら時間がかかってしまったので、これを何かGUIのエディタでどうにかすべきだと思いました。Dreamweaverとかでもいいですが、もっとCSSの知識不要で作りたいところです。