Moverio Controller Function SDK
Androidデベロッパーズガイド
専用コントローラー向けのAndroidアプリケーションを開発するために必要となる手順について記載します。
Windows10を搭載したパソコンにAndroid SDKを導入する方法について記載します。
Android Studioを下記のサイトからダウンロードします。
https://developer.android.com/studio/
インストーラーの指示に従いAndroid Studioをインストールします。
例)C:\Users\<ユーザー名>\AppData\Local\Android\Sdk
※以降、上記フォルダにAndroid Studioがインストールされていることを前提に記載します。
プロキシ設定の必要なネットワーク環境でアプリケーションを開発する場合は、Android Studio のプロキシ設定を行ってください。下記のサイトで詳細な手順を確認してください。
https://developer.android.com/studio/intro/studio-config#gradle-plugin
プロキシ設定が不明な場合は、ネットワーク管理者にプロキシを利用した外部ネットワークへの接続方法についてお問い合わせください。
アプリケーションの開発に必要なツール類は、Android SDK Manager を使用してください。下記のサイトで詳細な手順を確認してください。
https://developer.android.com/studio/intro/update#sdk-manager
ADBの接続確認コマンドでパソコンとホスト機器が接続されているか確認することができます。
コマンドプロンプトを起動し、"cd C:\Users\<ユーザー名>\AppData\Local\Android\sdk\platform tools"
を実行しフォルダを移動します。
※環境変数で上記のPathを通しておくと便利です。
"adb devices"を実行してリストにデバイス名が表示されればADB接続ができています。
※表示されない場合は、ホスト機器をUSB接続しなおし、再度"adb devices"を実行してください。
Moverio Controller Function SDKは、専用コントローラーを制御するための専用APIを有しています。専用APIの利用方法について説明します。
下記利用方法は、Android Studio でのアプリケーション開発を前提としています。
Android StudioのProjectViewを表示し、[File]--[New]--[Directory]で"libs"フォルダを作成します。
C:\Users\<ユーザー名>\AndroidStudioProjects\<アプリケーション名>\app\libs が作成されますので、ここへ Moverio Controller Function SDK_1.0.0.aar を置きます。
(作成したプロジェクトのフォルダが C:\Users\<ユーザー名>\AndroidStudioProjects の場合)
※以降、C:\Users\<ユーザー名>\AndroidStudioProjects\<アプリケーション名>にプロジェクトがあるものとして説明しています。
その後、アプリケーションの最小APIレベルを28で定義し、アプリケーションの依存関係を追加してください。
apply plugin: 'com.android.application'
android {
:
// Add API level 28
minSdkVersion 28
:
}
dependencies {
:
// Add Moverio Controller Function SDK.aar
implementation files('libs/MoverioControllerFunctionSDK_1.0.0.aar')
:
:
}
Android Studio上部のSync Project with Gradle Filesボタンを押して、Gradleの変更をプロジェクトに反映します。
USB接続型Moverioのアプリケーション開発では、USB接続型Moverioと専用コントローラーをUSB接続してアプリケーション動作を確認します。このときパソコンと専用コントローラーはUSB接続されていないためデバッグできません。
上記を解決する方法として、ネットワークを介して専用コントローラーにadb接続することでUSB接続型Moverioと専用コントローラーをUSB接続したまま開発する方法について記載します。下記サイトも合わせて確認ください。
https://developer.android.com/studio/command-line/adb#wireless
# adb tcpip 5555
# adb connect ip_address (例: adb connect 192.168.1.10#5555)
専用コントローラーのキー操作やタッチ操作のロックには、com.epson.moverio.btcontrol.BtCustomKeyをインポートしてください。
import com.epson.moverio.btcontrol.BtCustomKey;
BtCustomKeyクラスのインスタンスを作成して、setKeyEnable()メソッドの第一引数に下表のキーの定数を指定し、第二引数にboolean型のロック(false)、または、解除(true)を指定し、呼び出してください。
定数 | 説明 |
---|---|
BtCustomKey.VOLUME_UP | 音量アップキー |
BtCustomKey.VOLUME_DOWN | 音量ダウンキー |
BtCustomKey.TRIGGER | 切替キー |
BtCustomKey.HOME | HOMEキー |
BtCustomKey.BACK | BACKキー |
BtCustomKey.APP_SWITCH | 履歴キー |
BtCustomKey.POWER | 電源ボタン |
BtCustomKey.ALLKEY | すべての物理キーを指定する際に使用する。 |
public class UIControlActivity extends Activity {
private BtCustomKey mBtCustomKey = null;
private Button mButton_powerKeyLock = null;
@Override
public final void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBtCustomKey = new BtCustomKey(this);
mButton_powerKeyLock = (Button) findViewById(R.id.button_powerKeyLock);
mButton_powerKeyLock.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mBtCustomKey.setKeyEnable(BtCustomKey.POWER, false); /* Disable power key. */
}
});
}
:
:
}
専用コントローラーの物理キーに割り当てられたキーコードを変更することができます。ただし、電源ボタンのキーコードを変更することはできません。
専用コントローラーのキーコードのカスタマイズには、com.epson.moverio.btcontrol.BtCustomKeyをインポートしてください。
import com.epson.moverio.btcontrol.BtCustomKey;
BtCustomKeyクラスのインスタンスを作成して、setKeyAssign()メソッドの第一引数に下表の対象の物理キーを指定し、第二引数に下表の変更可能なキーコードを指定し、呼び出してください。
対象の物理キー | 変更可能なキーコード |
---|---|
BtCustomKey.VOLUME_UP | KeyEvent.KEYCODE_VOLUME_UP |
BtCustomKey.VOLUME_DOWN | KeyEvent.KEYCODE_VOLUME_DOW |
BtCustomKey.TRIGGER | KeyEvent.KEYCODE_FUNCTION |
BtCustomKey.HOME | KeyEvent.KEYCODE_HOME |
BtCustomKey.BACK | KeyEvent.KEYCODE_BACK |
BtCustomKey.APP_SWITCH | KeyEvent.KEYCODE_APP_SWITCH KeyEvent.KEYCODE_F1 |
KeyEvent.KEYCODE_F2 | |
KeyEvent.KEYCODE_F3 | |
KeyEvent.KEYCODE_F4 |
public class UIControlActivity extends Activity {
private BtCustomKey mBtCustomKey = null;
private Button mButton_appSwitch2Home = null;
@Override public final void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBtCustomKey = new BtCustomKey(this);
mButton_appSwitch2Home = (Button) findViewById(R.id.button_appSwitch2Home);
mButton_appSwitch2Home.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View view) {
mBtCustomKey.setKeyAssign(BtCustomKey.APP_SWITCH, KeyEvent.KEYCODE_HOME);
}
});
}
:
:
}
キー操作ロック、及びキーコードカスタマイズにより変更したキー状態を初期状態に戻すことができます。
キー操作ロック、またはキーコードカスタマイズで生成したインスタンスで、resetToDefault()を実行してください。
mBtCustomKey.resetToDefault();
※キー操作によるキー状態の初期化: "切替キー、ホームキー、音量Up キーを同時に3秒間押し続ける"ことによって、キー状態を初期化することができます。キー状態を変更したままアプリが異常終了してしまった場合に強制的にキー状態を元に戻す場合に使用して下さい。
専用コントローラーのLEDインジケーターの点灯/非点灯を制御することができます。
コントローラーLEDの制御には、com.epson.moverio.btcontrol.BtControllerLedModeをインポートしてください。
import com.epson.moverio.btcontrol.BtControllerLedMode;
BtControllerLedModeクラスのインスタンスを作成して、setControllerLedMode()メソッドの第一引数に下表のキーの定数を指定して呼び出してください。
定数 | 説明 |
---|---|
BtControllerLedMode.MODE_OFF | Not lighted mode |
BtControllerLedMode.LED_MODE_NORMAL | Lighted mode |
public class UIControlActivity extends Activity {
private BtControllerLedMode mBtLed = null;
private Button mButton_ledOff = null;
@Override public final void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBtLed = new BtControllerLedMode (this);
mButton_ledOff = (Button) findViewById(R.id.button_appSwitch2Home);
mButton_ledOff.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View view) {
mBtLed.setControllerLedMode(BtControllerLedMode.MODE_OFF);
}
});
}
:
:
}
専用コントローラーにはヘッドセットとコントローラーに同じ映像が表示されるミラーモードと、コントローラーをトラックパッドとして使用することができるトラックパッドモード、コントローラーを十字キーとして使用することができるアシストモードがあります。
これらのモードを本API切り替えることができます。
UIモード切替には、com.epson.moverio.btcontrol.UIControlをインポートしてください。
import com.epson.moverio.btcontrol.UIControl;
UIControlクラスのインスタンスを作成して、setUiMode()メソッドの第一引数に下表のキーの定数を指定して呼び出してください。
定数 | 説明 |
---|---|
UIControl.UI_MODE_MIRROR | ミラーモードに切り替える |
UIControl.UI_MODE_TRACK | トラックパッドモードに切り替える |
UIControl.UI_MODE_ASSIST | アシストモードに切り替える |
public class UIControlActivity extends Activity {
private UIControl mUiMode = null;
private Button mButton_modeTrack = null;
@Override public final void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mUiMode = new UIControl (this);
mButton_modeTrack = (Button) findViewById(R.id.button_appSwitch2Home);
mButton_modeTrack.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View view) {
mUiMode.setUiMode(UIControl. UI_MODE_TRACK);
}
});
}
:
:
}
※本APIでモードをミラーモードからトラックパッドモード、またはアシストモードへ切り替える時、アプリ画面を再構築するために一度アプリが自動的に終了します。モード切替が完了した後、終了する前のアプリ画面(アクティビティ)が自動的に再表示されます。
専用コントローラーのOSバージョン2.1.00より、コントローラーを十字キーとして使用することができるアシストモードが追加されました。
アシストモードに切り替わるとコントローラーに十字キーとエンターキーが表示され、キー操作に応じてアプリはAndroid APIのdispatchKeyEvent()により以下のキーイベントを取得することができます。
キーの名称 | キーイベント |
---|---|
Up key | KeyEvent.KEYCODE_DPAD_UP |
Down key | KeyEvent.KEYCODE_DPAD_DOWN |
Left key | KeyEvent.KEYCODE_DPAD_LEFT |
Right key | KeyEvent.KEYCODE_DPAD_RIGHT |
Enter key | KeyEvent.KEYCODE_DPAD_CENTER |
@Override
public boolean dispatchKeyEvent(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.KEYCODE_DPAD_UP && e.getAction() == KeyEvent.ACTION_DOWN) {
// Press Up key
} else if(e.getKeyCode() == KeyEvent.KEYCODE_DPAD_DOWN && e.getAction() == KeyEvent.ACTION_DOWN) {
// Press Down key
} else if(e.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT && e.getAction() == KeyEvent.ACTION_DOWN) {
// Press Left key
} else if(e.getKeyCode() == KeyEvent.KEYCODE_DPAD_RIGHT && e.getAction() == KeyEvent.ACTION_DOWN) {
// Press Right key
} else if(e.getKeyCode() == KeyEvent.KEYCODE_DPAD_CENTER && e.getAction() == KeyEvent.ACTION_DOWN) {
// Press Enter key
}
return true;
}
十字キーの上部に表示されているバーを引き下げると、アシストモードは終了し、トラックパッドモードになります。
設定->ユーザー補助->Arrow KeyのトグルボタンをONにすると、setUiMode()を使用しなくてもアシストモードが使用できるようになります。(デフォルト設定はOFFになります)
上記設定をONにするとトラックパッドモードの下側にバーが表示されます。Trackpad modeでこのバーを引き上げるとアシストモードになり、アシストモードでこのバーを引き下げるとトラックパッドモードに戻ります。
※アシストモードは新規アプリケーション開発向けの機能になります。プリインアプリも含めた既存のアプリは基本的に十字キー/エンターキーによる操作に対応していない(画面操作できない)ためご注意ください。
専用コントローラーの切替キーを長押した時に実行されるキーロック機能の有効/無効を切り替えることができます。
キーロックを切り替えるには、com.epson.moverio.btcontrol.KeyLockをインポートしてください。
import com.epson.moverio.btcontrol.KeyLock;
KeyLockクラスのインスタンスを作成して、setKeyLock()メソッドの第一引数に下表のキーの定数を指定し、呼び出してください。
定数 | 説明 |
---|---|
KeyLock.KEY_LOCK_ENABLE | キーロック機能有効 |
KeyLock.KEY_LOCK_DISABLE | キーロック機能無効 |
※キーロック機能が有効になっている場合、全ての専用コントローラーの操作(キー操作、及びタッチ操作)が無効になります。キーロック機能を有効にする必要が無い場合は、速やかに無効にしてください。以下のコードはキーロック機能を有効にした10秒後に自動でキーロック機能を無効にしています。また専用コントローラーがスリープ状態に入る時にキーロックは自動で無効になります。
public class UIControlActivity extends Activity {
private KeyLock mKeyLock = null;
private Button mButton_keyLock = null;
@Override public final void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mKeyLock = new KeyLock (this);
mButton_keyLock = (Button) findViewById(R.id.button_keyLock);
mButton_keyLock.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View view) {
mKeyLock.setKeyLock(KeyLock.KEY_LOCK_ENABLE);
Handler handler = new Handler(getMainLooper());
handler.postDelayed(new Runnable(){
public void run() {
if (keyLock.getKeyLock() == KeyLock.KEY_LOCK_ENABLE) {
keyLock.setKeyLock(KeyLock.KEY_LOCK_DISABLE);
}
}
}, 10000);
}
});
}
:
:
}
BT-45CやBT-35EのUSB接続型Moverioは装着者の前⾯を撮影するカメラをヘッドセットに搭載しており、カメラの映像データの取得や静⽌画/動画の撮影などができます。カメラの映像データを使⽤することで、通常の撮影に加え、マーカー認識などの⽤途に応⽤することができます。
ここでは専用コントローラー を⽤いてBT-45C、BT-35E カメラ制御をする⽅法について説明します。
*なお本章での説明は、専用コントローラーのOSバージョン2.1.00の内容になります。
Android CameraAPI やMoverio Basic Function SDK を使⽤すると、専用コントローラー を⽤いてBT-45C/BT-35E に搭載されているカメラを制御することができます。Moverio Basic Function SDKによる制御⽅法についてはデベロッパーズガイドを参照してください。
ただし、Android CameraAPIとMoverio Basic Function SDKによるカメラ制御は両立することができません。Moverio Basic Function SDKによるカメラ制御を行った後は、Android CameraAPIからはカメラを検出することができなくなります。Moverio Basic Function SDKによるカメラ制御使用後にAndroid CameraAPIを使用する場合は、BT-45C/BT-35Eを挿抜してください。
Android CameraAPI はAndroid CameraAPI1 とAndroid CameraAPI2 があります。各API の使⽤⽅法は以下の情報を参照してください。
https://developer.android.com/training/camera
※CameraAPI1 は⾮推奨です
https://developer.android.com/training/camera2
https://medium.com/google-developers/detecting-camera-features-with-camera2-61675bb7d1bf
以下はAndroid CameraAPI2 を使⽤したカメラアプリのサンプルソースコードです。アプリをビルドし、BT-45C/BT-35E を接続してアプリを起動すると、BT-45C/BT-35Eカメラを使⽤することができます。アプリ実装の参考にしてください。
https://github.com/googlearchive/android-Camera2Basic
専用コントローラー を⽤いてBT-45C/BT-35Eに搭載されているカメラをAndroid CameraAPI2 により制御することできます。ただし、⼀部の設定はAndroid CameraAPI2 では変更できません。変更するためにはMoverio Basic Function SDKを使⽤してください。BT-45C/BT-35Eのカメラスペック、及び各API/SDK による設定の制御可否を”Android CameraAPI2/Moverio Basic Function SDKでのカメラ制御可能対応表”に記載しています。
Android CameraAPI2/Moverio Basic Function SDK でのカメラ制御可能対応表
Settings | BT-35E | BT-45C | Android CameraAPI | Moverio Basic Function SDK |
---|---|---|---|---|
解像度 | 640x480 | 640x480 | ||
1280x720 | 1280x720 | |||
1600x1200 | ||||
1920x1080 | 1920x1080 | |||
2560x1440 | ||||
2592x1944 | ||||
3264x2448 | ||||
露出補正モード | auto/manual | auto/manual | auto only | |
⼿動露出補正ステップ | -5 ~ +5 | -7 ~ +5 | ||
フォーカスモード | Off (Pan) | auto/manual | auto only | |
手動フォーカス | +50 ~ +3000[mm] | |||
シャープネス | 0 ~ +128 | |||
明るさ | -127 ~ +127 | 0 ~ +255 | ||
ゲイン | 0 ~ +255 | +1024 ~ +16383 | ||
ホワイトバランス | auto/cloudy_daylight(6000K)/ daylght(5500K)/fluorescent(4200K)/ incandescent(3200K)/twilight(3500K) |
auto/cloudy_daylight(6000K)/ daylight(5500K)/fluorescent(4200K)/ incandescent(3200K)/twilight(3500K) |
auto only | |
電⼒線周波数 | 50Hz/60Hz | 50Hz/60Hz/Disable | ||
インジケーターモード | auto | Auto/on/off |
専用コントローラーにはカメラ(バックカメラ)が搭載されており、通常Android CameraAPIは専用コントローラーバックカメラを使用するようになっています。それに対して専用コントローラーにはBT-45C/BT-35Eカメラ(以下、ヘッドセットカメラ)を優先して使用できるようにする機能があります(以下、ヘッドセットカメラ優先使用機能)。機能のソフトウェア仕様については” Android CameraAPI を使用したカメラ制御仕様”を参照してください。ただし、OSバージョン2.1.00よりヘッドセットカメラ優先使用機能を利用するか選択できるCamera Custom Stateの設定が追加されています。
ヘッドセットカメラ優先使用機能は、OSバージョン2.1.00では以下の条件において有効になります。
OSバージョン2.0.00でもヘッドセットカメラ優先使用機能は搭載されていますが、有効条件がバージョン2.1.00と一部異なりますのでご注意ください。”ヘッドセットカメラ優先使用機能 有効条件”で、OSバージョン2.0.00と2.1.00における条件をまとめています。なお、表における”-”は、通常のAndroid CameraAPIによる制御状態にあることを示します。
ヘッドセットカメラ優先使用機能 有効条件
OSバージョン | バージョン2.0.00 | バージョン2.1.00 | |||||
---|---|---|---|---|---|---|---|
専用コントローラーのモード | その他条件 | Camera Custom State は無し |
Camera Custom State がオン |
Camera Custom State がオフ |
|||
Hカメラ* 接続 |
Hカメラ 非接続 |
Hカメラ 接続 |
Hカメラ 非接続 |
Hカメラ 接続 |
Hカメラ 非接続 |
||
ミラーモード | - | - | - | - | - | ||
トラックパッドモード | - | - | - | - | |||
アシストモード |
*Hカメラ:ヘッドセットカメラ
Camera Custom Stateはデフォルトでオフとなっていますが、BT-45C、またはBT-35Eが初めて接続された時に自動でオンになります。設定を変更したい場合は、設定->ユーザー補助-> Camera Custom Stateのトグルボタンから設定を変更してください。二回目以降の接続時に自動でオンになることはありません。
ヘッドセットカメラ優先使用機能利用時はAndroid CameraAPIの一部仕様が通常のAndroid CameraAPIから異なっています。以下では、CameraAPI2を例に、ヘッドセットカメラ優先使用機能の制御仕様について記載します。
カメラ特徴を取得するコードスニペット
CameraManager manager = (CameraManager)getSystemService(CAMERA_SERVICE);
try {
for (String cameraId : manager.getCameraIdList()) {
CameraCharacteristics chars = manager.getCameraCharacteristics(cameraId);
// Do something with the characteristics
} catch (CameraAccessException e) {
e.printStackTrace();
}
“カメラ特徴を取得するコードスニペット”はcameraId を指定し、カメラ特徴を取得します。専用コントローラー はUSB ポートが⼆つあり(ボトムポート, サイドポート)、ヘッドセットカメラや市販のUSB カメラを接続することでカメラ制御を行うことができます。ただし、ヘッドセットカメラはボトムポートの接続のみサポートしています。
ヘッドセットカメラを専用コントローラーのボトムポートに接続しヘッドセットカメラ優先使用機能を利用している場合、”ヘッドセットカメラ接続時のcameraId”のようにcameraId が割り当てられます。
ヘッドセットカメラ接続時のcameraId
Camera API | 専用コントローラーバックカメラ | ボトムポート | サイドポート * |
---|---|---|---|
Camera API1 | 1 | 0 | 2 |
Camera API2 | /dev/video3 | 0 | /dev/video4 |
* USB カメラをサイドポートに接続している場合
CameraCharacteristics を参照することでカメラ特徴を取得し、所望するカメラを選択することができます。
カメラレンズ属性を取得するコードスニペット
CameraCharacteristics chars = manager.getCameraCharacteristics(cameraId);
Integer facing = chars.get(CameraCharacteristics.LENS_FACING);
“カメラレンズ属性を取得するコードスニペット”はcameraId を指定し、取得したカメラ特徴からカメラのレンズ属性を取得します。
ヘッドセットカメラを専用コントローラーに接続しヘッドセットカメラ優先使用機能を利用している場合、レンズ属性は”ヘッドセットカメラ接続時のレンズ属性”のようになります。
ヘッドセットカメラ接続時のレンズ属性
Camera API | 専用コントローラーバックカメラ | ボトムポート | サイドポート * |
---|---|---|---|
Camera API1 | FRONT | BACK | FRONT |
Camera API2 | EXTERNAL | BACK | EXTERNAL |
* USB カメラをサイドポートに接続している場合
“ヘッドセットカメラを使⽤するコードスニペット”は、ヘッドセットカメラを使⽤する場合のコードスニペットです。
ヘッドセットカメラを使⽤するコードスニペット
CameraManager manager = (CameraManager)getSystemService(CAMERA_SERVICE);
try {
for (String cameraId : manager.getCameraIdList()) {
CameraCharacteristics chars = manager.getCameraCharacteristics(cameraId);
Integer facing = chars.get(CameraCharacteristics.LENS_FACING);
if (facing != null && facing == CameraCharacteristics.LENS_FACING_BACK) {
// Open BT-45C/BT-35E camera (Please refer to upper link)
}
} catch (CameraAccessException e) {
e.printStackTrace();
}
cameraIdの情報は、カメラを使用する他に、カメラのフラッシュライトを使用するAPIであるsetTorchModeでも利用します。 ”ヘッドセットカメラ接続時のcameraId”は、setTorchModeについても適用されますので、ヘッドセットカメラを接続しヘッドセットカメラ優先使用機能を利用している状態で専用コントローラーの背面フラッシュライトを利用したい場合は、cameraIdとして"/dev/video3"を指定する必要があります。
専用コントローラー にUSB カメラを接続する場合、ヘッドセットカメラ優先使用機能の状態に関わらずcameraId は”USB カメラ接続時のcameraId”のように割り当てられます。
USB カメラ接続時のcameraId
Camera API | 専用コントローラーバックカメラ | ボトムポート * | サイドポート * |
---|---|---|---|
Camera API1 | 0 | 1 or 2 | 1 or 2 |
Camera API2 | 0 | /dev/video3 or 4 | /dev/video3 or 4 |
* USB ポートは先に接続されたUSB カメラに⼩さい番号が割り当てられます
レンズ属性は”USB カメラ接続時のレンズ属性”のようになります。
USB カメラ接続時のレンズ属性
Camera API | 専用コントローラーバックカメラ | ボトムポート | サイドポート |
---|---|---|---|
Camera API1 | BACK | FRONT | FRONT |
Camera API2 | BACK | EXTERNAL | EXTERNAL |
USB カメラを専用コントローラーで使⽤する場合は、”USB カメラ接続時のcameraId”、及び”USB カメラ接続時のレンズ属性”のパラメータを、”ヘッドセットカメラを使⽤するコードスニペット”のソースコード内で指定してください。
Android CameraAPI2サンプルソースコードでは、コントローラカメラの向きに応じた適切なカメラ表⽰を行うために、コントローラーの回転状態に応じた画⾯回転処理を⾏っています。
しかし、ヘッドセットカメラを⽤いて同処理を⾏う場合、コントローラーの回転状態とヘッドセットカメラの回転状態は連動しないため、表⽰に不整合が⽣じます。そのため、ヘッドセットカメラを使⽤する場合は”回転処理を含めないソースコード”のように回転処理を含めない(matrix.postRotate()をコメントアウトする)ことを推奨します。
回転処理を含めないソースコード
if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
float scale = Math.max((float) viewHeight / mPreviewSize.getHeight(), (float) viewWidth / mPreviewSize.getWidth());
matrix.postScale(scale, scale, centerX, centerY);
// matrix.postRotate(90 * (rotation - 2), centerX, centerY);
}
また、専用コントローラーに搭載されているトラックパッドモード、アシストモードにおいては、”画⾯回転に関するAndroidAPI”のように画⾯回転に関するAndroidAPI の返り値が固定値となっていますのでご注意ください。
画⾯回転に関するAndroidAPI
Android API | 返り値 |
---|---|
Display#getRotation() | ROTATION_0 |
Configuration#orientation | ORIENTATION_LANDSCAPE |
専用コントローラー に搭載されているバックカメラとヘッドセットカメラでは取り付け⾓度が異なります。詳しくは、下記表を参照ください。
CameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)の返り値
カメラ種別 | 返り値 |
---|---|
専用コントローラー バックカメラ | 90 |
BT-45C/BT-35Eカメラ | 0 |
そのため、ヘッドセットカメラを使⽤する場合に取り付け⾓90 度を前提としたプレビューを⾏うと、プレビュー画⾯が歪む場合があります。(例えば、正⽅形の撮影物が⻑⽅形として表⽰されます)取り付け⾓度に注意し、取得される画像の解像度に適切なプレビューを⾏うことを推奨します。
Android CameraAPI2サンプルソースコードでは取り付け⾓90度を前提としたプレビューを⾏っています。“適切なアスペクト⽐に変更するコード”のような修正をすることでヘッドセットカメラで適切なアスペクト⽐によるプレビューを⾏うことができます。
適切なアスペクト⽐に変更するコード
if(mSensorOrientation != 0){
// When the mounting angle is not 0-degree, it is dedicated controller back camera, so it should be the same as the original source code.
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
mTextureView.setAspectRatio(mPreviewSize.getWidth(),mPreviewSize.getHeight());
} else {
mTextureView.setAspectRatio(
mPreviewSize.getHeight(), mPreviewSize.getWidth());
}
} else {
// When the mounting angle is 0-degree, it is determined to be BT-35E camera (USB camera).
//BT-35E camera doesn’t rotate regardless of the device state, so the aspect ratio is always assumed to be horizontal preview.
mTextureView.setAspectRatio(
mPreviewSize.getWidth(), mPreviewSize.getHeight());
}
if(mSensorOrientation !=0) {
// For BT-35E camera, the following process is not necessary because BT-35E camera doesn’t rotate regardless of the device state.
if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY());
matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
float scale = Math.max((float) viewHeight / mPreviewSize.getHeight(), (float) viewWidth / mPreviewSize.getWidth());
matrix.postScale(scale, scale, centerX, centerY);
matrix.postRotate(90 * (rotation - 2), centerX, centerY);
} else if (Surface.ROTATION_180 == rotation) {
matrix.postRotate(180, centerX, centerY);
}
}
BT-45C、BT-40、BT-35EのUSB接続型Moverioは動き、向き、環境を検知するセンサー(以下、ヘッドセットセンサー)を搭載しています。これにより装着者の頭の動きを推測したり、周辺環境の明るさを推測したりできます。
ここでは専用コントローラーを⽤いてヘッドセットセンサーを制御する⽅法について説明します。
*なお本章での説明は、専用コントローラーのOSバージョン2.1.00の内容になります。
Android Sensor API やMoverio Basic Function SDK を使⽤すると、専用コントローラーを⽤いてヘッドセットセンサーを制御することができます。Moverio Basic Function SDKによる制御⽅法についてはデベロッパーズガイドを参照してください。
ただし、Android Sensor APIとMoverio Basic Function SDKによるセンサー制御は両立することができません。Moverio Basic Function SDKによるセンサー制御を行った後は、Android Sensor APIからヘッドセットセンサーを検出することができなくなります。Moverio Basic Function SDKによるセンサー制御使用後にAndroid Sensor APIを使用する場合は、ヘッドセットを挿抜してください。
Android Sensor APIの仕様については以下の情報を参照してください。
https://developer.android.com/reference/android/hardware/Sensor
専用コントローラーにはヘッドセットセンサーと同じ種類のセンサーが搭載されており、通常Android Sensor APIはコントローラー搭載のセンサーを使用するようになっています。それに対して専用コントローラーにはヘッドセットセンサーを優先して使用できる機能があります(以下、ヘッドセットセンサー優先使用機能)。またヘッドセットセンサー優先使用機能を利用するか選択できるSensor Custom Stateの設定もあります。
ヘッドセットセンサー優先機能は、OSバージョン2.1.00では以下の条件において有効になります。
”ヘッドセットセンサー優先使用機能 有効条件”で、ヘッドセットセンサー優先機能の挙動についてまとめています。なお、表における”-”は、通常のSensor Custom APIによる制御状態にあることを示します。
ヘッドセットセンサー優先使用機能 有効条件
専用コントローラーのモード | その他条件 | Sensor custom state がON |
Sensor custom state がOFF |
||
---|---|---|---|---|---|
Hセンサー* 接続 |
Hセンサー 非接続 |
Hセンサー 接続 |
Hセンサー 非接続 |
||
ミラーモード | - | - | - | - | |
トラックパッドモード | - | - | - | ||
アシストモード |
* Hセンサー:ヘッドセットセンサー
Sensor Custom Stateはデフォルトでオフとなっていますが、BT-45C、BT-40、またはBT-35Eが初めて接続された時のみ自動でオンになります。設定を変更したい場合は、設定->ユーザー補助-> Sensor Custom Stateのトグルボタンから設定を変更してください。二回目以降の接続時に自動でオンになることはありません。
ヘッドセットには様々な種類のセンサーが搭載されています。各ヘッドセットでサポートしている(データを利用できる)センサータイプを”ヘッドセットでサポートしているセンサータイプ”に記載しています。
ヘッドセットでサポートしているセンサータイプ
センサータイプ | センサーID (Hex) | 説明 | Android 標準センサータイプ | イベント周期 | サポートしているセンサータイプ | |
---|---|---|---|---|---|---|
BT-45C BT-40 |
BT-35E | |||||
TYPE_ACCELEROMETER | 0x00000001 | 重力を含むヘッドセットの加速度を3軸(x、y、z)の加速度[m/s2]で測定します。 | 100Hz | |||
TYPE_MAGNETIC_FIELD | 0x00000002 | ヘッドセット周囲の地磁気を3軸(x、y、z)の地磁気[µT]で測定します。 | 100Hz | |||
TYPE_GYROSCOPE | 0x00000004 | ヘッドセットの角速度を3軸(x、y、z)の角速度[rad/s]で測定します。 | 100Hz | |||
TYPE_LIGHT | 0x00000005 | ヘッドセット周囲の照度[lx]を測定します。 | 5Hz | |||
TYPE_GRAVITY | 0x00000009 | 重力を3軸(x、y、z)のヘッドセットの加速度[m/s^2]で測定します。 | 100Hz | |||
TYPE_LINEAR_ACCELERATION | 0x0000000a | 重力を除いた3軸(x、y、z)のヘッドセットの加速度[m/s^2]で測定します。 | 100Hz | |||
TYPE_ROTATION_VECTOR | 0x0000000b | ヘッドセットの向きを回転ベクトルで測定します。 | 100Hz | |||
TYPE_MAGNETIC_FIELD_UNCALIBRATED | 0x0000000e | ヘッドセット周囲の地磁気を未校正の3軸(x、y、z)で測定します。 | 100Hz | |||
TYPE_GAME_ROTATION_VECTOR | 0x0000000f | ヘッドセットの向きを、地磁気を使用しない回転ベクトルで測定します。 | 100Hz | |||
TYPE_GYROSCOPE_UNCALIBRATED | 0x00000010 | ヘッドセットの角速度を未校正の3軸(x、y、z)の角速度[rad/s]で測定します。 | 100Hz | |||
TYPE_STATIONARY_DETECT | 0x0000001d | ヘッドセットの静止を検知します。ヘッドセットが静止して1分経過すると”1”のデータが出力されます。 | イベント発生時 | |||
TYPE_MOTION_DETECT | 0x0000001e | ヘッドセットの動作を検知します。ヘッドセットの静止を検知した状態からヘッドセットを動かすと”1”のデータが出力されます。 | イベント発生時 | |||
TYPE_ACCELEROMETER_UNCALIBRATED | 0x00000023 | 重力を含むヘッドセットの加速度を未校正の3軸(x、y、z)の加速度[m/s2]で測定します。 | 100Hz | |||
TYPE_HEADSET_TAP | 0x00102001 | ヘッドセットのタップを検知します。ヘッドセットがダブルタップされると”1”のデータが出力されます。 | - | イベント発生時 |
ヘッドセットセンサー優先使用機能が有効状態で、アプリは任意のセンサーIDをAndroid Sensor APIに指定することでヘッドセットセンサーのデータを取得できます。
TYPE_HEADSET_TAP以外のセンサータイプはAndroid標準センサータイプになるため、Sensorクラスの定数をセンサーIDとして使用できます。TYPE_HEADSET_TAPはAndroid標準センサータイプでないため、”ヘッドセットでサポートしているセンサータイプ”のセンサーID(0x00102001)を直接指定してください。
ヘッドセットセンサーのセンサー軸は、Android標準のセンサーの座標系と同じです。ヘッドセットを装着した場合、X軸は右方向を指し、Y軸は上方向を指し、Z軸は装着者の方向を指します。
ヘッドセットのセンサー軸
なおヘッドセットセンサー優先使用機能が有効状態でヘッドセットセンサーを使用する場合、以下の点に御留意ください。
ヘッドセットセンサー優先使用機能が有効の状態では、センサーIDをAndroid Sensor APIに指定してもコントローラーからセンサーを使用することができません。ヘッドセットセンサー優先使用機能が有効の状態でコントローラーセンサーのデータを使用したい場合は、専用コントローラー用カスタムセンサータイプのセンサーIDをAndroid Sensor APIに指定してください。
カスタムセンサータイプのセンサーIDは、”専用コントローラー用カスタムセンサータイプ”に記載しています。なおこれらのセンサータイプはAndroid標準センサータイプでないため、表に記載しているセンサーIDをAndroid Sensor APIに直接指定してください。
専用コントローラー用カスタムセンサータイプ
カスタムセンサータイプ | センサーID (Hex) | 対応するAndroid標準センサータイプ |
---|---|---|
TYPE_CONTROLLER_ACCELEROMETER | 0x00100001 | TYPE_ACCELEROMETER |
TYPE_CONTROLLER_MAGNETIC_FIELD | 0x00100002 | TYPE_MAGNETIC_FIELD |
TYPE_CONTROLLER_GYROSCOPE | 0x00100004 | TYPE_GYROSCOPE |
TYPE_CONTROLLER_LIGHT | 0x00100005 | TYPE_LIGHT |
TYPE_CONTROLLER_GRAVITY | 0x00100009 | TYPE_GRAVITY |
TYPE_CONTROLLER_LINEAR_ACCELERATION | 0x0010000a | TYPE_LINEAR_ACCELERATION |
TYPE_CONTROLLER_ROTATION_VECTOR | 0x0010000b | TYPE_ROTATION_VECTOR |
TYPE_CONTROLLER_MAGNETIC_FIELD_UNCALIBRATED | 0x0010000e | TYPE_MAGNETIC_FIELD_UNCALIBRATED |
TYPE_CONTROLLER_GAME_ROTATION_VECTOR | 0x0010000f | TYPE_GAME_ROTATION_VECTOR |
TYPE_CONTROLLER_GYROSCOPE_UNCALIBRATED | 0x00100010 | TYPE_GYROSCOPE_UNCALIBRATED |
TYPE_CONTROLLER_STATIONARY_DETECT | 0x0010001d | TYPE_STATIONARY_DETECT |
TYPE_CONTROLLER_MOTION_DETECT | 0x0010001e | TYPE_MOTION_DETECT |
TYPE_CONTROLLER_ACCELEROMETER_UNCALIBRATED | 0x00100023 | TYPE_ACCELEROMETER_UNCALIBRATED |
センサー優先使用機能に対応しているセンサータイプは、通常センサーとトリガーセンサーに分類されます。トリガーセンサーは以下のセンサータイプが該当し、それ以外のセンサータイプは通常センサーに該当します。
Android Sensor APIを利用して、アプリから通常センサーとトリガーセンサーを使用する方法を以下で説明します。
通常センサーを使用するために以下のモジュールをインポートします。
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
センサーデータを取得するために、ActivityやServiceにSensorEventListenerをimplementsします。
public class SampleActivity extends Activity implements SensorEventListener {
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
@Override
public void onSensorChanged(SensorEvent event) {
}
}
システムからセンサーサービスを取得し、使用したいセンサーをSensorEventListenerに登録します。
なお同時に使用可能なセンサーに特に制限はありませんが、パフォーマンス低下を防ぐために、必要なセンサーのみを登録することを推奨します。
例:TYPE_ACCELEROMETER、TYPE_CONTROLLER_ACCELEROMETER(センサーID : 0x00100001)の登録
SensorManager sm = (SensorManager)getSystemService(SENSOR_SERVICE);
// Register accelerometer
Sensor acc = sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
sm.registerListene(this, acc, SensorManager.SENSOR_DELAY_NORMAL);
// Register controller accelerometer
Sensor cacc = sm.getDefaultSensor(0x00100001);
sm.registerListener(this, cacc, SensorManager.SENSOR_DELAY_NORMAL);
onSensorChanged()でセンサーデータを取得します。
例:TYPE_ACCELEROMETER、TYPE_CONTROLLER_ACCELEROMETER(センサーID : 0x00100001)のデータ取得
@Override
public void onSensorChanged(SensorEvent event) {
switch(event.sensor.getType()) {
case Sensor.TYPE_ACCELEROMETER:
// Get accelerometer event (Show log)
Log.d("Sample", "x="+event.values[0]+",y="+event.values[1]+",z="+event.values[2]);
break;
case 0x00100001:
// Get controller accelerometer event (Show log)
Log.d("Sample", "x="+event.values[0]+",y="+event.values[1]+",z="+event.values[2]);
break;
}
}
アプリでセンサーの使用を止めるときは登録を解除します。
例:TYPE_ACCELEROMETER、TYPE_CONTROLLER_ACCELEROMETERの登録を解除
// Unregister accelerometer
sm.unregisterListener(this, acc);
// Unregister controller accelerometer
sm.unregisterListener(this, cacc);
トリガーセンサーを使用するために以下のモジュールをインポートします。
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.hardware.TriggerEvent;
import android.hardware.TriggerEventListener;
センサーを使用するActivityやService内に任意のクラスを作成し、TriggerEventListenerをextendsします。
public class SampleActivity extends Activity {
public class TriggerListener extends TriggerEventListener {
@Override
public void onTrigger(TriggerEvent e) {
}
}
}
システムからセンサーサービスを取得し、使用したいセンサーをリクエストします。
例:TYPE_STATIONARY_DETECTのリクエスト
SensorManager sm = (SensorManager)getSystemService(SENSOR_SERVICE);
TriggerListener triggerEventListener = new TriggerListener();
// Register stationary detect
sta = mSensorManager.getDefaultSensor(Sensor.TYPE_STATIONARY_DETECT);
sm.requestTriggerSensor(triggerEventListener, sta);
onTriggerにセンサーイベントが発行されます。トリガーセンサーはイベントが発行されるとリクエストが解除されるため、再度センサーを使用したい場合はもう一度リクエストしてください。
例)TYPE_STATIONARY_DETECTのデータ取得
public class TriggerListener extends TriggerEventListener {
@Override
public void onTrigger(TriggerEvent e) {
switch(e.sensor.getType()) {
case Sensor.TYPE_STATIONARY_DETECT:
mTextView_staData.setText(String.format("%f", e.values[0]));
Log.d(“Sample”, “Get stationary event : ” + e.values[0]);
break;
}
}
}
アプリがTYPE_ACCELEROMETER、TYPE_CONTROLLER_ACCELEROMETER、TYPE_STATIONARY_DETECT、TYPE_HEADSET_TAPを使用するサンプルコードです。
Package ***;
import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
import android.view.WindowManager;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.TextView;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.TriggerEvent;
import android.hardware.TriggerEventListener;
public class SampleActivity extends Activity implements SensorEventListener {
private SensorEventListener mSensorEventListener = null;
private TriggerEventListener mTriggerEventListener = null;
private SensorManager mSensorManager = null;
// Defined custom sensor type
private static final int TYPE_CONTROLLER_ACCELEROMETER = 0x00100001;
private static final int TYPE_HEADSET_TAP = 0x00102001;
private Sensor mAccSensor = null;
private CheckBox mCheckBox_acc = null;
private TextView mTextView_accData = null;
private Sensor mCAccSensor = null;
private CheckBox mCheckBox_cacc = null;
private TextView mTextView_caccData = null;
private Sensor mTapSensor = null;
private CheckBox mCheckBox_tap = null;
private TextView mTextView_tapData = null;
private TextView mTextView_tapEventTime = null;
private Sensor mStaSensor = null;
private CheckBox mCheckBox_sta = null;
private TextView mTextView_staData = null;
private TextView mTextView_staEventTime = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sample);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
mSensorEventListener = this;
mTriggerEventListener = new TriggerListener();
mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
// TYPE_ACCELEROMETER
mTextView_accData = (TextView) this.findViewById(R.id.textView_accData);
mCheckBox_acc = (CheckBox) this.findViewById(R.id.checkBox_acc);
mCheckBox_acc.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
mAccSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mSensorManager.registerListener(mSensorEventListener, mAccSensor, SensorManager.SENSOR_DELAY_NORMAL);
} else {
mSensorManager.unregisterListener(mSensorEventListener, mAccSensor);
}
}
});
// TYPE_CONTROLLER_ACCELEROMETER
mTextView_caccData = (TextView) this.findViewById(R.id.textView_caccData);
mCheckBox_cacc = (CheckBox) this.findViewById(R.id.checkBox_cacc);
mCheckBox_cacc.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
mCAccSensor = mSensorManager.getDefaultSensor(TYPE_CONTROLLER_ACCELEROMETER);
mSensorManager.registerListener(mSensorEventListener, mCAccSensor, SensorManager.SENSOR_DELAY_NORMAL);
} else {
mSensorManager.unregisterListener(mSensorEventListener, mCAccSensor);
}
}
});
// TYPE_STATIONARY_DETECT
mTextView_staData = (TextView) this.findViewById(R.id.textView_staData);
mCheckBox_sta = (CheckBox) this.findViewById(R.id.checkBox_sta);
mTextView_staEventTime = (TextView) this.findViewById(R.id.textView_staTime);
mCheckBox_sta.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
mStaSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_STATIONARY_DETECT);
mSensorManager.requestTriggerSensor(mTriggerEventListener, mStaSensor);
}
}
});
// TYPE_HEADSET_TAP
mTextView_tapData = (TextView) this.findViewById(R.id.textView_tapData);
mCheckBox_tap = (CheckBox) this.findViewById(R.id.checkBox_tap);
mTextView_tapEventTime = (TextView) this.findViewById(R.id.textView_tapTime);
mCheckBox_tap.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
mTapSensor = mSensorManager.getDefaultSensor(TYPE_HEADSET_TAP);
mSensorManager.registerListener(mSensorEventListener, mTapSensor, SensorManager.SENSOR_DELAY_NORMAL);
} else {
mSensorManager.unregisterListener(mSensorEventListener, mTapSensor);
}
}
});
}
@Override
protected void onPause() {
super.onPause();
if (mSensorManager != null) {
mSensorManager.unregisterListener(this);
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
@Override
public void onSensorChanged(SensorEvent event) {
String data = "";
for(int i = 0; i < event.values.length; i++) {
data += String.format("%.3f", event.values[i]) + ", ";
}
switch(event.sensor.getType()) {
case Sensor.TYPE_ACCELEROMETER:
// Show sensor data
mTextView_accData.setText(data);
break;
case TYPE_CONTROLLER_ACCELEROMETER:
// Show sensor data and
mTextView_caccData.setText(data);
break;
case TYPE_HEADSET_TAP:
// Show sensor data and time of occurring event
mTextView_tapData.setText(data);
mTextView_tapEventTime.setText(getNowTime());
break;
}
}
public class TriggerListener extends TriggerEventListener {
@Override
public void onTrigger(TriggerEvent e) {
switch(e.sensor.getType()) {
case Sensor.TYPE_STATIONARY_DETECT:
// Show sensor data and time of occurring event
mTextView_staData.setText(String.format("%.3f", e.values[0]));
mTextView_staEventTime.setText(getNowTime());
if(mCheckBox_sta.isChecked()) mCheckBox_sta.setChecked(false);
break;
}
}
}
private String getNowTime() {
LocalDateTime nowDate = LocalDateTime.now();
DateTimeFormatter dtf1 = DateTimeFormatter.ofPattern("yyyy/MM/dd/HH:mm:ss.SSS");
String formatNowDate = dtf1.format(nowDate);
return formatNowDate;
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:isScrollContainer="false"
android:id="@+id/scrollView">
<LinearLayout
android:id="@+id/LinearLayout"
android:orientation="vertical"
android:layout_height="match_parent"
android:layout_width="match_parent">
<!-- TYPE_ACCELEROMETER -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<CheckBox
android:id="@+id/checkBox_acc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="2"
android:text="TYPE_ACCELEROMETER" />
<Space
android:layout_width="5dip"
android:layout_height="match_parent" />
<TextView
android:id="@+id/textView_accData"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="data" />
</LinearLayout>
<!-- TYPE_CONTROLLER_ACCELEROMETER -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<CheckBox
android:id="@+id/checkBox_cacc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="2"
android:text="TYPE_CONTROLLER_ACCELEROMETER" />
<Space
android:layout_width="5dip"
android:layout_height="match_parent" />
<TextView
android:id="@+id/textView_caccData"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="data" />
</LinearLayout>
<Space
android:layout_width="match_parent"
android:layout_height="10dip" />
<!-- TYPE_STATIONARY_DETECT -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<CheckBox
android:id="@+id/checkBox_sta"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="2"
android:text="TYPE_STATIONARY_DETECT" />
<Space
android:layout_width="5dip"
android:layout_height="match_parent" />
<TextView
android:id="@+id/textView_staData"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="data" />
<Space
android:layout_width="5dip"
android:layout_height="match_parent" />
<TextView
android:id="@+id/textView_staTime"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="2"
android:text="event time" />
</LinearLayout>
<Space
android:layout_width="match_parent"
android:layout_height="10dip" />
<!-- TYPE_HEADSET_TAP -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<CheckBox
android:id="@+id/checkBox_tap"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="2"
android:text="TYPE_HEADSET_TAP" />
<Space
android:layout_width="5dip"
android:layout_height="match_parent" />
<TextView
android:id="@+id/textView_tapData"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="data" />
<Space
android:layout_width="5dip"
android:layout_height="match_parent" />
<TextView
android:id="@+id/textView_tapTime"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="2"
android:text="event time" />
</LinearLayout>
<Space
android:layout_width="match_parent"
android:layout_height="10dip" />
</LinearLayout>
</ScrollView>
</LinearLayout>