AndroidのTimePickerDialogのタイトルを設定する

    class MyTimePickerDialog extends TimePickerDialog {

        public MyTimePickerDialog(Context context, OnTimeSetListener callBack,
                int hourOfDay, int minute, boolean is24HourView) {
            super(context, callBack, hourOfDay, minute, is24HourView);
        }

        @Override
        public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {
            // do nothing
        }

    }

時刻を変更するたびにタイトルを上書きしてしまうので、継承してonTimingChangedを何もしないようにオーバーライドしてしまえばOKです。

また、インスタンスを作ったときにも自動的にタイトルを設定しているので、自分でタイトルを上書きするのも忘れずに。

            final MyTimePickerDialog timePickerDialog = new MyTimePickerDialog(
                    this, new TimePickerDialog.OnTimeSetListener() {
                        @Override
                        public void onTimeSet(TimePicker view, int hourOfDay,
                                int minute) {
                            // do something
                        }
                    }, 0, 0, true);
            // ここでタイトルを設定する
            timePickerDialog.setTitle(R.string.dialog_title);
            timePickerDialog.show();


getExternalFilesDirがnullを返す場合

API Level 8から使えるようになったContext.getExternalFilesDirは外部ストレージのパスに、パッケージ名を追加して、さらにディレクトリが存在しなければ作成もしておいてくれるという、今までEnvironment.getExternalStorageDirectoryを使って書いていた部分を短くできる便利なメソッドです。

で、このメソッドがnullを返す場合、大抵はパーミッション不足です。

AndroidManifest.xmlに外部ストレージへの書き込みを許可する行を追加しましょう。

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

何が問題かって、このメソッドがパーミッションのExceptionを投げてくれずに、nullしか返さないことです。

ちなみにこのメソッドを使ってファイルをコピーするプログラムはこんな感じになりました。

    File dst = new File(mContext.getExternalFilesDir(null), "dst.txt");
    // dst = "/mnt/sdcard/Android/data/com.sample/files/dst.txt"

    FileChannel srcChannel = null;
    FileChannel destChannel = null;

    try {
        // ファイルのコピー
        if (dst.exists()) {
            dst.delete();
        }
        dst.createNewFile();
        FileInputStream in = mContext.openFileInput("src.txt");
        FileOutputStream out = new FileOutputStream(dst, false);

        srcChannel = in.getChannel();
        destChannel = out.getChannel();

        srcChannel.transferTo(0, srcChannel.size(), destChannel);
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (srcChannel != null) {
            try {
                srcChannel.close();
            } catch (IOException e) {
            }
        }
        if (destChannel != null) {
            try {
                destChannel.close();
            } catch (IOException e) {
            }
        }
    }


Intent連携でのメール送信とFLAG_ACTIVITY_NEW_TASK

androidからIntentを使って外部Activityを起動するときに、contextがActivity contextじゃないと、AndroidRuntimeExceptionが発生します。

サービスや、独自のアプリケーションクラスからapplication contextを使って、メールクライアントを選択させてメール送信するためにstartActivityを実行したいという用事です。

E/AndroidRuntime(7525): android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity  context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?

FLAG_ACTIVITY_NEW_TASKをsetFlagsしてやればいいだけなのですが、その辺から適当にコードをコピーしてくるとハマる場合があります。

問題のコードはこれ。

        Intent intent = new Intent(Intent.ACTION_SEND);
        String[] to = { "hoge@sample.com" };
        intent.putExtra(Intent.EXTRA_EMAIL, to);
        intent.putExtra(Intent.EXTRA_TEXT, "test");
        intent.putExtra(Intent.EXTRA_SUBJECT, "send mail test");
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setType("message/rfc822");

        context.startActivity(Intent.createChooser(intent, "Choose Email Client"));

一見問題なさそうなのですが、これは結局Intent.createChooserがIntentを返すので、そのIntentに対してフラグを指定しなければいけないということでした。

