LINEのメッセージをモールス信号で通知する。

春休みなので、実家に帰省した。
帰省の電車の中で、友人たちとどういう訳か題位の話になり「出来たら面白いよね。」となったので、実際にAndroidのアプリを作ってみた。

#要件定義とか仕様

Lineのメッセージの受信は、Android4.3からの新機能のNotificationListenerServiceを使用。
モールス信号はスマホのバイブレーションを使用した。

　Lineのメッセージの取得には[非公式API](https://github.com/carpedm20/LINE)を使用することも考えたが、言語がpythonであることと非公式であることによるLineアカウントのBANの危険性があったので、今回は使用しなかった。
　モールス信号は、本当は通知LEDによる通知にしたかったが通知LEDのAPIであるnotificationクラスを使用するとon,offの時間のみの指定なので、一定周期で通知LEDを光らせることしかできない。
そこで、バイブレーションなら動かす時間のみの指定ができるのでThread.sleep()と組み合わせてモールス信号を実現した。
また、バイブレーションでの通知ならスマホをポケットから取り出さなくてもメッセージを読めるかも!?ということにも気づいた。

 lineのメッセージをバイブを用いてモールス信号で通知する。(29文字まで)
 英数字またはカタカナのみ使用可能。また使用するときはどちらかのみの排他的な使用のみとする。
  
#使用方法
相手にモールス信号で受信させたいときにメッセージの先頭に「m 」コマンドを付けて、メッセージを送る。
自分がモールス信号で受信したいときは相手に事前に「連絡はモールス信号で！」と頼み込んでおこう！

本アプリは、Lineのメッセージの受信のためにNotificationListenerServiceを用いて通知を取得しているためインストール後、設定から通知ヘのアクセスを許可する必要がある。

まず、Androidの設定を開きセキュリティの項目を探す。
![Screenshot_2015-03-06-17-57-55.png](https://qiita-image-store.s3.amazonaws.com/0/70879/ccdfd359-36ed-f6eb-28cc-b834e0079dae.png)



セキュリティの項目を開くと下の方に通知へのアクセスがあるので開く。
![Screenshot_2015-03-06-18-16-46.png](https://qiita-image-store.s3.amazonaws.com/0/70879/568970e7-f7b6-a56a-13e7-41f88515667d.png)



Morse_Converterにチェックを入れる。
![Screenshot_2015-03-06-18-16-54.png](https://qiita-image-store.s3.amazonaws.com/0/70879/4c8fe958-d57c-6d58-cbcb-6af3d736b46e.png)



恐ろしい警告が出るが個人情報を○国のサーバーに送ったりしてないのでOKする。
![Screenshot_2015-03-06-18-17-08.png](https://qiita-image-store.s3.amazonaws.com/0/70879/df3e8868-0df6-ef30-fb69-692612f1b82b.png)



チェックが入ったことを確認する。
![Screenshot_2015-03-06-18-17-19.png](https://qiita-image-store.s3.amazonaws.com/0/70879/c5068a4a-e301-9917-81e2-1e66878a2dbd.png)



#ソースコード

MainActivityの起動時にNotificationServiceを起動させているが、ここで起動させなくてもシステムから勝手に起動してくれる。MainActivityを残しておく理由は後でモールス信号の速度の設定を置こうと思っているため。今は使わないので実装しない。

```java:MainActivity.java
package com.example.morse_converter;

import android.app.Activity;
import android.app.ActionBar;
import android.app.Fragment;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import android.os.Build;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (savedInstanceState == null) {
            getFragmentManager().beginTransaction()
                    .add(R.id.container, new PlaceholderFragment())
                    .commit();
        }
        
        //サービスの起動
        Intent intent = new Intent(MainActivity.this, NotificationService.class);
        startService(intent);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    /**
     * A placeholder fragment containing a simple view.
     */
    public static class PlaceholderFragment extends Fragment {

        public PlaceholderFragment() {
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {
            View rootView = inflater.inflate(R.layout.fragment_main, container, false);
            return rootView;
        }
    }

}

```

```java:NotificationService.java
package com.example.morse_converter;

import java.util.Hashtable;

import android.os.Vibrator;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.widget.Toast;

public class NotificationService extends NotificationListenerService {
 
    private String TAG = "Notification";
    
    MorseMaker mm;
    
    @Override
    public void onCreate() {
        super.onCreate();
        
        mm = new MorseMaker();
        
        Toast.makeText(this, "StartService", Toast.LENGTH_LONG).show();
    }
 
    @Override
    public void onDestroy() {
        super.onDestroy();
    }
 
    @Override
    public void onNotificationPosted(StatusBarNotification sbn) {
        // ステータスバーに通知があった場合
        Log.d(TAG,"onNotificationPosted");
        Log.d(TAG,(String) sbn.getNotification().tickerText);
        
        //lineのメッセージを受信したとき
        if(sbn.getPackageName().equals("jp.naver.line.android")){
        	
        	//ユーザー名を除き、メッセージの取り出す
        	String meg = getMessage((String) sbn.getNotification().tickerText);
        	//「m 」コマンドの取り出し
        	String head = meg.substring(0, 2);
        	//本文の取り出し
        	String body = meg.substring(2);
        	//コマンドの判定。半角と全角パターン
        	if((head.equals("m ")||head.equals("M ")) || (head.equals("ｍ　")||head.equals("Ｍ　"))){
        		mm.morse(body);
        	}
        }
        
    }
 
    @Override
    public void onNotificationRemoved(StatusBarNotification sbn) {
        // ステータスバーから通知が消された場合
        Log.d(TAG,"onNotificationRemoved");
    }
    //通知のユーザー名を除きメッセージを取り出す。
    //「LINEお天気：入力内容が正しくありません。表示されている数字を入力してくださ...」こんな感じで来るこで：以降を返す。
    private String getMessage(String s){
    	String meg = null;
    	for(int i=0; i<s.length(); i++){
    		if(s.charAt(i) == '：'){
    			meg = s.substring(i+2);
    			break;
    		}
    	}
    	return meg;
    }
    
    
    
    
    
    private class MorseMaker{
    	
    	private Vibrator vib;
    	
    	private static final String TAG = "Morse";
    	
    	/*
    	 * 国際モールス符号は短点（・）と長点（－）を組み合わせて、アルファベット・数字・記号を表現する。
    	 * 長点1つは短点3つ分の長さに相当し、各点の間は短点1つ分の間隔をあける。
    	 * また、文字間隔は短点3つ分、語間隔は短点7つ分あけて区別する。 
    	 */
    	//上記のとおりなので、慣れたらこの規定に合わせること。
    	
    	//文字と文字の間隔
    	private static final int INTERVAL = 1000;
        //ツーの長さ
        private static final int LONG = 300;
        //トンの長さ
        private static final int SHORT = 100;
        //1文字内の間隔。トンツーの間隔
        private static final int BETWEEN = 500;
        
        //アルファベットのモールス信号
        private Hashtable<String, String> table_a;
        //半角から全角
        private Hashtable<String, String> table_h;
        //「ガ」とかの濁音を「カ゛」のように分解する。
        private Hashtable<String, String> table_s;
        //和文のモールス信号
        private Hashtable<String, String> table_i;
        
        
    	
    	//コンストラクタ
    	//初期化
    	MorseMaker(){
    		
    		 // Vibratorクラスのインスタンス取得
        	vib = (Vibrator)getSystemService(VIBRATOR_SERVICE);
        	
        	table_a = new Hashtable<String, String>();
        	table_a.put("A", "・−");
        	table_a.put("B", "−・・・");
        	table_a.put("C", "−・−・");
        	table_a.put("D", "−・・");
        	table_a.put("E", "・");
        	table_a.put("F", "・・−・");
        	table_a.put("G", "−−・");
        	table_a.put("H", "・・・・");
        	table_a.put("I", "・・");
        	table_a.put("J", "・−−−");
        	table_a.put("K", "−・−");
        	table_a.put("L", "・−・・");
        	table_a.put("M", "−−");
        	table_a.put("N", "−・");
        	table_a.put("O", "−−−");
        	table_a.put("P", "・−−・");
        	table_a.put("Q", "−−・−");
        	table_a.put("R", "・−・");
        	table_a.put("S", "・・・");
        	table_a.put("T", "−");
        	table_a.put("U", "・・−");
        	table_a.put("V", "・・・−");
        	table_a.put("W", "・−−");
        	table_a.put("X", "−・・−");
        	table_a.put("Y", "−・−−");
        	table_a.put("Z", "−−・・");
        	table_a.put("1", "・−−−−");
        	table_a.put("2", "・・−−−");
        	table_a.put("3", "・・・−−");
        	table_a.put("4", "・・・・−");
        	table_a.put("5", "・・・・・");
        	table_a.put("6", "−・・・・");
        	table_a.put("7", "−−・・・");
        	table_a.put("8", "−−−・・");
        	table_a.put("9", "−−−−・");
        	table_a.put("0", "−−−−−");
        	
        	//半角から全角
        	table_h = new Hashtable<String, String>();
        	table_h.put("ｱ", "ア");
        	table_h.put("ｲ", "イ");
        	table_h.put("ｳ", "ウ");
        	table_h.put("ｴ", "エ");
        	table_h.put("ｵ", "オ");
        	table_h.put("ｶ", "カ");
        	table_h.put("ｷ", "キ");
        	table_h.put("ｸ", "ク");
        	table_h.put("ｹ", "ケ");
        	table_h.put("ｺ", "コ");
        	table_h.put("ｻ", "サ");
        	table_h.put("ｼ", "シ");
        	table_h.put("ｽ", "ス");
        	table_h.put("ｾ", "セ");
        	table_h.put("ｿ", "ソ");
        	table_h.put("ﾀ", "タ");
        	table_h.put("ﾁ", "チ");
        	table_h.put("ﾂ", "ツ");
        	table_h.put("ﾃ", "テ");
        	table_h.put("ﾄ", "ト");
        	table_h.put("ﾅ", "ナ");
        	table_h.put("ﾆ", "ニ");
        	table_h.put("ﾇ", "ヌ");
        	table_h.put("ﾈ", "ネ");
        	table_h.put("ﾉ", "ノ");
        	table_h.put("ﾊ", "ハ");
        	table_h.put("ﾋ", "ヒ");
        	table_h.put("ﾌ", "フ");
        	table_h.put("ﾍ", "ヘ");
        	table_h.put("ﾎ", "ホ");
        	table_h.put("ﾏ", "マ");
        	table_h.put("ﾐ", "ミ");
        	table_h.put("ﾑ", "ム");
        	table_h.put("ﾒ", "メ");
        	table_h.put("ﾓ", "モ");
        	table_h.put("ﾔ", "ヤ");
        	table_h.put("ﾕ", "ユ");
        	table_h.put("ﾖ", "ヨ");
        	table_h.put("ﾗ", "ラ");
        	table_h.put("ﾘ", "リ");
        	table_h.put("ﾙ", "ル");
        	table_h.put("ﾚ", "レ");
        	table_h.put("ﾛ", "ロ");
        	table_h.put("ﾜ", "ワ");
        	table_h.put("ｦ", "ヲ");
        	table_h.put("ﾝ", "ン");
        	table_h.put("ﾞ", "゛");
        	table_h.put("ﾟ", "゜");
        	
        	
        	//「ガ」などの濁音を分解
        	table_s = new Hashtable<String, String>();
        	table_s.put("ガ", "カ゛");
        	table_s.put("ギ", "キ゛");
        	table_s.put("グ", "ク゛");
        	table_s.put("ゲ", "ケ゛");
        	table_s.put("ゴ", "コ゛");
        	table_s.put("ザ", "サ゛");
        	table_s.put("ジ", "シ゛");
        	table_s.put("ズ", "ス゛");
        	table_s.put("ゼ", "セ゛");
        	table_s.put("ゾ", "ソ゛");
        	table_s.put("ダ", "タ゛");
        	table_s.put("ヂ", "チ゛");
        	table_s.put("ヅ", "ツ゛");
        	table_s.put("デ", "テ゛");
        	table_s.put("ド", "ト゛");
        	table_s.put("バ", "ハ゛");
        	table_s.put("ビ", "ヒ゛");
        	table_s.put("ブ", "フ゛");
        	table_s.put("ベ", "ヘ゛");
        	table_s.put("ボ", "ホ゛");
        	table_s.put("パ", "ハ゜");
        	table_s.put("ピ", "ヒ゜");
        	table_s.put("プ", "フ゜");
        	table_s.put("ペ", "ヘ゜");
        	table_s.put("ポ", "ホ゜");
        	
        	//和文
        	table_i = new Hashtable<String, String>();
        	table_i.put("イ", "・−");
        	table_i.put("ロ","・−・−");
        	table_i.put("ハ","−・・・");
        	table_i.put("ニ","−・−・");
        	table_i.put("ホ","−・・");
        	table_i.put("ヘ","・");
        	table_i.put("ト","・・−・・");
        	table_i.put("チ","・・−・");
        	table_i.put("リ","−−・");
        	table_i.put("ヌ","・・・・");
        	table_i.put("ル","−・−−・");
        	table_i.put("ヲ","・−−−");
        	table_i.put("ワ","−・−");
        	table_i.put("カ","・−・・");
        	table_i.put("ヨ","−−");
        	table_i.put("タ","−・");
        	table_i.put("レ","−−−");
        	table_i.put("ソ","−−−・");
        	table_i.put("ツ","・−−・");
        	table_i.put("ネ","−−・−");
        	table_i.put("ナ","・−・");
        	table_i.put("ラ","・・・");
        	table_i.put("ム","−");
        	table_i.put("ウ","・・−");
        	table_i.put("ヰ","・−・・−");
        	table_i.put("ノ","・・−−");
        	table_i.put("オ","・−・・・");
        	table_i.put("ク","・・・−");
        	table_i.put("ヤ","・−−");
        	table_i.put("マ","−・・−");
        	table_i.put("ケ","−・−−");
        	table_i.put("フ","−−・・");
        	table_i.put("コ","−−−−");
        	table_i.put("エ","−・−−−");
        	table_i.put("テ","・−・−−");
        	table_i.put("ア","−−・−−");
        	table_i.put("サ","−・−・−");
        	table_i.put("キ","−・−・・");
        	table_i.put("ユ","−・・−−");
        	table_i.put("メ","−・・・−");
        	table_i.put("ミ","・・−・−");
        	table_i.put("シ","−−・−・");
        	table_i.put("ヱ","・−−・・");
        	table_i.put("ヒ","−−・・−");
        	table_i.put("モ","−・・−・");
        	table_i.put("セ","・−−−・");
        	table_i.put("ス","−−−・−");
        	table_i.put("ン","・−・−・");
        	table_i.put("゛","・・");
        	table_i.put("゜","・・−−・");
    	}
    	
    	//引数の文字列をモールス信号にする
    	protected void morse(String s){
    		
    		//大文字にする
    		s = s.toUpperCase();
    		
    		String c;
    		
    		//文字数分繰り返す
    		for (int i = 0; i < s.length(); i++) {
    			//1文字づつ取り出す
                String oneChar = String.valueOf(s.charAt(i));
                //ハッシュテーブルから取り出し
                String code_a = table_a.get(oneChar);
                //和文のとき、またはアルファベットでないとき
                if (code_a == null) {
                	//ハッシュテーブルから取り出し
                	String code_h = table_h.get(oneChar); //半角から全角に変換
                	String code_s = table_s.get(oneChar); //「ガ」などの濁音を分解
                	
                	//半角であったとき(すでに全角に変換済み)
                	if(code_h != null){
                		c = table_i.get(code_h);
                		one_character_morse(c);
                	}
                	//分解したので二度分モールス信号を発信する。
                	else if(code_s != null){
                		c = table_i.get(String.valueOf(code_s.charAt(0)));
                		one_character_morse(c);
                		
                		c = table_i.get(String.valueOf(code_s.charAt(1)));
                		one_character_morse(c);
                	}
                	//通常処理
                	else{
                		c = table_i.get(oneChar);
                		if(c != null){
                			one_character_morse(c);
                		}
                		else{
                			//ハッシュテーブルにない文字や空白は何もしない。
                		}
                	}
                } 
                //アルファベットのとき
                else {
                	one_character_morse(code_a);
                }
            }
    	}
    	
    	//1文字のモールス信号を発信する
    	private void one_character_morse(String s){
    		//文字内のトンツー分繰り返す
            for (int j = 0; j < s.length(); j++) {

                char c = s.charAt(j);
                
                int time = 0;
                switch (c) {
                    case '−':
                        time = LONG;
                        break;
                    case '・':
                        time = SHORT;
                        break;
                }
                
                vib.vibrate(time);// 指定時間ONする
                
                //文字と文字の間の間隔(空白)
                if(j == s.length() -1){
                	try {
                        Thread.sleep(BETWEEN);
                    } catch (InterruptedException e) {
                    }
                }
                //文字内のトンツーの間隔(空白)
                else{
                	try {
                        Thread.sleep(INTERVAL);
                    } catch (InterruptedException e) {
                    }
                }
                
            }
    	}
    	
    }

}

```

```xml:AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.morse_converter"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="18"
        android:targetSdkVersion="19" />
    
    <!-- バイブの許可を追加 -->
    <uses-permission android:name="android.permission.VIBRATE"></uses-permission>

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.morse_converter.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        
      	<!-- ここから -->
	    <service android:name=".NotificationService"
	         android:label="@string/app_name"
	         android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
	         <intent-filter>
	              <action android:name=
	                  "android.service.notification.NotificationListenerService" />
	         </intent-filter>
	    </service>
    	<!-- ここまで追加 -->
    	
    </application>
</manifest>
```

apkファイル
https://www.dropbox.com/s/bhoq384991et1ed/com.example.morse_converter-1.apk?dl=0



このアプリの制作後に軽い事故が起こったので備忘録として残しておく。
http://qiita.com/Tama_maru/items/ce17d47f4c910759e0e3