はじめに
BLEは省電力であることが最大の特徴ではありますが、使い方によっては電池の減りが早かったり以外なな落とし穴に陥る場合があります。
今回は消費電力を下げるためのアプローチについて掲載いたします。
Android Auto
API Simulators
|
車載機側プラットフォームのシュミレータアプリ。
このアプリ上で、Android Autoアプリの動作が確認できる。
現在はオーディオ系とNotification系の2種類が存在する。
|
Android Auto App
|
Android Auto向けに拡張したAndroidアプリ。
|
My Audio App
|
スマートフォン上で動く MusicPlayer アプリケーション
|
Android Auto App
|
“Auto enable car”上で車載器用に設計されたUIを表示し、操作を“My Audio App”へ中継するアプリケーション
|
Auto enable car
|
Android Auto 機能対応車載機。”Android Auto App” を表示する。
|
Android Auto
API Simulators
|
車載機側プラットフォームのシュミレータアプリ。
このアプリ上で、Android Autoアプリの動作が確認できる。
現在はオーディオ系とNotification系の2種類が存在する。
|
Android Auto App
|
Android Auto向けに拡張したAndroidアプリ。
|
My Audio App
|
スマートフォン上で動く MusicPlayer アプリケーション
|
Android Auto App
|
“Auto enable car”上で車載器用に設計されたUIを表示し、操作を“My Audio App”へ中継するアプリケーション
|
Auto enable car
|
Android Auto 機能対応車載機。”Android Auto App” を表示する。
|
<meta-data
android:name="com.google.android.gms.car.application"
android:resource="@xml/automotive_app_desc" />
別途、専用リソースファイル\res\xml\automotive_app_desc.xmlを用意します。
<automotiveapp/>
<uses name="media"/>
</automotiveapp/>
public class AndroidAutoMediaService extends MediaBrowserService implements
OnPreparedListener, OnCompletionListener, OnErrorListener{
private MediaSession mMediaSession; // MediaSessionクラス
private MediaPlayer mMediaPlayer; // MediaPlayerクラス
private List<MediaSession.QueueItem> mPlayingQueue; // 再生キューリスト
private int mCurrentQueueIndex; // 再生キューのインデックス
private static final String MEDIA_ID_ROOT = "__ROOT__";
@Override
public void onCreate() {
super.onCreate();
// 再生リストのオブジェクトを作る
mPlayingQueue = new ArrayList<MediaSession.QueueItem>();
// MediaSessionを生成
mMediaSession = new MediaSession(this, "MyMediaSession");
setSessionToken(mMediaSession.getSessionToken());
// コールバックを設定
mMediaSession.setCallback(new MyMediaSessionCallback());
// 再生キューの位置を初期化
mCurrentQueueIndex = 0;
}
@Override
public BrowserRoot onGetRoot(String clientPackageName, int clientUid,
Bundle rootHints) {
return new BrowserRoot(MEDIA_ID_ROOT, null);
}
@Override
public void onLoadChildren(String parentId, Result <List<MediaItem>> result) {
List<MediaBrowser.MediaItem> mediaItems = new ArrayList<MediaBrowser.MediaItem>();
// 再生データ情報の設定
MediaDescription.Builder mdb1 = new MediaDescription.Builder();
// 再生データ1
final Bundle mediaBundle1 = new Bundle();
mediaBundle1.putString("Path", "mnt/sdcard/M01.mp3"); // データパス
mediaBundle1.putInt("Index", 0); // 曲のインデックス
mdb1.setMediaId("MediaID01"); // メディアID
mdb1.setTitle("Title01"); // タイトル
mdb1.setSubtitle("SubTitle01"); // サブタイトル
mdb1.setExtras(mediaBundle1);
// 再生データ2
MediaDescription.Builder mdb2 = new MediaDescription.Builder();
final Bundle mediaBundle2 = new Bundle();
mediaBundle2.putString("Path", "mnt/sdcard/M02.mp3"); // データパス
mediaBundle2.putInt("Index", 1); // 曲のインデックス
mdb2.setMediaId("MediaID02"); // メディアID
mdb2.setTitle("Title02"); // タイトル
mdb2.setSubtitle("SubTitle02"); // サブタイトル
mdb2.setExtras(mediaBundle2);
mediaItems.add(new MediaBrowser.MediaItem(mdb1.build(),
MediaBrowser.MediaItem.FLAG_PLAYABLE));
mediaItems.add(new MediaBrowser.MediaItem(mdb2.build(),
MediaBrowser.MediaItem.FLAG_PLAYABLE));
// sendResult()をコールする前にdetach()のコールが必要
result.detach();
result.sendResult(mediaItems);
// 再生キューをセッションに設定
mPlayingQueue.add(new MediaSession.QueueItem(mdb1.build(), 0));
mPlayingQueue.add(new MediaSession.QueueItem(mdb2.build(), 1));
mMediaSession.setQueue(mPlayingQueue);
}
private void playMusic() {
// プレイヤー生成と準備(生成済みの場合はリセット)
if (mMediaPlayer == null) {
mMediaPlayer = new MediaPlayer();
mMediaPlayer.setOnPreparedListener(this);
mMediaPlayer.setOnCompletionListener(this);
mMediaPlayer.setOnErrorListener(this);
} else {
mMediaPlayer.reset();
}
// 現在のキュー位置を元に再生データのパスを取得する
MediaSession.QueueItem queueItem = mPlayingQueue.get(mCurrentQueueIndex);
String path = queueItem.getDescription().getExtras().getString("Path");
// MediaPlayerのデータ設定と準備(非同期)
try {
mMediaPlayer.setDataSource(path);
mMediaPlayer.prepareAsync();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onPrepared(MediaPlayer mp) {
// 再生準備完了通知を受け、再生を行う
if(mMediaPlayer != null) {
mMediaPlayer.start();
}
}
private final class MyMediaSessionCallback extends MediaSession.Callback {
@Override
public void onPlay() {
playMusic();
}
// リスト選択時にコールされる
@Override
public void onPlayFromMediaId(String mediaId, Bundle extras) {
// MediaIDを元に曲のインデックス番号を検索し、設定する。
for(int i = 0; i < mPlayingQueue.size(); i++) {
MediaSession.QueueItem queueItem = mPlayingQueue.get(i);
MediaDescription md = queueItem.getDescription();
if(mediaId.equals(md.getMediaId())) {
mCurrentQueueIndex = md.getExtras().getInt("Index");
}
}
playMusic();
}
@Override
public void onSkipToNext() {
// 再生キューの位置を次へ
mCurrentQueueIndex++;
if (mCurrentQueueIndex >= mPlayingQueue.size()) {
mCurrentQueueIndex = 0;
}
playMusic();
}
@Override
public void onSkipToPrevious() {
// 再生キューの位置を前へ
mCurrentQueueIndex--;
if (mCurrentQueueIndex < 0) {
mCurrentQueueIndex = 0;
}
playMusic();
}
@Override
public void onDestroy() {
super.onDestroy();
// MediaSessionの終了処理
mMediaSession.setCallback(null);
mMediaSession.release();
// MediaPlayerの終了処理
if(mMediaPlayer != null) {
mMediaPlayer.setOnPreparedListener(null);
mMediaPlayer.setOnCompletionListener(null);
mMediaPlayer.setOnErrorListener(null);
mMediaPlayer.release();
mMediaPlayer = null;
}
}
今回は割愛しましたが、MediaSessionクラスを正確に動作させるためには、setPlaybackState()にて再生状態や停止状態などを操作に合わせて設定する必要があります。<meta-data
android:name="com.google.android.gms.car.application"
android:resource="@xml/automotive_app_desc" />
別途、専用リソースファイル\res\xml\automotive_app_desc.xmlを用意します。
<automotiveapp/>
<uses name="media"/>
</automotiveapp/>
public class AndroidAutoMediaService extends MediaBrowserService implements
OnPreparedListener, OnCompletionListener, OnErrorListener{
private MediaSession mMediaSession; // MediaSessionクラス
private MediaPlayer mMediaPlayer; // MediaPlayerクラス
private List<MediaSession.QueueItem> mPlayingQueue; // 再生キューリスト
private int mCurrentQueueIndex; // 再生キューのインデックス
private static final String MEDIA_ID_ROOT = "__ROOT__";
@Override
public void onCreate() {
super.onCreate();
// 再生リストのオブジェクトを作る
mPlayingQueue = new ArrayList<MediaSession.QueueItem>();
// MediaSessionを生成
mMediaSession = new MediaSession(this, "MyMediaSession");
setSessionToken(mMediaSession.getSessionToken());
// コールバックを設定
mMediaSession.setCallback(new MyMediaSessionCallback());
// 再生キューの位置を初期化
mCurrentQueueIndex = 0;
}
@Override
public BrowserRoot onGetRoot(String clientPackageName, int clientUid,
Bundle rootHints) {
return new BrowserRoot(MEDIA_ID_ROOT, null);
}
@Override
public void onLoadChildren(String parentId, Result <List<MediaItem>> result) {
List<MediaBrowser.MediaItem> mediaItems = new ArrayList<MediaBrowser.MediaItem>();
// 再生データ情報の設定
MediaDescription.Builder mdb1 = new MediaDescription.Builder();
// 再生データ1
final Bundle mediaBundle1 = new Bundle();
mediaBundle1.putString("Path", "mnt/sdcard/M01.mp3"); // データパス
mediaBundle1.putInt("Index", 0); // 曲のインデックス
mdb1.setMediaId("MediaID01"); // メディアID
mdb1.setTitle("Title01"); // タイトル
mdb1.setSubtitle("SubTitle01"); // サブタイトル
mdb1.setExtras(mediaBundle1);
// 再生データ2
MediaDescription.Builder mdb2 = new MediaDescription.Builder();
final Bundle mediaBundle2 = new Bundle();
mediaBundle2.putString("Path", "mnt/sdcard/M02.mp3"); // データパス
mediaBundle2.putInt("Index", 1); // 曲のインデックス
mdb2.setMediaId("MediaID02"); // メディアID
mdb2.setTitle("Title02"); // タイトル
mdb2.setSubtitle("SubTitle02"); // サブタイトル
mdb2.setExtras(mediaBundle2);
mediaItems.add(new MediaBrowser.MediaItem(mdb1.build(),
MediaBrowser.MediaItem.FLAG_PLAYABLE));
mediaItems.add(new MediaBrowser.MediaItem(mdb2.build(),
MediaBrowser.MediaItem.FLAG_PLAYABLE));
// sendResult()をコールする前にdetach()のコールが必要
result.detach();
result.sendResult(mediaItems);
// 再生キューをセッションに設定
mPlayingQueue.add(new MediaSession.QueueItem(mdb1.build(), 0));
mPlayingQueue.add(new MediaSession.QueueItem(mdb2.build(), 1));
mMediaSession.setQueue(mPlayingQueue);
}
private void playMusic() {
// プレイヤー生成と準備(生成済みの場合はリセット)
if (mMediaPlayer == null) {
mMediaPlayer = new MediaPlayer();
mMediaPlayer.setOnPreparedListener(this);
mMediaPlayer.setOnCompletionListener(this);
mMediaPlayer.setOnErrorListener(this);
} else {
mMediaPlayer.reset();
}
// 現在のキュー位置を元に再生データのパスを取得する
MediaSession.QueueItem queueItem = mPlayingQueue.get(mCurrentQueueIndex);
String path = queueItem.getDescription().getExtras().getString("Path");
// MediaPlayerのデータ設定と準備(非同期)
try {
mMediaPlayer.setDataSource(path);
mMediaPlayer.prepareAsync();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onPrepared(MediaPlayer mp) {
// 再生準備完了通知を受け、再生を行う
if(mMediaPlayer != null) {
mMediaPlayer.start();
}
}
private final class MyMediaSessionCallback extends MediaSession.Callback {
@Override
public void onPlay() {
playMusic();
}
// リスト選択時にコールされる
@Override
public void onPlayFromMediaId(String mediaId, Bundle extras) {
// MediaIDを元に曲のインデックス番号を検索し、設定する。
for(int i = 0; i < mPlayingQueue.size(); i++) {
MediaSession.QueueItem queueItem = mPlayingQueue.get(i);
MediaDescription md = queueItem.getDescription();
if(mediaId.equals(md.getMediaId())) {
mCurrentQueueIndex = md.getExtras().getInt("Index");
}
}
playMusic();
}
@Override
public void onSkipToNext() {
// 再生キューの位置を次へ
mCurrentQueueIndex++;
if (mCurrentQueueIndex >= mPlayingQueue.size()) {
mCurrentQueueIndex = 0;
}
playMusic();
}
@Override
public void onSkipToPrevious() {
// 再生キューの位置を前へ
mCurrentQueueIndex--;
if (mCurrentQueueIndex < 0) {
mCurrentQueueIndex = 0;
}
playMusic();
}
@Override
public void onDestroy() {
super.onDestroy();
// MediaSessionの終了処理
mMediaSession.setCallback(null);
mMediaSession.release();
// MediaPlayerの終了処理
if(mMediaPlayer != null) {
mMediaPlayer.setOnPreparedListener(null);
mMediaPlayer.setOnCompletionListener(null);
mMediaPlayer.setOnErrorListener(null);
mMediaPlayer.release();
mMediaPlayer = null;
}
}
今回は割愛しましたが、MediaSessionクラスを正確に動作させるためには、setPlaybackState()にて再生状態や停止状態などを操作に合わせて設定する必要があります。 <receiver android:name=".MyMessageReadReceiver" >
<intent-filter>
<action android:name="com.example.androidautonotificationtest.ACTION_MESSAGE_READ" />
</intent-filter>
</receiver>
<receiver android:name=".MyMessageReplyReceiver" >
<intent-filter>
<action android:name="com.example.androidautonotificationtest.ACTION_MESSAGE_REPLY" />
</intent-filter>
</receiver>
<meta-data
android:name="com.google.android.gms.car.application"
android:resource="@xml/automotive_app_desc" />
別途、専用リソースファイルを用意します。 <automotiveapp>
<uses name="notification"/>
</automotiveapp>
int TEST_ID = 1;
String REPLY_ID = "conversation_id";
String READ_ACTION =
"com.example.androidautonotificationtest.ACTION_MESSAGE_READ";
Intent readIntent = new Intent()
.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
.setAction(READ_ACTION)
.putExtra(REPLY_ID, TEST_ID);
PendingIntent readPendingIntent = PendingIntent.getBroadcast(getApplicationContext(),
TEST_ID,
readIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
String REPLY_ACTION =
"com.example.androidautonotificationtest.ACTION_MESSAGE_REPLY";
String EXTRA_VOICE_REPLY = "extra_voice_reply";
Intent replyIntent = new Intent()
.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
.setAction(REPLY_ACTION)
.putExtra(REPLY_ID, TEST_ID);
RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_VOICE_REPLY)
.setLabel(getApplicationContext().getString(R.string.app_name))
.build();
PendingIntent replyPendingIntent = PendingIntent.getBroadcast(getApplicationContext(),
TEST_ID,
replyIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
String TEST_NAME = "BRIL TARO";
String TEST_MESSAGE = "Hello Android Auto!";
UnreadConversation.Builder unreadConvBuilder =
new UnreadConversation.Builder(TEST_NAME)
.setReadPendingIntent(readPendingIntent)
.setReplyAction(replyPendingIntent, remoteInput);
unreadConvBuilder.addMessage(TEST_MESSAGE);
NotificationCompat.Builder notificationBuilder =
new NotificationCompat.Builder(getApplicationContext())
.setSmallIcon(R.drawable.ic_launcher)
.extend(new CarExtender()
.setUnreadConversation(unreadConvBuilder.build()));
NotificationManagerCompat mNotificationManager = NotificationManagerCompat.from(getApplicationContext());
mNotificationManager.notify(TEST_ID, notificationBuilder.build());
public class MyMessageReadReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
int conversationId = intent.getIntExtra(MainActivity.REPLY_ID, -1);
}
}
public class MyMessageReplyReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
int conversationId = intent.getIntExtra(MainActivity.REPLY_ID, -1);
CharSequence reply;
Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
if (remoteInput != null) {
reply = remoteInput.getCharSequence(MainActivity.EXTRA_VOICE_REPLY);
}
}
}
<receiver android:name=".MyMessageReadReceiver" >
<intent-filter>
<action android:name="com.example.androidautonotificationtest.ACTION_MESSAGE_READ" />
</intent-filter>
</receiver>
<receiver android:name=".MyMessageReplyReceiver" >
<intent-filter>
<action android:name="com.example.androidautonotificationtest.ACTION_MESSAGE_REPLY" />
</intent-filter>
</receiver>
<meta-data
android:name="com.google.android.gms.car.application"
android:resource="@xml/automotive_app_desc" />
別途、専用リソースファイルを用意します。 <automotiveapp>
<uses name="notification"/>
</automotiveapp>
int TEST_ID = 1;
String REPLY_ID = "conversation_id";
String READ_ACTION =
"com.example.androidautonotificationtest.ACTION_MESSAGE_READ";
Intent readIntent = new Intent()
.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
.setAction(READ_ACTION)
.putExtra(REPLY_ID, TEST_ID);
PendingIntent readPendingIntent = PendingIntent.getBroadcast(getApplicationContext(),
TEST_ID,
readIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
String REPLY_ACTION =
"com.example.androidautonotificationtest.ACTION_MESSAGE_REPLY";
String EXTRA_VOICE_REPLY = "extra_voice_reply";
Intent replyIntent = new Intent()
.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
.setAction(REPLY_ACTION)
.putExtra(REPLY_ID, TEST_ID);
RemoteInput remoteInput = new RemoteInput.Builder(EXTRA_VOICE_REPLY)
.setLabel(getApplicationContext().getString(R.string.app_name))
.build();
PendingIntent replyPendingIntent = PendingIntent.getBroadcast(getApplicationContext(),
TEST_ID,
replyIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
String TEST_NAME = "BRIL TARO";
String TEST_MESSAGE = "Hello Android Auto!";
UnreadConversation.Builder unreadConvBuilder =
new UnreadConversation.Builder(TEST_NAME)
.setReadPendingIntent(readPendingIntent)
.setReplyAction(replyPendingIntent, remoteInput);
unreadConvBuilder.addMessage(TEST_MESSAGE);
NotificationCompat.Builder notificationBuilder =
new NotificationCompat.Builder(getApplicationContext())
.setSmallIcon(R.drawable.ic_launcher)
.extend(new CarExtender()
.setUnreadConversation(unreadConvBuilder.build()));
NotificationManagerCompat mNotificationManager = NotificationManagerCompat.from(getApplicationContext());
mNotificationManager.notify(TEST_ID, notificationBuilder.build());
public class MyMessageReadReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
int conversationId = intent.getIntExtra(MainActivity.REPLY_ID, -1);
}
}
public class MyMessageReplyReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
int conversationId = intent.getIntExtra(MainActivity.REPLY_ID, -1);
CharSequence reply;
Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
if (remoteInput != null) {
reply = remoteInput.getCharSequence(MainActivity.EXTRA_VOICE_REPLY);
}
}
}
<service android:exported="true" android:name=".AndroidAutoMediaService">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService">
</action>
</intent-filter>
</service>
<meta-data
android:name="com.google.android.gms.car.application"
android:resource="@xml/automotive_app_desc" />
<automotiveapp>
<uses name="media"/>
</automotiveapp>
public class AndroidAutoMediaService extends MediaBrowserService{
@Override
public BrowserRoot onGetRoot(String clientPackageName, int clientUid,
Bundle rootHints) {
return null;
}
@Override
public void onLoadChildren(String parentId, Result <List<MediaItem>> result) {
}
<service android:exported="true" android:name=".AndroidAutoMediaService">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService">
</action>
</intent-filter>
</service>
<meta-data
android:name="com.google.android.gms.car.application"
android:resource="@xml/automotive_app_desc" />
<automotiveapp>
<uses name="media"/>
</automotiveapp>
public class AndroidAutoMediaService extends MediaBrowserService{
@Override
public BrowserRoot onGetRoot(String clientPackageName, int clientUid,
Bundle rootHints) {
return null;
}
@Override
public void onLoadChildren(String parentId, Result <List<MediaItem>> result) {
}
Google Glassは無線での通信ができませんので実際には、Google Glassでは、それらの情報を表示する3つの画面を持つアプリを作成しました。
スマートフォン⇔(無線)⇔PC⇔(有線)⇔Google Glass
という何ともオーバーヘッドの多い方法でやり取りしています。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().requestFeature(WindowUtils.FEATURE_VOICE_COMMANDS);
}
<item
android:id="@+id/menu_stop"
android:title="@string/menu_stop"/>
@Override
public boolean onCreatePanelMenu(int featureId, Menu menu) {
if (featureId == WindowUtils.FEATURE_VOICE_COMMANDS) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
// Pass through to super to setup touch menu.
return super.onCreatePanelMenu(featureId, menu);
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
//条件に応じて、項目の有効、無効、表示、非表示を変更する
setMenuState(menu.findItem(R.id.start), !mGameInfo.isTimeCount);
setMenuState(menu.findItem(R.id.stop), mGameInfo.isTimeCount);
return super.onPrepareOptionsMenu(menu);
}
private static void setMenuState(MenuItem menuItem, boolean enabled) {
menuItem.setVisible(enabled);
menuItem.setEnabled(enabled);
}
@Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
if (featureId == WindowUtils.FEATURE_VOICE_COMMANDS) {
switch (item.getItemId()) {
case R.id.menu_start:
break;
case R.id.menu_stop:
break;
case R.id.menu_home:
break;
default:
return true;
}
return true;
}
return super.onMenuItemSelected(featureId, item);
}
Y軸の頷いたかどうかのチェックは、タッチパットをタッチした場合にも同様の加速度が発生するため判断が難しく実際に使用するにはもう少し認識方法を考える必要があると思います。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setContentView(R.layout.main);
mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
}
@Override
protected void onResume(){
super.onResume();
if(mSensorManager != null){
List<sensor> sensors = mSensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER);
if(sensors.size()>0){
mSensorManager.registerListener(this, sensors.get(0), SensorManager.SENSOR_DELAY_UI);
}
}
}
@Override
protected void onPause(){
super.onPause();
if(mSensorManager != null){
mSensorManager.unregisterListener(this);
}
}
今回は実験のため、緩めの数値にしています。値が高い場合、かなり強く振らないと認識しなくなるためです。ただ、弱すぎる場合は、思わぬ顔振りで画面が切り替わったり、Glassの取り外しで画面が変わってしまう事もありました。上手く使用するにはON/OFFの設定が必要かもしれません。
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
currentOrientationValues[0] = event.values[0] * 0.1f + currentOrientationValues[0] * (1.0f - 0.1f);
currentOrientationValues[1] = event.values[1] * 0.1f + currentOrientationValues[1] * (1.0f - 0.1f);
currentOrientationValues[2] = event.values[2] * 0.1f + currentOrientationValues[2] * (1.0f - 0.1f);
float acceleration_x = event.values[0] - currentOrientationValues[0];
float acceleration_y = event.values[1] - currentOrientationValues[1];
float acceleration_z = event.values[2] - currentOrientationValues[2];
//x軸に4以上の加速度が発生した場合は、右振りと判断する
if(acceleration_x > 4){
return;
}
//y軸に5.5以上の加速度が発生した場合は、頷きと判断する
//タッチパッド操作の誤認識を避けるために右振りよりも少し数値を強めにしています。
if(acceleration_y > 5.5){
return;
}
}
}
もし公道で使用したら・・
こんな怪しい人に追いかけられたり・・
こんな恐ろしい事故を起こすかもしれません。
Google Glassは無線での通信ができませんので実際には、Google Glassでは、それらの情報を表示する3つの画面を持つアプリを作成しました。
スマートフォン⇔(無線)⇔PC⇔(有線)⇔Google Glass
という何ともオーバーヘッドの多い方法でやり取りしています。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().requestFeature(WindowUtils.FEATURE_VOICE_COMMANDS);
}
<item
android:id="@+id/menu_stop"
android:title="@string/menu_stop"/>
@Override
public boolean onCreatePanelMenu(int featureId, Menu menu) {
if (featureId == WindowUtils.FEATURE_VOICE_COMMANDS) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
// Pass through to super to setup touch menu.
return super.onCreatePanelMenu(featureId, menu);
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
//条件に応じて、項目の有効、無効、表示、非表示を変更する
setMenuState(menu.findItem(R.id.start), !mGameInfo.isTimeCount);
setMenuState(menu.findItem(R.id.stop), mGameInfo.isTimeCount);
return super.onPrepareOptionsMenu(menu);
}
private static void setMenuState(MenuItem menuItem, boolean enabled) {
menuItem.setVisible(enabled);
menuItem.setEnabled(enabled);
}
@Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
if (featureId == WindowUtils.FEATURE_VOICE_COMMANDS) {
switch (item.getItemId()) {
case R.id.menu_start:
break;
case R.id.menu_stop:
break;
case R.id.menu_home:
break;
default:
return true;
}
return true;
}
return super.onMenuItemSelected(featureId, item);
}
Y軸の頷いたかどうかのチェックは、タッチパットをタッチした場合にも同様の加速度が発生するため判断が難しく実際に使用するにはもう少し認識方法を考える必要があると思います。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setContentView(R.layout.main);
mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
}
@Override
protected void onResume(){
super.onResume();
if(mSensorManager != null){
List<sensor> sensors = mSensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER);
if(sensors.size()>0){
mSensorManager.registerListener(this, sensors.get(0), SensorManager.SENSOR_DELAY_UI);
}
}
}
@Override
protected void onPause(){
super.onPause();
if(mSensorManager != null){
mSensorManager.unregisterListener(this);
}
}
今回は実験のため、緩めの数値にしています。値が高い場合、かなり強く振らないと認識しなくなるためです。ただ、弱すぎる場合は、思わぬ顔振りで画面が切り替わったり、Glassの取り外しで画面が変わってしまう事もありました。上手く使用するにはON/OFFの設定が必要かもしれません。
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
currentOrientationValues[0] = event.values[0] * 0.1f + currentOrientationValues[0] * (1.0f - 0.1f);
currentOrientationValues[1] = event.values[1] * 0.1f + currentOrientationValues[1] * (1.0f - 0.1f);
currentOrientationValues[2] = event.values[2] * 0.1f + currentOrientationValues[2] * (1.0f - 0.1f);
float acceleration_x = event.values[0] - currentOrientationValues[0];
float acceleration_y = event.values[1] - currentOrientationValues[1];
float acceleration_z = event.values[2] - currentOrientationValues[2];
//x軸に4以上の加速度が発生した場合は、右振りと判断する
if(acceleration_x > 4){
return;
}
//y軸に5.5以上の加速度が発生した場合は、頷きと判断する
//タッチパッド操作の誤認識を避けるために右振りよりも少し数値を強めにしています。
if(acceleration_y > 5.5){
return;
}
}
}
もし公道で使用したら・・
こんな怪しい人に追いかけられたり・・
こんな恐ろしい事故を起こすかもしれません。
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (mLiveCard == null) {
Log.d(TAG, "Publishing LiveCard");
mLiveCard = mTimelineManager.createLiveCard(LIVE_CARD_TAG);
// Keep track of the callback to remove it before unpublishing.
mCallback = new ChronometerDrawer(this);
mLiveCard.setDirectRenderingEnabled(true).getSurfaceHolder().addCallback(mCallback);
Intent menuIntent = new Intent(this, MenuActivity.class);
menuIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
mLiveCard.setAction(PendingIntent.getActivity(this, 0, menuIntent, 0));
mLiveCard.publish(PublishMode.REVEAL);
Log.d(TAG, "Done publishing LiveCard");
} else {
// TODO(alainv): Jump to the LiveCard when API is available.
}
return START_STICKY;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (mLiveCard == null) {
mLiveCard = new LiveCard(this, LIVE_CARD_TAG);
// Keep track of the callback to remove it before unpublishing.
mCallback = new ChronometerDrawer(this);
mLiveCard.setDirectRenderingEnabled(true).getSurfaceHolder().addCallback(mCallback);
Intent menuIntent = new Intent(this, MenuActivity.class);
menuIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
mLiveCard.setAction(PendingIntent.getActivity(this, 0, menuIntent, 0));
mLiveCard.attach(this);
mLiveCard.publish(PublishMode.REVEAL);
} else {
mLiveCard.navigate();
}
return START_STICKY;
}
<uses-permission android:name="com.google.android.glass.permission.DEVELOPMENT" />
@Override
public void onResume() {
super.onResume();
openOptionsMenu();
}
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
openOptionsMenu();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (mLiveCard == null) {
Log.d(TAG, "Publishing LiveCard");
mLiveCard = mTimelineManager.createLiveCard(LIVE_CARD_TAG);
// Keep track of the callback to remove it before unpublishing.
mCallback = new ChronometerDrawer(this);
mLiveCard.setDirectRenderingEnabled(true).getSurfaceHolder().addCallback(mCallback);
Intent menuIntent = new Intent(this, MenuActivity.class);
menuIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
mLiveCard.setAction(PendingIntent.getActivity(this, 0, menuIntent, 0));
mLiveCard.publish(PublishMode.REVEAL);
Log.d(TAG, "Done publishing LiveCard");
} else {
// TODO(alainv): Jump to the LiveCard when API is available.
}
return START_STICKY;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (mLiveCard == null) {
mLiveCard = new LiveCard(this, LIVE_CARD_TAG);
// Keep track of the callback to remove it before unpublishing.
mCallback = new ChronometerDrawer(this);
mLiveCard.setDirectRenderingEnabled(true).getSurfaceHolder().addCallback(mCallback);
Intent menuIntent = new Intent(this, MenuActivity.class);
menuIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
mLiveCard.setAction(PendingIntent.getActivity(this, 0, menuIntent, 0));
mLiveCard.attach(this);
mLiveCard.publish(PublishMode.REVEAL);
} else {
mLiveCard.navigate();
}
return START_STICKY;
}
<uses-permission android:name="com.google.android.glass.permission.DEVELOPMENT" />
@Override
public void onResume() {
super.onResume();
openOptionsMenu();
}
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
openOptionsMenu();
}
Android KitKat Hacks プロが教えるテクニック & ツール
株式会社ブリリアントサービス 著
NFC Hacks プロが教えるテクニック&ツール
株式会社ブリリアントサービス 著
Androidプログラミングの教科書
藤田 竜史、要 徳幸、住友 孝郎、日高 正博、小林 慎治、木村 尭海 著
入門Androidアプリケーションテスト
瀬戸 直喜/株式会社ブリリアントサービス 著
実践スマートフォンアプリケーション開発
株式会社ブリリアントサービス、八木 俊広、原 昇平、かわかみ ひろき 著
ジオモバイルプログラミング
郷田まり子/宅間俊志/近藤昭雄 著
ANDROID HACKS プロが教えるテクニック&ツール
株式会社ブリリアントサービス 著