なので、以下のようにする必要があります。

        Intent intent1 = new Intent(Intent.ACTION_SEND);
        String[] to = { "hoge@sample.com" };
        intent1.putExtra(Intent.EXTRA_EMAIL, to);
        intent1.putExtra(Intent.EXTRA_TEXT, "test");
        intent1.putExtra(Intent.EXTRA_SUBJECT, "send mail test");
        intent1.setType("message/rfc822");

        Intent intent2 = Intent.createChooser(intent1, "Choose Email Client");
        intent2.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        context.startActivity(intent2);

android – How can i call startActivity() to use ACTION_SEND from a different class – Stack Overflow


ISW11FをADBドライバで認識させる方法

Eclipseで開発をするときに、Arrows Z(ISW11F)がPCから認識しなくて困っていました。

PCからはFujitsu Mass storage USB Deviceと認識されてしまう場合の対処法です。

まずはドライバダウンロードします。
http://spf.fmworld.net/oss/usb_driver/isw11f/index.html

解凍したらReadme.txtは必ず読んでください。

インストール前に確認するべき項目は次の通り。

  • C:\Users\(ユーザー名)\.android\adb_usb.iniがあるか
  • 上記adb_usb.iniに「0x04C5」という行があるか(なければ行追加)
  • 端末がUSBデバッグになっているか

それとPCでデバイスマネージャーを起動しておきましょう。

これでUSBで端末を接続して、問題なくFujitsu HSUSB Deviceという項目が出現すればいいのですが、出現せず、下の画像のようになる場合があります。

WS000015

基本的には、すでに誤認識されてしまっているデバイスを削除してやればいいのですが、Fujitsu Mass storage USB Deviceの2行を削除しても、再接続すると元に戻ってしまいました。

なので、このデバイスの親ごと消す必要があります。作業は端末を接続したままで行います。

「ユニバーサル シリアル バス コントローラー」の下の「USB 大容量記憶装置」を右クリックして、削除します。ただし、複数デバイスがある場合は以下のように、デバイスマネージャーの表示を、「デバイス(接続別)」にしてやると、目的のデバイスがグループ化されるので、間違えずに消せるようになります。

WS000011

消したあとは、一度端末を外して、再度USBで接続すると、自動的にドライバのインストールが始まります。

WS000013

こうなれば成功です。ドライバーが見つかりませんと表示されますが、デバイス自体は認識されています。

WS000014

あとは、このFujitsu HSUSB Deviceを選択して、ドライバの更新をして、先ほどダウンロードして解凍したドライバファイルを選択すればAndroid Composite ADB Interfaceとして認識されます。

WS000016

ちなみにMass storage USB Deviceを直接「ドライバーの更新」すれば良いという情報があったのですが、試そうとしたら64bitドライバがない旨のメッセージが出て、進めませんでした。

今回はこちらのサイトが参考になりました。

AndroidがUSBデバッグで接続できない場合(USBストレージとしてしか認識されない場合) | コラビットの中の人


Windows版PHP 5.4.0でAPCを使う

まずはAPCをダウンロードします。

downloads.php.net/pierre/

php_apc-3.1.10-5.4-vc9-x86.zip (2012-04-11 23:46 -0700)
をダウンロードしました。

展開すると、ntsとtsのフォルダが作成されます。使っているPHPがNon Thread Safe版かThread Safe版かによって使う方を決めてください。

それぞれのフォルダの中に、php_apc.dllがあるので、phpがインストールされているフォルダのextフォルダにコピーします。

その後、php.iniを開いて
extension=php_apc.dll
を追加し、Apacheを再起動すればOKです。

ちなみに、dllのバージョンを見ると、5.4.1RC1-devと書かれていました。

phpinfo()ではこのようになります。特に問題なく使えているようです。

WS000000


Mouse without Bordersでルータを超える

Microsoft The Garageから提供されたMouse without Bordersは、Synergyのような、一台のキーボードとマウスで、他のPCを操作できるソフトウェアです。

Microsoft download from The Garage: Mouse without Borders – Next at Microsoft – Site Home – TechNet Blogs

これがかなり便利で、複数のPCを使う人にはぜひおすすめしたいところです。

