Android Studioを使用して、とりあえず動くWatch faceを作る(2/3)

<br />
[1/3](http://qiita.com/eoppp/items/e6f2a0783810026e4d56)からの続きです。
<br />
#Serviceの実装

##リソースファイルの追加

画像を追加します。

![スクリーンショット 2015-01-03 22.39.56.png](https://qiita-image-store.s3.amazonaws.com/0/63716/d0776979-d816-d4d5-5980-7d949db0c5fb.png)

* watchfaceの背景画像と、プレビュー用の画像を追加します。
* とりあえず、背景画像として以下の様な320*320くらいの黒で塗りつぶしたファイルを用意しましょう。ファイル名は、ここでは"bg.xxx"とします。(xxxはpngやjpg等、画像形式にあわせて入力して下さい)
* そのファイルを、wear→res→drawableに入れます。画像をコピーして、drawableの上でペーストすると、入ります。

![background.png](https://qiita-image-store.s3.amazonaws.com/0/63716/0051b09d-22d6-be8a-f736-5ce546d94daf.png)

* 次に、プレビュー用の画像を追加します。
* プレビュー用の画像は、円形のwearableと四角形のwearable用に2種類用意する必要があります。
* 中身は同じでも問題ないのですが、とりあえず以下のような画像ファイルを用意しました。
* 背景画像と同様に、drawableに入れます。ファイル名は、それぞれ"preview.xxx"、"preview_circular.xxx"とします。

![preview.png](https://qiita-image-store.s3.amazonaws.com/0/63716/4d99dffe-8a51-ee95-ddc8-3184194d8420.png)     ![preview_circular.png](https://qiita-image-store.s3.amazonaws.com/0/63716/6caa9cfc-d901-c837-00cd-f8bd28f73da2.png)

##コードの記述

[1/3](http://qiita.com/eoppp/items/e6f2a0783810026e4d56)で作成した、AnalogWatchFaceServiceに、以下を記述しました。とりあえず動作確認用に使用するだけなので、コピペしてしまってください。なお、このコードは、SDKのサンプルをそのまま使用しています。(android-sdk/samples/android-21/wearable/WatchFace)

```
public class AnalogWatchFaceService extends CanvasWatchFaceService {
  private static final String TAG = "AnalogWatchFaceService";

  /**
   * Update rate in milliseconds for interactive mode. We update once a second to advance the
   * second hand.
   */
  private static final long INTERACTIVE_UPDATE_RATE_MS = TimeUnit.SECONDS.toMillis(1);

  @Override
  public Engine onCreateEngine() {
    return new Engine();
  }

  private class Engine extends CanvasWatchFaceService.Engine {
    static final int MSG_UPDATE_TIME = 0;

    static final float TWO_PI = (float) Math.PI * 2f;

    Paint mHourPaint;
    Paint mMinutePaint;
    Paint mSecondPaint;
    Paint mTickPaint;
    boolean mMute;
    Calendar mCalendar;

    /**
     * Handler to update the time once a second in interactive mode.
     */
    final Handler mUpdateTimeHandler = new Handler() {
      @Override
      public void handleMessage(Message message) {
        switch (message.what) {
          case MSG_UPDATE_TIME:
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
              Log.v(TAG, "updating time");
            }
            invalidate();
            if (shouldTimerBeRunning()) {
              long timeMs = System.currentTimeMillis();
              long delayMs = INTERACTIVE_UPDATE_RATE_MS
                  - (timeMs % INTERACTIVE_UPDATE_RATE_MS);
              mUpdateTimeHandler.sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs);
            }
            break;
        }
      }
    };

    final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
      @Override
      public void onReceive(Context context, Intent intent) {
        mCalendar.setTimeZone(TimeZone.getDefault());
        invalidate();
      }
    };
    boolean mRegisteredTimeZoneReceiver = false;

    /**
     * Whether the display supports fewer bits for each color in ambient mode. When true, we
     * disable anti-aliasing in ambient mode.
     */
    boolean mLowBitAmbient;

    Bitmap mBackgroundBitmap;
    Bitmap mBackgroundScaledBitmap;

    @Override
    public void onCreate(SurfaceHolder holder) {
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "onCreate");
      }
      super.onCreate(holder);

      setWatchFaceStyle(new WatchFaceStyle.Builder(AnalogWatchFaceService.this)
          .setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT)
          .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
          .setShowSystemUiTime(false)
          .build());

      Resources resources = AnalogWatchFaceService.this.getResources();
      Drawable backgroundDrawable = resources.getDrawable(R.drawable.bg, null /* theme */);
      mBackgroundBitmap = ((BitmapDrawable) backgroundDrawable).getBitmap();

      mHourPaint = new Paint();
      mHourPaint.setARGB(255, 200, 200, 200);
      mHourPaint.setStrokeWidth(5.f);
      mHourPaint.setAntiAlias(true);
      mHourPaint.setStrokeCap(Paint.Cap.ROUND);

      mMinutePaint = new Paint();
      mMinutePaint.setARGB(255, 200, 200, 200);
      mMinutePaint.setStrokeWidth(3.f);
      mMinutePaint.setAntiAlias(true);
      mMinutePaint.setStrokeCap(Paint.Cap.ROUND);

      mSecondPaint = new Paint();
      mSecondPaint.setARGB(255, 255, 0, 0);
      mSecondPaint.setStrokeWidth(2.f);
      mSecondPaint.setAntiAlias(true);
      mSecondPaint.setStrokeCap(Paint.Cap.ROUND);

      mTickPaint = new Paint();
      mTickPaint.setARGB(100, 255, 255, 255);
      mTickPaint.setStrokeWidth(2.f);
      mTickPaint.setAntiAlias(true);

      mCalendar = Calendar.getInstance();
    }

    @Override
    public void onDestroy() {
      mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
      super.onDestroy();
    }

    @Override
    public void onPropertiesChanged(Bundle properties) {
      super.onPropertiesChanged(properties);
      mLowBitAmbient = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT, false);
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "onPropertiesChanged: low-bit ambient = " + mLowBitAmbient);
      }
    }

    @Override
    public void onTimeTick() {
      super.onTimeTick();
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "onTimeTick: ambient = " + isInAmbientMode());
      }
      invalidate();
    }

    @Override
    public void onAmbientModeChanged(boolean inAmbientMode) {
      super.onAmbientModeChanged(inAmbientMode);
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "onAmbientModeChanged: " + inAmbientMode);
      }
      if (mLowBitAmbient) {
        boolean antiAlias = !inAmbientMode;
        mHourPaint.setAntiAlias(antiAlias);
        mMinutePaint.setAntiAlias(antiAlias);
        mSecondPaint.setAntiAlias(antiAlias);
        mTickPaint.setAntiAlias(antiAlias);
      }
      invalidate();

      // Whether the timer should be running depends on whether we're in ambient mode (as well
      // as whether we're visible), so we may need to start or stop the timer.
      updateTimer();
    }

    @Override
    public void onInterruptionFilterChanged(int interruptionFilter) {
      super.onInterruptionFilterChanged(interruptionFilter);
      boolean inMuteMode = (interruptionFilter == WatchFaceService.INTERRUPTION_FILTER_NONE);
      if (mMute != inMuteMode) {
        mMute = inMuteMode;
        mHourPaint.setAlpha(inMuteMode ? 100 : 255);
        mMinutePaint.setAlpha(inMuteMode ? 100 : 255);
        mSecondPaint.setAlpha(inMuteMode ? 80 : 255);
        invalidate();
      }
    }

    @Override
    public void onDraw(Canvas canvas, Rect bounds) {
      mCalendar.setTimeInMillis(System.currentTimeMillis());

      int width = bounds.width();
      int height = bounds.height();

      // Draw the background, scaled to fit.
      if (mBackgroundScaledBitmap == null
          || mBackgroundScaledBitmap.getWidth() != width
          || mBackgroundScaledBitmap.getHeight() != height) {
        mBackgroundScaledBitmap = Bitmap.createScaledBitmap(mBackgroundBitmap,
            width, height, true /* filter */);
      }
      canvas.drawBitmap(mBackgroundScaledBitmap, 0, 0, null);

      // Find the center. Ignore the window insets so that, on round watches with a
      // "chin", the watch face is centered on the entire screen, not just the usable
      // portion.
      float centerX = width / 2f;
      float centerY = height / 2f;

      // Draw the ticks.
      float innerTickRadius = centerX - 10;
      float outerTickRadius = centerX;
      for (int tickIndex = 0; tickIndex < 12; tickIndex++) {
        float tickRot = tickIndex * TWO_PI / 12;
        float innerX = (float) Math.sin(tickRot) * innerTickRadius;
        float innerY = (float) -Math.cos(tickRot) * innerTickRadius;
        float outerX = (float) Math.sin(tickRot) * outerTickRadius;
        float outerY = (float) -Math.cos(tickRot) * outerTickRadius;
        canvas.drawLine(centerX + innerX, centerY + innerY,
            centerX + outerX, centerY + outerY, mTickPaint);
      }

      float seconds =
          mCalendar.get(Calendar.SECOND) + mCalendar.get(Calendar.MILLISECOND) / 1000f;
      float secRot = seconds / 60f * TWO_PI;
      float minutes = mCalendar.get(Calendar.MINUTE) + seconds / 60f;
      float minRot = minutes / 60f * TWO_PI;
      float hours = mCalendar.get(Calendar.HOUR) + minutes / 60f;
      float hrRot = hours / 12f * TWO_PI;

      float secLength = centerX - 20;
      float minLength = centerX - 40;
      float hrLength = centerX - 80;

      if (!isInAmbientMode()) {
        float secX = (float) Math.sin(secRot) * secLength;
        float secY = (float) -Math.cos(secRot) * secLength;
        canvas.drawLine(centerX, centerY, centerX + secX, centerY + secY, mSecondPaint);
      }

      float minX = (float) Math.sin(minRot) * minLength;
      float minY = (float) -Math.cos(minRot) * minLength;
      canvas.drawLine(centerX, centerY, centerX + minX, centerY + minY, mMinutePaint);

      float hrX = (float) Math.sin(hrRot) * hrLength;
      float hrY = (float) -Math.cos(hrRot) * hrLength;
      canvas.drawLine(centerX, centerY, centerX + hrX, centerY + hrY, mHourPaint);
    }

    @Override
    public void onVisibilityChanged(boolean visible) {
      super.onVisibilityChanged(visible);
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "onVisibilityChanged: " + visible);
      }

      if (visible) {
        registerReceiver();

        // Update time zone in case it changed while we weren't visible.
        mCalendar.setTimeZone(TimeZone.getDefault());
      } else {
        unregisterReceiver();
      }

      // Whether the timer should be running depends on whether we're visible (as well as
      // whether we're in ambient mode), so we may need to start or stop the timer.
      updateTimer();
    }

    private void registerReceiver() {
      if (mRegisteredTimeZoneReceiver) {
        return;
      }
      mRegisteredTimeZoneReceiver = true;
      IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
      AnalogWatchFaceService.this.registerReceiver(mTimeZoneReceiver, filter);
    }

    private void unregisterReceiver() {
      if (!mRegisteredTimeZoneReceiver) {
        return;
      }
      mRegisteredTimeZoneReceiver = false;
      AnalogWatchFaceService.this.unregisterReceiver(mTimeZoneReceiver);
    }

    /**
     * Starts the {@link #mUpdateTimeHandler} timer if it should be running and isn't currently
     * or stops it if it shouldn't be running but currently is.
     */
    private void updateTimer() {
      if (Log.isLoggable(TAG, Log.DEBUG)) {
        Log.d(TAG, "updateTimer");
      }
      mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
      if (shouldTimerBeRunning()) {
        mUpdateTimeHandler.sendEmptyMessage(MSG_UPDATE_TIME);
      }
    }

    /**
     * Returns whether the {@link #mUpdateTimeHandler} timer should be running. The timer should
     * only run when we're visible and in interactive mode.
     */
    private boolean shouldTimerBeRunning() {
      return isVisible() && !isInAmbientMode();
    }

  }
}
```

#実行

ここまででエラーがでていなければ、ひとまずプログラムは完成です。
早速、エミュレーター及び実機で実行してみましょう。

##エミュレーターでの実行

###エミュレーターの作成及び起動

![スクリーンショット 2015-01-03 23.02.41.png](https://qiita-image-store.s3.amazonaws.com/0/63716/30db3515-3fdf-b59d-929b-f82c056cc464.png)

* まず、エミュレーターを作成します。
* Tools→Android→AVD Managerを選択し、AVD Managerを起動します。
* Create Virtual Device...を選択します。
* 左側の"Category"から、"Wear"を選択します。
* 右側の、Android Wear SquareまたはAndroid Wear Roundを選択します。
* System Imageは、とりあえずLolipop, API Level21, x86のものを選択しておけばいいと思います。
* "Use Host GPU"には、一応チェックをつけておき、Finishで完成です。
* 完成したら、Actionsから再生ボタンを押して、エミュレーターを起動しましょう。

![スクリーンショット 2015-01-03 23.10.23.png](https://qiita-image-store.s3.amazonaws.com/0/63716/c00c15a8-5ec5-6e34-e4d1-4c8625d38b16.png)

###Wearableの実行

![スクリーンショット 2015-01-03 23.13.38.png](https://qiita-image-store.s3.amazonaws.com/0/63716/70343657-6f9f-7494-87c5-21e89fd66919.png)

* まず、Run→Edit Configurations...を選択します。
* 左側から、wearを選択します。（現在は、赤でバツが付いていると思います）
* 右側の、Activityの部分で、Do not launch Activity を選択し、OKとします。

![スクリーンショット 2015-01-03 23.15.42.png](https://qiita-image-store.s3.amazonaws.com/0/63716/8eb91bd4-a940-e0b3-812b-258bb0ed03fe.png)

* 以下から、wearを選択します。

![スクリーンショット 2015-01-03 23.17.55.png](https://qiita-image-store.s3.amazonaws.com/0/63716/24b0139d-517b-458d-0965-9ce0ede5c6fa.png)

* Run→Run wearを選択します。

![スクリーンショット 2015-01-03 23.19.42.png](https://qiita-image-store.s3.amazonaws.com/0/63716/dc592002-4988-e6cd-2f78-656f5ad44a57.png)

* Choose a running Deviceで、先ほど作成したエミュレーターを選択します。
* 画面下部の、4:Runを選択すると、現在の状況が表示されます。こちらの最終行に、Successと表示されるまで待ちます。

![スクリーンショット 2015-01-03 23.26.22.png](https://qiita-image-store.s3.amazonaws.com/0/63716/05d4bc31-e8e7-a1bd-20a9-0b7163179239.png)

* エミュレーター上で、時計が表示されている画面で、クリック長押しをすると、watch face選択画面に移行します。その中に、My Applicationという名前で、先ほど作成したプレビュー画像があると思います。
* そちらを選択すると、Watch faceが変更されます。

![スクリーンショット 2015-01-03 23.30.01.png](https://qiita-image-store.s3.amazonaws.com/0/63716/0a2bfdd8-6d70-b6a6-3eb6-ae4b6390cdc7.png)

![スクリーンショット 2015-01-03 23.31.46.png](https://qiita-image-store.s3.amazonaws.com/0/63716/a88b1478-3e7f-4ade-0d18-f38225d69505.png)

##実機での実行

以下が詳しいです。接続ができたら、エミュレーターと同様に実行します。

>[Android Wear実機へのadb接続](http://www.iplatform.org/blog/archives/2167)

#カスタマイズ等
[3/3](http://qiita.com/eoppp/items/dbf9f1c82d5987991e21)に続きます。

#参考にしたサイト等
>[美女時計を作ろう！Android Wear開発入門 30分で作る盤面アプリ](http://blog.btrax.com/jp/2014/12/16/develop-watch-face/)
>[Building a Watch Face Service](https://developer.android.com/training/wearables/watch-faces/service.html)(Android developers)