EclipseでTethering FireをAndroidWearに対応させました

2014/8/19 5375hit
このエントリーをはてなブックマークに追加

簡単にテザリングを開始できるAndroidアプリ、Tethering FireをAndroid Wearに対応させました。
これで、SONY Smart Watch(及び一部のBluetoothイヤホン)に加え、Android Wear、ロック画面、ホーム画面でテザリングをオン・オフ出来ます。
ウェアラブルデバイスでオン・オフできることでテザリングを開始するためにスマートフォンを取り出す手間が必要無くなり大変便利です。


TetheringFireの使い方


TetheringFireの紹介

ダウンロード

Google Playでダウンロード

要件

今回は、AndroidWear上で動くアプリを作るのではなく、Notificationで対応させました。
Notificationで対応する上で考えないといけなかったのは次の3つ
1.Notificationはウェアラブル上のみに表示し携帯端末上には表示させたくない
2.左右にスワイプせずに最初のカードをタップするだけでIntentを発行したい
3.一目見て操作できるようにしたい

そこで、まずは作法に従いandroid.support.v4.appを使った方法を試してみました。
開発環境はSonyのSmart Watchを使う関係上Eclipseを使って作られており、今回もそれを踏襲しました。

まず、サポートライブラリーのバージョンが古かったので最新版に変更します。
Eclipse上でプロジェクトを右クリックしPropertiesを表示します
Java Build Pathを選び、Libraryタグでextra.jarを選んでEdit
\sdk\extras\android\support\v4\android-support-v4.jarを選択
これで最新のsupport-v4.jarが使えます。
※jarをlibsに入れたり、Android Toolsを使って取り込む方法もあります。ご自由に

NotificationはNotificationCompat.Builderを使用します。


NotificationCompat.Builder notificationBuilder\
= new NotificationCompat.Builder(context).setGroup(DUMMY_KEY)
.setContentTitle(<表示したいタイトル>).setContentText(<表示したいテキスト>);
NotificationCompat.WearableExtender wearableExtender
= new NotificationCompat.WearableExtender().setBackground(background);

DUMMY_KEYはNotificationをStackに入れるためのStringです。
実際にはStackのなかみは1つのNotificationしか入れません。それでも指定する理由はウェアラブル上のみにNotificationを表示したいから
setGroupが指定されたNotificationはSummaryが携帯端末に表示されそれ以外がウェアラブルに表示されます。
Summaryを指定しないことでウェアラブル上のみにNotificationを表示できます。(ウェアラブル上にのみNotificationを表示するにはWearアプリを使う方法がよりスマートですが今回は開発工数を優先してこのような裏技を使っています)
表示したいタイトルにはテザリングの状態を表示するようにしています。ここのロジックはSmartWatch上のロジックで実装されていたのでとても楽でした。
setContentTextには「タップして開始」などの詳細を入れています。
setBackgroundには背景画像を設定しています。
背景画像のサイズは600px以上が推奨されていて、現在の状態に基づく画像を設定するように指定されています。
今回はテザリングの状態を表示するようにしています。
どのように動くかについては上記のTetheringFireの使い方の動画を見てください。

さらに、Notificationの実行時・終了時にはタップでの操作を可能とするため以下のロジックを入れています。

wearableExtender
.setContentIntentAvailableOffline(false)
.setContentAction(0)
.addAction(new NotificationCompat.Action.Builder(<small iconのdrawable id>, <テキスト>, <クリック時のIntent>).build())
.addAction(new NotificationCompat.Action.Builder(<big iconのdrawable id>, <テキスト>, <クリック時のIntent>).build());
notificationBuilder.setContentText(text);

テザリングはオフラインでは使用できないので、setContentIntentAvailableOfflineにはfalseを指定しています。
setContentActionを指定することでスワイプせずに最初のカードをタップすることでIntentを発行できるようになります。

