Android端末に搭載されているセンサーの一覧を取得する


私事ですが、Nexus7(2013)の回転センサーが効かなくなってしまいました。

ほぼ、カーナビとして固定状態で使っているので、回転しなくても困らないのですが、そういえば、この端末にはどんなセンサーが搭載されているんだろう？と疑問に思ったので、久々にAndroidの開発環境を整えて、リストにして表示するようにしてみました。

# Android Studioのインストール
[Download Android Studio and SDK Tools](https://developer.android.com/studio/index.html)から、ダウンロードして、インストールします。
執筆時点で、2.3.2.0というバージョンをダウンロードすることができました。

# Android Studioの設定
Android Studio自体にこれといった設定はしていません。デフォルトのまま使えます。

## emulatorの作成
実機デバッグする際には必要ないのですが、実機を繋ぐのはソレはソレで面倒なので、AVD ManagerでNexus7相当のemulatorを設定します。

数年前と比較して、emulatorがサクサク動く用になっていて、驚きました。

# SensorListActivityの作成
## "New Project"から
「File」→「New」→「New Project」で新しいプロジェクトの諸設定をします。あまり、色々考えず、とりあえず、「Empty Activity」で。

このEmpty Activityをちょっと書き換えます。

```java
public class SensorListActivity extends AppCompatActivity
```

AppCompatActivityを継承しているようになっているけれど、これをListActivityの継承に変更します。

```java
public class SensorListActivity extends ListActivity
```

それに合わせて、リソースファイルである、activity_sensor_list.xmlもListViewとListViewに何も表示されない時に代替の文字列を表示するTextViewを設定します。これは、ListActivityを使う時にはありがちな設定で、ちょっとしたチュートリアルにも必ず出てくると思います。

```
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="jp.gr.java_conf.fukuit.sensoractivity.SensorListActivity">

    <ListView android:id="@android:id/list"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:scrollbars="vertical"
        android:scrollbarStyle="outsideInset"
        android:smoothScrollbar="true" />
    <TextView android:id="@android:id/empty" android:text="@string/empty_text"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" />

</android.support.constraint.ConstraintLayout>
```

## Sensor項目を取得する
Sensor項目を取得するための変数を用意します。Sensor項目を格納するための、SensorItemというクラスを定義して、取得できそうなSensorの型ごとにインスタンスを生成し、ArrayListにそれを格納します。

```java
import java.util.ArrayList;

private ArrayList<SensorItem> sensorList;
private void initSensorList(){
    sensorList = new ArrayList<SensorItem>();
    sensorList.add( new SensorItem(Sensor.TYPE_ACCELEROMETER, "ACCELEROMETER"));
    sensorList.add( new SensorItem(Sensor.TYPE_AMBIENT_TEMPERATURE,"AMBIENT_TEMPERATURE"));
    sensorList.add( new SensorItem(Sensor.TYPE_DEVICE_PRIVATE_BASE,"DEVICE_PRIVATE_BASE"));
    sensorList.add( new SensorItem(Sensor.TYPE_GAME_ROTATION_VECTOR,"GAME_ROTATION_VECTOR"));
    sensorList.add( new SensorItem(Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR,"GEOMAGNETIC_ROTATION_VECTOR"));
    sensorList.add( new SensorItem(Sensor.TYPE_GRAVITY, "GRAVITY"));
    sensorList.add( new SensorItem(Sensor.TYPE_GYROSCOPE, "GYROSCOPE"));
    sensorList.add( new SensorItem(Sensor.TYPE_GYROSCOPE_UNCALIBRATED, "GYROSCOPE_UNCALIBRATED"));
    sensorList.add( new SensorItem(Sensor.TYPE_HEART_BEAT, "HEART_BEAT"));
    sensorList.add( new SensorItem(Sensor.TYPE_HEART_RATE, "HEART_RATE"));
    sensorList.add( new SensorItem(Sensor.TYPE_LIGHT, "LIGHT"));
    sensorList.add( new SensorItem(Sensor.TYPE_LINEAR_ACCELERATION, "LINEAR_ACCELERATION"));
    sensorList.add( new SensorItem(Sensor.TYPE_MAGNETIC_FIELD, "MAGNETIC_FIELD"));
    sensorList.add( new SensorItem(Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED, "MAGNETIC_FIELD_UNCALIBRATED"));
    sensorList.add( new SensorItem(Sensor.TYPE_MOTION_DETECT, "MOTION_DETECT"));
    sensorList.add( new SensorItem(Sensor.TYPE_POSE_6DOF, "POSE_6DOF"));
    sensorList.add( new SensorItem(Sensor.TYPE_PRESSURE, "PRESSURE"));
    sensorList.add( new SensorItem(Sensor.TYPE_PROXIMITY, "PROXIMITY"));
    sensorList.add( new SensorItem(Sensor.TYPE_RELATIVE_HUMIDITY, "RELATIVE_HUMIDITY"));
    sensorList.add( new SensorItem(Sensor.TYPE_ROTATION_VECTOR, "ROTATION_VECTOR"));
    sensorList.add( new SensorItem(Sensor.TYPE_SIGNIFICANT_MOTION, "SIGNIFICANT_MOTION"));
    sensorList.add( new SensorItem(Sensor.TYPE_STEP_COUNTER, "STEP_COUNTER"));
    sensorList.add( new SensorItem(Sensor.TYPE_STEP_DETECTOR, "STEP_DETECTOR"));
}
```

Sensor.TYPE_XXっていうのが、[Sensor](https://developer.android.com/reference/android/hardware/Sensor.html)のページを見るとConstantsの一覧に書いてあって、「Class representing a sensor. Use [getSensorList(int)](https://developer.android.com/reference/android/hardware/SensorManager.html#getSensorList(int)) to get the list of available Sensors.」とされているので、まあ、やりたいことのほとんどはこの辺りのドキュメントに書いてある感じですね。

このSensorManagerから取得するSensor.TYPE_XXXに対応する名前の文字列を保持することを目的とした配列というかArrayListになります。

このために使うSensorManager型の変数であるsensorManagerを定義しておきます。sensorManagerはnewするのではなく、getSystemService()でインスタンスを取得するようになっています。

```java
import android.hardware.Sensor;
import android.hardware.SensorManager;

private SensorManager sensorManager; 
sensorManager = (SensorManager)gtSystemService(SENSOR_SERVICE);
```

## List表示のための準備
List表示のためにArrayAdapter型の配列を接続します。そのために、まずArrayAdapter型の変数を宣言します。
List表示するのは、ArrayAdapterは文字列型の配列として宣言します。

```java
private ArrayAdapter<String> adapter;
```

## onCreate()の作成
onCreate() methodを次のように修正します。

```java

@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.activity_sensor_list);
	sensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
	adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1);
	setListAdapter(adapter);
	initSensorList();
	listSensors();
}
```

先に定義したsensorManagerとadapterを初期化します。```setListAdapter(adapter);``` することで、adapterとして定義されるListがListViewに表示されるようになります。

```initSensorList();``` で、sensorManagerから取得するセンサーのArrayListを作成し、最後の```listSensors();``` で、センサーの一覧を取得して、adapterにセンサーのリストを登録します。

## listSensors()メソッドの作成

ListSensors()はprivateなmethodです。単に、先に定義したsensorListのArrayListから一つずつSensorItemを取り出して、それをsensorManagerから取得します。

こうして取得したSensorItemをtoString()してできた文字列を、adapterに追加することで、リストに表示されるアイテムの一覧が完成します。


```java
private void listSensors(){
	for(int i=0; i < sensorList.size(); i++) {
    	SensorItem si = sensorList.get(i);
    	Sensor sensor = sensorManager.getDefaultSensor(si.getSensor());
    	if (sensor != null) {
    		si.setStatus(true);
    	} else {
    		si.setStatus(false);
    	}
    	adapter.add(si.toString());
    	(sensorList.get(i)).setStatus(si.getStatus());
    }
}
```

# 実行結果

以上のように、sensorListとsensorListNameでAndroidで利用可能なsensorの一覧を予め作成しておいて、ソレらがAndroid機で実装されているかどうかをgetDefaultSensor()で取得できるかどうか？の結果をArrayAdapterに蓄積し、そのArrayAdapterをListActivityに接続すると、以下のスクリーンショットのように、一覧が表示されるようになります。

![Screenshot_1496445758.png](https://qiita-image-store.s3.amazonaws.com/0/15553/9841afc6-238b-0dcd-7084-7c1f14a81609.png "Screenshot_1496445758.png")

ListActivityに表示されるlistItemはそれぞれclickableなので、これをクリックしたら、取得したsensorの値を表示するようにするのは、そんなに大変な拡張ではないはずなので、次はその辺りを目指してみようと思います。

とりあえず、Toastを使って、sensor.toString()の値を返す例は、以下に書いておきます。

```java
@Override
protected void onListItemClick(ListView lv, View v, int position, long id) {
    SensorItem si = sensorList.get(position);
    Sensor sensor = sensorManager.getDefaultSensor(si.getSensor());
	
	StringBuilder sb = new StringBuilder();
	if (sensor != null) {
		sb.append(sensor.toString());
	} else {
		sb.append(getString(R.string.empty_text));
	}
	Toast.makeText(this, sb.toString(), Toast.LENGTH_LONG).show();
}
```

## sensorの値の取得
sensorの値を取得するには、値を取得するためのリスナーを作成して登録しておく必要があります。

これは、sensorの種類ごとに取得できる値も異なるので、sensorの種類の数だけ場合分けをしたmethodを書く必要があるのかもしれません。
[SensorManager](https://developer.android.com/reference/android/hardware/SensorManager.html)のページにregisterListener()が複数定義されていることからも、なんとなく想像がつきます。

# 参考文献
- [SensorManager](https://developer.android.com/reference/android/hardware/SensorManager.html)
- [Sensor](https://developer.android.com/reference/android/hardware/Sensor.html)
- [ListActivity](https://developer.android.com/reference/android/app/ListActivity.html)

# 本日のコード
- [SensorListActivity.java](https://github.com/fukuit/SensorActivity/blob/master/app/src/main/java/jp/gr/java_conf/fukuit/sensoractivity/SensorListActivity.java)
- [SensorItem.java](https://github.com/fukuit/SensorActivity/blob/master/app/src/main/java/jp/gr/java_conf/fukuit/sensoractivity/SensorItem.java)
- [activity_sensor_list.xml](https://github.com/fukuit/SensorActivity/blob/master/app/src/main/res/layout/activity_sensor_list.xml)
- [strings.xml](https://github.com/fukuit/SensorActivity/blob/master/app/src/main/res/values/strings.xml)