Synergyと比べて、優れている点はこんなところです。

  • 人に勧められるレベルの簡単さ
  • UAC対応
  • ファイルのコピーがスクリーン間でドラッグアンドドロップでできる
  • 2台のPCのどちらのマウス(キーボードでも可)を使っても、双方向に操作できる
  • 1台のマウス(キーボードでも可)で複数のPCに、同時に同じ操作をさせられる
  • スクリーンの位置設定なども自動的に同期してくれる
  • ログイン前でも操作可能

Windowsキー+Lでのロックは、SynergyでもMouse without Bordersでも同じように両画面ロックしてしまうみたいですね。

インストール方法はこちらを参考にしてください。

窓の杜 – 【NEWS】Microsoft、複数PCでマウス・キーボードをLAN共有する「Mouse Without Borders」

で、今使っているLANの中に、もう一台ルータがいて、その中でまた別のネットワークを構築しているのですが、そのネットワーク内にあるPCと、外側のPCとでMouse without Bordersを使ってみました。

ちなみにWINSはない環境です。

image

Mouse without Bordersはコンピュータ名で接続先PCを特定するため、コンピュータ名からIPが特定できないといけません。

まず、それぞれのPCのhostsファイルに相手のIPを記述します。

PC1 C:\Windows\System32\drivers\etc\hosts
192.168.0.10 PC2

PC2 C:\Windows\System32\drivers\etc\hosts
172.16.10.1 PC1

ルータにはファイアウォールなどは入れていないので、これだけの設定でPC2からPC1への接続はできます。もしPC2からPC1を操作するだけなら、これで十分です。

双方向操作や、PC1からPC2を操作する場合はもう少し設定が必要です。

Mouse without BordersはデフォルトではTCPで接続するようになっています。使われるポート番号は15100と15101です。これをルータでアドレス変換してあげる必要があります。

AirStationでの設定はこんな感じです。

image

ちなみに15101だけでも接続できました。15100は何に使っているのか謎です。

これでルータは通るようになりましたが、192.168.0.10がどこを指すのか、PC1にはわかりません。

ですので、静的ルーティングを設定します。

管理者権限でコマンドプロンプトを開いて、
> route -p add 192.168.0.0 mask 255.255.255.0 172.16.10.2
とすればOKです。-pオプションは永続化です。

これで快適に2台のPCを操作できるようになりました。


PHPからarpテーブルを参照する

PHPからarpコマンドを実行してLAN内のMACアドレスとIPアドレスの変換テーブルを参照できるようにしてみました。単純に結果を正規表現にかけているだけです。

ちなみにひとつのMACが複数のIPを持つ場合には対応していません。それと、arpの性質上、頻繁にキャッシュクリアされるので、用途によっては注意が必要です。

<?php

$table = array();
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
    exec('arp -a', $output); // for Windows
}
else {
    exec('/usr/sbin/arp -a -n', $output); // for Linux(debian)
}
foreach ($output as $line) {
    if (preg_match('/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}).*([0-9a-f]{2}[\-:][0-9a-f]{2}[\-:][0-9a-f]{2}[\-:][0-9a-f]{2}[\-:][0-9a-f]{2}[\-:][0-9a-f]{2})/i', $line, $matches)) {
        $ip = $matches[1];
        $mac = strtoupper(str_replace(array('-', ':'), '', $matches[2]));
        $table[$mac] = $ip;
    }
}

var_dump($table);

結果

array(2) {
  ["123456789ABC"]=>
  string(13) "192.168.0.1"
  ["0003FFFFFFFF"]=>
  string(15) "192.168.131.254"
}

Linuxのarpコマンドがarp -aで実行するとすごく遅いので、原因を調べていたら、デフォルトではIPアドレスからDNSの逆引きをしているということでした。-nオプションを付けることで、DNSリクエストを送らなくなるので一瞬で表示できます。


MediaPlayerで通知音を鳴らす

AndroidのMediaPlayerで通知音を鳴らすときに、音量設定を反映させます。

MediaPlayer.createメソッドでインスタンスを作成した状態だと、「メディア音量」というものが反映されるので、setAudioStreamTypeメソッドで再生するオーディオのタイプを指定してやります。これでシステムの設定画面で指定した音量が反映されます。

