Smartwatch-Apps für Android Wear erstellen
Diese Recording-App läuft auf allen Android-Smartwatches. Schritt für Schritt führt Sie der Hamburger App-Developer Frank Lehmann durch die Entwicklung einer Recording-App, die auf allen Android-Smartwatches zum Einsatz kommen kann.

Code-Schnipsel für Android Wear Tutorial

Code-Schnipsel 4.1
if (mGoogleApiClient == null) {
  mGoogleApiClient = new GoogleApiClient.Builder(this)
          .addApi(Wearable.API)
          .addConnectionCallbacks(this)
          .addOnConnectionFailedListener(this)
          .build();

Code-Schnipsel 4.2
    public void onConnected(Bundle bundle) { 
Wearable. .ChannelApi.addListener(mGoogleApiClient, this); Wearable.MessageApi.addListener(mGoogleApiClient, this); Wearable.NodeApi.addListener(mGoogleApiClient, this);
    }

    protected void onPause() {
super.onPause();
Wearable.ChannelApi.removeListener(mGoogleApiClient, this);
Wearable.MessageApi.removeListener(mGoogleApiClient, this);
Wearable.NodeApi.removeListener(mGoogleApiClient, this);
mGoogleApiClient.disconnect();
    }

Code-Schnipsel 5.1
private void sendMessage(String message, String nodeId) {
String path = "TestPath";
byte[] messageBytes = message.getBytes();

// Better check mGoogleApiClient against null here
Wearable.MessageApi.sendMessage(
          mGoogleApiClient, nodeId, path, messageBytes).setResultCallback(
          new com.google.android.gms.common.api.
ResultCallback<MessageApi.SendMessageResult>() {
              @Override
              public void onResult(MessageApi.SendMessageResult sendMessageResult) {
                  if (!sendMessageResult.getStatus().isSuccess()) {
                      Log.e(getClass().getName(), "Failed to send message with status code: " + sendMessageResult.getStatus().getStatusCode());
                  } else {
                      Log.d(getClass().getName(), "Message sent with status code: " + sendMessageResult.getStatus().getStatusCode());
                  }
              }
          }
  );
}

Code-Schnipsel 5.2
<Button
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="Send Message"
  android:id="@+id/button"
  android:onClick="onClickSendMessage"
  android:layout_centerVertical="true"
  android:layout_centerHorizontal="true" />

Code-Schnipsel 5.3
public void onClickSendMessage(View view) {
  if (mGoogleApiClient.isConnected()) {
      Wearable.NodeApi.getConnectedNodes(mGoogleApiClient).setResultCallback(new ResultCallback<NodeApi.GetConnectedNodesResult>() {
          @Override
          public void onResult(NodeApi.GetConnectedNodesResult getConnectedNodesResult) {
              if (getConnectedNodesResult.getStatus().isSuccess()) {
                  for (Node node : getConnectedNodesResult.getNodes()) {
                      sendMessage("Hello from Phone", node.getId());
                  }
              } else {
                  Log.e(getClass().getName(), "getNodes bad result");
              }
          }
      });
  }
}

Code-Schnipsel 6.1
@Override
public void onMessageReceived(MessageEvent messageEvent) {
  Log.d(getClass().getName(), "onMessageReceived");
  if (messageEvent.getPath().equals("TestPath")) {
      final String message = new String(messageEvent.getData());
      Log.d(getClass().getName(), "onMessageReceived with TestPath and Message: " + message);
      // needed since we must modify the UI from the UI Thread
      runOnUiThread(new Runnable() { 
          public void run() {
              if (mTextView != null) {
                  mTextView.setText(message); // Display Message
              }
          }
      });
  }
}

Code-Schnipsel 7
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="104dp" android:layout_height="104dp"
  android:shape="oval">
  <solid android:color="#ff5151" />
</shape>

In der Datei »round_activity_main.xml« fügen wir in das RelativeLayout folgenden Code ein:

<Button
  android:id="@+id/button"
  style="?android:attr/borderlessButtonStyle"
  android:layout_width="104dp"
  android:layout_height="104dp"
  android:layout_centerInParent="true"
  android:background="@drawable/button_circle"
  android:onClick="onClick"
  android:text="REC"
  android:textSize="24sp" />

<TextView
  android:id="@+id/text"
  android:layout_width="wrap_content"
  android:layout_height="match_parent"
  android:layout_below="@+id/button"
  android:layout_centerHorizontal="true"
  android:gravity="center"
  android:text="02:00"
  android:textSize="20sp" />

Code-Schnipsel 8.1
private void startCountdown() {
  currentRemainingTime = 2 * 60 * 1000;

  recordTimer = new Timer();
  recordTimer.schedule(new TimerTask() {
      @Override
      public void run() {
          currentRemainingTime -= 1000;
          runOnUiThread(new Runnable() {
              @Override
              public void run() {
                  mTextView.setText(String.format("%02d:%02d",
                          TimeUnit.MILLISECONDS.toMinutes(currentRemainingTime),
                          TimeUnit.MILLISECONDS.toSeconds(currentRemainingTime) -
                                  TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(currentRemainingTime))
                  ));
                  if (mButton.getText().equals("REC")) {
                      mButton.setText("");
                  } else {
                      mButton.setText("REC");
                  }
              }
          });

          if (currentRemainingTime < 1) {
              isRecording.set(false);
          }
      }
  }, 0, 1000);
}