最初のカードにアクションを追加するかどうかのガイドラインは
Android WearのUIパターンで解説されています。
・特定のアクションだけが実行されると予測がつく場合
TeteringFireではタップすればテザリングが開始・終了されることが自明です。

・Card上のアクションには理解するためのテキストラベルが不要でなくてはいけません。
上記の通り自明です。(ただし実際に使用してみたところテキストなしでは難しかったので最初の利用者のために解説のテキストも入れてはいます。詳しくは最後に解説します)

・Card上のアクションはウェアラブル上で結果が表示されなくてはいけません(電話上で開くためのWeb linkを除く)
結果を表示するロジックは以下で解説します

・Cardに付き1アクションのみです。Cardにはメニューがありません。
テザリングの開始・終了という1アクションのみです。

addActionを2回行っています。
これには理由があって、1つ目は最初のカードのためのアクション、2つ目はスワイプした時のためのアクションです。
最初のカードにアクションを追加すれば2つ目は不要なのですが、通常Wearの操作方法は基本的に横方向にスワイプしてからアクションするため、Wearの操作に慣れている人に一貫的な操作方法を提供するために追加しています。
2つのNotificationに提供するIntentの違いとして画像ファイルのサイズが有ります。
最初の1枚に指定するのはSmall Iconの画像サイズで32x32dpiです。
現状出ているWearデバイスはxhmdpiなので実際にはdrawable-xhdpiの64x64の画像が使われます。
現状ではdrawable-nodpiに64x64の画像を置いてこれを固定で使うことも出来ますが、将来的にもっと画面密度が荒かったり細かいWear端末が出てくることは十分に考えられるので、きちんと多密度対応しています。
といっても、toAndroidを使って複数画面密度の画像を一度に出力しているので手間はそんなに有りません。

次の2枚目に指定するのはBig Iconの画像サイズで
詳細な画像サイズについては
Android Wear MaterialsのUI Tool Kitに書かれていますが
small iconは32dp x 32dpの画像に24dp x 24dpのサイズで黒色のガイドラインとして#434343が推奨されています。

この画像はスリープ時にも単色で表示されます。
今回はテザリングの状態を示すために、テザリング時は白塗りつぶし、非テザリング時は白枠のみとしました。
※ただし、実際にはこのサイズでは見づらかったので、もっと見分けが付くデザインに変更予定です。


big iconは64dp x 64dpの画像に48dp x 48dpの白一色で描くようになっています。

NOTIFICATTION_IDは固定で1つの値を指定します。
これにより、テザリング状態が変更した時は新しいNotificationを同じIDで指定することでステータスの状態が変更します。
※どのように動くかについては上記のTetheringFireの使い方の動画を見てください。
クリック時のIntentはPendingIntentで指定します。

今回は既にウィジェットがあり、ウィジェットタップ時と同じ処理を行えばいいのでウィジェット用に作っていたServiceにIntentを飛ばしています。

PendingIntent clickAction
= PendingIntent.getService(context, 0, new Intent(context, WidgetService.class).setAction(WidgetIntent.CLICK.ACTION), 0);


最後にNotificationCompat.Builderインスタンスのextend()を使って作成したExtendをセットします。
setContentIntent

notificationBuilder.extend(wearableExtender);
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());

全体のロジックは以下のとおりです。
他のクラスに依存している部分が多いためこのままコピペで動かないのは申し訳ないのですが、これだけでウェアラブルに対応したNotificationが発行出来てしまいます。


