Home | History | Annotate | Download | only in com.example.android.wearable.watchface
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.example.android.wearable.watchface;
     18 
     19 import android.content.BroadcastReceiver;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.content.IntentFilter;
     23 import android.opengl.GLES20;
     24 import android.opengl.Matrix;
     25 import android.support.wearable.watchface.Gles2WatchFaceService;
     26 import android.support.wearable.watchface.WatchFaceStyle;
     27 import android.util.Log;
     28 import android.view.Gravity;
     29 import android.view.SurfaceHolder;
     30 
     31 import java.util.Calendar;
     32 import java.util.TimeZone;
     33 import java.util.concurrent.TimeUnit;
     34 
     35 /**
     36  * Sample watch face using OpenGL. The watch face is rendered using
     37  * {@link Gles2ColoredTriangleList}s. The camera moves around in interactive mode and stops moving
     38  * when the watch enters ambient mode.
     39  */
     40 public class OpenGLWatchFaceService extends Gles2WatchFaceService {
     41 
     42     private static final String TAG = "OpenGLWatchFaceService";
     43 
     44     /** Expected frame rate in interactive mode. */
     45     private static final long FPS = 60;
     46 
     47     /** Z distance from the camera to the watchface. */
     48     private static final float EYE_Z = -2.3f;
     49 
     50     /** How long each frame is displayed at expected frame rate. */
     51     private static final long FRAME_PERIOD_MS = TimeUnit.SECONDS.toMillis(1) / FPS;
     52 
     53     @Override
     54     public Engine onCreateEngine() {
     55         return new Engine();
     56     }
     57 
     58     private class Engine extends Gles2WatchFaceService.Engine {
     59         /** Cycle time before the camera motion repeats. */
     60         private static final long CYCLE_PERIOD_SECONDS = 5;
     61 
     62         /** Number of camera angles to precompute. */
     63         private final int mNumCameraAngles = (int) (CYCLE_PERIOD_SECONDS * FPS);
     64 
     65         /** Projection transformation matrix. Converts from 3D to 2D. */
     66         private final float[] mProjectionMatrix = new float[16];
     67 
     68         /**
     69          * View transformation matrices to use in interactive mode. Converts from world to camera-
     70          * relative coordinates. One matrix per camera position.
     71          */
     72         private final float[][] mViewMatrices = new float[mNumCameraAngles][16];
     73 
     74         /** The view transformation matrix to use in ambient mode */
     75         private final float[] mAmbientViewMatrix = new float[16];
     76 
     77         /**
     78          * Model transformation matrices. Converts from model-relative coordinates to world
     79          * coordinates. One matrix per degree of rotation.
     80          */
     81         private final float[][] mModelMatrices = new float[360][16];
     82 
     83         /**
     84          * Products of {@link #mViewMatrices} and {@link #mProjectionMatrix}. One matrix per camera
     85          * position.
     86          */
     87         private final float[][] mVpMatrices = new float[mNumCameraAngles][16];
     88 
     89         /** The product of {@link #mAmbientViewMatrix} and {@link #mProjectionMatrix} */
     90         private final float[] mAmbientVpMatrix = new float[16];
     91 
     92         /**
     93          * Product of {@link #mModelMatrices}, {@link #mViewMatrices}, and
     94          * {@link #mProjectionMatrix}.
     95          */
     96         private final float[] mMvpMatrix = new float[16];
     97 
     98         /** Triangles for the 4 major ticks. These are grouped together to speed up rendering. */
     99         private Gles2ColoredTriangleList mMajorTickTriangles;
    100 
    101         /** Triangles for the 8 minor ticks. These are grouped together to speed up rendering. */
    102         private Gles2ColoredTriangleList mMinorTickTriangles;
    103 
    104         /** Triangle for the second hand. */
    105         private Gles2ColoredTriangleList mSecondHandTriangle;
    106 
    107         /** Triangle for the minute hand. */
    108         private Gles2ColoredTriangleList mMinuteHandTriangle;
    109 
    110         /** Triangle for the hour hand. */
    111         private Gles2ColoredTriangleList mHourHandTriangle;
    112 
    113         private Calendar mCalendar = Calendar.getInstance();
    114 
    115         /** Whether we've registered {@link #mTimeZoneReceiver}. */
    116         private boolean mRegisteredTimeZoneReceiver;
    117 
    118         private final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
    119             @Override
    120             public void onReceive(Context context, Intent intent) {
    121                 mCalendar.setTimeZone(TimeZone.getDefault());
    122                 invalidate();
    123             }
    124         };
    125 
    126         @Override
    127         public void onCreate(SurfaceHolder surfaceHolder) {
    128             if (Log.isLoggable(TAG, Log.DEBUG)) {
    129                 Log.d(TAG, "onCreate");
    130             }
    131             super.onCreate(surfaceHolder);
    132             setWatchFaceStyle(new WatchFaceStyle.Builder(OpenGLWatchFaceService.this)
    133                     .setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT)
    134                     .setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
    135                     .setStatusBarGravity(Gravity.RIGHT | Gravity.TOP)
    136                     .setHotwordIndicatorGravity(Gravity.LEFT | Gravity.TOP)
    137                     .setShowSystemUiTime(false)
    138                     .build());
    139         }
    140 
    141         @Override
    142         public void onGlContextCreated() {
    143             if (Log.isLoggable(TAG, Log.DEBUG)) {
    144                 Log.d(TAG, "onGlContextCreated");
    145             }
    146             super.onGlContextCreated();
    147 
    148             // Create program for drawing triangles.
    149             Gles2ColoredTriangleList.Program triangleProgram =
    150                     new Gles2ColoredTriangleList.Program();
    151 
    152             // We only draw triangles which all use the same program so we don't need to switch
    153             // programs mid-frame. This means we can tell OpenGL to use this program only once
    154             // rather than having to do so for each frame. This makes OpenGL draw faster.
    155             triangleProgram.use();
    156 
    157             // Create triangles for the ticks.
    158             mMajorTickTriangles = createMajorTicks(triangleProgram);
    159             mMinorTickTriangles = createMinorTicks(triangleProgram);
    160 
    161             // Create triangles for the hands.
    162             mSecondHandTriangle = createHand(
    163                     triangleProgram,
    164                     0.02f /* width */,
    165                     1.0f /* height */,
    166                     new float[]{
    167                             1.0f /* red */,
    168                             0.0f /* green */,
    169                             0.0f /* blue */,
    170                             1.0f /* alpha */
    171                     }
    172             );
    173             mMinuteHandTriangle = createHand(
    174                     triangleProgram,
    175                     0.06f /* width */,
    176                     1f /* height */,
    177                     new float[]{
    178                             0.7f /* red */,
    179                             0.7f /* green */,
    180                             0.7f /* blue */,
    181                             1.0f /* alpha */
    182                     }
    183             );
    184             mHourHandTriangle = createHand(
    185                     triangleProgram,
    186                     0.1f /* width */,
    187                     0.6f /* height */,
    188                     new float[]{
    189                             0.9f /* red */,
    190                             0.9f /* green */,
    191                             0.9f /* blue */,
    192                             1.0f /* alpha */
    193                     }
    194             );
    195 
    196             // Precompute the clock angles.
    197             for (int i = 0; i < mModelMatrices.length; ++i) {
    198                 Matrix.setRotateM(mModelMatrices[i], 0, i, 0, 0, 1);
    199             }
    200 
    201             // Precompute the camera angles.
    202             for (int i = 0; i < mNumCameraAngles; ++i) {
    203                 // Set the camera position (View matrix). When active, move the eye around to show
    204                 // off that this is 3D.
    205                 final float cameraAngle = (float) (((float) i) / mNumCameraAngles * 2 * Math.PI);
    206                 final float eyeX = (float) Math.cos(cameraAngle);
    207                 final float eyeY = (float) Math.sin(cameraAngle);
    208                 Matrix.setLookAtM(mViewMatrices[i],
    209                         0, // dest index
    210                         eyeX, eyeY, EYE_Z, // eye
    211                         0, 0, 0, // center
    212                         0, 1, 0); // up vector
    213             }
    214 
    215             Matrix.setLookAtM(mAmbientViewMatrix,
    216                     0, // dest index
    217                     0, 0, EYE_Z, // eye
    218                     0, 0, 0, // center
    219                     0, 1, 0); // up vector
    220         }
    221 
    222         @Override
    223         public void onGlSurfaceCreated(int width, int height) {
    224             if (Log.isLoggable(TAG, Log.DEBUG)) {
    225                 Log.d(TAG, "onGlSurfaceCreated: " + width + " x " + height);
    226             }
    227             super.onGlSurfaceCreated(width, height);
    228 
    229             // Update the projection matrix based on the new aspect ratio.
    230             final float aspectRatio = (float) width / height;
    231             Matrix.frustumM(mProjectionMatrix,
    232                     0 /* offset */,
    233                     -aspectRatio /* left */,
    234                     aspectRatio /* right */,
    235                     -1 /* bottom */,
    236                     1 /* top */,
    237                     2 /* near */,
    238                     7 /* far */);
    239 
    240             // Precompute the products of Projection and View matrices for each camera angle.
    241             for (int i = 0; i < mNumCameraAngles; ++i) {
    242                 Matrix.multiplyMM(mVpMatrices[i], 0, mProjectionMatrix, 0, mViewMatrices[i], 0);
    243             }
    244 
    245             Matrix.multiplyMM(mAmbientVpMatrix, 0, mProjectionMatrix, 0, mAmbientViewMatrix, 0);
    246         }
    247 
    248         /**
    249          * Creates a triangle for a hand on the watch face.
    250          *
    251          * @param program program for drawing triangles
    252          * @param width width of base of triangle
    253          * @param length length of triangle
    254          * @param color color in RGBA order, each in the range [0, 1]
    255          */
    256         private Gles2ColoredTriangleList createHand(Gles2ColoredTriangleList.Program program,
    257                 float width, float length, float[] color) {
    258             // Create the data for the VBO.
    259             float[] triangleCoords = new float[]{
    260                     // in counterclockwise order:
    261                     0, length, 0,   // top
    262                     -width / 2, 0, 0,   // bottom left
    263                     width / 2, 0, 0    // bottom right
    264             };
    265             return new Gles2ColoredTriangleList(program, triangleCoords, color);
    266         }
    267 
    268         /**
    269          * Creates a triangle list for the major ticks on the watch face.
    270          *
    271          * @param program program for drawing triangles
    272          */
    273         private Gles2ColoredTriangleList createMajorTicks(
    274                 Gles2ColoredTriangleList.Program program) {
    275             // Create the data for the VBO.
    276             float[] trianglesCoords = new float[9 * 4];
    277             for (int i = 0; i < 4; i++) {
    278                 float[] triangleCoords = getMajorTickTriangleCoords(i);
    279                 System.arraycopy(triangleCoords, 0, trianglesCoords, i * 9, triangleCoords.length);
    280             }
    281 
    282             return new Gles2ColoredTriangleList(program, trianglesCoords,
    283                     new float[]{
    284                             1.0f /* red */,
    285                             1.0f /* green */,
    286                             1.0f /* blue */,
    287                             1.0f /* alpha */
    288                     }
    289             );
    290         }
    291 
    292         /**
    293          * Creates a triangle list for the minor ticks on the watch face.
    294          *
    295          * @param program program for drawing triangles
    296          */
    297         private Gles2ColoredTriangleList createMinorTicks(
    298                 Gles2ColoredTriangleList.Program program) {
    299             // Create the data for the VBO.
    300             float[] trianglesCoords = new float[9 * (12 - 4)];
    301             int index = 0;
    302             for (int i = 0; i < 12; i++) {
    303                 if (i % 3 == 0) {
    304                     // This is where a major tick goes, so skip it.
    305                     continue;
    306                 }
    307                 float[] triangleCoords = getMinorTickTriangleCoords(i);
    308                 System.arraycopy(triangleCoords, 0, trianglesCoords, index, triangleCoords.length);
    309                 index += 9;
    310             }
    311 
    312             return new Gles2ColoredTriangleList(program, trianglesCoords,
    313                     new float[]{
    314                             0.5f /* red */,
    315                             0.5f /* green */,
    316                             0.5f /* blue */,
    317                             1.0f /* alpha */
    318                     }
    319             );
    320         }
    321 
    322         private float[] getMajorTickTriangleCoords(int index) {
    323             return getTickTriangleCoords(0.03f /* width */, 0.09f /* length */,
    324                     index * 360 / 4 /* angleDegrees */);
    325         }
    326 
    327         private float[] getMinorTickTriangleCoords(int index) {
    328             return getTickTriangleCoords(0.02f /* width */, 0.06f /* length */,
    329                     index * 360 / 12 /* angleDegrees */);
    330         }
    331 
    332         private float[] getTickTriangleCoords(float width, float length, int angleDegrees) {
    333             // Create the data for the VBO.
    334             float[] coords = new float[]{
    335                     // in counterclockwise order:
    336                     0, 1, 0,   // top
    337                     width / 2, length + 1, 0,   // bottom left
    338                     -width / 2, length + 1, 0    // bottom right
    339             };
    340 
    341             rotateCoords(coords, angleDegrees);
    342             return coords;
    343         }
    344 
    345         /**
    346          * Destructively rotates the given coordinates in the XY plane about the origin by the given
    347          * angle.
    348          *
    349          * @param coords flattened 3D coordinates
    350          * @param angleDegrees angle in degrees clockwise when viewed from negative infinity on the
    351          *                     Z axis
    352          */
    353         private void rotateCoords(float[] coords, int angleDegrees) {
    354             double angleRadians = Math.toRadians(angleDegrees);
    355             double cos = Math.cos(angleRadians);
    356             double sin = Math.sin(angleRadians);
    357             for (int i = 0; i < coords.length; i += 3) {
    358                 float x = coords[i];
    359                 float y = coords[i + 1];
    360                 coords[i] = (float) (cos * x - sin * y);
    361                 coords[i + 1] = (float) (sin * x + cos * y);
    362             }
    363         }
    364 
    365         @Override
    366         public void onAmbientModeChanged(boolean inAmbientMode) {
    367             if (Log.isLoggable(TAG, Log.DEBUG)) {
    368                 Log.d(TAG, "onAmbientModeChanged: " + inAmbientMode);
    369             }
    370             super.onAmbientModeChanged(inAmbientMode);
    371             invalidate();
    372         }
    373 
    374         @Override
    375         public void onVisibilityChanged(boolean visible) {
    376             if (Log.isLoggable(TAG, Log.DEBUG)) {
    377                 Log.d(TAG, "onVisibilityChanged: " + visible);
    378             }
    379             super.onVisibilityChanged(visible);
    380             if (visible) {
    381                 registerReceiver();
    382 
    383                 // Update time zone in case it changed while we were detached.
    384                 mCalendar.setTimeZone(TimeZone.getDefault());
    385 
    386                 invalidate();
    387             } else {
    388                 unregisterReceiver();
    389             }
    390         }
    391 
    392         private void registerReceiver() {
    393             if (mRegisteredTimeZoneReceiver) {
    394                 return;
    395             }
    396             mRegisteredTimeZoneReceiver = true;
    397             IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
    398             OpenGLWatchFaceService.this.registerReceiver(mTimeZoneReceiver, filter);
    399         }
    400 
    401         private void unregisterReceiver() {
    402             if (!mRegisteredTimeZoneReceiver) {
    403                 return;
    404             }
    405             mRegisteredTimeZoneReceiver = false;
    406             OpenGLWatchFaceService.this.unregisterReceiver(mTimeZoneReceiver);
    407         }
    408 
    409         @Override
    410         public void onTimeTick() {
    411             super.onTimeTick();
    412             if (Log.isLoggable(TAG, Log.DEBUG)) {
    413                 Log.d(TAG, "onTimeTick: ambient = " + isInAmbientMode());
    414             }
    415             invalidate();
    416         }
    417 
    418         @Override
    419         public void onDraw() {
    420             if (Log.isLoggable(TAG, Log.VERBOSE)) {
    421                 Log.v(TAG, "onDraw");
    422             }
    423             super.onDraw();
    424             final float[] vpMatrix;
    425 
    426             // Draw background color and select the appropriate view projection matrix. The
    427             // background should always be black in ambient mode. The view projection matrix used is
    428             // overhead in ambient. In interactive mode, it's tilted depending on the current time.
    429             if (isInAmbientMode()) {
    430                 GLES20.glClearColor(0, 0, 0, 1);
    431                 vpMatrix = mAmbientVpMatrix;
    432             } else {
    433                 GLES20.glClearColor(0.5f, 0.2f, 0.2f, 1);
    434                 final int cameraIndex =
    435                         (int) ((System.currentTimeMillis() / FRAME_PERIOD_MS) % mNumCameraAngles);
    436                 vpMatrix = mVpMatrices[cameraIndex];
    437             }
    438             GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
    439 
    440             // Compute angle indices for the three hands.
    441             mCalendar.setTimeInMillis(System.currentTimeMillis());
    442             float seconds =
    443                     mCalendar.get(Calendar.SECOND) + mCalendar.get(Calendar.MILLISECOND) / 1000f;
    444             float minutes = mCalendar.get(Calendar.MINUTE) + seconds / 60f;
    445             float hours = mCalendar.get(Calendar.HOUR) + minutes / 60f;
    446             final int secIndex = (int) (seconds / 60f * 360f);
    447             final int minIndex = (int) (minutes / 60f * 360f);
    448             final int hoursIndex = (int) (hours / 12f * 360f);
    449 
    450             // Draw triangles from back to front. Don't draw the second hand in ambient mode.
    451 
    452             // Combine the model matrix with the projection and camera view.
    453             Matrix.multiplyMM(mMvpMatrix, 0, vpMatrix, 0, mModelMatrices[hoursIndex], 0);
    454 
    455             // Draw the triangle.
    456             mHourHandTriangle.draw(mMvpMatrix);
    457 
    458             // Combine the model matrix with the projection and camera view.
    459             Matrix.multiplyMM(mMvpMatrix, 0, vpMatrix, 0, mModelMatrices[minIndex], 0);
    460 
    461             // Draw the triangle.
    462             mMinuteHandTriangle.draw(mMvpMatrix);
    463             if (!isInAmbientMode()) {
    464                 // Combine the model matrix with the projection and camera view.
    465                 Matrix.multiplyMM(mMvpMatrix, 0, vpMatrix, 0, mModelMatrices[secIndex], 0);
    466 
    467                 // Draw the triangle.
    468                 mSecondHandTriangle.draw(mMvpMatrix);
    469             }
    470 
    471             // Draw the major and minor ticks.
    472             mMajorTickTriangles.draw(vpMatrix);
    473             mMinorTickTriangles.draw(vpMatrix);
    474 
    475             // Draw every frame as long as we're visible and in interactive mode.
    476             if (isVisible() && !isInAmbientMode()) {
    477                 invalidate();
    478             }
    479         }
    480     }
    481 }
    482