Code-Schnipsel 8.2
public void onClick(View view) {
  Log.d(getClass().getName(), "onClick");

  // If we are recording, stop the recording with button click
  if (isRecording.getAndSet(false)) {
      return;
  }

  // Keep the watch turned on
  getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

  // Start new Thread for recording
  new AsyncTask<Void, Void, Boolean>() {
      private Boolean success;

      @Override
      protected void onPreExecute() {
          super.onPreExecute();
          success = false;
          // Set the record state
	        isRecording.set(true);
          // Start the Countdown Timer
          startCountdown();
      }

      @Override
      protected Boolean doInBackground(Void... params) {
          Node phoneNode = getFirstLocalNode(mGoogleApiClient, 1000);
          if (phoneNode != null) {
              OutputStream outputStream = getOutputStreamFromNewChannel(mGoogleApiClient, phoneNode, "path1", 1000);
              if (outputStream != null) {
                  success = streamAudio(outputStream);
              }
          }
          return success;
      }

      @Override
      protected void onPostExecute(Boolean success) {
          super.onPostExecute(success);
          stopAndRewindCountdown();
          // Let watch go to sleep
          if(getWindow() != null) {
              getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
          }
          if (!success) {
              isRecording.set(false);
              // TODO Handle recording error
          }
      }
  }.execute();
}

Code-Schnipsel 9
private Node getFirstLocalNode(GoogleApiClient googleApiClient, long timeout) {
  ArrayList<Node> nodes = (ArrayList<Node>) Wearable.NodeApi
          .getConnectedNodes(googleApiClient)
          .await(timeout, TimeUnit.MILLISECONDS)
          .getNodes();
  for (Node node : nodes) {
      // If phone or watch is connected to the internet, there is a node named 'cloud'
      if (!node.getDisplayName().equals("cloud")) {
          return node;
      }
  }
  return null;
}

Code-Schnipsel 9.2
private OutputStream getOutputStreamFromNewChannel(GoogleApiClient googleApiClient, Node node, String path, long timeout) {
  Channel channel = Wearable.ChannelApi
          .openChannel(googleApiClient, node.getId(), path)
          .await(timeout, TimeUnit.MILLISECONDS)
          .getChannel();

  if (channel != null) {
      return channel.getOutputStream(mGoogleApiClient)
              .await(timeout, TimeUnit.MILLISECONDS)
              .getOutputStream();
  }

  return null;
}

Code-Schnipsel 10
private void streamAudio(OutputStream outputStream) {
  int minBufferSize = AudioRecord.getMinBufferSize(44100, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);

  AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
          44100,
          AudioFormat.CHANNEL_IN_MONO,
          AudioFormat.ENCODING_PCM_16BIT,
          minBufferSize);

  if (audioRecord.getState() == AudioRecord.STATE_INITIALIZED) {
      // This does not record, just sets the state of the AudioRecord-object
      audioRecord.startRecording();
      startCountdown();
      byte[] byteBuffer = new byte[minBufferSize];

      while (isRecording.get()) {
          // gets the voice output from microphone to byte format
          audioRecord.read(byteBuffer, 0, byteBuffer.length);
          // writes to OutputStream, thus streaming to phone
          try {
              outputStream.write(byteBuffer, 0, byteBuffer.length);
          } catch (IOException e) {
              e.printStackTrace();
          }
      }
      // At this point of code, the recording stopped, because we passed the loop
      audioRecord.stop();
      audioRecord.release();
  }
  // TODO Close the outputstream
}

Code-Schnipsel 11.2
if (channel.getPath().equals("path1")) {
  channel.getInputStream(mGoogleApiClient)
          .setResultCallback(new ResultCallback<Channel.GetInputStreamResult>() {
      @Override
      public void onResult(Channel.GetInputStreamResult getInputStreamResult) {
          handleInputStream(getInputStreamResult.getInputStream());
      }
  });
}

Code-Schnipsel 11.3
private void handleInputStream(final InputStream inputStream) {
  // Create new file in the internal storage
  lastRecordFile = new File(getFilesDir(), "latest_record.pcm");

  // Receiving and saving audio in background thread
  new AsyncTask<Void, Void, Boolean>() {
      Boolean success = false;
      int length = 0;
      byte[] buffer = new byte[minBufferSize];
      OutputStream outputStream = null;

      @Override
      protected Boolean doInBackground(Void... params) {
          try {
              // FileOutputStream for writing to file
              outputStream = new FileOutputStream(lastRecordFile);
              // Reads an byte array from the inputStream and writes to buffer
              while ((length = inputStream.read(buffer)) != -1) {
                  // writes buffer to outputStream
                  outputStream.write(buffer, 0, length);
              }
              success = true;
          } catch (IOException e) {
              e.printStackTrace();
          } finally {
              // TODO Close input- and outputStream
          }
          return success;
      }

      @Override
      protected void onPostExecute(Boolean success) {
          super.onPostExecute(success);
          if (success) {
              playButton.setEnabled(true);
          } else {
              // TODO Handle file receiving/saving error
          }
      }
  }.execute();
}

Code-Schnipsel 12
public void onClick(final View view) {
  // Play Audio on background thread
  new AsyncTask<Void, Void, Void>() {
    @Override
    protected Boolean doInBackground(Void... params) {
      // FileInputStream for reading the file
      FileInputStream fileInputStream = new FileInputStream(lastRecordFile);
      AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 44100
      , AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT,
      minBufferSize, AudioTrack.MODE_STREAM);
      audioTrack.play();
      int length;
      byte[] buffer = new byte[minBufferSize];
      // Reads an byte array from the inputStream and writes to buffer
      while ((length = fileInputStream.read(buffer)) != -1) {
        audioTrack.write(buffer, 0, length);
      }
      // TODO close fileInputStream and release audioTrack
      return null;
    }            
  }.execute();
}