前回、Androidをペンタブレットにする(1/3) を投稿しましたが、タッチパネルだけに飽き足らず、Androidスマホが持っているいろんな機能を取り込んでみました。
そのうち、以下の機能の実装を備忘録としておきます。スニペット的に残しておきましたので、コピペで使ってください。
• Androidの地磁気センサー、ジャイロスコープ、加速度センサーを検出して、Windowsに通知する。
• AndroidのGPS情報を取得し、Windowsに通知する。
• AndroidでQRコードをスキャンし、Windowsに通知する。
• Androidで音声認識して、Windowsに通知する。
• AndroidのクリップボードにWindowsからコピーやペーストする。
地磁気センサー、ジャイロスコープ、加速度センサー
〇利用するクラス宣言
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
〇変数定義
SensorManager sensorManager;
boolean isGyroscope = false;
boolean isAccelerometer = false;
boolean isMagnetic = false;
〇onCreateにて
sensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
センサー機能を有効、無効にします。
isMagnetic等は、アクティビティがアクティブでない場合に一時停止し、アクティブになったら再度開始にするためのものです。
private void setSensorEnable(int type, boolean enable){
if( type == Sensor.TYPE_MAGNETIC_FIELD ) {
isMagnetic = enable;
}else if( type == Sensor.TYPE_GYROSCOPE){
isGyroscope = enable;
}else if( type == Sensor.TYPE_ACCELEROMETER ){
isAccelerometer = enable;
}
if (enable)
sensorStart(type);
else
sensorStop(type);
}
アクティビティがアクティブでないときは、センサー検知を一時停止にします。
@Override
protected void onResume(){
super.onResume();
if(isGyroscope)
sensorStart(Sensor.TYPE_GYROSCOPE);
if( isAccelerometer )
sensorStart(Sensor.TYPE_ACCELEROMETER);
if( isMagnetic )
sensorStart(Sensor.TYPE_MAGNETIC_FIELD);
}
@Override
protected void onPause(){
super.onPause();
sensorStop(Sensor.TYPE_GYROSCOPE);
sensorStop(Sensor.TYPE_ACCELEROMETER);
sensorStop(Sensor.TYPE_MAGNETIC_FIELD);
}
以下が、センサー検知イベントの開始です。
registerListenerで登録したクラスにイベントが通知されます。SensorEventListenerクラスを実装する必要があります。イベントの頻度は、SENSOR_DELAY_NORMAL以外にもいくつか種類があります。
private void sensorStart(int type){
if( sensorManager == null )
return;
if( type == Sensor.TYPE_MAGNETIC_FIELD ) {
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
if (sensor != null) {
sensorManager.registerListener(sensorListnerMagnetic, sensor, SensorManager.SENSOR_DELAY_NORMAL);
}
}else if( type == Sensor.TYPE_GYROSCOPE ){
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
if (sensor != null) {
sensorManager.registerListener( sensorListnerGyro, sensor, SensorManager.SENSOR_DELAY_NORMAL);
}
}else if( type == Sensor.TYPE_ACCELEROMETER){
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
if (sensor != null) {
sensorManager.registerListener( sensorListnerAccel, sensor, SensorManager.SENSOR_DELAY_NORMAL);
}
}
}
以下が、センサー検知イベントの停止です。
private void sensorStop(int type){
if( sensorManager == null )
return;
if( type == Sensor.TYPE_MAGNETIC_FIELD ) {
sensorManager.unregisterListener(sensorListnerMagnetic);
}else if( type == Sensor.TYPE_GYROSCOPE ){
sensorManager.unregisterListener(sensorListnerGyro);
}else if( type == Sensor.TYPE_ACCELEROMETER){
sensorManager.unregisterListener(sensorListnerAccel);
}
}
以降、センサー検知イベントの受信処理のためのクラス実装です。地磁気センサー、ジャイロスコープ、加速度センサーとありますが、同じフォーマットです。
private SensorEventListener sensorListnerMagnetic = new SensorEventListener(){
@Override
public void onSensorChanged(SensorEvent sensorEvent) {
switch( sensorEvent.sensor.getType() ){
case Sensor.TYPE_MAGNETIC_FIELD:{
// BLEセントラルにイベントを転送
int unit = Float.SIZE / Byte.SIZE;
byte[] send_buffer = new byte[1 + 3 * unit];
send_buffer[0] = Const.RSP_MAGNETIC;
setFloatBytes(send_buffer, 1, sensorEvent.values[0]);
setFloatBytes(send_buffer, 1 + unit, sensorEvent.values[1]);
setFloatBytes(send_buffer, 1 + 2 * unit, sensorEvent.values[2]);
sendBuffer(send_buffer, send_buffer.length); break;
}
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int i) {
// do nothieng
}
};
private SensorEventListener sensorListnerGyro = new SensorEventListener(){
@Override
public void onSensorChanged(SensorEvent sensorEvent) {
switch( sensorEvent.sensor.getType() ){
case Sensor.TYPE_GYROSCOPE:{
// BLEセントラルにイベントを転送
break;
}
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int i) {
// do nothieng
}
};
private SensorEventListener sensorListnerAccel = new SensorEventListener(){
@Override
public void onSensorChanged(SensorEvent sensorEvent) {
switch( sensorEvent.sensor.getType() ){
case Sensor.TYPE_ACCELEROMETER:{
// BLEセントラルにイベントを転送
break;
}
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int i) {
// do nothieng
}
};
#GPS情報
appのbuild.gradleのdependenciesに以下を追加します。
implementation 'com.google.android.gms:play-services-location:17.0.0'
AndroidManifest.xmlに以下を追加します。
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
〇利用するクラス宣言
import android.location.Location;
import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
〇変数宣言
FusedLocationProviderClient fusedLocationClient;
〇onCreateにて
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
LocationRequest locationRequest = new LocationRequest();
locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
GPS情報取得には、ユーザに対して許可を得る必要があります。許可を得ていないのであれば、許可を要求し、すでに得ているのであれば、GPS情報を取得します。
private void getLocation() {
if (fusedLocationClient == null)
return;
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, REQUEST_PERMISSION_LOCATION);
}else {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
fusedLocationClient.getLastLocation().addOnCompleteListener(this, new OnCompleteListener<Location>() {
@Override
public void onComplete(@NonNull Task<Location> task) {
Location location = task.getResult();
sendLocation(location.getLatitude(), location.getLongitude());
}
});
}
}
}
GPS情報取得の許可をユーザから得ると、以下のActivityの関数が呼ばれます。許可を得たのち、GPSを取得します。
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults){
if( requestCode == REQUEST_PERMISSION_LOCATION )
if (grantResults[0] == PackageManager.PERMISSION_GRANTED)
getLocation();
}
QRコードスキャン
こちらを使わせていただきました。
journeyapps/zxing-android-embedded
https://github.com/journeyapps/zxing-android-embedded
appのbuild.gradleのdependenciesに以下を追加します。
implementation 'com.journeyapps:zxing-android-embedded:3.2.0@aar'
implementation 'com.google.zxing:core:3.2.1'
〇利用するクラス宣言
import com.google.zxing.integration.android.IntentIntegrator;
import com.google.zxing.integration.android.IntentResult;
あとは、以下を呼ぶだけです。カメラが起動し、QRコードをスキャンします。
private void startQrScan(){
new IntentIntegrator(this).initiateScan();
}
スキャンが完了すると、以下のActivityの関数が呼ばれます。result.getContents()にスキャンした文字列があります。
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
IntentResult result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
if(result != null) {
if(result.getContents() != null)
sendQrcode(result.getContents());
return;
}
super.onActivityResult(requestCode, resultCode, data);
// ・・・
音声認識
AndroidManifest.xmlに以下を追加します。
<uses-permission android:name="android.permission.INTERNET" />
〇利用するクラス宣言
import android.speech.RecognizerIntent;
あとは、以下を呼ぶだけです。
private void doRecognize(){
Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.JAPAN.toString() );
intent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 100);
intent.putExtra(RecognizerIntent.EXTRA_PROMPT, "音声を入力");
startActivityForResult(intent, REQUEST_RECOGNITION);
}
音声認識が完了すると、Activityの以下の関数が呼ばれます。candidates.get(0)に一番可能性の高い文章が入っています。
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
//・・・
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == REQUEST_RECOGNITION && resultCode == RESULT_OK) {
ArrayList<String> candidates = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
if( candidates.size() > 0 )
sendText(Const.TYPE_TEXT_RECOGNITION, candidates.get(0) );
}
#クリップボード
〇利用するクラス宣言
import android.content.ClipData;
import android.content.ClipboardManager;
〇変数宣言
ClipboardManager clipboardManager;
〇onCreateにて
clipboardManager = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE);
クリップボードから文字列を取得し、BLEセントラルに転送します。クリップボード操作には直接は関係しませんが、クリップボードから取得したことをToastでもわかるようにしました。
private void sendCopy(){
if( clipboardManager == null )
return;
ClipData cd = clipboardManager.getPrimaryClip();
if(cd != null){
ClipData.Item item = cd.getItemAt(0);
Toast.makeText(this, item.getText().toString(), Toast.LENGTH_LONG).show();
sendText(Const.TYPE_TEXT_COPY, item.getText().toString());
}
}
BLEセントラルから受け取った文字列をクリップボードにコピーします。
クリップボード操作には直接は関係しませんが、クリップボードに受け取ったことをToast で表示したいのですが、UIスレッドでない場合に備えて、UIスレッドにメッセージ送信しています。
private void setPaste(String text){
if( clipboardManager == null )
return;
clipboardManager.setPrimaryClip(ClipData.newPlainText("", text));
handler.sendUIMessage(UIHandler.MSG_ID_TEXT, UIMSG_TOAST, "Receive Clipboard");
}
以上