PendingIntent clickAction = PendingIntent.
getService(context, 0, new Intent(context, WidgetService.class).
setAction(WidgetIntent.CLICK.ACTION), 0);
NotificationManagerCompat notificationManager = NotificationManagerCompat.
from(context);
Resources r = context.getResources();
String text;
int smallIconId;
int bigIconId;
boolean action;
switch (state) {
case WIFI_AP_STATE_ENABLED:
text = context.getString(R.string.tap_to_disable);
smallIconId = R.drawable.wear_pause;
bigIconId = R.drawable.ic_full_pause;
action = true;
break;
case WIFI_AP_STATE_DISABLED:
text = context.getString(R.string.tap_to_enable);
smallIconId = R.drawable.wear_start;
bigIconId = R.drawable.ic_full_start;
action = true;
break;
default:
text = "";
smallIconId = -1;
bigIconId = -1;
action = false;
break;
}
Bitmap background
= BitmapFactory.decodeResource(r, state.getWearDrawableId());
NotificationCompat.Builder notificationBuilder
= new NotificationCompat.Builder(context)
.setGroup(DUMMY_KEY)
.setContentTitle(state.getMessage(context))
.setContentText(text);
NotificationCompat.WearableExtender wearableExtender
= new NotificationCompat
.WearableExtender()
.setCustomSizePreset(NotificationCompat.WearableExtender.SIZE_LARGE)
.setBackground(background);
if (action) {
wearableExtender.setContentIntentAvailableOffline(false)
.setContentAction(0).addAction(new NotificationCompat
.Action.Builder(smallIconId, text, clickAction).build())
.addAction(new NotificationCompat.Action.Builder(bigIconId, text, clickAction)
.build());
notificationBuilder.setContentText(text);
}
PendingIntent activityIntnet = PendingIntent
.getActivity(context, 0, new Intent(context, MainActivity.class), 0);
notificationBuilder.extend(wearableExtender)
.setContentIntent(activityIntnet);
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());


AndroidManifestでIntentのフィルターをサービスに設定しています。

<service
android:name=".WidgetService"
android:enabled="true"
android:exported="false"
android:launchMode="singleTask" >
<intent-filter>
<action android:name="<アクション名>" />
</intent-filter>
</service>

あとはWidgetService内でIntentを受けた時にテザリングをON/OFFするロジックを入れています。

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// intentを受けた時の処理
}


付録:トグルスイッチの状態について

今回、最初のカードに「タップして開始」のような説明的テキストを追加するべきかどうかとても悩みました。
ガイドラインに従うなら追加するべきではないのでしょうが、実際に無い状態でユーザーとしてフィールドテストをしてみたところわかりづらい印象を受けました。
問題は、今回のようなON/OFFボタンを表示するときにアイコンをどう表示するかについて、

ひとつはボタンを押すと何が行えるかでアイコンを切り替える方法。
テザリングがONの時はOFFボタンを表示し
テザリングがOFFの時はONボタンを表示する。

もう一つは逆に現在の状態を表示する方法
テザリングがONの時はonボタンを表示し
テザリングがOFFの時はoffボタンを表示する。

前者は音楽アプリの再生、一時停止ボタンによくあるデザインパターンで、TetheringFireでも最初はこのデザインパターンを使いました。
しかし、実際にテストしてみるとスリープモード時に
「停止」 と書かれていると、現在テザリングが開始されていて、カードをタップすると「停止」するようにも見えてしまうということに気づきました。
ユーザーにとってテザリングの状態を把握できるのは重要なため後者に変更しました。
後者は現在の状態がわかりやすいのがメリットで設定画面などではこのデザインパターンがよく使われています。

これでテザリングの状態がわからない問題は解決しましたが、次は
「停止」offボタンと書かれているとタップでテザリングが開始されることがわかりづらいという問題が出てしまいました。
そこで、「停止」、「タップすると開始」と明記して、現在の状態と何が行えるかを表しました。
伝えたいことは「停止」していることで、タップすると開始するのは慣れた人には知る必要がない情報なので、「停止」は大きく濃い文字で表示されるタイトルを、「タップすると開始」は薄くて小さなテキストとして指定しました。

Wearでの操作は初めてのことが多く、実際に使ってみると正しく機能しないことが多々あります。
アプリを作る際には実機を使ったテストが大切であることを痛感しました。

前:交通事故!その時のために 次:日産デュアリス4年目のレビュー

関連キーワード

[Android][モバイル][IT][ウェアラブル]

コメントを投稿する

名前URI
コメント