が、setAudioStreamTypeメソッドをMediaPlayer.createで作成したインスタンスに対して実行すると、
error (-38, 0)
とか
prepareAsync called in state 8
とか
setAudioStream called in state 8
とかエラーが起こります。

これはMediaPlayer.createメソッドが、内部的にすでにprepare(準備)メソッドを呼んでいるためで、ストリームタイプの変更は、準備の前に実行しておく必要があります。

コードはこんな感じになりました。

    public static void playSound(Context context, String url) {

        if (url != null) {
            Uri uri = Uri.parse(url);
            // MediaPlayer.createはprepareを実行してしまうのでnew MediaPlayer()を使う
            // MediaPlayer mp = MediaPlayer.create(context, uri);
            MediaPlayer mp = new MediaPlayer(); 
            try {
                mp.setDataSource(context, uri);
                // setAudioStreamTypeはprepare前に実行する必要がある
                mp.setAudioStreamType(AudioManager.STREAM_NOTIFICATION); 
                mp.setLooping(false); 
                // prepareの前後で使えるメソッドが異なる
                mp.prepare(); 
                mp.seekTo(0);
                mp.start(); 
            } catch (IllegalStateException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

ちなみにマナーモードのときは、何も状態をチェックしなくてもストリームタイプを通知音にしておけば、音は鳴りません。MediaPlayer.createで作ると、メディアになってしまうので、マナーモードが反映されません。

それと、一部機種(音が長いと?)ではsetDataSourceが失敗する場合があるそうです。詳しくは以下。

Ringtoneを再生する時にFileDescriptorを使いたい – 日本Androidの会 | Google グループ


タスクスケジューラとPHPのコンソールアプリケーション

PHP5.3+Silexを使って、Windows上で定期的に処理をするコンソールアプリケーションを作っていたのですが、タスクスケジューラが起動するタイミングで、デスクトップにコマンドプロンプトのウィンドウ(DOS窓)が表示されてしまう現象に悩んでいました。

ウィンドウを表示させない方法として、ショートカットを作成してウィンドウ最小化するという手で試してみたのですが、taskeng.exeというタイトルのウィンドウが出たままになって止まってしまう状況でした。(多分PHPファイルを引数として認識していない)

結論としては簡単で、php.exeの代わりにphp-win.exeを使うだけです。

PHP: CLI と CGI – Manual

php-win.exeはコンソールを表示させないので、これを実行ファイルとしてタスクスケジューラに登録すればOKです。

ついでにタスクスケジューラに登録する方法も書いておきます。5分おきにPHPを実行し続けるタスクをコマンドプロンプトから登録するコマンドです。最後の/Fは確認メッセージの抑制。

schtasks /create /tn "Test PHP" /tr "C:\php\php-win.exe C:\hoge\console.php args" /sc minute /mo 5 /it /F

削除はこちら。

schtasks /delete /tn "Test PHP" /f


Silexで外部APIにHTTP Requestする

最近Silex始めました。

まずは外部のWebServerにリクエストを投げる方法です。軽量なWebClientであるBuzzを使います。gitでBuzz本体とProviderをダウンロードします。

$ cd vendor/
$ git clone https://github.com/kriswallsmith/Buzz.git Buzz
$ cd Buzz/
$ git checkout v0.5
$ git submodule update --init
$ cd ..
$ git clone https://github.com/marcw/silex-buzz-extension.git silex-buzz-extension

src/bootstrap.phpに追加します。

$app['autoloader']->registerNamespace('MarcW', __DIR__ . '/../vendor/silex-buzz-extension/lib');

$app->register(new MarcW\Silex\Provider\BuzzServiceProvider(), array(
    'buzz.options' => array(
    ),
    'buzz.class_path' => __DIR__ . '/../vendor/Buzz/lib',
));

これで準備完了です。あとはアプリケーションから

$app['buzz']->getClient()->setTimeout(30);
$response = $app['buzz']->get('http://www.google.co.jp');

echo $app['buzz']->getLastRequest()."\n";
echo $response;

として呼び出すことができます。Buzzのデフォルトのタイムアウトが5秒で、早すぎたので30秒に変更しておきました。