diff --git a/samples/browseable/ElizaChat/Shared/AndroidManifest.xml b/samples/browseable/ActionBarCompat-Basic/res/values-v21/base-colors.xml
similarity index 71%
rename from samples/browseable/ElizaChat/Shared/AndroidManifest.xml
rename to samples/browseable/ActionBarCompat-Basic/res/values-v21/base-colors.xml
index dfbb0927d..34c9cd138 100644
--- a/samples/browseable/ElizaChat/Shared/AndroidManifest.xml
+++ b/samples/browseable/ActionBarCompat-Basic/res/values-v21/base-colors.xml
@@ -14,12 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
+
Demonstrates how to the use Activity scene transitions when transitions - from one Activity to another. Uses a combination of moveImage and changeBounds - to nicely transition a grid of images to an Activity with a large image and detail - text. + from one Activity to another. Uses a combination of changeImageTransform and + changeBounds to nicely transition a grid of images to an Activity with a large image + and detail text.
diff --git a/samples/browseable/ActivitySceneTransitionBasic/res/layout/details.xml b/samples/browseable/ActivitySceneTransitionBasic/res/layout/details.xml index e61212ed2..8ca463482 100644 --- a/samples/browseable/ActivitySceneTransitionBasic/res/layout/details.xml +++ b/samples/browseable/ActivitySceneTransitionBasic/res/layout/details.xml @@ -27,7 +27,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> -- - Welcome to the Basic Android Key Store sample!\n\n - This sample demonstrates how to use the Android Key Store to safely create and store - encryption keys that only your application can access. You can also sign data - using those keys.\n\n - To create a new KeyPair, click \"Create\".\n\n - To sign some data using a KeyPair, click \"Sign\".\n\n - To verify the data using the signature provided, click \"Verify\".\n\n + +Welcome to the Basic Android Key Store sample!\n\n +This sample demonstrates how to use the Android Key Store to safely create and store +encryption keys that only your application can access. You can also sign data +using those keys.\n\n +To create a new KeyPair, click \"Create\".\n\n +To sign some data using a KeyPair, click \"Sign\".\n\n +To verify the data using the signature provided, click \"Verify\".\n\n +
diff --git a/samples/browseable/BasicAndroidKeyStore/res/layout-sw600dp-land/activity_main.xml b/samples/browseable/BasicAndroidKeyStore/res/layout-sw600dp-land/activity_main.xml index 653454bb2..8fed5f746 100644 --- a/samples/browseable/BasicAndroidKeyStore/res/layout-sw600dp-land/activity_main.xml +++ b/samples/browseable/BasicAndroidKeyStore/res/layout-sw600dp-land/activity_main.xml @@ -13,25 +13,30 @@ See the License for the specific language governing permissions and limitations under the License. --> -- This sample demonstrates how to use the ConnectivityManager to determine if you have - a network connection, and if so, what type of connection it is. - \n\nA "NetworkInfo" object is retrieved from the ConnectivityManager, which contains information - on the active connection, and then the connection type is printed to an on-screen console. + This sample demonstrates how to use the ConnectivityManager to determine if you have + a network connection, and if so, what type of connection it is. + \n\nA "NetworkInfo" object is retrieved from the ConnectivityManager, which contains information + on the active connection, and then the connection type is printed to an on-screen console.
diff --git a/samples/browseable/BasicNetworking/res/drawable-hdpi/ic_launcher.png b/samples/browseable/BasicNetworking/res/drawable-hdpi/ic_launcher.png index 22ce60611..fad6812b3 100755 Binary files a/samples/browseable/BasicNetworking/res/drawable-hdpi/ic_launcher.png and b/samples/browseable/BasicNetworking/res/drawable-hdpi/ic_launcher.png differ diff --git a/samples/browseable/BasicNetworking/res/drawable-mdpi/ic_launcher.png b/samples/browseable/BasicNetworking/res/drawable-mdpi/ic_launcher.png index f21e17b65..04cf11522 100755 Binary files a/samples/browseable/BasicNetworking/res/drawable-mdpi/ic_launcher.png and b/samples/browseable/BasicNetworking/res/drawable-mdpi/ic_launcher.png differ diff --git a/samples/browseable/BasicNetworking/res/drawable-xhdpi/ic_launcher.png b/samples/browseable/BasicNetworking/res/drawable-xhdpi/ic_launcher.png index 64b805902..53736dfe1 100755 Binary files a/samples/browseable/BasicNetworking/res/drawable-xhdpi/ic_launcher.png and b/samples/browseable/BasicNetworking/res/drawable-xhdpi/ic_launcher.png differ diff --git a/samples/browseable/BasicNetworking/res/drawable-xxhdpi/ic_launcher.png b/samples/browseable/BasicNetworking/res/drawable-xxhdpi/ic_launcher.png index 6b4434a87..6afbfca32 100755 Binary files a/samples/browseable/BasicNetworking/res/drawable-xxhdpi/ic_launcher.png and b/samples/browseable/BasicNetworking/res/drawable-xxhdpi/ic_launcher.png differ diff --git a/samples/browseable/BasicNetworking/res/values-v21/base-colors.xml b/samples/browseable/BasicNetworking/res/values-v21/base-colors.xml new file mode 100644 index 000000000..34c9cd138 --- /dev/null +++ b/samples/browseable/BasicNetworking/res/values-v21/base-colors.xml @@ -0,0 +1,20 @@ + + +- This sample demonstrates the use of the two step sensors (step detector and counter) and - sensor batching.\n\n It shows how to register a SensorEventListener with and without - batching and shows how these events are received.\n\nThe Step Detector sensor fires an +
This sample demonstrates the use of the two step sensors (step detector and counter) and + sensor batching.
+It shows how to register a SensorEventListener with and without + batching and shows how these events are received.
+The Step Detector sensor fires an event when a step is detected, while the step counter returns the total number of steps since a listener was first registered for this sensor. Both sensors only count steps while a listener is registered. This sample only covers the basic case, where a listener is only registered while the app is running. Likewise, batched sensors can be used in the background (when the CPU is suspended), which requires manually flushing the sensor event queue before it overflows, which is not - covered in this sample. + covered in this sample.
diff --git a/samples/browseable/BatchStepSensor/res/values-v21/base-colors.xml b/samples/browseable/BatchStepSensor/res/values-v21/base-colors.xml new file mode 100644 index 000000000..34c9cd138 --- /dev/null +++ b/samples/browseable/BatchStepSensor/res/values-v21/base-colors.xml @@ -0,0 +1,20 @@ + + +It shows how to register a SensorEventListener with and without + batching and shows how these events are received.
+The Step Detector sensor fires an event when a step is detected, while the step counter returns the total number of steps since a listener was first registered for this sensor. Both sensors only count steps while a listener is registered. This sample only covers the basic case, where a listener is only registered while the app is running. Likewise, batched sensors can be used in the background (when the CPU is suspended), which requires manually flushing the sensor event queue before it overflows, which is not - covered in this sample. + covered in this sample.
]]> diff --git a/samples/browseable/BluetoothChat/res/values-v21/base-colors.xml b/samples/browseable/BluetoothChat/res/values-v21/base-colors.xml new file mode 100644 index 000000000..34c9cd138 --- /dev/null +++ b/samples/browseable/BluetoothChat/res/values-v21/base-colors.xml @@ -0,0 +1,20 @@ + + +- You can cause the wearable to start an activity by pressing a button in the - companion app UI. You can also take a picture on the companion device, which will be transmitted as - an Asset for display on the wearable. + Sample demonstrating the usage of the GoogleApiClient in order to send data + from a handheld device to a wearable. The data transmitted is a picture taken by + the user of the sample.
diff --git a/samples/browseable/DelayedConfirmation/Application/AndroidManifest.xml b/samples/browseable/DelayedConfirmation/Application/AndroidManifest.xml index 3fa1f36fc..c9fdccbf1 100644 --- a/samples/browseable/DelayedConfirmation/Application/AndroidManifest.xml +++ b/samples/browseable/DelayedConfirmation/Application/AndroidManifest.xml @@ -17,8 +17,8 @@
+ * Always avoid loading resources from the main thread. In this sample, the background images are
+ * loaded from an background task and then updated using {@link #notifyRowBackgroundChanged(int)}
+ * and {@link #notifyPageBackgroundChanged(int, int)}.
*/
public class SampleGridPagerAdapter extends FragmentGridPagerAdapter {
+ private static final int TRANSITION_DURATION_MILLIS = 100;
private final Context mContext;
+ private List
- This sample shows how to implement an audio media app that provides
- media library metadata and playback controls through a standard
- service.
-
-
- - This sample uses the camera/camcorder as the A/V source for the MediaRecorder API. - A TextureView is used as the camera preview which limits the code to API 14+. This - can be easily replaced with a SurfaceView to run on older devices. - + +This sample uses the camera/camcorder as the A/V source for the MediaRecorder API. +A TextureView is used as the camera preview which limits the code to API 14+. This +can be easily replaced with a SurfaceView to run on older devices. +
diff --git a/samples/browseable/MediaRecorder/res/drawable-hdpi/ic_launcher.png b/samples/browseable/MediaRecorder/res/drawable-hdpi/ic_launcher.png index 13cd1e8ec..bea32bc08 100644 Binary files a/samples/browseable/MediaRecorder/res/drawable-hdpi/ic_launcher.png and b/samples/browseable/MediaRecorder/res/drawable-hdpi/ic_launcher.png differ diff --git a/samples/browseable/MediaRecorder/res/drawable-mdpi/ic_launcher.png b/samples/browseable/MediaRecorder/res/drawable-mdpi/ic_launcher.png index 00b2bd9a4..2a3a4909a 100644 Binary files a/samples/browseable/MediaRecorder/res/drawable-mdpi/ic_launcher.png and b/samples/browseable/MediaRecorder/res/drawable-mdpi/ic_launcher.png differ diff --git a/samples/browseable/MediaRecorder/res/drawable-xhdpi/ic_launcher.png b/samples/browseable/MediaRecorder/res/drawable-xhdpi/ic_launcher.png index 953f1cc53..9674428ee 100644 Binary files a/samples/browseable/MediaRecorder/res/drawable-xhdpi/ic_launcher.png and b/samples/browseable/MediaRecorder/res/drawable-xhdpi/ic_launcher.png differ diff --git a/samples/browseable/MediaRecorder/res/drawable-xxhdpi/ic_launcher.png b/samples/browseable/MediaRecorder/res/drawable-xxhdpi/ic_launcher.png index f2ccb102a..61e322cff 100644 Binary files a/samples/browseable/MediaRecorder/res/drawable-xxhdpi/ic_launcher.png and b/samples/browseable/MediaRecorder/res/drawable-xxhdpi/ic_launcher.png differ diff --git a/samples/browseable/MediaRecorder/res/values-v21/base-colors.xml b/samples/browseable/MediaRecorder/res/values-v21/base-colors.xml new file mode 100644 index 000000000..34c9cd138 --- /dev/null +++ b/samples/browseable/MediaRecorder/res/values-v21/base-colors.xml @@ -0,0 +1,20 @@ + + +- Demonstration of using RecyclerView with a LayoutManager to create a vertical ListView. + Demonstration of using RecyclerView with a LinearLayoutManager and GridLayoutManager + to create a vertical list. Tap \"SHOW LOG\" to view elements as they are bound to + their ViewHolder. The log also displays elements that you tap.
diff --git a/samples/browseable/RecyclerView/res/layout/recycler_view_frag.xml b/samples/browseable/RecyclerView/res/layout/recycler_view_frag.xml index 6682468ee..dda99ce3a 100644 --- a/samples/browseable/RecyclerView/res/layout/recycler_view_frag.xml +++ b/samples/browseable/RecyclerView/res/layout/recycler_view_frag.xml @@ -15,14 +15,28 @@ limitations under the License. --> -
-This sample uses the FusedLocation APIs of GMS on those
-devices that have a hardware GPS built in. In those cases,
-this sample provides a simple screen that shows the current
-speed of the device on the watch. User can set a speed limit
-and if the speed approaches that limit, it changes the color
-to yellow and if it exceeds the limit, it turns red. User
-can also enable recording of coordinates and when it pairs
-back with the phone, this data will be synced with the phone
+This sample uses the FusedLocation APIs of Google Play Services
+on those devices that have a hardware GPS built in. In those
+cases, this sample provides a simple screen that shows the
+current speed of the device on the watch. User can set a speed
+limit and if the speed approaches that limit, it changes the
+color to yellow and if it exceeds the limit, it turns red. User
+can also enable recording of coordinates and when it pairs back
+with the phone, this data will be synced with the phone
component of the app and user can see a track made of those
coordinates on a map on the phone.
diff --git a/samples/browseable/StorageClient/res/values-v21/base-colors.xml b/samples/browseable/StorageClient/res/values-v21/base-colors.xml
new file mode 100644
index 000000000..34c9cd138
--- /dev/null
+++ b/samples/browseable/StorageClient/res/values-v21/base-colors.xml
@@ -0,0 +1,20 @@
+
+
+
+ * If the current config {@link DataItem} doesn't exist, it isn't created and the callback
+ * receives an empty DataMap.
+ */
+ public static void fetchConfigDataMap(final GoogleApiClient client,
+ final FetchConfigDataMapCallback callback) {
+ Wearable.NodeApi.getLocalNode(client).setResultCallback(
+ new ResultCallback
+ * It is allowed that only some of the keys used in the config DataItem appear in
+ * {@code configKeysToOverwrite}. The rest of the keys remains unmodified in this case.
+ */
+ public static void overwriteKeysInConfigDataMap(final GoogleApiClient googleApiClient,
+ final DataMap configKeysToOverwrite) {
+
+ DigitalWatchFaceUtil.fetchConfigDataMap(googleApiClient,
+ new FetchConfigDataMapCallback() {
+ @Override
+ public void onConfigDataMapFetched(DataMap currentConfig) {
+ DataMap overwrittenConfig = new DataMap();
+ overwrittenConfig.putAll(currentConfig);
+ overwrittenConfig.putAll(configKeysToOverwrite);
+ DigitalWatchFaceUtil.putConfigDataItem(googleApiClient, overwrittenConfig);
+ }
+ }
+ );
+ }
+
+ /**
+ * Overwrites the current config {@link DataItem}'s {@link DataMap} with {@code newConfig}.
+ * If the config DataItem doesn't exist, it's created.
+ */
+ public static void putConfigDataItem(GoogleApiClient googleApiClient, DataMap newConfig) {
+ PutDataMapRequest putDataMapRequest = PutDataMapRequest.create(PATH_WITH_FEATURE);
+ DataMap configToPut = putDataMapRequest.getDataMap();
+ configToPut.putAll(newConfig);
+ Wearable.DataApi.putDataItem(googleApiClient, putDataMapRequest.asPutDataRequest())
+ .setResultCallback(new ResultCallback Note This is quite slow so it's best to use it sparingly in production builds.
+ *
+ * @param glOperation name of the OpenGL call to check
+ */
+ private static void checkGlError(String glOperation) {
+ int error = GLES20.glGetError();
+ if (error != GLES20.GL_NO_ERROR) {
+ String errorString = GLU.gluErrorString(error);
+ if (errorString == null) {
+ errorString = GLUtils.getEGLErrorString(error);
+ }
+ String message = glOperation + " caused GL error 0x" + Integer.toHexString(error) +
+ ": " + errorString;
+ Log.e(TAG, message);
+ throw new RuntimeException(message);
+ }
+ }
+
+ /**
+ * Compiles an OpenGL shader.
+ *
+ * @param type {@link GLES20#GL_VERTEX_SHADER} or {@link GLES20#GL_FRAGMENT_SHADER}
+ * @param shaderCode string containing the shader source code
+ * @return ID for the shader
+ */
+ private static int loadShader(int type, String shaderCode){
+ // Create a vertex or fragment shader.
+ int shader = GLES20.glCreateShader(type);
+ if (CHECK_GL_ERRORS) checkGlError("glCreateShader");
+ if (shader == 0) {
+ throw new IllegalStateException("glCreateShader failed");
+ }
+
+ // Add the source code to the shader and compile it.
+ GLES20.glShaderSource(shader, shaderCode);
+ if (CHECK_GL_ERRORS) checkGlError("glShaderSource");
+ GLES20.glCompileShader(shader);
+ if (CHECK_GL_ERRORS) checkGlError("glCompileShader");
+
+ return shader;
+ }
+
+ /** OpenGL shaders for drawing solid colored triangle lists. */
+ public static class Program {
+ /** Trivial vertex shader that transforms the input vertex by the MVP matrix. */
+ private static final String VERTEX_SHADER_CODE = "" +
+ "uniform mat4 uMvpMatrix;\n" +
+ "attribute vec4 aPosition;\n" +
+ "void main() {\n" +
+ " gl_Position = uMvpMatrix * aPosition;\n" +
+ "}\n";
+
+ /** Trivial fragment shader that draws with a fixed color. */
+ private static final String FRAGMENT_SHADER_CODE = "" +
+ "precision mediump float;\n" +
+ "uniform vec4 uColor;\n" +
+ "void main() {\n" +
+ " gl_FragColor = uColor;\n" +
+ "}\n";
+
+ /** ID OpenGL uses to identify this program. */
+ private final int mProgramId;
+
+ /** Handle for uMvpMatrix uniform in vertex shader. */
+ private final int mMvpMatrixHandle;
+
+ /** Handle for aPosition attribute in vertex shader. */
+ private final int mPositionHandle;
+
+ /** Handle for uColor uniform in fragment shader. */
+ private final int mColorHandle;
+
+ /**
+ * Creates a program to draw triangle lists. For optimal drawing efficiency, one program
+ * should be used for all triangle lists being drawn.
+ */
+ public Program() {
+ // Prepare shaders.
+ int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER_CODE);
+ int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER_CODE);
+
+ // Create empty OpenGL Program.
+ mProgramId = GLES20.glCreateProgram();
+ if (CHECK_GL_ERRORS) checkGlError("glCreateProgram");
+ if (mProgramId == 0) {
+ throw new IllegalStateException("glCreateProgram failed");
+ }
+
+ // Add the shaders to the program.
+ GLES20.glAttachShader(mProgramId, vertexShader);
+ if (CHECK_GL_ERRORS) checkGlError("glAttachShader");
+ GLES20.glAttachShader(mProgramId, fragmentShader);
+ if (CHECK_GL_ERRORS) checkGlError("glAttachShader");
+
+ // Link the program so it can be executed.
+ GLES20.glLinkProgram(mProgramId);
+ if (CHECK_GL_ERRORS) checkGlError("glLinkProgram");
+
+ // Get a handle to the uMvpMatrix uniform in the vertex shader.
+ mMvpMatrixHandle = GLES20.glGetUniformLocation(mProgramId, "uMvpMatrix");
+ if (CHECK_GL_ERRORS) checkGlError("glGetUniformLocation");
+
+ // Get a handle to the vertex shader's aPosition attribute.
+ mPositionHandle = GLES20.glGetAttribLocation(mProgramId, "aPosition");
+ if (CHECK_GL_ERRORS) checkGlError("glGetAttribLocation");
+
+ // Enable vertex array (VBO).
+ GLES20.glEnableVertexAttribArray(mPositionHandle);
+ if (CHECK_GL_ERRORS) checkGlError("glEnableVertexAttribArray");
+
+ // Get a handle to fragment shader's uColor uniform.
+ mColorHandle = GLES20.glGetUniformLocation(mProgramId, "uColor");
+ if (CHECK_GL_ERRORS) checkGlError("glGetUniformLocation");
+ }
+
+ /**
+ * Tells OpenGL to use this program. Call this method before drawing a sequence of
+ * triangle lists.
+ */
+ public void use() {
+ GLES20.glUseProgram(mProgramId);
+ if (CHECK_GL_ERRORS) checkGlError("glUseProgram");
+ }
+
+ /** Sends the given MVP matrix, vertex data, and color to OpenGL. */
+ public void bind(float[] mvpMatrix, FloatBuffer vertexBuffer, float[] color) {
+ // Pass the MVP matrix to OpenGL.
+ GLES20.glUniformMatrix4fv(mMvpMatrixHandle, 1 /* count */, false /* transpose */,
+ mvpMatrix, 0 /* offset */);
+ if (CHECK_GL_ERRORS) checkGlError("glUniformMatrix4fv");
+
+ // Pass the VBO with the triangle list's vertices to OpenGL.
+ GLES20.glEnableVertexAttribArray(mPositionHandle);
+ if (CHECK_GL_ERRORS) checkGlError("glEnableVertexAttribArray");
+ GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT,
+ false /* normalized */, VERTEX_STRIDE, vertexBuffer);
+ if (CHECK_GL_ERRORS) checkGlError("glVertexAttribPointer");
+
+ // Pass the triangle list's color to OpenGL.
+ GLES20.glUniform4fv(mColorHandle, 1 /* count */, color, 0 /* offset */);
+ if (CHECK_GL_ERRORS) checkGlError("glUniform4fv");
+ }
+ }
+}
diff --git a/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/SweepWatchFaceService.java b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/SweepWatchFaceService.java
new file mode 100644
index 000000000..44e9569f5
--- /dev/null
+++ b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/SweepWatchFaceService.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.wearable.watchface;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.support.wearable.watchface.CanvasWatchFaceService;
+import android.support.wearable.watchface.WatchFaceService;
+import android.support.wearable.watchface.WatchFaceStyle;
+import android.text.format.Time;
+import android.util.Log;
+import android.view.SurfaceHolder;
+
+import java.util.TimeZone;
+
+/**
+ * Sample analog watch face with a sweep second hand. In ambient mode, the second hand isn't shown.
+ * On devices with low-bit ambient mode, the hands are drawn without anti-aliasing in ambient mode.
+ * The watch face is drawn with less contrast in mute mode.
+ *
+ * {@link AnalogWatchFaceService} is similar but has a ticking second hand.
+ */
+public class SweepWatchFaceService extends CanvasWatchFaceService {
+ private static final String TAG = "SweepWatchFaceService";
+
+ @Override
+ public Engine onCreateEngine() {
+ return new Engine();
+ }
+
+ private class Engine extends CanvasWatchFaceService.Engine {
+ Paint mHourPaint;
+ Paint mMinutePaint;
+ Paint mSecondPaint;
+ Paint mTickPaint;
+ boolean mMute;
+ Time mTime;
+
+ final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mTime.clear(intent.getStringExtra("time-zone"));
+ mTime.setToNow();
+ }
+ };
+ 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(SweepWatchFaceService.this)
+ .setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT)
+ .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
+ .setShowSystemUiTime(false)
+ .build());
+
+ Resources resources = SweepWatchFaceService.this.getResources();
+ Drawable backgroundDrawable = resources.getDrawable(R.drawable.bg);
+ 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);
+
+ mTime = new Time();
+ }
+
+ @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();
+ }
+
+ @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) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "onDraw");
+ }
+ long now = System.currentTimeMillis();
+ mTime.set(now);
+ int milliseconds = (int) (now % 1000);
+
+ 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 = (float) (tickIndex * Math.PI * 2 / 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 = mTime.second + milliseconds / 1000f;
+ float secRot = seconds / 30f * (float) Math.PI;
+ int minutes = mTime.minute;
+ float minRot = minutes / 30f * (float) Math.PI;
+ float hrRot = ((mTime.hour + (minutes / 60f)) / 6f ) * (float) Math.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);
+
+ // Draw every frame as long as we're visible and in interactive mode.
+ if (isVisible() && !isInAmbientMode()) {
+ invalidate();
+ }
+ }
+
+ @Override
+ public void onVisibilityChanged(boolean visible) {
+ super.onVisibilityChanged(visible);
+
+ if (visible) {
+ registerReceiver();
+
+ // Update time zone in case it changed while we weren't visible.
+ mTime.clear(TimeZone.getDefault().getID());
+ mTime.setToNow();
+
+ invalidate();
+ } else {
+ unregisterReceiver();
+ }
+ }
+
+ private void registerReceiver() {
+ if (mRegisteredTimeZoneReceiver) {
+ return;
+ }
+ mRegisteredTimeZoneReceiver = true;
+ IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
+ SweepWatchFaceService.this.registerReceiver(mTimeZoneReceiver, filter);
+ }
+
+ private void unregisterReceiver() {
+ if (!mRegisteredTimeZoneReceiver) {
+ return;
+ }
+ mRegisteredTimeZoneReceiver = false;
+ SweepWatchFaceService.this.unregisterReceiver(mTimeZoneReceiver);
+ }
+ }
+}
diff --git a/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/TiltWatchFaceService.java b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/TiltWatchFaceService.java
new file mode 100644
index 000000000..6dd01b000
--- /dev/null
+++ b/samples/browseable/WatchFace/Wearable/src/com.example.android.wearable.watchface/TiltWatchFaceService.java
@@ -0,0 +1,478 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.example.android.wearable.watchface;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.opengl.GLES20;
+import android.opengl.Matrix;
+import android.support.wearable.watchface.Gles2WatchFaceService;
+import android.support.wearable.watchface.WatchFaceStyle;
+import android.text.format.Time;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.SurfaceHolder;
+
+import java.util.TimeZone;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Sample watch face using OpenGL. The watch face is rendered using
+ * {@link Gles2ColoredTriangleList}s. The camera moves around in interactive mode and stops moving
+ * when the watch enters ambient mode.
+ */
+public class TiltWatchFaceService extends Gles2WatchFaceService {
+
+ private static final String TAG = "TiltWatchFaceService";
+
+ /** Expected frame rate in interactive mode. */
+ private static final long FPS = 60;
+
+ /** How long each frame is displayed at expected frame rate. */
+ private static final long FRAME_PERIOD_MS = TimeUnit.SECONDS.toMillis(1) / FPS;
+
+ @Override
+ public Engine onCreateEngine() {
+ return new Engine();
+ }
+
+ private class Engine extends Gles2WatchFaceService.Engine {
+ /** Cycle time before the camera motion repeats. */
+ private static final long CYCLE_PERIOD_SECONDS = 5;
+
+ /** Number of camera angles to precompute. */
+ private final int mNumCameraAngles = (int) (CYCLE_PERIOD_SECONDS * FPS);
+
+ /** Projection transformation matrix. Converts from 3D to 2D. */
+ private final float[] mProjectionMatrix = new float[16];
+
+ /**
+ * View transformation matrices to use in interactive mode. Converts from world to camera-
+ * relative coordinates. One matrix per camera position.
+ */
+ private final float[][] mViewMatrices = new float[mNumCameraAngles][16];
+
+ /** The view transformation matrix to use in ambient mode */
+ private final float[] mAmbientViewMatrix = new float[16];
+
+ /**
+ * Model transformation matrices. Converts from model-relative coordinates to world
+ * coordinates. One matrix per degree of rotation.
+ */
+ private final float[][] mModelMatrices = new float[360][16];
+
+ /**
+ * Products of {@link #mViewMatrices} and {@link #mProjectionMatrix}. One matrix per camera
+ * position.
+ */
+ private final float[][] mVpMatrices = new float[mNumCameraAngles][16];
+
+ /** The product of {@link #mAmbientViewMatrix} and {@link #mProjectionMatrix} */
+ private final float[] mAmbientVpMatrix = new float[16];
+
+ /**
+ * Product of {@link #mModelMatrices}, {@link #mViewMatrices}, and
+ * {@link #mProjectionMatrix}.
+ */
+ private final float[] mMvpMatrix = new float[16];
+
+ /** Triangles for the 4 major ticks. These are grouped together to speed up rendering. */
+ private Gles2ColoredTriangleList mMajorTickTriangles;
+
+ /** Triangles for the 8 minor ticks. These are grouped together to speed up rendering. */
+ private Gles2ColoredTriangleList mMinorTickTriangles;
+
+ /** Triangle for the second hand. */
+ private Gles2ColoredTriangleList mSecondHandTriangle;
+
+ /** Triangle for the minute hand. */
+ private Gles2ColoredTriangleList mMinuteHandTriangle;
+
+ /** Triangle for the hour hand. */
+ private Gles2ColoredTriangleList mHourHandTriangle;
+
+ private Time mTime = new Time();
+
+ /** Whether we've registered {@link #mTimeZoneReceiver}. */
+ private boolean mRegisteredTimeZoneReceiver;
+
+ private final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ mTime.clear(intent.getStringExtra("time-zone"));
+ mTime.setToNow();
+ }
+ };
+
+ @Override
+ public void onCreate(SurfaceHolder surfaceHolder) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onCreate");
+ }
+ super.onCreate(surfaceHolder);
+ setWatchFaceStyle(new WatchFaceStyle.Builder(TiltWatchFaceService.this)
+ .setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT)
+ .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
+ .setStatusBarGravity(Gravity.RIGHT | Gravity.TOP)
+ .setHotwordIndicatorGravity(Gravity.LEFT | Gravity.TOP)
+ .setShowSystemUiTime(false)
+ .build());
+ }
+
+ @Override
+ public void onGlContextCreated() {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onGlContextCreated");
+ }
+ super.onGlContextCreated();
+
+ // Create program for drawing triangles.
+ Gles2ColoredTriangleList.Program triangleProgram =
+ new Gles2ColoredTriangleList.Program();
+
+ // We only draw triangles which all use the same program so we don't need to switch
+ // programs mid-frame. This means we can tell OpenGL to use this program only once
+ // rather than having to do so for each frame. This makes OpenGL draw faster.
+ triangleProgram.use();
+
+ // Create triangles for the ticks.
+ mMajorTickTriangles = createMajorTicks(triangleProgram);
+ mMinorTickTriangles = createMinorTicks(triangleProgram);
+
+ // Create triangles for the hands.
+ mSecondHandTriangle = createHand(
+ triangleProgram,
+ 0.02f /* width */,
+ 1.0f /* height */,
+ new float[]{
+ 1.0f /* red */,
+ 0.0f /* green */,
+ 0.0f /* blue */,
+ 1.0f /* alpha */
+ }
+ );
+ mMinuteHandTriangle = createHand(
+ triangleProgram,
+ 0.06f /* width */,
+ 0.8f /* height */,
+ new float[]{
+ 0.7f /* red */,
+ 0.7f /* green */,
+ 0.7f /* blue */,
+ 1.0f /* alpha */
+ }
+ );
+ mHourHandTriangle = createHand(
+ triangleProgram,
+ 0.1f /* width */,
+ 0.5f /* height */,
+ new float[]{
+ 0.9f /* red */,
+ 0.9f /* green */,
+ 0.9f /* blue */,
+ 1.0f /* alpha */
+ }
+ );
+
+ // Precompute the clock angles.
+ for (int i = 0; i < mModelMatrices.length; ++i) {
+ Matrix.setRotateM(mModelMatrices[i], 0, i, 0, 0, 1);
+ }
+
+ // Precompute the camera angles.
+ for (int i = 0; i < mNumCameraAngles; ++i) {
+ // Set the camera position (View matrix). When active, move the eye around to show
+ // off that this is 3D.
+ final float cameraAngle = (float) (((float) i) / mNumCameraAngles * 2 * Math.PI);
+ final float eyeX = (float) Math.cos(cameraAngle);
+ final float eyeY = (float) Math.sin(cameraAngle);
+ Matrix.setLookAtM(mViewMatrices[i],
+ 0, // dest index
+ eyeX, eyeY, -3, // eye
+ 0, 0, 0, // center
+ 0, 1, 0); // up vector
+ }
+
+ Matrix.setLookAtM(mAmbientViewMatrix,
+ 0, // dest index
+ 0, 0, -3, // eye
+ 0, 0, 0, // center
+ 0, 1, 0); // up vector
+ }
+
+ @Override
+ public void onGlSurfaceCreated(int width, int height) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onGlSurfaceCreated: " + width + " x " + height);
+ }
+ super.onGlSurfaceCreated(width, height);
+
+ // Update the projection matrix based on the new aspect ratio.
+ final float aspectRatio = (float) width / height;
+ Matrix.frustumM(mProjectionMatrix,
+ 0 /* offset */,
+ -aspectRatio /* left */,
+ aspectRatio /* right */,
+ -1 /* bottom */,
+ 1 /* top */,
+ 2 /* near */,
+ 7 /* far */);
+
+ // Precompute the products of Projection and View matrices for each camera angle.
+ for (int i = 0; i < mNumCameraAngles; ++i) {
+ Matrix.multiplyMM(mVpMatrices[i], 0, mProjectionMatrix, 0, mViewMatrices[i], 0);
+ }
+
+ Matrix.multiplyMM(mAmbientVpMatrix, 0, mProjectionMatrix, 0, mAmbientViewMatrix, 0);
+ }
+
+ /**
+ * Creates a triangle for a hand on the watch face.
+ *
+ * @param program program for drawing triangles
+ * @param width width of base of triangle
+ * @param length length of triangle
+ * @param color color in RGBA order, each in the range [0, 1]
+ */
+ private Gles2ColoredTriangleList createHand(Gles2ColoredTriangleList.Program program,
+ float width, float length, float[] color) {
+ // Create the data for the VBO.
+ float[] triangleCoords = new float[]{
+ // in counterclockwise order:
+ 0, length, 0, // top
+ -width / 2, 0, 0, // bottom left
+ width / 2, 0, 0 // bottom right
+ };
+ return new Gles2ColoredTriangleList(program, triangleCoords, color);
+ }
+
+ /**
+ * Creates a triangle list for the major ticks on the watch face.
+ *
+ * @param program program for drawing triangles
+ */
+ private Gles2ColoredTriangleList createMajorTicks(
+ Gles2ColoredTriangleList.Program program) {
+ // Create the data for the VBO.
+ float[] trianglesCoords = new float[9 * 4];
+ for (int i = 0; i < 4; i++) {
+ float[] triangleCoords = getMajorTickTriangleCoords(i);
+ System.arraycopy(triangleCoords, 0, trianglesCoords, i * 9, triangleCoords.length);
+ }
+
+ return new Gles2ColoredTriangleList(program, trianglesCoords,
+ new float[]{
+ 1.0f /* red */,
+ 1.0f /* green */,
+ 1.0f /* blue */,
+ 1.0f /* alpha */
+ }
+ );
+ }
+
+ /**
+ * Creates a triangle list for the minor ticks on the watch face.
+ *
+ * @param program program for drawing triangles
+ */
+ private Gles2ColoredTriangleList createMinorTicks(
+ Gles2ColoredTriangleList.Program program) {
+ // Create the data for the VBO.
+ float[] trianglesCoords = new float[9 * (12 - 4)];
+ int index = 0;
+ for (int i = 0; i < 12; i++) {
+ if (i % 3 == 0) {
+ // This is where a major tick goes, so skip it.
+ continue;
+ }
+ float[] triangleCoords = getMinorTickTriangleCoords(i);
+ System.arraycopy(triangleCoords, 0, trianglesCoords, index, triangleCoords.length);
+ index += 9;
+ }
+
+ return new Gles2ColoredTriangleList(program, trianglesCoords,
+ new float[]{
+ 0.5f /* red */,
+ 0.5f /* green */,
+ 0.5f /* blue */,
+ 1.0f /* alpha */
+ }
+ );
+ }
+
+ private float[] getMajorTickTriangleCoords(int index) {
+ return getTickTriangleCoords(0.03f /* width */, 0.09f /* length */,
+ index * 360 / 4 /* angleDegrees */);
+ }
+
+ private float[] getMinorTickTriangleCoords(int index) {
+ return getTickTriangleCoords(0.02f /* width */, 0.06f /* length */,
+ index * 360 / 12 /* angleDegrees */);
+ }
+
+ private float[] getTickTriangleCoords(float width, float length, int angleDegrees) {
+ // Create the data for the VBO.
+ float[] coords = new float[]{
+ // in counterclockwise order:
+ 0, 1, 0, // top
+ width / 2, length + 1, 0, // bottom left
+ -width / 2, length + 1, 0 // bottom right
+ };
+
+ rotateCoords(coords, angleDegrees);
+ return coords;
+ }
+
+ /**
+ * Destructively rotates the given coordinates in the XY plane about the origin by the given
+ * angle.
+ *
+ * @param coords flattened 3D coordinates
+ * @param angleDegrees angle in degrees clockwise when viewed from negative infinity on the
+ * Z axis
+ */
+ private void rotateCoords(float[] coords, int angleDegrees) {
+ double angleRadians = Math.toRadians(angleDegrees);
+ double cos = Math.cos(angleRadians);
+ double sin = Math.sin(angleRadians);
+ for (int i = 0; i < coords.length; i += 3) {
+ float x = coords[i];
+ float y = coords[i + 1];
+ coords[i] = (float) (cos * x - sin * y);
+ coords[i + 1] = (float) (sin * x + cos * y);
+ }
+ }
+
+ @Override
+ public void onAmbientModeChanged(boolean inAmbientMode) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onAmbientModeChanged: " + inAmbientMode);
+ }
+ super.onAmbientModeChanged(inAmbientMode);
+ invalidate();
+ }
+
+ @Override
+ public void onVisibilityChanged(boolean visible) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onVisibilityChanged: " + visible);
+ }
+ super.onVisibilityChanged(visible);
+ if (visible) {
+ registerReceiver();
+
+ // Update time zone in case it changed while we were detached.
+ mTime.clear(TimeZone.getDefault().getID());
+ mTime.setToNow();
+
+ invalidate();
+ } else {
+ unregisterReceiver();
+ }
+ }
+
+ private void registerReceiver() {
+ if (mRegisteredTimeZoneReceiver) {
+ return;
+ }
+ mRegisteredTimeZoneReceiver = true;
+ IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
+ TiltWatchFaceService.this.registerReceiver(mTimeZoneReceiver, filter);
+ }
+
+ private void unregisterReceiver() {
+ if (!mRegisteredTimeZoneReceiver) {
+ return;
+ }
+ mRegisteredTimeZoneReceiver = false;
+ TiltWatchFaceService.this.unregisterReceiver(mTimeZoneReceiver);
+ }
+
+ @Override
+ public void onTimeTick() {
+ super.onTimeTick();
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onTimeTick: ambient = " + isInAmbientMode());
+ }
+ invalidate();
+ }
+
+ @Override
+ public void onDraw() {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "onDraw");
+ }
+ super.onDraw();
+ final float[] vpMatrix;
+
+ // Draw background color and select the appropriate view projection matrix. The
+ // background should always be black in ambient mode. The view projection matrix used is
+ // overhead in ambient. In interactive mode, it's tilted depending on the current time.
+ if (isInAmbientMode()) {
+ GLES20.glClearColor(0, 0, 0, 1);
+ vpMatrix = mAmbientVpMatrix;
+ } else {
+ GLES20.glClearColor(0.5f, 0.2f, 0.2f, 1);
+ final int cameraIndex =
+ (int) ((System.currentTimeMillis() / FRAME_PERIOD_MS) % mNumCameraAngles);
+ vpMatrix = mVpMatrices[cameraIndex];
+ }
+ GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
+
+ // Compute angle indices for the three hands.
+ mTime.setToNow();
+ final int secIndex = mTime.second * 360 / 60;
+ final int minIndex = mTime.minute * 360 / 60;
+ final int hoursIndex = (mTime.hour % 12) * 360 / 12 + mTime.minute * 360 / 60 / 12;
+
+ // Draw triangles from back to front. Don't draw the second hand in ambient mode.
+ {
+ // Combine the model matrix with the projection and camera view.
+ Matrix.multiplyMM(mMvpMatrix, 0, vpMatrix, 0, mModelMatrices[hoursIndex], 0);
+
+ // Draw the triangle.
+ mHourHandTriangle.draw(mMvpMatrix);
+ }
+ {
+ // Combine the model matrix with the projection and camera view.
+ Matrix.multiplyMM(mMvpMatrix, 0, vpMatrix, 0, mModelMatrices[minIndex], 0);
+
+ // Draw the triangle.
+ mMinuteHandTriangle.draw(mMvpMatrix);
+ }
+ if (!isInAmbientMode()) {
+ // Combine the model matrix with the projection and camera view.
+ Matrix.multiplyMM(mMvpMatrix, 0, vpMatrix, 0, mModelMatrices[secIndex], 0);
+
+ // Draw the triangle.
+ mSecondHandTriangle.draw(mMvpMatrix);
+ }
+ {
+ // Draw the major and minor ticks.
+ mMajorTickTriangles.draw(vpMatrix);
+ mMinorTickTriangles.draw(vpMatrix);
+ }
+
+ // Draw every frame as long as we're visible and in interactive mode.
+ if (isVisible() && !isInAmbientMode()) {
+ invalidate();
+ }
+ }
+ }
+}
diff --git a/samples/browseable/WatchFace/_index.jd b/samples/browseable/WatchFace/_index.jd
new file mode 100644
index 000000000..fcba857b8
--- /dev/null
+++ b/samples/browseable/WatchFace/_index.jd
@@ -0,0 +1,12 @@
+page.tags="WatchFace"
+sample.group=Wearable
+@jd:body
+
+
+
+This sample demonstrates how to create watch faces for android wear and includes a phone app
+and a wearable app. The wearable app has a variety of watch faces including analog, digital,
+opengl, calendar, etc. It also includes a watch-side configuration example. The phone app
+includes a phone-side configuration example.
+
+
+ *
+ */
+ private static final int NUM_COLOR_COMPONENTS = 4;
+
+ /** Shaders to render this triangle list. */
+ private final Program mProgram;
+
+ /** The VBO containing the vertex coordinates. */
+ private final FloatBuffer mVertexBuffer;
+
+ /**
+ * Color of this triangle list represented as an array of floats in the range [0, 1] in RGBA
+ * order.
+ */
+ private final float mColor[];
+
+ /** Number of coordinates in this triangle list. */
+ private final int mNumCoords;
+
+ /**
+ * Creates a Gles2ColoredTriangleList to draw a triangle list with the given vertices and color.
+ *
+ * @param program program for drawing triangles
+ * @param triangleCoords flat array of 3D coordinates of triangle vertices in counterclockwise
+ * order
+ * @param color color in RGBA order, each in the range [0, 1]
+ */
+ public Gles2ColoredTriangleList(Program program, float[] triangleCoords, float[] color) {
+ if (triangleCoords.length % (VERTICE_PER_TRIANGLE * COORDS_PER_VERTEX) != 0) {
+ throw new IllegalArgumentException("must be multiple"
+ + " of VERTICE_PER_TRIANGLE * COORDS_PER_VERTEX coordinates");
+ }
+ if (color.length != NUM_COLOR_COMPONENTS) {
+ throw new IllegalArgumentException("wrong number of color components");
+ }
+ mProgram = program;
+ mColor = color;
+
+ ByteBuffer bb = ByteBuffer.allocateDirect(triangleCoords.length * BYTES_PER_FLOAT);
+
+ // Use the device hardware's native byte order.
+ bb.order(ByteOrder.nativeOrder());
+
+ // Create a FloatBuffer that wraps the ByteBuffer.
+ mVertexBuffer = bb.asFloatBuffer();
+
+ // Add the coordinates to the FloatBuffer.
+ mVertexBuffer.put(triangleCoords);
+
+ // Go back to the start for reading.
+ mVertexBuffer.position(0);
+
+ mNumCoords = triangleCoords.length / COORDS_PER_VERTEX;
+ }
+
+ /**
+ * Draws this triangle list using OpenGL commands.
+ *
+ * @param mvpMatrix the Model View Project matrix to draw this triangle list
+ */
+ public void draw(float[] mvpMatrix) {
+ // Pass the MVP matrix, vertex data, and color to OpenGL.
+ mProgram.bind(mvpMatrix, mVertexBuffer, mColor);
+
+ // Draw the triangle list.
+ GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, mNumCoords);
+ if (CHECK_GL_ERRORS) checkGlError("glDrawArrays");
+ }
+
+ /**
+ * Checks if any of the GL calls since the last time this method was called set an error
+ * condition. Call this method immediately after calling a GL method. Pass the name of the GL
+ * operation. For example:
+ *
+ *
+ * mColorHandle = GLES20.glGetUniformLocation(mProgram, "uColor");
+ * MyGLRenderer.checkGlError("glGetUniformLocation");
+ *
+ * If the operation is not successful, the check throws an exception.
+ *
+ *